#!/usr/bin/env python3
"""
Stage 10: Video Generation

Generates videos from selected frames using multiple providers.
Operates at scene level with iteration-based refinement.

Commands:
- new_video: Generate new video iteration for scene
- refine_video: Refine existing video with feedback
- reiterate_video: Reiterate video with same prompt but different settings
- select_video: Select video for scene
- rate_video: Rate video quality
- status_videos: Show status of all scenes
"""

import subprocess
import sys
import json
from pathlib import Path
from datetime import datetime, timezone
from typing import Optional, Dict

# Add parent to path
sys.path.insert(0, str(Path(__file__).parent.parent))

from lib import (
    save_metadata,
    ensure_dir,
    setup_generation_log,
    claude_env,
)
from lib.path_utils_v3 import get_next_iteration
from lib.video_providers import get_provider


def new_video(
    take_path: Path,
    scene: str,
    feedback: Optional[str] = None,
    provider: str = "pollo-kling",
    duration: Optional[int] = None,
    generate_audio: bool = True,
    end_frame: Optional[str] = None,
    end_scene: Optional[str] = None,
    resolution: str = "1080p",
    direct_prompt: Optional[str] = None
) -> dict:
    """
    Generate NEW video iteration for a scene.

    Args:
        take_path: Path to take directory
        scene: Scene name (e.g., "01-scene")
        feedback: Optional user feedback to modify prompt
        provider: Video provider (kling-direct, kling-o1, pollo-kling, pollo-veo3, laozhang-veo3)
        duration: Duration in seconds (None = auto-calculate, 5, or 10)
        generate_audio: Generate audio track
        end_frame: Optional path to end frame for interpolation
        end_scene: Optional end scene name for interpolation (e.g., "02-scene")
        resolution: Video resolution for Veo3 providers ("720p" or "1080p")
        direct_prompt: Optional exact prompt to use (bypasses Claude generation)

    Returns:
        Result dict with status, iteration, video_path
    """
    logger = setup_generation_log(take_path)
    logger.section(f"Stage 10: New Video - {scene}")

    # Validate inputs
    videos_dir = take_path / "10-videos"
    frames_dir = take_path / "08-frames"

    # Load frame selection from main pipeline
    from lib.pipeline import load_pipeline

    pipeline = load_pipeline(take_path)
    frames_selections = pipeline.get("08-frames")

    if not isinstance(frames_selections, dict):
        return {
            "status": "error",
            "error": "Frames not selected. Run extract-frames and select-frame first."
        }

    selected_frame = frames_selections.get(scene)
    if not selected_frame:
        return {
            "status": "error",
            "error": f"No frame selected for {scene}. Run select-frame first."
        }

    # Get frame path
    frame_path = frames_dir / scene / f"{selected_frame}.jpg"
    if not frame_path.exists():
        return {
            "status": "error",
            "error": f"Selected frame not found: {frame_path}"
        }

    logger.info(f"Selected frame: {selected_frame}.jpg")

    # Load adapted script
    script_file = _get_current_script(take_path)
    if not script_file:
        return {
            "status": "error",
            "error": "Adapted script not found. Run Stage 08 first."
        }

    with open(script_file) as f:
        script = json.load(f)

    # Find scene data
    scene_num = int(scene.split('-')[0])
    scene_data = _find_scene(script, scene_num)
    if not scene_data:
        return {
            "status": "error",
            "error": f"Scene {scene_num} not found in script"
        }

    # Find end scene data for interpolation
    end_scene_data = None
    if end_scene:
        end_scene_num = int(end_scene.split('-')[0])
        end_scene_data = _find_scene(script, end_scene_num)
        if end_scene_data:
            logger.info(f"Interpolation end scene: {end_scene} (scene {end_scene_num})")

    # Use script duration if specified, otherwise auto-calculate
    if duration is None:
        if scene_data.get("duration"):
            duration = scene_data["duration"]
            logger.info(f"Using script duration: {duration}s")
        else:
            duration = _calculate_duration(scene_data)
            logger.info(f"Auto-calculated duration: {duration}s")

    # Prepare O1 reference info if using kling-o1
    o1_references = None
    o1_ref_images = None
    if provider in ("kling-o1", "pollo-kling-o1"):
        o1_ref_images = _get_reference_images(take_path, pipeline, scene_num=scene_num)
        if o1_ref_images:
            o1_references = _build_o1_reference_descriptions(take_path, pipeline, o1_ref_images)
            logger.info(f"O1 mode: {len(o1_ref_images)} reference image(s)")

    # Build video prompt
    if direct_prompt:
        # Use exact prompt provided by user (bypasses Claude generation)
        prompt = direct_prompt
        logger.info("Using direct prompt (bypassing Claude)")
    else:
        style_info = script.get("style", {})
        prompt = _build_video_prompt(
            scene_data, style_info, feedback, None, take_path, None, logger,
            o1_references=o1_references,
            end_scene_data=end_scene_data,
            duration=duration
        )

    logger.info(f"Provider: {provider}")
    logger.info(f"Duration: {duration}s")
    logger.info(f"Audio: {generate_audio}")

    # Create scene video directory
    scene_video_dir = videos_dir / scene
    ensure_dir(scene_video_dir)

    # Get next iteration
    iteration = get_next_iteration(scene_video_dir)
    iter_path = scene_video_dir / iteration
    ensure_dir(iter_path)

    logger.info(f"Creating iteration {iteration}")

    # Save prompt
    prompt_file = iter_path / "prompt.txt"
    prompt_file.write_text(prompt)
    logger.info(f"Saved prompt: {prompt_file}")

    # Save feedback if provided
    if feedback:
        feedback_file = iter_path / "feedback.txt"
        feedback_file.write_text(feedback)
        logger.info(f"Saved feedback: {feedback_file}")

    # Generate video
    video_output = iter_path / "video.mp4"

    try:
        provider_instance = get_provider(provider)

        # Build provider kwargs (reference_images for kling-o1)
        provider_kwargs = {}
        if provider in ("kling-o1", "pollo-kling-o1") and o1_ref_images:
            provider_kwargs["reference_images"] = o1_ref_images

        result = provider_instance.generate_video(
            prompt=prompt,
            start_frame_path=str(frame_path),
            end_frame_path=end_frame,
            duration=duration,
            generate_audio=generate_audio,
            output_path=str(video_output),
            resolution=resolution,
            **provider_kwargs
        )

        if result["status"] != "success":
            logger.error(f"Video generation failed: {result.get('error')}")
            return {
                "status": "error",
                "error": result.get("error"),
                "iteration": iteration
            }

        # Save metadata
        based_on = {
            "08-frames": selected_frame,
            "09-script-adapted": _get_script_iteration(take_path)
        }

        metadata = {
            "stage": "10-videos",
            "scene": scene,
            "scene_number": scene_num,
            "iteration": iteration,
            "created_at": datetime.now(timezone.utc).isoformat(),
            "based_on": based_on,
            "refined_from": None,
            "rating": None,
            "provider": provider,
            "model": provider,  # TODO: Get actual model from provider
            "duration_requested": duration,
            "duration_actual": result.get("duration_actual", duration),
            "audio_generated": generate_audio,
            "aspect_ratio": "9:16",
            "resolution": resolution,
            "task_id": result.get("task_id"),
            "video_url": result.get("video_url"),
            "result": f"10-videos/{scene}/{iteration}/video.mp4"
        }

        metadata_file = iter_path / "metadata.json"
        save_metadata(metadata_file, metadata)
        logger.info(f"Saved metadata: {metadata_file}")

        # Build user message for feedback_request
        user_message_parts = []

        # What was generated
        actual_duration = result.get('duration_actual', duration)
        # Convert to float if it's a string or other type
        try:
            duration_val = float(actual_duration)
        except (TypeError, ValueError):
            duration_val = float(duration)

        user_message_parts.append(
            f"Generated {duration_val:.1f}s video for {scene} using **{provider}** "
            f"({'with audio' if generate_audio else 'no audio'})."
        )

        # Load scene data to describe what's in the video
        try:
            scene_num = int(scene.split('-')[0])
            script_data = _get_scene_from_script(take_path, scene_num)
            if script_data:
                action = script_data.get("action", "")
                setting = script_data.get("what_is_shown", "")
                dialogue = script_data.get("dialogue", {})

                user_message_parts.append(f"\n**Scene content:**")
                if action:
                    user_message_parts.append(f"- Action: {action}")
                if setting:
                    user_message_parts.append(f"- Setting: {setting}")
                if dialogue:
                    # Use text_caption for display, fall back to text
                    if isinstance(dialogue, list):
                        for d in dialogue:
                            if isinstance(d, dict):
                                dt = d.get("text_caption") or d.get("text")
                                if dt:
                                    user_message_parts.append(f"- Dialogue: \"{dt}\" ({d.get('delivery', 'neutral')})")
                    else:
                        display_text = dialogue.get("text_caption") or dialogue.get("text")
                        if display_text:
                            user_message_parts.append(f"- Dialogue: \"{display_text}\" ({dialogue.get('delivery', 'neutral')})")
        except Exception:
            pass

        # Frame used
        user_message_parts.append(f"\n**Frame used:** {selected_frame}.jpg")

        # Feedback (if provided for new video)
        if feedback:
            user_message_parts.append(f"**Feedback applied:** {feedback}")

        # Suggestions
        user_message_parts.append(
            f"\n**Accept** if the video captures the scene action and timing correctly. "
            f"**Refine** to adjust the prompt (e.g., slower motion, different camera angle, more dramatic action). "
            f"**Regenerate** to start fresh with different guidance. "
            f"**Change Provider** to try a different AI model (kling-direct for audio, veo3 for different style)."
        )

        user_message = "\n".join(user_message_parts)

        # Create feedback_request.json for user approval
        feedback_request = {
            "stage": "10-videos",
            "scene": scene,
            "iteration": iteration,
            "message": None,
            "user_message": user_message,
            "generated_file": str(video_output),
            "file_description": f"video.mp4 ({video_output.stat().st_size} bytes)",
            "options": [
                {
                    "action": "accept",
                    "label": "✓ Accept",
                    "description": f"Select this video for {scene}",
                    "requires_text": False,
                    "command": f"select-video {scene} {iteration}"
                },
                {
                    "action": "refine",
                    "label": "✏️ Refine",
                    "description": "Regenerate with feedback (keeps same base)",
                    "requires_text": True,
                    "command": f"refine-video {scene} {iteration} --feedback \"YOUR_FEEDBACK\""
                },
                {
                    "action": "regenerate",
                    "label": "🔄 Regenerate",
                    "description": "Generate new video from scratch",
                    "requires_text": True,
                    "command": f"new-video {scene} --feedback \"YOUR_FEEDBACK\""
                },
                {
                    "action": "change_provider",
                    "label": "🔀 Change Provider",
                    "description": "Try different provider (kling-direct, kling-o1, pollo-kling, pollo-veo3, laozhang-veo3)",
                    "requires_text": False,
                    "command": f"new-video {scene} --provider PROVIDER"
                }
            ]
        }

        feedback_request_file = iter_path / "feedback_request.json"
        with open(feedback_request_file, 'w') as f:
            json.dump(feedback_request, f, indent=2)

        logger.info(f"Created feedback_request.json")
        logger.info(f"Video generation completed!")
        logger.info(f"Output: {video_output}")

        return {
            "status": "success",
            "iteration": iteration,
            "video_path": str(video_output),
            "scene": scene,
            "provider": provider,
            "duration": result.get("duration_actual", duration),
            "feedback_request": str(feedback_request_file)
        }

    except Exception as e:
        logger.error(f"Video generation failed: {e}")
        import traceback
        traceback.print_exc()
        return {
            "status": "error",
            "error": str(e),
            "iteration": iteration
        }


