#pragma once

#include <string>
#include <vector>

#include "types_registry.h"
#include "entity_ops.h"
#include "node_groups.h"

#include "core/data_table_types.h"

#include <cstdint>
#include <unordered_map>

struct GraphData;
struct GraphViewportState;

namespace ge {

// Discriminador de la NodeGroups window (issue 0036b). Una window puede
// estar respaldada por una tabla DuckDB (kind=Table) o por una agrupacion
// de entidades en operations.db via `entities.group_id` (kind=Group).
enum class NodeGroupsKind { Table, Group };

// Estado compartido entre las vistas y el bucle render. Pasado por puntero
// desde main.cpp.
struct AppState {
    // Datos
    GraphData*           graph    = nullptr;
    GraphViewportState*  viewport = nullptr;

    // Layout activo — default grid (1) para que los grafos cargados de
    // operations.db se distribuyan ordenadamente al abrir.
    // Default: fixed (5) — respeta posiciones guardadas y physics off por
    // defecto. El usuario activa fisicas con el boton Physics y/o cambia
    // layout desde el dropdown Layout en la toolbar.
    int                  layout_mode = 5;           // 0=force, 1=grid, 2=circular, 3=radial, 4=hierarchical, 5=fixed
    int                  apply_layout_tick = 0;     // se incrementa cuando hay que reaplicar layout
    bool                 want_unpin_all  = false;   // Reset layout: limpia NF_PINNED y reaplica

    // Force layout — config + GPU toggle. Repulsion bajada de 1500→800
    // (issue 0006 follow-up) para evitar movimiento excesivo al cargar
    // grafos pequenos. Combinado con damping=0.7 y max_velocity=8 en
    // run_force_step.
    float                repulsion  = 800.0f;
    float                attraction = 0.04f;
    float                gravity    = 0.005f;
    bool                 use_gpu    = false;

    // Stats UI
    int                  fps_estimate = 0;          // sintetico, calculado en main loop

    // Filters / visibility por tipo (longitud = graph->type_count o rel_type_count)
    bool                 type_visible[256]     = {};
    bool                 rel_type_visible[256] = {};
    int                  type_visible_n        = 0;
    int                  rel_type_visible_n    = 0;

    // Inspector
    bool                 panel_legend     = true;
    bool                 panel_inspector  = true;
    bool                 panel_stats      = true;
    bool                 panel_viewport   = true;
    bool                 panel_note       = false;
    bool                 panel_jobs       = false;     // issue 0026
    bool                 panel_chat       = false;     // claude -p chat (issue 0001)
    bool                 panel_extract    = false;     // paste & extract (issue 0013)
    bool                 show_filters_modal = false;
    bool                 show_open_modal    = false;

    // Triggers — main.cpp lee estos flags y actua
    bool                 want_fit         = false;
    bool                 want_save_layout = false;
    bool                 want_reload      = false;
    bool                 want_open_file   = false;  // marcado al confirmar el modal Open
    char                 open_buf[512]    = {};

    // Project system (issue 0006)
    std::string          active_project;            // slug del proyecto activo
    bool                 want_switch_project = false;
    std::string          switch_project_target;     // slug objetivo del switch
    bool                 show_new_project_modal = false;
    char                 new_project_buf[80] = {};
    std::string          new_project_error;         // mensaje a mostrar en el modal
    std::vector<std::string> project_list_cache;    // refrescado al abrir el menu Project
    std::vector<std::string> project_recent_cache;

    // Labels overlay
    bool                 labels_enabled   = true;

    // Path activo de operations.db (para CRUD desde toolbar / contextmenu).
    // main.cpp lo escribe tras cargar y los handlers lo leen.
    std::string          input_db_path;

    // ---- Grouping (issue 0035b) ---------------------------------------------
    // Estado de expansion de nodos `Group` en RAM. Default vacio = todos los
    // grupos colapsados. No persiste entre sesiones (fase 1). El filtro del
    // loader (apply_group_filter) consulta este map: si una entidad tiene
    // `group_id != NULL` y el grupo padre no esta en este map con valor true,
    // la entidad se oculta del grafo. Sin UI todavia para togglear; se setean
    // valores manualmente desde tests/debug.
    std::unordered_map<std::string, bool> group_expanded;

    // Add-node toolbar input.
    char                 add_buf[256]     = {};

