from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Callable

try:
    from . import pair_clothing
except ImportError:  # Allows local smoke tests with top-level imports.
    import pair_clothing


BuildPrompt = Callable[..., dict[str, Any]]
AxisRng = Callable[[dict[str, int], str, int, int], Any]


@dataclass(frozen=True)
class InstaPairRowsRoute:
    soft_row: dict[str, Any]
    hard_row: dict[str, Any]
    hard_content_rng: Any

    def as_dict(self) -> dict[str, Any]:
        return {
            "soft_row": self.soft_row,
            "hard_row": self.hard_row,
            "hard_content_rng": self.hard_content_rng,
        }


def build_insta_pair_rows_result(
    *,
    row_number: int,
    start_index: int,
    seed: int,
    active_trigger: str,
    parsed_seed_config: dict[str, int],
    options: dict[str, Any],
    ethnicity: str,
    figure: str,
    no_plus_women: bool,
    no_black: bool,
    character_profile: str | dict[str, Any] | None,
    character_cast: str | dict[str, Any] | list[Any] | None,
    character_slot_map: dict[str, dict[str, Any]],
    pov_character_labels: list[str],
    hard_women_count: int,
    hard_men_count: int,
    soft_category: str,
    soft_subcategory: str,
    softcore_level_key: str,
    hardcore_random_subcategory: str,
    hardcore_position_config: str | dict[str, Any] | None,
    location_config: str | dict[str, Any] | None,
    composition_config: str | dict[str, Any] | None,
    build_prompt: BuildPrompt,
    axis_rng: AxisRng,
    cast_expression_intensity_override: Callable[
        [float, dict[str, dict[str, Any]], int, int, str],
        tuple[float | None, str],
    ],
    context_from_character_slot: Callable[[Any, dict[str, Any], str, str, str, bool, bool], dict[str, Any]],
    apply_character_context_to_row: Callable[[dict[str, Any], dict[str, Any]], dict[str, Any]],
    disable_row_expression: Callable[[dict[str, Any], str], dict[str, Any]],
    slot_softcore_outfit: Callable[[dict[str, Any] | None, Any], str],
    softcore_outfit: Callable[[Any, str], str],
    softcore_pose: Callable[[Any, str], str],
    softcore_item_prompt_label: Callable[[str], str],
    pov_prompt_directive: Callable[[list[str]], str],
    pov_composition_prompt: Callable[[Any, list[str]], str],
    parsed_softcore_seed_config: dict[str, int] | None = None,
    parsed_hardcore_seed_config: dict[str, int] | None = None,
    style_config: str | dict[str, Any] | None = "",
) -> InstaPairRowsRoute:
    soft_seed_config = parsed_softcore_seed_config or parsed_seed_config
    hard_seed_config = parsed_hardcore_seed_config or parsed_seed_config
    soft_content_rng = axis_rng(soft_seed_config, "content", seed, row_number + 311)
    soft_pose_rng = axis_rng(soft_seed_config, "pose", seed, row_number + 313)
    hard_content_rng = axis_rng(hard_seed_config, "content", seed, row_number + 317)
    soft_person_rng = axis_rng(soft_seed_config, "person", seed, row_number)

    soft_expression_women_count = hard_women_count if options["softcore_cast"] == "same_as_hardcore" else 1
    soft_expression_men_count = hard_men_count if options["softcore_cast"] == "same_as_hardcore" else 0
    soft_expression_enabled = bool(options["softcore_expression_enabled"])
    soft_expression_intensity = options["softcore_expression_intensity"]
    soft_expression_intensity_source = "input"
    if soft_expression_enabled:
        soft_expression_intensity, soft_expression_intensity_source = cast_expression_intensity_override(
            options["softcore_expression_intensity"],
            character_slot_map,
            soft_expression_women_count,
            soft_expression_men_count,
            "softcore",
        )
        if soft_expression_intensity is None:
            soft_expression_enabled = False
    else:
        soft_expression_intensity_source = "disabled"

    primary_slot = character_slot_map.get("Woman A")
    primary_slot_context = None
    if primary_slot:
        primary_slot_context = context_from_character_slot(
            soft_person_rng,
            primary_slot,
            "woman",
            ethnicity,
            figure,
            no_plus_women,
            no_black,
        )

    soft_row = build_prompt(
        category=soft_category,
        subcategory=soft_subcategory,
        row_number=row_number,
        start_index=start_index,
        seed=seed,
        clothing="minimal",
        ethnicity=ethnicity,
        poses="evocative",
        backside_bias=0.0,
        figure=figure,
        no_plus_women=no_plus_women,
        no_black=no_black,
        minimal_clothing_ratio=-1,
        standard_pose_ratio=-1,
        trigger=active_trigger,
        prepend_trigger_to_prompt=False,
        extra_positive="",
        extra_negative="",
        seed_config=soft_seed_config,
        women_count=1,
        men_count=0,
        expression_enabled=soft_expression_enabled,
        expression_intensity=soft_expression_intensity,
        character_profile="" if primary_slot else character_profile or "",
        character_cast="",
        location_config=location_config or "",
        composition_config=composition_config or "",
        style_config=style_config or "",
    )
    soft_row["expression_intensity_source"] = soft_expression_intensity_source
    if primary_slot_context:
        soft_row = apply_character_context_to_row(soft_row, primary_slot_context)
        soft_row["character_slot"] = primary_slot
        soft_row["character_slot_status"] = "applied:Woman A"
    if not soft_expression_enabled:
        soft_row = disable_row_expression(soft_row, soft_expression_intensity_source)

    primary_softcore_outfit = slot_softcore_outfit(primary_slot, soft_content_rng)
    soft_row["item"] = primary_softcore_outfit or softcore_outfit(soft_content_rng, softcore_level_key)
    soft_row["pose"] = softcore_pose(soft_pose_rng, softcore_level_key)
    soft_row["item_label"] = (
        "Insta/OF softcore body exposure"
        if softcore_level_key == "explicit_nude"
        else "Insta/OF softcore outfit"
    )
    soft_row["softcore_item_prompt_label"] = softcore_item_prompt_label(softcore_level_key)
    soft_row["custom_item"] = "insta_of_softcore_outfit"
    soft_row["softcore_outfit_policy"] = "character_slot:Woman A" if primary_softcore_outfit else "insta_of_safe_softcore"
    if softcore_level_key == "explicit_nude":
        soft_row["source_scene_text"] = soft_row.get("source_scene_text") or soft_row.get("scene_text", "")
        soft_row["scene_text"] = pair_clothing.body_exposure_scene_text(soft_row.get("scene_text", ""))
    soft_row["pov_character_labels"] = (
        pov_character_labels
        if options["softcore_cast"] == "same_as_hardcore"
        else []
    )
    soft_row["pov_prompt_directive"] = pov_prompt_directive(soft_row["pov_character_labels"])
    if soft_row["pov_character_labels"]:
        soft_row["source_composition"] = soft_row.get("source_composition") or soft_row.get("composition", "")
        soft_row["composition"] = pov_composition_prompt(
            soft_row["source_composition"],
            soft_row["pov_character_labels"],
        )

    hard_row = build_prompt(
        category="Hardcore sexual poses",
        subcategory=hardcore_random_subcategory,
        row_number=row_number,
        start_index=start_index,
        seed=seed,
        clothing="minimal",
        ethnicity=ethnicity,
        poses="evocative",
        backside_bias=0.0,
        figure=figure,
        no_plus_women=no_plus_women,
        no_black=no_black,
        minimal_clothing_ratio=-1,
        standard_pose_ratio=-1,
        trigger=active_trigger,
        prepend_trigger_to_prompt=False,
        extra_positive="",
        extra_negative="",
        seed_config=hard_seed_config,
        women_count=hard_women_count,
        men_count=hard_men_count,
        expression_enabled=options["hardcore_expression_enabled"],
        expression_intensity=options["hardcore_expression_intensity"],
        character_cast=character_cast or "",
        expression_phase="hardcore",
        hardcore_position_config=hardcore_position_config or "",
        location_config=location_config or "",
        composition_config=composition_config or "",
        style_config=style_config or "",
    )
    hard_row["hardcore_detail_density"] = options["hardcore_detail_density"]
    hard_row["pov_character_labels"] = pov_character_labels
    hard_row["pov_prompt_directive"] = pov_prompt_directive(pov_character_labels)

    return InstaPairRowsRoute(
        soft_row=soft_row,
        hard_row=hard_row,
        hard_content_rng=hard_content_rng,
    )


