import { Component, Emit, Prop, Watch, PropSync } from 'vue-property-decorator';
import Vue from 'vue';
import BaseSearchBar from '@components/common/base-search-bar/index.vue';
import { QPagination } from '@/models/models-ui';
import get from 'lodash/get';
import pick from 'lodash/pick';
import has from 'lodash/has';
import isEmpty from 'lodash/isEmpty';
import isEqual from 'lodash/isEqual';
import isFunction from 'lodash/isFunction';
import isArray from 'lodash/isArray';
import isString from 'lodash/isString';
import { TablePagination } from '@models/base/TablePagination';
import { RTableColumn, RTableSort } from '@/interfaces';
import ComponentImporter from '@/recruits/utils/component-importer';
import upperFirst from 'lodash/upperFirst';
import camelCase from 'lodash/camelCase';

interface Pagination {
  page: number;
  sortBy: string;
  descending: boolean;
  rowsPerPage: number;
}

const typeClasses: Record<Required<RTableColumn>['type'], string> = {
  date: 'Date',
  action: 'Action',
  regular: 'Regular',
  bold: 'Bold',
  address: 'Address',
  'gray-numbrts': 'GrayNumbr',
};

const alignClasses: Record<Required<RTableColumn>['align'], string> = {
  right: 'AlignRiht',
  left: 'AlignLeft',
  center: 'AlignCenter',
  stretch: 'AlignStretch',
};

@Component({
  components: { BaseSearchBar },
  methods: {
    isEmpty,
  },
})
export default class RTable extends Vue {
  isEmpty: typeof isEmpty;
  @Prop({
    type: Boolean,
    default: false,
  })
  showToggleMy?: boolean;

  /**
   * Использовать стандартный поиск
   */
  @Prop() useSearch?: boolean;
  /**
   * Скрыть кнопку обновления
   */
  @Prop({ default: false }) hideRefresh?: boolean;
  /**
   * Обработчик двойного клика на строку
   */
  @Prop() rowsHandler?: { (row: unknown): void };
  /**
   * Список опций доступных строк на странице
   */
  @Prop() rowsPerPageOptions?: number[];
  /**
   * Строка поиска по умолчанию
   */
  @Prop({ default: '' }) value?: string;
  /**
   * Список доступных колонок
   */
  @Prop() columns?: RTableColumn[];
  @Prop() sortBy?: RTableSort[];
  @Prop() data?: unknown[];
  @Prop() showColumns?: string[];
  @Prop() pagination?: QPagination | null;
  /**
   * Загрузка
   */
  @Prop() loading?: boolean;
  @Prop() title?: string;
  @Prop() maxRowHeight?: string;
  @Prop({
    default: false,
  })
  binarySort?: boolean;

  @Prop({ type: Boolean, default: false }) gridHeader: boolean;
  @Prop() selection?: 'single' | 'multiple';
  @Prop({ type: Boolean, default: false }) saveQuery?: boolean;
  @Prop({ type: Function }) checkSelectable?: { (row: unknown): boolean };

  get hasSelection(): boolean {
    if (!this.selection) return false;
    return ['single', 'multiple'].includes(this.selection);
  }

  /**
   * Колонки
   */
  get columnsByName(): Record<string, RTableColumn> {
    return (
      this.columns?.reduce((cbn, column) => {
        if (column.name) cbn[column.name] = column;
        return cbn;
      }, {} as Record<string, RTableColumn>) || {}
    );
  }

  get visibleColumns(): RTableColumn[] {
    if (!this.showColumns) return this.columns || [];
    const columns: RTableColumn[] = [];
    this.columns?.forEach((col) => {
      if (this.showColumns?.includes(col.name)) {
        columns.push(col);
      }
    });
    return columns;
  }

  get sortableColumns(): RTableColumn[] {
    return this.columns?.filter(({ sortable }) => !!sortable) || [];
  }