def refine_video(
    take_path: Path,
    scene: str,
    iteration: str,
    feedback: str,
    provider: Optional[str] = None,
    duration: Optional[int] = None,
    generate_audio: Optional[bool] = None,
    end_frame: Optional[str] = None,
    resolution: Optional[str] = None,
    direct_prompt: Optional[str] = None
) -> dict:
    """
    Refine existing video with feedback.

    Creates new iteration with accumulated feedback.

    Args:
        take_path: Path to take directory
        scene: Scene name
        iteration: Base iteration to refine
        feedback: User feedback
        provider: Optional provider override
        duration: Optional duration override
        generate_audio: Optional audio override
        end_frame: Optional end frame override
        resolution: Optional resolution override ("720p" or "1080p")
        direct_prompt: Optional exact prompt to use (bypasses Claude generation)

    Returns:
        Result dict with status, new iteration
    """
    logger = setup_generation_log(take_path)
    logger.section(f"Stage 10: Refine Video - {scene}/{iteration}")

    # Validate base iteration exists
    videos_dir = take_path / "10-videos"
    scene_video_dir = videos_dir / scene
    base_iter_path = scene_video_dir / iteration

    if not base_iter_path.exists():
        return {
            "status": "error",
            "error": f"Base iteration not found: {iteration}"
        }

    # Load base metadata
    base_metadata_file = base_iter_path / "metadata.json"
    if not base_metadata_file.exists():
        return {
            "status": "error",
            "error": f"Base metadata not found"
        }

    with open(base_metadata_file) as f:
        base_metadata = json.load(f)

    # Use base settings if not overridden
    if provider is None:
        provider = base_metadata.get("provider", "pollo-kling")
    if duration is None:
        duration = base_metadata.get("duration_requested", 5)
    if generate_audio is None:
        generate_audio = base_metadata.get("audio_generated", True)
    if resolution is None:
        resolution = base_metadata.get("resolution", "720p")

    # Get frame path from base
    based_on = base_metadata.get("based_on", {})
    selected_frame = based_on.get("08-frames")
    frames_dir = take_path / "08-frames"
    frame_path = frames_dir / scene / f"{selected_frame}.jpg"

    if not frame_path.exists():
        return {
            "status": "error",
            "error": f"Frame not found: {frame_path}"
        }

    # Load script
    script_file = _get_current_script(take_path)
    if not script_file:
        return {
            "status": "error",
            "error": "Adapted script not found"
        }

    with open(script_file) as f:
        script = json.load(f)

    scene_num = int(scene.split('-')[0])
    scene_data = _find_scene(script, scene_num)

    # Collect accumulated feedback
    all_feedback = _collect_feedback(scene_video_dir, based_on)
    previous_feedback = "\n\n".join([
        f"Iteration {fb['iteration']}: {fb['feedback']}"
        for fb in all_feedback
    ]) if all_feedback else None

    logger.info(f"Collected {len(all_feedback)} previous feedback(s)")

    # Read original prompt from base iteration
    original_prompt = None
    original_prompt_file = base_iter_path / "prompt.txt"
    if original_prompt_file.exists():
        original_prompt = original_prompt_file.read_text().strip()
        logger.info(f"Loaded original prompt from iteration {iteration}")

    # Prepare O1 reference info if using kling-o1
    from lib.pipeline import load_pipeline as _load_pipeline
    _pipeline = _load_pipeline(take_path)

    o1_references = None
    o1_ref_images = None
    if provider in ("kling-o1", "pollo-kling-o1"):
        o1_ref_images = _get_reference_images(take_path, _pipeline, scene_num=scene_num)
        if o1_ref_images:
            o1_references = _build_o1_reference_descriptions(take_path, _pipeline, o1_ref_images)
            logger.info(f"O1 mode: {len(o1_ref_images)} reference image(s)")

    # Auto-resolve end frame for interpolation mode
    end_scene_data = None
    if not end_frame:
        end_frame, end_scene_data = _resolve_interpolation_end(
            take_path, scene, scene_num, script, _pipeline, logger)

    # Build prompt - use direct_prompt if provided (bypasses Claude)
    if direct_prompt:
        prompt = direct_prompt
        logger.info("Using direct prompt (bypassing Claude)")
    else:
        style_info = script.get("style", {})
        prompt = _build_video_prompt(
            scene_data, style_info, feedback, previous_feedback, take_path, original_prompt, logger,
            o1_references=o1_references, end_scene_data=end_scene_data, duration=duration
        )

    # Get next iteration
    new_iteration = get_next_iteration(scene_video_dir)
    new_iter_path = scene_video_dir / new_iteration
    ensure_dir(new_iter_path)

    logger.info(f"Creating iteration {new_iteration} (refined from {iteration})")
    logger.info(f"Provider: {provider}")

    # Save prompt and feedback
    (new_iter_path / "prompt.txt").write_text(prompt)
    (new_iter_path / "feedback.txt").write_text(feedback)

    # Generate video
    video_output = new_iter_path / "video.mp4"

    try:
        provider_instance = get_provider(provider)

        # Build provider kwargs (reference_images for kling-o1)
        provider_kwargs = {}
        if provider in ("kling-o1", "pollo-kling-o1") and o1_ref_images:
            provider_kwargs["reference_images"] = o1_ref_images

        result = provider_instance.generate_video(
            prompt=prompt,
            start_frame_path=str(frame_path),
            end_frame_path=end_frame,
            duration=duration,
            generate_audio=generate_audio,
            output_path=str(video_output),
            resolution=resolution,
            **provider_kwargs
        )

        if result["status"] != "success":
            return {
                "status": "error",
                "error": result.get("error"),
                "iteration": new_iteration
            }

        # Save metadata
        metadata = {
            "stage": "10-videos",
            "scene": scene,
            "scene_number": scene_num,
            "iteration": new_iteration,
            "created_at": datetime.now(timezone.utc).isoformat(),
            "based_on": based_on,
            "refined_from": iteration,
            "rating": None,
            "provider": provider,
            "model": provider,
            "duration_requested": duration,
            "duration_actual": result.get("duration_actual", duration),
            "audio_generated": generate_audio,
            "aspect_ratio": "9:16",
            "resolution": resolution,
            "task_id": result.get("task_id"),
            "video_url": result.get("video_url"),
            "result": f"10-videos/{scene}/{new_iteration}/video.mp4"
        }

        save_metadata(new_iter_path / "metadata.json", metadata)

        # Build user message for feedback_request
        user_message_parts = []

        # What was refined
        actual_duration = result.get('duration_actual', duration)
        # Convert to float if it's a string or other type
        try:
            duration_val = float(actual_duration)
        except (TypeError, ValueError):
            duration_val = float(duration)

        user_message_parts.append(
            f"Refined video for {scene} (iteration {new_iteration}) based on iteration {iteration}. "
            f"Duration: {duration_val:.1f}s, Provider: **{provider}** "
            f"({'with audio' if generate_audio else 'no audio'})."
        )

        # Feedback applied
        user_message_parts.append(f"\n**Current feedback:** {feedback}")

        # Show accumulated feedback history
        if all_feedback:
            user_message_parts.append(f"\n**Previous feedback ({len(all_feedback)} iterations):**")
            for fb in all_feedback[:3]:  # Show max 3 previous
                user_message_parts.append(f"- Iteration {fb['iteration']}: {fb['feedback']}")
            if len(all_feedback) > 3:
                user_message_parts.append(f"- ... and {len(all_feedback) - 3} more")

        # Frame used
        frame_used = selected_frame.split('/')[-1] if selected_frame else "unknown"
        user_message_parts.append(f"\n**Frame used:** {frame_used}")

        # Suggestions
        user_message_parts.append(
            f"\n**Accept** if the refinement addressed your feedback satisfactorily. "
            f"**Refine** to continue iterating with additional feedback. "
            f"**Regenerate** to discard this refinement chain and start fresh."
        )

        user_message = "\n".join(user_message_parts)

        # Create feedback_request.json
        feedback_request = {
            "stage": "10-videos",
            "scene": scene,
            "iteration": new_iteration,
            "message": None,
            "user_message": user_message,
            "generated_file": str(video_output),
            "file_description": f"video.mp4 ({video_output.stat().st_size} bytes)",
            "options": [
                {
                    "action": "accept",
                    "label": "✓ Accept",
                    "description": f"Select this refined video for {scene}",
                    "requires_text": False,
                    "command": f"select-video {scene} {new_iteration}"
                },
                {
                    "action": "refine",
                    "label": "✏️ Refine More",
                    "description": "Continue refining with more feedback",
                    "requires_text": True,
                    "command": f"refine-video {scene} {iteration} --feedback \"YOUR_FEEDBACK\""
                },
                {
                    "action": "regenerate",
                    "label": "🔄 Regenerate",
                    "description": "Discard refinement chain, start fresh",
                    "requires_text": True,
                    "command": f"new-video {scene} --feedback \"YOUR_FEEDBACK\""
                }
            ]
        }

        feedback_request_file = new_iter_path / "feedback_request.json"
        with open(feedback_request_file, 'w') as f:
            json.dump(feedback_request, f, indent=2)

        logger.info(f"Created feedback_request.json")
        logger.info(f"Video refinement completed!")
        logger.info(f"Output: {video_output}")

        return {
            "status": "success",
            "iteration": new_iteration,
            "refined_from": iteration,
            "video_path": str(video_output),
            "scene": scene,
            "feedback_request": str(feedback_request_file)
        }

    except Exception as e:
        logger.error(f"Video refinement failed: {e}")
        import traceback
        traceback.print_exc()
        return {
            "status": "error",
            "error": str(e),
            "iteration": new_iteration
        }


