from __future__ import annotations

from typing import Any

try:
    from . import caption_format_route
    from . import caption_metadata_routes
    from . import caption_policy
    from . import caption_text_policy
    from . import formatter_input as input_policy
    from .prompt_hygiene import sanitize_prose_text
except ImportError:  # Allows local smoke tests with `python -c`.
    import caption_format_route
    import caption_metadata_routes
    import caption_policy
    import caption_text_policy
    import formatter_input as input_policy
    from prompt_hygiene import sanitize_prose_text


OLD_TRIGGER = caption_policy.OLD_TRIGGER
DEFAULT_TRIGGER = caption_policy.DEFAULT_TRIGGER
STYLE_TAILS = caption_policy.STYLE_TAILS

PROMPT_FIELD_LABELS = caption_text_policy.PROMPT_FIELD_LABELS
ITEM_LABELS = caption_policy.ITEM_LABELS
ACTION_FAMILY_CAPTION_LABELS = caption_policy.ACTION_FAMILY_CAPTION_LABELS
POSITION_FAMILY_CAPTION_LABELS = caption_policy.POSITION_FAMILY_CAPTION_LABELS


def _clean_text(value: Any) -> str:
    return caption_text_policy.clean_text(value)


def _is_false(value: Any) -> bool:
    return caption_text_policy.is_false(value)


def _expression_disabled(row: dict[str, Any]) -> bool:
    return caption_text_policy.expression_disabled(row)


def _cap_first(text: str) -> str:
    return caption_text_policy.cap_first(text)


def _article(noun_phrase: str) -> str:
    return caption_text_policy.article(noun_phrase)


def _sentence(text: str) -> str:
    return caption_text_policy.sentence(text)


def _join_sentences(parts: list[str]) -> str:
    return caption_text_policy.join_sentences(parts)


def _formatter_hint_parts(row: dict[str, Any]) -> list[str]:
    return caption_text_policy.formatter_hint_parts(row)


def _append_formatter_hints(prose: str, row: dict[str, Any]) -> str:
    return caption_text_policy.append_formatter_hints(prose, row)


def _human_join(parts: list[str]) -> str:
    return caption_text_policy.human_join(parts)


def _metadata_action_label(row: dict[str, Any], default: str = "sexual pose") -> str:
    return caption_text_policy.metadata_action_label(row, default)


def _prompt_cast_descriptors(text: str) -> str:
    return caption_text_policy.prompt_cast_descriptors(text)


def _cast_entries(text: str) -> list[tuple[str, str]]:
    return caption_text_policy.cast_entries(text)


def _natural_cast_descriptor_text(text: str) -> str:
    return caption_text_policy.natural_cast_descriptor_text(text)


def _cast_labels(text: str) -> list[str]:
    return caption_text_policy.cast_labels(text)


def _natural_label_text(text: Any, labels: list[str]) -> str:
    return caption_text_policy.natural_label_text(text, labels)


def _strip_style_tail(text: str) -> str:
    return caption_text_policy.strip_style_tail(text)


def _remove_trigger(text: str, trigger: str) -> str:
    return caption_text_policy.remove_trigger(text, trigger)


def _with_trigger(text: str, trigger: str, include_trigger: bool) -> str:
    return caption_text_policy.with_trigger(text, trigger, include_trigger)


def _maybe_json(text: str) -> dict[str, Any] | None:
    return input_policy.maybe_json(text)


def _row_from_inputs(source_text: str, metadata_json: str, input_hint: str) -> tuple[dict[str, Any] | None, str]:
    return input_policy.row_from_inputs(source_text, metadata_json, input_hint)


def _prompt_field(text: str, label: str) -> str:
    return caption_text_policy.prompt_field(text, label)


def _row_value(row: dict[str, Any], key: str, labels: tuple[str, ...] = ()) -> str:
    return caption_text_policy.row_value(row, key, labels)


def _field_from_any_prompt(text: str, labels: tuple[str, ...]) -> str:
    return caption_text_policy.field_from_any_prompt(text, labels)


def _normalize_composition(text: str) -> str:
    return caption_text_policy.normalize_composition(text)


def _clean_clothing(text: str) -> str:
    return caption_text_policy.clean_clothing(text)


def _body_phrase(body: Any, figure_note: Any = "") -> str:
    return caption_text_policy.body_phrase(body, figure_note)


def _single_caption_front(row: dict[str, Any]) -> dict[str, str]:
    return caption_text_policy.single_caption_front(row)


def _pose_clause(pose: str) -> str:
    return caption_text_policy.pose_clause(pose)


def _age_subject(age: str, subject: str) -> str:
    return caption_text_policy.age_subject(age, subject)


def _clean_age_phrase(age: str) -> str:
    return caption_text_policy.clean_age_phrase(age)


