use std::fmt::Debug;

use anyhow::anyhow;

use super::SpriteDesc;
use crate::{animation::traits::AnimationAssets, prelude::*};

const DEFAULT_KEY: &str = "default";

/// Component that handles sprite animation
#[derive(Clone)]
pub struct Animator<TextureKey> {
    pub request_key: Option<String>,
    pub sprite_desc: SpriteDesc<TextureKey>,
    pub current: u16,
    pub repeat: bool,
    pub tick: f32,
}

impl<TextureKey: Default> Default for Animator<TextureKey> {
    fn default() -> Self {
        Self {
            request_key: None,
            sprite_desc: SpriteDesc {
                texture_key: Default::default(),
                start: 0,
                end: 0,
                frame_secs: 0.1,
                tile_size: UVec2::splat(32),
                scale: 1.0,
                angle: 0.0,
            },
            current: 0,
            repeat: false,
            tick: 0.0,
        }
    }
}

impl<TextureKey: Clone + Debug> Animator<TextureKey> {
    /// Check if the animation has finished playing
    pub fn is_finished(&self) -> bool {
        self.current == self.sprite_desc.end && !self.repeat
    }

    /// Request a state change (will be processed on next update)
    pub fn request_state(&mut self, key: String) {
        self.request_key = Some(key);
    }

    /// Update the animation state based on elapsed time
    pub fn update(
        assets: &impl AnimationAssets<TextureKey>,
        tick: f32,
        animators: &mut Component<Self>,
    ) {
        for e in animators.iter_mut() {
            let anim = &mut e.value;

            // update request state
            if let Some(request_key) = anim.request_key.take() {
                let Some(desc) = assets
                    .get_sprite_desc(&request_key)
                    .or_else(|| assets.get_sprite_desc(DEFAULT_KEY))
                else {
                    continue;
                };
                anim.current = desc.start;
                anim.sprite_desc = desc.clone();
                anim.tick = 0.0;
            }

            // tick
            anim.tick -= tick;

            // next frame
            if anim.tick < 0.0 {
                // check stop
                if anim.is_finished() {
                    continue;
                }

                if anim.current < anim.sprite_desc.end {
                    anim.current += 1;
                } else if anim.repeat {
                    anim.current = anim.sprite_desc.start;
                }
                anim.tick = anim.sprite_desc.frame_secs;
            }
        }
    }

    pub fn draw(
        &self,
        g: &mut Engine,
        assets: &impl AnimationAssets<TextureKey>,
        tf: &Transform,
    ) -> Result<()> {
        let sprite = assets
            .get_texture(&self.sprite_desc.texture_key)
            .cloned()
            .ok_or_else(|| anyhow!("missing sprite {:?}", self.sprite_desc.texture_key))?;
        let tile_size = self.sprite_desc.tile_size.as_vec2();
        let sprite = sprite.with_tile(self.current, tile_size);
        g.draw(&sprite, *tf);
        Ok(())
    }

    /// Get the current frame index
    pub fn current_frame(&self) -> u16 {
        self.current
    }

    /// Set whether the animation should repeat
    pub fn set_repeat(&mut self, repeat: bool) {
        self.repeat = repeat;
    }
}
