#!/usr/bin/env python3
"""Promote existing pool turn views into role-qualified shelf slots.

This tool copies blessed pool files only. It never generates images, never
deletes pool sources, and validates the full mapping before writing anything.
"""

from __future__ import annotations

import argparse
import json
import shutil
from pathlib import Path
from typing import Mapping

from recoil.core.paths import ProjectPaths
from recoil.core.ref_resolver import TURN_VIEWS, resolve_character_bundle
from recoil.core.ref_stem import ref_filename, subject_id_norm

IMAGE_SUFFIXES = {".png", ".jpg", ".jpeg", ".webp"}


def _validate_pool_relpath(pool_relpath: str) -> Path:
    rel = Path(pool_relpath)
    if rel.is_absolute():
        raise ValueError(f"pool_relpath must be relative: {pool_relpath!r}")
    if ".." in rel.parts:
        raise ValueError(f"pool_relpath may not contain '..': {pool_relpath!r}")
    return rel


def promote_turn_views(
    project: str,
    subject: str,
    *,
    mapping: Mapping[str, str],
    apply: bool = False,
) -> dict:
    """Copy existing pool turn views into canonical shelf turn slots.

    Args:
        project: Project slug passed to ProjectPaths.for_project().
        subject: Character subject id.
        mapping: {view: pool_relpath}; pool_relpath is relative to the subject's
            base look dir, e.g. "pool/turn/jade_turn_front_v01.png".
        apply: False returns a validated plan without writing.

    Returns:
        A plan/result dict containing the promoted view list and copy paths.
    """
    paths = ProjectPaths.for_project(project)
    subject_norm = subject_id_norm(subject)
    look_dir = paths.asset_look_dir("char", subject_norm)
    planned: list[dict] = []

    for view, pool_relpath in mapping.items():
        if view not in TURN_VIEWS:
            raise ValueError(f"invalid turn view {view!r}; expected one of {TURN_VIEWS!r}")
        rel = _validate_pool_relpath(pool_relpath)
        src = look_dir / rel
        if src.is_symlink():
            raise ValueError(f"pool source may not be a symlink: {src}")
        if not src.is_file():
            raise ValueError(f"pool source does not exist or is not a file: {src}")
        if src.suffix.lower() not in IMAGE_SUFFIXES:
            raise ValueError(f"pool source is not a supported image: {src}")
        dst = look_dir / "turn" / ref_filename(
            subject,
            "turn",
            src.suffix.lstrip("."),
            view=view,
        )
        planned.append({
            "view": view,
            "src": str(src),
            "dst": str(dst),
        })

    result = {
        "project": project,
        "subject": subject,
        "apply": apply,
        "promoted": [item["view"] for item in planned],
        "copies": planned,
    }
    if not apply:
        return result

    for item in planned:
        src = Path(item["src"])
        dst = Path(item["dst"])
        dst.parent.mkdir(parents=True, exist_ok=True)
        shutil.copy2(src, dst)

        bundle = resolve_character_bundle(paths, subject)
        if not any(
            asset.kind == "turn"
            and asset.view == item["view"]
            and asset.source == "shelf"
            for asset in bundle.assets
        ):
            raise RuntimeError(
                f"promoted turn view did not resolve from shelf: {subject} {item['view']}"
            )

    return result


def _parse_view_flags(view_flags: list[str] | None) -> dict[str, str]:
    mapping: dict[str, str] = {}
    for flag in view_flags or []:
        if "=" not in flag:
            raise ValueError(f"--view must be KEY=RELPATH, got {flag!r}")
        view, relpath = flag.split("=", 1)
        if not view or not relpath:
            raise ValueError(f"--view must be KEY=RELPATH, got {flag!r}")
        mapping[view] = relpath
    return mapping


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Promote existing pool turn views into canonical shelf slots."
    )
    parser.add_argument("--project", default="tartarus")
    parser.add_argument("--subject", required=True)
    parser.add_argument(
        "--view",
        action="append",
        help="Turn-view mapping as KEY=RELPATH, repeatable.",
    )
    parser.add_argument("--apply", action="store_true")
    args = parser.parse_args()

    mapping = _parse_view_flags(args.view)
    result = promote_turn_views(
        args.project,
        args.subject,
        mapping=mapping,
        apply=args.apply,
    )
    print(json.dumps(result, indent=2, sort_keys=True))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
