/*************************************************************
 *
 *  Copyright (c) 2021-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 colortbl package.
 *
 * @author dpvc@mathjax.org (Davide P. Cervone)
 */

import { HandlerType, ConfigurationType } from '../HandlerTypes.js';
import { ArrayItem } from '../base/BaseItems.js';
import {
  Configuration,
  ParserConfiguration,
  ConfigurationHandler,
} from '../Configuration.js';
import { CommandMap } from '../TokenMap.js';
import TexParser from '../TexParser.js';
import TexError from '../TexError.js';

import { TeX } from '../../tex.js';

/**
 * Information about table colors.
 */
export interface ColorData {
  cell: string;
  row: string;
  col: string[];
}

//
//  Sublcass the ArrayItem to handle colored entries
//
export class ColorArrayItem extends ArrayItem {
  /**
   *  Store current color for cell, row, and columns.
   */
  public color: ColorData = {
    cell: '',
    row: '',
    col: [],
  };

  /**
   * True if any cell is colored (we will make sure the edge cells are full sized).
   */
  public hasColor: boolean = false;

  /**
   * @override
   */
  public EndEntry() {
    super.EndEntry();
    const cell = this.row[this.row.length - 1];
    const color =
      this.color.cell || this.color.row || this.color.col[this.row.length - 1];
    if (color) {
      cell.attributes.set('mathbackground', color);
      this.color.cell = '';
      this.hasColor = true;
    }
  }

  /**
   * @override
   */
  public EndRow() {
    super.EndRow();
    this.color.row = '';
  }

  /**
   * @override
   */
  public createMml() {
    //
    // If there is any color in the array, give it an empty frame,
    //   if there isn't one already.  This will make sure the color
    //   in edge cells extends past their contents.
    //
    const mml = super.createMml();
    let table = mml.isKind('mrow') ? mml.childNodes[1] : mml;
    if (table.isKind('mstyle')) {
      table = table.childNodes[0].childNodes[0];
    }
    if (table.isKind('menclose')) {
      table = table.childNodes[0].childNodes[0];
    }
    if (this.hasColor) {
      const attributes = table.attributes;
      if (
        attributes.get('frame') === 'none' &&
        attributes.get('data-frame-styles') === undefined
      ) {
        attributes.set('data-frame-styles', '');
      }
    }
    return mml;
  }
}

/**
 * Add color to a column, row, or cell.
 *
 * @param {TexParser} parser       The active TeX parser
 * @param {string} name            The name of the macro that is being processed
 * @param {keyof ColorData} type   The type (col, row, cell) of color being added
 */
function TableColor(parser: TexParser, name: string, type: keyof ColorData) {
  const lookup = parser.configuration.packageData.get('color').model; // use the color extension's color model
  const model = parser.GetBrackets(name, '');
  const color = lookup.getColor(model, parser.GetArgument(name));
  //
  // Check that we are in a colorable array.
  //
  const top = parser.stack.Top() as ColorArrayItem;
  if (!(top instanceof ColorArrayItem)) {
    throw new TexError(
      'UnsupportedTableColor',
      'Unsupported use of %1',
      parser.currentCS
    );
  }
  //
  //  Check the position of the macro and save the color.
  //
  if (type === 'col') {
    if (top.table.length && top.color.col[top.row.length] !== color) {
      throw new TexError(
        'ColumnColorNotTop',
        '%1 must be in the top row or preamble',
        name
      );
    }
    top.color.col[top.row.length] = color;
    //
    // Ignore the left and right overlap options.
    //
    if (parser.GetBrackets(name, '')) {
      parser.GetBrackets(name, '');
    }
  } else {
    top.color[type] = color;
    if (type === 'row' && (top.Size() || top.row.length)) {
      throw new TexError(
        'RowColorNotFirst',
        '%1 must be at the beginning of a row',
        name
      );
    }
  }
}

//
//  Define macros for table coloring.
//
new CommandMap('colortbl', {
  cellcolor: [TableColor, 'cell'],
  rowcolor: [TableColor, 'row'],
  columncolor: [TableColor, 'col'],
});

/**
 * The configuration function for colortbl.
 *
 * @param {ParserConfiguration} config   The configuration being used.
 * @param {TeX} jax                      The TeX jax using this configuration.
 */
const config = function (config: ParserConfiguration, jax: TeX<any, any, any>) {
  //
  //  Make sure color is configured.  (It doesn't have to be included in tex.packages)
  //
  if (!jax.parseOptions.packageData.has('color')) {
    ConfigurationHandler.get('color').config(config, jax);
  }
};

//
//  Create the color-table configuration.
//
// Use priority 10 to make sure we are processed after the base package
// (to override its array and config)
//
export const ColortblConfiguration = Configuration.create('colortbl', {
  [ConfigurationType.HANDLER]: { [HandlerType.MACRO]: ['colortbl'] },
  [ConfigurationType.ITEMS]: { array: ColorArrayItem }, // overrides original array class
  [ConfigurationType.PRIORITY]: 10,
  [ConfigurationType.CONFIG]: [config, 10],
});
