/*
 * Copyright (C) 2022 StApps
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <https://www.gnu.org/licenses/>.
 */

import {ChangeDetectorRef} from '@angular/core';
import {SCDateSeries} from '@openstapps/core';

export enum Selection {
  ON = 2,
  PARTIAL = 1,
  OFF = 0,
}

/**
 * A tree
 *
 * The generic is to preserve type safety of how deep the tree goes.
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export class TreeNode<T extends TreeNode<any> | SelectionValue> {
  /**
   * Value of this node
   */
  checked: boolean;

  /**
   * If items are partially selected
   */
  indeterminate: boolean;

  /**
   * Parent of this node
   */
  parent?: TreeNode<TreeNode<T>>;

  constructor(
    readonly children: T[],
    readonly ref: ChangeDetectorRef,
  ) {
    this.updateParents();
    this.accumulateApplyValues();
  }

  /**
   * Accumulate values of children to set current value
   */
  private accumulateApplyValues() {
    const selections: number[] = this.children.map(it =>
      it instanceof TreeNode
        ? it.checked
          ? Selection.ON
          : it.indeterminate
            ? Selection.PARTIAL
            : Selection.OFF
        : (it as SelectionValue).selected
          ? Selection.ON
          : Selection.OFF,
    );

    this.checked = selections.every(it => it === Selection.ON);
    this.indeterminate = this.checked ? false : selections.some(it => it > Selection.OFF);
  }

  /**
   * Apply the value of this node to all child nodes
   */
  private applyValueDownwards() {
    for (const child of this.children) {
      if (child instanceof TreeNode) {
        child.checked = this.checked;
        child.indeterminate = false;
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (child as TreeNode<any>).applyValueDownwards();
      } else {
        (child as SelectionValue).selected = this.checked;
      }
    }
  }

  /**
   * Set all children's parent to this
   */
  private updateParents() {
    for (const child of this.children) {
      if (child instanceof TreeNode) {
        child.parent = this as TreeNode<TreeNode<T>>;
      }
    }
  }

  /**
   * Update values to all parents upwards
   */
  private updateValueUpwards() {
    this.parent?.accumulateApplyValues();
    this.parent?.updateValueUpwards();
  }

  /**
   * Click on this node
   */
  click() {
    this.checked = !this.checked;
    this.indeterminate = false;
    this.applyValueDownwards();
    this.updateValueUpwards();
  }

  /**
   * Notify that a child's value has changed
   */
  notifyChildChanged() {
    this.accumulateApplyValues();
    this.updateValueUpwards();
  }
}

export interface SelectionValue {
  /**
   * Item that was selected
   */
  item: SCDateSeries;

  /**
   * Selection
   */
  selected: boolean;
}
