#!/usr/bin/env python3
"""
asset_naming.py — Naming convention for generated visual assets.

Format: {PRJ}_EP{NNN}_S{NN}_T{NN}_{CHAR}[_{suffix}].{ext}

The project code (PRJ) is read from the project's ORCHESTRATION.md
(## Asset Code section). Falls back to first 3 letters of folder name uppercase.

Usage:
    # As a module
    from asset_naming import build_asset_name, asset_path, next_take_number, parse_asset_name

    name = build_asset_name("LEV", episode=1, shot=10, take=1, char="JINX")
    # => "LEV_EP001_S10_T01_JINX"

    path = asset_path("leviathan/", episode=1, shot=10, take=1, char="JINX", suffix="_f1", ext="png")
    # => "leviathan/storyboards/assets/ep_001/LEV_EP001_S10_T01_JINX_f1.png"

    # Self-test
    python3 tools/asset_naming.py --test
"""

import argparse
import re
import sys
from pathlib import Path
from typing import Optional

# ── Naming Convention ────────────────────────────────────────────────────

NAMING_REGEX = re.compile(
    r"^(?P<prj>[A-Z]{2,6})"
    r"_EP(?P<ep>\d{3})"
    r"_S(?P<shot>\d{2})"
    r"_T(?P<take>\d{2})"
    r"_(?P<char>[A-Z][A-Z0-9\-]+)"
    r"(?:_(?P<suffix>[a-z0-9_]+))?"
    r"\.(?P<ext>[a-z0-9]+)$"
)

# Valid suffixes (documented in naming convention)
VALID_SUFFIXES = {
    "f1", "f2", "f3",                         # Keyframes
    "strip",                                    # Source triptych strip
    "f1_up", "f2_up", "f3_up",                # Upscaled
    "seg1", "seg2",                             # FLF segments (triptych)
    "flf",                                      # Standard FLF video
    "push",                                     # Ken Burns push video
    "full",                                     # Joined final video
}


def get_asset_code(project_dir: Path) -> str:
    """Read asset code from ORCHESTRATION.md, fallback to first 3 chars of folder name."""
    orch_path = project_dir / "ORCHESTRATION.md"
    if orch_path.is_file():
        text = orch_path.read_text()
        # Look for code on the line after "## Asset Code"
        match = re.search(r"## Asset Code\s*\n+`([A-Z]{2,6})`", text)
        if match:
            return match.group(1)
    # Fallback
    return project_dir.name[:3].upper()


def build_asset_name(
    prj: str,
    episode: int,
    shot: int,
    take: int,
    char: str,
) -> str:
    """Build base asset name without suffix or extension.

    Args:
        prj: Project code (e.g., "LEV").
        episode: Episode number (1-999).
        shot: Shot number (1-99).
        take: Take number (1-99).
        char: Character tag — uppercase, alphabetical for multi-char (e.g., "JINX", "JINX-KIAN", "ENV", "PROP").

    Returns:
        Base name like "LEV_EP001_S10_T01_JINX".
    """
    return f"{prj}_EP{episode:03d}_S{shot:02d}_T{take:02d}_{char}"


def asset_path(
    project_dir,
    episode: int,
    shot: int,
    take: int,
    char: str,
    suffix: str = "",
    ext: str = "png",
) -> Path:
    """Build full file path for an asset.

    Args:
        project_dir: Project directory path.
        episode: Episode number.
        shot: Shot number.
        take: Take number.
        char: Character tag.
        suffix: Asset suffix (e.g., "_f1", "_strip", "_flf"). Include leading underscore.
        ext: File extension without dot.

    Returns:
        Full Path to the asset file inside storyboards/assets/ep_NNN/.
    """
    project_dir = Path(project_dir)
    prj = get_asset_code(project_dir)
    base = build_asset_name(prj, episode, shot, take, char)
    filename = f"{base}{suffix}.{ext}"
    return project_dir / "storyboards" / "assets" / f"ep_{episode:03d}" / filename


def next_take_number(
    project_dir,
    episode: int,
    shot: int,
    char: str,
) -> int:
    """Find the next available take number by scanning existing files.

    Returns:
        Next take number (1 if no existing files, max+1 otherwise).
    """
    project_dir = Path(project_dir)
    prj = get_asset_code(project_dir)
    assets_dir = project_dir / "storyboards" / "assets" / f"ep_{episode:03d}"
    if not assets_dir.is_dir():
        return 1

    prefix = f"{prj}_EP{episode:03d}_S{shot:02d}_T"
    char_part = f"_{char}"
    max_take = 0
    for f in assets_dir.iterdir():
        name = f.name
        if name.startswith(prefix) and char_part in name:
            match = NAMING_REGEX.match(name)
            if match and match.group("char") == char:
                take = int(match.group("take"))
                max_take = max(max_take, take)

    return max_take + 1 if max_take > 0 else 1


