import { Component, Emit, Prop, PropSync, Watch, Vue } from 'vue-property-decorator';
import { omitBy, isNil, isEqual, cloneDeep } from 'lodash';

import { QPagination } from '@/models/models-ui';
import { SearchFilter, UrlQueryParams } from '@/recruits/models'

interface UrlIntegratedSearchComponent {
  loading: boolean;
  pagination: QPagination;
  fetchData(query: UrlQueryParams): Promise<{ totalRows: number | undefined }>;
  refresh(): void;
  onRequest(props: any): Promise<void>;
  executeSearch(): void;
  handleSortClick({sortBy, descending} : { sortBy: string, descending: boolean }): void;
}

@Component({})
export default class UrlIntegratedSearch extends Vue implements UrlIntegratedSearchComponent {

  public loading = false;
  private isMounted = false;
  protected searchOnMounted = true;

  public pagination: QPagination = {
    sortBy: '',
    descending: false,
    page: 1,
    rowsPerPage: 10,
    rowsNumber: 1000,
  };

  protected filter = new SearchFilter();
  private lastSearchFilter: (SearchFilter & { sortBy?: string; descending?: boolean }) | null = null;

  mounted() {
    this.isMounted = true;
    if (this.needToUpdateParamsFromRoute()) {
      this.updateParamsFromRoute();
    }

    if (this.searchOnMounted) this.executeSearch();
  }

  @Emit('loadingStateChange')
  @Watch('loading')
  private loadingStateWatcher(loading: boolean) { }

  @Watch('$route', { immediate: true })
  private routeWatcher(to: any): void {
    // При переходе по кнопке браузера 'Назад' нужно было отследить изменения в route.
    // Если листать страницы данных а потом нажать кнопку браузера 'назад' то mounted() уже
    // не срабатывает и данные по параметрам предыдущей страницы (или фильтров) не загружаются.
    // Следующие проверки должны отслеживать такой случай и загружать соответствующие данные.
    // Проверка isMounted для того что бы загрузка данных не выполнялась в случае когда watcher триггериться до метода mount()
    if (
      this.isMounted &&
      !this.loading &&
      this.needToUpdateParamsFromRoute()
    ) {
      this.updateParamsFromRoute();
      this.executeSearch();
    }
  }  
  
  private prepareRequestParams(
    pagination?: QPagination,
    filters?: SearchFilter
  ): UrlQueryParams {
    if (pagination === undefined) pagination = this.pagination;
    if (filters === undefined) filters = this.filter;

    return Object.assign(
      {
        page: String(pagination.page || 1),
        perpage: String(pagination.rowsPerPage || 10),
        sortby: pagination.sortBy || undefined,
        descending: pagination.sortBy ? String(pagination.descending) : undefined,
        search: filters.search || undefined,
      },
      filters.prepareRequestParams(),
    );
  }

  private updateParamsFromRoute() {
    const query = this.$route.query as UrlQueryParams;
    if (query.page) {
      this.pagination.page = Number(query.page);
    }
    if (query.perpage) {
      this.pagination.rowsPerPage = Number(query.perpage);
    }
    if (query.sortby) {
      this.pagination.sortBy = String(query.sortby);
    }
    if (query.descending) {
      this.pagination.descending = query.descending === 'true';
    }
    this.filter.updateParamsFromRoute({ ...query });
  }

  private needToUpdateParamsFromRoute(): boolean {
    const routeParams = omitBy(Object.assign({}, this.$route.query), isNil);
    const currentParams = omitBy(
      Object.assign({}, this.prepareRequestParams()),
      isNil
    );
    return !isEqual(routeParams, currentParams);
  }  

  private updateRouteFromParams() {
    const queryParams = omitBy(
      Object.assign({}, this.$route.query, this.prepareRequestParams()),
      isNil
    );
    if (!isEqual(this.$route.query, queryParams)) {
      this.$router.replace({ query: { ...queryParams } });
    }
  }  

  private async loadData(
    pagination: QPagination,
    filters: SearchFilter
  ) {
    this.loading = true;
    try {
      const { totalRows } = await this.fetchData(this.prepareRequestParams(pagination, filters));
      this.pagination.page = pagination.page;
      this.pagination.rowsPerPage = pagination.rowsPerPage;
      this.pagination.sortBy = pagination.sortBy;
      this.pagination.descending = pagination.descending;
      this.pagination.rowsNumber = totalRows;
      this.updateRouteFromParams();
    } catch (error) {
      console.error('Error requesting data', error);
    } finally {
      this.loading = false;
    }
  }

  public async fetchData(query: UrlQueryParams): Promise<{ totalRows: number | undefined }> {
    // Типа абстрактный метод
    throw new Error("fetchData method not implemented");
  }

  public refresh(): void {
    this.loadData(
      this.pagination,
      this.lastSearchFilter ? this.lastSearchFilter : this.filter
    );
  }  

  public async onRequest(props: any) {
    let { page } = props.pagination;
    // сбросить номер страницы если фильтры или сортировка изменились
    const newFilter = Object.assign(this.filter, {
      sortBy: this.pagination.sortBy,
      descending: this.pagination.descending,
    });
    if (
      this.lastSearchFilter !== null &&
      !isEqual(this.lastSearchFilter, newFilter)
    ) {
      page = 1;
    }
    this.lastSearchFilter = cloneDeep(newFilter);
    await this.loadData({ ...props.pagination, page }, this.filter);
  }
  
  public executeSearch(): void {
    this.onRequest({
      pagination: this.pagination,
    });
  }  

  public handleSortClick({
    sortBy,
    descending,
  }: {
    sortBy: string;
    descending: boolean;
  }) {
    this.pagination.sortBy = sortBy;
    this.pagination.descending = descending;
    this.executeSearch();
  }
}