/*************************************************************
 *
 *  Copyright (c) 2018-2026 The MathJax Consortium
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

/**
 * @file Methods for TeX parsing of the physics package.
 *
 * @author v.sorge@mathjax.org (Volker Sorge)
 */

import { HandlerType } from '../HandlerTypes.js';
import { ParseMethod, ParseResult } from '../Types.js';
import BaseMethods from '../base/BaseMethods.js';
import TexParser from '../TexParser.js';
import TexError from '../TexError.js';
import { TEXCLASS, MmlNode } from '../../../core/MmlTree/MmlNode.js';
import { ParseUtil } from '../ParseUtil.js';
import NodeUtil from '../NodeUtil.js';
import { NodeFactory } from '../NodeFactory.js';
import { Macro } from '../Token.js';
import { AutoOpen } from './PhysicsItems.js';

/**
 * Pairs open and closed fences.
 *
 * @type {{[fence: string]: string}}
 */
const pairs: { [fence: string]: string } = {
  '(': ')',
  '[': ']',
  '{': '}',
  '|': '|',
};

/**
 * Regular expression for matching big fence arguments.
 *
 * @type {RegExp}
 */
const biggs: RegExp = /^(b|B)i(g{1,2})$/;

const latinCap: [number, number] = [0x41, 0x5a];
const latinSmall: [number, number] = [0x61, 0x7a];
const greekCap: [number, number] = [0x391, 0x3a9];
const greekSmall: [number, number] = [0x3b1, 0x3c9];
const digits: [number, number] = [0x30, 0x39];

/**
 * Checks if a value is in a given numerical interval.
 *
 * @param {number} value The value.
 * @param {[number, number]} range The closed interval.
 * @returns {boolean} True if in given range.
 */
function inRange(value: number, range: [number, number]): boolean {
  return value >= range[0] && value <= range[1];
}

/**
 * Method to create a token for the vector commands. It creates a vector token
 * with the specific vector font (e.g., bold) in case it is a Latin or capital
 * Greek character, accent or small Greek character if command is starred. This
 * is a replacement for the original token method in the node factory.
 *
 * @param {NodeFactory} factory The current node factory.
 * @param {string} kind The type of token to create.
 * @param {any} def The attributes for the node.
 * @param {string} text The text contained in the token node.
 * @returns {MmlNode} The newly create token node.
 */
function createVectorToken(
  factory: NodeFactory,
  kind: string,
  def: any,
  text: string
): MmlNode {
  const parser = factory.configuration.parser;
  const token = NodeFactory.createToken(factory, kind, def, text);
  const code: number = text.codePointAt(0);
  if (
    text.length === 1 &&
    !parser.stack.env.font &&
    parser.stack.env.vectorFont &&
    (inRange(code, latinCap) ||
      inRange(code, latinSmall) ||
      inRange(code, greekCap) ||
      inRange(code, digits) ||
      (inRange(code, greekSmall) && parser.stack.env.vectorStar) ||
      NodeUtil.getAttribute(token, 'accent'))
  ) {
    NodeUtil.setAttribute(token, 'mathvariant', parser.stack.env.vectorFont);
  }
  return token;
}

/**
 * Computes the application of a vector operation.
 *
 * @param {TexParser} parser The calling parser.
 * @param {string} kind The type of stack item to parse the operator into.
 * @param {string} name The macro name.
 * @param {string} operator The operator expression.
 * @param {string[]} fences List of opening fences that should be
 *     automatically sized and paired to its corresponding closing fence.
 */