def parse_asset_name(filename: str) -> Optional[dict]:
    """Parse a filename into its components. Returns None if format doesn't match."""
    match = NAMING_REGEX.match(filename)
    if not match:
        return None
    return {
        "project_code": match.group("prj"),
        "episode": int(match.group("ep")),
        "shot": int(match.group("shot")),
        "take": int(match.group("take")),
        "char": match.group("char"),
        "suffix": match.group("suffix") or "",
        "ext": match.group("ext"),
    }


def char_tag_from_list(characters: list) -> str:
    """Convert a list of character names to the canonical character tag.

    Args:
        characters: List like ["jinx"], ["jinx", "kian"], or [].

    Returns:
        Tag like "JINX", "JINX-KIAN", "ENV".
    """
    if not characters:
        return "ENV"
    names = sorted(set(c.upper() for c in characters))
    return "-".join(names)


# ── Self-Test ────────────────────────────────────────────────────────────

def run_tests():
    """Run self-tests for the naming convention."""
    passed = 0
    failed = 0

    def check(name, got, expected):
        nonlocal passed, failed
        if got == expected:
            passed += 1
            print(f"  PASS  {name}")
        else:
            failed += 1
            print(f"  FAIL  {name}")
            print(f"        expected: {expected}")
            print(f"        got:      {got}")

    print("asset_naming.py — self-test\n")

    # build_asset_name
    check("basic name",
          build_asset_name("LEV", 1, 10, 1, "JINX"),
          "LEV_EP001_S10_T01_JINX")

    check("multi-char name",
          build_asset_name("LEV", 1, 17, 2, "JINX-KIAN"),
          "LEV_EP001_S17_T02_JINX-KIAN")

    check("env name",
          build_asset_name("LEV", 1, 1, 1, "ENV"),
          "LEV_EP001_S01_T01_ENV")

    check("prop name",
          build_asset_name("ASI", 12, 5, 3, "PROP"),
          "ASI_EP012_S05_T03_PROP")

    # parse_asset_name
    parsed = parse_asset_name("LEV_EP001_S10_T01_JINX_f1.png")
    check("parse basic",
          parsed,
          {"project_code": "LEV", "episode": 1, "shot": 10, "take": 1,
           "char": "JINX", "suffix": "f1", "ext": "png"})

    parsed = parse_asset_name("LEV_EP001_S17_T02_JINX-KIAN_strip.png")
    check("parse multi-char",
          parsed,
          {"project_code": "LEV", "episode": 1, "shot": 17, "take": 2,
           "char": "JINX-KIAN", "suffix": "strip", "ext": "png"})

    parsed = parse_asset_name("LEV_EP001_S10_T01_JINX_f1_up.png")
    check("parse upscaled",
          parsed,
          {"project_code": "LEV", "episode": 1, "shot": 10, "take": 1,
           "char": "JINX", "suffix": "f1_up", "ext": "png"})

    parsed = parse_asset_name("LEV_EP001_S10_T01_JINX_seg1.mp4")
    check("parse video segment",
          parsed,
          {"project_code": "LEV", "episode": 1, "shot": 10, "take": 1,
           "char": "JINX", "suffix": "seg1", "ext": "mp4"})

    parsed = parse_asset_name("LEV_EP001_S04_T01_JINX.png")
    check("parse no suffix",
          parsed,
          {"project_code": "LEV", "episode": 1, "shot": 4, "take": 1,
           "char": "JINX", "suffix": "", "ext": "png"})

    check("parse invalid", parse_asset_name("random_file.png"), None)
    check("parse bad format", parse_asset_name("ep001_shot10.png"), None)

    # char_tag_from_list
    check("char_tag single", char_tag_from_list(["jinx"]), "JINX")
    check("char_tag multi", char_tag_from_list(["kian", "jinx"]), "JINX-KIAN")
    check("char_tag empty", char_tag_from_list([]), "ENV")
    check("char_tag dupes", char_tag_from_list(["jinx", "JINX"]), "JINX")

    # NAMING_REGEX edge cases
    check("regex full suffix",
          bool(NAMING_REGEX.match("LEV_EP001_S10_T01_JINX_f1_up.png")), True)
    check("regex no suffix",
          bool(NAMING_REGEX.match("LEV_EP001_S10_T01_JINX.png")), True)
    check("regex video ext",
          bool(NAMING_REGEX.match("LEV_EP001_S10_T01_JINX_flf.mp4")), True)

    print(f"\n{'=' * 40}")
    print(f"  {passed} passed, {failed} failed")
    print(f"{'=' * 40}")
    return 0 if failed == 0 else 1


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Asset naming convention utility")
    parser.add_argument("--test", action="store_true", help="Run self-tests")
    args = parser.parse_args()

    if args.test:
        sys.exit(run_tests())
    else:
        parser.print_help()
