use std::collections::HashMap;

use crate::error::{ParseError, ParseResult};
use crate::functions::{define_function_full, ArgType, FunctionContext, FunctionSpec};
use crate::parse_node::ParseNode;

struct DelimInfo {
    mclass: &'static str,
    size: u8,
}

fn delim_info(name: &str) -> Option<DelimInfo> {
    match name {
        "\\bigl" => Some(DelimInfo { mclass: "mopen", size: 1 }),
        "\\Bigl" => Some(DelimInfo { mclass: "mopen", size: 2 }),
        "\\biggl" => Some(DelimInfo { mclass: "mopen", size: 3 }),
        "\\Biggl" => Some(DelimInfo { mclass: "mopen", size: 4 }),
        "\\bigr" => Some(DelimInfo { mclass: "mclose", size: 1 }),
        "\\Bigr" => Some(DelimInfo { mclass: "mclose", size: 2 }),
        "\\biggr" => Some(DelimInfo { mclass: "mclose", size: 3 }),
        "\\Biggr" => Some(DelimInfo { mclass: "mclose", size: 4 }),
        "\\bigm" => Some(DelimInfo { mclass: "mrel", size: 1 }),
        "\\Bigm" => Some(DelimInfo { mclass: "mrel", size: 2 }),
        "\\biggm" => Some(DelimInfo { mclass: "mrel", size: 3 }),
        "\\Biggm" => Some(DelimInfo { mclass: "mrel", size: 4 }),
        "\\big" => Some(DelimInfo { mclass: "mord", size: 1 }),
        "\\Big" => Some(DelimInfo { mclass: "mord", size: 2 }),
        "\\bigg" => Some(DelimInfo { mclass: "mord", size: 3 }),
        "\\Bigg" => Some(DelimInfo { mclass: "mord", size: 4 }),
        _ => None,
    }
}

pub fn register(map: &mut HashMap<&'static str, FunctionSpec>) {
    define_function_full(
        map,
        &[
            "\\bigl", "\\Bigl", "\\biggl", "\\Biggl",
            "\\bigr", "\\Bigr", "\\biggr", "\\Biggr",
            "\\bigm", "\\Bigm", "\\biggm", "\\Biggm",
            "\\big", "\\Big", "\\bigg", "\\Bigg",
        ],
        "delimsizing",
        1, 0,
        Some(vec![ArgType::Primitive]),
        false, false, true, false, false,
        handle_delimsizing,
    );
}

fn handle_delimsizing(
    ctx: &mut FunctionContext,
    args: Vec<ParseNode>,
    _opt_args: Vec<Option<ParseNode>>,
) -> ParseResult<ParseNode> {
    let delim = check_delimiter(&args[0], &ctx.func_name)?;
    let info = delim_info(&ctx.func_name).unwrap();

    Ok(ParseNode::DelimSizing {
        mode: ctx.parser.mode,
        size: info.size,
        mclass: info.mclass.to_string(),
        delim,
        loc: None,
    })
}

fn check_delimiter(node: &ParseNode, func_name: &str) -> ParseResult<String> {
    if let Some(text) = node.symbol_text() {
        if is_valid_delimiter(text) {
            return Ok(text.to_string());
        }
        return Err(ParseError::msg(format!(
            "Invalid delimiter '{}' after '{}'",
            text, func_name
        )));
    }
    Err(ParseError::msg(format!(
        "Invalid delimiter type '{}' after '{}'",
        node.type_name(),
        func_name
    )))
}

fn is_valid_delimiter(text: &str) -> bool {
    matches!(
        text,
        "(" | "\\lparen" | ")" | "\\rparen"
            | "[" | "\\lbrack" | "]" | "\\rbrack"
            | "\\{" | "\\lbrace" | "\\}" | "\\rbrace"
            | "\\lfloor" | "\\rfloor"
            | "\\lceil" | "\\rceil"
            | "<" | ">" | "\\langle" | "\\rangle" | "\\lt" | "\\gt"
            | "\\lvert" | "\\rvert" | "\\lVert" | "\\rVert"
            | "\\lgroup" | "\\rgroup"
            | "\\lmoustache" | "\\rmoustache"
            | "/" | "\\backslash"
            | "|" | "\\vert" | "\\|" | "\\Vert"
            | "\\uparrow" | "\\Uparrow"
            | "\\downarrow" | "\\Downarrow"
            | "\\updownarrow" | "\\Updownarrow"
            | "."
    )
}
