/*
 * Copyright (C) 2018 Robert Bosch GmbH Copyright (C) 2018 Robert Bosch Manufacturing Solutions GmbH, Germany. All rights reserved.
 */

import {
  AfterContentInit,
  Component,
  ContentChild,
  EventEmitter,
  Input,
  OnChanges,
  OnDestroy,
  OnInit,
  Output,
  ViewChild,
  AfterViewInit,
  SimpleChanges,
} from '@angular/core';
import { ModalWindowService } from '@bci-web-core/core';
import { Subscription, Subject } from 'rxjs';
import { PinboardConfig } from '../models/pinboard-config';
import { UserSettingChangeEvent } from '../models/user-setting-change-event';
import { Widget } from '../models/widget';
import { WidgetContentComponent } from '../models/widget-content-component';
import { WidgetDefinition } from '../models/widget-definition';
import { PinboardGridComponent } from '../pinboard-grid/pinboard-grid.component';
import { PinboardI18nService } from '../services/pinboard-i18n.service';
import { PinboardInteractionService } from '../services/pinboard-interaction.service';
import { WidgetCatalogService } from '../services/widget-catalog.service';
import { WidgetDefinitionService } from '../services/widget-definition.service';
import { WidgetCatalogComponent } from '../widget-catalog/widget-catalog.component';
import { WidgetSettingsFormComponent } from '../widget-settings/widget-settings-form/widget-settings-form.component';
import { WidgetSettingsComponent } from '../widget-settings/widget-settings.component';
import { LeavingApiService } from '../services/leaving-api.service';
import { takeUntil } from 'rxjs/operators';
import { MatTabGroup, MatTab, MatTabHeader } from '@angular/material/tabs';
import { PinboardFilterBarComponent } from '../pinboard-filter-bar/pinboard-filter-bar.component';

const tabSwitch = {
  SAVE: true,
  DONTSAVE: false,
};

@Component({
  selector: 'bci-pinboard',
  templateUrl: './pinboard.component.html',
  styleUrls: ['./pinboard.component.scss'],
  providers: [],
})
export class PinboardComponent implements OnInit, AfterContentInit, OnDestroy, OnChanges, AfterViewInit {
  widgetAddSubscription: Subscription;

  @Input() canAdd = true;
  @Input() dashboards: PinboardConfig[] = [];
  /**
   * Only invoked if a saved dashboard is opened. If a dashboard in edit mode is opened, then this is not invoked.
   */
  @Output() dashboardOpened = new EventEmitter<PinboardConfig>();
  @Output() dashboardSaved = new EventEmitter<PinboardConfig>();
  /**
   * Invoked every time a dashboard is loaded, so even if the edit mode is active and a dashboard is loaded.
   */
  @Output() dashboardLoaded = new EventEmitter<PinboardConfig>();
  activeDashboard: PinboardConfig;
  activeIndex = 0;
  editDashboards: Array<string> = [];
  changedDashboards: Array<PinboardConfig> = [];
  // eslint-disable-next-line @angular-eslint/no-output-on-prefix
  onDestroy$ = new Subject<void>();
  switchDashboardTab: boolean;
  editMode = false;
  @ViewChild(MatTabGroup) tabGroup: MatTabGroup;
  @ContentChild(WidgetSettingsComponent, { static: true }) public widgetSettingsComponent: WidgetSettingsComponent;
  @ContentChild(PinboardGridComponent, { static: true }) private pinboardGridComponent: PinboardGridComponent;
  @ContentChild(WidgetCatalogComponent, { static: true }) public widgetCatalogComponent: WidgetCatalogComponent;
  @ContentChild(WidgetSettingsFormComponent, { static: true })
  private widgetSettingsFormComponent: WidgetSettingsFormComponent;
  @ContentChild(PinboardFilterBarComponent, { static: true })
  private pinboardFilterBarComponent: PinboardFilterBarComponent;

