// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3

#pragma once

#include "color_var_resolver.h"
#include "element_styles.h"
#include "imgui.h"
#include <string>
#include <unordered_map>
#include <any>
#include <filesystem>

namespace dragonx {
namespace ui {
namespace schema {

/**
 * @brief Responsive breakpoint state
 */
enum class Breakpoint {
    Compact,    // Small window (e.g., < 500px wide)
    Normal,     // Default
    Expanded    // Large window (e.g., > 900px wide)
};

/**
 * @brief Unified UI schema singleton
 *
 * Loads theme palette, globals, and per-section element styles from a single
 * TOML file. Provides merged lookups (section → globals → fallback) and
 * poll-based hot-reload.
 *
 * Usage:
 *   auto& UI = UISchema::instance();
 *   UI.loadFromFile("res/themes/ui.toml");
 *
 *   // In main loop:
 *   UI.pollForChanges();
 *   UI.applyIfDirty();
 *
 *   // Lookups:
 *   auto btn = UI.button("dialogs.about", "close-button");
 *   auto win = UI.window("dialogs.about");
 *   ImU32 col = UI.resolveColor("var(--primary)");
 */
class UISchema {
public:
    /**
     * @brief Get the singleton instance
     */
    static UISchema& instance();

    /**
     * @brief Load a unified skin file
     * @param path Path to TOML skin file (e.g., "res/themes/ui.toml")
     * @return true if loaded successfully
     */
    bool loadFromFile(const std::string& path);

    /**
     * @brief Load from a TOML string (e.g., embedded data fallback)
     * @param tomlStr Raw TOML text
     * @param label   Human-readable label for log messages
     * @return true if parsed successfully
     */
    bool loadFromString(const std::string& tomlStr, const std::string& label = "embedded");

    /**
     * @brief Merge a theme overlay on top of the currently loaded base.
     *
     * Only replaces visual properties (theme palette/elevation/images and
     * backdrop section). Layout values from the base are preserved.
     *
     * @param path Path to the overlay TOML (e.g., dark.toml, light.toml)
     * @return true if overlay was parsed and merged successfully
     */
    bool mergeOverlayFromFile(const std::string& path);

    /**
     * @brief Check if a schema file has been loaded successfully
     */
    bool isLoaded() const { return loaded_; }

    /**
     * @brief Get the path of the currently loaded file
     */
    const std::string& currentPath() const { return currentPath_; }

    /**
     * @brief Get the base layout file path (ui.toml)
     */
    const std::string& basePath() const { return basePath_; }

    /**
     * @brief Check if the base theme was loaded from an embedded string.
     * When true, reloadBase() uses the stored embedded data instead of a file.
     */
    bool hasEmbeddedBase() const { return !embeddedTomlStr_.empty(); }

    /**
     * @brief Reload the base theme (ui.toml) from file or embedded data.
     * Used by SkinManager before merging an overlay to ensure layout values
     * are always restored from the authoritative base, even on platforms
     * where ui.toml is embedded (e.g., single-file Windows distribution).
     * @return true if base was reloaded successfully
     */
    bool reloadBase();

    /**
     * @brief Get the active overlay file path (empty if no overlay)
     */
    const std::string& overlayPath() const { return overlayPath_; }

    // ====================================================================
    // Hot-reload
    // ====================================================================

    /**
     * @brief Poll for file changes (call from main loop)
     *
     * Checks file modification time every pollInterval_ seconds.
     * Sets dirty flag if file has changed.
     */
    void pollForChanges();

    /**
     * @brief Check if the schema needs re-applying
     */
    bool isDirty() const { return dirty_; }

    /**
     * @brief Re-parse and apply if file has changed
     */
    void applyIfDirty();

    /**
     * @brief Check and consume the fonts-changed flag.
     * Returns true once after a hot-reload that changed font sizes.
     */
    bool consumeFontsChanged() {
        bool v = fonts_changed_;
        fonts_changed_ = false;
        return v;
    }

