"""
select_x_posts: SELECTOR de posts para X (Twitter) a partir de un pack.

De un pack (Output/packs/<clave>_<tema>/, con pack_manifest.json + img/) deriva
3 posts SFW-sugerentes y 2 posts explicitos, 4 imagenes cada uno, y los copia a
<pack>/_x_posts/<post>/ con un x_posts_manifest.json. Solo lee el manifest y la
carpeta img/ del pack; no toca Forge ni la GPU.

    python Engine/select_x_posts.py --pack "Output/packs/Furina_playa"
    python Engine/select_x_posts.py --pack "Output/packs/Furina_playa" \
        --sfw 2 --exp 1 --per-post 4

Reglas:
- load_approved: si ALGUN registro trae `verdict` no nulo, solo se toman los
  `aprobada`; si el pack aun no se ha auditado (todos `verdict` = null), se
  toman todos (un pack sin auditar es seleccionable entero).
- SFW desde stages {sugerente, spicy1}; explicito desde
  {spicy2, explicit1, explicit2}. Los packs viejos caen por `nivel`:
  spicy -> spicy1 y explicito -> explicit2.
- Variedad dentro de cada post: no repetir `pose_id` (ni `tema`) si hay
  alternativa; el sesgo es suave (una pose/tema repetido solo se elige cuando
  no queda nada mejor).
- Si un pool tiene menos imagenes de las necesarias, se toma lo que hay y el
  faltante queda escrito en x_posts_manifest.json (nunca falla la seleccion
  entera).
- Nombres: post1_sfw..postN_sfw y luego postN+1_exp.. (la numeracion sigue).
"""
import argparse
import json
import random
import shutil
import sys
from pathlib import Path

from stages import stage_for_record

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

BASE_DIR = Path(__file__).resolve().parent.parent

SFW_STAGES = ("sugerente", "spicy1")
EXPLICIT_STAGES = ("spicy2", "explicit1", "explicit2")

# Semilla fija: la seleccion es reproducible entre corridas.
SHUFFLE_SEED = 0


def load_approved(pack_dir) -> list:
    """Registros seleccionables del pack.

    Si algun registro trae `verdict` no nulo (el pack fue auditado), devuelve
    solo los `verdict == "aprobada"`. Si el pack aun no se ha auditado (todos
    `verdict` = null), devuelve TODOS los registros. Lanza si el manifest falta
    o no es una lista.
    """
    pack_dir = Path(pack_dir)
    manifest = pack_dir / "pack_manifest.json"
    if not manifest.exists():
        raise FileNotFoundError(f"sin pack_manifest.json en {pack_dir}")
    records = json.loads(manifest.read_text(encoding="utf-8"))
    if not isinstance(records, list):
        raise ValueError(f"pack_manifest.json con forma inesperada en {manifest}")
    records = [r for r in records if isinstance(r, dict)]
    audited = any(r.get("verdict") is not None for r in records)
    if audited:
        return [r for r in records if r.get("verdict") == "aprobada"]
    return records


def _pick_variety(remaining, n) -> list:
    """Saca hasta n registros de `remaining` (los consume) maximizando variedad.

    En cada paso elige el registro con menos choques: repetir `pose_id` pesa 2,
    repetir `tema` pesa 1. Con empate gana el primero (orden estable).
    """
    picked = []
    used_pose = set()
    used_tema = set()
    while remaining and len(picked) < n:
        best_i = 0
        best_score = None
        for i, rec in enumerate(remaining):
            score = 0
            pid = rec.get("pose_id")
            tema = tuple(rec.get("tema") or [])
            if pid is not None and pid in used_pose:
                score += 2
            if tema and tema in used_tema:
                score += 1
            if best_score is None or score < best_score:
                best_score = score
                best_i = i
                if score == 0:
                    break
        rec = remaining.pop(best_i)
        picked.append(rec)
        pid = rec.get("pose_id")
        if pid is not None:
            used_pose.add(pid)
        tema = tuple(rec.get("tema") or [])
        if tema:
            used_tema.add(tema)
    return picked


def _pool(records, stages) -> list:
    return [r for r in records if stage_for_record(r) in stages]


