from __future__ import annotations

import random
from typing import Any

try:
    from . import character_config as character_policy
    from . import character_profile as character_profile_policy
    from . import character_slot as character_slot_policy
    from . import generate_prompt_batches as g
    from . import seed_config as seed_policy
except ImportError:  # Allows local smoke tests with top-level imports.
    import character_config as character_policy
    import character_profile as character_profile_policy
    import character_slot as character_slot_policy
    import generate_prompt_batches as g
    import seed_config as seed_policy


def _choose(rng: random.Random, items: list[Any]) -> Any:
    return items[rng.randrange(len(items))]


def slot_softcore_outfit(slot: dict[str, Any] | None, rng: random.Random | None = None) -> str:
    if not slot:
        return ""
    outfit = character_policy.slot_value(slot.get("softcore_outfit"))
    if outfit:
        return outfit
    if rng is None:
        return ""
    return character_policy.characteristic_choice(
        character_policy.parse_characteristics_config(slot.get("characteristics")),
        "softcore_outfits",
        rng,
    )


def slot_hardcore_clothing(slot: dict[str, Any] | None, rng: random.Random | None = None) -> str:
    if not slot:
        return ""
    clothing = character_policy.slot_value(slot.get("hardcore_clothing"))
    if clothing:
        return clothing
    if rng is None:
        return ""
    return character_policy.characteristic_choice(
        character_policy.parse_characteristics_config(slot.get("characteristics")),
        "hardcore_clothing",
        rng,
    )


def hair_descriptor_from_slot(base_hair: Any, slot: dict[str, Any], rng: random.Random) -> str:
    hair_config = character_policy.parse_hair_config(slot.get("hair_config"))
    color_choice = character_policy.normalize_hair_choice(slot.get("hair_color"), character_policy.CHARACTER_HAIR_COLOR_CHOICES)
    length_choice = character_policy.normalize_hair_choice(slot.get("hair_length"), character_policy.CHARACTER_HAIR_LENGTH_CHOICES)
    style_choice = character_policy.normalize_hair_choice(slot.get("hair_style"), character_policy.CHARACTER_HAIR_STYLE_CHOICES)
    color_options = hair_config.get("colors") or []
    length_options = hair_config.get("lengths") or []
    style_options = hair_config.get("styles") or []
    if (
        color_choice == "random"
        and length_choice == "random"
        and style_choice == "random"
        and not color_options
        and not length_options
        and not style_options
    ):
        return ""
    if color_choice != "random":
        color_key = color_choice
    elif color_options:
        color_key = _choose(rng, color_options)
    else:
        color_key = character_policy.infer_hair_color_key(base_hair)

    if length_choice != "random":
        length_key = length_choice
    elif length_options:
        length_key = _choose(rng, length_options)
    else:
        length_key = character_policy.infer_hair_length_key(base_hair)

    if style_choice != "random":
        style_key = style_choice
    elif style_options:
        style_key = _choose(rng, style_options)
    else:
        style_key = character_policy.infer_hair_style_key(base_hair)
    if color_key == "random":
        color_key = character_policy.choose_hair_key(rng, character_policy.CHARACTER_HAIR_COLOR_CHOICES)
    if length_key == "random":
        length_key = character_policy.choose_hair_key(rng, character_policy.CHARACTER_HAIR_LENGTH_CHOICES)
    if style_key == "random":
        style_key = character_policy.choose_hair_key(rng, character_policy.CHARACTER_HAIR_STYLE_CHOICES)
    if length_key == "updo" and style_key not in ("ponytail", "braid", "braids", "bun", "messy_bun", "locs", "twists"):
        style_key = _choose(rng, ["ponytail", "braid", "bun", "messy_bun"])
    return character_policy.hair_phrase_from_parts(color_key, length_key, style_key)


def appearance_for_subject(
    rng: random.Random,
    subject_type: str,
    ethnicity: str,
    figure: str,
    no_plus_women: bool,
    no_black: bool,
) -> dict[str, str]:
    if subject_type == "single_any":
        subject_type = "woman" if rng.random() < 0.82 else "man"

    if subject_type == "man":
        men_ethnicity = ethnicity if ethnicity else "any"
        subject, age, body, skin, hair, eyes = g.choose(rng, g.by_ethnicity(g.MEN, men_ethnicity))
        return {
            "subject_type": "man",
            "subject": subject,
            "subject_phrase": subject,
            "age": age,
            "body": body,
            "skin": skin,
            "hair": hair,
            "eyes": eyes,
            "body_phrase": f"{body} figure",
        }

    subject, age, body, skin, hair, eyes = g.choose_woman(rng, ethnicity, no_plus_women, no_black)
    figure_note = g.choose(rng, g.figure_pool(figure))
    return {
        "subject_type": "woman",
        "subject": subject,
        "subject_phrase": subject,
        "age": age,
        "body": body,
        "skin": skin,
        "hair": hair,
        "eyes": eyes,
        "body_phrase": character_profile_policy.body_phrase(body, figure_note),
        "figure": figure_note,
    }