    /**
     * @brief Re-apply Material Design colors from schema palette to ImGui
     *
     * Builds a ColorTheme from the current palette and applies to ImGui style.
     * Called automatically during hot-reload.
     */
    void reapplyColorsToImGui();

    /**
     * @brief Set the poll interval in seconds (default 0.5)
     */
    void setPollInterval(float seconds) { pollInterval_ = seconds; }

    // ====================================================================
    // Color resolution
    // ====================================================================

    /**
     * @brief Get the color variable resolver (for direct access)
     */
    const ColorVarResolver& colors() const { return colorResolver_; }

    /**
     * @brief Resolve a color string to ImU32
     * @param ref Color string: "var(--primary)", "#hex", "rgba(...)", etc.
     * @param fallback Value if resolution fails
     */
    ImU32 resolveColor(const std::string& ref, ImU32 fallback = IM_COL32(0,0,0,0)) const;

    // ====================================================================
    // Font resolution
    // ====================================================================

    /**
     * @brief Resolve a font name string to ImFont*
     * @param fontName e.g., "button", "button-sm", "button-lg", "h4", "body1"
     * @return ImFont* pointer, or nullptr if name is empty/unknown
     */
    ImFont* resolveFont(const std::string& fontName) const;

    // ====================================================================
    // Responsive
    // ====================================================================

    /**
     * @brief Get current breakpoint based on window/viewport size
     */
    Breakpoint currentBreakpoint() const;

    /**
     * @brief Get the breakpoint config
     */
    const BreakpointConfig& breakpoints() const { return breakpoints_; }

    // ====================================================================
    // Element lookups — merged (section → globals → C++ default)
    // ====================================================================

    /**
     * @brief Look up a button style
     * @param section Dot-separated section path: "dialogs.about", "tabs.send"
     * @param name Element name within the section: "close-button"
     * @return Merged ButtonStyle (responsive overrides already applied)
     */
    ButtonStyle button(const std::string& section, const std::string& name) const;

    /**
     * @brief Look up an input style
     */
    InputStyle input(const std::string& section, const std::string& name) const;

    /**
     * @brief Look up a label style
     */
    LabelStyle label(const std::string& section, const std::string& name) const;

    /**
     * @brief Look up a table style
     */
    TableStyle table(const std::string& section, const std::string& name) const;

    /**
     * @brief Look up a checkbox style
     */
    CheckboxStyle checkbox(const std::string& section, const std::string& name) const;

    /**
     * @brief Look up a combo style
     */
    ComboStyle combo(const std::string& section, const std::string& name) const;

    /**
     * @brief Look up a slider style
     */
    SliderStyle slider(const std::string& section, const std::string& name) const;

    /**
     * @brief Look up a window/dialog style
     * @param section Section path: "dialogs.about"
     * @param name Defaults to "window" — the window block within that section
     */
    WindowStyle window(const std::string& section, const std::string& name = "window") const;

    /**
     * @brief Look up a separator style
     */
    SeparatorStyle separator(const std::string& section, const std::string& name) const;

    /**
     * @brief Look up a DrawList custom element style
     *
     * Returns a cached const reference. The cache is invalidated on
     * hot-reload (applyIfDirty) and full loads. For missing elements
     * a static empty sentinel is returned.
     */
    const DrawElementStyle& drawElement(const std::string& section, const std::string& name) const;

    /**
     * @brief Find a raw stored element by section and name.
     * Returns an opaque pointer to the stored data (toml::table* at runtime),
     * or nullptr if not found. Consumer must cast via
     * static_cast<const toml::table*>(ptr) after including <toml++/toml.hpp>.
     */
    const void* findElement(const std::string& section, const std::string& name) const;

    // ====================================================================
    // Global defaults access
    // ====================================================================

