/*************************************************************
 *
 *  Copyright (c) 2009-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 Node factory for creating MmlNodes. This allows extension
 *     packages to add node constructors or overwrite existing ones.
 *
 * @author v.sorge@mathjax.org (Volker Sorge)
 */

import { TextNode, MmlNode } from '../../core/MmlTree/MmlNode.js';
import { MmlFactory } from '../../core/MmlTree/MmlFactory.js';
import ParseOptions from './ParseOptions.js';
import NodeUtil from './NodeUtil.js';

export type NodeFactoryMethod = (
  factory: NodeFactory,
  kind: string,
  ...rest: any[]
) => MmlNode;

export class NodeFactory {
  /**
   * Parser configuration that can be used to pass information between node methods.
   *
   * @type {ParseOptions}
   */
  public configuration: ParseOptions;

  /**
   * The external node factory.
   *
   * @type {MmlFactory}
   */
  protected mmlFactory: MmlFactory = null;

  /**
   * The factory table populated with some default methods.
   */
  private factory: { [kind: string]: NodeFactoryMethod } = {
    node: NodeFactory.createNode,
    token: NodeFactory.createToken,
    text: NodeFactory.createText,
    error: NodeFactory.createError,
  };

  /**
   * Default node generation function.
   *
   * @param {NodeFactory} factory The current node factory.
   * @param {string} kind The type of node to create.
   * @param {MmlNode[]} children Its children.
   * @param {any=} def Its properties.
   * @param {TextNode=} text An optional text node if this is a token.
   * @returns {MmlNode} The newly created Mml node.
   */
  public static createNode(
    factory: NodeFactory,
    kind: string,
    children: MmlNode[] = [],
    def: any = {},
    text?: TextNode
  ): MmlNode {
    const node = factory.mmlFactory.create(kind);
    node.setChildren(children);
    if (text) {
      node.appendChild(text);
    }
    NodeUtil.setProperties(node, def);
    return node;
  }

  /**
   * Default token generation function.
   *
   * @param {NodeFactory} factory The current node factory.
   * @param {string} kind The type of node to create.
   * @param {any} def Its properties.
   * @param {string} text Text of the token.
   * @returns {MmlNode} The newly created token node.
   */
  public static createToken(
    factory: NodeFactory,
    kind: string,
    def: any = {},
    text: string = ''
  ): MmlNode {
    const textNode = factory.create('text', text);
    return factory.create('node', kind, [], def, textNode);
  }

  /**
   * Default text node generation function.
   *
   * @param {NodeFactory} factory The current node factory.
   * @param {string} text The text for the new node.
   * @returns {TextNode} The newly created text node.
   */
  public static createText(factory: NodeFactory, text: string): TextNode {
    if (text == null) {
      return null;
    }
    return (factory.mmlFactory.create('text') as TextNode).setText(text);
  }

  /**
   * Default error node generation function.
   *
   * @param {NodeFactory} factory The current node factory.
   * @param {string} message The error message.
   * @returns {MmlNode} The newly created error node.
   */
  public static createError(factory: NodeFactory, message: string): MmlNode {
    const text = factory.create('text', message);
    const mtext = factory.create('node', 'mtext', [], {}, text);
    const error = factory.create('node', 'merror', [mtext], {
      'data-mjx-error': message,
    });
    return error;
  }

  /**
   * @param {MmlFactory} mmlFactory   The MmlFactory for the TeX jax to use
   */
  public setMmlFactory(mmlFactory: MmlFactory) {
    this.mmlFactory = mmlFactory;
  }

  /**
   * Adds a method to the factory.
   *
   * @param {string} kind The type of node the method creates.
   * @param {NodeFactoryMethod} func The node creator.
   */
  public set(kind: string, func: NodeFactoryMethod) {
    this.factory[kind] = func;
  }

  /**
   * Adds a set of node creators to the factory.
   *
   * @param {{ [kind: string]: NodeFactoryMethod }} maps The set of functions.
   */
  public setCreators(maps: { [kind: string]: NodeFactoryMethod }) {
    for (const kind in maps) {
      this.set(kind, maps[kind]);
    }
  }

  /**
   * Creates a node for the internal data structure from the factory.
   *
   * @param {string} kind The type of node to be created.
   * @param {any[]} rest The arguments for the node.
   * @returns {MmlNode} The created node.
   */
  public create(kind: string, ...rest: any[]): MmlNode {
    const func = this.factory[kind] || this.factory['node'];
    const node = func(this, rest[0], ...rest.slice(1));
    if (kind === 'node') {
      this.configuration.addNode(rest[0], node);
    }
    return node;
  }

  /**
   * Gets the method for generating a node of given kind.
   *
   * @param {string} kind The kind of node.
   * @returns {NodeFactoryMethod} The method to generate that node type.
   */
  public get(kind: string): NodeFactoryMethod {
    return this.factory[kind];
  }
}