def reiterate_video(
    take_path: Path,
    scene: str,
    iteration: str,
    provider: Optional[str] = None,
    duration: Optional[int] = None,
    generate_audio: Optional[bool] = None,
    end_frame: Optional[str] = None,
    resolution: Optional[str] = None
) -> dict:
    """
    Reiterate existing video with same prompt but different settings.

    Creates new iteration using the exact same prompt from source iteration,
    but allows changing provider and technical settings.

    Args:
        take_path: Path to take directory
        scene: Scene name
        iteration: Source iteration to copy prompt from
        provider: Optional provider override
        duration: Optional duration override
        generate_audio: Optional audio override
        end_frame: Optional end frame override
        resolution: Optional resolution override ("720p" or "1080p")

    Returns:
        Result dict with status, new iteration
    """
    logger = setup_generation_log(take_path)
    logger.section(f"Stage 10: Reiterate Video - {scene}/{iteration}")

    # Validate source iteration exists
    videos_dir = take_path / "10-videos"
    scene_video_dir = videos_dir / scene
    source_iter_path = scene_video_dir / iteration

    if not source_iter_path.exists():
        return {
            "status": "error",
            "error": f"Source iteration not found: {iteration}"
        }

    # Load source metadata
    source_metadata_file = source_iter_path / "metadata.json"
    if not source_metadata_file.exists():
        return {
            "status": "error",
            "error": f"Source metadata not found"
        }

    with open(source_metadata_file) as f:
        source_metadata = json.load(f)

    # Use source settings if not overridden
    if provider is None:
        provider = source_metadata.get("provider", "pollo-kling")
    if duration is None:
        duration = source_metadata.get("duration_requested", 5)
    if generate_audio is None:
        generate_audio = source_metadata.get("audio_generated", True)
    if resolution is None:
        resolution = source_metadata.get("resolution", "720p")

    # Get frame path from source
    based_on = source_metadata.get("based_on", {})
    selected_frame = based_on.get("08-frames")
    frames_dir = take_path / "08-frames"
    frame_path = frames_dir / scene / f"{selected_frame}.jpg"

    if not frame_path.exists():
        return {
            "status": "error",
            "error": f"Frame not found: {frame_path}"
        }

    # Load prompt from source iteration
    source_prompt_file = source_iter_path / "prompt.txt"
    if not source_prompt_file.exists():
        return {
            "status": "error",
            "error": f"Source prompt not found"
        }

    prompt = source_prompt_file.read_text().strip()
    logger.info(f"Loaded prompt from iteration {iteration}")

    # Auto-resolve end frame for interpolation mode
    if not end_frame:
        from lib.pipeline import load_pipeline as _load_pipe
        _pipe = _load_pipe(take_path)
        scene_num = int(scene.split('-')[0])
        end_frame, _ = _resolve_interpolation_end(
            take_path, scene, scene_num, None, _pipe, logger)

    # Create new iteration
    new_iteration = get_next_iteration(scene_video_dir)
    iter_path = scene_video_dir / new_iteration
    ensure_dir(iter_path)
    logger.info(f"Creating iteration {new_iteration} (reiterating {iteration})")
    logger.info(f"Provider: {provider}")

    # Save prompt
    prompt_file = iter_path / "prompt.txt"
    prompt_file.write_text(prompt)
    logger.info(f"Saved prompt: {prompt_file}")

    try:
        # Generate video
        from lib.pipeline import load_pipeline as _load_pipeline
        _pipeline = _load_pipeline(take_path)

        provider_instance = get_provider(provider)

        video_output = iter_path / "video.mp4"

        # Build provider kwargs (reference_images for kling-o1)
        provider_kwargs = {}
        if provider in ("kling-o1", "pollo-kling-o1"):
            ref_images = _get_reference_images(take_path, _pipeline)
            if ref_images:
                provider_kwargs["reference_images"] = ref_images
                # For reiterate: if prompt doesn't already have <<<image_N>>>, inject fallback
                if "<<<image_" not in prompt:
                    refs = " and ".join(f"<<<image_{i+1}>>>" for i in range(len(ref_images)))
                    prompt = f"The person in the video looks exactly like {refs}. {prompt}"
                    # Re-save prompt with O1 references
                    prompt_file.write_text(prompt)
                logger.info(f"O1 mode: {len(ref_images)} reference image(s)")

        result = provider_instance.generate_video(
            prompt=prompt,
            start_frame_path=str(frame_path),
            end_frame_path=end_frame,
            duration=duration,
            generate_audio=generate_audio,
            output_path=str(video_output),
            resolution=resolution,
            **provider_kwargs
        )

        if result["status"] != "success":
            return result

        logger.info(f"Created feedback_request.json")
        logger.info(f"Video reiteration completed!")
        logger.info(f"Output: {video_output}")

        # Save metadata
        scene_num = int(scene.split('-')[0])
        metadata = {
            "stage": "10-videos",
            "scene": scene,
            "scene_number": scene_num,
            "iteration": new_iteration,
            "created_at": datetime.now(timezone.utc).isoformat(),
            "based_on": based_on,
            "prompt_from": iteration,  # Track source of prompt
            "refined_from": None,
            "rating": None,
            "provider": provider,
            "model": result.get("model"),
            "duration_requested": duration,
            "duration_actual": result.get("duration_actual", duration),
            "audio_generated": generate_audio,
            "resolution": resolution,
            "aspect_ratio": "9:16",
            "task_id": result.get("task_id"),
            "video_url": result.get("video_url"),
            "result": f"10-videos/{scene}/{new_iteration}/video.mp4"
        }

        metadata_file = iter_path / "metadata.json"
        save_metadata(metadata_file, metadata)
        logger.info(f"Saved metadata: {metadata_file}")

        # Build user message for feedback_request
        user_message_parts = []

        # What was generated
        actual_duration = result.get('duration_actual', duration)
        try:
            duration_val = float(actual_duration)
        except (TypeError, ValueError):
            duration_val = float(duration)

        user_message_parts.append(
            f"Reiterated video for {scene} (iteration {new_iteration}) based on prompt from iteration {iteration}. "
            f"Duration: {duration_val:.1f}s, Provider: **{provider}** "
            f"({'with audio' if generate_audio else 'no audio'})."
        )

        # Frame used
        user_message_parts.append(f"\n**Frame used:** {selected_frame}.jpg")

        # Technical changes (if any)
        changes = []
        if provider != source_metadata.get("provider"):
            changes.append(f"Provider changed from {source_metadata.get('provider')} to {provider}")
        if resolution != source_metadata.get("resolution", "720p"):
            changes.append(f"Resolution changed from {source_metadata.get('resolution', '720p')} to {resolution}")
        if duration != source_metadata.get("duration_requested"):
            changes.append(f"Duration changed from {source_metadata.get('duration_requested')}s to {duration}s")
        if generate_audio != source_metadata.get("audio_generated"):
            changes.append(f"Audio {'enabled' if generate_audio else 'disabled'}")

        if changes:
            user_message_parts.append(f"\n**Technical changes:**")
            for change in changes:
                user_message_parts.append(f"- {change}")

        # Suggestions
        user_message_parts.append(
            f"\n**Accept** if the video quality/style is better with these settings. "
            f"**Reiterate** again to try different technical settings (provider, resolution, duration). "
            f"**Refine** to modify the prompt with feedback."
        )

        user_message = "\n".join(user_message_parts)

        # Create feedback_request.json for user approval
        feedback_request = {
            "stage": "10-videos",
            "scene": scene,
            "iteration": new_iteration,
            "message": None,
            "user_message": user_message,
            "generated_file": str(video_output),
            "file_description": f"video.mp4 ({video_output.stat().st_size} bytes)",
            "options": [
                {
                    "action": "accept",
                    "label": "✓ Accept",
                    "description": f"Select this video for {scene}",
                    "requires_text": False,
                    "command": f"select-video {scene} {new_iteration}"
                },
                {
                    "action": "reiterate",
                    "label": "🔁 Reiterate",
                    "description": "Try same prompt with different settings",
                    "requires_text": False,
                    "command": f"reiterate-video {scene} {iteration} --provider PROVIDER"
                },
                {
                    "action": "refine",
                    "label": "✏️ Refine",
                    "description": "Regenerate with feedback (modifies prompt)",
                    "requires_text": True,
                    "command": f"refine-video {scene} {new_iteration} --feedback \"YOUR_FEEDBACK\""
                }
            ]
        }

        feedback_request_file = iter_path / "feedback_request.json"
        with open(feedback_request_file, 'w') as f:
            json.dump(feedback_request, f, indent=2)

        return {
            "status": "success",
            "iteration": new_iteration,
            "prompt_from": iteration,
            "output": str(video_output),
            "provider": provider,
            "duration": result.get("duration_actual", duration),
            "feedback_request": str(feedback_request_file)
        }

    except Exception as e:
        logger.error(f"Video reiteration failed: {e}")
        import traceback
        traceback.print_exc()
        return {
            "status": "error",
            "error": str(e),
            "iteration": new_iteration
        }


