import { isVegaTableColumnGroupingModelType } from '../../../../../types/type-guard';
/**
 * Represents the hierarchical structure of table columns (including group columns).
 *
 * Responsibilities:
 * - Build the tree from flat or grouped column configs (.from / .fromGroupColumns)
 * - Provide traversal helpers (iterator / dfs) with caching
 * - Provide utility checks (isLeaf, hasGroupColumn, hasPinnedColumn)
 * - Produce leaf column configs for rendering (generateColumnConfigByLeaves)
 * - Order root children by pinned type for rendering (orderChildrenByPinned)
 */
export class ColumnStructureModel {
  /**
   * Constructor for ColumnStructureModel.
   *
   * @param {string} key - Unique key for the column.
   * @param {number} level - Row level of this column (root uses ROOT_LEVEL).
   * @param {ColumnStructureModel} [parent] - Optional parent node.
   * @param {string} [label] - Optional display label for the column.
   * @param {VegaTablePinnedType} [pinned] - Optional pinned metadata ('left'|'right'|null).
   */
  constructor(key, level, parent, label, pinned) {
    this.key = key;
    this.level = level;
    this.parent = parent;
    this.label = label;
    this.pinned = pinned;
    this.leaves = [];
    this.orderedChildren = [];
    this.iteratorCacheMap = new Map();
  }
  /**
   * Creates the root ColumnStructureModel from column configs.
   *
   * @param {VegaTableColumnConfig[]} columnsConfig - Array of column configs.
   * @param {Nullable<VegaTableGroupColumnConfig[]>} groupColumnConfig - Optional grouping configuration.
   * @returns {ColumnStructureModel} Root column structure model.
   */
  static from(columnsConfig, groupColumnConfig) {
    const rootModel = new ColumnStructureModel(this.ROOT_KEY, ColumnStructureModel.ROOT_LEVEL);
    if (groupColumnConfig) {
      rootModel.children = this.fromGroupColumns(rootModel, groupColumnConfig);
      return rootModel;
    }
    else if (columnsConfig) {
      rootModel.children = columnsConfig.map((column) => new ColumnStructureModel(column.key || column.prop || column.label, 0, rootModel, null, column.pinned));
      return rootModel;
    }
    else {
      rootModel.children = [];
      return rootModel;
    }
  }
  /**
   * Recursively creates ColumnStructureModel children from group column configs.
   *
   * @param {ColumnStructureModel} parent - Parent node for the created children.
   * @param {VegaTableGroupColumnConfig[]} groupColumns - Array of group column configs.
   * @param {number} level - Current row level.
   * @returns {ColumnStructureModel[]} Array of child column structure models.
   */
  static fromGroupColumns(parent, groupColumns, level = 0) {
    if (groupColumns && groupColumns.length > 0) {
      return groupColumns.map((groupColumn) => {
        if (isVegaTableColumnGroupingModelType(groupColumn)) {
          const current = new ColumnStructureModel(groupColumn.key, level, parent, groupColumn.label, groupColumn.pinned);
          current.children = this.fromGroupColumns(current, groupColumn.children, level + 1);
          return current;
        }
        else {
          return new ColumnStructureModel(groupColumn.key, level, parent);
        }
      });
    }
    return [];
  }
  /**
   * Generates an array of VegaTableColumnConfig for all leaf columns.
   *
   * @param {VegaTableColumnConfig[]} columns - Array of all column configs.
   * @returns {VegaTableColumnConfig[]} Array of configs for leaf columns.
   */
  generateColumnConfigByLeaves(columns) {
    return this.getLeaves().map((leaf) => columns.find((column) => column.key === leaf.key || column.prop === leaf.key || column.label === leaf.key));
  }
  /**
   * Compute the maximum depth (highest level value) under this node.
   * This is useful to determine the number of header rows required for rendering.
   *
   * @returns {number} Maximum level found in the subtree (or 0 when empty).
   */
  getMaxDepth() {
    const models = this.dfsIterator();
    if (models.length === 0) {
      return 0;
    }
    return Math.max(...models.map((model) => model.level)) + 1;
  }
  /**
   * Checks if this subtree contains any group (non-leaf) columns.
   *
   * @returns {boolean} True if any descendant is a group column.
   */
  hasGroupColumn() {
    return !!this.children.find((model) => !model.isLeaf());
  }
  /**
   * Checks if this subtree contains any left pinned children.
   *
   * @returns {boolean} True if any immediate child has a pinned value.
   */
  hasLeftPinnedColumn() {
    return !!this.children.find((model) => model.pinned === 'left');
  }
  /**
   * Checks if this subtree contains any right pinned children.
   *
   * @returns {boolean} True if any immediate child has a pinned value.
   */
  hasRightPinnedColumn() {
    return !!this.children.find((model) => model.pinned === 'right');
  }
  /**
   * Checks if this node is a leaf (no children).
   *
   * @returns {boolean} True when this node has no children.
   */
  isLeaf() {
    return !this.children || this.children.length === 0;
  }
  /**
   * Return an array of nodes for this subtree using the specified traversal mode.
   *
   * Currently only depth-first search ('dfs') is supported. The returned array is
   * cached per-mode to avoid repeated traversal costs.
   *
   * @param {IteratorMode} mode - Traversal mode to use. Defaults to 'dfs'.
   * @returns {ColumnStructureModel[]} Array of ColumnStructureModel nodes in traversal order.
   */
  iterator(mode = 'dfs') {
    if (mode === 'dfs') {
      return this.dfsIterator();
    }
    return [];
  }
  /**
   * Gets all leaf nodes in the subtree, with caching.
   *
   * Subsequent calls return the cached array until the underlying model is rebuilt.
   *
   * @returns {ColumnStructureModel[]} Array of leaf column models.
   */
  getLeaves() {
    if (this.leaves.length > 0) {
      return this.leaves;
    }
    const leaves = this.dfsIterator().filter((model) => model.isLeaf());
    this.leaves = leaves;
    return leaves;
  }
  /**
   * For root nodes, returns children ordered by pinned type (left, none, right).
   * For non-root nodes, returns children in their natural order.
   *
   * @returns {ColumnStructureModel[]} Array of child column models.
   */
  getOrderedChildren() {
    if (!this.isRoot())
      return this.children;
    return this.orderedChildren;
  }
  /**
   * Orders root children by pinned type (left, none, right) and caches the result.
   *
   * For non-root nodes this method returns children in their natural order.
   *
   * @returns {ColumnStructureModel[]} Array of ordered child column models.
   */
  orderChildrenByPinned() {
    if (!this.isRoot())
      return this.children;
    if (this.orderedChildren.length > 0) {
      return this.orderedChildren;
    }
    const leftPinned = [];
    const noPinned = [];
    const rightPinned = [];
    this.children.forEach((child) => {
      if (child.pinned === 'left') {
        leftPinned.push(child);
      }
      else if (child.pinned === 'right') {
        rightPinned.push(child);
      }
      else {
        noPinned.push(child);
      }
    });
    const children = [...leftPinned, ...noPinned, ...rightPinned];
    this.orderedChildren = children;
    return children;
  }
  /**
   * Checks if this node is the root node.
   *
   * @returns {boolean} True if root node, false otherwise.
   */
  isRoot() {
    return this.level === ColumnStructureModel.ROOT_LEVEL;
  }
  /**
   * Performs a depth-first traversal of the column structure.
   *
   * The traversal respects root-child pinned ordering (left/no/right) for the root node.
   * Results are cached per-node to avoid repeated recursion costs.
   *
   * @returns {ColumnStructureModel[]} Array of all nodes in DFS order.
   */
  dfsIterator() {
    if (this.iteratorCacheMap.has('dfs')) {
      return this.iteratorCacheMap.get('dfs');
    }
    const localResult = [];
    const children = this.orderChildrenByPinned();
    if (!this.isRoot()) {
      localResult.push(this);
    }
    if (children && children.length > 0) {
      children.forEach((child) => {
        localResult.push(...child.dfsIterator());
      });
    }
    this.iteratorCacheMap.set('dfs', localResult);
    return localResult;
  }
}
ColumnStructureModel.ROOT_LEVEL = -1;
ColumnStructureModel.ROOT_KEY = '#root-model#';