def select_posts(records, sfw=3, exp=2, per_post=4) -> list:
    """Reparte los registros en `sfw` posts SFW + `exp` posts explicitos.

    Cada item: {"post": "post1_sfw", "kind": "sfw", "files": [<nombres>]}.
    Los pools se barajan (semilla fija) y se reparten post a post, evitando
    repetir pose/tema dentro de un post cuando hay alternativa. Cada registro se
    usa como mucho una vez; si un pool se agota, los posts restantes salen
    cortos (o vacios).
    """
    rng = random.Random(SHUFFLE_SEED)
    groups = [
        ("sfw", _pool(records, SFW_STAGES), max(0, int(sfw))),
        ("exp", _pool(records, EXPLICIT_STAGES), max(0, int(exp))),
    ]
    per_post = max(0, int(per_post))
    posts = []
    index = 0
    for kind, pool, count in groups:
        remaining = list(pool)
        rng.shuffle(remaining)
        for _ in range(count):
            index += 1
            chosen = _pick_variety(remaining, per_post)
            files = [Path(r.get("file", "")).name for r in chosen]
            posts.append({
                "post": f"post{index}_{kind}",
                "kind": kind,
                "files": [f for f in files if f],
            })
    return posts


def write_posts(pack_dir, posts, per_post=4) -> Path:
    """Copia los ficheros elegidos y escribe el manifest.

    Copia pack_dir/img/<nombre> -> pack_dir/_x_posts/<post>/<nombre> y escribe
    pack_dir/_x_posts/x_posts_manifest.json. Devuelve la ruta del manifest. Una
    imagen ausente se salta con aviso (no aborta). `per_post` solo se usa para
    calcular el faltante por post.
    """
    pack_dir = Path(pack_dir)
    img_dir = pack_dir / "img"
    out_root = pack_dir / "_x_posts"
    out_root.mkdir(parents=True, exist_ok=True)
    per_post = max(0, int(per_post))

    entries = []
    total_shortfall = 0
    for post in posts:
        name = post["post"]
        dest_dir = out_root / name
        dest_dir.mkdir(parents=True, exist_ok=True)
        copied = []
        for fname in post.get("files", []):
            if not fname:
                continue
            src = img_dir / fname
            if src.exists():
                shutil.copy2(src, dest_dir / fname)
                copied.append(fname)
            else:
                print(f"[!] falta la imagen {src}; se omite")
        shortfall = max(0, per_post - len(copied))
        total_shortfall += shortfall
        entries.append({
            "post": name,
            "kind": post.get("kind"),
            "files": copied,
            "count": len(copied),
            "shortfall": shortfall,
        })

    manifest = {
        "pack": pack_dir.name,
        "per_post": per_post,
        "posts": entries,
        "shortfall": total_shortfall,
    }
    manifest_path = out_root / "x_posts_manifest.json"
    manifest_path.write_text(
        json.dumps(manifest, ensure_ascii=False, indent=1), encoding="utf-8")
    return manifest_path


def main():
    p = argparse.ArgumentParser(description="Selector de posts para X (PlumLoom)")
    p.add_argument("--pack", required=True,
                   help="carpeta del pack (con pack_manifest.json e img/)")
    p.add_argument("--sfw", type=int, default=3, help="posts SFW-sugerentes (default 3)")
    p.add_argument("--exp", type=int, default=2, help="posts explicitos (default 2)")
    p.add_argument("--per-post", type=int, default=4, help="imagenes por post (default 4)")
    args = p.parse_args()

    pack_dir = Path(args.pack)
    if not pack_dir.is_dir():
        raise SystemExit(f"[!] no existe la carpeta del pack: {pack_dir}")
    if not (pack_dir / "pack_manifest.json").exists():
        raise SystemExit(f"[!] sin pack_manifest.json en {pack_dir}")

    records = load_approved(pack_dir)
    raw = json.loads((pack_dir / "pack_manifest.json").read_text(encoding="utf-8"))
    approved = sum(1 for r in records if r.get("verdict") == "aprobada")
    if approved:
        print(f"[i] pack auditado: {approved} aprobadas de {len(raw)}")
    elif any(r.get("verdict") is not None for r in raw if isinstance(r, dict)):
        print(f"[i] pack auditado sin aprobadas: 0 de {len(raw)}")
    else:
        print(f"[i] pack sin auditar: {len(records)} registros")

    posts = select_posts(records, sfw=args.sfw, exp=args.exp, per_post=args.per_post)
    manifest_path = write_posts(pack_dir, posts, per_post=args.per_post)

    total = sum(len(post["files"]) for post in posts)
    if total == 0:
        print("[!] no hay imagenes para seleccionar")
    for post in posts:
        files = ", ".join(post["files"]) or "-"
        print(f"  {post['post']} ({post['kind']}): {files}")
    print(f"[OK] {total} imagenes en {len(posts)} posts -> {pack_dir / '_x_posts'}")
    print(f"[OK] manifest: {manifest_path}")


if __name__ == "__main__":
    main()
