"""
@RAG_CONTEXT
{
  "archivo": "src/ocr_indexer/cli.py",
  "lineas": 826,
  "tokens_estimados": 9034,
  "hash_contenido": "306b73a6e72db5b06b676e1497109717315ed022fbda5ee1bb8030760a0baeac",
  "creacion": "2026-03-13",
  "modificacion": "2026-03-17",
  "tags": [
    "version",
    "sys",
    "time",
    "importlib.metadata",
    "ai_disponible",
    "logging",
    "get_ai_config",
    "subprocess",
    ".ai_refiner",
    "json",
    "shutil",
    "os"
  ],
  "descripcion_corta": "CLI para motor OCR local con búsqueda y monitoreo",
  "descripcion_larga": "Interfaz de línea de comandos para el sistema OCR Indexer Pro. Proporciona control completo sobre el motor de indexación OCR local, incluyendo búsqueda booleana O(1), monitoreo en tiempo real, gestión de tolerancias de caracteres, configuración de IA para refinamiento, y operaciones de background con soporte tmux para persistencia. Implementa funciones de progreso visual, detección automática del motor, y gestión de múltiples instancias en el sistema."
}
"""
#!/usr/bin/env python3
import os
import sys
import json
import subprocess
import time

GREEN = '\033[0;32m'
BLUE = '\033[0;34m'
YELLOW = '\033[1;33m'
RED = '\033[0;31m'
NC = '\033[0m'

def format_time(segundos):
    m, s = divmod(int(segundos), 60)
    h, m = divmod(m, 60)
    return f"{h:02d}h {m:02d}m {s:02d}s"

