import {
  Component,
  ContentChild,
  EventEmitter,
  Input,
  OnInit,
  Output,
  TemplateRef,
} from '@angular/core';
import {
  ResolvedTableColumn,
  TableColumn,
  TableColumnFuzzyFilter,
  TableRequireAnyPermissions,
} from 'src/app/modules/shared/components/table/table.model';
import { TableLazyLoadEvent } from 'primeng/table';
import { RowsPerPageOption } from 'src/app/service/filter.service';
import { Filter } from 'src/app/model/graphql/filter/filter.model';
import { Sort, SortOrder } from 'src/app/model/graphql/filter/sort.model';
import { FormControl, FormGroup } from '@angular/forms';
import { debounceTime, Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Logger } from 'src/app/service/logger.service';
import { AuthService } from 'src/app/service/auth.service';
import { ToastService } from 'src/app/service/toast.service';
import {
  CustomActionsDirective,
  CustomRowActionsDirective,
} from 'src/app/modules/shared/components/table/table';
import { DbModel } from 'src/app/model/entities/db-model';

const logger = new Logger('TableComponent');

@Component({
  selector: 'app-table',
  templateUrl: './table.component.html',
  styleUrl: './table.component.scss',
})
export class TableComponent<T> implements OnInit {
  private _rows: T[] = [];
  @Input({ required: true }) get rows() {
    return this._rows;
  }

  set rows(value: T[]) {
    this._rows = value;
    this.resolveColumns();
  }

  @Input({ required: true }) columns: TableColumn<T>[] = [];
  @Input() fuzzyFilter?: TableColumnFuzzyFilter;

  get visibleColumns() {
    return this.columns.filter(x => x.visible);
  }

  @Input({ required: true }) rowsPerPageOptions: RowsPerPageOption[] = [];
  @Input({ required: true }) countHeaderTranslation!: string;
  @Input({ required: true }) requireAnyPermissions!: TableRequireAnyPermissions;

  @Input() totalCount = 0;

  @Input() take = 10;
  @Output() takeChange = new EventEmitter<number>();

  @Input() skip = 0;
  @Output() skipChange = new EventEmitter<number>();

  @Input() defaultFilter: Filter[] = [];
  @Input() filter: Filter[] = [];
  @Output() filterChange = new EventEmitter<Filter[]>();

  @Input() sort: Sort[] = [];
  @Output() sortChange = new EventEmitter<Sort[]>();

  @Input() rowDisabled: (row: T) => boolean = () => false;

  // eslint-disable-next-line @angular-eslint/no-output-native
  @Output() load = new EventEmitter<void>();
  @Input() loadHistory?: (id: number) => Observable<DbModel[] | undefined>;
  @Input() loading = true;

  @Input() dataKey = 'id';
  @Input() responsiveLayout: 'stack' | 'scroll' = 'stack';
  @Input() selectableColumns = true;

  @Input() history = false;
  @Input() create = false;
  @Input() update = false;
  @Input() createBaseUrl = '';
  @Input() updateBaseUrl = '';

  @Output() delete: EventEmitter<T> = new EventEmitter<T>();
  @Output() restore: EventEmitter<T> = new EventEmitter<T>();

  @ContentChild(CustomActionsDirective, { read: TemplateRef })
  customActions!: TemplateRef<never>;

  @ContentChild(CustomRowActionsDirective, { read: TemplateRef })
  customRowActions!: TemplateRef<never>;

  get customRowActionsVisible() {
    return !!this.customRowActions;
  }

  protected resolvedColumns: ResolvedTableColumn<T>[][] = [];
  protected filterForm!: FormGroup;
  protected defaultFilterForm!: FormGroup;

  private hide_deleted_filter: Filter = { deleted: { equal: false } };

  get showDeleted() {
    return !this.filter.some(
      f => JSON.stringify(f) === JSON.stringify(this.hide_deleted_filter)
    );
  }

  get showFilters() {
    return this.columns.some(x => x.filterable);
  }

  protected unsubscriber$ = new Subject<void>();

  protected hasPermissions = {
    read: false,
    create: false,
    update: false,
    delete: false,
    restore: false,
  };

  constructor(
    private auth: AuthService,
    private toast: ToastService
  ) {}

  async ngOnInit() {
    this.hasPermissions = {
      read: await this.auth.hasAnyPermissionLazy(
        this.requireAnyPermissions.read ?? []
      ),
      create: await this.auth.hasAnyPermissionLazy(
        this.requireAnyPermissions.create ?? []
      ),
      update: await this.auth.hasAnyPermissionLazy(
        this.requireAnyPermissions.update ?? []
      ),
      delete: await this.auth.hasAnyPermissionLazy(
        this.requireAnyPermissions.delete ?? []
      ),
      restore: await this.auth.hasAnyPermissionLazy(
        this.requireAnyPermissions.restore ?? []
      ),
    };

    this.buildDefaultFilterForm();
    this.setDefaultFilterForm();
    this.setFilterForm();
    this.loading = false;
  }

  resolveColumns() {
    const columns = this.columns
      .map(x => {
        if (x.visible === undefined || x.visible === null) {
          x.visible = true;
        }
        return x;
      })
      .filter(x => !x.hidden && x.visible === true);

    if (!this.rows || this.rows.length == 0) {
      this.resolvedColumns = [];
      return;
    }

    const resolvedColumns: ResolvedTableColumn<T>[][] = [];
    this.rows.forEach(row => {
      const resolvedRow: ResolvedTableColumn<T>[] = [];
      columns.forEach(column => {
        resolvedRow.push({
          value: column.value(row),
          data: row,
          column,
        });
      });
      resolvedColumns.push(resolvedRow);
    });

    this.resolvedColumns = resolvedColumns;
  }

