"""
CLI de indices de personaje.

Usa la MISMA logica de claves que PromptBuilder (importa compute_character_keys),
así que lo que imprime aqui es exactamente lo que acepta el menu de plumloom_engine.

    python Engine/character_keys.py --list
    python Engine/character_keys.py --random 5
    python Engine/character_keys.py --random 5 --out Archivos/selected_chars.txt
    python Engine/character_keys.py --check        # colisiones / LoRAs sin fichero
"""
import argparse
import difflib
import os
import random
import re
import sys
from datetime import datetime
from pathlib import Path

sys.stdout.reconfigure(encoding="utf-8", errors="backslashreplace")

BASE_DIR = Path(__file__).resolve().parent.parent
CHAR_FILE = BASE_DIR / "Archivos" / "personajes.txt"
LORA_DIR = Path(r"D:\Data\Packages\Stable Diffusion WebUI Forge - Neo\models\Lora")

sys.path.insert(0, str(Path(__file__).resolve().parent))
from prompt_builder import compute_character_keys  # noqa: E402


def load():
    rows = [l.strip() for l in CHAR_FILE.open(encoding="utf-8-sig")
            if l.strip() and not l.strip().startswith("#")]
    return compute_character_keys(rows)


def cmd_list(items, args):
    if args.out:
        write_out(items[:args.limit] if args.limit else items, args.out)
        return
    for i, (key, line, lora) in enumerate(items, 1):
        print(f"{i:3}. {key}")
        if args.verbose:
            print(f"     lora: {lora or '(sin LoRA)'}")
            print(f"     {line[:110]}")


def write_out(items, out_path):
    out = Path(out_path)
    if not out.is_absolute():
        out = BASE_DIR / out
    out.parent.mkdir(parents=True, exist_ok=True)
    stamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    with out.open("w", encoding="utf-8", newline="\n") as f:
        f.write(f"# {len(items)} personajes | generado {stamp}\n")
        f.write("# formato: <clave>|<linea de personajes.txt>\n")
        for key, line, _ in items:
            f.write(f"{key}|{line}\n")
    print(f"Guardados {len(items)} en {out}")


def cmd_random(items, args):
    n = min(args.count, len(items))
    picked = random.sample(items, n)
    out = args.out or (BASE_DIR / "Archivos" / "selected_chars.txt")
    write_out(picked, out)
    print()
    print(f"=== {n} PERSONAJES (pasa la CLAVE al menu [1]/[2]/[3]) ===")
    for key, line, lora in picked:
        print(f"  {key}")
        print(f"      {line[:100]}")


def lora_stems():
    """Nombres de LoRA disponibles. Forge/kohya anade sufijos tipo -10 o -1234,
    asi que se indexa tanto el stem exacto como el stem sin ese sufijo numerico."""
    out = {}
    if not LORA_DIR.exists():
        return out
    for p in LORA_DIR.rglob("*.safetensors"):
        out.setdefault(p.stem.lower(), p.name)
        base = re.sub(r"-\d+$", "", p.stem)
        if base != p.stem:
            out.setdefault(base.lower(), p.name)
    return out


def cmd_check(items, args):
    print(f"lineas: {len(items)} | claves unicas: {len(set(k for k, _, _ in items))}")

    available = lora_stems()
    print(f"LoRAs en disco: {len(available)} nombres")
    if not available:
        print(f"AVISO: no se encuentra {LORA_DIR}")
        return

    missing = [(k, l) for k, _, l in items if l and l.lower() not in available]
    if missing:
        print(f"\nLO RAs SIN FICHERO EN DISCO ({len(missing)}): el prompt NO cargara el LoRA")
        print("  (el personaje saldra con el aspecto por defecto del checkpoint)")
        print()
        for k, l in missing:
            print(f"  {k}")
            close = difflib.get_close_matches(l.lower(), list(available), n=3, cutoff=0.6)
            if close:
                print(f"     ¿quizas quisiste decir? {', '.join(available[c] for c in close)}")
            print(f"     {l[:100]}")
    else:
        print("OK: todos los LoRA existen en disco")

    sin_lora = [k for k, _, l in items if not l]
    if sin_lora:
        print(f"\nLINEAS SIN LoRA ({len(sin_lora)}): responden por defecto del checkpoint")
        for k in sin_lora:
            print(f"  {k}")


def main():
    p = argparse.ArgumentParser(description="Indices de personaje de PlumLoom")
    p.add_argument("--list", action="store_true", help="lista todas las claves")
    p.add_argument("--random", type=int, metavar="N", dest="count",
                   help="selecciona N al azar")
    p.add_argument("--check", action="store_true", help="colisiones y LoRAs ausentes")
    p.add_argument("--out", help="fichero destino (por defecto Archivos/selected_chars.txt)")
    p.add_argument("--limit", type=int, help="limita el numero de lineas con --list --out")
    p.add_argument("--verbose", "-v", action="store_true", help="muestra lora y linea")
    args = p.parse_args()

    items = load()
    if args.check:
        cmd_check(items, args)
    elif args.count:
        cmd_random(items, args)
    elif args.list:
        cmd_list(items, args)
    else:
        p.print_help()


if __name__ == "__main__":
    main()