def select_video(take_path: Path, scene: str, iteration: str) -> dict:
    """
    Select video for scene.

    Updates video pipeline.json with selection.

    Args:
        take_path: Path to take directory
        scene: Scene name
        iteration: Iteration to select

    Returns:
        Result dict with status
    """
    logger = setup_generation_log(take_path)
    logger.section(f"Stage 10: Select Video - {scene}/{iteration}")

    videos_dir = take_path / "10-videos"
    scene_video_dir = videos_dir / scene
    iter_path = scene_video_dir / iteration

    # Validate iteration exists
    if not iter_path.exists():
        return {
            "status": "error",
            "error": f"Iteration not found: {iteration}"
        }

    video_file = iter_path / "video.mp4"
    if not video_file.exists():
        return {
            "status": "error",
            "error": f"Video file not found: {video_file}"
        }

    # Update main pipeline with video selection
    from lib.pipeline import load_pipeline, save_pipeline

    pipeline = load_pipeline(take_path)

    # Initialize 10-videos as dict if not already
    if not isinstance(pipeline.get("10-videos"), dict):
        pipeline["10-videos"] = {}

    # Update selection
    pipeline["10-videos"][scene] = iteration

    # Save pipeline
    save_pipeline(take_path, pipeline)

    logger.info(f"Selected {iteration} for {scene}")

    # Update iteration metadata
    metadata_file = iter_path / "metadata.json"
    if metadata_file.exists():
        with open(metadata_file) as f:
            metadata = json.load(f)

        metadata["selected_at"] = datetime.now(timezone.utc).isoformat()

        with open(metadata_file, 'w') as f:
            json.dump(metadata, f, indent=2)

    return {
        "status": "success",
        "scene": scene,
        "iteration": iteration
    }


