"""
Limpia los tags de ROPA incrustados en las lineas de personajes.txt.

Cada LoRA fue entrenado por alguien distinto y su linea puede llevar el outfit
entendido como identidad. Eso pelea con los wildcards de ropa: el prompt acaba
con dosPorque hay dos conjuntos de ropa compitiendo.

Se conservan los ACCESORIOS (orejas, lazitos, diademas) porque forman parte de
la identidad, y se quitan las PRENDAS (vestidos, camisas, faldas, uniformes).

    python Engine/clean_outfit_tags.py            # dry-run: solo el informe
    python Engine/clean_outfit_tags.py --apply    # escribe personajes.txt
"""
import argparse
import re
import shutil
import sys
from collections import Counter
from datetime import datetime
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(Path(__file__).resolve().parent))
from wd14_harvest import OUTFIT, DROP_IDENTITY  # noqa: E402

CHAR_FILE = BASE_DIR / "Archivos" / "personajes.txt"
BACKUP = BASE_DIR / "_legacy" / "Datos" / "personajes_216_antes_de_limpiar.txt"
REPORT = BASE_DIR / "_legacy" / "Datos" / "limpieza_outfit_informe.txt"

# Accesorios: forman parte de la identidad, se quedan. Se comparan con los
# espacios normalizados a guiones bajos ('yellow ribbon' == 'yellow_ribbon').
KEEP_EXACT = {
    "earrings", "earring", "stud_earrings", "crystal_earrings", "heart_earrings",
    "ear_cover", "ear_covers", "ear_piercing", "ear_gauntlet",
    "hair_ornament", "hair_bow", "hair_ribbon", "hairclip", "hair_flower",
    "hairpin", "hairband", "headband", "headwear", "hair_tubes", "hair_tie",
    "hair_bobbles", "scrunchie", "hair_stick", "hairpin",
    "hat", "cap", "beret", "tokin_hat", "sun_hat", "baseball_cap", "beanie",
    "helmet", "crown", "tiara", "circlet", "veil", "headdress", "bonnet",
    "maid_headdress", "monocle", "piercing",
    "jewelry", "necklace", "pendant", "bracelet", "wristband", "anklet",
    "armband", "ring", "brooch", "choker", "collarbone", "badge",
    "ribbon", "bow", "collar", "detached_collar", "glasses", "sunglasses",
    "blindfold", "eyepatch",
}

# Cualquier tag con estos sufijos es accesorio (colores/formas/series).
ACCESSORY_SUFFIX = (
    "_ribbon", "_bow", "_earrings", "_hair_ornament", "_choker", "_necklace",
    "_hair_bow", "_hair_ribbon", "_glasses", "_hat", "_cap", "_headband",
    "_crown", "_tiara", "_veil", "_headdress", "_pendant", "_bracelet",
)

NEVER_DROP = {"1girl", "solo", "mature_female", "collarbone"}


def norm(t):
    return t.lower().replace(" ", "_").replace("-", "_")


def parse(line):
    m = re.search(r"<lora:([^:>]+):[^>]*>", line)
    head = m.group(0) if m else ""
    body = line[len(head):]
    tags = [t.strip() for t in body.split(",") if t.strip()]
    return head, tags


def is_accessory(t):
    low = norm(t)
    if low in NEVER_DROP:
        return True
    return low in KEEP_EXACT or low.endswith(ACCESSORY_SUFFIX)


def is_clothing(t):
    low = norm(t)
    if low in NEVER_DROP or is_accessory(low):
        return False
    if DROP_IDENTITY.search(low):
        return False
    return bool(OUTFIT.search(low))


def clean_line(line):
    head, tags = parse(line)
    keep = [t for t in tags if not is_clothing(t)]
    dropped = [t for t in tags if is_clothing(t)]
    if not dropped:
        return line, []
    sep = " " if head else ""
    return head + sep + ", ".join(keep), dropped


def main():
    p = argparse.ArgumentParser(description="Quita ropa incrustada en personajes.txt")
    p.add_argument("--apply", action="store_true", help="escribe el fichero (sin esto, dry-run)")
    args = p.parse_args()

    lines = [l.strip() for l in CHAR_FILE.open(encoding="utf-8-sig") if l.strip()]
    out, report = [], []
    for l in lines:
        new, dropped = clean_line(l)
        out.append(new)
        if dropped:
            head, _ = parse(l)
            report.append((head or l[:34], dropped, len(parse(l)[1]) - len(dropped)))

    removed = Counter()
    total_removed = 0
    for _, d, _ in report:
        removed.update(d)
        total_removed += len(d)

    print(f"{len(lines)} lineas | {len(report)} modificadas | "
          f"{total_removed} tags de ropa eliminados")

    print("\n=== tags mas quitados ===")
    for t, n in removed.most_common(30):
        print(f"  {n:3}x  {t}")

    print("\n=== lineas modificadas (muestra) ===")
    for head, d, kept in report[:25]:
        print(f"  {head[:50]:52} -{len(d):2} -> {kept:2} tags")
        print(f"       quita: {', '.join(d)}")

    if not args.apply:
        print("\n[DRY-RUN] no se ha escrito nada. Usa --apply para aplicarlo.")
        return

    if BACKUP.exists():
        print(f"\n[!] El backup ya existe, no lo sobrescribo: {BACKUP}")
        raise SystemExit(1)

    BACKUP.parent.mkdir(parents=True, exist_ok=True)
    shutil.copy2(CHAR_FILE, BACKUP)
    CHAR_FILE.write_text("\n".join(out) + "\n", encoding="utf-8", newline="\n")

    lines_report = [
        "LIMPIEZA DE ROPA EN personajes.txt",
        f"generado {datetime.now():%Y-%m-%d %H:%M}",
        f"backup: {BACKUP.name}",
        f"{len(lines)} lineas | {len(report)} modificadas | {total_removed} tags eliminados",
        "",
    ]
    for head, d, kept in report:
        lines_report.append(f"{head}  ->  {kept} tags")
        lines_report.append(f"    quita: {', '.join(d)}")
    REPORT.write_text("\n".join(lines_report) + "\n", encoding="utf-8")

    print(f"\n[OK] backup  -> {BACKUP}")
    print(f"[OK] escrito -> {CHAR_FILE}")
    print(f"[OK] informe -> {REPORT}")


if __name__ == "__main__":
    main()