///|
fn is_public_builtin_command(name : String) -> Bool {
  name.has_prefix("\\") &&
  name.length() > 1 &&
  !name.contains("@") &&
  (!name.has_prefix("\\\\") || name == "\\\\") &&
  name != "\\DOTSI" &&
  name != "\\DOTSB" &&
  name != "\\DOTSX"
}

///|
fn command_symbol_group_name(group : SymbolGroup) -> String {
  match group {
    AccentTokenGroup => "accent-token"
    BinaryGroup => "binary"
    CloseGroup => "close"
    InnerGroup => "inner"
    MathOrdGroup => "mathord"
    OperatorTokenGroup => "operator-token"
    OpenGroup => "open"
    PunctuationGroup => "punctuation"
    RelationGroup => "relation"
    SpacingGroup => "spacing"
    TextOrdGroup => "textord"
  }
}

///|
fn command_arguments(
  count : Int,
  optional : Int,
  types : Array[ArgType],
  fallback : ArgType,
) -> Array[CommandArgument] {
  Array::makei(count + optional, index => {
    arg_type: Some(types.get(index).unwrap_or(fallback)),
    optional: index < optional,
  })
}

///|
fn command_templates(
  name : String,
  args : Array[CommandArgument],
) -> Array[String] {
  let required = StringBuilder()
  let full = StringBuilder()
  required.write_string(name)
  full.write_string(name)
  let mut has_optional = false
  for index, arg in args {
    let field = "#{arg\{index + 1}}"
    if arg.optional {
      has_optional = true
      full.write_string("[\{field}]")
    } else {
      required.write_string("{\{field}}")
      full.write_string("{\{field}}")
    }
  }
  if has_optional {
    [required.to_string(), full.to_string()]
  } else {
    [required.to_string()]
  }
}

///|
fn function_command_info(
  name : String,
  spec : FunctionSpec,
  symbol : CommandInfo?,
) -> CommandInfo {
  let args = command_arguments(
    spec.num_args,
    spec.num_optional_args,
    spec.arg_types,
    if spec.primitive {
      PrimitiveArg
    } else {
      OriginalArg
    },
  )
  let modes : Array[Mode] = []
  if spec.allowed_in_math {
    modes.push(Math)
  }
  if spec.allowed_in_text {
    modes.push(Text)
  }
  complete_command_signature({
    name,
    kind: FunctionCommand,
    syntax: if spec.infix {
      InfixCommand
    } else {
      RegularCommand
    },
    args,
    modes: Some(modes),
    unicode: symbol.map_or(None, info => info.unicode),
    group: symbol.map_or(None, info => info.group),
    templates: command_templates(name, args),
  })
}

///|
fn macro_command_info(
  name : String,
  args : Array[CommandArgument],
  symbol : CommandInfo?,
) -> CommandInfo {
  complete_command_signature({
    name,
    kind: MacroCommand,
    syntax: RegularCommand,
    args,
    modes: None,
    unicode: symbol.map_or(None, info => info.unicode),
    group: symbol.map_or(None, info => info.group),
    templates: command_templates(name, args),
  })
}

