/*************************************************************
 *
 *  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 parsing the braket package.
 *
 * @author v.sorge@mathjax.org (Volker Sorge)
 */

import { CheckType, BaseItem, StackItem } from '../StackItem.js';
import { TEXCLASS, MmlNode } from '../../../core/MmlTree/MmlNode.js';
import { ParseUtil } from '../ParseUtil.js';
import { MATHSPACE, em } from '../../../util/lengths.js';

const THINSPACE = em(MATHSPACE.thinmathspace);

/**
 * A bra-ket command. Collates elements from the opening brace to the closing
 * brace, adding bars to a given maximal number (e.g., only one in case of
 * set). To finalise it adds the surrounding angle brackets or braces.
 */
export class BraketItem extends BaseItem {
  /**
   * @override
   */
  get kind() {
    return 'braket';
  }

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

  /**
   * The MathML nodes that appear before the latest bar (so that \over can be handled properly).
   */
  public barNodes: MmlNode[] = [];

  /**
   * @override
   */
  public checkItem(item: StackItem): CheckType {
    if (item.isKind('close')) {
      if (item.getProperty('braketbar')) {
        this.barNodes.push(...super.toMml(true, true).childNodes);
        this.Clear();
        return BaseItem.fail;
      }
      return [[this.factory.create('mml', this.toMml())], true];
    }
    if (item.isKind('mml')) {
      this.Push(item.toMml());
      if (this.getProperty('single')) {
        return [[this.toMml()], true];
      }
      return BaseItem.fail;
    }
    return super.checkItem(item);
  }

  /**
   * @override
   */
  public toMml(inferred: boolean = true, forceRow?: boolean) {
    let inner = super.toMml(inferred, forceRow);
    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 inner;
    }
    const open = this.getProperty('open') as string;
    const close = this.getProperty('close') as string;
    //
    // Add any saved bar nodes
    //
    if (this.barNodes.length) {
      inner = this.create('node', 'inferredMrow', [...this.barNodes, inner]);
    }
    if (this.getProperty('stretchy')) {
      //
      //  Add spacing, if requested
      //
      if (this.getProperty('space')) {
        inner = this.create('node', 'inferredMrow', [
          this.create('token', 'mspace', { width: THINSPACE }),
          inner,
          this.create('token', 'mspace', { width: THINSPACE }),
        ]);
      }
      return ParseUtil.fenced(this.factory.configuration, open, inner, close);
    }
    const attrs = {
      fence: true,
      stretchy: false,
      symmetric: true,
      texClass: TEXCLASS.OPEN,
    };
    const openNode = this.create('token', 'mo', attrs, open);
    attrs.texClass = TEXCLASS.CLOSE;
    const closeNode = this.create('token', 'mo', attrs, close);
    const mrow = this.create('node', 'mrow', [openNode, inner, closeNode], {
      open: open,
      close: close,
    });
    return mrow;
  }
}
