// Demos individuales de text_editor y file_watcher (Wave 1, issue 0025).
//
// Aunque las dos primitivas estan diseñadas para componerse, en gallery se
// muestran por separado para que cada entry exhiba un solo primitivo y su
// API minima.

#include "demos.h"
#include "demo.h"

#include "core/text_editor.h"
#include "core/file_watcher.h"
#include "core/button.h"
#include "core/tokens.h"

#include <imgui.h>

#include <cerrno>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <deque>
#include <string>

namespace gallery {

// ===========================================================================
// text_editor — editor de codigo con syntax highlighting
// ===========================================================================

namespace {

const char* kSampleGLSL =
    "#version 330\n"
    "// fragment shader demo\n"
    "out vec4 frag_color;\n"
    "uniform vec2 u_resolution;\n"
    "uniform float u_time;\n"
    "\n"
    "void main() {\n"
    "    vec2 uv = gl_FragCoord.xy / u_resolution;\n"
    "    vec3 col = 0.5 + 0.5 * cos(u_time + uv.xyx + vec3(0,2,4));\n"
    "    frag_color = vec4(col, 1.0);\n"
    "}\n";

const char* kSampleSQL =
    "-- fts5 search sobre el registry\n"
    "SELECT id, kind, purity, description\n"
    "FROM functions\n"
    "WHERE id IN (\n"
    "    SELECT id FROM functions_fts\n"
    "    WHERE functions_fts MATCH 'name:slic* OR description:slic*'\n"
    ")\n"
    "ORDER BY name\n"
    "LIMIT 50;\n";

const char* kSampleCpp =
    "#include <imgui.h>\n"
    "namespace fn {\n"
    "  bool button(const char* label, ButtonVariant v) {\n"
    "    auto& tk = tokens::current();\n"
    "    ImGui::PushStyleColor(ImGuiCol_Button, tk.bg_for(v));\n"
    "    bool clicked = ImGui::Button(label);\n"
    "    ImGui::PopStyleColor();\n"
    "    return clicked;\n"
    "  }\n"
    "}\n";

struct EditorState {
    fn::TextEditorState* ed   = nullptr;
    fn::CodeLang         lang = fn::CodeLang::GLSL;
};

EditorState& editor_state() {
    static EditorState s;
    return s;
}

void ensure_editor() {
    auto& s = editor_state();
    if (!s.ed) {
        s.ed = fn::text_editor_create(s.lang);
        fn::text_editor_set_text(s.ed, kSampleGLSL);
    }
}

void apply_language(fn::CodeLang next) {
    auto& s = editor_state();
    if (next == s.lang) return;
    fn::text_editor_destroy(s.ed);
    s.ed   = fn::text_editor_create(next);
    s.lang = next;
    switch (next) {
        case fn::CodeLang::GLSL:    fn::text_editor_set_text(s.ed, kSampleGLSL); break;
        case fn::CodeLang::SQL:     fn::text_editor_set_text(s.ed, kSampleSQL);  break;
        case fn::CodeLang::Cpp:     fn::text_editor_set_text(s.ed, kSampleCpp);  break;
        case fn::CodeLang::Generic: fn::text_editor_set_text(s.ed, "");          break;
    }
}

} // namespace

void demo_text_editor() {
    using namespace fn_tokens;

    demo_header("text_editor", "v1.0.0",
        "Editor de codigo embebido en ImGui con syntax highlighting (GLSL/SQL/Cpp/Generic). "
        "Wrapper PIMPL sobre ImGuiColorTextEdit (MIT). API: create / set_text / get_text / "
        "render / is_dirty.");

    ensure_editor();
    auto& s = editor_state();

    section("language");
    {
        const char* labels[] = {"GLSL", "SQL", "Cpp", "Generic"};
        const fn::CodeLang langs[] = {
            fn::CodeLang::GLSL, fn::CodeLang::SQL, fn::CodeLang::Cpp, fn::CodeLang::Generic
        };
        for (int i = 0; i < 4; ++i) {
            if (i > 0) ImGui::SameLine();
            bool active = (s.lang == langs[i]);
            if (active) ImGui::PushStyleColor(ImGuiCol_Button, colors::primary);
            if (ImGui::Button(labels[i])) apply_language(langs[i]);
            if (active) ImGui::PopStyleColor();
        }
    }

    section("editor");
    {
        ImVec2 avail = ImGui::GetContentRegionAvail();
        float h = avail.y - 60.0f;
        if (h < 220.0f) h = 220.0f;
        fn::text_editor_render(s.ed, "##fn_text_editor_solo", ImVec2(-1, h));

        if (fn::text_editor_is_dirty(s.ed)) {
            ImGui::PushStyleColor(ImGuiCol_Text, colors::warning);
            ImGui::TextUnformatted("(modified)");
            ImGui::PopStyleColor();
            ImGui::SameLine();
            if (ImGui::Button("clear dirty##te_solo")) fn::text_editor_clear_dirty(s.ed);
        } else {
            ImGui::PushStyleColor(ImGuiCol_Text, colors::text_dim);
            ImGui::TextUnformatted("(clean)");
            ImGui::PopStyleColor();
        }
    }

    code_block(
        "auto* ed = fn::text_editor_create(fn::CodeLang::GLSL);\n"
        "fn::text_editor_set_text(ed, src);\n"
        "if (fn::text_editor_render(ed, \"##ed\", {600, 400}))\n"
        "    on_changed(fn::text_editor_get_text(ed));"
    );
}

// ===========================================================================
// file_watcher — watcher cross-platform no bloqueante
// ===========================================================================

namespace {

constexpr const char* kWatchPath = "/tmp/fn_demo.glsl";

struct WatcherDemoState {
    fn::FileWatcher* fw      = nullptr;
    bool             active  = false;
    std::string      err;
    std::deque<std::string> events;
};

WatcherDemoState& watcher_state() {
    static WatcherDemoState s;
    return s;
}

void ensure_watcher() {
    auto& s = watcher_state();
    if (!s.fw) {
        s.fw     = fn::file_watcher_create();
        s.active = fn::file_watcher_add(s.fw, kWatchPath);
        if (!s.active) s.err = fn::file_watcher_last_error(s.fw);
    }
}

const char* kind_label(fn::FileEvent::Kind k) {
    switch (k) {
        case fn::FileEvent::Modified: return "MODIFIED";
        case fn::FileEvent::Created:  return "CREATED";
        case fn::FileEvent::Deleted:  return "DELETED";
    }
    return "?";
}

void poll_and_log() {
    auto& s = watcher_state();
    if (!s.fw) return;
    auto evs = fn::file_watcher_poll(s.fw);
    for (auto& e : evs) {
        char buf[512];
        std::snprintf(buf, sizeof(buf), "[%s] %s", kind_label(e.kind), e.path.c_str());
        s.events.push_back(buf);
    }
    while (s.events.size() > 200) s.events.pop_front();
}

bool touch_demo_file(std::string& err_out) {
    FILE* f = std::fopen(kWatchPath, "a");
    if (!f) { err_out = std::strerror(errno); return false; }
    std::fprintf(f, "// touch %ld\n", (long)std::time(nullptr));
    std::fclose(f);
    return true;
}

} // namespace

void demo_file_watcher() {
    using namespace fn_tokens;

    demo_header("file_watcher", "v1.0.0",
        "Watcher de archivos cross-platform no bloqueante. Linux: inotify. Windows: "
        "ReadDirectoryChangesW. API: create / add / poll (drain) / destroy. Cap del "
        "buffer de eventos: 200.");

    ensure_watcher();
    poll_and_log();

    auto& s = watcher_state();

    section("watcher state");
    ImGui::Text("path:    %s", kWatchPath);
    ImGui::Text("active:  %s", s.active ? "yes" : "no");
    if (!s.err.empty()) {
        ImGui::PushStyleColor(ImGuiCol_Text, colors::error);
        ImGui::TextWrapped("err: %s", s.err.c_str());
        ImGui::PopStyleColor();
    }

    section("trigger events");
    {
        if (ImGui::Button("touch (append timestamp)")) {
            std::string e;
            if (!touch_demo_file(e)) s.err = "touch failed: " + e;
            else                     s.err.clear();
            // Si el archivo no existia al inicio, reintenta el add.
            if (!s.active) {
                s.active = fn::file_watcher_add(s.fw, kWatchPath);
                if (!s.active) s.err = fn::file_watcher_last_error(s.fw);
            }
        }
        ImGui::SameLine();
        if (ImGui::Button("clear events")) s.events.clear();
        ImGui::SameLine();
        ImGui::TextDisabled("(o desde otro terminal: echo hi >> %s)", kWatchPath);
    }

    section("event log");
    ImGui::Text("captured: %d", (int)s.events.size());
    ImGui::BeginChild("##fw_evlog", ImVec2(0, 0), ImGuiChildFlags_Borders);
    if (s.events.empty()) {
        ImGui::PushStyleColor(ImGuiCol_Text, colors::text_dim);
        ImGui::TextWrapped("Sin eventos. Pulsa touch o modifica el path desde otro terminal.");
        ImGui::PopStyleColor();
    } else {
        for (auto it = s.events.rbegin(); it != s.events.rend(); ++it) {
            ImGui::TextUnformatted(it->c_str());
        }
    }
    ImGui::EndChild();

    code_block(
        "auto* fw = fn::file_watcher_create();\n"
        "fn::file_watcher_add(fw, \"/tmp/foo.glsl\");\n"
        "for (auto& e : fn::file_watcher_poll(fw)) {\n"
        "    handle_event(e.path, e.kind);\n"
        "}"
    );
}

} // namespace gallery
