"""Player management plugin for handling player controls and state.

This module provides the PlayerPlugin class, which handles player spawning,
movement processing, and animation updates. It decouples the player logic
from the main GameView.
"""

import logging
from typing import Any, ClassVar

import arcade

from pedre.helpers import asset_path
from pedre.plugins.player.base import PlayerBasePlugin
from pedre.plugins.registry import PluginRegistry
from pedre.sprites import AnimatedSprite

logger = logging.getLogger(__name__)


@PluginRegistry.register
class PlayerPlugin(PlayerBasePlugin):
    """Manages player spawning, movement, and animation.

    Responsibilities:
    - Spawn player sprite based on map data (Tiled 'Player' layer)
    - Handle player movement based on InputPlugin state
    - Update player animation state
    - Update GameContext with player sprite reference
    """

    name: ClassVar[str] = "player"
    dependencies: ClassVar[list[str]] = ["input", "waypoint"]

    def __init__(self) -> None:
        """Initialize the player plugin."""
        self.player_sprite: AnimatedSprite | None = None
        self.player_list: arcade.SpriteList | None = None

    def get_player_sprite(self) -> AnimatedSprite | None:
        """Get the player sprite."""
        return self.player_sprite

    def load_from_tiled(
        self,
        tile_map: arcade.TileMap,
        arcade_scene: arcade.Scene,
    ) -> None:
        """Load player from Tiled map object layer."""
        # Get Player object layer
        player_layer = tile_map.object_lists.get("Player")
        if not player_layer:
            logger.warning("No 'Player' object layer found in map")
            return

        # Use first player object
        player_obj = player_layer[0]

        # Determine spawn position
        shape_x = player_obj.shape[0]
        shape_y = player_obj.shape[1]
        if not isinstance(shape_x, (int, float)) or not isinstance(shape_y, (int, float)):
            logger.warning("PlayerPlugin: Player object has invalid shape coordinates, skipping")
            return
        spawn_x = float(shape_x)
        spawn_y = float(shape_y)

        # Resolve player definition from content registry
        content_registry = self.context.content_registry
        players = content_registry.get_sub_registry("players")
        if not players.has("player"):
            logger.warning("PlayerPlugin: No 'player' definition found in players registry.")
            return
        player_def = players.get("player")

        # Determine spawn behavior from registry definition
        spawn_at_position_maps = player_def.get("spawn_at_position", [])
        current_map = self.context.scene_plugin.get_current_scene()
        spawn_at_portal = current_map not in spawn_at_position_maps

        next_spawn_waypoint = self.context.scene_plugin.get_next_spawn_waypoint()
        logger.debug(
            "PlayerPlugin: spawn_at_portal=%s, next_spawn_waypoint=%s",
            spawn_at_portal,
            next_spawn_waypoint,
        )
        if spawn_at_portal and next_spawn_waypoint:
            waypoints = self.context.waypoint_plugin.get_waypoints()
            logger.debug(
                "PlayerPlugin: Available waypoints: %s",
                list(waypoints.keys()) if waypoints else [],
            )
            if waypoints and next_spawn_waypoint in waypoints:
                spawn_x, spawn_y = waypoints[next_spawn_waypoint]
                logger.debug(
                    "PlayerPlugin: Spawning at waypoint '%s': pixel (%.1f, %.1f)",
                    next_spawn_waypoint,
                    spawn_x,
                    spawn_y,
                )
                # Clear the spawn waypoint
                self.context.scene_plugin.clear_next_spawn_waypoint()
            else:
                logger.warning(
                    "PlayerPlugin: Waypoint '%s' not found in available waypoints",
                    next_spawn_waypoint,
                )

        # Resolve sprite definition via content registry
        sprites = content_registry.get_sub_registry("sprites")
        sprite_id = player_def["sprite_id"]

        if sprite_id and sprites.has(sprite_id):
            sprite_def = dict(sprites.get(sprite_id))
            sprite_def["sprite_sheet"] = asset_path(sprite_def["sprite_sheet"])
            self.player_sprite = AnimatedSprite.from_definition(
                sprite_def,
                center_x=spawn_x,
                center_y=spawn_y,
            )
        else:
            logger.warning("Player: no content registry sprite definition found. Cannot create player sprite.")
            return

        self.player_list = arcade.SpriteList()
        self.player_list.append(self.player_sprite)

        # Add to scene
        if "Player" in arcade_scene:
            arcade_scene.remove_sprite_list_by_name("Player")
        arcade_scene.add_sprite_list("Player", sprite_list=self.player_list)

        logger.info("Player loaded at (%.1f, %.1f)", spawn_x, spawn_y)

    def update(self, delta_time: float) -> None:
        """Update player movement and animation."""
        if not self.player_sprite:
            return

        # Check if dialog is showing (blocking movement)
        dialog_plugin = self.context.dialog_plugin
        dialog_showing = dialog_plugin.is_showing() if dialog_plugin else False

        # Get input plugin
        input_plugin = self.context.input_plugin

        # Determine movement
        dx, dy = 0.0, 0.0
        moving = False

        if input_plugin and not dialog_showing:
            dx, dy = input_plugin.get_movement_vector(delta_time)
            moving = dx != 0 or dy != 0

        # Apply movement
        self.player_sprite.change_x = dx
        self.player_sprite.change_y = dy

        # Update direction state logic
        if isinstance(self.player_sprite, AnimatedSprite):
            # Determine direction based on movement
            new_direction = self.player_sprite.current_direction

            if dx > 0:
                new_direction = "right"
            elif dx < 0:
                new_direction = "left"
            elif dy > 0:
                new_direction = "up"
            elif dy < 0:
                new_direction = "down"

            # Only change direction when it actually changes
            if new_direction != self.player_sprite.current_direction:
                self.player_sprite.set_direction(new_direction)

            # Update animation
            if moving:
                self.player_sprite.request_state("walk")
            else:
                self.player_sprite.release_state("walk")
            self.player_sprite.update_animation(delta_time)

    def get_save_state(self) -> dict[str, Any]:
        """Get save state."""
        return self.to_dict()

    def restore_save_state(self, state: dict[str, Any]) -> None:
        """Phase 1: No metadata to restore for player."""

    def apply_entity_state(self, state: dict[str, Any]) -> None:
        """Phase 2: Apply saved player position after sprite exists."""
        self.from_dict(state)
        if self.player_sprite and "player_x" in state:
            logger.info(
                "Applied saved player position (%.1f, %.1f)",
                self.player_sprite.center_x,
                self.player_sprite.center_y,
            )

    def reset(self) -> None:
        """Reset player plugin state for new game."""
        self.player_sprite = None
        self.player_list = None

    def from_dict(self, data: dict[str, float]) -> None:
        """Apply position to sprite if it exists."""
        if "player_x" not in data or "player_y" not in data:
            return
        if self.player_sprite:
            self.player_sprite.center_x = float(data["player_x"])
            self.player_sprite.center_y = float(data["player_y"])

    def to_dict(self) -> dict[str, float]:
        """Load player coordinates from saved dictionary data."""
        if self.player_sprite:
            return {"player_x": self.player_sprite.center_x, "player_y": self.player_sprite.center_y}
        return {}
