use std::{
    convert::Infallible,
    ops::{Deref, DerefMut},
    str::FromStr,
};

use serde::{Deserialize, Serialize};

use crate::lyrics::{
    LyricFormat,
    synced_lyrics::{LyricParseError, SyncedLyrics},
};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum LyricData {
    Instrumental,
    Plain(PlainLyrics),
    Synced(SyncedLyrics),
}

impl LyricData {
    pub fn infer_from_string(string: impl AsRef<str>) -> Result<Self, LyricParseError> {
        let string = string.as_ref();

        if string.is_empty() {
            return Ok(Self::Instrumental);
        }

        if let Ok(synced_lyrics) = SyncedLyrics::from_synced_lyrics(string) {
            Ok(Self::Synced(synced_lyrics))
        } else {
            Ok(LyricData::Plain(PlainLyrics::from_str(string).unwrap()))
        }
    }

    #[must_use]
    pub fn has_lyrics(&self) -> bool {
        match self {
            LyricData::Instrumental => false,
            LyricData::Plain(_) => true,
            LyricData::Synced(_) => true,
        }
    }

    #[must_use]
    pub fn instrumental(&self) -> bool {
        match self {
            LyricData::Instrumental => true,
            LyricData::Plain(_) => false,
            LyricData::Synced(_) => false,
        }
    }

    #[must_use]
    pub fn to_lyrics(&self) -> Option<String> {
        match self {
            LyricData::Instrumental => None,
            LyricData::Plain(plain_lyrics) => Some(plain_lyrics.to_string()),
            LyricData::Synced(synced_lyrics) => {
                Some(synced_lyrics.to_lyrics(LyricFormat::Lrc { a2: false }))
            }
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct PlainLyrics {
    lyrics: String,
}

impl Deref for PlainLyrics {
    type Target = String;

    fn deref(&self) -> &Self::Target {
        &self.lyrics
    }
}

impl DerefMut for PlainLyrics {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.lyrics
    }
}

impl FromStr for PlainLyrics {
    type Err = Infallible;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self {
            lyrics: s.to_owned(),
        })
    }
}