  constructor(
    private widgetDefService: WidgetDefinitionService,
    private pinboardInteraction: PinboardInteractionService,
    private widgetCatalogService: WidgetCatalogService,
    private modalWindowService: ModalWindowService,
    public pinboardI18n: PinboardI18nService,
    public leavingApiService: LeavingApiService
  ) {}

  ngOnInit(): void {
    // When burgermenu button 'Edit' is clicked, open the WidgetSettingsComponent
    this.pinboardGridComponent.widgetEdit.subscribe(this.openWidgetSettings.bind(this));
    this.widgetAddSubscription = this.widgetCatalogService.widgetAdded.subscribe((widgetDefinitions) => {
      this.pinboardGridComponent.addWidgetsToGrid(widgetDefinitions);
    });
    this.widgetSettingsComponent.cancel.subscribe(() => {
      this.pinboardGridComponent.leaveWidgetEditMode();
      this.pinboardGridComponent.updateGrid();
    });
    this.widgetSettingsComponent.save.subscribe(() => {
      this.pinboardGridComponent.leaveWidgetEditMode();
      this.pinboardGridComponent.updateGrid();
    });
    this.widgetSettingsComponent.confirm.subscribe(() => {
      this.changedDashboards.push(this.activeDashboard);
      this.pinboardGridComponent.leaveWidgetEditMode();
      this.pinboardGridComponent.updateGrid();
    });
    // When the user changes settings, trigger a Gridster update (e.g. size changes)
    if (!this.widgetSettingsComponent && !this.widgetSettingsFormComponent) {
      console.error(
        'You need to place either WidgetSettingsComponent or WidgetSettingsFormComponent inside the Pinboard!'
      );
    } else if (this.widgetSettingsComponent && !this.widgetSettingsFormComponent) {
      this.widgetSettingsComponent.settingsChange.subscribe((w: UserSettingChangeEvent) => {
        this.pinboardGridComponent.updateGrid();
        this.pinboardGridComponent.resizeWidgets(w.widget);
      });
    }
    this.pinboardGridComponent.widgetsUpdated.subscribe((widgets) => {
      this.activeDashboard.settings = widgets;
      this.changedDashboards.push(this.activeDashboard);
    });
  }

  ngAfterViewInit(): void {
    // To prevent the tab switch while configuring the widget
    // it will wait for leaving api to confirm widget configuration to save or dont't save
    if (this.tabGroup) {
      this.tabGroup._handleClick = (tab: MatTab, tabHeader: MatTabHeader, tabIndex: number) =>
        this.interceptTabChange(tabIndex, this.activeIndex);
    }
  }

  ngOnChanges(changes: SimpleChanges) {
    const dashboardChanges = changes.dashboards;
    if (!dashboardChanges || this.dashboards.length === 0) {
      return;
    }
    // If there is no active dashboard defined or if it has been deleted
    // then set the first item in the list as the active dashbboard
    // else stay on the same tab
    if (!this.activeDashboard) {
      this.openDashboard(0);
      return;
    }
    const index = this.dashboards.findIndex((item) => item.id === this.activeDashboard.id);
    // If tab swich happens after adding a new dashboard,
    // show a leave confirmation pop up in case there are unsaved changes.
    if (
      index > -1 &&
      (this.activeIndex !== index ||
        (dashboardChanges.previousValue[index] &&
          dashboardChanges.previousValue[index].id !== dashboardChanges.currentValue[index].id))
    ) {
      this.interceptTabChange(index, this.activeIndex);
    } else {
      this.openDashboard(index > -1 ? index : 0);
    }
  }

  ngOnDestroy() {
    if (this.widgetAddSubscription) {
      this.widgetAddSubscription.unsubscribe();
    }
    this.onDestroy$.next();
    this.onDestroy$.complete();
  }

  /**
   * Reset all widget definitions in the pinboard
   */
  public resetWidgetDefinitions() {
    this.widgetDefService.reset();
  }

