///|
fn delimiter_size(func_name : String) -> Int raise ParseFailure {
  match func_name {
    "\\bigl" | "\\bigr" | "\\bigm" | "\\big" => 1
    "\\Bigl" | "\\Bigr" | "\\Bigm" | "\\Big" => 2
    "\\biggl" | "\\biggr" | "\\biggm" | "\\bigg" => 3
    "\\Biggl" | "\\Biggr" | "\\Biggm" | "\\Bigg" => 4
    _ =>
      raise InternalInvariant(
        message="Unknown delimiter sizing function " + func_name,
      )
  }
}

///|
fn delimiter_mclass(func_name : String) -> AtomFamily raise ParseFailure {
  match func_name {
    "\\bigl" | "\\Bigl" | "\\biggl" | "\\Biggl" => Mopen
    "\\bigr" | "\\Bigr" | "\\biggr" | "\\Biggr" => Mclose
    "\\bigm" | "\\Bigm" | "\\biggm" | "\\Biggm" => Mrel
    "\\big" | "\\Big" | "\\bigg" | "\\Bigg" => Mord
    _ =>
      raise InternalInvariant(
        message="Unknown delimiter sizing function " + func_name,
      )
  }
}

///|
fn delimiter_text(node : ParseNode) -> String? {
  match node {
    Atom(text~, ..) => Some(text)
    MathOrd(text~, ..) => Some(text)
    TextOrd(text~, ..) => Some(text)
    Spacing(text~, ..) => Some(text)
    AccentToken(text~, ..) => Some(text)
    OperatorToken(text~, ..) => Some(text)
    _ => None
  }
}

///|
fn is_delimiter(text : String) -> Bool {
  match 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"
    | "." => true
    _ => false
  }
}

///|
fn checked_delimiter(
  node : ParseNode,
  func_name : String,
) -> String raise ParseFailure {
  match delimiter_text(node) {
    Some(text) if is_delimiter(text) => text
    Some(text) =>
      raise InvalidArgument(
        message="Invalid delimiter '" + text + "' after '" + func_name + "'",
        loc=None,
      )
    None =>
      raise InvalidArgument(
        message="Invalid delimiter type after '" + func_name + "'",
        loc=None,
      )
  }
}

///|
fn delim_sizing_spec() -> FunctionSpec {
  FunctionSpec::make(
    [
      "\\bigl", "\\Bigl", "\\biggl", "\\Biggl", "\\bigr", "\\Bigr", "\\biggr", "\\Biggr",
      "\\bigm", "\\Bigm", "\\biggm", "\\Biggm", "\\big", "\\Big", "\\bigg", "\\Bigg",
    ],
    1,
    arg_types=[PrimitiveArg],
    handler=delim_sizing_handler,
  )
}

///|
fn delim_sizing_handler(
  context : FunctionContext,
  args : Array[ParseNode],
  _opt_args : Array[ParseNode?],
) -> ParseNode raise ParseFailure {
  DelimSizing(
    mode=context.mode,
    size=delimiter_size(context.func_name),
    mclass=delimiter_mclass(context.func_name),
    delim=checked_delimiter(
      require_function_arg(args, 0, context.func_name),
      context.func_name,
    ),
  )
}

///|
fn left_right_closing_spec() -> FunctionSpec {
  FunctionSpec::make(
    ["\\right"],
    1,
    arg_types=[PrimitiveArg],
    primitive=true,
    handler=left_right_closing_handler,
  )
}

///|
fn left_right_closing_handler(
  context : FunctionContext,
  args : Array[ParseNode],
  _opt_args : Array[ParseNode?],
) -> ParseNode raise ParseFailure {
  LeftRightRight(
    mode=context.mode,
    delim=checked_delimiter(
      require_function_arg(args, 0, context.func_name),
      context.func_name,
    ),
    color=(context.current_color)(),
  )
}

///|
fn left_right_spec() -> FunctionSpec {
  FunctionSpec::make(
    ["\\left"],
    1,
    arg_types=[PrimitiveArg],
    primitive=true,
    handler=left_right_handler,
  )
}

///|
fn left_right_handler(
  context : FunctionContext,
  args : Array[ParseNode],
  _opt_args : Array[ParseNode?],
) -> ParseNode raise ParseFailure {
  (context.parse_left_right)(
    checked_delimiter(
      require_function_arg(args, 0, context.func_name),
      context.func_name,
    ),
  )
}

///|
fn middle_spec() -> FunctionSpec {
  FunctionSpec::make(
    ["\\middle"],
    1,
    arg_types=[PrimitiveArg],
    primitive=true,
    handler=middle_handler,
  )
}

///|
fn middle_handler(
  context : FunctionContext,
  args : Array[ParseNode],
  _opt_args : Array[ParseNode?],
) -> ParseNode raise ParseFailure {
  let delim = checked_delimiter(
    require_function_arg(args, 0, context.func_name),
    context.func_name,
  )
  guard (context.in_left_right)() else {
    raise InvalidArgument(message="\\middle without preceding \\left", loc=None)
  }
  Middle(mode=context.mode, delim~)
}