  get gridЕemplateСolumns(): string {
    const columns = this.visibleColumns.map(({ width }) => {
      return width || '1fr';
    });
    if (this.hasSelection) columns.unshift('42px');
    return columns.join(' ');
  }

  @PropSync('selected', {
    default: () => [],
  })
  syncedSelected?: unknown[];

  @PropSync('value', {
    default: '',
  })
  inputSearch: string;

  public getSortButtonClasses(target?: string): Record<string, boolean> {
    return {
      'RTable-SortBtn_CurrentSort': this.pagination?.sortBy === target,
      'RTable-SortBtn_Descending': !!this.pagination?.descending,
    };
  }

  @Emit('requestTable')
  public sortHandler(sortBy?: string): void {
    if (this.pagination) {
      if (this.pagination.sortBy !== sortBy) {
        this.pagination.sortBy = sortBy;
        this.pagination.descending = false;
      } else {
        if (!this.pagination.descending) {
          this.pagination.descending = true;
        } else {
          this.pagination.descending = true;
          this.pagination.sortBy = undefined;
        }
      }
    }
    this.updateRouteQueryString(this.pagination);
  }

  get allRowsWereSelected(): boolean {
    if (isEmpty(this.data)) return false;
    return isEqual(this.syncedSelected, this.data);
  }

  toggleSelectAll(ev: Event): void {
    this.syncedSelected = get(ev, 'target.checked')
      ? this.data?.filter(this.isSelectable)
      : [];
  }

  get rowsPerPageOptionsCustom(): number[] {
    return this.rowsPerPageOptions || [3, 5, 10, 15, 20, 25, 50];
  }

  paginationLabelCustom(pagination: TablePagination): string {
    const page = pagination.page;
    const rowsPerPage = pagination.rowsPerPage;
    const rowsNumber = pagination.rowsNumber || Number(this.data?.length);
    const firstNumber = (page - 1) * rowsPerPage + rowsPerPage;

    return `${(page - 1) * rowsPerPage + 1} - ${
      firstNumber < rowsNumber ? firstNumber : rowsNumber
    } ${this.$t('base.of').toString().toLowerCase()} ${rowsNumber}`;
  }

  private crop<T>(field: T[], start = 0, end = 2): T[] {
    return field.slice(start, end);
  }

  private difference<T>(field: T[], start = 2): T[] {
    return this.crop(field, start, field.length);
  }

  getFields(column: Partial<RTableColumn>): Partial<RTableColumn>[] {
    if (isArray(column.fields) && !isEmpty(column.fields)) return column.fields;
    return [column];
  }

  /**
   * Получение комбенированного лейбла со всех субколонок
   * @param {Partial<RTableColumn>} column Настройки колонки
   * @param {boolean} full Вывести длинный лейбл если есть
   * @returns {RTableColumn['label' | 'fullLabel']} строка или объект i18n
   */
  getCombinedLabel(
    column: Partial<RTableColumn>,
    full = false
  ): RTableColumn['label' | 'fullLabel'] {
    if ((full && column.fullLabel) || column.label)
      return (full && column.fullLabel) || column.label || '';
    return this.getFields(column)
      .flatMap(({ label, fullLabel }) => (full && fullLabel) || label || [])
      .join(' / ');
  }

  getAlignClass(align: RTableColumn['align'], base = 'RTable-Col'): string {
    if (!align) return '';
    return `${base}_${alignClasses[align]}`;
  }

  getTypeClass(type: RTableColumn['type'], base = 'RTable-Col'): string {
    if (!type) return '';
    return `${base}_${typeClasses[type]}`;
  }

  getCamelCaseName(name = ''): string {
    return upperFirst(camelCase(name));
  }

  isSelectable(row: unknown): boolean {
    if (!isFunction(this.checkSelectable)) return true;
    return this.checkSelectable(row);
  }

