"""NPC behavior state machine."""

from mudlib.entity import Mob
from mudlib.zone import Zone

# Valid behavior states
VALID_STATES = {"idle", "patrol", "converse", "flee", "working"}


def transition_state(mob: Mob, new_state: str, data: dict | None = None) -> bool:
    """Transition mob to a new behavior state.

    Args:
        mob: The mob to transition
        new_state: The target state (must be in VALID_STATES)
        data: Optional state-specific data

    Returns:
        True if transition succeeded, False if state is invalid
    """
    if new_state not in VALID_STATES:
        return False

    mob.behavior_state = new_state
    mob.behavior_data = data or {}
    return True


async def process_behavior(mob: Mob, world: Zone) -> None:
    """Process mob behavior for current tick.

    Called each game loop tick. Dispatches based on behavior_state:
    - idle: do nothing (existing wander logic handles movement)
    - patrol: move through waypoints
    - converse: do nothing (conversation is player-driven)
    - flee: do nothing (direction computed by get_flee_direction)
    - working: do nothing (stationary NPC)

    Args:
        mob: The mob to process
        world: The zone the mob is in
    """
    if mob.behavior_state == "idle":
        # No-op, existing wander logic handles idle movement
        pass
    elif mob.behavior_state == "patrol":
        await _process_patrol(mob, world)
    elif mob.behavior_state == "converse":
        # No-op, mob stays put during conversation
        pass
    elif mob.behavior_state == "flee":
        # No-op, mob_ai uses get_flee_direction for movement
        pass
    elif mob.behavior_state == "working":
        # No-op, mob stays at their post
        pass


async def _process_patrol(mob: Mob, world: Zone) -> None:
    """Process patrol behavior: advance waypoint if at current target."""
    waypoints = mob.behavior_data.get("waypoints", [])
    if not waypoints:
        return

    waypoint_index = mob.behavior_data.get("waypoint_index", 0)
    current_waypoint = waypoints[waypoint_index]

    # Check if at waypoint (use world wrapping for distance)
    if mob.x == current_waypoint["x"] and mob.y == current_waypoint["y"]:
        # Advance to next waypoint (loop back to start if at end)
        waypoint_index = (waypoint_index + 1) % len(waypoints)
        mob.behavior_data["waypoint_index"] = waypoint_index


def get_flee_direction(mob: Mob, world: Zone) -> str | None:
    """Get the cardinal direction to flee away from threat.

    Args:
        mob: The mob fleeing
        world: The zone the mob is in

    Returns:
        Cardinal direction ("north", "south", "east", "west") or None if no threat
    """
    flee_from = mob.behavior_data.get("flee_from")
    if not flee_from:
        return None

    threat_x = flee_from["x"]
    threat_y = flee_from["y"]

    # Calculate direction away from threat
    dx = mob.x - threat_x
    dy = mob.y - threat_y

    # Handle toroidal wrapping for shortest distance
    if world.toroidal:
        # Adjust dx/dy if wrapping gives shorter distance
        if abs(dx) > world.width // 2:
            dx = -(world.width - abs(dx)) * (1 if dx > 0 else -1)
        if abs(dy) > world.height // 2:
            dy = -(world.height - abs(dy)) * (1 if dy > 0 else -1)

    # Prefer axis with larger distance (prefer moving away faster)
    if abs(dx) >= abs(dy):
        return "east" if dx > 0 else "west" if dx < 0 else None
    else:
        return "south" if dy > 0 else "north" if dy < 0 else None


def get_patrol_direction(mob: Mob, world: Zone) -> str | None:
    """Get the cardinal direction to move toward current patrol waypoint.

    Args:
        mob: The mob on patrol
        world: The zone the mob is in

    Returns:
        Cardinal direction ("north", "south", "east", "west") or None if at waypoint
    """
    waypoints = mob.behavior_data.get("waypoints", [])
    if not waypoints:
        return None

    waypoint_index = mob.behavior_data.get("waypoint_index", 0)
    current_waypoint = waypoints[waypoint_index]

    target_x = current_waypoint["x"]
    target_y = current_waypoint["y"]

    # Check if already at waypoint
    if mob.x == target_x and mob.y == target_y:
        return None

    # Calculate direction to waypoint
    dx = target_x - mob.x
    dy = target_y - mob.y

    # Handle toroidal wrapping (find shortest path)
    if world.toroidal:
        # Check if wrapping gives a shorter distance on x axis
        if abs(dx) > world.width // 2:
            # Wrapping is shorter, reverse direction
            dx = -(world.width - abs(dx)) * (1 if dx > 0 else -1)

        # Check if wrapping gives a shorter distance on y axis
        if abs(dy) > world.height // 2:
            # Wrapping is shorter, reverse direction
            dy = -(world.height - abs(dy)) * (1 if dy > 0 else -1)

    # Prefer axis with larger distance (ties prefer x)
    if abs(dx) >= abs(dy):
        return "east" if dx > 0 else "west"
    else:
        return "south" if dy > 0 else "north"