def rate_video(take_path: Path, scene: str, iteration: str, rating: int) -> dict:
    """
    Rate video (1-5).

    Args:
        take_path: Path to take directory
        scene: Scene name
        iteration: Iteration to rate
        rating: Rating (1-5)

    Returns:
        Result dict with status
    """
    if not 1 <= rating <= 5:
        return {
            "status": "error",
            "error": "Rating must be 1-5"
        }

    logger = setup_generation_log(take_path)
    logger.section(f"Stage 10: Rate Video - {scene}/{iteration}")

    videos_dir = take_path / "10-videos"
    iter_path = videos_dir / scene / iteration

    if not iter_path.exists():
        return {
            "status": "error",
            "error": f"Iteration not found: {iteration}"
        }

    metadata_file = iter_path / "metadata.json"
    if not metadata_file.exists():
        return {
            "status": "error",
            "error": "Metadata not found"
        }

    # Update rating
    with open(metadata_file) as f:
        metadata = json.load(f)

    metadata["rating"] = rating
    metadata["rated_at"] = datetime.now(timezone.utc).isoformat()

    with open(metadata_file, 'w') as f:
        json.dump(metadata, f, indent=2)

    logger.info(f"Rated {scene}/{iteration} as {rating}/5")

    return {
        "status": "success",
        "scene": scene,
        "iteration": iteration,
        "rating": rating
    }


def status_videos(take_path: Path) -> dict:
    """
    Show status of all scenes.

    Returns:
        {
            "status": "success",
            "scenes": [
                {
                    "scene": "01-scene",
                    "selected": "003",
                    "iterations": ["001", "002", "003"],
                    "provider": "kling-direct",
                    "duration": 5
                },
                ...
            ],
            "all_selected": bool
        }
    """
    logger = setup_generation_log(take_path)
    logger.section("Stage 10: Video Status")

    videos_dir = take_path / "10-videos"
    frames_dir = take_path / "08-frames"

    # Get scene count
    extraction_meta = frames_dir / "extraction_metadata.json"
    if extraction_meta.exists():
        with open(extraction_meta) as f:
            meta = json.load(f)
            scene_count = meta["scene_count"]
    else:
        scene_count = 9  # Default

    # Load video selections from main pipeline
    from lib.pipeline import load_pipeline

    pipeline = load_pipeline(take_path)
    video_selections = pipeline.get("10-videos")

    # If not dict, no selections yet
    if not isinstance(video_selections, dict):
        video_selections = {}

    # Scan scenes
    scenes = []
    for scene_num in range(1, scene_count + 1):
        scene = f"{scene_num:02d}-scene"
        scene_video_dir = videos_dir / scene

        selected = video_selections.get(scene)
        iterations = []

        if scene_video_dir.exists():
            for item in sorted(scene_video_dir.iterdir()):
                if item.is_dir() and item.name.isdigit() and len(item.name) == 3:
                    iterations.append(item.name)

        scenes.append({
            "scene": scene,
            "selected": selected,
            "iterations": iterations,
            "iteration_count": len(iterations)
        })

    return {
        "status": "success",
        "scenes": scenes,
        "scene_count": scene_count
    }


# Helper functions

def _get_current_script(take_path: Path) -> Optional[Path]:
    """Get current adapted script path."""
    script_stage = take_path / "09-script-adapted"

    # Check for current symlink first
    current_link = script_stage / "current"
    if current_link.is_symlink():
        script_file = current_link / "script.json"
        if script_file.exists():
            return script_file

    # Fallback: find most recent iteration
    iterations = []
    for item in script_stage.iterdir():
        if item.is_dir() and item.name.isdigit() and len(item.name) == 3:
            script_file = item / "script.json"
            if script_file.exists():
                iterations.append(item.name)

    if iterations:
        latest = max(iterations)
        return script_stage / latest / "script.json"

    return None


def _get_script_iteration(take_path: Path) -> str:
    """Get current script iteration number."""
    script_file = _get_current_script(take_path)
    if script_file:
        return script_file.parent.name
    return "unknown"


def _find_scene(script: dict, scene_num: int) -> Optional[dict]:
    """Find scene in script by number."""
    for scene in script.get("scenes", []):
        if scene.get("number") == scene_num:
            return scene
    return None


def _calculate_duration(scene_data: dict) -> int:
    """
    Auto-calculate video duration from dialogue.

    Logic:
    - No dialogue → 5s
    - Short dialogue (≤5s estimated) → 5s
    - Long dialogue (>5s estimated) → 10s

    Estimation: ~150 words/min = 2.5 words/sec + 30% buffer
    """
    dialogue = scene_data.get("dialogue")
    if not dialogue:
        return 5  # Default for no dialogue

    # Handle dialogue as list (multiple speakers) — combine texts
    if isinstance(dialogue, list):
        texts = []
        for d in dialogue:
            if isinstance(d, dict):
                texts.append(d.get("text_video") or d.get("text", ""))
        dialogue_text = " ".join(texts)
    else:
        # Use text_video (clean) or fall back to text for word count
        dialogue_text = dialogue.get("text_video") or dialogue.get("text", "")
    if not dialogue_text:
        return 5
    word_count = len(dialogue_text.split())
    estimated_duration = (word_count / 2.5) * 1.3  # 30% buffer

    if estimated_duration > 5:
        return 10
    else:
        return 5


def _format_dialogue_for_video(text: str) -> str:
    """
    Format dialogue text for video generation:
    1. Convert ALL-CAPS to sentence case (first letter caps, rest lowercase)
    2. Add [pause] or [short pause] markers between sentences
    3. Quote each segment

    Examples:
        "WHAT DID YOU SAY! ARE YOU CRAZY!" ->
        '"What did you say!" [pause] "Are you crazy!"'

        "Yes sir! But the colonel said so!" ->
        '"Yes sir!" [pause] "But the colonel said so!"'
    """
    import re

    # Check if text is mostly ALL-CAPS (more than 50% uppercase letters)
    alpha_chars = [c for c in text if c.isalpha()]
    is_all_caps = alpha_chars and sum(1 for c in alpha_chars if c.isupper()) / len(alpha_chars) > 0.5

    if is_all_caps:
        # Convert to lowercase first
        text = text.lower()
        # Fix standalone "i" -> "I"
        text = re.sub(r'\bi\b', 'I', text)

    # Split on sentence boundaries (! ? .) while keeping the punctuation
    # Use a regex that captures sentences with their ending punctuation
    sentence_pattern = r'([^!?.]*[!?.])'
    matches = re.findall(sentence_pattern, text)

    # Filter out empty strings
    sentences = [s.strip() for s in matches if s.strip()]

    if not sentences:
        # No sentence boundaries found - return as single quoted sentence
        capitalized = text.strip()
        if capitalized:
            capitalized = capitalized[0].upper() + capitalized[1:]
        return f'"{capitalized}"'

    # Capitalize first letter of each sentence
    capitalized_sentences = []
    for sentence in sentences:
        sentence = sentence.strip()
        if sentence:
            sentence = sentence[0].upper() + sentence[1:]
        capitalized_sentences.append(sentence)

    if len(capitalized_sentences) <= 1:
        # Single sentence - just quote it
        return f'"{capitalized_sentences[0]}"'

    # Multiple sentences - add pause markers
    result_parts = []
    for i, sentence in enumerate(capitalized_sentences):
        # Quote the sentence
        quoted = f'"{sentence}"'
        result_parts.append(quoted)

        # Add pause marker between sentences (not after last)
        if i < len(capitalized_sentences) - 1:
            # Use [short pause] for quick sentences (<= 3 words)
            word_count = len(sentence.split())
            if word_count <= 3:
                result_parts.append("[short pause]")
            else:
                result_parts.append("[pause]")

    return " ".join(result_parts)