  /**
   * Add a WidgetDefinition to the pinboard
   */
  public defineWidget(widgetDefinition: WidgetDefinition<WidgetContentComponent, any, any>) {
    this.widgetDefService.defineWidget(widgetDefinition);
  }

  /**
   * Returns the successfully defined WidgetDefinitions
   */
  public getDefinedWidgets(): WidgetDefinition<WidgetContentComponent, any, any>[] {
    return this.widgetDefService.getWidgetDefinitions();
  }

  /**
   * Exports the current Pinboard as PinboardConfig
   * gridWidgets contains a copy of the current grid Widgets
   * (no deep copy)
   */
  public exportPinboardConfig(id: string, label?: string): PinboardConfig {
    const conf = {} as PinboardConfig;
    conf.settings = [];
    conf.id = id;
    conf.label = label;

    const refs = this.pinboardGridComponent.getWidgets();
    for (const ref of refs) {
      let tmp = {} as any;
      tmp = Object.assign(tmp, ref);
      conf.settings.push(tmp);
    }
    return conf;
  }

  /**
   * Check which Pinboard Components the dev set
   */
  ngAfterContentInit(): void {
    if (!this.widgetSettingsComponent && !this.widgetSettingsFormComponent) {
      console.error('Missing WidgetSettingsComponent and WidgetSettingsFormComponent');
      return;
    }

    if (!this.pinboardGridComponent) {
      console.error('Missing PinboardGridComponent');
      return;
    }

    this.pinboardInteraction.setPinboardGridComponent(this.pinboardGridComponent);
  }

  /**
   * Load PinboardConfig
   * Before loading, you need to define WidgetDefinitions!
   * The widget objects of the gridWidgets attribute,
   * need the widgetDefinition.id attribute.
   */
  public loadPinboardConfig(config: PinboardConfig): void {
    const resolvedWidgets: Widget[] = [];
    for (const w of config.settings) {
      const defId = w.widgetDefinition.id;
      const def = this.widgetDefService.getWidgetDefinitions(defId)[0];

      if (!def) {
        console.error(`There is no WidgetDefinition with id ${defId}!`);
        continue;
      }

      let widget = new Widget(w.id);
      widget = Object.assign(widget, w);
      widget.widgetDefinition = def;
      widget.settings = def.settings;

      resolvedWidgets.push(widget);
    }
    this.pinboardGridComponent.setGridWidgets(resolvedWidgets);
    this.dashboardLoaded.emit({ ...config, settings: resolvedWidgets });
  }

  /**
   * Opens the WidgetSettingsSidebar for a Widget
   */
  private openWidgetSettings(widget: Widget): void {
    if (this.widgetSettingsComponent) {
      this.widgetSettingsComponent.openWidgetSettings(widget);
      this.pinboardGridComponent.startWidgetEditMode(widget);
    } else if (this.widgetSettingsFormComponent) {
      this.widgetSettingsFormComponent.buildFormsForWidget(widget);
    }
  }

  /**
   * Opens the WidgetCatalog
   */
  public openCatalog(): void {
    this.modalWindowService.openDialogWithComponent(WidgetCatalogComponent, { panelClass: 'widget-catalog-dialog' });
    this.widgetSettingsComponent.closeWidgetSettings();
    this.pinboardGridComponent.leaveWidgetEditMode();
  }

  public openDashboard(selectedIndex: number) {
    this.activeIndex = selectedIndex;
    this.activeDashboard = this.dashboards[selectedIndex];
    if (this.editMode != this.activeDashboardInEditMode()) {
      if (this.activeDashboardInEditMode()) {
        this.editMode = true;
        this.pinboardGridComponent.startGridEditMode();
      } else {
        this.editMode = false;
        this.pinboardGridComponent.stopGridEditMode();
      }
    }
    if (this.tabGroup) {
      this.tabGroup.selectedIndex = selectedIndex;
    }
    if (this.activeDashboardInEditMode()) {
      return this.loadPinboardConfig(this.getActiveDashboardsSettings());
    }
    this.dashboardOpened.emit(this.activeDashboard);
  }