function vectorApplication(
  parser: TexParser,
  kind: string,
  name: string,
  operator: string,
  fences: string[]
) {
  const op = new TexParser(
    operator,
    parser.stack.env,
    parser.configuration
  ).mml();
  parser.Push(parser.itemFactory.create(kind, op));
  const left = parser.GetNext();
  const right = pairs[left];
  if (!right) {
    return;
  }
  let lfence = '',
    rfence = '',
    arg = '';
  const enlarge = fences.includes(left);
  if (left === '{') {
    arg = parser.GetArgument(name);
    lfence = enlarge ? '\\left\\{' : '';
    rfence = enlarge ? '\\right\\}' : '';
    const macro = `${lfence} ${arg} ${rfence}`;
    parser.string = macro + parser.string.slice(parser.i);
    parser.i = 0;
    return;
  }
  if (!enlarge) {
    return;
  }
  parser.i++;
  parser.Push(
    parser.itemFactory
      .create('auto open')
      .setProperties({ open: left, close: right })
  );
}

/**
 * Generates the expanded braket LaTeX code for matrix operations.
 *
 * @param {string[]} args The three braket arguments.
 * @param {string} args.arg1 The left braket argument.
 * @param {string} args.arg2 The middle braket argument.
 * @param {string} args.arg3 The right braket argument.
 * @param {boolean} star1 No automatic sizing of fences.
 * @param {boolean} star2 Automatic sizing of fences wrt. to arg1 & arg3 only.
 * @returns {string} The expanded macro.
 */
function outputBraket(
  [arg1, arg2, arg3]: [string, string, string],
  star1: boolean,
  star2: boolean
): string {
  return star1 && star2
    ? `\\left\\langle{${arg1}}\\middle\\vert{${arg2}}\\middle\\vert{${arg3}}\\right\\rangle`
    : star1
      ? `\\langle{${arg1}}\\vert{${arg2}}\\vert{${arg3}}\\rangle`
      : `\\left\\langle{${arg1}}\\right\\vert{${arg2}}\\left\\vert{${arg3}}\\right\\rangle`;
}

/**
 * Creates the a (anti)diagonal matrix string.
 *
 * @param {string[]} elements The elements on the diagonal.
 * @param {boolean} anti True if constructing anti-diagonal matrix.
 * @returns {string} The expanded macro.
 */
function makeDiagMatrix(elements: string[], anti: boolean): string {
  const length = elements.length;
  const matrix = [];
  for (let i = 0; i < length; i++) {
    matrix.push(
      Array(anti ? length - i : i + 1).join('&') + `\\mqty{${elements[i]}}`
    );
  }
  return matrix.join('\\\\ ');
}

