"""Tests for animation extraction helpers."""

from __future__ import annotations

import pytest


# ---------------------------------------------------------------------------
# _strip_string_name
# ---------------------------------------------------------------------------

class TestStripStringName:
    """Tests for _strip_string_name utility."""

    def test_strips_ampersand_syntax(self):
        """&"idle" should become "idle"."""
        from godotiq.tools.animation import _strip_string_name
        assert _strip_string_name('&"idle"') == "idle"

    def test_passthrough_clean_string(self):
        """Already clean string returned as-is."""
        from godotiq.tools.animation import _strip_string_name
        assert _strip_string_name("idle") == "idle"

    def test_non_string_passthrough(self):
        """Non-string values returned unchanged."""
        from godotiq.tools.animation import _strip_string_name
        assert _strip_string_name(42) == 42


# ---------------------------------------------------------------------------
# _extract_tscn_animations
# ---------------------------------------------------------------------------

class TestExtractTscnAnimations:
    """Tests for inline animation extraction from .tscn sub_resources."""

    def test_four_animations_from_character(self, anim_session):
        """animated_character.tscn should yield 4 inline animations."""
        from godotiq.tools.animation import _extract_tscn_animations
        scene = anim_session.get_scene("res://scenes/animated_character.tscn")
        anims = _extract_tscn_animations(scene)
        names = {a["name"] for a in anims}
        assert names == {"idle", "walk", "attack", "idle_ground"}

    def test_idle_metadata(self, anim_session):
        """idle animation: length=2.0, loop_mode="linear", track_count=4, all value type."""
        from godotiq.tools.animation import _extract_tscn_animations
        scene = anim_session.get_scene("res://scenes/animated_character.tscn")
        anims = _extract_tscn_animations(scene)
        idle = next(a for a in anims if a["name"] == "idle")
        assert idle["length"] == 2.0
        assert idle["loop_mode"] == "linear"
        assert idle["track_count"] == 4
        assert idle["tracks_by_type"].get("value", 0) == 4

    def test_walk_mixed_tracks(self, anim_session):
        """walk animation: 3 value + 1 method track."""
        from godotiq.tools.animation import _extract_tscn_animations
        scene = anim_session.get_scene("res://scenes/animated_character.tscn")
        anims = _extract_tscn_animations(scene)
        walk = next(a for a in anims if a["name"] == "walk")
        assert walk["tracks_by_type"]["value"] == 3
        assert walk["tracks_by_type"]["method"] == 1

    def test_attack_broken_track_path(self, anim_session):
        """attack animation should have "NonExistent:position" in track_paths."""
        from godotiq.tools.animation import _extract_tscn_animations
        scene = anim_session.get_scene("res://scenes/animated_character.tscn")
        anims = _extract_tscn_animations(scene)
        attack = next(a for a in anims if a["name"] == "attack")
        assert any("NonExistent" in p for p in attack["track_paths"])

    def test_no_animations_returns_empty(self, anim_session):
        """no_animation_scene.tscn should return empty list."""
        from godotiq.tools.animation import _extract_tscn_animations
        scene = anim_session.get_scene("res://scenes/no_animation_scene.tscn")
        anims = _extract_tscn_animations(scene)
        assert anims == []


# ---------------------------------------------------------------------------
# _extract_animation_libraries
# ---------------------------------------------------------------------------

class TestExtractAnimationLibraries:
    """Tests for AnimationLibrary sub_resource extraction."""

    def test_one_library_four_refs(self, anim_session):
        """animated_character.tscn should have 1 library with 4 animation refs."""
        from godotiq.tools.animation import _extract_animation_libraries
        scene = anim_session.get_scene("res://scenes/animated_character.tscn")
        libs = _extract_animation_libraries(scene)
        assert len(libs) == 1
        lib_data = list(libs.values())[0]
        assert len(lib_data) == 4

    def test_library_maps_idle(self, anim_session):
        """Library should map "idle" to "Animation_idle" sub_resource id."""
        from godotiq.tools.animation import _extract_animation_libraries
        scene = anim_session.get_scene("res://scenes/animated_character.tscn")
        libs = _extract_animation_libraries(scene)
        lib_data = list(libs.values())[0]
        assert lib_data["idle"] == "Animation_idle"


# ---------------------------------------------------------------------------
# _extract_animation_players
# ---------------------------------------------------------------------------

class TestExtractAnimationPlayers:
    """Tests for AnimationPlayer node extraction."""

    def test_one_player_named(self, anim_session):
        """animated_character.tscn should have 1 player named "AnimationPlayer"."""
        from godotiq.tools.animation import _extract_animation_players
        scene = anim_session.get_scene("res://scenes/animated_character.tscn")
        players = _extract_animation_players(scene)
        assert len(players) == 1
        assert players[0]["name"] == "AnimationPlayer"

    def test_player_has_default_library(self, anim_session):
        """Player should have library_refs with empty string key (default)."""
        from godotiq.tools.animation import _extract_animation_players
        scene = anim_session.get_scene("res://scenes/animated_character.tscn")
        players = _extract_animation_players(scene)
        assert "" in players[0]["library_refs"]

    def test_no_player_returns_empty(self, anim_session):
        """no_animation_scene.tscn should return empty list."""
        from godotiq.tools.animation import _extract_animation_players
        scene = anim_session.get_scene("res://scenes/no_animation_scene.tscn")
        players = _extract_animation_players(scene)
        assert players == []