def draw_progress(porcentaje):
    bar_length = 20
    filled = int(bar_length * porcentaje // 100)
    bar = '█' * filled + '-' * (bar_length - filled)
    return f"[{bar}] {porcentaje:.1f}%"

def find_engine():
    curr_dir = os.getcwd()
    while curr_dir != '/':
        engine_path = os.path.join(curr_dir, '.ocr-indexer')
        if os.path.isdir(engine_path):
            return engine_path
        curr_dir = os.path.dirname(curr_dir)
    # Fallback: motor activo global
    active_file = os.path.expanduser("~/.ocr_active_engine")
    if os.path.exists(active_file):
        try:
            with open(active_file) as f:
                path = f.read().strip()
            candidate = os.path.join(path, '.ocr-indexer')
            if os.path.isdir(candidate):
                return candidate
        except Exception:
            pass
    return None

def _supports_color():
    return sys.stdout.isatty() and not os.environ.get("NO_COLOR")


def cmd_help():
    try:
        from importlib.metadata import version as _pkg_version
        ver = _pkg_version("ocr-indexer-pro")
    except Exception:
        ver = "?"
    c = _supports_color()
    R  = "\033[0m"  if c else ""
    B  = "\033[1m"  if c else ""
    C  = "\033[36m" if c else ""
    Y  = "\033[33m" if c else ""
    G  = "\033[32m" if c else ""
    DM = "\033[2m"  if c else ""
    W  = 60
    print(f"\n{B}{C}╔{'═'*W}╗{R}")
    print(f"{B}{C}║{'OCR INDEXER PRO':^{W}}║{R}")
    print(f"{B}{C}║{f'v{ver}  —  Motor OCR local para servidores Linux':^{W}}║{R}")
    print(f"{B}{C}╚{'═'*W}╝{R}\n")
    grupos = [
        ("🔍 BÚSQUEDA Y MONITOREO", [
            ("search",              "Búsqueda booleana (&&, ||) con índice invertido O(1)"),
            ("last",                "Últimos 10 archivos indexados con preview de texto OCR"),
            ("status",              "Estado del motor, fase actual y progreso en tiempo real"),
            ("stats",               "Estadísticas de extensiones indexadas"),
            ("logs",                "Ver últimas 50 líneas del log del indexador"),
        ]),
        ("⚙️  CONTROL DEL MOTOR", [
            ("dir [ruta]",          "Crear motor local en la ruta indicada (o cwd)"),
            ("run",                 "Pipeline completo: Tesseract + IA + vigía tmux en un comando"),
            ("start",               "Iniciar indexador completo (Fase 1 + Fase 2) en background"),
            ("watch",               "Iniciar vigía de archivos nuevos en background"),
            ("watch-tmux",          "Iniciar vigía en sesión tmux persistente (sobrevive SSH)"),
            ("watch-tmux-stop",     "Detener y eliminar la sesión tmux del vigía"),
            ("watch-tmux-status",   "Ver estado de la sesión tmux del vigía"),
            ("stop",                "Detener indexador y vigía"),
            ("reindex <ruta>",      "Re-procesar un archivo específico con OCR"),
            ("refine",              "Refinar texto OCR con IA para archivos sin refinar"),
            ("refine --force",      "Re-refinar TODOS los archivos con IA"),
            ("ai-config",           "Ver/configurar API de IA, proveedor y modo visión"),
            ("reset",               "Borrar el índice completo y archivos auxiliares"),
        ]),
        ("🧠 CONFIGURACIÓN OCR", [
            ("tol-list",            "Ver reglas de tolerancia activas (ej. O ↔ 0)"),
            ("tol-add",             "Añadir reglas en lote (formato: O:0, I:1)"),
            ("tol-rm",              "Eliminar una regla de tolerancia (y su espejo)"),
            ("tol-clear",           "Borrar todas las reglas de tolerancia"),
            ("blacklist-list",      "Ver carpetas excluidas del indexado"),
            ("blacklist-add",       "Añadir carpeta a la lista negra"),
            ("blacklist-rm",        "Quitar carpeta de la lista negra"),
        ]),
        ("🌐 SISTEMA GLOBAL", [
            ("engines",             "Descubrir y navegar a otros motores en el sistema"),
            ("shell-setup",         "Instalar función ocre() en .bashrc/.zshrc para cd automático"),
            ("install-deps",        "Instalar Tesseract OCR en el sistema (requiere apt-get)"),
            ("help",                "Mostrar esta ayuda"),
        ]),
    ]
    for titulo, cmds in grupos:
        print(f"{B}{Y}  {titulo}{R}")
        for cmd, desc in cmds:
            print(f"    {G}{cmd:<24}{R}  {DM}{desc}{R}")
        print()
    print(f"{DM}  Uso: ocr-indexer <comando> [argumentos]{R}")
    print(f"{DM}  Sin argumentos: menú interactivo numerado{R}\n")


def cmd_install_deps():
    """Instala tesseract-ocr y el paquete de idioma español vía apt-get."""
    import shutil
    paquetes = ["tesseract-ocr", "tesseract-ocr-spa"]
    ya_instalados = [p for p in paquetes if shutil.which("tesseract") and p == "tesseract-ocr"]

    print(f"\n{BLUE}--- INSTALAR DEPENDENCIAS DEL SISTEMA ---{NC}")

    # Verificar tesseract actual
    if shutil.which("tesseract"):
        result = subprocess.run(["tesseract", "--version"], capture_output=True, text=True)
        version = result.stdout.splitlines()[0] if result.stdout else "desconocida"
        print(f"Tesseract   : {GREEN}ya instalado ({version}){NC}")
    else:
        print(f"Tesseract   : {RED}NO instalado{NC}")

    print(f"\nPaquetes a instalar: {YELLOW}{' '.join(paquetes)}{NC}")
    resp = input(f"{YELLOW}¿Continuar con apt-get install? [S/n]: {NC}").strip().lower()
    if resp == 'n':
        print("Cancelado.")
        return

    print(f"{YELLOW}Ejecutando apt-get update...{NC}")
    ret = subprocess.run(["apt-get", "update", "-qq"], capture_output=False)
    if ret.returncode != 0:
        print(f"{RED}apt-get update falló (¿permisos de root?). Intenta: sudo ocr-indexer install-deps{NC}")
        return

    print(f"{YELLOW}Instalando {' '.join(paquetes)}...{NC}")
    ret = subprocess.run(["apt-get", "install", "-y", "-qq"] + paquetes, capture_output=False)
    if ret.returncode == 0:
        result = subprocess.run(["tesseract", "--version"], capture_output=True, text=True)
        version = result.stdout.splitlines()[0] if result.stdout else ""
        print(f"\n{GREEN}✅ Tesseract instalado correctamente. {version}{NC}")
    else:
        print(f"\n{RED}❌ Error en la instalación. Instálalo manualmente:{NC}")
        print(f"  apt-get install -y tesseract-ocr tesseract-ocr-spa")


def get_tmux_session(engine):
    config_path = os.path.join(engine, 'config.json')
    try:
        with open(config_path, 'r') as f:
            config = json.load(f)
        root = config.get('root', os.path.dirname(engine))
    except Exception:
        root = os.path.dirname(engine)
    base = os.path.basename(root.rstrip('/')) or "proyecto"
    return f"{base}___ocr-indexer"


def _tmux_session_existe(name):
    r = subprocess.run(["tmux", "has-session", "-t", name], capture_output=True)
    return r.returncode == 0


def cmd_watch_tmux(engine, script_watcher):
    try:
        subprocess.run(["tmux", "-V"], capture_output=True, check=True)
    except (FileNotFoundError, subprocess.CalledProcessError):
        print(f"{RED}❌ tmux no está instalado. Instala con: apt install tmux{NC}")
        return
    name = get_tmux_session(engine)
    if not _tmux_session_existe(name):
        try:
            subprocess.run(["tmux", "new-session", "-d", "-s", name, "-n", "Vigía"], check=True, timeout=10)
            subprocess.run(["tmux", "send-keys", "-t", f"{name}:Vigía",
                            f"{sys.executable} -u {script_watcher} {engine}", "Enter"], timeout=5)
            subprocess.run(["tmux", "new-window", "-t", f"{name}:", "-n", "Shell"], check=True, timeout=10)
            subprocess.run(["tmux", "select-window", "-t", f"{name}:Vigía"], timeout=5)
            print(f"{GREEN}✅ Sesión '{name}' creada. Vigía activo en ventana [Vigía].{NC}")
        except subprocess.CalledProcessError as e:
            print(f"{RED}❌ Error creando sesión tmux: {e}{NC}")
            return
    else:
        print(f"{YELLOW}⚡ Sesión '{name}' ya existe — reconectando.{NC}")
    if os.environ.get("TMUX"):
        print(f"{YELLOW}⚠️  Ya estás dentro de tmux. Cambiando a sesión '{name}'...{NC}")
        os.execlp("tmux", "tmux", "switch-client", "-t", name)
    else:
        os.execlp("tmux", "tmux", "attach-session", "-t", name)


def cmd_watch_tmux_stop(engine):
    name = get_tmux_session(engine)
    if _tmux_session_existe(name):
        subprocess.run(["tmux", "kill-session", "-t", name])
        print(f"{RED}🛑 Sesión '{name}' eliminada.{NC}")
    else:
        print(f"{YELLOW}No hay sesión activa con nombre '{name}'.{NC}")


def cmd_watch_tmux_status(engine):
    name = get_tmux_session(engine)
    existe = _tmux_session_existe(name)
    print(f"\n{BLUE}--- ESTADO VIGÍA TMUX ---{NC}")
    print(f"Sesión tmux  : {YELLOW}{name}{NC}")
    if existe:
        print(f"Estado       : {GREEN}ACTIVA{NC}")
        r = subprocess.run(["tmux", "list-windows", "-t", name], capture_output=True, text=True)
        if r.stdout.strip():
            for win in r.stdout.strip().split('\n'):
                print(f"  Ventana    : {YELLOW}{win.strip()}{NC}")
    else:
        print(f"Estado       : {RED}INACTIVA{NC}")
        print(f"{YELLOW}Usa 'watch-tmux' para iniciar.{NC}")
    print(f"{BLUE}-------------------------{NC}\n")


def cmd_reindex(engine, index_path, script_indexer, args):
    if len(args) < 2:
        print(f"{RED}Uso: ocr-indexer reindex <ruta/al/archivo>{NC}")
        return
    filepath = os.path.abspath(args[1])
    try:
        with open(index_path, 'r') as f:
            db = json.load(f)
    except Exception as e:
        print(f"{RED}Error leyendo índice: {e}{NC}")
        return
    if filepath not in db:
        print(f"{YELLOW}⚠️  El archivo no está en el índice: {filepath}{NC}")
        print(f"{YELLOW}Usa 'start' para indexar todos los archivos pendientes.{NC}")
        return
    db[filepath]["estado"] = "pendiente"
    db[filepath].pop("refinado_ia", None)
    if isinstance(db[filepath].get("contenido"), dict):
        db[filepath]["contenido"]["texto_ocr"] = ""
    try:
        with open(index_path, 'w') as f:
            json.dump(db, f, indent=4)
    except Exception as e:
        print(f"{RED}Error guardando índice: {e}{NC}")
        return
    log_path = os.path.join(engine, 'indexer.log')
    with open(log_path, 'a') as log_file:
        subprocess.Popen([sys.executable, "-u", script_indexer, engine, "--fase2"],
                         stdout=log_file, stderr=log_file)
    print(f"{GREEN}✅ '{os.path.basename(filepath)}' marcado para re-indexar. Fase 2 iniciada.{NC}")


def cmd_ai_config(engine, config_path):
    from .ai_refiner import get_ai_config, _MODELOS, _MODELOS_VISION, ai_disponible, vision_disponible
    try:
        with open(config_path, 'r') as f:
            config = json.load(f)
    except Exception as e:
        print(f"{RED}Error leyendo config.json: {e}{NC}")
        return

    proveedor, api_key, modelo = get_ai_config(engine, config)
    ia_cfg = config.get("ia", {}) if isinstance(config.get("ia"), dict) else {}
    use_vision = ia_cfg.get("use_vision", False)

    print(f"\n{BLUE}--- CONFIGURACIÓN IA (Refinado OCR) ---{NC}")
    print(f"Proveedor  : {GREEN}{proveedor or '(no configurado)'}{NC}")
    if api_key:
        preview = api_key[:4] + '*' * max(4, len(api_key) - 8) + api_key[-4:] if len(api_key) > 8 else '****'
        print(f"API Key    : {YELLOW}{preview}{NC}")
    else:
        print(f"API Key    : {RED}(no configurada){NC}")
    print(f"Modelo     : {GREEN}{modelo or '(auto)'}{NC}")
    print(f"Modo visión: {(GREEN + 'ACTIVO') if use_vision else (YELLOW + 'INACTIVO')}{NC}")
    print(f"Estado IA  : {(GREEN + 'ACTIVA') if ai_disponible(engine, config) else (RED + 'INACTIVA')}{NC}")
    print(f"\nProveedores: openai | anthropic | gemini | deepseek")
    print(f"Visión IA  : openai (gpt-4o) | anthropic (claude-3-5-sonnet) | gemini (gemini-2.5-flash)")
    print(f"También puedes usar variables de entorno: RAG_AI_PROVIDER + RAG_API_KEY")
    print(f"{BLUE}----------------------------------------------------{NC}")

    nuevo_proveedor = input(f"{YELLOW}Proveedor [{proveedor or 'deepseek'}]: {NC}").strip().lower() or proveedor or "deepseek"
    nueva_key = input(f"{YELLOW}API Key (Enter para conservar actual): {NC}").strip() or api_key

    if not nueva_key:
        print(f"{YELLOW}Sin API key. Configuración no guardada.{NC}")
        return

    config.setdefault("ia", {})
    config["ia"]["proveedor"] = nuevo_proveedor
    config["ia"]["api_key"] = nueva_key
    config["ia"]["modelo"] = _MODELOS.get(nuevo_proveedor, "")

    if nuevo_proveedor == "deepseek":
        print(f"{YELLOW}⚠️  DeepSeek no soporta visión. use_vision=false forzado.{NC}")
        config["ia"]["use_vision"] = False
        config["ia"].pop("modelo_vision", None)
    else:
        resp_v = input(f"{YELLOW}¿Activar modo visión (OCR directo desde imagen, más preciso)? [s/N]: {NC}").strip().lower()
        config["ia"]["use_vision"] = (resp_v == "s")
        config["ia"]["modelo_vision"] = _MODELOS_VISION.get(nuevo_proveedor, "")

    try:
        with open(config_path, 'w') as f:
            json.dump(config, f, indent=4)
        vision_str = f" | Visión: {GREEN}ACTIVA{NC}" if config["ia"].get("use_vision") else ""
        print(f"{GREEN}✅ Config IA guardada. Proveedor: {nuevo_proveedor} | Modelo: {config['ia']['modelo']}{vision_str}{NC}")
    except Exception as e:
        print(f"{RED}Error guardando config: {e}{NC}")


def cmd_refine(engine, index_path, config_path, args):
    from .ai_refiner import ai_disponible, refinar_fase3, get_ai_config
    import logging as _logging
    _log = _logging.getLogger("cli")
    force = "--force" in args

    try:
        with open(config_path, 'r') as f:
            config = json.load(f)
    except Exception as e:
        print(f"{RED}Error leyendo config.json: {e}{NC}")
        return

    if not ai_disponible(engine, config):
        print(f"{RED}❌ No hay API de IA configurada.{NC}")
        print(f"{YELLOW}Usa 'ocr-indexer ai-config' para configurarla.{NC}")
        return

    try:
        with open(index_path, 'r') as f:
            db = json.load(f)
    except Exception as e:
        print(f"{RED}Error leyendo índice: {e}{NC}")
        return

    if force:
        pendientes = [k for k, v in db.items() if v.get("estado") == "completado"]
        print(f"{YELLOW}Modo --force: se re-procesarán TODOS los archivos completados.{NC}")
    else:
        pendientes = [k for k, v in db.items()
                      if v.get("estado") == "completado" and not v.get("refinado_ia")]

    if not pendientes:
        print(f"{GREEN}✅ Todos los archivos ya fueron refinados por IA. Usa --force para re-procesar.{NC}")
        return

    proveedor, api_key, modelo = get_ai_config(engine, config)
    print(f"\n{BLUE}--- REFINADO IA ---{NC}")
    print(f"Archivos a refinar : {YELLOW}{len(pendientes)}{NC}")
    print(f"Proveedor          : {GREEN}{proveedor} / {modelo}{NC}")

    if not force:
        res = input(f"{YELLOW}¿Iniciar refinado? [S/n]: {NC}").strip().lower()
        if res == 'n':
            print("Cancelado.")
            return

    status_path = os.path.join(engine, 'status.json')
    log_path = os.path.join(engine, 'indexer.log')

    import logging as _log_module
    _log_module.basicConfig(filename=log_path, level=_log_module.INFO,
                            format='%(asctime)s [%(levelname)s] %(message)s',
                            datefmt='%Y-%m-%d %H:%M:%S')

    def actualizar_status(datos):
        try:
            with open(status_path, 'w') as f:
                json.dump(datos, f, indent=4)
        except Exception:
            pass

    refinar_fase3(pendientes, db, index_path, engine, config, _log, actualizar_status)


def mostrar_help():
    print(f"Uso: {GREEN}ocr-indexer [comando]{NC} (o solo {GREEN}ocr-indexer{NC} para el menú interactivo)")

def mostrar_menu():
    print(f"\n{BLUE}===================================================={NC}")
    print(f"{YELLOW}          SISTEMA OCR - MENÚ INTERACTIVO {NC}")
    print(f"{BLUE}===================================================={NC}")
    
    categorias = [
        ("🔍 BÚSQUEDA Y MONITOREO", [
            ("Buscar texto", ["search"]),
            ("Ver últimos 10 procesados", ["last"]),
            ("Ver estado y progreso en vivo", ["status"]),
            ("Ver estadísticas de extensiones", ["stats"]),
            ("Ver registros de terminal (logs)", ["logs"])
        ]),
        ("⚙️ CONTROL DEL MOTOR LOCAL", [
            ("Pipeline completo + vigía tmux", ["run"]),
            ("Iniciar indexador en background", ["start"]),
            ("Iniciar vigía de archivos nuevos", ["watch"]),
            ("Vigía persistente en sesión tmux", ["watch-tmux"]),
            ("Detener sesión tmux del vigía", ["watch-tmux-stop"]),
            ("Estado de sesión tmux del vigía", ["watch-tmux-status"]),
            ("Detener indexador y vigía", ["stop"]),
            ("Re-indexar un archivo específico", ["reindex"]),
            ("Refinar texto OCR con IA", ["refine"]),
            ("Configurar API de IA", ["ai-config"]),
            ("Resetear motor (Borrar índice)", ["reset"]),
            ("Crear motor en esta carpeta", ["dir"])
        ]),
        ("🧠 REGLAS DE TOLERANCIA OCR", [
            ("Ver reglas actuales", ["tol-list"]),
            ("Añadir reglas (Lote: O:0, I:1)", ["tol-add"]),
            ("Eliminar regla", ["tol-rm"]),
            ("Limpiar todas", ["tol-clear"])
        ]),
        ("🚫 LISTA NEGRA (EXCLUSIONES)", [
            ("Ver lista negra actual", ["blacklist-list"]),
            ("Añadir carpeta a lista negra", ["blacklist-add"]),
            ("Quitar carpeta de lista negra", ["blacklist-rm"])
        ]),
        ("🌐 SISTEMA GLOBAL", [
            ("Buscar y viajar a otros motores", ["engines"]),
            ("Ver ayuda de comandos", ["help"]),
            ("Salir", ["salir"])
        ])
    ]

    opciones_planas = []
    contador = 1

    for titulo, opciones in categorias:
        print(f"{YELLOW}--- {titulo} ---{NC}")
        for desc, cmd in opciones:
            comando_texto = f"ocr-indexer {cmd[0]}" if cmd[0] != "salir" else "salir"
            print(f"  {GREEN}[{contador:<2}]{NC} {desc:<35} {YELLOW}({comando_texto}){NC}")
            opciones_planas.append(cmd)
            contador += 1
        print("") 

    print(f"{BLUE}----------------------------------------------------{NC}")
    try:
        sel = input(f"{YELLOW}Elige el número de la acción: {NC}")
        sel = int(sel)
        if 1 <= sel <= len(opciones_planas):
            cmd = opciones_planas[sel-1]
            if cmd[0] == "salir":
                sys.exit(0)
            
            if cmd[0] in ["blacklist-add", "blacklist-rm"]:
                carpeta = input(f"{YELLOW}Escribe el nombre de la carpeta: {NC}")
                if not carpeta.strip():
                    sys.exit(0)
                return [cmd[0], carpeta.strip()]

            if cmd[0] == "reindex":
                ruta = input(f"{YELLOW}Ruta del archivo a re-indexar: {NC}").strip()
                if not ruta:
                    sys.exit(0)
                return [cmd[0], ruta]

            return cmd
        else:
            print(f"{RED}❌ Opción inválida.{NC}")
            sys.exit(1)
    except ValueError:
        print(f"{RED}❌ Ingresa solo el número.{NC}")
        sys.exit(1)
    except KeyboardInterrupt:
        sys.exit(0)

def _save_goto_path(path):
    """Guarda el motor seleccionado para shell wrapper (ocre) y motor activo global."""
    for fname in ("~/.ocr_goto_path", "~/.ocr_active_engine"):
        try:
            with open(os.path.expanduser(fname), 'w') as f:
                f.write(path)
        except Exception:
            pass


_SHELL_FUNC = """\n# OCR-Indexer shell integration — añadido por ocr-indexer shell-setup
ocre() {
    ocr-indexer engines
    local _p
    _p=$(cat ~/.ocr_goto_path 2>/dev/null)
    [ -n "$_p" ] && [ -d "$_p" ] && cd "$_p" && echo "📍 $_p"
}
"""


def main():
    initial_args = sys.argv[1:]
    interactive = not initial_args or initial_args[0] == "menu"

    while True:
        if interactive:
            args = mostrar_menu()
        else:
            args = initial_args

        comando = args[0]
        base_dir = os.path.dirname(__file__)
        script_indexer = os.path.join(base_dir, "indexer.py")
        script_searcher = os.path.join(base_dir, "searcher.py")
        script_watcher = os.path.join(base_dir, "watcher.py")
        engine = find_engine()

        if comando == "engines":
            cache_file = os.path.expanduser("~/.ocr_engines_cache.json")
            motores = []
            if os.path.exists(cache_file):
                try:
                    with open(cache_file, 'r') as f: motores = json.load(f)
                except: pass
                if motores:
                    print(f"\n{BLUE}===================================================={NC}")
                    print(f"{YELLOW}        ⚡ MOTORES CONOCIDOS (Carga Rápida)        {NC}")
                    print(f"{BLUE}===================================================={NC}")
                    for i, motor in enumerate(motores, 1): print(f"  {GREEN}[{i:<2}]{NC} {motor}")
                    print(f"{BLUE}----------------------------------------------------{NC}")
                    sel = input(f"{YELLOW}Elige un número para viajar YA, o [Enter] para escanear: {NC}").strip()
                    if sel.isdigit() and 1 <= int(sel) <= len(motores):
                        _goto_path = motores[int(sel)-1]
                        _save_goto_path(_goto_path)
                        os.chdir(_goto_path)
                        print(f"{GREEN}🚀 Motor activo: {_goto_path}{NC}")
                        if interactive: continue
                        else: break
                    elif sel.lower() == 'salir':
                        if interactive: continue
                        else: break

            print(f"\n{BLUE}🔍 Escaneando todo el sistema en busca de motores...{NC}")
            proc = subprocess.run("find / -type d -name '.ocr-indexer' 2>/dev/null", shell=True, capture_output=True, text=True)
            rutas = proc.stdout.strip().split('\n')
            nuevos_motores = [r.replace('/.ocr-indexer', '') for r in rutas if r]
            if not nuevos_motores:
                if interactive: continue
                else: break
            try:
                with open(cache_file, 'w') as f: json.dump(nuevos_motores, f, indent=4)
            except: pass
            print(f"\n{BLUE}===================================================={NC}")
            for i, motor in enumerate(nuevos_motores, 1): print(f"  {GREEN}[{i:<2}]{NC} {motor}")
            print(f"{BLUE}----------------------------------------------------{NC}")
            try:
                sel = input(f"{YELLOW}Elige el número para viajar a esa carpeta: {NC}").strip()
                if sel.isdigit() and 1 <= int(sel) <= len(nuevos_motores):
                    _goto_path = nuevos_motores[int(sel)-1]
                    _save_goto_path(_goto_path)
                    os.chdir(_goto_path)
                    print(f"{GREEN}🚀 Motor activo: {_goto_path}{NC}")
            except: pass
            if interactive: continue
            else: break

        if comando == "shell-setup":
            targets = []
            for rc in ("~/.bashrc", "~/.zshrc"):
                p = os.path.expanduser(rc)
                if os.path.exists(p):
                    targets.append(p)
            if not targets:
                targets.append(os.path.expanduser("~/.bashrc"))
            installed = False
            for target in targets:
                try:
                    content = open(target).read() if os.path.exists(target) else ""
                except Exception:
                    content = ""
                if "OCR-Indexer shell integration" in content:
                    print(f"{YELLOW}⚠️  Ya instalado en {target}{NC}")
                    installed = True
                    continue
                with open(target, 'a') as f:
                    f.write(_SHELL_FUNC)
                print(f"{GREEN}✅ Función ocre() instalada en {target}{NC}")
                installed = True
            if installed:
                print(f"\n{YELLOW}Ejecuta: source ~/.bashrc  (o abre una nueva terminal){NC}")
                print(f"{GREEN}Luego usa 'ocre' para viajar a un motor y hacer cd automáticamente.{NC}")
            if not interactive: break
            continue

        if comando == "dir":
            target = args[1] if len(args) > 1 else os.getcwd()
            engine_path = os.path.join(target, '.ocr-indexer')
            if not os.path.exists(engine_path):
                os.makedirs(engine_path)
                with open(os.path.join(engine_path, 'config.json'), 'w') as f:
                    json.dump({"root": target, "blacklist": ["Talk", "trashbin"], "tolerancias": {"O": "0", "0": "O"}}, f, indent=4)
                with open(os.path.join(engine_path, 'ocr-indexer.json'), 'w') as f: json.dump({}, f)
                print(f"{GREEN}✅ Motor local creado en: {target}{NC}")
            if not interactive: break
            continue

        if comando == "help":
            cmd_help()
            if not interactive: break
            continue

        if not engine:
            print(f"{RED}❌ No se detectó ningún motor aquí.{NC}")
            if not interactive: break
            continue

        config_path = os.path.join(engine, 'config.json')
        index_path = os.path.join(engine, 'ocr-indexer.json')
        status_path = os.path.join(engine, 'status.json')
        log_path = os.path.join(engine, 'indexer.log')

        def cargar_config():
            with open(config_path, 'r') as f: return json.load(f)
        def guardar_config(data):
            with open(config_path, 'w') as f: json.dump(data, f, indent=4)

        if comando == "run":
            print(f"\n{GREEN}🚀 PIPELINE COMPLETO{NC}")
            print(f"  Motor   : {GREEN}{engine}{NC}")
            print(f"  Flujo   : Fase 1 (escaneo) → Fase 2 (Tesseract) → Fase 3 (IA) + vigía tmux")
            with open(log_path, 'a') as log_file:
                subprocess.Popen([sys.executable, "-u", script_indexer, engine],
                                 stdout=log_file, stderr=log_file)
            print(f"  {GREEN}✅ Indexador iniciado en background.{NC} {YELLOW}(ocr-indexer logs para ver progreso){NC}")
            print(f"  {YELLOW}Adjuntando vigía tmux...{NC}\n")
            cmd_watch_tmux(engine, script_watcher)
            # os.execlp en cmd_watch_tmux reemplaza el proceso — no se llega aquí
            if not interactive: break
            continue

        if comando == "start":
            print(f"{GREEN}🚀 Iniciando indexador...{NC}")
            with open(log_path, 'a') as log_file:
                subprocess.Popen([sys.executable, "-u", script_indexer, engine], stdout=log_file, stderr=log_file)

        elif comando == "watch":
            print(f"{GREEN}👁️  Iniciando vigía de archivos nuevos...{NC}")
            with open(log_path, 'a') as log_file:
                subprocess.Popen([sys.executable, "-u", script_watcher, engine], stdout=log_file, stderr=log_file)
            print(f"{YELLOW}El vigía corre en background. Los archivos nuevos se indexarán automáticamente.{NC}")

        elif comando == "watch-tmux":
            cmd_watch_tmux(engine, script_watcher)

        elif comando == "watch-tmux-stop":
            cmd_watch_tmux_stop(engine)

        elif comando == "watch-tmux-status":
            cmd_watch_tmux_status(engine)

        elif comando == "reindex":
            cmd_reindex(engine, index_path, script_indexer, args)

        elif comando == "refine":
            cmd_refine(engine, index_path, config_path, args)

        elif comando == "ai-config":
            cmd_ai_config(engine, config_path)

        elif comando == "logs":
            try:
                subprocess.run(["tail", "-n", "50", log_path])
            except Exception as e:
                print(f"{RED}Error leyendo logs: {e}{NC}")

        elif comando == "stop":
            subprocess.run(["pkill", "-f", engine])
            print(f"{RED}🛑 Indexador y vigía detenidos.{NC}")

        elif comando in ["status", "stats"]:
            print(f"{BLUE}================ ESTADO DEL MOTOR ================{NC}")
            proc = subprocess.run(["pgrep", "-f", engine], capture_output=True, text=True)
            estado_proceso = f"{GREEN}CORRIENDO{NC}" if proc.stdout.strip() else f"{RED}DETENIDO{NC}"

            try:
                with open(status_path, 'r') as f:
                    st = json.load(f)
            except:
                st = {"fase": "Iniciando o Inactivo"}

            fase = st.get("fase", "Desconocida")
            print(f"Estado General : {estado_proceso}")
            print(f"Fase Actual    : {YELLOW}{fase}{NC}")

            if fase == "Procesando OCR":
                total = st.get("total_ocr", 0)
                procesados = st.get("procesados", 0)
                faltantes = total - procesados
                porcentaje = (procesados / total * 100) if total > 0 else 100
                inicio = st.get("inicio_ocr", time.time())
                transcurrido = time.time() - inicio
                velocidad = procesados / transcurrido if transcurrido > 0 and procesados > 0 else 0
                eta = faltantes / velocidad if velocidad > 0 else 0

                print(f"\n{BLUE}--- RENDIMIENTO OCR ---{NC}")
                print(f"Progreso       : {GREEN}{draw_progress(porcentaje)}{NC}")
                print(f"Completados    : {procesados} archivos")
                print(f"Faltantes      : {YELLOW}{faltantes} archivos{NC}")
                print(f"T. Transcurrido: {format_time(transcurrido)}")
                print(f"ETA (Restante) : {YELLOW}{format_time(eta)}{NC}")

            if comando == "stats":
                print(f"\n{BLUE}--- ESTADÍSTICAS DE ARCHIVOS ---{NC}")
                try:
                    with open(index_path, 'r') as f:
                        data = json.load(f)
                    stats = {}
                    for item in data.values():
                        ext = item.get("archivo", {}).get("extension", "unknown")
                        stats[ext] = stats.get(ext, 0) + 1
                    for k, v in stats.items():
                        print(f"  {k.upper()}: {v}")
                except:
                    print(f"{YELLOW}Aún no hay datos analizados.{NC}")
            print(f"{BLUE}===================================================={NC}")

        elif comando == "last":
            try:
                with open(index_path, 'r') as f:
                    db = json.load(f)

                items = list(db.items())
                ultimos = items[-10:] if len(items) >= 10 else items

                print(f"\n{BLUE}===================================================={NC}")
                print(f"{YELLOW}       ÚLTIMOS {len(ultimos)} ARCHIVOS EN EL ÍNDICE       {NC}")
                print(f"{BLUE}===================================================={NC}")

                if not ultimos:
                    print(f"{YELLOW}El índice está vacío.{NC}")
                else:
                    for filepath, data in reversed(ultimos):
                        if isinstance(data, dict):
                            nombre = data.get("archivo", {}).get("nombre", "Desconocido")
                            ext = data.get("archivo", {}).get("extension", "")
                            estado = data.get("estado", "Desconocido")

                            texto = data.get("contenido", {}).get("texto_ocr", "")
                            if texto:
                                texto = texto[:60].replace('\n', ' ') + "..."
                            else:
                                texto = "(Sin texto extraído aún)"

                            color_estado = GREEN if estado == "completado" else YELLOW

                            print(f"{BLUE}📄 {nombre}.{ext}{NC}")
                            print(f"   Estado: {color_estado}{estado.upper()}{NC}")
                            print(f"   Texto : {texto}\n")

                print(f"{BLUE}===================================================={NC}")
                input(f"{YELLOW}Presiona [Enter] para regresar al menú...{NC}")

            except Exception as e:
                print(f"{RED}Error al leer el índice: {e}{NC}")

        elif comando == "search":
            subprocess.run([sys.executable, script_searcher, index_path])

        elif comando == "tol-list":
            data = cargar_config()
            tols = data.get("tolerancias", {})
            print(f"\n{BLUE}--- REGLAS DE TOLERANCIA OCR ---{NC}")
            if not tols: print(f"{YELLOW}No hay reglas activas.{NC}")

            impresos = set()
            for orig, reemp in tols.items():
                if orig not in impresos:
                    if tols.get(reemp) == orig:
                        print(f"  {GREEN}{orig} <=> {reemp}{NC} (Bidireccional)")
                        impresos.add(orig)
                        impresos.add(reemp)
                    else:
                        print(f"  {GREEN}{orig}  => {reemp}{NC}")
                        impresos.add(orig)
            print("")

        elif comando == "tol-add":
            while True:
                data = cargar_config()
                tols = data.get("tolerancias", {})

                print(f"\n{BLUE}--- AÑADIR NUEVAS REGLAS ---{NC}")
                if tols:
                    reglas_str = ", ".join([f"{k}:{v}" for k, v in tols.items()])
                    print(f"{GREEN}Reglas actuales: {NC}{reglas_str}")
                else:
                    print(f"{YELLOW}Reglas actuales: Ninguna{NC}")

                print(f"{YELLOW}Formato: original:reemplazo, original:reemplazo{NC}")
                entrada = input(f"{YELLOW}Ejemplo (O:0, I:1): {NC}").strip()

                if not entrada: break

                tols_ref = data.setdefault("tolerancias", {})
                agregadas = 0

                reglas = entrada.split(',')
                for regla in reglas:
                    if ':' in regla:
                        orig, reemp = regla.split(':', 1)
                        orig = orig.strip()
                        reemp = reemp.strip()

                        if orig and reemp:
                            tols_ref[orig] = reemp
                            tols_ref[reemp] = orig
                            agregadas += 2
                            print(f"  {GREEN}✅ Añadida: '{orig}' <=> '{reemp}' (Bidireccional){NC}")
                    else:
                        if regla.strip():
                            print(f"  {RED}❌ Ignorado (falta formato ':'): '{regla.strip()}'{NC}")

                if agregadas > 0:
                    guardar_config(data)

                print(f"{BLUE}----------------------------------------------------{NC}")
                resp = input(f"{YELLOW}¿Qué deseas hacer? [Enter] Añadir más | [M] Menú | [S] Salir: {NC}").strip().lower()
                if resp == 's': sys.exit(0)
                elif resp == 'm': break

        elif comando == "tol-rm":
            if len(args) > 1:
                data = cargar_config()
                tols = data.get("tolerancias", {})
                orig = args[1]
                if orig in tols:
                    inverso = tols[orig]
                    del tols[orig]
                    print(f"{YELLOW}🗑️ Regla '{orig}' eliminada.{NC}")
                    if inverso in tols and tols[inverso] == orig:
                        del tols[inverso]
                        print(f"{YELLOW}🗑️ Espejo '{inverso}' eliminado automáticamente.{NC}")
                    data["tolerancias"] = tols
                    guardar_config(data)
            else:
                while True:
                    data = cargar_config()
                    tols = data.get("tolerancias", {})
                    if not tols:
                        print(f"{YELLOW}No hay reglas para eliminar.{NC}")
                        time.sleep(1)
                        break

                    print(f"\n{BLUE}--- REGLAS ACTUALES ---{NC}")
                    reglas_str = ", ".join([f"{k}:{v}" for k, v in tols.items()])
                    print(f"{GREEN}{reglas_str}{NC}")

                    orig = input(f"\n{YELLOW}Escribe el caracter de la regla a eliminar (Respetando Mayúsculas): {NC}").strip()
                    if not orig: break

                    if orig in tols:
                        inverso = tols[orig]
                        del tols[orig]
                        print(f"{YELLOW}🗑️ Regla '{orig}' eliminada.{NC}")
                        if inverso in tols and tols[inverso] == orig:
                            del tols[inverso]
                            print(f"{YELLOW}🗑️ Espejo '{inverso}' eliminado automáticamente.{NC}")

                        data["tolerancias"] = tols
                        guardar_config(data)
                    else:
                        print(f"{RED}❌ No existe regla para '{orig}'.{NC}")

                    print(f"{BLUE}----------------------------------------------------{NC}")
                    resp = input(f"{YELLOW}¿Qué deseas hacer? [Enter] Eliminar otra | [M] Menú | [S] Salir: {NC}").strip().lower()
                    if resp == 's': sys.exit(0)
                    elif resp == 'm': break

        elif comando == "tol-clear":
            res = input(f"{YELLOW}¿Seguro que deseas eliminar TODAS las reglas de tolerancia? (s/n): {NC}")
            if res.lower() == 's':
                data = cargar_config()
                data["tolerancias"] = {}
                guardar_config(data)
                print(f"{RED}♻️ Tolerancias borradas exitosamente.{NC}")

        elif comando == "blacklist-list":
            print('\n'.join(cargar_config().get('blacklist', [])))
        elif comando == "blacklist-add":
            if len(args) > 1:
                data = cargar_config()
                if args[1] not in data.get('blacklist', []):
                    data.setdefault('blacklist', []).append(args[1])
                    guardar_config(data)
                print(f"{GREEN}✅ '{args[1]}' añadido a la lista negra.{NC}")
        elif comando == "blacklist-rm":
            if len(args) > 1:
                data = cargar_config()
                if args[1] in data.get('blacklist', []):
                    data['blacklist'].remove(args[1])
                    guardar_config(data)
                print(f"{YELLOW}🗑️ '{args[1]}' eliminado de la lista negra.{NC}")

        elif comando == "install-deps":
            cmd_install_deps()

        elif comando == "reset":
            res = input(f"{YELLOW}¿Borrar el índice? Esto no se puede deshacer. (s/n): {NC}")
            if res.lower() == 's':
                with open(index_path, 'w') as f:
                    json.dump({}, f)
                for extra in [status_path, os.path.join(engine, 'inverted.json')]:
                    if os.path.exists(extra):
                        os.remove(extra)
                print(f"{GREEN}✅ Índice reiniciado. Usa 'start' para re-indexar.{NC}")

        if not interactive:
            break

if __name__ == "__main__":
    main()
