from __future__ import annotations

from typing import Any, Callable

try:
    from . import cast_context as cast_context_policy
    from . import character_profile as character_profile_policy
    from . import pair_clothing
    from . import pair_options
    from . import softcore_text_policy
except ImportError:  # Allows local smoke tests with top-level imports.
    import cast_context as cast_context_policy
    import character_profile as character_profile_policy
    import pair_clothing
    import pair_options
    import softcore_text_policy


AxisRng = Callable[[dict[str, int], str, int, int], Any]
Choose = Callable[[Any, list[str]], str]
CharacterContextForLabel = Callable[
    [str, dict[str, dict[str, Any]], Any, str, str, bool, bool],
    tuple[dict[str, Any], dict[str, Any] | None],
]
CharacterSlotLabelMap = Callable[[list[dict[str, Any]]], dict[str, dict[str, Any]]]
ParseCharacterCast = Callable[[str | dict[str, Any] | list[Any] | None], list[dict[str, Any]]]
SlotIsPov = Callable[[dict[str, Any] | None], bool]
SlotSoftcoreOutfit = Callable[[dict[str, Any] | None, Any], str]


def cast_summary_phrase(women_count: int, men_count: int) -> str:
    return cast_context_policy.cast_summary_phrase(women_count, men_count)


def insta_descriptor_from_row(row: dict[str, Any]) -> str:
    return character_profile_policy.descriptor_from_parts(
        "woman",
        row.get("age_band") or row.get("age"),
        row.get("body_phrase"),
        row.get("skin"),
        row.get("hair"),
        row.get("eyes"),
        row.get("descriptor_detail"),
    )


def insta_descriptor_from_context(context: dict[str, Any]) -> str:
    subject = str(context.get("subject") or context.get("subject_type") or "person").strip()
    return character_profile_policy.descriptor_from_parts(
        subject,
        context.get("age"),
        context.get("body_phrase"),
        context.get("skin"),
        context.get("hair"),
        context.get("eyes"),
        context.get("descriptor_detail"),
    )


def prompt_cast_descriptors(text: str) -> str:
    return str(text or "").replace("Woman A / primary creator:", "Woman A:")


def cast_descriptor_entries_from_slots(
    *,
    seed_config: dict[str, int],
    seed: int,
    row_number: int,
    ethnicity: str,
    figure: str,
    no_plus_women: bool,
    no_black: bool,
    women_count: int,
    men_count: int,
    character_slots: list[dict[str, Any]],
    character_slot_map: dict[str, dict[str, Any]],
    primary_descriptor: str = "",
    axis_rng: AxisRng,
    character_context_for_label: CharacterContextForLabel,
    slot_is_pov: SlotIsPov,
) -> tuple[list[str], list[dict[str, Any]]]:
    rng = axis_rng(seed_config, "person", seed, row_number + 997)
    descriptors: list[str] = []
    for index in range(max(0, women_count)):
        label = f"Woman {chr(ord('A') + index)}"
        if index == 0 and primary_descriptor:
            descriptors.append(f"Woman A / primary creator: {primary_descriptor}")
            continue
        context, _slot = character_context_for_label(
            label,
            character_slot_map,
            rng,
            ethnicity,
            figure,
            no_plus_women,
            no_black,
        )
        descriptors.append(f"{label}: {insta_descriptor_from_context(context)}")
    for index in range(max(0, men_count)):
        label = f"Man {chr(ord('A') + index)}"
        if slot_is_pov(character_slot_map.get(label)):
            continue
        context, _slot = character_context_for_label(
            label,
            character_slot_map,
            rng,
            ethnicity,
            figure,
            no_plus_women,
            no_black,
        )
        descriptors.append(f"{label}: {insta_descriptor_from_context(context)}")
    return descriptors, character_slots


def cast_descriptor_entries(
    *,
    seed_config: dict[str, int],
    seed: int,
    row_number: int,
    ethnicity: str,
    figure: str,
    no_plus_women: bool,
    no_black: bool,
    women_count: int,
    men_count: int,
    character_cast: str | dict[str, Any] | list[Any] | None = "",
    primary_descriptor: str = "",
    parse_character_cast: ParseCharacterCast,
    character_slot_label_map: CharacterSlotLabelMap,
    axis_rng: AxisRng,
    character_context_for_label: CharacterContextForLabel,
    slot_is_pov: SlotIsPov,
) -> tuple[list[str], list[dict[str, Any]]]:
    slots = parse_character_cast(character_cast)
    label_map = character_slot_label_map(slots)
    return cast_descriptor_entries_from_slots(
        seed_config=seed_config,
        seed=seed,
        row_number=row_number,
        ethnicity=ethnicity,
        figure=figure,
        no_plus_women=no_plus_women,
        no_black=no_black,
        women_count=women_count,
        men_count=men_count,
        character_slots=slots,
        character_slot_map=label_map,
        primary_descriptor=primary_descriptor,
        axis_rng=axis_rng,
        character_context_for_label=character_context_for_label,
        slot_is_pov=slot_is_pov,
    )


