"""
visual_sync.py — Vision-based bible text extraction from approved images.

Uses Gemini Flash to analyze character images and propose structured
bible field updates. Human confirmation required before write.

Architecture decision: Consultation 2026-03-02, Approach C (Hybrid).
See starsend/consultations/design_lock_writeback/SYNTHESIS.md
"""
import json
import base64
import os
from pathlib import Path
from typing import Optional

import google.generativeai as genai

from recoil.core.model_profiles import get_model

# Flash for extraction — human gate catches errors, Pro unnecessary
_MODEL = get_model("flash", "text")

_SYSTEM_PROMPT = """You are a Script Supervisor ensuring continuity for a TV production.

Input: A character description (current bible text) and an approved production image.
Task: Update the specified fields to accurately describe what is visible in the image.

Rules:
1. Be descriptive and concrete — materials, colors, specific items.
2. If the text contradicts the image, update to match the image.
3. If a detail in the text is not visible in the image but not contradicted by it (e.g., hidden by camera angle), PRESERVE it.
4. Use dry production language (e.g., 'worn leather vest' not 'cool looking vest').
5. Retain existing terminology where possible.
6. Output valid JSON only — no markdown fencing, no explanation."""

_PROMPT_HERO = """Analyze this character's HERO IMAGE (the actor's face and body).

Current bible text:
visual_description: {visual_description}
wardrobe_description: {wardrobe_description}

Output a JSON object with these fields updated to match the image:
{{
  "visual_description": "...",
  "wardrobe_description": "..."
}}"""

_PROMPT_PHASE = """Analyze this WARDROBE PHASE image (costume fitting — focus on clothing, hair, accessories).

Current bible text:
wardrobe_description: {wardrobe_description}
hair_makeup: {hair_makeup}
distinguishing_marks: {distinguishing_marks}

Output a JSON object with these fields updated to match the image:
{{
  "wardrobe_description": "...",
  "hair_makeup": "...",
  "distinguishing_marks": "..."
}}

IMPORTANT: Do NOT describe the character's face or body build — only clothing, hair styling, makeup, and visible marks/accessories."""


def _load_image_part(image_path: str) -> dict:
    """Load an image file as a Gemini API inline_data Part."""
    abs_path = Path(image_path)
    if not abs_path.exists():
        raise FileNotFoundError(f"Image not found: {abs_path}")

    data = abs_path.read_bytes()
    ext = abs_path.suffix.lower().lstrip(".")
    mime_map = {
        "jpg": "image/jpeg",
        "jpeg": "image/jpeg",
        "png": "image/png",
        "webp": "image/webp",
    }
    mime = mime_map.get(ext, "image/png")
    return {"inline_data": {"mime_type": mime, "data": base64.b64encode(data).decode()}}


def _strip_markdown_fencing(text: str) -> str:
    """Remove markdown code fences from LLM output."""
    text = text.strip()
    if text.startswith("```"):
        text = text.split("\n", 1)[1] if "\n" in text else text[3:]
        if text.endswith("```"):
            text = text[:-3].strip()
        if text.startswith("json"):
            text = text[4:].strip()
    return text


def propose_visual_sync(
    image_path: str,
    current_text: dict,
    sync_type: str = "phase",
) -> dict:
    """
    Analyze an approved image and propose bible text updates.

    Args:
        image_path: Absolute path to the approved image.
        current_text: Dict of current bible field values.
        sync_type: "hero" for base traits (face/body + phase[0] wardrobe),
                   "phase" for wardrobe/hair/marks only.

    Returns:
        Dict with proposed field values.
    """
    api_key = os.environ.get("GOOGLE_API_KEY") or os.environ.get("GEMINI_API_KEY")
    if not api_key:
        raise RuntimeError("GOOGLE_API_KEY or GEMINI_API_KEY required")

    genai.configure(api_key=api_key)
    model = genai.GenerativeModel(_MODEL, system_instruction=_SYSTEM_PROMPT)

    if sync_type == "hero":
        prompt = _PROMPT_HERO.format(
            visual_description=current_text.get("visual_description", ""),
            wardrobe_description=current_text.get("wardrobe_description", ""),
        )
    else:
        prompt = _PROMPT_PHASE.format(
            wardrobe_description=current_text.get("wardrobe_description", ""),
            hair_makeup=current_text.get("hair_makeup", ""),
            distinguishing_marks=current_text.get("distinguishing_marks", ""),
        )

    image_part = _load_image_part(image_path)
    response = model.generate_content([image_part, prompt])
    text = _strip_markdown_fencing(response.text)

    return json.loads(text)