def build_insta_pair_rows(
    *,
    row_number: int,
    start_index: int,
    seed: int,
    active_trigger: str,
    parsed_seed_config: dict[str, int],
    options: dict[str, Any],
    ethnicity: str,
    figure: str,
    no_plus_women: bool,
    no_black: bool,
    character_profile: str | dict[str, Any] | None,
    character_cast: str | dict[str, Any] | list[Any] | None,
    character_slot_map: dict[str, dict[str, Any]],
    pov_character_labels: list[str],
    hard_women_count: int,
    hard_men_count: int,
    soft_category: str,
    soft_subcategory: str,
    softcore_level_key: str,
    hardcore_random_subcategory: str,
    hardcore_position_config: str | dict[str, Any] | None,
    location_config: str | dict[str, Any] | None,
    composition_config: str | dict[str, Any] | None,
    build_prompt: BuildPrompt,
    axis_rng: AxisRng,
    cast_expression_intensity_override: Callable[
        [float, dict[str, dict[str, Any]], int, int, str],
        tuple[float | None, str],
    ],
    context_from_character_slot: Callable[[Any, dict[str, Any], str, str, str, bool, bool], dict[str, Any]],
    apply_character_context_to_row: Callable[[dict[str, Any], dict[str, Any]], dict[str, Any]],
    disable_row_expression: Callable[[dict[str, Any], str], dict[str, Any]],
    slot_softcore_outfit: Callable[[dict[str, Any] | None, Any], str],
    softcore_outfit: Callable[[Any, str], str],
    softcore_pose: Callable[[Any, str], str],
    softcore_item_prompt_label: Callable[[str], str],
    pov_prompt_directive: Callable[[list[str]], str],
    pov_composition_prompt: Callable[[Any, list[str]], str],
    parsed_softcore_seed_config: dict[str, int] | None = None,
    parsed_hardcore_seed_config: dict[str, int] | None = None,
    style_config: str | dict[str, Any] | None = "",
) -> dict[str, Any]:
    return build_insta_pair_rows_result(
        row_number=row_number,
        start_index=start_index,
        seed=seed,
        active_trigger=active_trigger,
        parsed_seed_config=parsed_seed_config,
        parsed_softcore_seed_config=parsed_softcore_seed_config,
        parsed_hardcore_seed_config=parsed_hardcore_seed_config,
        options=options,
        ethnicity=ethnicity,
        figure=figure,
        no_plus_women=no_plus_women,
        no_black=no_black,
        character_profile=character_profile,
        character_cast=character_cast,
        character_slot_map=character_slot_map,
        pov_character_labels=pov_character_labels,
        hard_women_count=hard_women_count,
        hard_men_count=hard_men_count,
        soft_category=soft_category,
        soft_subcategory=soft_subcategory,
        softcore_level_key=softcore_level_key,
        hardcore_random_subcategory=hardcore_random_subcategory,
        hardcore_position_config=hardcore_position_config,
        location_config=location_config,
        composition_config=composition_config,
        style_config=style_config,
        build_prompt=build_prompt,
        axis_rng=axis_rng,
        cast_expression_intensity_override=cast_expression_intensity_override,
        context_from_character_slot=context_from_character_slot,
        apply_character_context_to_row=apply_character_context_to_row,
        disable_row_expression=disable_row_expression,
        slot_softcore_outfit=slot_softcore_outfit,
        softcore_outfit=softcore_outfit,
        softcore_pose=softcore_pose,
        softcore_item_prompt_label=softcore_item_prompt_label,
        pov_prompt_directive=pov_prompt_directive,
        pov_composition_prompt=pov_composition_prompt,
    ).as_dict()
