import {Component, Input, OnInit, ViewChild} from '@angular/core';
import {ApiService, ResourceNotAvailableOfflineError} from '../../../service/api.service';
import {AlertController, LoadingController, ModalController} from '@ionic/angular';
import {ColumnsComponent} from './columns/columns.component';
import {AgGridAngular} from 'ag-grid-angular';
import {NumericEditorComponent} from './editors/numeric/numeric-editor.component';
import {ParagraphEditorComponent} from './editors/paragraph/paragraph-editor.component';
import {BooleanEditorComponent} from './editors/boolean/boolean-editor.component';
import {SelectEditorComponent} from './editors/select/select-editor.component';
import {MultiSelectEditorComponent} from './editors/select/multiselect-editor.component';
import {DatetimeEditorComponent} from './editors/datetime/datetime-editor.component';
import {DatetimeRendererComponent} from './renderers/datetime-renderer.component';
import {CurrencyRendererComponent} from './renderers/currency-renderer.component';
import {BooleanRendererComponent} from './renderers/boolean-renderer.component';
import {EditorNodeContract} from '../../nodes/EditorNode.contract';
import {EditorService} from '../../../service/editor.service';
import {WysiwygEditorComponent} from './editors/wysiwyg/wysiwyg-editor.component';
import {debounce, debug} from '../../../utility';
import {DateTimeFilterComponent} from './filters/date-time.filter';
import {DatabasePageComponent} from './database-page.component';
import {PageLinkRendererComponent} from './renderers/page-link-renderer.component';
import {LinkRendererComponent} from './renderers/link-renderer.component';
import {PageLinkEditorComponent} from './editors/page-link/page-link-editor.component';

@Component({
  selector: 'editor-database',
  templateUrl: './database.component.html',
  styleUrls: ['./database.component.scss'],
})
export class DatabaseComponent extends EditorNodeContract implements OnInit {
  @Input() nodeId: string;
  @Input() editorUUID?: string;
  @Input() fullPage = false;
  @ViewChild('agGridElement') agGridElement: AgGridAngular;

  frameworkComponents = {
    agDateInput: DateTimeFilterComponent
  };

  public dbRecord: any;
  public pendingSetup = true;
  public dirty = false;
  public lastClickRow = -1;
  public dbName = '';
  public notAvailableOffline = false;
  public pages = [];
  protected dbId!: string;
  protected isInitialLoad = false;
  protected triggerSaveDebounce = debounce(() => {
    if ( this.agGridElement.api.getCellEditorInstances().length < 1 ) {
      this.editorService.triggerSave();
    } else {
      this.triggerSaveDebounce();
    }
  }, 1000);

  protected gridReady = false;
  protected deferredUIActivation = false;

  title = 'app';
  columnDefs = [];
  rowData = [];

  public isDark() {
    return document.body.classList.contains('dark');
  }

  public get readonly() {
    return !this.node || !this.editorService.canEdit();
  }

  constructor(
      protected api: ApiService,
      protected modals: ModalController,
      protected alerts: AlertController,
      protected loader: LoadingController,
      public editorService: EditorService,
  ) { super(); }

  public isDirty(): boolean | Promise<boolean> {
    return this.dirty;
  }

  public needsSave(): boolean | Promise<boolean> {
    return this.dirty;
  }

  public needsLoad(): boolean | Promise<boolean> {
    return this.node && this.pendingSetup;
  }

  public writeChangesToNode(): void | Promise<void> {
    this.node.Value.Mode = 'database';
  }

  async ngOnInit() {
    this.pages = this.flattenItems(await this.api.getMenuItems(true));
    this.editorService = this.editorService.getEditor(this.editorUUID);
    this.editorService.registerNodeEditor(this.nodeId, this).then(() => {

    });
  }

  protected flattenItems(items: any[], level = 0) {
    let newItems = [];

    for ( const item of items ) {
      item.level = level;
      newItems.push(item);

      if ( Array.isArray(item.children) ) {
        newItems = [...newItems, ...this.flattenItems(item.children, level + 1)];
      }
    }

    return newItems;
  }

