#pragma once

#include <cstddef>
#include <functional>

// Forward declarations para evitar incluir headers pesados aqui. Las
// definiciones reales viven en cpp/functions/core/*.h y se incluyen desde
// app_base.cpp.
namespace fn_ui {
    struct PanelToggle;
    struct LayoutCallbacks;

    // Info estatica para la ventana About. Si name != nullptr, fn::run_app
    // llama about_window_set_info(name, version, description) tras settings_load().
    struct AppAboutInfo {
        const char* name        = nullptr;
        const char* version     = nullptr;
        const char* description = nullptr;
    };

    // Config de logging. Si file_path != nullptr, fn::run_app llama
    // fn_log::logger_init(file_path, level) al inicio y fn_log::logger_close()
    // al exit. file_path se interpreta relativo al cwd (junto al ejecutable,
    // igual que app_settings.ini). Si file_path == nullptr, no se escribe a
    // disco — la ventana Logs sigue funcionando contra el buffer in-memory.
    //
    // level: 0=Debug, 1=Info, 2=Warn, 3=Error. Default Info.
    struct AppLogConfig {
        const char* file_path = nullptr;
        int         level     = 1; // fn_log::Level::Info
    };
}

namespace fn {

// ----------------------------------------------------------------------------
// Local files — separacion de archivos distribuibles vs estado local.
// ----------------------------------------------------------------------------
//
// Convencion del registry: TODA app coloca sus archivos escribibles
// (settings, DBs, layouts ImGui, caches, proyectos del usuario) bajo
// `<exe_dir>/local_files/`. Los archivos distribuibles (.exe, .ttf,
// .dll, runtime/, enrichers/) viven directos en `<exe_dir>/`.
//
// Esto mantiene la carpeta del .exe limpia para distribuir, separa
// nitidamente "lo que vino con el zip" de "lo que el PC genero", y
// facilita el reset (basta con borrar local_files/).
//
// `fn::run_app` configura `io.IniFilename = local_path("imgui.ini")` y
// `app_settings.ini` se lee/escribe desde local_files/ automaticamente.
// Cualquier archivo escribible adicional de la app debe usar
// `fn::local_path("nombre")` al construir su path.
//
// La carpeta se crea on-demand en la primera llamada a `local_dir()`.
// Si existen archivos viejos en el cwd (compat con versiones previas
// del registry), `migrate_to_local_files()` los mueve.

// Devuelve el directorio del ejecutable actual (sin trailing slash).
// "" si no se puede resolver (raro — fallback al cwd).
const char* exe_dir();

// Devuelve el path absoluto a `<exe_dir>/local_files/`. Crea la
// carpeta si no existe. Sin trailing slash.
const char* local_dir();

// Construye `<local_dir>/<name>`. El puntero retornado apunta a un
// std::string interno por-thread que permanece valido hasta la
// proxima llamada — copia el valor si vas a guardarlo.
const char* local_path(const char* name);

// Devuelve `<exe_dir>/assets/` — read-only sidecar shipped con la
// app (ttfs, enrichers, runtime Python, etc.). NO crea la carpeta;
// si no existe, la app debe fallback a buscar los assets en
// FN_CPP_ROOT u otras rutas. Sin trailing slash.
const char* asset_dir();

// Construye `<asset_dir>/<name>`. Mismo lifetime que local_path.
const char* asset_path(const char* name);

// Mueve los archivos listados de cwd o exe_dir a local_files/ si
// existen ahi pero NO existen ya en local_files/. Idempotente. Las
// apps lo llaman al iniciar para migrar instalaciones viejas.
void migrate_to_local_files(const char* const* names, std::size_t n);

// Framework metadata (auto-generated from modules/framework/module.md via
// `fn index`). About panel reads these.
const char* framework_version();
const char* framework_description();

// Modos de tema para run_app.
enum class ThemeMode {
    FnDark,    // Identidad del registry (Mantine v9 dark + indigo). DEFAULT.
    ImGuiDark, // Tema estandar de ImGui (ImGui::StyleColorsDark).
    ImGuiLight,
    None,      // No tocar el ImGuiStyle — la app lo configura.
};

struct AppConfig {
    const char* title = "fn_registry";
    int width = 1280;
    int height = 720;
    bool vsync = true;
    bool viewports = true; // Multi-viewport ON por defecto: ventanas ImGui arrastrables fuera del main window
    ThemeMode theme = ThemeMode::FnDark; // Identidad visual unificada por defecto
    float bg_r = 0.102f; // fn_tokens::colors::bg (dark.7 #1A1B1E)
    float bg_g = 0.106f;
    float bg_b = 0.118f;

