use crate::formatter::lexer::lex_str;

#[derive(Debug, Clone, Copy)]
pub enum ErrorKind {
    /// A bad closure was discovered, like an unmatched `{` or `}`
    BadClosure(&'static str),

    /// Something was defined twice, like `{content<prefix<prefix_again}`
    DoubleDefinition(&'static str),
}

#[derive(Debug, thiserror::Error)]
#[error("{kind:?}")]
pub struct FormatError {
    kind: ErrorKind,
}

impl FormatError {
    pub fn new(kind: ErrorKind) -> Self {
        Self { kind }
    }

    pub fn kind(&self) -> ErrorKind {
        self.kind
    }
}

mod block;
mod condition;
mod lexer;
mod tag;

mod format_args;
pub use format_args::Arguments;

mod format_table;
pub use format_table::*;

pub(crate) trait Render {
    fn render(&self, format_table: &FormatTable) -> String;
}

/// Returns the format [`Arguments`] for the input format string
///
/// There is not yet any documentation for how the format string works, that will come soon in the repository wiki
pub fn format_args<'a>(format_string: &'a str) -> Result<Arguments<'a>, FormatError> {
    let lexes = lex_str(format_string);
    Arguments::from_lex(lexes)
}

/// Renders the input [`Arguments`], replacing variables with values using the `format_table` input
#[inline(always)]
pub fn format(args: &Arguments, format_table: &FormatTable) -> String {
    args.render(format_table)
}

/// Helper function around [`format_args()`] and [`format()`]
pub fn format_str(
    format_string: impl AsRef<str>,
    format_table: &FormatTable,
) -> Result<String, FormatError> {
    Ok(format(&format_args(format_string.as_ref())?, format_table))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn format_equality() {
        let table = FormatTable::new();
        let result = format_str("Hello, world!", &table).unwrap();
        assert_eq!(result, "Hello, world!");
    }

    #[test]
    fn format_simple_tag() {
        let mut table = FormatTable::new();
        table.add_entry("tag", "Hello, world!");
        let result = format_str("$tag", &table).unwrap();
        assert_eq!(result, "Hello, world!");
    }

    #[test]
    fn format_block_tag() {
        let mut table = FormatTable::new();
        table.add_entry("tag", "Hello, world!");
        let result = format_str("${tag}", &table).unwrap();
        assert_eq!(result, "Hello, world!");
    }

    #[test]
    fn format_double_tag() {
        let mut table = FormatTable::new();
        table.add_entry("foo", "foo");
        table.add_entry("bar", "bar");
        let result = format_str("$foo$bar", &table).unwrap();
        assert_eq!(result, "foobar");
    }

    #[test]
    fn format_tag_ends_at_spaces() {
        let mut table = FormatTable::new();
        table.add_entry("foo", "foo");
        table.add_entry("bar", "bar");
        let result = format_str("$foo $bar", &table).unwrap();
        assert_eq!(result, "foo bar");
    }

    #[test]
    fn format_recursive_block_tag() {
        let mut table = FormatTable::new();
        table.add_entry("foo", "bar");
        table.add_entry("bar", "Hello, world!");
        let result = format_str("${$foo}", &table).unwrap();
        assert_eq!(result, "Hello, world!");
    }

    #[test]
    fn format_conditional_block() {
        let table = FormatTable::new();
        let result = format_str("{Hello, world!@$invalid}", &table).unwrap();
        assert_eq!(result, "");
    }

    #[test]
    fn format_inverted_conditional_block() {
        let table = FormatTable::new();
        let result = format_str("{Hello, world!@!$invalid}", &table).unwrap();
        assert_eq!(result, "Hello, world!");
    }

    #[test]
    fn format_double_invert() {
        let table = FormatTable::new();
        let result = format_str("{Hello, world!@!!$invalid}", &table).unwrap();
        assert_eq!(result, "");
    }

    #[test]
    fn format_double_definition_fails() {
        let table = FormatTable::new();
        let result = format_str("{content<prefix>suffix?fallback<prefix_again}", &table);

        match result {
            Err(err) if matches!(err.kind(), ErrorKind::DoubleDefinition(_)) => {}
            _ => panic!("Unexpected result: {result:?}"),
        };
    }

    #[test]
    fn format_prefix_and_suffix() {
        let table = FormatTable::new();
        let result = format_str("{content<prefix > suffix}", &table).unwrap();
        assert_eq!(result, "prefix content suffix");
    }

    #[test]
    fn format_no_content_no_prefix_and_suffix() {
        let table = FormatTable::new();
        let result = format_str("{$empty<prefix > suffix}", &table).unwrap();
        assert_eq!(result, "");
    }

    #[test]
    fn format_fallback() {
        let table = FormatTable::new();
        let result = format_str("{$invalid?fallback}", &table).unwrap();
        assert_eq!(result, "fallback");
    }

    #[test]
    fn format_fallback_uses_prefix_and_suffix() {
        let table = FormatTable::new();
        let result = format_str("{$empty<prefix > suffix?fallback}", &table).unwrap();
        assert_eq!(result, "prefix fallback suffix");
    }

    #[test]
    fn format_fallback_condition() {
        let table = FormatTable::new();
        let result = format_str("{$invalid?fallback@$invalid}", &table).unwrap();
        assert_eq!(result, "");
    }

    #[test]
    fn format_or_condition() {
        let mut table = FormatTable::new();
        table.add_entry("a", "foo");
        let result = format_str("{Hello, world!@$invalid||$a}", &table).unwrap();
        assert_eq!(result, "Hello, world!");
    }

    #[test]
    fn format_and_condition() {
        let mut table = FormatTable::new();
        table.add_entry("a", "foo");
        table.add_entry("b", "bar");
        let result = format_str("{Hello, world!@$a&&$b}", &table).unwrap();
        assert_eq!(result, "Hello, world!");
    }

    #[test]
    fn format_nand_condition() {
        let mut table = FormatTable::new();
        table.add_entry("a", "foo");
        let result = format_str("{Hello, world!@$a!&$invalid}", &table).unwrap();
        assert_eq!(result, "");
    }

    #[test]
    fn format_nor_condition() {
        let table = FormatTable::new();
        let result = format_str("{Hello, world!@$invalid!|$invalid}", &table).unwrap();
        assert_eq!(result, "");
    }

    #[test]
    fn format_verify_conditional_left_to_right() {
        let mut table = FormatTable::new();
        table.add_entry("a", "foo");
        let result = format_str("{Hello, world!@$invalid&&$invalid||$a&&$a}", &table).unwrap();
        assert_eq!(result, "Hello, world!");
    }
}