    // Triggers de mutacion — main.cpp los procesa y dispara reload.
    bool                 want_add_node    = false;   // commit del input add_buf
    bool                 want_delete_node = false;   // delete del nodo en ctx_node
    bool                 want_duplicate_node = false;
    bool                 want_change_type = false;   // a ctx_new_type
    int                  ctx_node         = -1;      // node_idx objetivo
    char                 ctx_new_type[64] = {};

    // Context menu state — popup global identificado por nombre.
    bool                 ctx_open_request = false;   // se setea en on_context_menu

    // Note editor (panel "Note" abierto con doble click sobre nodo).
    int                  note_node        = -1;      // node_idx siendo editado
    std::string          note_entity_id;             // sql id resuelto
    std::string          note_entity_label;          // display
    std::string          note_entity_type;
    std::vector<char>    note_buf;                   // editable, NUL-terminated
    bool                 note_dirty       = false;
    bool                 want_save_note   = false;
    bool                 want_open_note   = false;   // doble click → cargar y abrir
    int                  open_note_target = -1;      // node_idx a abrir

    // ---- Inspector editable (issue 0008) ----------------------------------
    // Schema vivo del proyecto activo (load/save desde types.yaml).
    ParsedTypes              parsed_types;

    // Draft del inspector — todo lo que el usuario esta editando para el
    // nodo seleccionado. Se carga desde BD al cambiar la seleccion (si no
    // hay cambios pendientes) y se persiste con entity_update al guardar.
    int                      insp_node_idx        = -1;
    std::string              insp_entity_id;
    char                     insp_name_buf[256]   = {};
    char                     insp_type_buf[64]    = {};
    std::vector<char>        insp_desc_buf;                  // multiline
    int                      insp_status_idx      = 0;       // 0=active 1=stale 2=corrupted 3=archived

    // Listas paralelas: keys + valores actuales de los campos de metadata.
    // Las claves del schema del tipo van primero (en su orden), las "extras"
    // van detras. `is_extra[i]` distingue para render diferenciado y para
    // permitir borrar solo extras desde la UI.
    std::vector<std::string>   insp_field_keys;
    std::vector<std::string>   insp_field_values;
    std::vector<unsigned char> insp_is_extra;

    std::vector<std::string> insp_tags;
    char                     insp_tag_input[64]   = {};
    char                     insp_extra_key[64]   = {};

    bool                     insp_dirty           = false;
    bool                     insp_show_unsaved    = false;
    int                      insp_pending_target  = -1;

    bool                     want_inspector_save     = false;
    bool                     want_inspector_discard  = false;

    // Caches refrescadas tras cargar grafo o tras Save.
    std::vector<std::string> insp_tag_suggestions;
    std::vector<std::string> insp_type_options;

    // ---- Table node (issue 0010) ------------------------------------------
    // Cache de conteo de filas por nodo Table indexado por user_data hash.
    // Refrescado tras load_input y tras mutaciones que afecten a Tables.
    std::unordered_map<uint64_t, int64_t>  node_groups_counts;

    // ---- NodeGroups window (issue 0011, renombrado en 0036a, kind en 0036b) -
    // Estado runtime por ventana de NodeGroups. Hay dos kinds (issue 0036b):
    //   - Table: respaldada por DuckDB (el comportamiento original — un nodo
    //     `Table` del grafo que apunta a un .duckdb + table_name).
    //   - Group: respaldada por la propia operations.db. Lista las entidades
    //     hijas (`entities.group_id = container_id`) con columnas fijas
    //     id/name/type_ref/status/updated_at.
    //
    // Una entrada por container_id (entity_id del nodo contenedor — Table o
    // Group). La ventana se cierra al pulsar la X de ImGui o, en kind=Table,
    // al hacer set_expanded(false) desde el menu contextual.
    struct NodeGroupsWindowState {
        NodeGroupsKind             kind = NodeGroupsKind::Table;  // default compat 0036a
        NodeGroupsMeta             meta;            // refrescada cada vez que entity cambia
        int64_t                    total_rows = 0;
        int64_t                    offset     = 0;
        std::vector<NodeGroupsRow> page;
        bool                       page_dirty = true;
        bool                       open       = true;  // bound a ImGui::Begin
        bool                       focus_request = false;  // 0036c: pedir SetWindowFocus
        std::string                last_error;        // ultimo error de query (vacio = OK)
    };
    std::unordered_map<std::string, NodeGroupsWindowState> node_groups_windows;

