import type { DId } from '../../utils/types';
import type { TreeNodeStatus, TreeOrigin } from './abstract-node';

import { AbstractTreeNode, CHECKED, UNCHECKED, INDETERMINATE } from './abstract-node';

export class MultipleTreeNode<ID extends DId, T extends TreeOrigin> extends AbstractTreeNode<ID, T> {
  children?: MultipleTreeNode<ID, T>[];

  protected _id: ID;
  protected _status!: TreeNodeStatus;
  protected _disabled: boolean;

  constructor(
    origin: T,
    private getId: (origin: T) => ID,
    private opts: {
      disabled?: boolean;
    }
  ) {
    super(origin);
    this._id = getId(origin);
    this._disabled = !!(opts.disabled || origin.disabled);

    this._setUpChildren();
  }

  changeStatus(status: TreeNodeStatus, checkeds: Set<ID>): Set<ID> {
    const newCheckeds = this._changeStatus(status, checkeds);

    return newCheckeds;
  }

  protected override _calculateStatus(checkeds: Set<ID>): void {
    if (this.children) {
      let checkedNum = 0;
      let hasIndeterminate = false;
      let enabledLength = 0;
      for (const child of this.children) {
        if (child.enabled) {
          enabledLength += 1;
        }
        if (child.checked) {
          checkedNum += 1;
        } else if (child.enabled && child.indeterminate) {
          hasIndeterminate = true;
        }
      }
      this._status = hasIndeterminate
        ? INDETERMINATE
        : checkedNum === 0
        ? UNCHECKED
        : checkedNum === enabledLength
        ? CHECKED
        : INDETERMINATE;
    } else {
      this._status = checkeds.has(this._id) ? CHECKED : UNCHECKED;
    }
  }

  protected _changeStatus(status: TreeNodeStatus, initValue: Set<ID>): Set<ID> {
    let res = new Set(initValue);
    if (this.enabled && this._status !== status) {
      this._status = status;
      if (this.children) {
        this.children.forEach((child) => {
          res = child._changeStatus(status, res);
        });
      } else {
        if (status === CHECKED) {
          res.add(this._id);
        } else {
          res.delete(this._id);
        }
      }
    }

    return res;
  }

  private _setUpChildren(): void {
    if (this.origin.children) {
      this.children = this.origin.children.map((child) => {
        const node = new MultipleTreeNode(
          child as T,
          this.getId,
          Object.assign(this.opts, {
            disabled: this._disabled,
          })
        );
        node._setParent(this);
        return node;
      });
    }
  }
}