#!/usr/bin/env python3
"""
filmkit.py — the spine of the kit.

One command: `filmkit new "Client - Project"`

Creates a full client project folder, drops a Claude brief template, writes a
START_HERE.md telling you exactly the next 6 things to do, and prepares the
plumbing for Bridge / cull / cutdowns / ingest.

Usage:
  python3 filmkit.py new "Atelier London - Anya Wedding"
  python3 filmkit.py new "Darb - Founder Doc 03" --type doc --fps 24
  python3 filmkit.py new "Cocoon Studios - Podcast Q2" --type podcast
  python3 filmkit.py new "client" --root ~/Footage          # custom parent dir
  python3 filmkit.py list
  python3 filmkit.py status   # in project root

Project types: wedding (default), doc, podcast, commercial
Standard FPS: wedding 25 (UK) / 24 (cinematic), doc 23.976, podcast 24

Python 3.9+. Standard library only.
"""
from __future__ import annotations

import argparse
import datetime as _dt
import re
import sys
from pathlib import Path

PROJECT_TYPES = ("wedding", "doc", "podcast", "commercial")
DEFAULT_FPS = {
    "wedding":    25.0,
    "doc":        23.976,
    "podcast":    24.0,
    "commercial": 24.0,
}

FOLDER_TREE = """\
00_ADMIN
01_BRIEF
02_CARD_BACKUPS/A_CAM
02_CARD_BACKUPS/B_CAM
02_CARD_BACKUPS/C_CAM
02_CARD_BACKUPS/AUDIO
02_CARD_BACKUPS/DRONE
03_TRANSCODES/PROXIES
03_TRANSCODES/AUDIO_WAV
04_RESOLVE
05_CLAUDE/transcripts
05_CLAUDE/outlines
05_CLAUDE/cut_notes
06_MUSIC_SFX
07_EXPORTS/review
07_EXPORTS/final
07_EXPORTS/social
08_ARCHIVE
""".strip().splitlines()


def slugify(text: str) -> str:
    s = re.sub(r"[^a-zA-Z0-9]+", "-", text.strip()).strip("-")
    return s or "project"


def project_brief_template(name: str, ptype: str, fps: float, date: str) -> str:
    return f"""\
# {name} — brief

Created: {date}
Type: {ptype}
Target FPS: {fps}

## One-line pitch
(What is this film about, in one sentence?)

## Audience
(Who watches it, where, on what device?)

## Story spine (BBC-Earth template — fill the gaps)
- **Exposition** — establishing world, tone, characters
- **Hero** — protagonist intro + their stake
- **Villain / Pressure** — the obstacle (person, condition, time)
- **Valley** — lowest moment, doubt
- **Crescendo** — turning point, momentum builds
- **Release** — resolution, what changed

## Must-have shots
-
-

## Must-have lines / quotes
-

## References (films, reels, photos)
-

## Delivery
- Master: ProRes 422 HQ, [resolution], [duration target]
- Web: H.264 1080p, [target bitrate]
- Social: 9:16 reel ([cutdown count]), 1:1 feed ([cutdown count])
- Deadline:
- Review rounds budgeted:

## Music direction
- Tone:
- BPM range:
- Reference tracks:

## Risks / open questions
-
"""


