from __future__ import annotations

from typing import Any, Callable, Mapping

try:
    from . import camera_config as camera_policy
    from . import scene_camera_adapters
except ImportError:  # Allows local smoke tests with top-level imports.
    import camera_config as camera_policy
    import scene_camera_adapters


PovLabelResolver = Callable[[dict[str, Any]], list[str]]


def _list_from(value: Any) -> list[Any]:
    if value is None:
        return []
    if isinstance(value, list):
        return value
    return [value]


def composition_prompt(composition: Any) -> str:
    composition = str(composition or "").strip()
    if not composition:
        return composition
    lower = composition.lower()
    if lower.startswith("vertical ") or " vertical " in lower or lower.endswith(" vertical"):
        return composition
    return f"vertical {composition}"


def insert_positive_directive(prompt: str, directive: str) -> str:
    marker = " Avoid:"
    if marker in prompt:
        before, after = prompt.split(marker, 1)
        return f"{before.rstrip()} {directive}{marker}{after}"
    return f"{prompt.rstrip()} {directive}"


def camera_caption_text(parsed: dict[str, Any]) -> str:
    return camera_policy.camera_caption_text(parsed)


def coworking_composition_prompt(scene_text: Any, composition: Any, subject_kind: str = "subjects") -> str:
    return scene_camera_adapters.coworking_composition_prompt(scene_text, composition, subject_kind)


def row_scene_text(row: dict[str, Any]) -> Any:
    return row.get("scene_text") or row.get("source_scene_text") or row.get("scene")


def row_scene_theme(row: dict[str, Any]) -> str:
    return str(row.get("scene_theme") or row.get("location_theme") or "")


def row_scene_profile_key(row: dict[str, Any]) -> str:
    return str(row.get("scene_camera_profile_key") or "")


def apply_contextual_composition(row: dict[str, Any], subject_kind: str) -> dict[str, Any]:
    scene_text = row_scene_text(row)
    old_composition = str(row.get("composition") or "").strip()
    new_composition = scene_camera_adapters.contextual_composition_prompt(
        scene_text,
        old_composition,
        subject_kind,
        scene_entry=row.get("scene_entry"),
        theme=row_scene_theme(row),
        profile_key=row_scene_profile_key(row),
    )
    if not old_composition or new_composition == old_composition:
        return row
    row["source_composition"] = row.get("source_composition") or old_composition
    row["composition"] = new_composition
    row["composition_prompt"] = composition_prompt(new_composition)
    prompt = str(row.get("prompt") or "")
    replacements = (
        (f"Composition: vertical {old_composition}.", f"Composition: {composition_prompt(new_composition)}."),
        (f"Composition: {old_composition}.", f"Composition: {composition_prompt(new_composition)}."),
        (f"Framed as {old_composition}.", f"Framed as {new_composition}."),
    )
    for old_fragment, new_fragment in replacements:
        if old_fragment in prompt:
            row["prompt"] = prompt.replace(old_fragment, new_fragment)
            break
    row["caption"] = str(row.get("caption") or "").replace(f", {old_composition},", f", {new_composition},")
    return row


def scene_camera_profile_metadata(
    scene_text: Any = "",
    *,
    scene_entry: Any = None,
    theme: Any = "",
    profile_key: Any = "",
) -> dict[str, str]:
    profile = scene_camera_adapters.scene_camera_profile(
        scene_text,
        scene_entry=scene_entry,
        theme=theme,
        profile_key=profile_key,
    )
    if not profile:
        return {}
    return {
        "key": str(profile.get("key") or ""),
        "family": str(profile.get("family") or ""),
        "layout_label": str(profile.get("layout_label") or ""),
        "place": str(profile.get("place") or ""),
    }


def camera_scene_directive_for_context(
    scene_text: Any,
    composition: Any,
    camera_config: str | dict[str, Any] | None,
    pov_labels: list[str] | None = None,
    subject_kind: str = "subjects",
    compact_labels: Mapping[str, str] | None = None,
    *,
    scene_entry: Any = None,
    theme: Any = "",
    profile_key: Any = "",
) -> tuple[str, dict[str, Any]]:
    parsed = camera_policy.parse_camera_config(camera_config)
    directive = scene_camera_adapters.camera_scene_directive_for_context(
        scene_text,
        parsed,
        pov_labels,
        subject_kind,
        compact_labels,
        scene_entry=scene_entry,
        theme=theme,
        profile_key=profile_key,
    )
    return directive, parsed


def row_camera_subject_kind(row: dict[str, Any]) -> str:
    subject_type = str(row.get("subject_type") or row.get("primary_subject") or "").lower()
    if subject_type in ("woman", "adult woman") or subject_type == "single_any":
        return "woman"
    if subject_type in ("man", "adult man"):
        return "man"
    try:
        women_count = int(row.get("women_count") or 0)
        men_count = int(row.get("men_count") or 0)
    except (TypeError, ValueError):
        women_count = men_count = 0
    if women_count == 1 and men_count == 0:
        return "woman"
    if women_count == 0 and men_count == 1:
        return "man"
    if women_count + men_count == 2:
        return "couple"
    return "subjects"


def row_pov_labels(row: dict[str, Any], resolver: PovLabelResolver | None = None) -> list[str]:
    resolved: list[str] = []
    if resolver is not None:
        resolved = [str(label) for label in _list_from(resolver(row)) if str(label).strip()]
    if resolved:
        return resolved
    return [str(label) for label in _list_from(row.get("pov_character_labels")) if str(label).strip()]


def apply_camera_config(
    row: dict[str, Any],
    camera_config: str | dict[str, Any] | None,
    *,
    pov_label_resolver: PovLabelResolver | None = None,
    compact_labels: Mapping[str, str] | None = None,
) -> dict[str, Any]:
    directive, parsed = camera_policy.camera_directive(camera_config)
    pov_labels = row_pov_labels(row, pov_label_resolver)
    subject_kind = row_camera_subject_kind(row)
    row = apply_contextual_composition(row, subject_kind)
    profile_metadata = scene_camera_profile_metadata(
        row_scene_text(row),
        scene_entry=row.get("scene_entry"),
        theme=row_scene_theme(row),
        profile_key=row_scene_profile_key(row),
    )
    if profile_metadata:
        row["scene_camera_profile"] = profile_metadata
        row["scene_camera_profile_key"] = profile_metadata.get("key", "")
    scene_directive, parsed = camera_scene_directive_for_context(
        row_scene_text(row),
        row.get("composition") or row.get("source_composition"),
        parsed,
        pov_labels,
        subject_kind,
        compact_labels,
        scene_entry=row.get("scene_entry"),
        theme=row_scene_theme(row),
        profile_key=row_scene_profile_key(row),
    )
    row["camera_config"] = parsed
    row["camera_scene_directive"] = scene_directive
    row["camera_directive"] = "" if pov_labels else directive
    combined_directive = " ".join(part for part in (scene_directive, row["camera_directive"]) if part)
    if not combined_directive:
        return row
    row["prompt"] = insert_positive_directive(str(row.get("prompt") or ""), combined_directive)
    caption = camera_caption_text(parsed)
    if caption and not pov_labels:
        row["caption"] = f"{row.get('caption', '').rstrip()}, {caption}"
    return row
