"""Mob template loading, global registry, and spawn/despawn/query."""

import logging
import tomllib
from dataclasses import dataclass, field
from pathlib import Path

from mudlib.entity import Mob
from mudlib.loot import LootEntry
from mudlib.npc_schedule import NpcSchedule, ScheduleEntry
from mudlib.zone import Zone

logger = logging.getLogger(__name__)


@dataclass
class MobTemplate:
    """Definition loaded from TOML — used to spawn Mob instances."""

    name: str
    description: str
    pl: float
    stamina: float
    max_stamina: float
    moves: list[str] = field(default_factory=list)
    loot: list[LootEntry] = field(default_factory=list)
    schedule: NpcSchedule | None = None
    npc_name: str | None = None


# Module-level registries
mob_templates: dict[str, MobTemplate] = {}
mobs: list[Mob] = []


def load_mob_template(path: Path) -> MobTemplate:
    """Parse a mob TOML file into a MobTemplate."""
    with open(path, "rb") as f:
        data = tomllib.load(f)

    loot_entries = []
    for entry_data in data.get("loot", []):
        loot_entries.append(
            LootEntry(
                name=entry_data["name"],
                chance=entry_data["chance"],
                min_count=entry_data.get("min_count", 1),
                max_count=entry_data.get("max_count", 1),
                description=entry_data.get("description", ""),
            )
        )

    # Parse schedule if present
    schedule = None
    schedule_data = data.get("schedule", [])
    if schedule_data:
        entries = []
        for entry_data in schedule_data:
            # Extract location if present
            location = None
            if "location" in entry_data:
                location = entry_data["location"]

            # Extract all other fields as data (excluding hour, state, location)
            data_fields = {
                k: v
                for k, v in entry_data.items()
                if k not in ("hour", "state", "location")
            }
            entry_behavior_data = data_fields if data_fields else None

            entries.append(
                ScheduleEntry(
                    hour=entry_data["hour"],
                    state=entry_data["state"],
                    location=location,
                    data=entry_behavior_data,
                )
            )
        schedule = NpcSchedule(entries=entries)

    return MobTemplate(
        name=data["name"],
        description=data["description"],
        pl=data["pl"],
        stamina=data["stamina"],
        max_stamina=data["max_stamina"],
        moves=data.get("moves", []),
        loot=loot_entries,
        schedule=schedule,
        npc_name=data.get("npc_name"),
    )


def load_mob_templates(directory: Path) -> dict[str, MobTemplate]:
    """Load all .toml files in a directory into a dict keyed by name."""
    templates: dict[str, MobTemplate] = {}
    for path in sorted(directory.glob("*.toml")):
        template = load_mob_template(path)
        templates[template.name] = template
    return templates


def spawn_mob(
    template: MobTemplate, x: int, y: int, zone: Zone, home_region: dict | None = None
) -> Mob:
    """Create a Mob instance from a template at the given position.

    Args:
        template: The mob template to spawn from
        x: X coordinate in the zone
        y: Y coordinate in the zone
        zone: The zone where the mob will be spawned
        home_region: Optional home region dict with x and y bounds

    Returns:
        The spawned Mob instance
    """
    mob = Mob(
        name=template.name,
        location=zone,
        x=x,
        y=y,
        pl=template.pl,
        stamina=template.stamina,
        max_stamina=template.max_stamina,
        description=template.description,
        moves=list(template.moves),
        schedule=template.schedule,
        npc_name=template.npc_name,
    )
    if home_region is not None:
        # Validate home_region structure
        if (
            isinstance(home_region.get("x"), list)
            and isinstance(home_region.get("y"), list)
            and len(home_region["x"]) == 2
            and len(home_region["y"]) == 2
            and all(isinstance(v, int) for v in home_region["x"])
            and all(isinstance(v, int) for v in home_region["y"])
        ):
            mob.home_x_min = home_region["x"][0]
            mob.home_x_max = home_region["x"][1]
            mob.home_y_min = home_region["y"][0]
            mob.home_y_max = home_region["y"][1]
        else:
            logger.warning(
                "Malformed home_region for mob %s: %s. Expected "
                "{x: [int, int], y: [int, int]}. Skipping home region.",
                template.name,
                home_region,
            )
    mobs.append(mob)
    return mob


def despawn_mob(mob: Mob) -> None:
    """Remove a mob from the registry and mark it dead."""
    mob.alive = False
    if mob in mobs:
        mobs.remove(mob)


def get_nearby_mob(
    name: str, x: int, y: int, zone: Zone, range_: int = 10
) -> Mob | None:
    """Find the closest alive mob matching name within range.

    Uses zone.contents_near() to find all nearby objects, then filters
    for alive mobs matching the name and picks the closest.

    Args:
        name: Name of the mob to find
        x: X coordinate of the search center
        y: Y coordinate of the search center
        zone: The zone to search in
        range_: Maximum Manhattan distance (default 10)

    Returns:
        The closest matching mob, or None if none found
    """
    best: Mob | None = None
    best_dist = float("inf")

    # Get all nearby objects from the zone
    nearby = zone.contents_near(x, y, range_)

    for obj in nearby:
        # Filter for alive mobs matching the name
        if not isinstance(obj, Mob) or not obj.alive or obj.name != name:
            continue

        # Calculate wrapping-aware distance to find closest
        dx = abs(obj.x - x)
        dy = abs(obj.y - y)
        if zone.toroidal:
            dx = min(dx, zone.width - dx)
            dy = min(dy, zone.height - dy)

        dist = dx + dy
        if dist < best_dist:
            best = obj
            best_dist = dist

    return best
