from __future__ import annotations

import re
from dataclasses import dataclass
from typing import Any, Callable


@dataclass(frozen=True)
class KreaNormalRowRequest:
    row: dict[str, Any]
    detail_level: str
    style_mode: str
    subject_type: str
    primary: str
    item: str
    scene: str
    pose: str
    expression: str
    composition: str
    camera: str
    camera_scene: str
    style: str


@dataclass(frozen=True)
class KreaNormalRowPrompt:
    prompt: str
    method: str

    def as_tuple(self) -> tuple[str, str]:
        return self.prompt, self.method


@dataclass(frozen=True)
class KreaNormalRowDependencies:
    clean: Callable[[Any], str]
    row_value: Callable[[dict[str, Any], str, tuple[str, ...]], str]
    age_subject: Callable[[dict[str, Any], str], str]
    age_detail_phrase: Callable[[Any], str]
    appearance_phrase: Callable[[dict[str, Any]], str]
    with_indefinite_article: Callable[[str], str]
    paragraph: Callable[[list[str]], str]


def _couple_clothing_phrase(item: str, clean: Callable[[Any], str]) -> str:
    item = clean(item)
    lower = item.lower()
    partner_text = re.sub(r"\bPartner ([AB]) wears\b", r"Partner \1 wearing", item)
    partner_text = re.sub(r"\bPartner ([AB]) has\b", r"Partner \1 with", partner_text)
    if lower.startswith("partner a "):
        return f"The outfits show {partner_text}"
    if lower.startswith(("two ", "paired ", "coordinated ")):
        return f"The outfits are {partner_text}"
    return f"The couple wears {item}"


def _cap_first(text: str) -> str:
    text = str(text or "").strip()
    return f"{text[:1].upper()}{text[1:]}" if text else ""


def _couple_subject_phrase(subject: str, ages: str) -> str:
    subject = _cap_first(subject or "adult couple")
    ages = str(ages or "").strip()
    if ages:
        return f"{subject}, {ages}"
    return subject


def _framed_composition_phrase(composition: str, prefix: str = "framed as") -> str:
    composition = re.sub(r"\s+composition$", "", str(composition or "").strip(), flags=re.IGNORECASE)
    composition = re.sub(
        r"\bcomposition\b",
        "frame",
        composition,
        flags=re.IGNORECASE,
    ).strip(" ,")
    if not composition:
        return ""
    return f"{prefix} {composition}"


def _appearance_with_phrase(appearance: str, with_indefinite_article: Callable[[str], str]) -> str:
    appearance = str(appearance or "").strip()
    if not appearance:
        return ""
    first_clause = appearance.split(",", 1)[0].lower()
    if re.search(r"\b(?:body|build|figure|frame|physique|silhouette)\b", first_clause):
        nested_shape = re.match(
            r"^(.+\b(?:body|build|figure|frame|physique|silhouette))\s+with\s+(.+?)(?=,\s+[^,]*(?:skin|hair|eyes)\b|$)(.*)$",
            appearance,
            flags=re.IGNORECASE,
        )
        if nested_shape:
            shape = with_indefinite_article(nested_shape.group(1).strip())
            detail = nested_shape.group(2).strip()
            rest = nested_shape.group(3).strip()
            return f"with {shape} defined by {detail}{rest}"
        appearance = with_indefinite_article(appearance)
    return f"with {appearance}"


def format_normal_row_result(
    request: KreaNormalRowRequest,
    deps: KreaNormalRowDependencies,
) -> KreaNormalRowPrompt:
    row = request.row
    subject_type = request.subject_type
    primary = request.primary
    item = request.item
    scene = request.scene
    pose = request.pose
    expression = request.expression
    composition = request.composition
    camera = request.camera
    camera_scene = request.camera_scene
    style = request.style
    detail_level = request.detail_level

    if primary in ("woman", "man") or subject_type in ("woman", "man", "single_any"):
        subject = deps.age_subject(row, "adult woman")
        appearance = deps.appearance_phrase(row)
        subject_phrase = deps.with_indefinite_article(subject)
        appearance_phrase = _appearance_with_phrase(appearance, deps.with_indefinite_article)
        if appearance_phrase:
            subject_phrase = f"{subject_phrase} {appearance_phrase}"
        parts = [
            subject_phrase,
            f"wearing {item}" if item else "",
            f"{pose}" if pose else "",
            f"with {expression}" if expression else "",
            f"in {scene}" if scene else "",
            camera_scene,
            _framed_composition_phrase(composition),
            camera,
            style if detail_level != "concise" else "",
        ]
        return KreaNormalRowPrompt(
            deps.paragraph([", ".join(part for part in parts[:5] if part), *parts[5:]]),
            "metadata(single)",
        )

    if subject_type == "couple" or primary in ("two women", "two men", "a woman and a man"):
        subject = deps.clean(row.get("subject_phrase") or primary or "adult couple")
        if subject == "woman and man":
            subject = "a woman and a man"
        ages = deps.age_detail_phrase(deps.row_value(row, "age", ("Ages",)) or row.get("age_band"))
        body = deps.row_value(row, "body", ("Body types",)) or deps.clean(row.get("body_type"))
        parts = [
            _couple_subject_phrase(subject, ages),
            f"Body types: {body}" if body else "",
            _couple_clothing_phrase(item, deps.clean) if item else "",
            f"The pose is {pose}" if pose else "",
            f"The setting is {scene}" if scene else "",
            camera_scene,
            f"Facial expressions are {expression}" if expression else "",
            _framed_composition_phrase(composition, "The image is framed as"),
            camera,
            style if detail_level != "concise" else "",
        ]
        return KreaNormalRowPrompt(deps.paragraph(parts), "metadata(couple)")

    subject = deps.age_subject(row, primary or "adult scene")
    parts = [
        f"{subject}",
        f"featuring {item}" if item else "",
        f"in {scene}" if scene else "",
        camera_scene,
        f"with {expression}" if expression else "",
        _framed_composition_phrase(composition),
        camera,
        style if detail_level != "concise" else "",
    ]
    return KreaNormalRowPrompt(deps.paragraph(parts), "metadata(generic)")


def format_normal_row(
    request: KreaNormalRowRequest,
    deps: KreaNormalRowDependencies,
) -> tuple[str, str]:
    return format_normal_row_result(request, deps).as_tuple()