  loadData(event: TableLazyLoadEvent) {
    logger.trace('lazy load event', event);

    const { rows, first, sortField, sortOrder } = event;
    const reload =
      (rows !== undefined && rows !== null) ||
      (first !== undefined && first !== null) ||
      (sortField !== undefined && sortField !== null) ||
      (sortOrder !== undefined && sortOrder !== null);

    if (rows !== undefined && rows !== null) {
      this.takeChange.emit(rows);
    }

    if (first !== undefined && first !== null) {
      this.skipChange.emit(first);
    }

    if (
      sortField !== undefined &&
      sortField !== null &&
      sortOrder !== undefined &&
      sortOrder !== null
    ) {
      if (sortField instanceof Array) {
        this.sortChange.emit(
          sortField.map(x => ({
            [x]: sortOrder === 1 ? SortOrder.ASC : SortOrder.DESC,
          }))
        );
      } else {
        this.sortChange.emit([
          { [sortField]: sortOrder === 1 ? SortOrder.ASC : SortOrder.DESC },
        ]);
      }
    }

    if (reload) {
      this.load.emit();
    }
  }

  toggleShowDeleted() {
    if (!this.showDeleted) {
      this.filter.splice(this.filter.indexOf(this.hide_deleted_filter), 1);
    } else {
      this.filter.push(this.hide_deleted_filter);
    }

    this.filterChange.emit(this.filter);
    this.load.emit();
  }

  resetSort() {
    this.sort = [];
    this.sortChange.emit(this.sort);
  }

  resetFilters() {
    this.filterForm.reset();
  }

  buildDefaultFilterForm() {
    this.defaultFilterForm = new FormGroup({});
    if (this.fuzzyFilter) {
      this.defaultFilterForm.addControl(
        'fuzzy',
        new FormControl<string | undefined>(undefined)
      );
    }

    this.columns
      .filter(x => x.filterable)
      .forEach(x => {
        let control!: FormControl;

        if (x.type === 'text') {
          control = new FormControl<string | undefined>(undefined);
        } else if (x.type === 'number') {
          control = new FormControl<number | undefined>(undefined);
        } else if (x.type === 'bool') {
          control = new FormControl<boolean | undefined>(undefined);
        } else if (x.type === 'date') {
          control = new FormControl<Date | undefined>(undefined);
        } else {
          control = new FormControl<unknown | undefined>(undefined);
        }
        this.defaultFilterForm.addControl(x.name, control);
      });
  }

  setDefaultFilterForm() {
    this.defaultFilter.forEach(x => {
      Object.keys(x).forEach(key => {
        const value = x[key];
        if (!(key in this.defaultFilterForm.controls)) {
          return;
        }
        if (typeof value === 'object' && value !== null) {
          Object.keys(value).forEach(subKey => {
            this.defaultFilterForm.get([key])?.setValue(value[subKey]);
          });
        } else {
          this.defaultFilterForm.get(key)?.setValue(value);
        }
      });
    });
  }

  setFilterForm() {
    this.filterForm = this.defaultFilterForm;

    this.filter.forEach(f => {
      Object.keys(f).forEach(key => {
        const value = f[key];
        if (this.filterForm.contains(key)) {
          if (typeof value === 'object' && value !== null) {
            Object.keys(value).forEach(subKey => {
              this.filterForm.get(key)?.setValue(value[subKey]);
            });
          } else {
            this.filterForm.get(key)?.setValue(value);
          }
        }
      });
    });

    this.filterForm.valueChanges
      .pipe(takeUntil(this.unsubscriber$), debounceTime(200))
      .subscribe(changes => {
        logger.trace('Filter input', changes);
        if (this.filterForm.disabled) {
          return;
        }

        const filter = Object.keys(changes)
          .filter(
            key =>
              changes[key] !== undefined &&
              changes[key] !== null &&
              changes[key] !== ''
          )
          .map(key => {
            if (key === 'fuzzy') {
              if (!this.fuzzyFilter) return {};
              return {
                fuzzy: {
                  term: changes[key],
                  fields: this.fuzzyFilter?.columns,
                  threshold: this.fuzzyFilter?.threshold,
                },
              };
            }

            const column = this.columns.find(x => x.name === key);
            if (!column || !column.filterable) {
              return {};
            }

            let value = changes[key];
            let defaultFilterMode = 'contains';
            switch (column.type) {
              case 'number':
                defaultFilterMode = 'equal';
                value = +value;
                break;
              case 'bool':
                defaultFilterMode = 'equal';
            }

            const filterMode = column.filterMode || defaultFilterMode;

            if (column.filterSelector) {
              return column.filterSelector(filterMode, value);
            }

            return { [key]: { [filterMode]: value } };
          });

        if (!this.showDeleted) {
          filter.push(this.hide_deleted_filter);
        }

        this.filter = filter;
        this.filterChange.emit(filter);
        this.load.emit();
      });
  }

  copy(val: T | T[keyof T] | string) {
    navigator.clipboard.writeText(val as string).then(() => {
      this.toast.info('common.copied', 'common.copied_to_clipboard');
    });
  }
}
