use leptos::{ev::EventDescriptor, logging};
use leptos_use::use_event_listener;
use web_sys::EventTarget;

pub const DEFAULT_MP3: &str = "/Mseq_-_Journey.mp3a";

#[derive(Clone)]
pub struct MediaPlayer {
    context: web_sys::AudioContext,
    audio_element: web_sys::HtmlAudioElement,
}

impl MediaPlayer {
    pub async fn new() -> Result<Self, MediaPlayerError> {
        let context = web_sys::AudioContext::new()?;
        let audio_element = web_sys::HtmlAudioElement::new_with_src(DEFAULT_MP3)?;
        let source_node = context.create_media_element_source(&audio_element)?;
        let gain_node = context.create_gain()?;
        let analyser_node = context.create_analyser()?;
        analyser_node.set_fft_size(2048);
        analyser_node.set_smoothing_time_constant(0.8);

        source_node.connect_with_audio_node(&analyser_node)?;
        source_node.connect_with_audio_node(&gain_node)?;
        gain_node.connect_with_audio_node(&context.destination())?;

        Ok(Self {
            context,
            audio_element,
        })
    }

    pub fn set_title(&self, title: &str) {
        self.audio_element.set_src(title);
    }

    pub fn get_title(&self) -> String {
        // Hardcoded for now, eventually I'll make this a proper
        // player again...
        "Journey".to_owned()
    }

    pub fn context(&self) -> EventTarget {
        self.context.clone().into()
    }

    pub fn audio_element(&self) -> EventTarget {
        self.audio_element.clone().into()
    }

    pub fn use_media_event<Ev, F>(&self, event: Ev, handler: F) -> impl Fn() + Clone + Send + Sync + use<Ev, F>
    where
        F: FnMut(<Ev as EventDescriptor>::EventType) + 'static,
        Ev: EventDescriptor + 'static,
    {
        use_event_listener(self.audio_element.clone(), event, handler)
    }

    pub fn use_statechange<F>(&self, handler: F) -> impl Fn() + Clone + Send + Sync
    where
        F: FnMut(<media_events::statechange as EventDescriptor>::EventType) + 'static,
    {
        use_event_listener(self.context.clone(), media_events::statechange, handler)
    }
}

#[derive(thiserror::Error, Debug, Clone)]
pub enum MediaPlayerError {
    #[error("todo")]
    Todo,
}

impl From<web_sys::wasm_bindgen::JsValue> for MediaPlayerError {
    fn from(value: web_sys::wasm_bindgen::JsValue) -> Self {
        logging::error!("Some kind of error");
        Self::Todo {}
    }
}

pub mod media_events {
    use leptos::ev::EventDescriptor;
    use std::borrow::Cow;

    #[derive(Copy, Clone, Debug)]
    #[allow(non_camel_case_types)]
    pub struct error;

    impl EventDescriptor for error {
        type EventType = web_sys::Event;
        const BUBBLES: bool = false;

        #[inline(always)]
        fn name(&self) -> Cow<'static, str> {
            "error".into()
        }

        #[inline(always)]
        fn event_delegation_key(&self) -> Cow<'static, str> {
            "$$$error".into()
        }
    }

    #[derive(Copy, Clone, Debug)]
    #[allow(non_camel_case_types)]
    pub struct statechange;

    impl EventDescriptor for statechange {
        type EventType = web_sys::Event;
        const BUBBLES: bool = false;

        #[inline(always)]
        fn name(&self) -> Cow<'static, str> {
            "statechange".into()
        }

        #[inline(always)]
        fn event_delegation_key(&self) -> Cow<'static, str> {
            "$$$statechange".into()
        }
    }
}
