#pragma once

#include <atomic>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <vector>

// Panel "Paste & Extract" (issue 0013).
//
// Textarea grande para pegar texto. Boton Extract lanza el script
// `enrichers/paste_extract/run.py` en un hilo aparte (no bloquea UI).
// El script devuelve un JSON con entidades y relaciones propuestas (modo
// preview — no escribe a operations.db). El panel muestra dos tablas
// (entidades / relaciones) con checkboxes; al pulsar "Apply Selected"
// se persisten via entity_ops con dedupe por (type_ref, name).
//
// Threading: una llamada Extract a la vez (extract_busy bool). El hilo
// rellena la propuesta tras hacerse el subprocess. Apply corre en el
// thread principal y dispara reload del grafo via app.want_reload.
//
// El enricher esta declarado en `enrichers/paste_extract/manifest.yaml`
// pero NO se invoca via el sistema de jobs — el panel lo lanza
// directamente. Vivir en `enrichers/` permite que se distribuya y que
// el script use el mismo Python runtime resolution que el resto.

namespace ge {

struct AppState;

// Una entidad propuesta por el extractor. Se guarda como string para
// poder editarla inline antes del Apply.
struct ProposedEntity {
    std::string tmp_id;        // "tmp_0", "tmp_1", ... vinculado a relaciones
    std::string type_ref;      // editable
    std::string name;          // editable
    std::string source;        // "regex" | "hybrid"
    int         start_offset = -1;  // span en el texto pegado
    int         end_offset   = -1;
    double      confidence   = 1.0;
    std::string metadata_json;      // JSON literal (no editable v1)
    bool        selected     = true;

    // Buffers mutables para edicion inline en ImGui.
    char        type_buf[64]  = {};
    char        name_buf[256] = {};
};

struct ProposedRelation {
    std::string from_tmp_id;
    std::string to_tmp_id;
    std::string name;            // ej: "works_at"
    std::string source;          // "hybrid" | ...
    double      confidence = 0.0;
    bool        selected   = true;
};

struct ExtractResult {
    std::vector<ProposedEntity>   entities;
    std::vector<ProposedRelation> relations;
    std::vector<std::string>      layers;
    std::string                   error;     // vacio si OK
    std::string                   stderr_tail;
};

struct ExtractPanelState {
    // Buffer de texto del textarea. Crece dinamicamente.
    std::vector<char>             text_buf;
    bool                          text_initialized = false;

    // Resultado del ultimo Extract (poblado por el worker thread).
    std::shared_ptr<ExtractResult> result;
    std::mutex                    result_mu;
    std::atomic<bool>             busy{false};
    std::atomic<bool>             new_result{false};   // hay resultado fresco

    // Mensaje de status (en el footer) — refrescado por el worker.
    std::string                   status;

    // Stats del ultimo apply.
    int  last_apply_entities  = 0;
    int  last_apply_relations = 0;
    int  last_apply_dedup     = 0;

    // Toggle: ¿usar hybrid (GLiNER/GLiREL) si esta disponible?
    bool use_hybrid = false;

    // Worker thread; joinable cuando esta vivo.
    std::thread                   worker;
};

// Configura paths que el worker necesita para invocar Python. Llamar una
// vez tras `jobs_init` (re-usa el resolver de Python runtime + paths).
void extract_panel_init(const char* enrichers_dir,
                         const char* app_dir,
                         const char* registry_root);

// Suelta el worker thread si esta corriendo (cancelable). Llamar al
// shutdown de la app.
void extract_panel_shutdown();

// Renderiza el panel. Si app.panel_extract es false, retorna sin dibujar.
void extract_panel_render(AppState& app);

// Aplica las entidades/relaciones marcadas como selected al
// operations.db indicado. Inserta entidades nuevas con dedupe por
// (type_ref, name); reusa el id existente si lo encuentra. Despues
// inserta las relaciones cuyos endpoints (mapeados via tmp_id ->
// real_id) sean ambos validos.
//
// Devuelve los conteos en out_added_entities, out_dedup_entities,
// out_added_relations. Tolera que algunas relaciones no resuelvan
// (out_skipped_relations). El caller decide si setear app.want_reload.
//
// Esta funcion es testeable en aislamiento (no toca ImGui).
bool extract_panel_apply(const char* ops_db_path,
                          const ExtractResult& result,
                          int* out_added_entities,
                          int* out_dedup_entities,
                          int* out_added_relations,
                          int* out_skipped_relations);

// Helper interno expuesto para tests: parsea el JSON que produce
// `enrichers/paste_extract/run.py`. Devuelve true si el parseo es OK.
// En error, result.error se rellena.
bool extract_panel_parse_result(const std::string& json_text,
                                 ExtractResult* result);

// Spawnea el subprocess Python para extraer. Sincronico (bloquea el
// hilo del caller). El panel lo invoca en un std::thread aparte para
// no congelar la UI. Expuesto por si los tests quieren llamarlo
// directamente (no por ahora — los tests cubren el lado Python via
// pytest, y el lado C++ via parse_result + apply).
bool extract_panel_run_subprocess(const std::string& text,
                                   bool use_hybrid,
                                   ExtractResult* out);

} // namespace ge