def _subject_phrase_from_counts(row: dict[str, Any]) -> str:
    return caption_text_policy.subject_phrase_from_counts(row)


def _verb_for_row(row: dict[str, Any]) -> str:
    return caption_text_policy.verb_for_row(row)


def _detail_allows(level: str, dense_only: bool = False) -> bool:
    return caption_text_policy.detail_allows(level, dense_only=dense_only)


def _caption_metadata_route_dependencies() -> caption_metadata_routes.CaptionMetadataRouteDependencies:
    return caption_text_policy.metadata_route_dependencies(_metadata_to_prose)


def _caption_metadata_route_request(
    row: dict[str, Any],
    detail_level: str,
    keep_style: bool,
    target: str = "auto",
) -> caption_metadata_routes.CaptionMetadataRouteRequest:
    return caption_metadata_routes.CaptionMetadataRouteRequest(
        row=row,
        detail_level=detail_level,
        keep_style=keep_style,
        target=target,
    )


def _single_from_row(
    row: dict[str, Any],
    detail_level: str,
    keep_style: bool,
    target: str = "auto",
) -> tuple[str, str] | None:
    return caption_metadata_routes.single_from_row(
        _caption_metadata_route_request(row, detail_level, keep_style, target),
        _caption_metadata_route_dependencies(),
    )


def pronoun(subject: str) -> str:
    return caption_metadata_routes.pronoun(subject)


def possessive_pronoun(subject: str) -> str:
    return caption_metadata_routes.possessive_pronoun(subject)


def _couple_clothing_sentence(clothing: str) -> str:
    return caption_metadata_routes.couple_clothing_sentence(clothing, _clean_text)


def _couple_from_row(
    row: dict[str, Any],
    detail_level: str,
    keep_style: bool,
    target: str = "auto",
) -> tuple[str, str] | None:
    return caption_metadata_routes.couple_from_row(
        _caption_metadata_route_request(row, detail_level, keep_style, target),
        _caption_metadata_route_dependencies(),
    )


def _configured_cast_from_row(
    row: dict[str, Any],
    detail_level: str,
    keep_style: bool,
    target: str = "auto",
) -> tuple[str, str] | None:
    return caption_metadata_routes.configured_cast_from_row(
        _caption_metadata_route_request(row, detail_level, keep_style, target),
        _caption_metadata_route_dependencies(),
    )


def _group_or_layout_from_row(
    row: dict[str, Any],
    detail_level: str,
    keep_style: bool,
    target: str = "auto",
) -> tuple[str, str] | None:
    return caption_metadata_routes.group_or_layout_from_row(
        _caption_metadata_route_request(row, detail_level, keep_style, target),
        _caption_metadata_route_dependencies(),
    )


def _insta_of_pair_from_row(
    row: dict[str, Any],
    detail_level: str,
    keep_style: bool,
    target: str = "auto",
) -> tuple[str, str] | None:
    return caption_metadata_routes.insta_of_pair_from_row(
        _caption_metadata_route_request(row, detail_level, keep_style, target),
        _caption_metadata_route_dependencies(),
    )


def _metadata_to_prose(
    row: dict[str, Any],
    detail_level: str,
    keep_style: bool,
    target: str = "auto",
) -> tuple[str, str]:
    for builder in (
        _insta_of_pair_from_row,
        _configured_cast_from_row,
        _single_from_row,
        _couple_from_row,
        _group_or_layout_from_row,
    ):
        result = builder(row, detail_level, keep_style, target)
        if result:
            prose, method = result
            return _append_formatter_hints(prose, row), method
    prose, method = _text_to_prose(_clean_text(row.get("caption") or row.get("prompt")), detail_level, keep_style)
    return _append_formatter_hints(prose, row), method


def _prompt_to_prose(text: str, detail_level: str, keep_style: bool) -> tuple[str, str] | None:
    if ":" not in text:
        return None
    cast = _field_from_any_prompt(text, ("Cast",))
    item = _field_from_any_prompt(text, ITEM_LABELS)
    scene = _field_from_any_prompt(text, ("Setting", "Scene"))
    pose = _field_from_any_prompt(text, ("Pose",))
    role_graph = _field_from_any_prompt(text, ("Role graph",))
    expression = _field_from_any_prompt(text, ("Facial expressions", "Facial expression"))
    composition = _normalize_composition(_field_from_any_prompt(text, ("Composition",)))
    if not any((cast, item, scene, pose, role_graph, expression, composition)):
        return None

    subject = _clean_text(text.split(":", 1)[0])
    parts = []
    if subject:
        parts.append(f"{_cap_first(subject)}")
    if cast:
        parts.append(f"The cast is {cast}")
    if role_graph:
        parts.append(role_graph)
    if item:
        item_label = "sexual pose" if _field_from_any_prompt(text, ("Sexual pose",)) else "key detail"
        parts.append(f"The {item_label} is {item}")
    elif pose:
        parts.append(f"The pose is {pose}")
    scene_bits = []
    if scene:
        scene_bits.append(f"set in {scene}")
    if expression:
        scene_bits.append(f"with {expression}")
    if composition:
        scene_bits.append(f"framed as {composition}")
    if scene_bits and _detail_allows(detail_level):
        parts.append(", ".join(scene_bits))
    if keep_style:
        style = _clean_text(text.split(":", 1)[1].split(".", 1)[0])
        if style:
            parts.append(f"The visual style is {style}")
    return _join_sentences(parts), "prompt(labels)"