def _get_transition_override(scene_data: dict) -> Optional[str]:
    """Return explicit transition override from script, or None to let Claude decide."""
    return scene_data.get('transition')


def _resolve_interpolation_end(
    take_path: Path,
    scene: str,
    scene_num: int,
    script: Optional[dict],
    pipeline: dict,
    logger
) -> tuple:
    """
    Auto-resolve end frame and end scene data for interpolation mode.

    Checks if this scene is the start of an interpolation pair (odd scene
    where the next even scene has "interpolation-end" in pipeline).

    Returns:
        (end_frame_path, end_scene_data) or (None, None) if not interpolation
    """
    video_selections = pipeline.get("10-videos", {})
    frames_selections = pipeline.get("08-frames", {})

    # Only odd scenes are start of interpolation pair
    if scene_num % 2 != 1:
        return None, None

    end_scene_name = f"{scene_num + 1:02d}-scene"
    end_video_sel = video_selections.get(end_scene_name)

    if end_video_sel != "interpolation-end":
        return None, None

    # Find end frame
    end_frame_sel = frames_selections.get(end_scene_name)
    if not end_frame_sel:
        if logger:
            logger.warning(f"No frame selection for {end_scene_name}")
        return None, None

    end_frame_path = str(take_path / "08-frames" / end_scene_name / f"{end_frame_sel}.jpg")
    if not Path(end_frame_path).exists():
        if logger:
            logger.warning(f"End frame not found: {end_frame_path}")
        return None, None

    if logger:
        logger.info(f"Interpolation: resolved end frame from {end_scene_name}/{end_frame_sel}")

    # Find end scene data from script
    end_scene_data = None
    if script:
        end_scene_data = _find_scene(script, scene_num + 1)
        if end_scene_data and logger:
            logger.info(f"Interpolation: loaded end scene data for scene {scene_num + 1}")

    return end_frame_path, end_scene_data


def _build_video_prompt(
    scene_data: dict,
    style_info: dict,
    feedback: Optional[str],
    previous_feedback: Optional[str],
    take_path: Path,
    original_prompt: Optional[str] = None,
    logger = None,
    o1_references: Optional[list] = None,
    allow_fallback: bool = False,
    end_scene_data: Optional[dict] = None,
    duration: Optional[int] = None
) -> str:
    """
    Build video prompt from scene data using Claude.

    Always uses Claude to generate prompts with proper dialogue formatting.
    By default, raises on failure. Set allow_fallback=True to fall back to
    Python-based generation if Claude fails.

    Args:
        scene_data: Scene from adapted script
        style_info: Style information from script
        feedback: Current user feedback (if any)
        previous_feedback: Accumulated previous feedback
        take_path: Path to take (for session ID)
        original_prompt: Original prompt to modify (for refinements)
        logger: Logger instance
        o1_references: List of reference image descriptions for O1 model.
                       Each item: {"index": 1, "description": "characteristic shot of Olga"}
                       Claude will use <<<image_N>>> syntax in the prompt.
        allow_fallback: If False (default), raise on Claude failure. If True, fall back to Python-based prompt.
        end_scene_data: Optional end scene data for interpolation mode.
                        When provided, prompt describes the transition from start to end frame.
        duration: Video duration in seconds (used for ACT timing in interpolation mode)

    Returns:
        Video generation prompt
    """
    # Always try Claude first for proper dialogue formatting
    # Use empty feedback string for initial generation to trigger Claude
    effective_feedback = feedback if feedback else "Generate video prompt with proper dialogue formatting"

    try:
        return _regenerate_prompt_with_claude(
            scene_data, style_info, effective_feedback, previous_feedback, take_path,
            original_prompt, logger, o1_references=o1_references,
            end_scene_data=end_scene_data, duration=duration
        )
    except Exception as e:
        if allow_fallback:
            if logger:
                logger.warning(f"Claude generation failed, using fallback: {e}")
            # Fall through to Python-based generation
        else:
            raise

    # Build prompt from components
    lines = []

    # O1 reference prefix for fallback mode
    if o1_references:
        refs = " and ".join(f"<<<image_{r['index']}>>>" for r in o1_references)
        lines.append(f"The person in the video looks exactly like {refs}.")
        lines.append("")

    # 1. ACTION (most important) - with integrated dialogue
    action = scene_data.get("action", "")
    what_is_shown = scene_data.get("what_is_shown", "")

    # Check for dialogue to integrate into action
    dialogue = scene_data.get("dialogue")
    dialogue_text = None
    delivery = None
    if dialogue:
        if isinstance(dialogue, list):
            # Multiple speakers — attribute each line, format each separately
            # NO [pause] between different speakers (confuses model into thinking it's one dialogue)
            parts = []
            for d in dialogue:
                if isinstance(d, dict):
                    char = d.get("character", "character").replace("_", " ").title()
                    t = d.get("text_video") or d.get("text")
                    dlv = d.get("delivery", "natural")
                    if t:
                        formatted_t = _format_dialogue_for_video(t)
                        parts.append(f"{char} says {formatted_t} with {dlv} delivery")
            text = ". ".join(parts) if parts else ""
            delivery = "multi-speaker"
            # Already formatted per-speaker, skip global formatting
            dialogue_text = text if text else None
        else:
            text = dialogue.get("text_video") or dialogue.get("text")
            delivery = dialogue.get('delivery', 'natural')
        if text and dialogue_text is None:
            # Format dialogue: no ALL-CAPS, add [pause]/[short pause] markers between sentences
            # (multi-speaker already formatted per-speaker above)
            dialogue_text = _format_dialogue_for_video(text)

    if action:
        # Integrate dialogue into action if present
        if dialogue_text:
            # Build action with integrated dialogue
            action_with_dialogue = f"{action} Character speaks with {delivery} delivery: {dialogue_text}"
            lines.append(f"ACTION: {action_with_dialogue}")
        else:
            lines.append(f"ACTION: {action}.")
        lines.append(f"SETTING: {what_is_shown}")
    else:
        lines.append(what_is_shown)

    # 2. CAMERA
    camera = scene_data.get("camera", "static")
    if camera and camera != "static":
        lines.append(f"\nCamera: {camera}")

    # 3. STYLE
    visual_style = style_info.get("visual", "cinematic")
    tone = style_info.get("tone", "dramatic")
    lines.append(f"\nStyle: {visual_style}, {tone}, photorealistic")

    # 4. AUDIO
    audio_ambient = scene_data.get("audio_ambient", "")
    if audio_ambient:
        lines.append(f"\nAudio: {audio_ambient}")

    # 6. NEGATIVE PROMPTS
    lines.append("\n\nNO text overlays, NO subtitles, NO watermarks, NO distorted faces or hands")

    return "\n".join(lines)