def claude_project_seed(name: str, ptype: str, fps: float) -> str:
    return f"""\
# Claude Project — {name}

Drop this file into your Claude.ai Project. It carries the rules for any
conversation about this film.

## Custom instructions (paste into Claude Project settings)

You are helping me edit "{name}" (type: {ptype}, target FPS: {fps}).

When I give you raw material (interview transcript, footage description,
mood notes), produce one of these on request:

1. **Outline (Bridge-ready)** — markdown headings, format exactly:
       ## HH:MM:SS — BEAT NAME
       1–3 sentences of shot description.
   Beat names from: EXPOSITION / HERO / VILLAIN / VALLEY / CRESCENDO / RELEASE.

2. **Radio edit script** — selected interview lines with clean transitions,
   targeting [duration]. Mark each line `[speaker name] [in: 00:01:23] [out: 00:01:34]`.

3. **Cutdown sheet** — markdown table: start | end | label, suitable for
   the `cutdowns` CLI.

4. **Music brief** — 2-3 sentences of vibe, BPM range, two reference tracks
   I can search in Artlist.

Hard rules:
- Stay in {fps} fps timebase. All timecodes in HH:MM:SS form.
- Storytelling beats use BBC-Earth-style narrative arc, not influencer hooks.
- If I paste in a transcript with timecodes, treat that as ground truth.
- If I ask for "the outline", default to format #1 above.

## Files to attach to the Project

- This file (claude-project.md)
- The brief.md from this folder
- The film's references folder (drag the markdown notes in)
- The transcript SRT once it exists (`05_CLAUDE/transcripts/`)
"""


def start_here(name: str, ptype: str, fps: float, root: Path, kit_dir: Path) -> str:
    kit_rel = kit_dir.resolve()
    return f"""\
# START HERE — {name}

You're standing in your project folder. Here's the order.

## 1. Open the brief (5 min)
Edit `01_BRIEF/brief.md`. Fill the one-line pitch + audience + delivery spec.
Don't worry about the rest yet.

## 2. Ingest cards (10–30 min, depends on size)
When cards come back, dual-copy + checksum:

    python3 "{kit_rel / 'ingest.py'}" /Volumes/A_CAM_SD ./02_CARD_BACKUPS/A_CAM

That writes a manifest you can verify before formatting the card. **Never
format the card until ingest exits 0 and the manifest is on both drives.**

## 3. QC the dailies (5–10 min)
Catch bad takes before they slow your edit:

    python3 "{kit_rel / 'cull.py'}" ./02_CARD_BACKUPS/A_CAM

Reads `cull-report.md` — flags dead-air, clipping, black frames.

## 4. Transcribe interviews (15–60 min, set and forget)
Use Resolve Studio's built-in Transcribe (Window → Subtitles → Create from
audio), or the Bridge README explains the Whisper CLI fallback.

Drop the resulting SRT into `05_CLAUDE/transcripts/`.

## 5. Outline in Claude (10 min)
Open a fresh Claude.ai Project. Upload `05_CLAUDE/claude-project.md` to set
the custom instructions. Drag in the transcript. Ask:

    "Give me the outline for this in the Bridge format."

Save the response as `05_CLAUDE/outlines/v1.md`.

## 6. Bridge it into Resolve (30 sec)

    python3 "{kit_rel / 'bridge.py'}" ./05_CLAUDE/outlines/v1.md --fps {fps} \\
        --project "{name}" \\
        --out ./04_RESOLVE/outline-v1.fcpxml

Then in Resolve: File → Import → Timeline → pick the .fcpxml. Your
storytelling beats are now placed at exact timecodes on V1.

## 7. Edit. (The part nobody can automate.)

## 8. Cutdowns (when picture-lock)

    python3 "{kit_rel / 'cutdowns.py'}" ./07_EXPORTS/final/master.mov \\
        ./05_CLAUDE/cut_notes/social.md --aspect 9:16 \\
        --out ./07_EXPORTS/social/

Cut sheet format inside cutdowns.py is `- 00:01:30 - 00:01:45 | Hero shot`.

---

When you're stuck, check `playbooks/` next to the kit for the one that
matches your problem (media offline / wedding backup / color management /
client revision).
"""