# ---------------------------------------------------------------------------
# _extract_animation_trees
# ---------------------------------------------------------------------------

class TestExtractAnimationTrees:
    """Tests for AnimationTree + StateMachine extraction."""

    def test_one_tree_two_states(self, anim_session):
        """animated_scene_tree.tscn should have 1 tree with 2 states."""
        from godotiq.tools.animation import _extract_animation_trees
        scene = anim_session.get_scene("res://scenes/animated_scene_tree.tscn")
        trees = _extract_animation_trees(scene)
        assert len(trees) == 1
        assert set(trees[0]["states"]) == {"idle", "walk"}

    def test_tree_two_transitions(self, anim_session):
        """Tree should have 2 transitions: idle->walk and walk->idle."""
        from godotiq.tools.animation import _extract_animation_trees
        scene = anim_session.get_scene("res://scenes/animated_scene_tree.tscn")
        trees = _extract_animation_trees(scene)
        transitions = trees[0]["transitions"]
        assert len(transitions) == 2
        pairs = {(t["from"], t["to"]) for t in transitions}
        assert ("idle", "walk") in pairs
        assert ("walk", "idle") in pairs

    def test_animation_refs(self, anim_session):
        """animation_refs should include "idle" and "walk"."""
        from godotiq.tools.animation import _extract_animation_trees
        scene = anim_session.get_scene("res://scenes/animated_scene_tree.tscn")
        trees = _extract_animation_trees(scene)
        assert "idle" in trees[0]["animation_refs"]
        assert "walk" in trees[0]["animation_refs"]


# ---------------------------------------------------------------------------
# _extract_code_animations
# ---------------------------------------------------------------------------

class TestExtractCodeAnimations:
    """Tests for GDScript animation pattern extraction."""

    def test_play_calls_from_character(self, anim_root):
        """character.gd should have play_calls including "idle", "attack", "special_move"."""
        from godotiq.tools.animation import _extract_code_animations
        content = (anim_root / "scripts" / "entities" / "character.gd").read_text()
        result = _extract_code_animations(content)
        assert "idle" in result["play_calls"]
        assert "attack" in result["play_calls"]
        assert "special_move" in result["play_calls"]

    def test_anim_maps_from_character(self, anim_root):
        """character.gd should have ANIM_MAP with 4 entries."""
        from godotiq.tools.animation import _extract_code_animations
        content = (anim_root / "scripts" / "entities" / "character.gd").read_text()
        result = _extract_code_animations(content)
        assert len(result["anim_maps"]) > 0
        anim_map = result["anim_maps"][0]
        assert len(anim_map["entries"]) == 4

    def test_looping_lists_from_character(self, anim_root):
        """character.gd should have LOOPING_ANIMS with 2 entries."""
        from godotiq.tools.animation import _extract_code_animations
        content = (anim_root / "scripts" / "entities" / "character.gd").read_text()
        result = _extract_code_animations(content)
        assert len(result["looping_lists"]) > 0
        assert len(result["looping_lists"][0]["entries"]) == 2

    def test_glb_nlatrack_entries(self, anim_root):
        """glb_animated_entity.gd should have ANIM_MAP with NlaTrack values."""
        from godotiq.tools.animation import _extract_code_animations
        content = (anim_root / "scripts" / "entities" / "glb_animated_entity.gd").read_text()
        result = _extract_code_animations(content)
        assert len(result["anim_maps"]) > 0
        entries = result["anim_maps"][0]["entries"]
        assert any("NlaTrack" in v for v in entries.values())

    def test_stringname_play_detected(self):
        """`.play(&"name")` syntax should be detected."""
        from godotiq.tools.animation import _extract_code_animations
        result = _extract_code_animations('anim_player.play(&"jump")')
        assert "jump" in result["play_calls"]

    def test_queue_included_in_play_calls(self):
        """`.queue("name")` should appear in play_calls."""
        from godotiq.tools.animation import _extract_code_animations
        result = _extract_code_animations('anim_player.queue("fade_out")')
        assert "fade_out" in result["play_calls"]

    def test_empty_script_no_crash(self):
        """Empty script should return empty results without errors."""
        from godotiq.tools.animation import _extract_code_animations
        result = _extract_code_animations("")
        assert result["play_calls"] == []
        assert result["travel_calls"] == []
        assert result["anim_maps"] == []
        assert result["looping_lists"] == []