def _parts_to_sentence(parts: list[str], detail_level: str) -> str:
    parts = [part for part in (_clean_text(part).strip(" ,.") for part in parts) if part]
    if not parts:
        return ""
    if len(parts) == 1:
        return _sentence(parts[0])
    subject = parts[0]
    trailing_style = ""
    if parts[-1].lower().endswith("illustration"):
        trailing_style = parts.pop()
    composition = parts[-1] if len(parts) >= 2 else ""
    scene = parts[-2] if len(parts) >= 3 else ""
    details = parts[1:-2] if len(parts) >= 3 else parts[1:]
    sentences = [f"{_cap_first(subject)} includes {', '.join(details)}" if details else _cap_first(subject)]
    if _detail_allows(detail_level) and scene:
        sentences.append(f"The setting is {scene}")
    if _detail_allows(detail_level) and composition:
        sentences.append(f"The composition is {composition}")
    if trailing_style and _detail_allows(detail_level, dense_only=True):
        sentences.append(f"The visual style is {trailing_style}")
    return _join_sentences(sentences)


def _text_to_prose(text: str, detail_level: str, keep_style: bool) -> tuple[str, str]:
    text = _clean_text(text)
    prompt_result = _prompt_to_prose(text, detail_level, keep_style)
    if prompt_result:
        return prompt_result
    text = _remove_trigger(_strip_style_tail(text), DEFAULT_TRIGGER)
    text = _remove_trigger(text, OLD_TRIGGER)
    parts = [part.strip() for part in text.split(",")]
    prose = _parts_to_sentence(parts, detail_level)
    return prose or _sentence(text), "text(fallback)"


def _caption_format_dependencies() -> caption_format_route.CaptionFormatDependencies:
    return caption_format_route.CaptionFormatDependencies(
        apply_caption_profile=lambda profile, detail, style, include: caption_policy.apply_caption_profile(
            profile,
            detail_level=detail,
            style_policy=style,
            include_trigger=include,
        ),
        keep_style_terms=caption_policy.keep_style_terms,
        row_from_inputs=_row_from_inputs,
        metadata_to_prose=_metadata_to_prose,
        text_to_prose=_text_to_prose,
        with_trigger=_with_trigger,
        sanitize_prose_text=sanitize_prose_text,
    )


def naturalize_caption(
    source_text: str,
    metadata_json: str = "",
    input_hint: str = "auto",
    trigger: str = DEFAULT_TRIGGER,
    include_trigger: bool = True,
    detail_level: str = "balanced",
    style_policy: str = "drop_style_tail",
    caption_profile: str = caption_policy.CAPTION_PROFILE_DEFAULT,
    target: str = "auto",
) -> tuple[str, str]:
    """Rewrite tag-style prompt/caption text into compact natural language."""
    return caption_format_route.naturalize_caption(
        caption_format_route.CaptionFormatRequest(
            source_text=source_text,
            metadata_json=metadata_json,
            input_hint=input_hint,
            target=target,
            trigger=trigger,
            include_trigger=include_trigger,
            detail_level=detail_level,
            style_policy=style_policy,
            caption_profile=caption_profile,
        ),
        _caption_format_dependencies(),
    )


def naturalize_caption_with_trace(
    source_text: str,
    metadata_json: str = "",
    input_hint: str = "auto",
    target: str = "auto",
    trigger: str = DEFAULT_TRIGGER,
    include_trigger: bool = True,
    detail_level: str = "balanced",
    style_policy: str = "drop_style_tail",
    caption_profile: str = caption_policy.CAPTION_PROFILE_DEFAULT,
) -> tuple[str, str, str]:
    """Rewrite text like naturalize_caption and include formatter route trace JSON."""
    result = caption_format_route.naturalize_caption_result(
        caption_format_route.CaptionFormatRequest(
            source_text=source_text,
            metadata_json=metadata_json,
            input_hint=input_hint,
            target=target,
            trigger=trigger,
            include_trigger=include_trigger,
            detail_level=detail_level,
            style_policy=style_policy,
            caption_profile=caption_profile,
        ),
        _caption_format_dependencies(),
    )
    return result.as_trace_tuple()