  public interceptTabChange(nextIndex: number, prevIndex: number) {
    const widgetForm = this.widgetSettingsComponent.widgetSettingsFormComponent;
    this.switchDashboardTab = false;
    this.pinboardGridComponent.leaveWidgetEditMode();
    if (widgetForm && widgetForm.formGroup.dirty) {
      const dashboardTitle = prevIndex ? this.dashboards[prevIndex].label : this.dashboards[this.activeIndex].label;
      this.leavingApiService
        .showLeavingApiConfirmation(dashboardTitle, widgetForm.formGroup.valid)
        .pipe(takeUntil(this.onDestroy$))
        .subscribe((result) => {
          if (result === tabSwitch.SAVE) {
            widgetForm.saveEdit();
            this.widgetSettingsComponent.closeWidgetSettings();
            this.switchDashboardTab = true;
            this.openDashboard(nextIndex);
          } else if (result === tabSwitch.DONTSAVE) {
            this.widgetSettingsComponent.closeWidgetSettings();
            this.openDashboard(nextIndex);
            this.switchDashboardTab = true;
          } else {
            this.switchDashboardTab = false;
          }
        });
    } else {
      this.widgetSettingsComponent.closeWidgetSettings();
      this.openDashboard(nextIndex);
      this.switchDashboardTab = true;
    }
  }

  /**
   * Start Edit Mode which allows to add/delete/edit widgets for active dashboard
   */
  public startEditMode(): void {
    this.toggleEditModeState(true);
    this.editDashboards.push(this.activeDashboard.id);
    this.removeDashboardFromChangedList(this.activeDashboard.id);
    this.pinboardGridComponent.startGridEditMode();
  }

  /**
   * Save active dashboard
   */
  public saveEditMode(): void {
    this.toggleEditModeState(false);
    this.pinboardGridComponent.stopGridEditMode();
    this.removeDashboardFromEditList(this.activeDashboard.id);
    this.removeDashboardFromChangedList(this.activeDashboard.id);
    this.activeDashboard.settings = this.pinboardGridComponent.getWidgets();
    this.dashboardSaved.emit(this.activeDashboard);
  }

  /**
   * Revert changes in active dashboard and exit Edit Mode
   */
  public cancelEditMode(): void {
    this.toggleEditModeState(false);
    this.removeDashboardFromEditList(this.activeDashboard.id);
    this.removeDashboardFromChangedList(this.activeDashboard.id);
    this.pinboardGridComponent.stopGridEditMode();
    this.dashboardOpened.emit(this.activeDashboard);
  }

  public dashboardInEditMode(dashboard: PinboardConfig): boolean {
    return !!this.editDashboards.find((editDashboard) => editDashboard === dashboard.id);
  }

  public activeDashboardInEditMode(): boolean {
    return !!this.editDashboards.find((dashboard) => dashboard === this.activeDashboard.id);
  }

  public activeDashboardChanged(): boolean {
    return !!this.changedDashboards.find((dashboard) => dashboard.id === this.activeDashboard.id);
  }

  private getActiveDashboardsSettings(): PinboardConfig {
    const changedDashboard = this.changedDashboards.find((dashboard) => dashboard.id === this.activeDashboard.id);
    return changedDashboard || this.activeDashboard;
  }

  private toggleEditModeState(active: boolean): void {
    this.editMode = active;
    this.widgetSettingsComponent.editMode = active;
    if (this.pinboardFilterBarComponent) {
      this.pinboardFilterBarComponent.editMode = active;
    }
  }

  private removeDashboardFromEditList(id: string): void {
    this.editDashboards = this.editDashboards.filter((dashboard) => dashboard !== id);
  }

  private removeDashboardFromChangedList(id: string): void {
    this.changedDashboards = this.changedDashboards.filter((dashboard) => dashboard.id !== id);
  }
}
