use crate::formatter::{block::BlockMode, condition::ConditionalToken};

pub(super) enum Lex<'a> {
    Text(&'a str),
    BlockStart,
    BlockEnd,
    Variable,
    Conditional,
    Prefix,
    Suffix,
    Fallback,
    Or,
    And,
    Not,
    Space,
    Escape,
}

impl<'a> Lex<'a> {
    pub fn to_str(&self) -> &'a str {
        match self {
            Lex::Text(str) => str,
            Lex::BlockStart => "{",
            Lex::BlockEnd => "}",
            Lex::Variable => "$",
            Lex::Conditional => "@",
            Lex::Prefix => "<",
            Lex::Suffix => ">",
            Lex::Fallback => "?",
            Lex::Or => "|",
            Lex::And => "&",
            Lex::Not => "!",
            Lex::Space => " ",
            Lex::Escape => "\\",
        }
    }

    pub(super) fn to_block_mode(&self) -> Option<BlockMode> {
        match self {
            Lex::Conditional => Some(BlockMode::Condition),
            Lex::Prefix => Some(BlockMode::Prefix),
            Lex::Suffix => Some(BlockMode::Suffix),
            Lex::Fallback => Some(BlockMode::Fallback),
            _ => None,
        }
    }

    pub(super) fn to_condition_token(&self) -> Option<ConditionalToken<'a>> {
        match self {
            Lex::Or => Some(ConditionalToken::Or),
            Lex::And => Some(ConditionalToken::And),
            Lex::Not => Some(ConditionalToken::Not),
            _ => None,
        }
    }
}

pub(super) fn lex_str<'a>(str: &'a str) -> Vec<Lex<'a>> {
    let mut lex = Vec::new();

    let mut text_start: Option<usize> = None;

    for (i, byte) in str.bytes().enumerate() {
        let new_lex = match byte {
            b'{' => Some(Lex::BlockStart),
            b'}' => Some(Lex::BlockEnd),
            b'$' => Some(Lex::Variable),
            b'@' => Some(Lex::Conditional),
            b'<' => Some(Lex::Prefix),
            b'>' => Some(Lex::Suffix),
            b'?' => Some(Lex::Fallback),
            b'|' => Some(Lex::Or),
            b'&' => Some(Lex::And),
            b'!' => Some(Lex::Not),
            b' ' => Some(Lex::Space),
            b'\\' => Some(Lex::Escape),
            _ => None,
        };

        if let Some(token) = new_lex {
            if let Some(start) = text_start.take() {
                lex.push(Lex::Text(&str[start..i]));
            }
            lex.push(token);
        } else {
            text_start.get_or_insert(i);
        }
    }

    if let Some(start) = text_start.take() {
        lex.push(Lex::Text(&str[start..]));
    }

    lex
}
