import { Component, OnInit, Input, Output, ViewChild, ViewEncapsulation, EventEmitter } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { MatDialog } from '@angular/material/dialog';

import * as _ from 'lodash';

import { Node } from '../models/node.model';
import { Settings } from '../../core/settings';
import { InputDialogComponent } from '../dialog/input-dialog/input-dialog.component';
import { ConfirmDialogComponent } from '../dialog/confirm-dialog/confirm-dialog.component';

@Component({
  selector: 'hi-node-viewer',
  templateUrl: './node-viewer.component.html',
  styleUrls: ['./node-viewer.component.scss'],
  // Since we are importing external styles in this component
  // we will not use Shadow DOM at all to make sure the styles apply
  encapsulation: ViewEncapsulation.None
})
export class NodeViewerComponent implements OnInit {

  @ViewChild('simpleTable', {static: true}) simpleTable;
  @ViewChild('listTable', {static: true}) listTable;
  @ViewChild('mapTable', {static: true}) mapTable;

  @Output('update')
  change: EventEmitter<Node> = new EventEmitter<Node>();

  @Output('create')
  create: EventEmitter<Node> = new EventEmitter<Node>();

  @Output('delete')
  delete: EventEmitter<Node> = new EventEmitter<Node>();

  @Input()
  unlockable = false;

  @Input()
  loadingIndicator = false;

  private _editable = false;

  protected _obj: any;
  protected node: Node;

  headerHeight = Settings.tableHeaderHeight;
  rowHeight = Settings.tableRowHeight;
  sorts = [
    { prop: 'name', dir: 'asc'}
  ];
  keyword = '';
  columns = {
    simpleConfigs: [
      {
        name: 'Name',
        editable: false
      },
      {
        name: 'Value',
        editable: false
      }
    ],
    listConfigs: [
      {
        name: 'Value',
        editable: false
      }
    ]
  };

  _simpleConfigs: any[];

  _listConfigs: any[];

  _mapConfigs: any[];

  // MODE 1: use directly in components
  @Input()
  set obj(value: any) {
    if (value != null) {
      this._obj = value;
      this.node = new Node(value);
    }
  }
  get obj(): any {
    return this._obj;
  }
  set editable(value: boolean) {
    this._editable = value;
    this.columns.simpleConfigs[1].editable = this._editable;
    this.columns.listConfigs[0].editable = this._editable;
  }
  get editable() {
    return this._editable;
  }
  get simpleConfigs(): any[] {
    return this.node ? _.filter(this.node.simpleFields, config => config.name.toLowerCase().indexOf(this.keyword) >= 0
        || config.value.toLowerCase().indexOf(this.keyword) >= 0) : [];
  }
  get listConfigs(): any[] {
    return this.node ?  _.filter(this.node.listFields, config => config.name.toLowerCase().indexOf(this.keyword) >= 0
        || _.some(config.value as any[], subconfig => subconfig.value.toLowerCase().indexOf(this.keyword) >= 0)) : [];
  }
  get mapConfigs(): any[] {
    return this.node ?  _.filter(this.node.mapFields, config => config.name.toLowerCase().indexOf(this.keyword) >= 0
        || _.some(config.value as any[], subconfig => subconfig.name.toLowerCase().indexOf(this.keyword) >= 0
            || subconfig.value.toLowerCase().indexOf(this.keyword) >= 0)) : [];
  }

  constructor(
    protected dialog: MatDialog,
    protected route: ActivatedRoute
  ) { }

  ngOnInit() {
    // MODE 2: use in router
    if (this.route.snapshot.data.path) {
      const path = this.route.snapshot.data.path;

      // try parent data first
      this.obj = _.get(this.route.parent, `snapshot.data.${ path }`);

      if (this.obj == null) {
        // try self data then
        this.obj = _.get(this.route.snapshot.data, path);
      }
    }
  }

  updateFilter(event) {
    this.keyword = event.target.value.toLowerCase().trim();

    // Whenever the filter changes, always go back to the first page
    if (this.simpleTable) {
      this.simpleTable.offset = 0;
    }
    if (this.listTable) {
      this.listTable.offset = 0;
    }
    if (this.mapTable) {
      this.mapTable.offset = 0;
    }
  }

  getNameCellClass({ value }): any {
    return {
      // highlight HELIX own configs
      primary: _.snakeCase(value).toUpperCase() === value
    };
  }

  onCreate(type) {
    this.dialog
      .open(InputDialogComponent, {
        data: {
          title: `Create a new ${ type } configuration`,
          message: 'Please enter the name of the new configuration. You\'ll be able to add values later:',
          values: {
            name: {
              label: 'the name of the new configuration'
            }
          }
        }
      })
      .afterClosed()
      .subscribe(result => {
        if (result) {
          const entry = [{
            name: result.name.value,
            value: []
          }];

          const newNode: Node = new Node(null);
          if (type === 'list') {
            newNode.listFields = entry;
          } else if (type === 'map') {
            newNode.mapFields = entry;
          }

          this.create.emit(newNode);
        }
      });
  }