const PhysicsMethods: { [key: string]: ParseMethod } = {
  /***********************
   * Physics package section 2.1
   * Automatic bracing
   */

  /**
   * Automatic sizing of fences, e.g., \\qty(x). Some with content.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   * @param {string=} open Opening fence.
   * @param {string=} close Closing fence.
   * @param {boolean=} arg Fences contain an argument.
   * @param {string=} named Name operator.
   * @param {string=} variant A font for the mathvariant.
   */
  Quantity(
    parser: TexParser,
    name: string,
    open: string = '(',
    close: string = ')',
    arg: boolean = false,
    named: string = '',
    variant: string = ''
  ) {
    const star = arg ? parser.GetStar() : false;
    let next = parser.GetNext();
    const position = parser.i;
    let big = null;
    if (next === '\\') {
      parser.i++;
      big = parser.GetCS();
      if (!big.match(biggs)) {
        // empty
        const empty = parser.create('node', 'mrow');
        parser.Push(ParseUtil.fenced(parser.configuration, open, empty, close));
        parser.i = position;
        return;
      }
      next = parser.GetNext();
    }
    let right = pairs[next];
    if (arg && next !== '{') {
      throw new TexError(
        'MissingArgFor',
        'Missing argument for %1',
        parser.currentCS
      );
    }
    if (!right) {
      const empty = parser.create('node', 'mrow');
      parser.Push(ParseUtil.fenced(parser.configuration, open, empty, close));
      parser.i = position;
      return;
    }
    // Get the fences
    if (named) {
      const mml = parser.create(
        'token',
        'mi',
        { texClass: TEXCLASS.OP },
        named
      );
      if (variant) {
        NodeUtil.setAttribute(mml, 'mathvariant', variant);
      }
      parser.Push(parser.itemFactory.create('fn', mml));
    }
    if (next === '{') {
      let argument = parser.GetArgument(name);
      next = arg ? open : '\\{';
      right = arg ? close : '\\}';
      // TODO: Make all these fenced expressions.
      argument = star
        ? `${next} ${argument} ${right}`
        : big
          ? `\\${big}l${next} ${argument} \\${big}r${right}`
          : `\\left${next} ${argument} \\right${right}`;
      parser.Push(
        new TexParser(argument, parser.stack.env, parser.configuration).mml()
      );
      return;
    }
    parser.i++;
    parser.Push(
      parser.itemFactory
        .create('auto open')
        .setProperties({ open: next, close: right, big: big })
    );
  },

  /**
   * The evaluate macro.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   */
  Eval(parser: TexParser, name: string) {
    const star = parser.GetStar();
    const next = parser.GetNext();
    if (next === '(' || next === '[') {
      parser.i++;
      parser.Push(
        parser.itemFactory.create('auto open').setProperties({
          open: next,
          close: '|',
          smash: star,
          right: '\\vphantom{\\int}',
        })
      );
      return;
    }
    let replace = '\\left.\\vphantom{\\int}\\right|';
    if (next === '{') {
      const arg = parser.GetArgument(name);
      replace = `\\left.${star ? `\\smash{${arg}}` : arg}\\vphantom{\\int}\\right|`;
    }
    parser.string =
      parser.string.substring(0, parser.i) +
      replace +
      parser.string.slice(parser.i);
  },

  /**
   * The anti/commutator and poisson macros.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   * @param {string=} open Opening fence.
   * @param {string=} close Closing fence.
   */
  Commutator(
    parser: TexParser,
    name: string,
    open: string = '[',
    close: string = ']'
  ) {
    const star = parser.GetStar();
    let next = parser.GetNext();
    let big = null;
    if (next === '\\') {
      parser.i++;
      big = parser.GetCS();
      if (!big.match(biggs)) {
        // Actually a commutator error arg1 error.
        throw new TexError(
          'MissingArgFor',
          'Missing argument for %1',
          parser.currentCS
        );
      }
      next = parser.GetNext();
    }
    if (next !== '{') {
      throw new TexError(
        'MissingArgFor',
        'Missing argument for %1',
        parser.currentCS
      );
    }
    const arg1 = parser.GetArgument(name);
    const arg2 = parser.GetArgument(name);
    let argument = arg1 + ',' + arg2;
    argument = star
      ? `${open} ${argument} ${close}`
      : big
        ? `\\${big}l${open} ${argument} \\${big}r${close}`
        : `\\left${open} ${argument} \\right${close}`;
    parser.Push(
      new TexParser(argument, parser.stack.env, parser.configuration).mml()
    );
  },

  /***********************
   * Physics package section 2.2
   * Vector notation
   */

  /**
   * Bold vector notation.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   */
  VectorBold(parser: TexParser, name: string) {
    const star = parser.GetStar();
    const arg = parser.GetArgument(name);
    const oldToken = parser.configuration.nodeFactory.get('token');
    const oldFont = parser.stack.env.font;
    delete parser.stack.env.font;
    parser.configuration.nodeFactory.set('token', createVectorToken);
    parser.stack.env.vectorFont = star ? 'bold-italic' : 'bold';
    parser.stack.env.vectorStar = star;
    const node = new TexParser(
      arg,
      parser.stack.env,
      parser.configuration
    ).mml();
    if (oldFont) {
      parser.stack.env.font = oldFont;
    }
    delete parser.stack.env.vectorFont;
    delete parser.stack.env.vectorStar;
    parser.configuration.nodeFactory.set('token', oldToken);
    parser.Push(node);
  },

  /**
   * Macros that can have an optional star which is propagated.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   * @param {number} argcount Number of arguments.
   * @param {string[]} parts List of parts from which to assemble the macro.
   *     If the original command is starred, a star will be injected at each part.
   */
  StarMacro(
    parser: TexParser,
    name: string,
    argcount: number,
    ...parts: string[]
  ) {
    const star = parser.GetStar();
    const args: string[] = [];
    if (argcount) {
      for (let i = args.length; i < argcount; i++) {
        args.push(parser.GetArgument(name));
      }
    }
    let macro = parts.join(star ? '*' : '');
    macro = ParseUtil.substituteArgs(parser, args, macro);
    parser.string = ParseUtil.addArgs(
      parser,
      macro,
      parser.string.slice(parser.i)
    );
    parser.i = 0;
    ParseUtil.checkMaxMacros(parser);
  },

  /**
   * An operator that needs to be parsed (e.g., a Greek letter or nabla) and
   * applied to a possibly fenced expression. By default automatic fences are
   * parentheses and brakets, with braces being ignored.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   * @param {string} operator The operator expression.
   * @param {string[]} fences List of opening fences that should be
   *     automatically sized and paired to its corresponding closing fence.
   */
  OperatorApplication(
    parser: TexParser,
    name: string,
    operator: string,
    ...fences: string[]
  ) {
    vectorApplication(parser, 'fn', name, operator, fences);
  },

  /**
   * A vector operator that needs to be parsed (e.g., a Greek letter or nabla with
   * a crossproduct) and connected to a possibly fenced expression. By default
   * automatic fences are parentheses and brakets.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   * @param {string} operator The operator expression.
   * @param {string[]} fences List of opening fences that should be
   *     automatically sized and paired to its corresponding closing fence.
   */
  VectorOperator(
    parser: TexParser,
    name: string,
    operator: string,
    ...fences: string[]
  ) {
    vectorApplication(parser, 'mml', name, operator, fences);
  },

  /***********************
   * Physics package section 2.3
   * Operators
   */

  /**
   * Operator expression with automatic fences and optional exponent.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   * @param {boolean=} opt Set to false if no optional exponent is allowed.
   * @param {string=} id The name of the function if different from name.
   */
  Expression(
    parser: TexParser,
    name: string,
    opt: boolean = true,
    id: string = ''
  ) {
    id = id || name.slice(1);
    const exp = opt ? parser.GetBrackets(name) : null;
    let mml = parser.create('token', 'mi', { texClass: TEXCLASS.OP }, id);
    if (exp) {
      const sup = new TexParser(
        exp,
        parser.stack.env,
        parser.configuration
      ).mml();
      mml = parser.create('node', 'msup', [mml, sup]);
    }
    parser.Push(parser.itemFactory.create('fn', mml));
    if (parser.GetNext() !== '(') {
      return;
    }
    parser.i++;
    parser.Push(
      parser.itemFactory
        .create('auto open')
        .setProperties({ open: '(', close: ')' })
    );
  },

  /***********************
   * Physics package section 2.4
   * Quick quad text
   */

  /**
   * Quad text macros.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   * @param {string} text The text that is to be padded with quad spaces.
   */
  Qqtext(parser: TexParser, name: string, text: string) {
    const star = parser.GetStar();
    const arg = text ? text : parser.GetArgument(name);
    const replace = (star ? '' : '\\quad') + '\\text{' + arg + '}\\quad ';
    parser.string =
      parser.string.slice(0, parser.i) +
      replace +
      parser.string.slice(parser.i);
  },

  /***********************
   * Physics package section 2.5
   * Derivatives
   */

  /**
   * The differential and variation macros.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   * @param {string} op The operator. It will be parsed.
   */
  Differential(parser: TexParser, name: string, op: string) {
    const optArg = parser.GetBrackets(name);
    const power = optArg != null ? '^{' + optArg + '}' : ' ';
    const parens = parser.GetNext() === '(';
    const braces = parser.GetNext() === '{';
    let macro = op + power;
    if (!(parens || braces)) {
      macro += parser.GetArgument(name, true) || '';
      const mml = new TexParser(
        macro,
        parser.stack.env,
        parser.configuration
      ).mml();
      parser.Push(mml);
      return;
    }
    if (braces) {
      macro += parser.GetArgument(name);
      const mml = new TexParser(
        macro,
        parser.stack.env,
        parser.configuration
      ).mml();
      parser.Push(
        parser.create('node', 'TeXAtom', [mml], { texClass: TEXCLASS.OP })
      );
      return;
    }
    parser.Push(
      new TexParser(macro, parser.stack.env, parser.configuration).mml()
    );
    parser.i++;
    parser.Push(
      parser.itemFactory
        .create('auto open')
        .setProperties({ open: '(', close: ')' })
    );
  },

  /**
   * The derivative macro. Its behaviour depends on the number of arguments
   * provided. In case of
   * 1 argument: will be part of the denominator.
   * 2 arguments: argument one is numerator, argument two is denominator.
   * 3+ arguments: arguments above 2 will be part of the denominator and the
   * exponent of the enumerator will depend on the number of denominator
   * arguments. In particular, the optional exponent argument will be ignored!
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   * @param {number} argMax The maximum number of arguments for the macro.
   * @param {string} op The derivative operator.
   */
  Derivative(parser: TexParser, name: string, argMax: number, op: string) {
    const star = parser.GetStar();
    const optArg = parser.GetBrackets(name);
    let argCounter = 1;
    const args = [];
    args.push(parser.GetArgument(name));
    while (parser.GetNext() === '{' && argCounter < argMax) {
      args.push(parser.GetArgument(name));
      argCounter++;
    }
    let ignore = false;
    let power1 = ' ';
    let power2 = ' ';
    if (argMax > 2 && args.length > 2) {
      power1 = '^{' + (args.length - 1) + '}';
      ignore = true;
    } else if (optArg != null) {
      if (argMax > 2 && args.length > 1) {
        ignore = true;
      }
      power1 = `^{${optArg}}`;
      power2 = power1;
    }
    const frac = star ? '\\flatfrac' : '\\frac';
    const first = args.length > 1 ? args[0] : '';
    const second = args.length > 1 ? args[1] : args[0];
    let rest = '';
    for (let i = 2, arg; (arg = args[i]); i++) {
      rest += op + ' ' + arg;
    }
    const macro = `${frac}{${op}${power1}${first}}{${op} ${second}${power2} ${rest}}`;
    parser.Push(
      new TexParser(macro, parser.stack.env, parser.configuration).mml()
    );
    if (parser.GetNext() === '(') {
      parser.i++;
      parser.Push(
        parser.itemFactory
          .create('auto open')
          .setProperties({ open: '(', close: ')', ignore: ignore })
      );
    }
  },

  /***********************
   * Physics package section 2.6
   * Dirac bra-ket notation
   */

  /**
   * The bra macro.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   */
  Bra(parser: TexParser, name: string) {
    const starBra = parser.GetStar();
    const bra = parser.GetArgument(name);
    let ket = '';
    let hasKet = false;
    let starKet = false;
    if (parser.GetNext() === '\\') {
      let saveI = parser.i;
      parser.i++;
      // This ensures that bra-ket also works if \let bound versions of \ket.
      const cs = parser.GetCS();
      const token = parser.lookup(HandlerType.MACRO, cs) as Macro;
      if (token && token.token === 'ket') {
        hasKet = true;
        saveI = parser.i;
        starKet = parser.GetStar();
        if (parser.GetNext() === '{') {
          ket = parser.GetArgument(cs, true);
        } else {
          parser.i = saveI;
          starKet = false;
        }
      } else {
        parser.i = saveI;
      }
    }
    let macro = '';
    if (hasKet) {
      macro =
        starBra || starKet
          ? `\\langle{${bra}}\\vert{${ket}}\\rangle`
          : `\\left\\langle{${bra}}\\middle\\vert{${ket}}\\right\\rangle`;
    } else {
      macro = starBra
        ? `\\langle{${bra}}\\vert`
        : `\\left\\langle{${bra}}\\right\\vert{${ket}}`;
    }
    parser.Push(
      new TexParser(macro, parser.stack.env, parser.configuration).mml()
    );
  },

  /**
   * The ket macro.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   */
  Ket(parser: TexParser, name: string) {
    const star = parser.GetStar();
    const ket = parser.GetArgument(name);
    const macro = star
      ? `\\vert{${ket}}\\rangle`
      : `\\left\\vert{${ket}}\\right\\rangle`;
    parser.Push(
      new TexParser(macro, parser.stack.env, parser.configuration).mml()
    );
  },

  /**
   * The braket macro.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   */
  BraKet(parser: TexParser, name: string) {
    const star = parser.GetStar();
    const bra = parser.GetArgument(name);
    let ket = null;
    if (parser.GetNext() === '{') {
      ket = parser.GetArgument(name, true);
    }
    let macro = '';
    if (ket == null) {
      macro = star
        ? `\\langle{${bra}}\\vert{${bra}}\\rangle`
        : `\\left\\langle{${bra}}\\middle\\vert{${bra}}\\right\\rangle`;
    } else {
      macro = star
        ? `\\langle{${bra}}\\vert{${ket}}\\rangle`
        : `\\left\\langle{${bra}}\\middle\\vert{${ket}}\\right\\rangle`;
    }
    parser.Push(
      new TexParser(macro, parser.stack.env, parser.configuration).mml()
    );
  },

  /**
   * The ketbra macro.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   */
  KetBra(parser: TexParser, name: string) {
    const star = parser.GetStar();
    const ket = parser.GetArgument(name);
    let bra = null;
    if (parser.GetNext() === '{') {
      bra = parser.GetArgument(name, true);
    }
    let macro = '';
    if (bra == null) {
      macro = star
        ? `\\vert{${ket}}\\rangle\\!\\langle{${ket}}\\vert`
        : `\\left\\vert{${ket}}\\middle\\rangle\\!\\middle\\langle{${ket}}\\right\\vert`;
    } else {
      macro = star
        ? `\\vert{${ket}}\\rangle\\!\\langle{${bra}}\\vert`
        : `\\left\\vert{${ket}}\\middle\\rangle\\!\\middle\\langle{${bra}}\\right\\vert`;
    }
    parser.Push(
      new TexParser(macro, parser.stack.env, parser.configuration).mml()
    );
  },

  /**
   * The expectation value macro.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   */
  Expectation(parser: TexParser, name: string) {
    const star1 = parser.GetStar();
    const star2 = star1 && parser.GetStar();
    const arg1 = parser.GetArgument(name);
    let arg2 = null;
    if (parser.GetNext() === '{') {
      arg2 = parser.GetArgument(name, true);
    }
    const macro =
      arg1 && arg2
        ? outputBraket([arg2, arg1, arg2], star1, star2)
        : // Braces for semantics, similar to braket package.
          star1
          ? `\\langle {${arg1}} \\rangle`
          : `\\left\\langle {${arg1}} \\right\\rangle`;
    parser.Push(
      new TexParser(macro, parser.stack.env, parser.configuration).mml()
    );
  },

  /**
   * The matrix element macro.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   */
  MatrixElement(parser: TexParser, name: string) {
    const star1 = parser.GetStar();
    const star2 = star1 && parser.GetStar();
    const arg1 = parser.GetArgument(name);
    const arg2 = parser.GetArgument(name);
    const arg3 = parser.GetArgument(name);
    const macro = outputBraket([arg1, arg2, arg3], star1, star2);
    parser.Push(
      new TexParser(macro, parser.stack.env, parser.configuration).mml()
    );
  },

  /********************
   * Physics package Section 2.7
   * Matrix macros
   */
  /**
   * The matrix quantity macro.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   * @param {boolean=} small Use small matrix.
   */
  MatrixQuantity(parser: TexParser, name: string, small?: boolean) {
    const star = parser.GetStar();
    const next = parser.GetNext();
    const array = small ? 'smallmatrix' : 'array';
    let arg = '';
    let open = '';
    let close = '';
    switch (next) {
      case '{':
        arg = parser.GetArgument(name);
        break;
      case '(':
        parser.i++;
        open = star ? '\\lgroup' : '(';
        close = star ? '\\rgroup' : ')';
        arg = parser.GetUpTo(name, ')');
        break;
      case '[':
        parser.i++;
        open = '[';
        close = ']';
        arg = parser.GetUpTo(name, ']');
        break;
      case '|':
        parser.i++;
        open = '|';
        close = '|';
        arg = parser.GetUpTo(name, '|');
        break;
      default:
        open = '(';
        close = ')';
        break;
    }
    const macro =
      (open ? '\\left' : '') +
      `${open}\\begin{${array}}{} ${arg}\\end{${array}}` +
      (open ? '\\right' : '') +
      close;
    parser.Push(
      new TexParser(macro, parser.stack.env, parser.configuration).mml()
    );
  },

  /**
   * Generation of identity matrices.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   */
  IdentityMatrix(parser: TexParser, name: string) {
    const arg = parser.GetArgument(name);
    const size = parseInt(arg, 10);
    if (isNaN(size)) {
      throw new TexError('InvalidNumber', 'Invalid number');
    }
    if (size <= 1) {
      parser.string = '1' + parser.string.slice(parser.i);
      parser.i = 0;
      return;
    }
    const zeros = Array(size).fill('0');
    const columns = [];
    for (let i = 0; i < size; i++) {
      const row = zeros.slice();
      row[i] = '1';
      columns.push(row.join(' & '));
    }
    parser.string = columns.join('\\\\ ') + parser.string.slice(parser.i);
    parser.i = 0;
  },

  /**
   * Generation of matrices with fixed value.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   */
  XMatrix(parser: TexParser, name: string) {
    const star = parser.GetStar();
    const arg1 = parser.GetArgument(name);
    const arg2 = parser.GetArgument(name);
    const arg3 = parser.GetArgument(name);
    let n = parseInt(arg2, 10);
    let m = parseInt(arg3, 10);
    if (
      isNaN(n) ||
      isNaN(m) ||
      m.toString() !== arg3 ||
      n.toString() !== arg2
    ) {
      throw new TexError('InvalidNumber', 'Invalid number');
    }
    n = n < 1 ? 1 : n;
    m = m < 1 ? 1 : m;
    // Elements
    if (!star) {
      const row = Array(m).fill(arg1).join(' & ');
      const matrix = Array(n).fill(row).join('\\\\ ');
      parser.string = matrix + parser.string.slice(parser.i);
      parser.i = 0;
      return;
    }
    let matrix = '';
    if (n === 1 && m === 1) {
      // Case 1: n=m=1, no index.
      matrix = arg1;
    } else if (n === 1) {
      // Case 2: n=1, row vector, single index.
      const row = [];
      for (let i = 1; i <= m; i++) {
        row.push(`${arg1}_{${i}}`);
      }
      matrix = row.join(' & ');
    } else if (m === 1) {
      // Case 3: m=1, column vector, single index.
      const row = [];
      for (let i = 1; i <= n; i++) {
        row.push(`${arg1}_{${i}}`);
      }
      matrix = row.join('\\\\ ');
    } else {
      // Case 4: matrix, double index. Note the extra mrows for indices.
      const rows = [];
      for (let i = 1; i <= n; i++) {
        const row = [];
        for (let j = 1; j <= m; j++) {
          row.push(`${arg1}_{{${i}}{${j}}}`);
        }
        rows.push(row.join(' & '));
      }
      matrix = rows.join('\\\\ ');
    }
    parser.string = matrix + parser.string.slice(parser.i);
    parser.i = 0;
    return;
  },

  /**
   * Generation of Pauli matrices. Matrix 0 is the 2x2 identity.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   */
  PauliMatrix(parser: TexParser, name: string) {
    const arg = parser.GetArgument(name);
    let matrix = arg.slice(1);
    switch (arg[0]) {
      case '0':
        matrix += ' 1 & 0\\\\ 0 & 1';
        break;
      case '1':
      case 'x':
        matrix += ' 0 & 1\\\\ 1 & 0';
        break;
      case '2':
      case 'y':
        matrix += ' 0 & -i\\\\ i & 0';
        break;
      case '3':
      case 'z':
        matrix += ' 1 & 0\\\\ 0 & -1';
        break;
      default:
    }
    parser.string = matrix + parser.string.slice(parser.i);
    parser.i = 0;
  },

  /**
   * Generation of anti/diagonal matrices.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} name The macro name.
   * @param {boolean=} anti True if constructing anti-diagonal matrix.
   */
  DiagonalMatrix(parser: TexParser, name: string, anti?: boolean) {
    if (parser.GetNext() !== '{') {
      return;
    }
    const startI = parser.i;
    /* let arg =*/ parser.GetArgument(name);
    const endI = parser.i;
    parser.i = startI + 1;
    const elements = [];
    let element = '';
    let currentI = parser.i;
    while (currentI < endI) {
      try {
        element = parser.GetUpTo(name, ',');
      } catch (_e) {
        parser.i = endI;
        elements.push(parser.string.slice(currentI, endI - 1));
        break;
      }
      currentI = parser.i;
      elements.push(element);
    }
    parser.string = makeDiagMatrix(elements, anti) + parser.string.slice(endI);
    parser.i = 0;
  },

  /**
   * Closes an automatic fence if one was opened.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} fence The fence.
   * @param {number} texclass The TeX class.
   * @returns {ParseResult} The parse result.
   */
  AutoClose(parser: TexParser, fence: string, texclass: number): ParseResult {
    //
    // Get the top item, skipping an \over item, if there is one.
    //
    let top = parser.stack.Top();
    if (top.isKind('over')) {
      top = parser.stack.Top(2);
    }
    //
    //  If the top isn't an AutoOpen, or this isn't its closing fence,
    //    then process the fence as a normal character
    //
    if (!top.isKind('auto open') || !(top as AutoOpen).closing(fence)) {
      return false;
    }
    //
    // Close any open \over items, then push the mo for the fence.
    //
    const mo = parser.create('token', 'mo', { texClass: texclass }, fence);
    parser.Push(
      parser.itemFactory
        .create('close')
        .setProperties({ 'pre-autoclose': true })
    );
    parser.Push(
      parser.itemFactory.create('mml', mo).setProperties({ autoclose: true })
    );
    return true;
  },

  /**
   * Generates the vector nabla depending on the arrowdel option.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} _name The macro name.
   * @returns {ParseResult} The parse result.
   */
  Vnabla(parser: TexParser, _name: string): ParseResult {
    const argument = parser.options.physics.arrowdel
      ? '\\vec{\\gradientnabla}'
      : '{\\gradientnabla}';
    return parser.Push(
      new TexParser(argument, parser.stack.env, parser.configuration).mml()
    );
  },

  /**
   * Generates the differential d depending on the italicdiff option.
   *
   * @param {TexParser} parser The calling parser.
   * @param {string} _name The macro name.
   * @returns {ParseResult} The parse result.
   */
  DiffD(parser: TexParser, _name: string): ParseResult {
    const argument = parser.options.physics.italicdiff ? 'd' : '{\\rm d}';
    return parser.Push(
      new TexParser(argument, parser.stack.env, parser.configuration).mml()
    );
  },

  Macro: BaseMethods.Macro,
  NamedFn: BaseMethods.NamedFn,
  Array: BaseMethods.Array,
};

export default PhysicsMethods;