    const ButtonStyle&    defaultButton() const { return globalButton_; }
    const InputStyle&     defaultInput() const { return globalInput_; }
    const LabelStyle&     defaultLabel() const { return globalLabel_; }
    const TableStyle&     defaultTable() const { return globalTable_; }
    const CheckboxStyle&  defaultCheckbox() const { return globalCheckbox_; }
    const ComboStyle&     defaultCombo() const { return globalCombo_; }
    const SliderStyle&    defaultSlider() const { return globalSlider_; }
    const WindowStyle&    defaultWindow() const { return globalWindow_; }
    const SeparatorStyle& defaultSeparator() const { return globalSeparator_; }

    // ====================================================================
    // Theme metadata
    // ====================================================================

    const std::string& themeName() const { return themeName_; }
    bool isDarkTheme() const { return darkTheme_; }

    /**
     * @brief Monotonically increasing generation counter.
     * Incremented on every loadFromFile / mergeOverlay / applyIfDirty.
     * Downstream caches compare against this to invalidate.
     */
    uint32_t generation() const { return generation_; }

    /**
     * @brief Get theme-specified background image path (empty = use default)
     */
    const std::string& backgroundImagePath() const { return backgroundImagePath_; }

    /**
     * @brief Get theme-specified logo image path (empty = use default)
     */
    const std::string& logoImagePath() const { return logoImagePath_; }

private:
    UISchema() = default;
    ~UISchema() = default;
    UISchema(const UISchema&) = delete;
    UISchema& operator=(const UISchema&) = delete;

    // Parse top-level sections from a TOML table (passed as opaque void*)
    void parseTheme(const void* dataObj);
    void parseGlobals(const void* dataObj);
    void parseSections(const void* dataObj, const std::string& prefix);
    void parseFlatSection(const void* dataObj, const std::string& prefix);
    void parseBreakpoints(const void* dataObj);

    // ====================================================================
    // State
    // ====================================================================

    bool loaded_ = false;
    std::string currentPath_;   ///< Path currently loaded (base or overlay for hot-reload)
    std::string basePath_;      ///< Path to the base layout file (ui.toml)
    std::string overlayPath_;   ///< Path to active overlay file (empty if base-only)
    std::string embeddedTomlStr_; ///< Raw TOML string from loadFromString (for reload)

    // Hot-reload
    float pollInterval_ = 0.5f;
    double lastPollTime_ = 0;
    std::filesystem::file_time_type lastModTime_;
    std::filesystem::file_time_type baseModTime_;   ///< Track base file changes when overlay active
    bool dirty_ = false;
    bool fonts_changed_ = false;

    // Theme
    std::string themeName_;
    bool darkTheme_ = true;
    std::string backgroundImagePath_;
    std::string logoImagePath_;
    ColorVarResolver colorResolver_;
    uint32_t generation_ = 0;   ///< bumped on every load/merge/reload

    // Breakpoints
    BreakpointConfig breakpoints_;

    // Global defaults
    ButtonStyle    globalButton_;
    InputStyle     globalInput_;
    LabelStyle     globalLabel_;
    TableStyle     globalTable_;
    CheckboxStyle  globalCheckbox_;
    ComboStyle     globalCombo_;
    SliderStyle    globalSlider_;
    WindowStyle    globalWindow_;
    SeparatorStyle globalSeparator_;

    // Per-section element storage
    // Key: "section.elementName" → stored TOML table (accessed via std::any_cast)
    // This avoids storing every possible style struct up front —
    // we only parse what's actually looked up.
    struct StoredElement {
        std::any data;  // holds toml::table at runtime
    };
    std::unordered_map<std::string, StoredElement> elements_;

    // Parsed DrawElementStyle cache — avoids repeated TOML table iteration.
    // Populated lazily on first drawElement() lookup, cleared on reload.
    mutable std::unordered_map<std::string, DrawElementStyle> styleCache_;
};

// Convenience alias
inline UISchema& UI() { return UISchema::instance(); }

} // namespace schema
} // namespace ui
} // namespace dragonx