    // Triggers consumidos por main.cpp tras click en filas.
    bool        want_promote_row = false;
    std::string promote_table_id;       // entity_id del Table de origen
    std::string promote_row_id;         // valor del id_column

    bool        want_demote_entity = false;
    std::string demote_entity_id;

    bool        want_focus_entity = false;     // tras promote+open inspector
    std::string focus_entity_id;

    // Issue 0036d: Promote en NodeGroups kind=Group → saca la entidad
    // del grupo (group_id = NULL). main.cpp lo procesa y dispara reload.
    bool        want_clear_group_id_entity = false;
    std::string clear_group_id_entity_id;

    // Modal "Import dataset..." (issue 0011 Ingesta).
    bool        show_import_modal = false;
    char        import_path_buf[512]   = {};
    char        import_table_buf[64]   = {};
    char        import_duckdb_buf[256] = {};   // relativo a project root
    char        import_row_type_buf[64] = {};
    bool        want_import = false;
    std::string import_error;

    // Toggle NodeGroups window desde context menu del viewport.
    bool        want_toggle_nodegroups = false;
    std::string toggle_nodegroups_id;

    // ---- Table view (issue 0004) -------------------------------------------
    // Vista tabular dockeable. Tabs por type_ref del grafo activo + opcional
    // "All". Click selecciona el nodo en el viewport (mismo flujo que el
    // Selectable del Inspector).
    struct TableRow {
        std::string id;
        std::string name;
        std::string type_ref;
        std::string status;
        std::string updated_at;
        std::string group_id;       // si pertenece a un Group, su sql id; "" si no
        int         neighbors = 0;
        int         node_idx  = -1;
    };
    bool                     panel_table       = false;
    std::vector<TableRow>    table_rows;        // snapshot, refrescado tras load/reload
    bool                     table_cache_dirty = true;
    char                     table_search_buf[96] = {};
    bool                     table_show_all    = false;
    int                      table_active_tab  = 0;
    // Filtros por columna: column_user_id (0..5) -> substring filter.
    // Visible UX: right-click sobre header de columna abre popup con input;
    // chips con filtros activos por encima de la tabla.
    std::unordered_map<int, std::string> table_col_filters;
    char                     table_filter_input[96] = {};   // buffer del popup activo
    int                      table_filter_pending_col = -1; // col_user_id en edicion

    // data_table::State para el panel Table (issue 0081-J).
    // Persiste filters/sort/stages entre frames; uno por instancia de render.
    data_table::State        table_dt_state;

    // data_table::State para el panel Jobs (issue 0081-J Phase 1).
    // Persiste filters/sort/stages entre frames para la tabla de jobs.
    data_table::State        jobs_dt_state;

    // ---- Type Editor (issue 0007) ------------------------------------------
    // Draft del editor de tipos. Se inicializa con una copia de parsed_types
    // tras cargar el grafo. Save reescribe `types.yaml` y dispara
    // apply_types_yaml + rebuild de IconAtlas.
    bool                      panel_type_editor    = false;
    ParsedTypes               types_draft;
    bool                      types_dirty          = false;
    int                       te_tab_idx           = 0;   // 0=Entities 1=Relations
    int                       te_entity_idx        = -1;  // seleccion entity
    int                       te_relation_idx      = -1;  // seleccion relation
    bool                      want_types_save      = false;
    bool                      want_types_reload    = false;
    int                       te_pending_delete_e  = -1;  // entity idx pendiente de confirmar
    int                       te_pending_delete_r  = -1;
    int                       te_delete_use_count  = 0;   // entidades afectadas
    bool                      show_te_delete_modal = false;
    std::string               types_save_error;            // mensaje a renderizar bajo Save

    // ---- Filtros y busqueda FTS5 (issue 0009) ------------------------------
    // Modos: 0 = highlight (no-match dimmed), 1 = hide (no-match invisible).
    enum FilterMode { FM_HIGHLIGHT = 0, FM_HIDE = 1 };
    int                       filter_mode          = FM_HIGHLIGHT;
    char                      filter_query_buf[128] = {};
    std::vector<std::string>  filter_tags;             // chips activos
    std::vector<EntityHit>    filter_hits;             // dropdown FTS (max 20)
    bool                      filter_dropdown_open  = false;
    bool                      filter_dirty          = false;  // pide reapply
    int                       filter_focus_target   = -1;     // node_idx a centrar
    char                      filter_tag_input[64]  = {};     // input de chip nuevo

