#!/usr/bin/env python3
"""
Transition Check — Standalone inter-episode transition analysis.

NOTE: This functionality is now integrated into script_doctor.py as Pass 1
(structural transitions). Use `script_doctor.py [project] --full` for the
integrated multi-pass pipeline, or `--focus transitions` for standalone.

This file is kept for backward compatibility and quick one-off checks.

Usage:
    python3 transition_check.py [project]
    python3 transition_check.py [project] --dry-run

Requires:
    pip install google-generativeai
    export GEMINI_API_KEY="your-key-here"
"""

import argparse
import json
import os
import re
import sys
import time
from datetime import datetime, timezone
from pathlib import Path

from recoil.core.model_profiles import get_model
from recoil.core.paths import ProjectPaths

# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------

DEFAULT_MODEL = get_model("pro", "text")
TOKENS_PER_WORD = 1.3


def find_project_root() -> Path:
    current = Path(__file__).resolve().parent
    while current != current.parent:
        if (current / "tools").is_dir() and (current / "editors").is_dir():
            return current
        current = current.parent
    print("ERROR: Could not find Recoil root.", file=sys.stderr)
    sys.exit(1)


def bundle_episodes(project: str, root: Path) -> dict:
    """Read all episodes in order."""
    project_path = root / project
    episodes_dir = project_path / "episodes"
    if not episodes_dir.is_dir():
        print(f"ERROR: Episodes directory not found: {episodes_dir}", file=sys.stderr)
        sys.exit(1)

    episode_files = sorted(episodes_dir.glob("ep_*.md"))
    episodes = {}
    for ep_file in episode_files:
        match = re.search(r"ep_(\d+)", ep_file.name)
        if match:
            ep_num = int(match.group(1))
            episodes[ep_num] = ep_file.read_text(encoding="utf-8")

    return episodes


def build_prompt(episodes: dict) -> str:
    """Build the transition analysis prompt."""
    parts = []

    parts.append("""# ROLE

You are a script continuity editor analyzing a 60-episode vertical microdrama series.
Each episode is ~90 seconds. Viewers watch consecutively. Every transition between
episodes must feel inevitable — causally driven, not arbitrary.

# THE RULE: THEREFORE / BUT — NEVER "AND THEN"

Every episode boundary (cliffhanger of episode N → hook of episode N+1) must connect
via one of these causal connectors:

**THEREFORE** — The cliffhanger causes or necessitates the next hook.
Example: "Kian's battery dies" → THEREFORE → "Jinx drags his dead chassis to safety"

**BUT** — The next hook complicates, subverts, or contradicts expectations from the cliffhanger.
Example: "They escape the Collectors" → BUT → "The person Jinx brings Kian to is terrified of him"

**NEVER "AND THEN"** — The next hook merely follows chronologically with no causal link.
Example: "Emotional confrontation in compartment" → AND THEN → "They're at a workshop" (how? why?)

# WHAT TO CHECK AT EACH BOUNDARY

For every transition from Episode N to Episode N+1, evaluate:

1. **Causal Logic** — Is the connection THEREFORE, BUT, or AND THEN?
   - If AND THEN: this is a finding. The cliffhanger needs a bridge line, or the hook
     needs to acknowledge how we got here.

2. **Spatial Continuity** — Does the location change make sense?
   - If Episode N ends at Location A and Episode N+1 opens at Location B:
     - Is there any indication of travel/transition?
     - Could the audience infer how they got there?
     - Or does it feel like a teleport?

3. **Character Positioning** — When a new character appears, do we know why?
   - How did they find the protagonists?
   - Were they established as being nearby?
   - Or do they just materialize?

4. **Emotional Continuity** — Does the emotional state carry across?
   - If Episode N ends on a heavy emotional beat, does N+1 acknowledge it?
   - Or does the emotional thread get dropped?

# OUTPUT FORMAT

Return ONLY valid JSON (no markdown fencing, no commentary outside the JSON):

{
  "transitions": [
    {
      "boundary": "EP 6 → EP 7",
      "ep_n_cliffhanger": "Brief description of how ep N ends",
      "ep_n1_hook": "Brief description of how ep N+1 opens",
      "connector": "AND THEN | THEREFORE | BUT",
      "issues": [
        {
          "type": "causal | spatial | character | emotional",
          "severity": "P1 | P2 | P3",
          "description": "What's wrong and why it matters"
        }
      ],
      "suggested_fix": "How to bridge this transition (add a line, modify the hook, etc.)"
    }
  ],
  "summary": {
    "total_transitions": 59,
    "therefore_count": 0,
    "but_count": 0,
    "and_then_count": 0,
    "p1_issues": 0,
    "p2_issues": 0,
    "p3_issues": 0
  }
}

SEVERITY GUIDE:
- P1: Audience will be confused or disoriented. Breaks immersion. (teleportation, character materialization, emotional whiplash)
- P2: Noticeable gap but inferrable. Could be smoother. (location change without bridge, dropped emotional thread)
- P3: Minor. Works but could be tighter. (weak causal link, could be strengthened)

**Only report transitions that have issues.** Do NOT include transitions that work well (THEREFORE or BUT with no spatial/character/emotional problems). This keeps the output focused on actionable findings.

# EPISODES
""")

    for ep_num in sorted(episodes.keys()):
        parts.append(f"\n## EPISODE {ep_num}\n\n{episodes[ep_num]}\n")

    return "\n".join(parts)


