from __future__ import annotations

import json
from typing import Any

try:
    from . import category_library as category_policy
    from . import generate_prompt_batches as g
    from . import row_item as row_item_policy
except ImportError:  # Allows local smoke tests with top-level imports.
    import category_library as category_policy
    import generate_prompt_batches as g
    import row_item as row_item_policy


BUILTIN_CATEGORIES = [
    "auto_weighted",
    "auto_full",
    "woman",
    "man",
    "couple",
    "group_or_layout",
    "custom_random",
]

_EXTENSIONS_APPLIED = False


def list_from(value: Any) -> list[Any]:
    if value is None:
        return []
    if isinstance(value, list):
        return value
    return [value]


def unique_extend(target: list[Any], additions: list[Any]) -> None:
    seen = set()
    for item in target:
        try:
            seen.add(json.dumps(item, sort_keys=True))
        except TypeError:
            seen.add(repr(item))
    for item in additions:
        try:
            marker = json.dumps(item, sort_keys=True)
        except TypeError:
            marker = repr(item)
        if marker not in seen:
            target.append(item)
            seen.add(marker)


def extension_targets() -> dict[str, tuple[list[Any], bool]]:
    return {
        "women_clothes": (g.WOMEN_CLOTHES, False),
        "women_clothes_minimal": (g.WOMEN_CLOTHES_MINIMAL, False),
        "men_clothes": (g.MEN_CLOTHES, False),
        "men_clothes_minimal": (g.MEN_CLOTHES_MINIMAL, False),
        "couple_outfits": (g.COUPLE_OUTFITS, False),
        "couple_outfits_minimal": (g.COUPLE_OUTFITS_MINIMAL, False),
        "poses": (g.POSES, False),
        "evocative_poses": (g.EVOCATIVE_POSES, False),
        "backside_poses": (g.BACKSIDE_POSES, False),
        "expressions": (g.EXPRESSIONS, False),
        "compositions": (g.COMPOSITIONS, False),
        "props": (g.PROPS, False),
        "figure_curvy": (g.FIGURE_CURVY, False),
        "figure_athletic": (g.FIGURE_ATHLETIC, False),
        "figure_bombshell": (g.FIGURE_BOMBSHELL, False),
        "scenes": (g.SCENES, True),
        "group_scenes": (g.GROUP_SCENES, True),
        "layouts_full": (g.LAYOUTS_FULL, True),
        "layouts_minimal": (g.LAYOUTS_MINIMAL, True),
        "group_compositions": (g.GROUP_COMPOSITIONS, False),
        "group_ages": (g.GROUP_AGES, False),
    }


def apply_pool_extensions() -> None:
    global _EXTENSIONS_APPLIED
    if _EXTENSIONS_APPLIED:
        return
    targets = extension_targets()
    for path in category_policy.category_json_files():
        data = category_policy.read_category_json(path)
        extensions = data.get("pool_extensions", {})
        if not isinstance(extensions, dict):
            raise ValueError(f"pool_extensions in {path} must be an object")
        for target_name, additions in extensions.items():
            if target_name not in targets:
                known = ", ".join(sorted(targets))
                raise ValueError(f"Unknown pool extension '{target_name}' in {path}. Known: {known}")
            target, expects_pair = targets[target_name]
            normalized = (
                [row_item_policy.pair_from(item) for item in list_from(additions)]
                if expects_pair
                else [row_item_policy.item_text(item) for item in list_from(additions)]
            )
            unique_extend(target, normalized)
    g.EVOCATIVE_ALL = g.EVOCATIVE_POSES + g.BACKSIDE_POSES
    _EXTENSIONS_APPLIED = True


def category_choices() -> list[str]:
    apply_pool_extensions()
    custom = [category["name"] for category in category_policy.load_category_library()]
    return BUILTIN_CATEGORIES + [name for name in custom if name not in BUILTIN_CATEGORIES]


def subcategory_choices() -> list[str]:
    apply_pool_extensions()
    choices = [category_policy.RANDOM_SUBCATEGORY]
    for category in category_policy.load_category_library():
        for subcategory in category["subcategories"]:
            choices.append(category_policy.exact_subcategory_selector(category, subcategory))
    return choices