def _regenerate_prompt_with_claude(
    scene_data: dict,
    style_info: dict,
    feedback: str,
    previous_feedback: Optional[str],
    take_path: Path,
    original_prompt: Optional[str] = None,
    logger = None,
    o1_references: Optional[list] = None,
    end_scene_data: Optional[dict] = None,
    duration: Optional[int] = None
) -> str:
    """
    Use Claude to regenerate prompt incorporating feedback.

    Args:
        scene_data: Scene from adapted script
        style_info: Style information
        feedback: Current feedback
        previous_feedback: Accumulated previous feedback
        take_path: Path to take (for session ID)
        original_prompt: Original prompt to modify
        logger: Logger instance
        o1_references: List of O1 reference image descriptions for <<<image_N>>> syntax
        end_scene_data: Optional end scene data for interpolation mode
        duration: Video duration in seconds (for ACT timing in interpolation mode)

    Returns:
        Regenerated video prompt

    Raises:
        RuntimeError: If Claude CLI fails
    """
    # Load session ID
    session_file = take_path.parent / "session.txt"
    if not session_file.exists():
        raise RuntimeError(f"Session file not found: {session_file}")

    session_id = session_file.read_text().strip()
    story_path = take_path.parent

    if logger:
        logger.info(f"Calling Claude to regenerate prompt (session: {session_id})")
        logger.info(f"Original prompt exists: {original_prompt is not None}")
        if o1_references:
            logger.info(f"O1 references: {len(o1_references)}")

    # Build feedback section
    feedback_section = f"MOST RECENT FEEDBACK (apply this):\n{feedback}"
    if previous_feedback:
        feedback_section += f"\n\nPREVIOUS FEEDBACK (already addressed, for context):\n{previous_feedback}"

    # Dialogue formatting rules (shared between modes)
    dialogue_rules = """
DIALOGUE FORMATTING (critical for Kling audio):
- NEVER use ALL-CAPS for dialogue - use sentence case
- Integrate dialogue INTO the ACTION line, not as a separate "Dialogue:" line
- Always say WHO is speaking before each quoted line
- Put each character's FULL dialogue in ONE continuous quoted string (not split into separate quotes)
- Add [pause] or [short pause] markers INSIDE the quotes between sentences
- Describe character's delivery style after the quote

Single speaker example:
  The sergeant screams at the private "What did you say! [pause] Are you out of your mind!" with volcanic fury, veins bulging.

Multi-speaker example (NO [pause] BETWEEN different speakers — only INSIDE one speaker's lines):
  The officer nods and says "Outstanding work, Sergeant. [pause] You're a credit to this base." with crisp approval. The sergeant responds in flat monotone "Thank you, sir. [pause] Your top button is undone, sir." His expression does not change.

NOT this (bad — no speaker attribution, split quotes):
  ACTION: Sergeant screams at private.
  Dialogue: "WHAT DID YOU SAY!" [pause] "ARE YOU OUT OF YOUR MIND!"
"""

    # O1 reference instructions
    o1_rules = ""
    if o1_references:
        o1_rules = """
KLING O1 REFERENCE IMAGES:
This prompt is for Kling O1 (OmniVideo) model which supports reference images.
You MUST use <<<image_N>>> syntax to reference them NATURALLY in the prompt.

Available references:
"""
        for ref in o1_references:
            o1_rules += f"  <<<image_{ref['index']}>>> = {ref['description']}\n"

        o1_rules += """
HOW TO USE REFERENCES:
- Use <<<image_N>>> as the SUBJECT in the prompt, replacing generic descriptions like "a woman" or "a person"
- Place the reference naturally where the character is first mentioned
- Example: "<<<image_1>>> walks along a narrow path into the valley, wearing a red dress..."
- NOT: "The person in the video looks like <<<image_1>>>. A woman walks along..."
- The reference tells O1 what the person LOOKS like — your prompt describes what they DO
- You can reference the same image multiple times if needed
- Keep the prompt under 2500 characters total (O1 supports longer prompts than standard Kling)
"""

    # Build Claude prompt based on whether we have original prompt
    if original_prompt:
        # MODIFICATION mode: ask Claude to modify the original prompt
        claude_prompt = f"""You are modifying a video generation prompt for video AI models (Kling, Veo3).

ORIGINAL PROMPT:
{original_prompt}

{feedback_section}
{dialogue_rules}
{o1_rules}

INSTRUCTIONS:
Your task is to MODIFY the original prompt above to incorporate the feedback. Make ONLY the changes requested in the feedback - do not rewrite or restructure the entire prompt.

- Keep the original structure, format, and style
- Only modify the specific parts mentioned in the feedback
- Preserve all other details that weren't mentioned in the feedback
- Keep it under {"2500" if o1_references else "500"} characters total
{"- Make sure <<<image_N>>> references are preserved and used naturally" if o1_references else ""}

Write ONLY the modified prompt, nothing else."""
    else:
        # GENERATION mode: write from scratch (for new videos without refinement)
        # Format dialogue helper
        def _format_dialogue(raw):
            if isinstance(raw, list):
                parts = []
                for d in raw:
                    if isinstance(d, dict):
                        char = d.get('character', 'unknown')
                        text = d.get('text_video') or d.get('text', '')
                        delivery = d.get('delivery', 'natural')
                        parts.append(f"{char} ({delivery}): \"{text}\"")
                return " | ".join(parts) if parts else "none", "see per-character delivery above"
            elif isinstance(raw, dict):
                return raw.get('text_video') or raw.get('text', 'none'), raw.get('delivery', 'natural')
            return "none", "none"

        dialogue_ctx, delivery_ctx = _format_dialogue(scene_data.get('dialogue'))

        # Interpolation mode: ACT 1 / TRANSITION / ACT 2 format
        if end_scene_data:
            end_dialogue_ctx, _ = _format_dialogue(end_scene_data.get('dialogue'))

            # Use adapted script's video_prompt if available — it's already well-crafted
            start_video_prompt = scene_data.get('video_prompt', '')
            end_video_prompt = end_scene_data.get('video_prompt', '')

            # Duration split between acts
            total_dur = duration or scene_data.get('duration') or 8
            half = total_dur // 2
            act1_dur = half
            act2_dur = total_dur - half

            # Transition type — explicit override from script, or let Claude decide
            transition_override = _get_transition_override(scene_data)
            transition_instruction = f"USE THIS TRANSITION: {transition_override}" if transition_override else """CHOOSE the best transition from this list:
- "hard cut" — instant, no delay. DEFAULT — use this unless another type is clearly better.
- "fade through black" — only for dramatic mood shifts (horror, death, nightmare)
- "fade through white" — only for blinding flash, explosion, revelation
- "gentle cross-dissolve" — only for dreamy, slow, contemplative scenes
- "match cut" — only when a specific visual element connects both acts"""

            claude_prompt = f"""Write a video prompt for frame interpolation. The AI has start and end frames.

Use this EXACT structure (output ONLY this, nothing else):

ACT 1 ({act1_dur}s): [scene description]
TRANSITION: [your chosen transition type].
ACT 2 ({act2_dur}s): [scene description]
--no text overlays subtitles watermarks

START SCENE (adapted script prompt):
{start_video_prompt if start_video_prompt and not start_video_prompt.startswith('[end') else f"ACTION: {scene_data.get('action', '')}"}

END SCENE (adapted script prompt):
{end_video_prompt if end_video_prompt and not end_video_prompt.startswith('[end') else f"ACTION: {end_scene_data.get('action', '')}"}

DIALOGUE FOR ACT 1 (start scene): {dialogue_ctx}
DIALOGUE FOR ACT 2 (end scene): {end_dialogue_ctx}
AUDIO/AMBIENT: {scene_data.get('audio_ambient', 'none')}
STYLE: {style_info.get('visual', 'cinematic')}, {style_info.get('tone', 'dramatic')}

{transition_instruction}

{feedback_section}
{dialogue_rules}

RULES for each ACT:
1. Name characters explicitly — never "behind him", say "behind the man in the dark hoodie"
2. Include ALL key physical actions (plugging cord into outlet, picking up flashlight, etc.) — don't skip the action itself
3. Include dialogue with speaker name and delivery style (e.g., whispers, shouts excitedly)
4. DIALOGUE PLACEMENT IS CRITICAL: "DIALOGUE FOR ACT 1" goes ONLY in ACT 1, "DIALOGUE FOR ACT 2" goes ONLY in ACT 2. If dialogue is "none" for an act, that act has NO spoken words.
5. 200-400 characters per act — be specific about actions, the frames already show visual details
6. ACT 1 describes the START state and what begins happening
7. ACT 2 describes what CHANGED — new actions, mood shift, resolution

Write ONLY the formatted prompt (ACT 1 / TRANSITION / ACT 2 / --no line), nothing else."""

        else:
            # Standard single-frame mode
            claude_prompt = f"""You are writing a video generation prompt for {"Kling O1 (OmniVideo)" if o1_references else "video AI models (Kling, Veo3)"}.

SCENE CONTEXT:
- Action: {scene_data.get('action', 'none')}
- Visual setting: {scene_data.get('what_is_shown', '')}
- Camera: {scene_data.get('camera', 'static')}
- Audio: {scene_data.get('audio_ambient', 'none')}
- Dialogue: {dialogue_ctx}
- Delivery: {delivery_ctx}
- Style: {style_info.get('visual', 'cinematic')}, {style_info.get('tone', 'dramatic')}

{feedback_section}
{dialogue_rules}
{o1_rules}

IMPORTANT RULES:
1. Describe the ACTION step-by-step: what happens from start to finish
2. Keep it under {"2500" if o1_references else "500"} characters for video models
3. Include camera movement if specified
4. {"Do NOT include audio description (O1 does not generate audio)" if o1_references else "End with audio description"}
5. Add negative prompts: NO text overlays, NO subtitles, NO watermarks
6. Pay attention to scale/intensity words: "SHORT", "SMALL", "BRIEF" mean minimize the action
{"7. Use <<<image_N>>> references naturally as the subject — see O1 instructions above" if o1_references else ""}

Write ONLY the final video prompt, nothing else."""

    try:
        # Call Claude Code CLI with --resume to continue existing session
        result = subprocess.run(
            ["claude", "--resume", session_id,
             "--model", "sonnet",
             "-p", claude_prompt],
            capture_output=True,
            text=True,
            cwd=str(story_path),
            timeout=300,
            env=claude_env()
        )

        if logger:
            logger.info(f"Claude CLI return code: {result.returncode}")
            if result.stdout:
                logger.info(f"Claude stdout length: {len(result.stdout)}")
            if result.stderr:
                logger.warning(f"Claude stderr: {result.stderr}")

        if result.returncode == 0 and result.stdout.strip():
            if logger:
                logger.info("Successfully regenerated prompt with Claude")
            return result.stdout.strip()
        else:
            # FAIL - don't use fallback
            error_detail = result.stderr or result.stdout
            error_msg = f"Claude CLI failed with return code {result.returncode}"
            if error_detail:
                error_msg += f": {error_detail}"
            if logger:
                logger.error(error_msg)
            raise RuntimeError(error_msg)

    except subprocess.TimeoutExpired as e:
        error_msg = f"Claude CLI timeout after 300s"
        if logger:
            logger.error(error_msg)
        raise RuntimeError(error_msg) from e
    except Exception as e:
        error_msg = f"Claude CLI error: {str(e)}"
        if logger:
            logger.error(error_msg)
        raise RuntimeError(error_msg) from e