    // About window. Si about.name != nullptr, run_app llama
    // fn_ui::about_window_set_info(name, version, description) tras settings_load().
    fn_ui::AppAboutInfo about{};

    // Paneles toggleables del menubar. Si panels != nullptr y panel_count > 0,
    // run_app llama fn_ui::app_menubar(panels, panel_count, layouts_cb) cada frame
    // ANTES de render_fn().
    const fn_ui::PanelToggle* panels      = nullptr;
    std::size_t               panel_count = 0;

    // Callbacks de layouts persistentes. Si layouts_cb != nullptr, run_app
    // llama fn_ui::app_menubar(panels, panel_count, layouts_cb) cada frame.
    // Si layouts_cb == nullptr y auto_layouts == true (default), run_app abre
    // un fn_ui::LayoutStorage por defecto sobre `<local_dir>/<auto_layouts_db>`,
    // genera unos LayoutCallbacks estandar (save/load/list/delete/reset),
    // los aplica al inicio de cada frame y los cierra al salir. Asi cualquier
    // app obtiene el menu Layouts gratis sin tocar codigo.
    fn_ui::LayoutCallbacks*   layouts_cb  = nullptr;

    // Auto-wiring del menu Layouts cuando layouts_cb == nullptr.
    // - true (default): run_app crea LayoutStorage interno con SQLite.
    // - false: no se crea storage. Util si la app no quiere persistencia
    //   (ej. demo headless, capture mode).
    bool auto_layouts = true;

    // Nombre del archivo SQLite (relativo a `<exe_dir>/local_files/`) usado
    // por el layout storage por defecto. Solo se consulta si layouts_cb es
    // nullptr y auto_layouts es true. Default "layouts.db".
    const char* auto_layouts_db = "layouts.db";

    // Items extra dentro del menu "View", al final tras los toggles de
    // paneles. Si view_extras != nullptr, run_app lo pasa a app_menubar.
    // El callback se invoca dentro de un BeginMenu("View") ya abierto:
    // la app llama directamente a ImGui::Separator(), MenuItem(), etc.
    // NO debe abrir/cerrar el menu View.
    std::function<bool()>     view_extras{};

    // Si true, run_app llama fn::gfx::gl_loader_init() tras crear el contexto
    // GL y antes del primer frame. Necesario para apps que llaman gl* directo
    // en Windows (en Linux es no-op).
    bool init_gl_loader = false;

    // Auto-dockspace: si true, run_app llama
    // ImGui::DockSpaceOverViewport(0, GetMainViewport(), PassthruCentralNode)
    // ANTES de render_fn() cada frame. Asi cualquier app obtiene un dockspace
    // central donde re-anclar ventanas flotantes (incl. viewports OS) sin
    // tocar su render(). Apps con layout custom (shaders_lab) o galerias
    // (primitives_gallery) pueden poner false para gestionarlo ellas.
    bool auto_dockspace = true;

    // Logging opcional. Si log.file_path != nullptr, run_app inicializa el
    // logger global antes del primer frame y lo cierra al exit. La ventana
    // "Logs..." en el menubar siempre esta disponible (lee del buffer
    // in-memory aunque no haya archivo).
    fn_ui::AppLogConfig log{};

    // Hook opcional ejecutado al inicio de cada frame (despues de
    // ImGui::NewFrame, ANTES de app_menubar y de auto-dockspace). Pensado para
    // que apps con LayoutStorage propio llamen layout_storage_apply_pending
    // en el momento correcto: ImGui requiere LoadIniSettingsFromMemory antes
    // de cualquier Begin() del frame para que las dock-nodes guardadas se
    // restauren correctamente. Si la app llama LoadIni mid-frame (dentro de
    // render_fn) las ventanas docked aparecen flotantes hasta el siguiente
    // ciclo. Default null = no-op.
    std::function<void()> pre_frame{};

