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 location_config as location_policy
except ImportError:  # Allows local smoke tests with top-level imports.
    import category_library as category_policy
    import generate_prompt_batches as g
    import location_config as location_policy


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


def _is_false(value: Any) -> bool:
    if isinstance(value, bool):
        return value is False
    if isinstance(value, str):
        return value.strip().lower() in ("false", "0", "no", "off")
    return False


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 scene_pool(
    category: dict[str, Any],
    subcategory: dict[str, Any],
    item: Any,
    subject_type: str,
    location_config: dict[str, Any] | None = None,
) -> list[Any]:
    location_config = location_config or {}
    location_entries = _list_from(location_config.get("scene_entries"))
    if location_policy.location_config_active(location_config) and location_config.get("apply_mode") == "replace":
        return location_entries
    fallback = g.GROUP_SCENES if subject_type in ("group", "configured_cast") else g.SCENES
    scene_entries: list[Any] = []
    scene_pools = category_policy.load_scene_pool_library()
    item_source = item if isinstance(item, dict) else None
    if item_source is not None and _is_false(item_source.get("inherit_scenes")):
        sources = (item_source,)
    elif _is_false(subcategory.get("inherit_scenes")):
        sources = (subcategory, item_source)
    else:
        sources = (category, subcategory, item_source)
    for source in sources:
        if not isinstance(source, dict):
            continue
        if "scenes" in source:
            _unique_extend(scene_entries, _list_from(source["scenes"]))
        refs = _list_from(source.get("scene_pool")) + _list_from(source.get("scene_pools"))
        for ref in refs:
            ref_name = str(ref).strip()
            if ref_name not in scene_pools:
                raise ValueError(f"Unknown scene pool '{ref_name}'")
            _unique_extend(scene_entries, scene_pools[ref_name])
    if location_policy.location_config_active(location_config) and location_config.get("apply_mode") == "add":
        _unique_extend(scene_entries, location_entries)
    return scene_entries or fallback


def expression_pool(category: dict[str, Any], subcategory: dict[str, Any], item: Any) -> list[Any]:
    return category_policy.configured_pool(
        category,
        subcategory,
        item,
        "expressions",
        "expression_pools",
        category_policy.load_expression_pool_library(),
        "inherit_expressions",
    ) or g.EXPRESSIONS


def pose_pool(category: dict[str, Any], subcategory: dict[str, Any], item: Any, subject_type: str, poses: str) -> list[Any]:
    configured = category_policy.merged_field(category, subcategory, item, "poses")
    if configured:
        return _list_from(configured)
    if subject_type == "couple":
        return [entry[2] for entry in g.COUPLE_TYPES]
    if subject_type in ("layout", "scene"):
        return ["clean designed layout"]
    return g.EVOCATIVE_ALL if poses == "evocative" else g.POSES


def composition_pool(
    category: dict[str, Any],
    subcategory: dict[str, Any],
    item: Any,
    subject_type: str,
    composition_config: dict[str, Any] | None = None,
) -> list[Any]:
    composition_config = composition_config or {}
    composition_entries = _list_from(composition_config.get("composition_entries"))
    if location_policy.composition_config_active(composition_config) and composition_config.get("apply_mode") == "replace":
        return composition_entries
    configured = category_policy.configured_pool(
        category,
        subcategory,
        item,
        "compositions",
        "composition_pools",
        category_policy.load_composition_pool_library(),
        "inherit_compositions",
    )
    if location_policy.composition_config_active(composition_config) and composition_config.get("apply_mode") == "add":
        configured = list(configured or [])
        _unique_extend(configured, composition_entries)
    if configured:
        return configured
    if subject_type in ("group", "configured_cast"):
        return g.GROUP_COMPOSITIONS
    if subject_type in ("layout", "scene"):
        return ["designed illustration layout"]
    return g.COMPOSITIONS
