/*
 * Copyright (C) 2021 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/>.
 */

/* eslint-disable @typescript-eslint/no-explicit-any */
import {
  ChangeDetectorRef,
  Component,
  Input,
  OnDestroy,
  OnInit,
} from '@angular/core';
import {ModalController, PopoverController} from '@ionic/angular';
import {SCDateSeries} from '@openstapps/core';
import {
  difference,
  every,
  flatMap,
  groupBy,
  mapValues,
  some,
  sortBy,
  union,
  values,
} from 'lodash-es';
import {capitalize, last} from 'lodash-es';
import {Subscription} from 'rxjs';
import {
  DateSeriesRelevantData,
  ScheduleProvider,
  toDateSeriesRelevantData,
} from '../../calendar/schedule.provider';
import {CalendarService} from '../../calendar/calendar.service';
import {AddEventReviewModalComponent} from '../../calendar/add-event-review-modal.component';
import {ThingTranslatePipe} from '../../../translation/thing-translate.pipe';

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

/**
 * A tree
 *
 * The generic is to preserve type safety of how deep the tree goes.
 */
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 = every(selections, it => it === Selection.ON);
    this.indeterminate = this.checked
      ? false
      : some(selections, 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;
        // tslint:disable-next-line:no-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();
  }
}

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

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

/**
 * Shows a horizontal list of action chips
 */
@Component({
  selector: 'stapps-add-event-popover-component',
  templateUrl: 'add-event-popover.html',
  styleUrls: ['add-event-popover.scss'],
})
export class AddEventPopoverComponent implements OnInit, OnDestroy {
  /**
   * Lodash alias
   */
  capitalize: (item: string) => string = capitalize;

  /**
   * The item the action belongs to
   */
  @Input() items: SCDateSeries[];

  /**
   * Lodash alias
   */
  last: <T>(item: T[] | null | undefined) => T | undefined = last;

  /**
   * Selection of the item
   */
  selection: TreeNode<TreeNode<SelectionValue>>;

  /**
   * Uuids
   */
  partialDateSeries: DateSeriesRelevantData[];

  /**
   * Uuid Subscription
   */
  uuidSubscription: Subscription;

  constructor(
    readonly ref: ChangeDetectorRef,
    readonly scheduleProvider: ScheduleProvider,
    readonly popoverController: PopoverController,
    readonly calendar: CalendarService,
    readonly modalController: ModalController,
    readonly thingTranslatePipe: ThingTranslatePipe,
  ) {}

  /**
   * Destroy
   */
  ngOnDestroy() {
    this.uuidSubscription.unsubscribe();
  }

  /**
   * Init
   */
  ngOnInit() {
    this.uuidSubscription = this.scheduleProvider.partialEvents$.subscribe(
      async result => {
        this.partialDateSeries = result;

        this.selection = new TreeNode(
          values(
            groupBy(
              sortBy(
                this.items.map(item => ({
                  selected: this.partialDateSeries.some(
                    it => it.uid === item.uid,
                  ),
                  item: item,
                })),
                it => it.item.repeatFrequency,
              ),
              it => it.item.repeatFrequency,
            ),
          ).map(item => new TreeNode(item, this.ref)),
          this.ref,
        );
      },
    );
  }

  getSelection(): {
    selected: DateSeriesRelevantData[];
    unselected: DateSeriesRelevantData[];
  } {
    const selection = mapValues(
      groupBy(flatMap(this.selection.children, 'children'), 'selected'),
      value => value.map(it => toDateSeriesRelevantData(it.item)),
    );

    return {selected: selection.true ?? [], unselected: selection.false ?? []};
  }

  async export() {
    const modal = await this.modalController.create({
      component: AddEventReviewModalComponent,
      swipeToClose: true,
      cssClass: 'add-modal',
      componentProps: {
        dismissAction: () => {
          modal.dismiss();
        },
        dateSeries: this.items,
      },
    });

    await modal.present();
    await modal.onWillDismiss();
  }

  /**
   * On selection change
   */
  async onCommit(save: boolean) {
    if (save) {
      const {selected, unselected} = this.getSelection();
      console.log(selected, unselected);
      this.scheduleProvider.partialEvents$.next(
        union(difference(this.partialDateSeries, unselected), selected),
      );
    }

    await this.popoverController.dismiss();
  }
}