  field(
    row: unknown,
    column: Partial<RTableColumn>,
    def?: unknown
  ): unknown | unknown[] {
    let field: unknown = def || '';

    if (isFunction(column.field)) {
      field = column.field(row);
    } else if (isString(column.field)) {
      field = get(row, `${column.field}`);
    }

    return field;
  }

  format(
    row: unknown,
    column: Partial<RTableColumn>,
    def?: unknown
  ): unknown | unknown[] {
    const field = this.field(row, column, def);

    let format = field;

    if (isString(column.formatField)) {
      if (isArray(field)) {
        format = field.map((item) => {
          const value = get(item, `${column.formatField}`, '');
          const defaultValue = get(item, `${column.defaultFormatField}`, '');
          return value || defaultValue;
        });
        console.log(format);
      } else {
        const value = get(field, `${column.formatField}`, '');
        const defaultValue = get(field, `${column.defaultFormatField}`, '');
        format = value || defaultValue;
      }
    } else if (isFunction(column?.format)) {
      format = column.format(field);
    }

    return format;
  }

  handler(ev: MouseEvent, row: unknown, column: Partial<RTableColumn>, item?: unknown): void {
    if (!isFunction(column.handler)) return;

    const field = this.field(row, column, item);

    column.handler(
      has(column, 'handlerField')
        ? get(field, `${column.handlerField}`)
        : field,
      {
        targetBlank: ev.metaKey || ev.ctrlKey,
      }
    );
  }

  has(column?: RTableColumn, name?: string): boolean {
    return has(column, `${name}`);
  }

  rowsHandlerCast(row: unknown): void {
    if (!!this.rowsHandler && typeof this.rowsHandler === 'function') {
      this.rowsHandler(row);
    }
  }

  requestHandler(value: { pagination: Pagination }): void {
    // QTable requestServerInteraction
    // Обработка данного события сделана с изменением имени события request -> requestTable, из-за того,
    // что обычный request всплывает несколько раз для случая, когда мы перешли на каку-то страницу поиска
    // и изменили фильтр
    this.updateRouteQueryString(value.pagination);
    this.$emit('request', value);
  }

  private updateRouteQueryString(pagination: any) {
    const paramsQuery: any = pick(pagination, [
      'page',
      'sortBy',
      'descending',
      'rowsPerPage',
    ]);

    const routeQuery = { ...this.$route.query };

    Object.keys(paramsQuery).forEach((keyQuery: string) => {
      if (paramsQuery[keyQuery] === null) {
        delete paramsQuery[keyQuery];
        delete routeQuery[keyQuery];
      }
    });

    if (this.saveQuery) {
      this.$router.push({
        query: {
          ...routeQuery,
          ...paramsQuery,
        },
      });
    }
  }

  @Watch('visibleColumns', { immediate: true })
  onVisibleColumnsChange(columns: RTableColumn[]): void {
    this.fetchComponents(columns);
  }

  components: Record<string, unknown> = {};

  fetchComponents(columns: RTableColumn[]): void {
    const componentsObj = this.components ? this.components : {};

    columns
      .flatMap((col) => {
        const res = [col];
        if (col.fields) {
          res.unshift(...col.fields);
        }
        return res;
      })
      .forEach((col) => {
        const pathComponent = get(col, 'componentPath');

        if (!pathComponent || get(this.components, `${pathComponent}`)) {
          return;
        } // не выполняем загрузку, если нет pathComponent или уже загружено
        componentsObj[pathComponent] = {};
        this.$set(
          componentsObj,
          pathComponent,
          ComponentImporter.registerComponent(
            pathComponent,
            'components/common/cells/',
            false
          )
        );
      });
    this.components = componentsObj;
  }

  @Emit('update')
  updateHandler() {
    //
  }

  @Emit('update:pagination')
  paginationHandler(value: any) {
    //
  }

  @Emit('search')
  searchHandler() {
    //
  }

  @Emit('query')
  queryHandler() {
    //
  }
}