///|
/// Templates use #{field} internally so the JS backend never embeds a literal
/// ${field} in a generated JavaScript template string. Export normalizes it.
/// Correct public signatures whose input is consumed by handlers or by
/// expanded aliases rather than by the function/macro's own argument list.
fn complete_command_signature(info : CommandInfo) -> CommandInfo {
  let name = info.name
  match name {
    "\\operatorname" | "\\operatorname*" | "\\Braket" | "\\Set" | "\\set" => {
      let args = command_arguments(1, 0, [], OriginalArg)
      { ..info, args, templates: command_templates(name, args), }
    }
    "\\tag" | "\\tag*" => {
      let args = command_arguments(1, 0, [TextArg], TextArg)
      { ..info, args, templates: command_templates(name, args), }
    }
    "\\hspace" | "\\hspace*" => {
      let args = command_arguments(1, 0, [SizeArg], SizeArg)
      { ..info, args, templates: command_templates(name, args), }
    }
    "\\TextOrMath" => {
      let args = command_arguments(2, 0, [TextArg, MathArg], OriginalArg)
      { ..info, args, templates: command_templates(name, args), }
    }
    "\\message" | "\\errmessage" => {
      let args = command_arguments(1, 0, [RawArg], RawArg)
      { ..info, args, templates: command_templates(name, args), }
    }
    "\\\\" => {
      let args = command_arguments(0, 1, [SizeArg], SizeArg)
      { ..info, args, templates: command_templates(name, args), }
    }
    "\\newcommand" | "\\renewcommand" | "\\providecommand" =>
      {
        ..info,
        syntax: DefinitionCommand,
        args: [
          { arg_type: Some(RawArg), optional: false, },
          { arg_type: Some(RawArg), optional: true, },
          { arg_type: Some(RawArg), optional: false, },
        ],
        templates: [
          name + "{\\#{name}}{#{definition}}",
          name + "{\\#{name}}[#{count}]{#{definition}}",
        ],
      }
    "\\def" | "\\gdef" | "\\edef" | "\\xdef" =>
      {
        ..info,
        syntax: DefinitionCommand,
        args: command_arguments(3, 0, [RawArg, RawArg, RawArg], RawArg),
        templates: [name + "\\#{name}#{parameters}{#{definition}}"],
      }
    "\\let" =>
      {
        ..info,
        syntax: TokenCommand,
        args: command_arguments(2, 0, [], RawArg),
        templates: [name + "\\#{name}=\\#{command}"],
      }
    "\\futurelet" =>
      {
        ..info,
        syntax: TokenCommand,
        args: command_arguments(3, 0, [], RawArg),
        templates: [name + "\\#{name}\\#{next}\\#{command}"],
      }
    "\\global" | "\\long" =>
      {
        ..info,
        syntax: TokenCommand,
        args: command_arguments(1, 0, [], RawArg),
        templates: [name + "\\#{command}"],
      }
    "\\noexpand" | "\\show" =>
      {
        ..info,
        syntax: TokenCommand,
        args: command_arguments(1, 0, [], RawArg),
        templates: [name + "\\#{command}"],
      }
    "\\expandafter" =>
      {
        ..info,
        syntax: TokenCommand,
        args: command_arguments(2, 0, [], RawArg),
        templates: [name + "\\#{first}\\#{second}"],
      }
    "\\char" =>
      {
        ..info,
        syntax: TokenCommand,
        args: command_arguments(1, 0, [], RawArg),
        templates: [name + "\"#{hex} ", name + "\u{0060}#{character}"],
      }
    "\\verb" | "\\verb*" =>
      {
        ..info,
        syntax: VerbatimCommand,
        args: command_arguments(1, 0, [], RawArg),
        templates: [name + "|#{text}|"],
      }
    "\\left" =>
      {
        ..info,
        syntax: DelimiterCommand,
        templates: ["\\left(#{body}\\right)"],
      }
    "\\right"
    | "\\middle"
    | "\\big"
    | "\\Big"
    | "\\bigg"
    | "\\Bigg"
    | "\\bigl"
    | "\\Bigl"
    | "\\biggl"
    | "\\Biggl"
    | "\\bigr"
    | "\\Bigr"
    | "\\biggr"
    | "\\Biggr"
    | "\\bigm"
    | "\\Bigm"
    | "\\biggm"
    | "\\Biggm" =>
      { ..info, syntax: DelimiterCommand, templates: [name + " #{delimiter}"], }
    "\\begin" =>
      {
        ..info,
        syntax: EnvironmentCommand,
        templates: environment_command_templates(),
      }
    "\\end" => {
      let templates : Array[String] = []
      for spec in builtin_environments {
        for env in spec.names {
          templates.push("\\end{\{env}}")
        }
      }
      { ..info, syntax: EnvironmentCommand, templates, }
    }
    "\\begingroup" =>
      {
        ..info,
        syntax: GroupCommand,
        templates: [name + " #{body}\\endgroup"],
      }
    "\\bgroup" =>
      { ..info, syntax: GroupCommand, templates: [name + " #{body}\\egroup"], }
    "\\egroup" => { ..info, syntax: GroupCommand, }
    "\\(" => { ..info, syntax: GroupCommand, templates: ["\\(#{body}\\)"], }
    "\\)" | "\\]" => { ..info, syntax: GroupCommand, }
    "\\color" =>
      {
        ..info,
        syntax: DeclarationCommand,
        templates: ["{\\color{#{color}} #{body}}"],
      }
    "\\over" | "\\choose" | "\\atop" | "\\brace" | "\\brack" =>
      { ..info, templates: ["{#{left} " + name + " #{right}}"], }
    "\\above" => { ..info, templates: ["{#{left} \\above{#{size}} #{right}}"], }
    _ => {
      let declaration = sizing_commands.contains(name) ||
        old_font_commands.any(command => command.name == name) ||
        name == "\\displaystyle" ||
        name == "\\textstyle" ||
        name == "\\scriptstyle" ||
        name == "\\scriptscriptstyle"
      if declaration {
        {
          ..info,
          syntax: DeclarationCommand,
          templates: ["{" + name + " #{body}}"],
        }
      } else {
        info
      }
    }
  }
}

///|
fn environment_command_templates() -> Array[String] {
  let templates : Array[String] = []
  for spec in builtin_environments {
    for env in spec.names {
      let args = if env.has_suffix("matrix*") {
        command_arguments(0, 1, [RawArg], RawArg)
      } else {
        command_arguments(
          spec.num_args,
          spec.num_optional_args,
          spec.arg_types,
          OriginalArg,
        )
      }
      for opening in command_templates("\\begin{\{env}}", args) {
        templates.push(opening + "\n#{body}\n\\end{\{env}}")
      }
    }
  }
  templates.sort()
  templates
}