use crate::formatter::{
    ErrorKind, FormatError, Render,
    condition::{Condition, ConditionalToken},
    format_args::FormatArg,
};

#[derive(Debug, Clone)]
pub(crate) struct Block<'a> {
    content: Vec<FormatArg<'a>>,
    prefix: Vec<FormatArg<'a>>,
    postfix: Vec<FormatArg<'a>>,
    fallback: Vec<FormatArg<'a>>,
    condition: Option<Condition<'a>>,
}

impl<'a> Render for Block<'a> {
    fn render(&self, format_table: &super::FormatTable) -> String {
        if let Some(condition) = &self.condition
            && !condition.solve(format_table)
        {
            return String::new();
        }

        let mut content = self.content.render(format_table);

        if content.is_empty() {
            let fallback = self.fallback.render(format_table);

            if fallback.is_empty() {
                return String::new();
            }

            content = fallback
        }

        let mut prefix = self.prefix.render(format_table);
        prefix.push_str(&content);
        prefix.push_str(&self.postfix.render(format_table));

        prefix
    }
}

#[derive(Debug, PartialEq, Eq)]
pub(super) enum BlockMode {
    Content,
    Prefix,
    Suffix,
    Fallback,
    Condition,
}

impl std::fmt::Display for BlockMode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            BlockMode::Content => f.write_str("Content"),
            BlockMode::Prefix => f.write_str("Prefix"),
            BlockMode::Suffix => f.write_str("Suffix"),
            BlockMode::Fallback => f.write_str("Fallback"),
            BlockMode::Condition => f.write_str("Condition"),
        }
    }
}

impl BlockMode {
    pub fn to_str(&self) -> &'static str {
        match self {
            BlockMode::Content => panic!(
                "Attempted to turn BlockMode::Content into a &str. This mode should never be set, only defaulted to"
            ),
            BlockMode::Prefix => "<",
            BlockMode::Suffix => ">",
            BlockMode::Fallback => "?",
            BlockMode::Condition => "@",
        }
    }
}

pub(super) struct BlockBuilder<'a> {
    content: Vec<FormatArg<'a>>,
    prefix: Vec<FormatArg<'a>>,
    postfix: Vec<FormatArg<'a>>,
    fallback: Vec<FormatArg<'a>>,
    condition: Option<Condition<'a>>,
    mode: Vec<BlockMode>,
}

impl<'a> Default for BlockBuilder<'a> {
    fn default() -> Self {
        Self {
            content: Default::default(),
            prefix: Default::default(),
            postfix: Default::default(),
            fallback: Default::default(),
            condition: Default::default(),
            mode: vec![BlockMode::Content],
        }
    }
}

impl<'a> BlockBuilder<'a> {
    pub(super) fn build(self) -> Block<'a> {
        Block {
            content: self.content,
            prefix: self.prefix,
            postfix: self.postfix,
            fallback: self.fallback,
            condition: self.condition,
        }
    }

    pub(super) fn set_mode(&mut self, mode: BlockMode) -> Result<(), FormatError> {
        if self.mode.contains(&mode) {
            Err(FormatError::new(ErrorKind::DoubleDefinition(mode.to_str())))
        } else {
            self.mode.push(mode);
            Ok(())
        }
    }

    pub(super) fn push_arg(&mut self, arg: FormatArg<'a>) {
        let arg_list = match self.mode.last().unwrap() {
            BlockMode::Content => &mut self.content,
            BlockMode::Prefix => &mut self.prefix,
            BlockMode::Suffix => &mut self.postfix,
            BlockMode::Fallback => &mut self.fallback,
            BlockMode::Condition => {
                let condition = self.condition.get_or_insert(Condition::default());
                condition.tokens.push(ConditionalToken::Arg(arg));
                return;
            }
        };

        arg_list.push(arg);
    }

    pub(super) fn push_conditional_token(&mut self, token: ConditionalToken<'a>) {
        match self.mode.last().unwrap() {
            BlockMode::Condition => {
                let condition = self.condition.get_or_insert(Condition::default());
                condition.tokens.push(token);
            }
            _ => self.push_arg(FormatArg::Text(token.to_str())),
        }
    }
}