  onGridReady($event) {
    this.gridReady = true;
    if ( this.deferredUIActivation && !this.pendingSetup ) {
      this.performUIActivation();
    }
  }

  onColumnResize($event) {
    if ( $event.source === 'uiColumnDragged' && $event.finished ) {
      debug('Column resized: ', $event);
      const state = $event.columnApi.getColumnState().find(x => x.colId === $event.column.colId );
      if ( state ) {
        const colDef = this.columnDefs.find(x => x.field === $event.column.colId);
        if ( colDef ) {
          colDef.width = state.width;
          this.dirty = true;
          this.triggerSaveDebounce();
        }
      }
    }
  }

  onRowDragEnd($event) {
    console.log($event, this);
    if ( !this.isInitialLoad && this.editorService.canEdit() ) {
      this.dirty = true;
      this.triggerSaveDebounce();
    }
  }

  onCellValueChanged() {
    if ( !this.isInitialLoad ) {
      this.dirty = true;
      this.triggerSaveDebounce();
    }
  }

  async onManageColumns() {
    if ( this.readonly ) {
      return;
    }

    const modal = await this.modals.create({
      component: ColumnsComponent,
      componentProps: {columnSets: this.columnDefs.slice(1)},
      cssClass: 'modal-med',
    });

    modal.onDidDismiss().then(result => {
      if ( result?.data ) {
        this.setColumns(result.data);
      }
    });

    const modalState = {
      modal : true,
      desc : 'Manage Columns'
    };

    history.pushState(modalState, null);

    await modal.present();
  }

  onInsertRow() {
    if ( this.readonly ) {
      return;
    }

    this.rowData.push({});
    this.agGridElement.api.setRowData(this.rowData);
    this.dirty = true;
    this.triggerSaveDebounce();
  }

  async onRemoveRow() {
    if ( this.readonly ) {
      return;
    }

    const alert = await this.alerts.create({
      header: 'Are you sure?',
      message: `You are about to delete row ${this.lastClickRow + 1}. This cannot be undone.`,
      buttons: [
        {
          text: 'Keep It',
          role: 'cancel',
        },
        {
          text: 'Delete It',
          handler: () => {
            this.rowData = this.rowData.filter((x, i) => {
              return i !== this.lastClickRow;
            });
            this.agGridElement.api.setRowData(this.rowData);
            this.lastClickRow = -1;
            this.dirty = true;
            this.triggerSaveDebounce();
          },
        }
      ],
    });

    await alert.present();
  }


  onRowClicked($event) {
    this.lastClickRow = $event.rowIndex;
  }