    // ------------------------------------------------------------------------
    // Header badge — identidad visual en viewports secundarios (panels
    // arrastrados fuera del main window). Cuando un panel se separa, el
    // framework dibuja un pequeño cuadrado redondeado con la inicial de la
    // app (o un glyph custom) en la esquina top-left de la title bar de su
    // viewport, asi distingues de un vistazo de que app viene cada panel
    // flotante cuando tienes varias apps abiertas a la vez.
    //
    // Si todos los campos quedan por defecto, el framework auto-deriva color
    // estable desde about.name (hash -> HSV) y glyph desde la primera letra.
    // No requiere accion en la app; con solo declarar about.name ya hay
    // identidad. Apps que ya tengan icon.accent en su app.md deberian setear
    // header_badge.accent_hex con el mismo hex para coherencia visual con el
    // App Hub.
    // ------------------------------------------------------------------------
    struct AppHeaderBadge {
        // Color de fondo del badge en formato "#RRGGBB" o "RRGGBB" sRGB.
        // "" -> auto-derive desde about.name (hash estable).
        const char* accent_hex = "";

        // Glyph dibujado en blanco encima del fondo. nullptr/"" -> primera
        // letra de about.name (uppercase). Soporta cualquier UTF-8 corto
        // (1-2 chars o un TI_* macro de cpp/functions/core/icons_tabler.h).
        const char* glyph = nullptr;

        // Tamaño cuadrado del badge en pixels.
        float size_px = 18.0f;

        // Margen desde top-left del viewport.
        float margin_px = 6.0f;

        // false -> deshabilita el badge para esta app (capture mode, headless).
        bool enabled = true;
    };
    AppHeaderBadge header_badge{};
};

// Run an ImGui application. The render_fn is called every frame
// between ImGui::NewFrame() and ImGui::Render().
// Returns 0 on clean exit, 1 on error.
int run_app(AppConfig config, std::function<void()> render_fn);

// Convenience: run with default config
int run_app(std::function<void()> render_fn);

// Test-only observability hooks for the Win32 anti-jitter / Alt+RMB resize
// subclass. Counters increment monotonically across the life of the process.
// On non-Windows targets they always return 0.
namespace internal {
int sizemove_enter_count();
int alt_rmb_resize_count();
int alt_lmb_move_count();
int rbuttondown_seen_count();
void set_force_alt_for_test(bool v);
}

} // namespace fn

// ----------------------------------------------------------------------------
// E2E testing — Dear ImGui Test Engine integration.
// ----------------------------------------------------------------------------
//
// Only available when the registry is built with -DFN_BUILD_TESTS=ON. The
// CMake option defines IMGUI_ENABLE_TEST_ENGINE on imgui+fn_framework and
// links the imgui_test_engine static lib. Without the option, run_app_test is
// not declared and apps build identically to today.
#ifdef IMGUI_ENABLE_TEST_ENGINE
struct ImGuiTestEngine;

namespace fn {

// Run an app under Dear ImGui Test Engine. Same as run_app, but:
//   1. Creates a test engine and binds it to the ImGui context.
//   2. Calls register_tests(engine) once before the main loop. Tests are
//      registered with IM_REGISTER_TEST(engine, "category", "name") and
//      assigned a TestFunc lambda that drives the UI.
//   3. Queues all tests matching `filter` (default "all") and ticks frames
//      until the queue empties.
//   4. Exits with code 0 if all tests pass, 1 if any failed or crashed.
//
// register_tests must be non-null and must register at least one test or the
// function returns 1.
int run_app_test(AppConfig config,
                 std::function<void()> render_fn,
                 std::function<void(ImGuiTestEngine*)> register_tests,
                 const char* filter = "all");

} // namespace fn
#endif // IMGUI_ENABLE_TEST_ENGINE