def softcore_partner_styling(
    *,
    seed_config: dict[str, int],
    seed: int,
    row_number: int,
    women_count: int,
    men_count: int,
    pov_labels: list[str] | None,
    label_map: dict[str, dict[str, Any]] | None,
    axis_rng: AxisRng,
    choose: Choose,
    slot_softcore_outfit: SlotSoftcoreOutfit,
) -> dict[str, Any]:
    content_rng = axis_rng(seed_config, "content", seed, row_number + 421)
    pose_rng = axis_rng(seed_config, "pose", seed, row_number + 421)
    pov_set = set(pov_labels or [])
    outfits: list[str] = []
    for index in range(max(0, women_count - 1)):
        label = chr(ord("B") + index)
        full_label = f"Woman {label}"
        outfit = slot_softcore_outfit((label_map or {}).get(full_label), content_rng) or choose(
            content_rng,
            pair_options.INSTA_OF_SOFTCORE_PARTNER_WOMEN_OUTFITS,
        )
        sentence = pair_clothing.softcore_outfit_sentence(full_label, outfit)
        if sentence:
            outfits.append(sentence)
    for index in range(max(0, men_count)):
        label = chr(ord("A") + index)
        full_label = f"Man {label}"
        if full_label in pov_set:
            continue
        outfit = slot_softcore_outfit((label_map or {}).get(full_label), content_rng) or choose(
            content_rng,
            pair_options.INSTA_OF_SOFTCORE_PARTNER_MEN_OUTFITS,
        )
        sentence = pair_clothing.softcore_outfit_sentence(full_label, outfit)
        if sentence:
            outfits.append(sentence)
    return {
        "outfits": outfits,
        "pose": choose(pose_rng, pair_options.SOFTCORE_CAST_POSES),
    }


def resolve_insta_pair_cast_context(
    *,
    soft_row: dict[str, Any],
    options: dict[str, Any],
    parsed_seed_config: dict[str, int],
    seed: int,
    row_number: int,
    ethnicity: str,
    figure: str,
    no_plus_women: bool,
    no_black: bool,
    hard_women_count: int,
    hard_men_count: int,
    character_slots: list[dict[str, Any]],
    character_slot_map: dict[str, dict[str, Any]],
    pov_character_labels: list[str],
    platform_styles: dict[str, str],
    soft_levels: dict[str, str],
    hardcore_levels: dict[str, str],
    axis_rng: AxisRng,
    character_context_for_label: CharacterContextForLabel,
    slot_is_pov: SlotIsPov,
    choose: Choose,
    slot_softcore_outfit: SlotSoftcoreOutfit,
    parsed_softcore_seed_config: dict[str, int] | None = None,
) -> dict[str, Any]:
    soft_seed_config = parsed_softcore_seed_config or parsed_seed_config
    descriptor = insta_descriptor_from_row(soft_row)
    cast_descriptors, _descriptor_slots = cast_descriptor_entries_from_slots(
        seed_config=parsed_seed_config,
        seed=seed,
        row_number=row_number,
        ethnicity=ethnicity,
        figure=figure,
        no_plus_women=no_plus_women,
        no_black=no_black,
        women_count=hard_women_count,
        men_count=hard_men_count,
        character_slots=character_slots,
        character_slot_map=character_slot_map,
        primary_descriptor=descriptor,
        axis_rng=axis_rng,
        character_context_for_label=character_context_for_label,
        slot_is_pov=slot_is_pov,
    )
    cast_descriptor_text = prompt_cast_descriptors("; ".join(cast_descriptors))
    same_softcore_cast = options["softcore_cast"] == "same_as_hardcore"
    soft_cast_descriptor_text = cast_descriptor_text if same_softcore_cast else f"Woman A: {descriptor}"
    soft_partner_styling = softcore_partner_styling(
        seed_config=soft_seed_config,
        seed=seed,
        row_number=row_number,
        women_count=hard_women_count if same_softcore_cast else 1,
        men_count=hard_men_count if same_softcore_cast else 0,
        pov_labels=pov_character_labels if same_softcore_cast else [],
        label_map=character_slot_map,
        axis_rng=axis_rng,
        choose=choose,
        slot_softcore_outfit=slot_softcore_outfit,
    )
    if not same_softcore_cast:
        soft_partner_styling = {"outfits": [], "pose": ""}
    soft_partner_outfit_text = "; ".join(soft_partner_styling["outfits"])

    soft_cast = (
        "solo creator setup with Woman A alone"
        if options["softcore_cast"] == "solo"
        else f"soft creator-teaser setup with {cast_summary_phrase(hard_women_count, hard_men_count)}"
    )
    soft_cast_presence = (
        softcore_text_policy.softcore_cast_presence_phrase(
            same_cast=same_softcore_cast,
            pov_labels=pov_character_labels,
            cast_label="Woman A and the listed partners",
            woman_label="Woman A",
        )
        + ". "
    )
    soft_cast_styling_sentence = (
        f"Partner softcore styling: {soft_partner_outfit_text}. Cast pose: {soft_partner_styling['pose']}. "
        if same_softcore_cast and soft_partner_outfit_text
        else ""
    )
    hard_cast = cast_summary_phrase(hard_women_count, hard_men_count)
    soft_descriptor_sentence = (
        f"Cast descriptors: {soft_cast_descriptor_text}. "
        if same_softcore_cast
        else f"Woman A: {descriptor}. "
    )

    return {
        "descriptor": descriptor,
        "cast_descriptors": cast_descriptors,
        "cast_descriptor_text": cast_descriptor_text,
        "soft_cast_descriptor_text": soft_cast_descriptor_text,
        "soft_partner_styling": soft_partner_styling,
        "soft_partner_outfit_text": soft_partner_outfit_text,
        "platform_style": platform_styles[options["platform_style"]],
        "soft_level": soft_levels[options["softcore_level"]],
        "hard_level": hardcore_levels[options["hardcore_level"]],
        "soft_cast": soft_cast,
        "soft_cast_presence": soft_cast_presence,
        "soft_cast_styling_sentence": soft_cast_styling_sentence,
        "hard_cast": hard_cast,
        "soft_descriptor_sentence": soft_descriptor_sentence,
    }