def context_from_character_slot(
    rng: random.Random,
    slot: dict[str, Any],
    subject_type: str,
    ethnicity: str,
    figure: str,
    no_plus_women: bool,
    no_black: bool,
) -> dict[str, Any]:
    slot_ethnicity = character_policy.slot_value(slot.get("ethnicity"))
    slot_body = character_policy.slot_value(slot.get("body"))
    effective_ethnicity = slot_ethnicity or ethnicity
    effective_figure = character_slot_policy.slot_effective_figure(slot, subject_type, figure)
    effective_no_plus = bool(no_plus_women) and not slot_body
    effective_no_black = bool(no_black) and not slot_ethnicity
    appearance_rng = character_slot_policy.slot_context_rng(slot, rng)
    context = appearance_for_subject(
        appearance_rng,
        subject_type,
        effective_ethnicity,
        effective_figure,
        effective_no_plus,
        effective_no_black,
    )

    characteristics = character_policy.parse_characteristics_config(slot.get("characteristics"))
    age = character_policy.slot_value(slot.get("age")) or character_policy.characteristic_choice(characteristics, "ages", appearance_rng)
    body_phrase = character_policy.slot_value(slot.get("body_phrase"))
    if not slot_body:
        slot_body = character_policy.characteristic_choice(characteristics, "bodies", appearance_rng)
    if age:
        context["age"] = age
    if slot_body:
        context["body"] = slot_body
        if subject_type == "woman":
            context["body_phrase"] = character_profile_policy.body_phrase(slot_body, context.get("figure", ""))
        else:
            context["body_phrase"] = f"{slot_body} figure"
    if body_phrase:
        context["body_phrase"] = body_phrase
    skin_value = character_policy.slot_value(slot.get("skin"))
    if skin_value:
        context["skin"] = skin_value
    eyes_value = character_policy.slot_value(slot.get("eyes"))
    if not eyes_value:
        eyes_value = character_policy.eye_phrase_from_key(character_policy.characteristic_choice(characteristics, "eyes", appearance_rng))
    if eyes_value:
        context["eyes"] = eyes_value
    hair_value = character_policy.slot_value(slot.get("hair"))
    if hair_value:
        context["hair"] = hair_value
    else:
        hair_descriptor = hair_descriptor_from_slot(context.get("hair"), slot, appearance_rng)
        if hair_descriptor:
            context["hair"] = hair_descriptor
    context["descriptor_detail"] = character_policy.normalize_descriptor_detail(slot.get("descriptor_detail"))
    context["presence_mode"] = character_policy.normalize_presence_mode(slot.get("presence_mode"), subject_type)
    context["expression_enabled"] = character_slot_policy.slot_expression_enabled(slot)
    expression_intensity = character_slot_policy.slot_expression_intensity(slot)
    if expression_intensity is not None:
        context["expression_intensity"] = expression_intensity
    context["subject_type"] = subject_type
    context["subject"] = subject_type
    context["subject_phrase"] = subject_type
    return context


def character_context_for_label(
    label: str,
    label_map: dict[str, dict[str, Any]],
    rng: random.Random,
    ethnicity: str,
    figure: str,
    no_plus_women: bool,
    no_black: bool,
) -> tuple[dict[str, Any], dict[str, Any] | None]:
    subject_type = "man" if label.startswith("Man ") else "woman"
    slot = label_map.get(label)
    if slot:
        return context_from_character_slot(rng, slot, subject_type, ethnicity, figure, no_plus_women, no_black), slot
    return appearance_for_subject(rng, subject_type, ethnicity, figure, no_plus_women, no_black), None


def apply_character_context_to_row(row: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]:
    for key in (
        "subject_type",
        "subject",
        "subject_phrase",
        "age",
        "body",
        "body_phrase",
        "skin",
        "hair",
        "eyes",
        "figure",
        "descriptor_detail",
        "presence_mode",
        "expression_enabled",
        "expression_intensity",
    ):
        value = context.get(key)
        if value is not None and value != "":
            row[key] = value
    if context.get("age"):
        row["age_band"] = context["age"]
    return row


def row_from_character_slot(character_slot: str | dict[str, Any] | None) -> dict[str, Any]:
    slots = character_slot_policy.parse_character_cast(character_slot)
    if not slots:
        return {}
    slot = slots[-1]
    if character_slot_policy.slot_seed(slot) >= 0:
        subject_type = str(slot.get("subject_type") or "woman")
        return context_from_character_slot(
            random.Random(seed_policy.row_seed(character_slot_policy.slot_seed(slot), 1, 719)),
            slot,
            subject_type,
            "any",
            "curvy",
            False,
            False,
        )
    return slot
