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

import { CheckType, BaseItem, StackItem } from '../StackItem.js';
import { ParseUtil } from '../ParseUtil.js';
import NodeUtil from '../NodeUtil.js';
import TexParser from '../TexParser.js';
import { AbstractMmlTokenNode } from '../../../core/MmlTree/MmlNode.js';

export class AutoOpen extends BaseItem {
  /**
   * @override
   */
  protected static errors = Object.assign(Object.create(BaseItem.errors), {
    stop: ['ExtraOrMissingDelims', 'Extra open or missing close delimiter'],
  });

  /**
   * The number of unpaired open delimiters that need to be matched before
   *   a close delimiter will close this item. (#2831)
   */
  public openCount: number = 0;

  /**
   * @override
   */
  public get kind() {
    return 'auto open';
  }

  /**
   * @override
   */
  get isOpen() {
    return true;
  }

  /**
   * @override
   */
  public toMml(inferred: boolean = true, forceRow?: boolean) {
    if (!inferred) {
      //
      // When toMml() is being called from processing an \over item, we don't want to
      // add the delimiters, so just do the super toMml() method. (Issue #3000)
      //
      return super.toMml(inferred, forceRow);
    }
    // Smash and right/left
    const parser = this.factory.configuration.parser;
    const right = this.getProperty('right') as string;
    if (this.getProperty('smash')) {
      const mml = super.toMml();
      const smash = parser.create('node', 'mpadded', [mml], {
        height: 0,
        depth: 0,
      });
      this.Clear();
      this.Push(parser.create('node', 'TeXAtom', [smash]));
    }
    if (right) {
      this.Push(
        new TexParser(right, parser.stack.env, parser.configuration).mml()
      );
    }
    const mml = ParseUtil.fenced(
      this.factory.configuration,
      this.getProperty('open') as string,
      super.toMml(),
      this.getProperty('close') as string,
      this.getProperty('big') as string
    );
    //
    //  Remove fence markers that would cause it to be TeX class INNER,
    //  so it is treated as a regular mrow when setting the tex class (#2760)
    //
    NodeUtil.removeProperties(mml, 'open', 'close', 'texClass');
    return mml;
  }

  /**
   * Test whether a fence is a closing one for this item,
   * decrementing the open count if appropriate.
   *
   * @param {string} fence The fence character.
   * @returns {boolean} True if the fence is the appropriate closing one.
   */
  public closing(fence: string): boolean {
    return fence === this.getProperty('close') && !this.openCount--;
  }

  /**
   * @override
   */
  public checkItem(item: StackItem): CheckType {
    //
    // If we are closing \over items, we are done
    //
    if (item.getProperty('pre-autoclose')) {
      return BaseItem.fail;
    }
    //
    // If this is the closing fence, produce the proper output
    //
    if (item.getProperty('autoclose')) {
      if (this.getProperty('ignore')) {
        this.Clear();
        return [[], true];
      }
      return [[this.toMml()], true];
    }
    //
    //  Check for nested open delimiters (#2831)
    //
    if (item.isKind('mml') && item.Size() === 1) {
      const mml = item.toMml();
      if (
        mml.isKind('mo') &&
        (mml as AbstractMmlTokenNode).getText() === this.getProperty('open')
      ) {
        this.openCount++;
      }
    }
    //
    // Do the default check
    //
    return super.checkItem(item);
  }
}
