from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Callable

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


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


@dataclass(frozen=True)
class InstaPairBuildRequest:
    row_number: int
    start_index: int
    seed: int
    ethnicity: str
    figure: str
    no_plus_women: bool
    no_black: bool
    trigger: str
    prepend_trigger_to_prompt: bool
    seed_config: str | dict[str, Any] | None = None
    softcore_seed_config: str | dict[str, Any] | None = None
    hardcore_seed_config: str | dict[str, Any] | None = None
    options_json: str | dict[str, Any] | None = None
    filter_config: str | dict[str, Any] | None = None
    camera_config: str | dict[str, Any] | None = None
    softcore_camera_config: str | dict[str, Any] | None = None
    hardcore_camera_config: str | dict[str, Any] | None = None
    character_profile: str | dict[str, Any] | None = ""
    character_cast: str | dict[str, Any] | list[Any] | None = ""
    hardcore_position_config: str | dict[str, Any] | None = ""
    location_config: str | dict[str, Any] | None = ""
    composition_config: str | dict[str, Any] | None = ""
    style_config: str | dict[str, Any] | None = ""
    extra_positive: str = ""
    extra_negative: str = ""


@dataclass(frozen=True)
class InstaPairBuildDependencies:
    default_trigger: str
    random_subcategory: str
    soft_negative_base: str
    hard_negative_base: str
    camera_detail_choices: list[str] | tuple[str, ...]
    hardcore_clothing_continuity: dict[str, str]
    platform_styles: dict[str, str]
    soft_levels: dict[str, str]
    hardcore_levels: dict[str, str]
    parse_options: Callable[[str | dict[str, Any] | None], dict[str, Any]]
    parse_filter_config: Callable[[str | dict[str, Any] | None], dict[str, Any]]
    parse_seed_config: Callable[[str | dict[str, Any] | None], dict[str, int]]
    parse_character_cast: Callable[[str | dict[str, Any] | list[Any] | None], list[dict[str, Any]]]
    character_slot_label_map: Callable[[list[dict[str, Any]]], dict[str, dict[str, Any]]]
    pov_character_labels: Callable[[dict[str, dict[str, Any]], int], list[str]]
    softcore_category: Callable[[str], tuple[str, str]]
    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]
    hardcore_counts: Callable[[dict[str, Any]], tuple[int, int]]
    character_context_for_label: Callable[
        [str, dict[str, dict[str, Any]], Any, str, str, bool, bool],
        tuple[dict[str, Any], dict[str, Any] | None],
    ]
    slot_is_pov: Callable[[dict[str, Any] | None], bool]
    choose: Choose
    camera_config_with_mode: Callable[[str | dict[str, Any] | None, str], dict[str, Any]]
    camera_directive: Callable[[str | dict[str, Any] | None], tuple[str, dict[str, Any]]]
    apply_contextual_composition: Callable[[dict[str, Any], str], dict[str, Any]]
    contextual_composition_prompt: Callable[[Any, Any, str], str]
    composition_prompt: Callable[[Any], str]
    camera_scene_directive_for_context: Callable[
        [Any, Any, str | dict[str, Any] | None, list[str] | None, str],
        tuple[str, dict[str, Any]],
    ]
    slot_hardcore_clothing: Callable[[dict[str, Any] | None, Any], str]
    hardcore_detail_directive: Callable[[Any], str]
    camera_caption_text: Callable[[dict[str, Any]], str]