    // ---- Enricher config window --------------------------------------------
    // Cuando el usuario clica un enricher con `params` no vacios en el
    // context menu, se rellena este bloque y se abre una ventana ImGui
    // (no modal) que permite ajustar los valores antes de submitear el
    // job. La ventana es dockeable y movible; cerrar la X cancela.
    // Si el enricher no declara params, se submitea directamente con `{}`
    // sin pasar por aqui.
    bool                      enr_window_open = false;        // visibilidad
    std::string               enr_modal_id;                   // enricher.id
    std::string               enr_modal_node_id;              // sql_id del nodo
    std::string               enr_modal_node_label;           // label visible
    // Buffer editable por param. Tamano fijo 256 para inputs de texto;
    // suficiente para queries y URLs cortas. Indices alineados con
    // EnricherSpec::params del enricher seleccionado.
    std::vector<std::vector<char>> enr_modal_param_bufs;
};

// Toolbar superior (Open file, Layout selector, Filters..., Fit, Save layout).
void views_toolbar(AppState& app);

// Panel Legend — checkboxes por tipo (entity / relation) con color swatch.
void views_legend(AppState& app);

// Panel Inspector — metadata del nodo seleccionado + vecinos.
void views_inspector(AppState& app);

// Stats line — counts + fps + energy + selection.
void views_stats(AppState& app);

// Note editor — abre con doble click sobre un nodo. Edita la columna `notes`
// (markdown) de la entidad y guarda con un boton.
void views_note(AppState& app);

// Modal Filters — toggles por tipo agrupados en columnas. Devuelve true si
// el usuario togglo algo.
bool views_filters_modal(AppState& app);

// Modal Open file — text input + boton Open.
bool views_open_modal(AppState& app);

// Modal "New project..." — slug input con validacion. Devuelve true si el
// usuario confirma con un slug valido.
bool views_new_project_modal(AppState& app);

// Refresca los flags `flags` de cada nodo/arista segun el array
// `type_visible[]` / `rel_type_visible[]`. Lineal en N+M.
void views_apply_visibility(AppState& app);

// Inicializa los arrays type_visible / rel_type_visible a true para todos
// los tipos del grafo activo. Llamar tras cargar/recargar el grafo.
void views_reset_visibility(AppState& app);

// ---- Inspector editable helpers (issue 0008) ------------------------------

// Refresca insp_tag_suggestions e insp_type_options leyendo BD y schema.
// Llamar tras cargar el grafo o tras un Save.
void views_inspector_refresh_caches(AppState& app);

// Carga el draft del Inspector desde la BD para el nodo `node_idx`. Si el
// nodo no es resoluble o no existe, deja el draft vacio. No respeta dirty:
// el caller debe haberlo manejado ya.
void views_inspector_load_draft(AppState& app, int node_idx,
                                 const char* entity_id);

// Construye un EntityRecord desde el draft actual respetando el schema
// del type_ref para decidir is_string de cada metadata field.
EntityRecord views_inspector_build_record(const AppState& app);

// Resetea el draft (todos los buffers + dirty=false). Util tras Save o
// al cambiar de proyecto.
void views_inspector_clear_draft(AppState& app);

// ---- NodeGroups window (issue 0011, renombrado en 0036a) ----------------

// Renderiza una ventana ImGui dockeable por cada NodeGroups en
// node_groups_windows con `open=true`. Cabecera con nombres de columnas.
// Filas paginadas con ImGuiListClipper consumiendo el page cache; al
// cambiar el offset, marca dirty para que main.cpp refresque via
// node_groups_page. Doble click en fila no promovida -> setea
// promote_table_id/promote_row_id; promovida -> focus_entity_id. Cerrar la
// ventana setea expanded=false en BD.
void views_node_groups_window(AppState& app);

// Modal "Import dataset..." — formulario para crear una tabla DuckDB
// desde CSV/Parquet/JSON y registrar el nodo Table correspondiente.
bool views_import_dataset_modal(AppState& app);

// Sincroniza node_groups_windows con la metadata.expanded de cada nodo
// Table. Llamar tras load + tras mutaciones que cambien expanded. Crea
// entradas para nuevos expanded y borra las que ya no aplican.
void views_node_groups_windows_sync(AppState& app, const char* ops_db);

// Crea o reusa una entrada en `app.node_groups_windows[container_id]` y la
// marca con el `kind` indicado. Setea `focus_request = true` para que el
// render pueda llamar a ImGui::SetWindowFocus (lo consume 0036c). Si la
// entry ya existe se respeta su kind anterior y solo se setea
// focus_request — no recarga ni resetea offset. Si es nueva, llama a
// `node_groups_load_metadata` para popular `meta` (en kind=Group eso pre-
// puebla las columnas fijas; en kind=Table lee la metadata del Table-typed
// node de operations.db).
//
// `ops_db` es el path de operations.db. Si esta vacio, no se carga
// metadata pero la entry se crea de todos modos (caller puede rellenar
// despues). Devuelve puntero a la entry — nunca nullptr salvo
// container_id vacio.
AppState::NodeGroupsWindowState*
    views_node_groups_open(AppState& app,
                            const std::string& container_id,
                            NodeGroupsKind kind,
                            const char* ops_db);

// ---- Table node overlay (issue 0010) ------------------------------------

// Dibuja un overlay rectangulo redondeado sobre cada nodo `Table` del grafo
// con etiqueta "Table · N rows" leyendo de app.node_groups_counts. Llamar
// despues de graph_viewport(...) — usa GetItemRectMin/Max + GetWindowDrawList
// del item viewport. No interactua con eventos; el hit-testing del nodo
// sigue usandolo el viewport circular de fondo.
void views_node_groups_overlay(AppState& app);

// ---- Table view (issue 0004) --------------------------------------------

// Renderiza el panel "Table". Lee de app.table_rows; el caller ya ha hecho el
// build/refresh tras cargar el grafo. Click en fila selecciona el nodo en el
// viewport (mismo flujo que el Selectable del Inspector). Filtro por
// substring sobre name/id en la cabecera.
void views_table(AppState& app);

// Recompute neighbors[] y node_idx[] de las filas existentes a partir del
// grafo cargado. Llamar tras cargar el grafo o tras una mutacion.
void views_table_refresh_indices(AppState& app);

// ---- Type Editor (issue 0007) -------------------------------------------

// Renderiza el panel "Types" — tabs Entities/Relations, lista a la izquierda
// con +/-, panel de edicion a la derecha. Marca app.types_dirty al cambiar y
// activa app.want_types_save / app.want_types_reload desde el footer.
void views_type_editor(AppState& app);

// Modal de confirmacion para borrar un tipo en uso. Se abre cuando
// app.show_te_delete_modal = true. main.cpp es responsable de poblar
// te_delete_use_count via consulta a operations.db antes de mostrarlo.
bool views_type_editor_delete_modal(AppState& app);

// ---- Jobs panel (issue 0026) ---------------------------------------------

// Renderiza el panel "Jobs" — tabla con todos los jobs (queued/running/done/
// error/cancelled). Botones por fila para cancelar / reintentar / borrar.
// Click en target_node centra el viewport sobre ese nodo (futuro). Polling
// cada N frames para no spammear la BD.
void views_jobs(AppState& app);

// ---- Filter helpers (issue 0009) -----------------------------------------

// True si el filtro tiene query no vacia o al menos un tag activo.
bool views_filter_active(const AppState& app);

// Anade un tag como chip si no existe ya. Marca filter_dirty = true.
void views_filter_add_tag(AppState& app, const char* tag);

// Limpia query + tags y marca filter_dirty.
void views_filter_clear(AppState& app);

// Reaplica el filtro al grafo: recompute la mascara de visibilidad/alpha
// segun filter_query, filter_tags, filter_mode. Llama a la BD si hace
// falta (FTS + tags). Resetea color_override a 0 en los nodos que pasan
// el filtro y aplica un alpha bajo en los que no (modo highlight) o limpia
// NF_VISIBLE (modo hide). Tambien refresca filter_hits para el dropdown
// con max 20 resultados ordenados por rank.
void views_filter_apply(AppState& app);

} // namespace ge