def call_gemini(prompt: str, model: str = DEFAULT_MODEL) -> str:
    """Call Gemini API and return the response text."""
    import google.generativeai as genai

    api_key = os.environ.get("GEMINI_API_KEY")
    if not api_key:
        print("ERROR: GEMINI_API_KEY not set.", file=sys.stderr)
        sys.exit(1)

    genai.configure(api_key=api_key)

    config = genai.GenerationConfig(
        temperature=0.3,
        max_output_tokens=16384,
        response_mime_type="application/json",
    )

    model_obj = genai.GenerativeModel(model)

    print(f"Calling Gemini ({model})...", flush=True)
    start = time.time()

    response = model_obj.generate_content(prompt, generation_config=config)

    elapsed = time.time() - start
    print(f"Response received in {elapsed:.1f}s", flush=True)

    return response.text


def main():
    parser = argparse.ArgumentParser(description="Transition Check")
    parser.add_argument("project", help="Project directory name")
    parser.add_argument("--dry-run", action="store_true", help="Save prompt without calling API")
    parser.add_argument("--model", default=DEFAULT_MODEL, help="Gemini model")
    args = parser.parse_args()

    root = find_project_root()
    episodes = bundle_episodes(args.project, root)
    print(f"Loaded {len(episodes)} episodes")

    prompt = build_prompt(episodes)
    word_count = len(prompt.split())
    est_tokens = int(word_count * TOKENS_PER_WORD)
    print(f"Prompt: {word_count:,} words (~{est_tokens:,} tokens)")

    state_dir = ProjectPaths.from_root(root / args.project).state_dir
    state_dir.mkdir(parents=True, exist_ok=True)

    if args.dry_run:
        payload_path = state_dir / "transition_check_payload.txt"
        payload_path.write_text(prompt, encoding="utf-8")
        print(f"Payload saved to {payload_path}")
        return

    response_text = call_gemini(prompt, args.model)

    # Parse JSON
    try:
        # Strip markdown fencing if present
        cleaned = response_text.strip()
        if cleaned.startswith("```"):
            cleaned = re.sub(r"^```\w*\n?", "", cleaned)
            cleaned = re.sub(r"\n?```$", "", cleaned)
        results = json.loads(cleaned)
    except json.JSONDecodeError as e:
        print(f"WARNING: Could not parse JSON response: {e}", file=sys.stderr)
        raw_path = state_dir / "transition_check_raw.txt"
        raw_path.write_text(response_text, encoding="utf-8")
        print(f"Raw response saved to {raw_path}")
        return

    # Add metadata
    results["generated"] = datetime.now(timezone.utc).isoformat()
    results["model"] = args.model
    results["project"] = args.project

    # Save results
    output_path = state_dir / "transition_check.json"
    output_path.write_text(json.dumps(results, indent=2), encoding="utf-8")
    print(f"\nResults saved to {output_path}")

    # Print summary
    summary = results.get("summary", {})
    transitions = results.get("transitions", [])
    print(f"\n{'='*60}")
    print(f"TRANSITION ANALYSIS — {args.project}")
    print(f"{'='*60}")
    print(f"THEREFORE: {summary.get('therefore_count', '?')}")
    print(f"BUT:       {summary.get('but_count', '?')}")
    print(f"AND THEN:  {summary.get('and_then_count', '?')}")
    print(f"P1 issues: {summary.get('p1_issues', '?')}")
    print(f"P2 issues: {summary.get('p2_issues', '?')}")
    print(f"P3 issues: {summary.get('p3_issues', '?')}")
    print(f"{'='*60}")

    for t in transitions:
        severity_tags = [i["severity"] for i in t.get("issues", [])]
        worst = "P3"
        if "P1" in severity_tags:
            worst = "P1"
        elif "P2" in severity_tags:
            worst = "P2"

        marker = "!!" if worst == "P1" else "!" if worst == "P2" else " "
        print(f"\n{marker} {t['boundary']} [{t['connector']}]")
        print(f"  Cliffhanger: {t['ep_n_cliffhanger']}")
        print(f"  Hook:        {t['ep_n1_hook']}")
        for issue in t.get("issues", []):
            print(f"  [{issue['severity']}] {issue['type']}: {issue['description']}")
        if t.get("suggested_fix"):
            print(f"  FIX: {t['suggested_fix']}")


if __name__ == "__main__":
    main()