def _get_reference_images(take_path: Path, pipeline: dict,
                          scene_num: Optional[int] = None) -> list:
    """
    Get reference images for O1 provider (characteristic shot + person refs).

    In story mode with scene_num, only includes characters appearing in that scene.

    Args:
        take_path: Path to take directory
        pipeline: Pipeline dict
        scene_num: Optional scene number for per-scene character filtering

    Returns list of file paths.
    """
    ref_images = []

    # 1. Characteristic shot (always available at this stage)
    char_iter = pipeline.get("06-characteristic")
    if char_iter:
        char_dir = take_path / "06-characteristic" / char_iter
        # Find the characteristic shot image
        for ext in (".jpg", ".png", ".jpeg"):
            for img in char_dir.glob(f"*{ext}"):
                if "characteristic" in img.name or img.name.startswith("image"):
                    ref_images.append(str(img))
                    break
            if ref_images:
                break
        # Fallback: take any image in the directory
        if not ref_images:
            for ext in (".jpg", ".png", ".jpeg"):
                for img in char_dir.glob(f"*{ext}"):
                    ref_images.append(str(img))
                    break
                if ref_images:
                    break

    # 2. Person reference photos from project references
    try:
        sys.path.insert(0, str(Path(__file__).parent.parent))
        import reference_loader
        from reference_loader import (
            is_story_mode, get_episode_characters, select_character_refs,
            get_episode_locations, get_episode_objects, select_asset_refs,
        )

        project_path = take_path.parent
        refs = reference_loader.load_project_references(project_path)
        if refs.get("available"):
            if is_story_mode(refs):
                # Story mode: per-scene characters, locations, objects
                char_data = get_episode_characters(take_path, refs)
                if scene_num is not None and scene_num in char_data["by_scene"]:
                    char_ids = char_data["by_scene"][scene_num]
                else:
                    char_ids = char_data["all"]
                char_refs = select_character_refs(refs, char_ids)
                for r in char_refs:
                    if r["path"] not in ref_images:
                        ref_images.append(r["path"])

                # Per-scene location refs
                loc_data = get_episode_locations(take_path, refs)
                if scene_num is not None and scene_num in loc_data["by_scene"]:
                    loc_ids = loc_data["by_scene"][scene_num]
                else:
                    loc_ids = loc_data["all"]
                loc_refs = select_asset_refs(refs, loc_ids, "locations")
                for r in loc_refs:
                    if r["path"] not in ref_images:
                        ref_images.append(r["path"])

                # Per-scene object refs
                obj_data = get_episode_objects(take_path, refs)
                if scene_num is not None and scene_num in obj_data["by_scene"]:
                    obj_ids = obj_data["by_scene"][scene_num]
                else:
                    obj_ids = obj_data["all"]
                obj_refs = select_asset_refs(refs, obj_ids, "objects")
                for r in obj_refs:
                    if r["path"] not in ref_images:
                        ref_images.append(r["path"])
            else:
                # Product mode: single "main" person
                main_person = refs.get("people", {}).get("main", {})
                person_photos = main_person.get("photos", [])
                for photo in person_photos[:2]:
                    if photo not in ref_images:
                        ref_images.append(photo)
    except Exception:
        pass

    return ref_images


def _build_o1_reference_descriptions(take_path: Path, pipeline: dict, ref_images: list) -> list:
    """
    Build descriptions for O1 reference images so Claude knows what each <<<image_N>>> is.

    In story mode, extracts character_id from path and loads first line of description.md.

    Returns list of {"index": N, "description": "..."} for each reference image.
    """
    descriptions = []

    for i, ref_path in enumerate(ref_images):
        idx = i + 1
        path = Path(ref_path)

        # Determine what this image is based on its location
        if "06-characteristic" in str(path):
            desc = "characteristic shot — the definitive look of the main character (face, hair, outfit, body type)"
        elif "references/people" in str(path):
            # Extract character_id from path: .../references/people/{character_id}/sheet.jpg
            try:
                # Find the part after "people/"
                parts = path.parts
                people_idx = parts.index("people")
                character_id = parts[people_idx + 1]
                char_name = character_id.replace("_", " ").title()

                # Try to load first line of description.md for richer context
                desc_file = path.parent / "description.md"
                extra = ""
                if desc_file.exists():
                    first_line = desc_file.read_text().strip().split("\n")[0]
                    extra = f" — {first_line.lstrip('# ').strip()}"

                desc = f"{char_name} character reference ({path.name}){extra}"
            except (ValueError, IndexError):
                desc = f"person reference photo ({path.name}) — real photo showing the character's actual appearance"
        else:
            desc = f"reference image ({path.name})"

        descriptions.append({"index": idx, "description": desc})

    return descriptions


def _collect_feedback(scene_video_dir: Path, current_based_on: dict) -> list:
    """
    Collect ALL feedback from iterations with same based_on.

    Args:
        scene_video_dir: Path to scene video directory
        current_based_on: Current based_on dict

    Returns:
        List of {"iteration": str, "feedback": str}
    """
    all_feedback = []

    if not scene_video_dir.exists():
        return all_feedback

    for iter_dir in sorted(scene_video_dir.iterdir()):
        if not iter_dir.is_dir() or not iter_dir.name.isdigit():
            continue

        metadata_file = iter_dir / "metadata.json"
        if metadata_file.exists():
            with open(metadata_file) as f:
                meta = json.load(f)

            # Check if same based_on
            if meta.get("based_on") == current_based_on:
                feedback_file = iter_dir / "feedback.txt"
                if feedback_file.exists():
                    all_feedback.append({
                        "iteration": iter_dir.name,
                        "feedback": feedback_file.read_text().strip()
                    })

    return all_feedback


