#pragma once
#include "thirdparty/toml.hpp"
#include "log.hpp"
#include <string>
#include <vector>
#include <typeinfo>
#include <typeindex>
#include <optional>
#define OPTIONS (*Looper::Options::options)
namespace Looper::Options {
    extern toml::table *options;
    void save_options();
    void load_options();
    extern std::optional<const char *> options_path_override;
    std::string get_options_path();
    inline bool option_set(std::string name) {
        toml::path path(name);
        auto *tmp = options;
        std::vector<std::string> components;
        for (auto component : path) {
            components.push_back((std::string)component);
        }
        std::string last_component = components[components.size() - 1];
        components.pop_back();
        for (auto component : components) {
            auto &cur_tmp = *tmp;
            if (cur_tmp.contains(component)) {
                if (!cur_tmp[component].is_table()) {
                    return false;
                } else {
                    tmp = cur_tmp[component].as_table();
                }
            } else {
                return false;
            }
        }
        if (tmp->contains(last_component)) {
            return true;
        } else {
            return false;
        }
    }
    inline void delete_option(std::string name) {
        toml::path path(name);
        auto *tmp = &OPTIONS;
        std::vector<std::string> components;
        for (auto component : path) {
            std::string component_str = (std::string)component;
            components.push_back(component_str);
        }
        while (!path.empty()) {
            if (option_set(path.str())) {
                toml::path parent_path = path.parent();
                auto &parent = OPTIONS.at(parent_path.str());
                auto last_component = path[path.size() - 1];
                if (parent.is_table()) {
                    auto &table = *parent.as_table();
                    table.erase((std::string)last_component);
                    if (!table.empty()) {
                        return;
                    }
                } else if (parent.is_array()) {
                    auto &array = *parent.as_array();
                    array.erase(array.begin() + last_component.index());
                    if (!array.empty()) {
                        return;
                    }
                }
                path = parent_path;
            }
        }
        save_options();
    }
    template<class T>
    void set_option(std::string name, T value) {
        toml::path path(name);
        auto *tmp = &OPTIONS;
        std::vector<std::string> components;
        for (auto component : path) {
            std::string component_str = (std::string)component;
            components.push_back(component_str);
        }
        auto last_component = components[components.size() - 1];
        components.pop_back();
        for (auto component : components) {
            auto &cur_tmp = *tmp;
            if (cur_tmp.contains(component)) {
                if (cur_tmp[component].is_table()) {
                    tmp = cur_tmp[component].as_table();
                } else {
                    return;
                }
            } else {
                tmp->insert_or_assign(component, toml::table());
                tmp = cur_tmp[component].as_table();
            }
        }
        tmp->insert_or_assign(last_component, value);
        save_options();
    }
    template<class T>
    inline void init_option(std::string name, T value) {
        if (!option_set(name)) {
            set_option<T>(name, value);
        }
    }
    template<class T>
    T get_option(std::string name, std::optional<T> initial_value = {}) {
        if (initial_value.has_value()) {
            init_option<T>(name, initial_value.value());
        }
        auto option = OPTIONS.at_path(name);
        return (**(option).as<T>());
    }
    template<>
    inline bool get_option<bool>(std::string name, std::optional<bool> initial_value) {
        if (initial_value.has_value()) {
            init_option<bool>(name, initial_value.value());
        }
        auto option = OPTIONS.at_path(name).as<bool>();
        if (option == nullptr) {
            return false;
        }
        return (option->get());
    }
}
#define UI_BACKEND() (Looper::Options::get_option<std::string>("ui.frontend", "imgui"))