def build_insta_of_pair(request: InstaPairBuildRequest, deps: InstaPairBuildDependencies) -> dict[str, Any]:
    options = deps.parse_options(request.options_json)
    ethnicity = request.ethnicity
    figure = request.figure
    no_plus_women = request.no_plus_women
    no_black = request.no_black
    if request.filter_config:
        filters = deps.parse_filter_config(request.filter_config)
        ethnicity = filters["ethnicity"]
        figure = filters["figure"]
        no_plus_women = filters["no_plus_women"]
        no_black = filters["no_black"]

    hard_women_count, hard_men_count = deps.hardcore_counts(options)
    active_trigger = request.trigger.strip() or deps.default_trigger
    parsed_seed_config = deps.parse_seed_config(request.seed_config)
    parsed_softcore_seed_config = (
        deps.parse_seed_config(request.softcore_seed_config)
        if request.softcore_seed_config
        else parsed_seed_config
    )
    parsed_hardcore_seed_config = (
        deps.parse_seed_config(request.hardcore_seed_config)
        if request.hardcore_seed_config
        else parsed_seed_config
    )
    character_slots = deps.parse_character_cast(request.character_cast)
    character_slot_map = deps.character_slot_label_map(character_slots)
    pov_character_labels = deps.pov_character_labels(character_slot_map, hard_men_count)
    softcore_level_key = str(options["softcore_level"])
    soft_category, soft_subcategory = deps.softcore_category(softcore_level_key)

    row_route = pair_rows.build_insta_pair_rows_result(
        row_number=request.row_number,
        start_index=request.start_index,
        seed=request.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=request.character_profile,
        character_cast=request.character_cast or "",
        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=deps.random_subcategory,
        hardcore_position_config=request.hardcore_position_config,
        location_config=request.location_config or "",
        composition_config=request.composition_config or "",
        style_config=request.style_config or "",
        build_prompt=deps.build_prompt,
        axis_rng=deps.axis_rng,
        cast_expression_intensity_override=deps.cast_expression_intensity_override,
        context_from_character_slot=deps.context_from_character_slot,
        apply_character_context_to_row=deps.apply_character_context_to_row,
        disable_row_expression=deps.disable_row_expression,
        slot_softcore_outfit=deps.slot_softcore_outfit,
        softcore_outfit=deps.softcore_outfit,
        softcore_pose=deps.softcore_pose,
        softcore_item_prompt_label=deps.softcore_item_prompt_label,
        pov_prompt_directive=deps.pov_prompt_directive,
        pov_composition_prompt=deps.pov_composition_prompt,
    )
    soft_row = row_route.soft_row
    hard_row = row_route.hard_row
    hard_content_rng = row_route.hard_content_rng

    cast_context = pair_cast.resolve_insta_pair_cast_context(
        soft_row=soft_row,
        options=options,
        parsed_seed_config=parsed_seed_config,
        parsed_softcore_seed_config=parsed_softcore_seed_config,
        seed=request.seed,
        row_number=request.row_number,
        ethnicity=ethnicity,
        figure=figure,
        no_plus_women=no_plus_women,
        no_black=no_black,
        hard_women_count=hard_women_count,
        hard_men_count=hard_men_count,
        character_slots=character_slots,
        character_slot_map=character_slot_map,
        pov_character_labels=pov_character_labels,
        platform_styles=deps.platform_styles,
        soft_levels=deps.soft_levels,
        hardcore_levels=deps.hardcore_levels,
        axis_rng=deps.axis_rng,
        character_context_for_label=deps.character_context_for_label,
        slot_is_pov=deps.slot_is_pov,
        choose=deps.choose,
        slot_softcore_outfit=deps.slot_softcore_outfit,
    )

    camera_route = pair_camera.resolve_insta_pair_camera_result(
        soft_row=soft_row,
        hard_row=hard_row,
        options=options,
        camera_config=request.camera_config,
        softcore_camera_config=request.softcore_camera_config,
        hardcore_camera_config=request.hardcore_camera_config,
        hard_women_count=hard_women_count,
        hard_men_count=hard_men_count,
        pov_character_labels=pov_character_labels,
        camera_detail_choices=deps.camera_detail_choices,
        camera_config_with_mode=deps.camera_config_with_mode,
        camera_directive=deps.camera_directive,
        apply_contextual_composition=deps.apply_contextual_composition,
        contextual_composition_prompt=deps.contextual_composition_prompt,
        composition_prompt=deps.composition_prompt,
        camera_scene_directive_for_context=deps.camera_scene_directive_for_context,
    )
    soft_row = camera_route.soft_row
    hard_row = camera_route.hard_row
    hard_scene = camera_route.hard_scene

    character_hardcore_clothing_entries = pair_clothing.character_hardcore_clothing_entries(
        character_slot_map,
        hard_women_count,
        hard_men_count,
        pov_character_labels,
        hard_content_rng,
        deps.slot_hardcore_clothing,
    )
    clothing_route = pair_clothing.resolve_hardcore_pair_clothing_result(
        hard_row=hard_row,
        mode=options["hardcore_clothing_continuity"],
        softcore_outfit=soft_row["item"],
        character_hardcore_clothing_entries=character_hardcore_clothing_entries,
        men_count=hard_men_count,
        pov_labels=pov_character_labels,
        rng=hard_content_rng,
        continuity_map=deps.hardcore_clothing_continuity,
        choose=deps.choose,
        label_map=character_slot_map,
        slot_hardcore_clothing=deps.slot_hardcore_clothing,
    )
    if clothing_route.requires_body_exposure_scene:
        hard_scene = pair_clothing.body_exposure_scene_text(hard_scene)
        hard_row["source_scene_text"] = hard_row.get("source_scene_text") or hard_row.get("scene_text", "")
        hard_row["scene_text"] = hard_scene

    hard_detail_density = options["hardcore_detail_density"]
    return pair_output.assemble_insta_pair_metadata(
        active_trigger=active_trigger,
        prepend_trigger_to_prompt=bool(request.prepend_trigger_to_prompt),
        extra_positive=request.extra_positive,
        extra_negative=request.extra_negative,
        soft_negative_base=deps.soft_negative_base,
        hard_negative_base=deps.hard_negative_base,
        options=options,
        platform_style=cast_context["platform_style"],
        soft_descriptor_sentence=cast_context["soft_descriptor_sentence"],
        soft_level=cast_context["soft_level"],
        soft_cast=cast_context["soft_cast"],
        soft_cast_presence=cast_context["soft_cast_presence"],
        soft_cast_styling_sentence=cast_context["soft_cast_styling_sentence"],
        soft_row=soft_row,
        soft_camera_scene_sentence=camera_route.soft_camera_scene_sentence,
        soft_camera_sentence=camera_route.soft_camera_sentence,
        hard_level=cast_context["hard_level"],
        hard_cast=cast_context["hard_cast"],
        cast_descriptor_text=cast_context["cast_descriptor_text"],
        pov_directive=deps.pov_prompt_directive(pov_character_labels),
        pov_character_labels=pov_character_labels,
        hard_clothing_sentence=clothing_route.hardcore_clothing_sentence,
        hard_row=hard_row,
        hard_scene=hard_scene,
        hard_camera_scene_sentence=camera_route.hard_camera_scene_sentence,
        hard_composition=camera_route.hard_composition,
        hard_detail_directive=deps.hardcore_detail_directive(hard_detail_density),
        hard_camera_sentence=camera_route.hard_camera_sentence,
        descriptor=cast_context["descriptor"],
        soft_partner_outfit_text=cast_context["soft_partner_outfit_text"],
        soft_partner_styling=cast_context["soft_partner_styling"],
        soft_camera_scene_directive=camera_route.soft_camera_scene_directive,
        soft_camera_config=camera_route.soft_camera_config,
        soft_camera_directive=camera_route.soft_camera_directive,
        hard_camera_scene_directive=camera_route.hard_camera_scene_directive,
        hard_camera_config=camera_route.hard_camera_config,
        hard_camera_directive=camera_route.hard_camera_directive,
        camera_caption_text=deps.camera_caption_text,
        cast_descriptors=cast_context["cast_descriptors"],
        character_hardcore_clothing_entries=character_hardcore_clothing_entries,
        pov_hardcore_clothing_entries=clothing_route.pov_hardcore_clothing,
        default_man_hardcore_clothing_entries=clothing_route.default_man_hardcore_clothing,
        hard_clothing_state=clothing_route.hardcore_clothing_state,
        hard_detail_density=hard_detail_density,
        hard_women_count=hard_women_count,
        hard_men_count=hard_men_count,
        character_slots=character_slots,
        character_slot_map=character_slot_map,
    )