def cmd_new(args: argparse.Namespace) -> int:
    name = args.name.strip()
    if not name:
        print("filmkit: name cannot be empty", file=sys.stderr)
        return 2
    if args.type not in PROJECT_TYPES:
        print(f"filmkit: unknown type {args.type!r}. Choices: {', '.join(PROJECT_TYPES)}", file=sys.stderr)
        return 2

    fps = args.fps if args.fps is not None else DEFAULT_FPS[args.type]
    date = _dt.date.today().isoformat()
    folder_name = f"{date}-{slugify(name)}"
    root = args.root.expanduser().resolve()
    project_dir = root / folder_name

    if project_dir.exists() and not args.force:
        print(f"filmkit: {project_dir} already exists. Pass --force to overwrite scaffolding.", file=sys.stderr)
        return 1

    for sub in FOLDER_TREE:
        (project_dir / sub).mkdir(parents=True, exist_ok=True)

    # Kit directory — wherever this file lives
    kit_dir = Path(__file__).parent.resolve()

    (project_dir / "01_BRIEF" / "brief.md").write_text(
        project_brief_template(name, args.type, fps, date), encoding="utf-8"
    )
    (project_dir / "05_CLAUDE" / "claude-project.md").write_text(
        claude_project_seed(name, args.type, fps), encoding="utf-8"
    )
    (project_dir / "START_HERE.md").write_text(
        start_here(name, args.type, fps, root, kit_dir), encoding="utf-8"
    )
    (project_dir / ".filmkit.json").write_text(
        f'{{"name": "{name}", "type": "{args.type}", "fps": {fps}, "created": "{date}"}}\n',
        encoding="utf-8",
    )

    print(f"filmkit: created {project_dir}")
    print(f"  type: {args.type}    fps: {fps}")
    print(f"  → open START_HERE.md inside the new folder")
    return 0


def cmd_list(args: argparse.Namespace) -> int:
    root = args.root.expanduser().resolve()
    if not root.is_dir():
        print(f"filmkit: {root} not a directory", file=sys.stderr)
        return 2
    projects = sorted(p for p in root.iterdir() if p.is_dir() and (p / ".filmkit.json").exists())
    if not projects:
        print(f"filmkit: no projects under {root}")
        return 0
    print(f"filmkit projects under {root}:")
    for p in projects:
        print(f"  {p.name}")
    return 0


def cmd_status(args: argparse.Namespace) -> int:
    cwd = Path.cwd()
    meta = cwd / ".filmkit.json"
    if not meta.exists():
        print(f"filmkit: {cwd} is not a filmkit project (no .filmkit.json)", file=sys.stderr)
        return 1
    print(f"filmkit project: {cwd}")
    print(f"  metadata: {meta.read_text().strip()}")
    print("  folder status:")
    for sub in FOLDER_TREE:
        path = cwd / sub
        if not path.exists():
            print(f"    [missing] {sub}")
            continue
        n = sum(1 for _ in path.iterdir())
        flag = "·" if n == 0 else "•"
        print(f"    {flag} {sub:<40}  {n} item(s)")
    return 0


def main(argv: list[str] | None = None) -> int:
    p = argparse.ArgumentParser(prog="filmkit", description=__doc__.split("\n\n")[0])
    sub = p.add_subparsers(dest="cmd", required=True)

    new = sub.add_parser("new", help="Create a new project")
    new.add_argument("name", help='Project name, e.g. "Atelier London - Anya Wedding"')
    new.add_argument("--type", default="wedding", choices=PROJECT_TYPES,
                     help="Project type. Default: wedding.")
    new.add_argument("--fps", type=float, default=None,
                     help="Override default FPS for the type.")
    new.add_argument("--root", type=Path, default=Path("~/Footage"),
                     help="Parent directory for projects. Default ~/Footage")
    new.add_argument("--force", action="store_true",
                     help="Re-create scaffolding inside an existing folder.")
    new.set_defaults(func=cmd_new)

    ls = sub.add_parser("list", help="List filmkit projects")
    ls.add_argument("--root", type=Path, default=Path("~/Footage"))
    ls.set_defaults(func=cmd_list)

    st = sub.add_parser("status", help="Status of the project in cwd")
    st.set_defaults(func=cmd_status)

    args = p.parse_args(argv)
    return args.func(args)


if __name__ == "__main__":
    sys.exit(main())
