/*************************************************************
 *
 *  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 Configuration file for the unicode package.
 *
 * @author v.sorge@mathjax.org (Volker Sorge)
 */

import { HandlerType, ConfigurationType } from '../HandlerTypes.js';
import { Configuration } from '../Configuration.js';
import { EnvList } from '../StackItem.js';
import TexParser from '../TexParser.js';
import TexError from '../TexError.js';
import { CommandMap } from '../TokenMap.js';
import { ParseMethod } from '../Types.js';
import { UnitUtil } from '../UnitUtil.js';
import NodeUtil from '../NodeUtil.js';
import { numeric } from '../../../util/Entities.js';
import { Other } from '../base/BaseConfiguration.js';

const UnicodeCache: { [key: number]: [number, number, string, number] } = {};

// Namespace
const UnicodeMethods: { [key: string]: ParseMethod } = {
  /**
   * Parse function for unicode macro.
   *
   * @param {TexParser} parser The current tex parser.
   * @param {string} name The name of the macro.
   */
  Unicode(parser: TexParser, name: string) {
    const HD = parser.GetBrackets(name);
    let HDsplit = null;
    let font = '';
    if (HD) {
      if (
        HD.replace(/ /g, '').match(/^(\d+(\.\d*)?|\.\d+),(\d+(\.\d*)?|\.\d+)$/)
      ) {
        HDsplit = HD.replace(/ /g, '').split(/,/);
        font = parser.GetBrackets(name) || '';
      } else {
        font = HD;
      }
    }
    if (font.match(/;/)) {
      throw new TexError(
        'BadFont',
        "Font name for %1 can't contain semicolons",
        parser.currentCS
      );
    }
    const n = UnitUtil.trimSpaces(parser.GetArgument(name)).replace(/^0x/, 'x');
    if (!n.match(/^(x[0-9A-Fa-f]+|[0-9]+)$/)) {
      throw new TexError(
        'BadUnicode',
        'Argument to %1 must be a number',
        parser.currentCS
      );
    }
    const N = parseInt(n.match(/^x/) ? '0' + n : n);
    if (!UnicodeCache[N]) {
      UnicodeCache[N] = [800, 200, font, N];
    } else if (!font) {
      font = UnicodeCache[N][2];
    }
    if (HDsplit) {
      UnicodeCache[N][0] = Math.floor(parseFloat(HDsplit[0]) * 1000);
      UnicodeCache[N][1] = Math.floor(parseFloat(HDsplit[1]) * 1000);
    }
    const variant = parser.stack.env.font as string;
    const def: EnvList = {};
    if (font) {
      UnicodeCache[N][2] = def.fontfamily = font.replace(/'/g, "'");
      if (variant) {
        if (variant.match(/bold/)) {
          def.fontweight = 'bold';
        }
        if (variant.match(/italic|-mathit/)) {
          def.fontstyle = 'italic';
        }
      }
    } else if (variant) {
      def.mathvariant = variant;
    }
    const node = parser.create('token', 'mtext', def, numeric(n));
    NodeUtil.setProperty(node, 'unicode', true);
    parser.Push(node);
  },

  /**
   * Create a raw unicode character in TeX input.
   *
   * @param {TexParser} parser The current tex parser.
   * @param {string} name The name of the macro.
   */
  RawUnicode(parser: TexParser, name: string) {
    const hex = parser.GetArgument(name).trim();
    if (!hex.match(/^[0-9A-F]{1,6}$/)) {
      throw new TexError(
        'BadRawUnicode',
        'Argument to %1 must a hexadecimal number with 1 to 6 digits',
        parser.currentCS
      );
    }
    const n = parseInt(hex, 16);
    parser.string = String.fromCodePoint(n) + parser.string.substring(parser.i);
    parser.i = 0;
  },

  /**
   * Implements the \char control sequence.
   *
   * @param {TexParser} parser The current tex parser.
   * @param {string} _name The name of the macro.
   */
  Char(parser: TexParser, _name: string) {
    let match;
    const next = parser.GetNext();
    let c = '';
    const text = parser.string.substring(parser.i);
    if (next === "'") {
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      match = text.match(/^'([0-7]{1,7}) ?/u);
      if (match) {
        c = String.fromCodePoint(parseInt(match[1], 8));
      }
    } else if (next === '"') {
      match = text.match(/^"([0-9A-F]{1,6}) ?/);
      if (match) {
        c = String.fromCodePoint(parseInt(match[1], 16));
      }
    } else if (next === '`') {
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      match = text.match(/^`(?:(\\\S)|(.))/u);
      if (match) {
        if (match[2]) {
          c = match[2];
        } else {
          parser.i += 2;
          const cs = [...parser.GetCS()];
          if (cs.length > 1) {
            throw new TexError(
              'InvalidAlphanumeric',
              'Invalid alphanumeric constant for %1',
              parser.currentCS
            );
          }
          c = cs[0];
          match = [''];
        }
      }
    } else {
      match = text.match(/^([0-9]{1,7}) ?/);
      if (match) {
        c = String.fromCodePoint(parseInt(match[1]));
      }
    }
    if (!c) {
      throw new TexError(
        'MissingNumber',
        'Missing numeric constant for %1',
        parser.currentCS
      );
    }
    parser.i += match[0].length;
    if (c >= '0' && c <= '9') {
      parser.Push(parser.create('token', 'mn', {}, c));
    } else if (c.match(/[A-Za-z]/)) {
      parser.Push(parser.create('token', 'mi', {}, c));
    } else {
      Other(parser, c);
    }
  },
};

new CommandMap('unicode', {
  unicode: UnicodeMethods.Unicode,
  U: UnicodeMethods.RawUnicode,
  char: UnicodeMethods.Char,
});

export const UnicodeConfiguration = Configuration.create('unicode', {
  [ConfigurationType.HANDLER]: { [HandlerType.MACRO]: ['unicode'] },
});