  beforeDelete(type, row) {
    this.dialog
      .open(ConfirmDialogComponent, {
        data: {
          title: 'Confirmation',
          message: 'Are you sure you want to delete this configuration?'
        }
      })
      .afterClosed()
      .subscribe(result => {
        if (result) {
          this.onDelete(type, row);
        }
      });
  }

  onDelete(type, row) {
    const newNode: Node = new Node(null);

    if (type === 'simple') {
      newNode.appendSimpleField(row.name, '');
    } else if (type === 'list') {
      newNode.listFields = [{ name: row.name, value: [] }];
    } else if (type === 'map') {
      newNode.mapFields =  [{ name: row.name, value: null }];
    }

    this.delete.emit(newNode);
  }

  created(type, data, key) {
    const newNode: Node = new Node(null);

    switch (type) {
      case 'simple':
        newNode.appendSimpleField(data.name.value, data.value.value);
        break;

      case 'list':
        if (key) {
          const entry = _.find(this.node.listFields, {name: key});
  //         Property 'value' does not exist on type 'number | ListFieldObject | ((searchElement: ListFieldObject, fromIndex?: number) => boolean) | ((...'.
  // Property 'value' does not exist on type 'number'.ts(2339)
  // @ts-ignore
          entry.value.push({
            name: '',
            value: data.value.value
          });
          // Argument of type 'number | ListFieldObject | ((searchElement: ListFieldObject, fromIndex?: number) => boolean) | ((...' is not assignable to parameter of type 'ListFieldObject'.
  // Type 'number' is not assignable to type 'ListFieldObject'.ts(2345)
  // @ts-ignore
          newNode.listFields.push(entry);
        }
        break;

      case 'map':
        if (key) {
          const entry = _.find(this.node.mapFields, {name: key});
  //         Property 'value' does not exist on type 'number | MapFieldObject | ((searchElement: MapFieldObject, fromIndex?: number) => boolean) | (() ...'.
  // Property 'value' does not exist on type 'number'.ts(2339)
  // @ts-ignore
          _.forEach(entry.value, (item: any) => {
            newNode.appendMapField(key, item.name, item.value);
          });
          newNode.appendMapField(key, data.name.value, data.value.value);
        }
        break;
    }

    this.create.emit(newNode);
  }

  edited(type, {row, column, value}, key, isDeleting) {
    if (!isDeleting && column.name !== 'Value') {
      return;
    }

    const newNode: Node = new Node(null);

    switch (type) {
      case 'simple':
        newNode.appendSimpleField(row.name, value);
        break;

      case 'list':
        if (key) {
          const entry = _.find(this.node.listFields, {name: key});
  //         Property 'value' does not exist on type 'number | ListFieldObject | ((searchElement: ListFieldObject, fromIndex?: number) => boolean) | ((...'.
  // Property 'value' does not exist on type 'number'.ts(2339)
  // @ts-ignore
          const index = _.findIndex(entry.value, {value: row.value});
          if (isDeleting) {
  //           Property 'value' does not exist on type 'number | ListFieldObject | ((searchElement: ListFieldObject, fromIndex?: number) => boolean) | ((...'.
  // Property 'value' does not exist on type 'number'.ts(2339)
  // @ts-ignore
            entry.value.splice(index, 1);
          } else {
  //           Property 'value' does not exist on type 'number | ListFieldObject | ((searchElement: ListFieldObject, fromIndex?: number) => boolean) | ((...'.
  // Property 'value' does not exist on type 'number'.ts(2339)
  // @ts-ignore
            entry.value[index].value = value;
          }
          // Argument of type 'number | ListFieldObject | ((searchElement: ListFieldObject, fromIndex?: number) => boolean) | ((...' is not assignable to parameter of type 'ListFieldObject'.
          // Type 'number' is not assignable to type 'ListFieldObject'.ts(2345)
          // @ts-ignore
          newNode.listFields.push(entry);
        }
        break;

      case 'map':
        if (key) {
          // have to fetch all other configs under this key
          const entry = _.find(this.node.mapFields, {name: key});
          newNode.mapFields =  [{ name: key, value: [] }];

          // Property 'value' does not exist on type 'number | MapFieldObject | ((searchElement: MapFieldObject, fromIndex?: number) => boolean) | (() ...'.
          //   Property 'value' does not exist on type 'number'.ts(2339)
          // @ts-ignore
          _.forEach(entry.value, (item: any) => {
            if (item.name === row.name) {
              if (!isDeleting) {
                newNode.appendMapField(key, item.name, value);
              }
            } else {
              newNode.appendMapField(key, item.name, item.value);
            }
          });
        }
        break;
    }

    this.change.emit(newNode);
  }
}