  setColumns(data, triggerSave = true) {
    this.columnDefs = [{
      width: 20,
      // rowDrag: !this.readonly,
      // rowDragText: (params, dragItemCount) => `${dragItemCount} ${dragItemCount === 1 ? 'row' : 'rows'}`,
    }, ...data.map(x => {
      x.editable = !this.readonly;
      x.minWidth = 150;
      x.resizable = true;
      x._parentEditorUUID = this.editorUUID;

      if ( x.additionalData?.width ) {
        x.width = x.additionalData.width;
      }

      if ( x.additionalData?.pinned ) {
        x.pinned = x.additionalData.pinned;
      }

      // Set editors and renderers for different types
      if ( x.Type === 'text' ) {
        x.editor = 'agTextCellEditor';
        x.filter = 'agTextColumnFilter';
      } else if ( x.Type === 'number' ) {
        x.cellEditorFramework = NumericEditorComponent;
        x.filter = 'agNumberColumnFilter';
      } else if ( x.Type === 'paragraph' ) {
        x.cellEditorFramework = ParagraphEditorComponent;
        x.filter = 'agTextColumnFilter';
      } else if ( x.Type === 'boolean' ) {
        x.cellRendererFramework = BooleanRendererComponent;
        x.cellEditorFramework = BooleanEditorComponent;
        x.suppressSizeToFit = true;
      } else if ( x.Type === 'select' ) {
        x.cellEditorFramework = SelectEditorComponent;
        x.filter = 'agTextColumnFilter';
      } else if ( x.Type === 'multiselect' ) {
        x.cellEditorFramework = MultiSelectEditorComponent;
        x.filter = 'agTextColumnFilter';
      } else if ( x.Type === 'datetime' ) {
        x.cellEditorFramework = DatetimeEditorComponent;
        x.cellRendererFramework = DatetimeRendererComponent;
        x.filter = 'agDateColumnFilter';
        x.filterParams = {
          buttons: ['apply', 'clear'],
          displayFormat: x.additionalData.format,
          comparator: (filterDate: Date, cellValue) => {
            if ( !cellValue ) {
              return 0;
            }

            const cellDate = new Date(cellValue);

            if ( x.additionalData.format === 'YYYY-MM-DD' ) {
              cellDate.setHours(0);
              cellDate.setMinutes(0);
              cellDate.setSeconds(0);
              cellDate.setMilliseconds(0);
            } else if ( x.additionalData.format === 'h:mm a' ) {
              cellDate.setFullYear(filterDate.getFullYear());
              cellDate.setMonth(filterDate.getMonth());
              cellDate.setDate(filterDate.getDate());
            }

            // Now that both parameters are Date objects, we can compare
            if (cellDate < filterDate) {
              return -1;
            } else if (cellDate > filterDate) {
              return 1;
            } else {
              return 0;
            }
          },
        };
      } else if ( x.Type === 'currency' ) {
        x.cellEditorFramework = NumericEditorComponent;
        x.cellRendererFramework = CurrencyRendererComponent;
        x.filter = 'agNumberColumnFilter';
      } else if ( x.Type === 'index' ) {
        x.editable = false;
        x.suppressSizeToFit = true;
        x.filter = 'agNumberColumnFilter';
        if ( !x.width ) {
          x.width = 80;
        }
        x.minWidth = 80;
      } else if ( x.Type === 'wysiwyg' ) {
        x.cellEditorFramework = WysiwygEditorComponent;
        x.filter = 'agTextColumnFilter';
      } else if ( x.Type === 'page_link' ) {
        x.cellRendererFramework = PageLinkRendererComponent;
        x.cellEditorFramework = PageLinkEditorComponent;
        x.filter = 'agTextColumnFilter';

        if ( !x.cellEditorParams ) {
          x.cellEditorParams = {} as any;
        }

        if ( !x.cellRendererParams ) {
          x.cellRendererParams = {} as any;
        }

        x.cellEditorParams._pagesData = this.pages;
        x.cellRendererParams._pagesData = this.pages;
      } else if ( x.Type === 'link' ) {
        x.cellRendererFramework = LinkRendererComponent;
        x.editor = 'agTextCellEditor';
        x.filter = 'agTextColumnFilter';
      }

      return x;
    })];

    this.agGridElement.api.setColumnDefs([]);
    this.agGridElement.api.setColumnDefs(this.columnDefs);
    this.agGridElement.api.sizeColumnsToFit();
    if ( triggerSave ) {
      this.dirty = true;
      this.triggerSaveDebounce();
    }
  }

  public onResized() {
    this.agGridElement.api.sizeColumnsToFit();
  }

