import TableSelection, {type CloneWithConfig, type SelectionItem, type TableSelectionConfig} from '../table/selection';

interface DataListSelectionConfig<T extends SelectionItem> extends TableSelectionConfig<T> {
  partialSelected?: Set<T> | undefined;
}

interface DataListCloneWithConfig<T> extends CloneWithConfig<T> {
  partialSelected?: Set<T> | undefined;
}

export default class Selection<T extends SelectionItem> extends TableSelection<T> {
  protected _partialSelected: Set<T>;

  constructor({partialSelected = new Set(), ...props}: DataListSelectionConfig<T>) {
    super(props);
    this._partialSelected = partialSelected;
  }

  protected _buildData(data: T[]) {
    return new Set(this._getDescendants(data));
  }

  protected _buildSelected(data: Set<T>, selected: Set<T>) {
    // eslint-disable-next-line no-underscore-dangle
    const _selected = new Set(selected);

    [...data].forEach(item => {
      if (_selected.has(item)) {
        this._selectDescendants(item, _selected, this._partialSelected);
      }
    });

    return _selected;
  }

  private _getDescendants(items: readonly T[]) {
    let result: T[] = [];

    items.forEach(item => {
      result.push(item);
      result = [...result, ...this._getDescendants(this._getChildren(item))];
    });

    return result;
  }

  private _getAncestors(item: T) {
    let result: T[] = [];

    const parent = [...this._data].find(it => this._getChildren(it).includes(item));
    if (parent) {
      result = [parent, ...this._getAncestors(parent)];
    }

    return result;
  }

  private _selectDescendants(item: T, selected: Set<T>, partialSelected: Set<T>) {
    this._getDescendants(this._getChildren(item)).forEach(it => {
      selected.add(it);
      partialSelected.delete(it);
    });
  }

  private _deselectDescendants(item: T, selected: Set<T>, partialSelected: Set<T>) {
    this._getDescendants(this._getChildren(item)).forEach(it => {
      selected.delete(it);
      partialSelected.delete(it);
    });
  }

  private _selectAncestors(item: T, selected: Set<T>, partialSelected: Set<T>) {
    this._getAncestors(item).forEach(ancestor => {
      const groupIsSelected = this._getChildren(ancestor)
        .filter(it => this._isItemSelectable(it))
        .every(it => selected.has(it));
      const groupIsPartialSelected = this._getChildren(ancestor)
        .filter(it => this._isItemSelectable(it))
        .some(it => selected.has(it) || partialSelected.has(it));

      if (groupIsSelected) {
        selected.add(ancestor);
        partialSelected.delete(ancestor);
      } else if (groupIsPartialSelected) {
        partialSelected.add(ancestor);
      }
    });
  }

  private _deselectAncestors(item: T, selected: Set<T>, partialSelected: Set<T>) {
    this._getAncestors(item).forEach(ancestor => {
      const groupIsPartialSelected = this._getChildren(ancestor)
        .filter(it => this._isItemSelectable(it))
        .filter(it => it !== item)
        .some(it => selected.has(it) || partialSelected.has(it));

      if (groupIsPartialSelected) {
        partialSelected.add(ancestor);
      } else {
        partialSelected.delete(ancestor);
      }
      selected.delete(ancestor);
    });
  }

  select(value = this._focused): Selection<T> {
    if (!value || !this._isItemSelectable(value)) {
      return this;
    }

    const selected = new Set(this._selected);
    const partialSelected = new Set(this._partialSelected);
    selected.add(value);
    partialSelected.delete(value);

    this._selectDescendants(value, selected, partialSelected);
    this._selectAncestors(value, selected, partialSelected);

    return this.cloneWith({selected, partialSelected});
  }

  isPartialSelected(value: T | null): boolean {
    return value !== null && value !== undefined && this._partialSelected.has(value);
  }

  focus(value: T | null | undefined) {
    return super.focus(value) as Selection<T>;
  }

  resetSelection() {
    return super.resetSelection() as Selection<T>;
  }

  cloneWith({partialSelected = this._partialSelected, ...rest}: DataListCloneWithConfig<T>): Selection<T> {
    const parentClone = super.cloneWith(rest) as Selection<T>;

    return new (this.constructor as typeof Selection)({
      // eslint-disable-next-line no-underscore-dangle
      data: parentClone._rawData,
      // eslint-disable-next-line no-underscore-dangle
      selected: parentClone._selected,
      // eslint-disable-next-line no-underscore-dangle
      focused: parentClone._focused,
      // eslint-disable-next-line no-underscore-dangle
      getKey: parentClone._getKey,
      // eslint-disable-next-line no-underscore-dangle
      getChildren: parentClone._getChildren,
      // eslint-disable-next-line no-underscore-dangle
      isItemSelectable: parentClone._isItemSelectable,
      partialSelected,
    });
  }

  deselect(value = this._focused) {
    if (!value || !this._isItemSelectable(value)) {
      return this;
    }

    const selected = new Set(this._selected);
    const partialSelected = new Set(this._partialSelected);
    selected.delete(value);
    partialSelected.delete(value);

    this._deselectDescendants(value, selected, partialSelected);
    this._deselectAncestors(value, selected, partialSelected);

    return this.cloneWith({selected, partialSelected});
  }
}
