import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ElementRef,
  EventEmitter,
  forwardRef,
  Input,
  OnDestroy,
  OnInit,
  Output,
  TemplateRef,
  ViewChild,
  ViewEncapsulation
} from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormControl, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { BixiI18nService, IEditableI18n } from '@bixi/core/i18n';
import { log } from '@bixi/core/utils';
import cloneDeep from 'lodash.clonedeep';
import { NzSafeAny } from 'ng-zorro-antd/core/types/any';
import { NzSelectComponent } from 'ng-zorro-antd/select';
import { Observable, of, Subscription } from 'rxjs';
import {
  IEditableCol,
  IEditableRow,
  IEditableSelectCol,
  IEditableSelectOption,
  IEditableSelectOptions,
  IValidatorError
} from './editable-table.type';
import { EditableTemplateService } from './editable-template.service';

export type IOnTouchedType = () => NzSafeAny;
export type IOnChangeType = (value: NzSafeAny) => void;

@Component({
  selector: 'bixi-editable-table',
  exportAs: 'bixiEditableTable',
  host: { '[class.bixi-editable-table]': 'true' },
  templateUrl: 'editable-table.component.html',
  providers: [
    EditableTemplateService, {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => BixiEditableTableComponent),
      multi: true
    }],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class BixiEditableTableComponent implements ControlValueAccessor, OnInit, OnDestroy {
  isValid: boolean = true;
  form: FormGroup;
  editedRows: IEditableRow[] = [];
  editRow: IEditableRow;
  cacheRow: IEditableRow | null = null;
  editRowIndex: number | null = null; // 当前编辑行数
  _rows: IEditableRow[] = [];
  addText: string;

  i18nSubscription = new Subscription();
  editTranstions: IEditableI18n;

  @Input()
  get rows() {
    return this._rows;
  }
  set rows(value: IEditableRow[]) {
    this._rows = cloneDeep(value);
  }

  _cols: IEditableCol[] = [];
  @Input()
  get cols() {
    return this._cols;
  }
  set cols(cols: IEditableCol[]) {
    this._cols = cols.map((col) => {
      col = col as IEditableSelectCol;
      const { options } = col;
      if (options) {
        let _options: IEditableSelectOptions;
        if (!(options instanceof Observable)) {
          _options = of(options);
        } else {
          _options = options;
        }
        col.options = _options;
      }
      return col;
    });
  }
  @Input() scroll = { y: '300px' };
  @Input() dragable = false;
  @Input() footer?: TemplateRef<{}> | string | null;
  @Input() validate: (fg: FormGroup, rows: IEditableRow[], row: IEditableRow | null) => { key: string; message: string }[];

  @Output() deleteRow: EventEmitter<IEditableRow> = new EventEmitter<IEditableRow>();
  @Output() addRow: EventEmitter<FormGroup> = new EventEmitter<FormGroup>();
  @Output() rowsChange: EventEmitter<IEditableRow[]> = new EventEmitter<IEditableRow[]>();
  @Output() dirtyRowsChange: EventEmitter<IEditableRow[]> = new EventEmitter<IEditableRow[]>();

  @ViewChild('editContent') firstEditingEle: ElementRef | NzSelectComponent;

  // tslint:disable-next-line: no-empty
  onTableChange: IOnChangeType = () => { };
  // tslint:disable-next-line: no-empty
  onTableTouched: IOnTouchedType = () => { };

  constructor(
    private fb: FormBuilder,
    private i18n: BixiI18nService,
    private cdr: ChangeDetectorRef
  ) { }

  getSelectOptionName(options: IEditableSelectOption[], key: string) {
    const target = options.find((option) => option.value === key);
    if (target) {
      return target.name;
    }
    return key;
  }

  ngOnInit() {
    this.form = this.fb.group({});
    this.collectFormGroup();
    if (!this.editTranstions) {
      this.i18nSubscription = this.i18n.localeChange.subscribe(() => this.setTranslation());
    }
  }

  private setTranslation() {
    this.editTranstions = this.i18n.getLocaleData('editableTable') || {};
    this.addText = this.editTranstions.addText;
    this.cdr.markForCheck();
  }

  get isEditing() {
    return this.editRowIndex !== null;
  }

  private collectFormGroup() {
    if (this.cols) {
      this.cols.forEach((col) => {
        this.form.addControl(col.key, new FormControl(null, col.validators));
      });
    }
  }

  private checkValidity() {
    this.cols.forEach((col) => {
      if (col.editable !== false) {
        const control = this.form.get(col.key);
        if (control) {
          control.markAsDirty();
          control.updateValueAndValidity();
        }
      }
    });
    if (this.validate) {
      const compose = this.validate(this.form, this._rows, (this.isEditing) ? {
        ...this.editRow,
        ...this.form.value
      } : null);
      if (!compose) return;
      compose.forEach((item: IValidatorError) => {
        const control = this.form.controls[item.key];
        if (!control) {
          log('bixi-editable-table', `校验 ${item.key} 未找到相对于控制器, 请查看校验返回数据是否和列配置对应！`);
        }
        control.setErrors({
          message: item.message
        });
      });
    }
  }

  private startEdit(row: IEditableRow, index: number): void {
    if (this.isEditing && this.editDirty) {
      return; // 不可多行编辑
    }
    this.form.reset();
    this.cacheRow = { ...row };
    this.editRow = { ...row };
    this.editRowIndex = index;
    this.form.patchValue(row);
    setTimeout(() => {
      if (this.firstEditingEle) {
        if (this.firstEditingEle instanceof NzSelectComponent) {
          this.firstEditingEle.focus();
        } else {
          (this.firstEditingEle.nativeElement as HTMLInputElement).focus();
        }
      }
    });
  }

  get editDirty() {
    return this.form.dirty;
  }

  reset() {
    this.editRow = {};
    this.cacheRow = {};
    this.editedRows = [];
    this.stopEdit();
  }

  private stopEdit(): void {
    this.editRowIndex = null;
  }

  onConfirm() {
    if (!this.isEditing) return;
    this.checkValidity();
    if (!this.form.valid) {
      return;
    }
    if (this.isEditing) {
      const saveRow = {
        ...this.editRow,
        ...this.form.value
      };
      this.rows.splice(this.editRowIndex || 0, 1, saveRow);
      this.collectRowChange(saveRow);
      this.comfirmChange();
      this.onTableChange(this.rows);
    }
    this.stopEdit();
  }

  onCancel() {
    if (this.isEditing) {
      if (this.cacheRow) {
        this.rows.splice(this.editRowIndex || 0, 1, this.cacheRow);
      } else {
        // cacheRow 为空，表示此次取消是新建内容取消
        this.rows.splice(this.editRowIndex || 0, 1);
      }
      this.stopEdit();
    }
  }

  onClear(input: HTMLInputElement, key: string) {
    input.value = '';
    this.form.controls[key].markAsDirty();
    this.form.patchValue({
      [key]: ''
    });
    this.form.controls[key].updateValueAndValidity();
    this.onChange(input.value, key);
  }

  onChange(value: string, key: string) {
    // 无效的 valueChange（patchValue） 不触发
    if (typeof this.editRow === 'object' && !this.rowEqual(this.editRow)) {
      this.editRow[key] = value;
      if (!value) return;
      const changedCol = this.cols.find(col => col.key === key);
      if (changedCol && changedCol.onChange) {
        changedCol.onChange(this.form);
      }
    }
  }

  private rowEqual(change: { [key: string]: NzSafeAny; }): boolean {
    let rtn = true;
    if (Object.keys(change).length !== Object.keys(this.form.value).length) return false;
    Object.keys(change).forEach((key) => {
      if (this.form.get(key)?.value !== change[key]) {
        rtn = false;
      }
    });
    return rtn;
  }

  onDelete(index: number) {
    if (this.isEditing) {
      return;
    }
    this.collectRowChange({
      ...this.rows[index],
      __deleted: true
    });
    const row = this.rows.splice(index, 1);
    this.comfirmChange();
    this.deleteRow.emit(row);
  }

  private collectRowChange(row: IEditableRow) {
    if (row) {
      this.editedRows.push(row);
    }
  }

  comfirmChange() {
    this.rowsChange.emit(this.rows);
    this.dirtyRowsChange.emit(this.editedRows);
  }

  drop(event: CdkDragDrop<IEditableRow[]>): void {
    moveItemInArray(this._rows, event.previousIndex, event.currentIndex);
    this.comfirmChange();
  }

  onAddRow() {
    if (this.isEditing) {
      return;
    }
    this._rows = [
      ...this.rows,
      {}
    ];
    this.startEdit({}, this._rows.length - 1);
    this.cacheRow = null;
    setTimeout(() => {
      // 因为 rows 变化渲染过程中会改变 form 中的值，这里等渲染完成后再赋值
      this.addRow.emit(this.form);
    });
  }

  writeValue(value: IEditableRow[]): void {
    this.rows = value;
    this.cdr.markForCheck();
  }

  registerOnChange(fn: IOnChangeType): void {
    this.onTableChange = fn;
  }

  registerOnTouched(fn: IOnTouchedType): void {
    this.onTableTouched = fn;
  }

  ngOnDestroy() {
    this.i18nSubscription.unsubscribe();
  }
}