  public async performLoad(): Promise<void> {
    this.isInitialLoad = true;

    if ( !this.node.Value ) {
      this.node.Value = {};
    }

    // Load the database record itself
    if ( !this.node.Value.Value && this.editorService.canEdit() ) {
      this.dbRecord = await this.api.createDatabase(this.page.UUID, this.node.UUID);
      this.dbName = this.dbRecord.Name;
      this.node.Value.Mode = 'database';
      this.node.Value.Value = this.dbRecord.UUID;
      this.node.value = this.dbRecord.UUID;
    } else {
      try {
        this.dbRecord = await this.api.getDatabase(this.page.UUID, this.node.UUID, this.node.Value.Value);
        this.dbName = this.dbRecord.Name;
        this.notAvailableOffline = false;
      } catch (e: unknown) {
        if ( e instanceof ResourceNotAvailableOfflineError ) {
          this.notAvailableOffline = true;
        } else {
          throw e;
        }
      }
    }

    // Load the columns
    const columns = await this.api.getDatabaseColumns(this.page.UUID, this.node.UUID, this.node.Value.Value);
    this.setColumns(columns, false);

    const rows = await this.api.getDatabaseEntries(this.page.UUID, this.node.UUID, this.node.Value.Value);
    this.rowData = rows.map(x => x.RowData);
    this.agGridElement.api.setRowData(this.rowData);

    this.pendingSetup = false;
    this.dirty = false;
    this.isInitialLoad = false;

    if ( this.deferredUIActivation && this.gridReady ) {
      await this.performUIActivation();
    }
  }

  public async performDelete(): Promise<void> {
    await this.api.deleteDatabase(this.page.UUID, this.node.UUID, this.node.Value.Value);
  }

  public getSaveColumns() {
    return this.columnDefs.slice(1).map(x => {
      if ( !x.additionalData ) {
        x.additionalData = {};
      }

      if ( x.width ) {
        x.additionalData.width = x.width;
      }

      return x;
    });
  }

  public async performSave(): Promise<void> {
    // Save the columns first
    await this.api.saveDatabaseColumns(this.page.UUID, this.node.UUID, this.node.Value.Value, this.getSaveColumns());

    // Save the data
    const rows = await this.api.saveDatabaseEntries(this.page.UUID, this.node.UUID, this.node.Value.Value, this.rowData);
    this.rowData = rows.map(x => x.RowData);

    // Dynamically update the row data to avoid breaking open editors
    const rowUUIDs = this.rowData.map(x => x.UUID);
    const gridUUIDs = [];

    const rowDataTransaction = {
      add: [],
      remove: [],
      update: [],
    };

    this.agGridElement.api.forEachNode((rowNode, index) => {
      const data = rowNode.data;
      if ( !data.UUID || !rowUUIDs.includes(data.UUID) ) {
        rowDataTransaction.remove.push(rowNode.id);
      } else {
        gridUUIDs.push(data.UUID);
        const updatedRow = this.rowData.find(x => x.UUID === data.UUID);

        if ( updatedRow ) {
          for ( const prop in updatedRow ) {
            if ( !updatedRow.hasOwnProperty(prop) ) {
              continue;
            }

            data[prop] = updatedRow[prop];
          }

          rowDataTransaction.update.push(data);
        }
      }
    });

    for ( const row of this.rowData ) {
      if ( !gridUUIDs.includes(row.UUID) ) {
        rowDataTransaction.add.push(row);
      }
    }

    // @ts-ignore
    this.agGridElement.api.applyTransaction(rowDataTransaction);

    // this.agGridElement.api.setRowData(this.rowData);

    // Save the name
    await this.api.saveDatabaseName(this.page.UUID, this.node.UUID, this.node.Value.Value, this.dbName);

    this.dirty = false;
  }

  async openDatabase() {
    const modal = await this.modals.create({
      component: DatabasePageComponent,
      componentProps: {
        nodeId: this.nodeId,
        pageId: this.editorService.currentPageId,
      },
      cssClass: 'modal-big',
    });

    modal.onDidDismiss().then(() => {
      this.editorService.reload();
    });

    const modalState = {
      modal : true,
      desc : 'Open Database'
    };

    history.pushState(modalState, null);

    await modal.present();
  }

  performUIActivation() {
    if ( this.deferredUIActivation ) {
      this.deferredUIActivation = false;
    }

    if ( this.gridReady && !this.pendingSetup ) {
      return this.openDatabase();
    } else {
      this.deferredUIActivation = true;
    }
  }

  dismiss() {
    this.modals.dismiss();
  }
}
