import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { UntypedFormArray, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { ChartType } from 'chart.js';
import { distinctUntilChanged } from 'rxjs/operators';

import { toggleVertically } from '@app/animations';
import { ResourceTableColumn } from '@app/shared/resource-table/ResourceTableColumn';
import { UserService } from '@app/services/user.service';
import { IChartBlueprint } from '@app/services/models/chart';
import {
  oneElementInArrayValidator,
  uniqueValuesInArrayValidator,
  valueInArrayValidator,
} from '@app/shared/form-validators/form-array.validators';
import { ArrayHelper } from '@app/shared/helpers';

/**
 * Resource Chart Form Component
 */
@Component({
  selector: 'app-resource-chart-form',
  templateUrl: './resource-chart-form.component.html',
  animations: [toggleVertically],
})
export class ResourceChartFormComponent implements OnInit {
  /**
   * Table view columns as a data source
   */
  @Input() chartColumns: ResourceTableColumn[];

  /**
   * Chart blueprint generated by the form
   */
  @Input() chartBlueprint: IChartBlueprint;

  /**
   * Saved chart Id
   */
  @Input() savedChartId: string;

  /**
   * Emits chart blueprint when form is valid and submitted
   */
  @Output() chartBlueprintCreated: EventEmitter<IChartBlueprint> = new EventEmitter<IChartBlueprint>();

  /**
   * Emits and event when save action is required
   */
  @Output() save: EventEmitter<null> = new EventEmitter<null>();

  /**
   * Emits and event when delete action is required
   */
  @Output() delete: EventEmitter<null> = new EventEmitter<null>();

  /**
   * Emits and event when revoke action is required
   */
  @Output() revoke: EventEmitter<null> = new EventEmitter<null>();

  /**
   * Determines whether at least one numeric column exists
   */
  numericColumnList: ResourceTableColumn[];

  /**
   * Non numeric column list - data labels
   */
  nonNumericColumnList: ResourceTableColumn[];

  /**
   * All selected column names without duplicates
   */
  allSelectedColumnNames: string[];

  /**
   * All selected column based on selected column names - without duplicates
   */
  allSelectedColumns: ResourceTableColumn[];

  /**
   * Chart form of resource chart form component
   */
  chartForm: UntypedFormGroup;

  /**
   * Determines whether currently selected chart is circular
   */
  isCircularChart: boolean;

  /**
   * Determines whether currently selected chart is scatter
   */
  isScatterChart: boolean;

  /**
   * Available chart types
   */
  chartTypes: ChartType[] = ['line', 'bar', 'scatter', 'pie', 'doughnut', 'polarArea'];

  /**
   * Numeric chart types
   */
  circularChartTypes: ChartType[] = this.chartTypes.slice(3);

  /**
   * Determines whether user is logged in
   */
  isUserLoggedIn = false;

  /**
   * Determines whether user can save chart
   */
  isSaveAvailable: boolean;

  /**
   * Current chart url
   */
  currentChartUrl: string;

  /**
   * Determines whether current component is in embedded view (no header and footer)
   */
  isEmbeddedView: boolean;

  /**
   * Determines whether name form is visible
   */
  isNameFormVisible = false;

  /**
   * Saved chart name
   */
  @Input() savedChartName: string;

  /**
   * Determines whether named chart mode is on - chart name is mandatory
   */
  @Input() isNamedChartMode = true;

  /**
   * @ignore
   */
  constructor(
    private formBuilder: UntypedFormBuilder,
    private userService: UserService,
    private router: Router,
  ) {}

  /**
   * Checks whether there is alredy stored chart blueprint.
   * Creates chart form a structure and assigns validators.
   * Determines whether table columns are numeric or other type.
   */
  ngOnInit() {
    this.currentChartUrl = this.router.url;
    this.isEmbeddedView = this.checkIsEmbedded();

    this.chartForm = this.formBuilder.group(
      {
        chart_type: [null, Validators.required],
        datasets: this.formBuilder.array(
          [this.formBuilder.control(null, Validators.required)],
          uniqueValuesInArrayValidator,
        ),
        labels: [null, Validators.required],
        sort: null,
      },
      { validator: valueInArrayValidator('labels', 'datasets') },
    );

    this.fillForm();

    this.chartForm.valueChanges
      .pipe(
        distinctUntilChanged((prevValue, newValue) => {
          return JSON.stringify(prevValue) === JSON.stringify(newValue);
        }),
      )
      .subscribe((formValue) => {
        if (this.chartBlueprint && JSON.stringify(this.chartBlueprint) === JSON.stringify(formValue)) {
          this.isSaveAvailable = true;
        } else {
          this.isSaveAvailable = false;
        }
      });

    this.numericColumnList = this.chartColumns.filter((item) => item.type === 'integer' || item.type === 'number');
    this.nonNumericColumnList = this.chartColumns.filter((item) => item.type !== 'integer' && item.type !== 'number');
    this.isUserLoggedIn = !!this.userService.token;
  }

  /**
   * Checks whether current route embedded - no header and footer
   */
  checkIsEmbedded() {
    return this.currentChartUrl.indexOf('embed') !== -1;
  }

  /**
   * Updates validation rules based on currently selected chart type
   * @param {string} chartType
   */
  onChartTypeChange(chartType: string) {
    if (this.isScatterChart && chartType !== 'scatter') {
      this.isScatterChart = false;
      this.chartForm.patchValue({ labels: null });
    } else if (!this.isScatterChart && chartType === 'scatter') {
      this.isScatterChart = true;
      this.chartForm.patchValue({ labels: null });
    }

    const datasetsControl = this.chartForm.get('datasets');
    datasetsControl.clearValidators();

    if (this.circularChartTypes.some((item) => item === chartType)) {
      this.isCircularChart = true;

      if (this.datasetsArray.controls.length > 1) {
        datasetsControl.setValidators(oneElementInArrayValidator);
      }
    } else {
      this.isCircularChart = false;
      datasetsControl.setValidators(uniqueValuesInArrayValidator);
    }

    datasetsControl.updateValueAndValidity();
  }

  /**
   * Runs on every column change.
   * Updates sort list.
   * Runs validation rules on 'chart type' if other field was touched first
   */
  onColumnChange() {
    this.prepareSortList();

    const chartTypeControl = this.chartForm.get('chart_type');
    if (chartTypeControl.errors) {
      chartTypeControl.markAsTouched();
    }
  }

  /**
   * Appends new datasets dropdown to the form
   */
  onAppendDataColumn() {
    this.datasetsArray.push(new UntypedFormControl(null, Validators.required));
  }

  /**
   * Removes dataset dropdown from the form.
   * Updates sort list.
   * @param {number} field
   */
  onRemoveDataColumn(index: number) {
    this.datasetsArray.removeAt(index);
    this.prepareSortList();
  }

  /**
   * Removes duplicated values.
   * Emits chart blueprint
   */
  onPreviewChart() {
    const chartFormValue = { ...this.chartForm.value };
    chartFormValue['datasets'] = ArrayHelper.removeNullsAndDuplicates(chartFormValue['datasets']);

    this.chartBlueprintCreated.emit(chartFormValue);
    this.isSaveAvailable = true;
  }

  /**
   * Emits save event
   */
  onSaveChart() {
    this.save.emit();
  }

  /**
   * Emits delete event
   */
  onDeleteChart() {
    this.delete.emit();
  }

  /**
   * Resets the form.
   */
  onResetChartForm() {
    while (this.datasetsArray.length > 1) {
      this.datasetsArray.removeAt(this.datasetsArray.length - 1);
    }

    this.chartForm.reset();
    this.prepareSortList();
  }

  /**
   * Updates sort list without duplicates
   */
  private prepareSortList() {
    this.chartForm.patchValue({ sort: null });
    const chartFormValue = { ...this.chartForm.value };
    const allSelectedColumnNames = ArrayHelper.removeNullsAndDuplicates([
      ...chartFormValue['datasets'],
      ...this.getLabelsArray(chartFormValue['labels']),
    ]);

    this.allSelectedColumns = allSelectedColumnNames.map((columnName) => {
      return this.chartColumns.find((item) => item.name === columnName);
    });
  }

  /**
   * Fills form with chart blueprint data
   */
  private fillForm() {
    if (!this.chartBlueprint) {
      return;
    }

    if (JSON.stringify(this.chartForm.value) === JSON.stringify(this.chartBlueprint)) {
      return;
    }

    if (this.datasetsArray.controls.length !== this.chartBlueprint['datasets'].length) {
      this.datasetsArray.controls.splice(0);
      this.chartBlueprint['datasets'].forEach(() => this.onAppendDataColumn());
    }

    this.chartForm.setValue(this.chartBlueprint);
    this.prepareSortList();
    this.chartForm.patchValue({ sort: this.chartBlueprint['sort'] });

    if (this.isNamedChartMode) {
      this.onChartTypeChange(this.chartBlueprint.chart_type);
      this.chartForm.patchValue({ labels: this.chartBlueprint.labels });
    }
  }

  /**
   * Gets datasets array
   * @returns {FormArray}
   */
  get datasetsArray(): UntypedFormArray {
    return this.chartForm.get('datasets') as UntypedFormArray;
  }

  private getLabelsArray(labels: string | string[]): string[] {
    if (Array.isArray(labels)) {
      return labels;
    } else {
      return [labels];
    }
  }

  /**
   * Event when chart name is going to be saved
   * @param {string} chartName
   */
  onChartNameSaved(chartName: string) {
    this.chartBlueprint.name = chartName;
    this.save.emit();
  }

  /**
   * Event when chart settings are going to be restored
   */
  onRevokeChartSettings() {
    this.revoke.emit();
  }
}
