import type { ComputedRef, Ref } from 'vue';

import type { PaginationProps } from '../types/pagination';
import type { BasicTableProps, FetchParams, SorterResult } from '../types/table';

import { computed, nextTick, onMounted, reactive, ref, unref, watch, watchEffect } from 'vue';

import { buildUUID, isBoolean, isFunction, isObject } from '@jnpf/utils';

import { useTimeoutFn } from '@vueuse/core';
import { cloneDeep, get, merge } from 'lodash-es';

import { FETCH_SETTING, PAGE_SIZE, ROW_KEY } from '../const';

interface ActionType {
  clearSelectedRowKeys: () => void;
  expandAll: () => void;
  getFieldsValue: () => Recordable;
  getPaginationInfo: ComputedRef<boolean | PaginationProps>;
  getTreeConfig: ComputedRef<any>;
  setLoading: (loading: boolean) => void;
  setPagination: (info: Partial<PaginationProps>) => void;
  tableData: Ref<Recordable[]>;
}

interface SearchState {
  filterInfo: Record<string, string[]>;
  protoFilterInfo: Partial<Record<string, string[]>>;
  protoSortInfo: Recordable;
  sortInfo: Recordable;
}
export function useDataSource(
  propsRef: ComputedRef<BasicTableProps>,
  { clearSelectedRowKeys, expandAll, getFieldsValue, getPaginationInfo, getTreeConfig, setLoading, setPagination, tableData }: ActionType,
  emit: EmitType,
) {
  const searchState = reactive<SearchState>({
    filterInfo: {},
    protoFilterInfo: {},
    protoSortInfo: {},
    sortInfo: {},
  });
  const dataSourceRef = ref<Recordable[]>([]);
  const rawDataSourceRef = ref<Recordable>({});
  const fetchParams = ref<Recordable>({});

  watchEffect(() => {
    tableData.value = unref(dataSourceRef);
  });
  watch(
    () => unref(propsRef).dataSource,
    () => {
      const { api, dataSource } = unref(propsRef);
      !api && dataSource && (dataSourceRef.value = dataSource);
    },
    {
      deep: true,
      immediate: true,
    },
  );

  const getProtoFilterInfo = computed((): SorterResult => searchState.protoFilterInfo);
  const getProtoSortInfo = computed((): SorterResult => searchState.protoSortInfo);

  function handleTableChange(pagination: PaginationProps, filters: Partial<Recordable<string[]>>, sorter: SorterResult) {
    const { clearSelectOnPageChange, filterFn, sortFn } = unref(propsRef);
    if (clearSelectOnPageChange) {
      clearSelectedRowKeys();
    }
    setPagination(pagination);

    const params: Recordable = {};
    if (sorter && isFunction(sortFn)) {
      searchState.protoSortInfo = sorter;
      const sortInfo = sortFn(sorter);
      searchState.sortInfo = sortInfo;
      params.sortInfo = sortInfo;
    }

    if (filters && isFunction(filterFn)) {
      searchState.protoFilterInfo = filters;
      const filterInfo = filterFn(filters);
      searchState.filterInfo = filterInfo;
      params.filterInfo = filterInfo;
    }
    fetch(params);
  }

  function setTableKey(items: any[]) {
    if (!items || !Array.isArray(items)) return;
    items.forEach((item) => {
      if (!item[ROW_KEY]) {
        item[ROW_KEY] = buildUUID();
      }
      if (item.children && item.children.length > 0) {
        setTableKey(item.children);
      }
    });
  }

  const getAutoCreateKey = computed(() => {
    return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey;
  });

  const getRowKey = computed(() => {
    const { rowKey } = unref(propsRef);
    return unref(getAutoCreateKey) ? ROW_KEY : rowKey;
  });

  const getDataSourceRef = computed(() => {
    const dataSource = unref(dataSourceRef);
    if (!dataSource || dataSource.length === 0) {
      return unref(dataSourceRef);
    }
    if (unref(getAutoCreateKey)) {
      const firstItem = dataSource[0];
      const lastItem = dataSource[dataSource.length - 1];

      if (firstItem && lastItem && (!firstItem[ROW_KEY] || !lastItem[ROW_KEY])) {
        const data = cloneDeep(unref(dataSourceRef));
        data.forEach((item) => {
          if (!item[ROW_KEY]) {
            item[ROW_KEY] = buildUUID();
          }
          if (item.children && item.children.length > 0) {
            setTableKey(item.children);
          }
        });
        dataSourceRef.value = data;
      }
    }
    return unref(dataSourceRef);
  });

  async function updateTableData(index: number, key: string, value: any) {
    const record = dataSourceRef.value[index];
    if (record) {
      (dataSourceRef.value[index] as any)[key] = value;
    }
    return dataSourceRef.value[index];
  }

  function updateTableDataRecord(rowKey: number | string, record: Recordable): Recordable | undefined {
    const row = findTableDataRecord(rowKey);

    if (row) {
      for (const field in row) {
        if (Reflect.has(record, field)) row[field] = record[field];
      }
      return row;
    }
  }

  function deleteTableDataRecord(rowKey: number | number[] | string | string[]) {
    if (!dataSourceRef.value || dataSourceRef.value.length === 0) return;
    const rowKeyName = unref(getRowKey);
    if (!rowKeyName) return;
    const rowKeys = Array.isArray(rowKey) ? rowKey : [rowKey];

    function deleteRow(data, key) {
      const row: { data: []; index: number } = findRow(data, key);
      if (row === null || row.index === -1) {
        return;
      }
      row.data.splice(row.index, 1);

      function findRow(data, key) {
        if (data === null || data === undefined) {
          return null;
        }
        for (let i = 0; i < data.length; i++) {
          const row = data[i];
          let targetKeyName: string = rowKeyName as string;
          if (isFunction(rowKeyName)) {
            targetKeyName = rowKeyName(row);
          }
          if (row[targetKeyName] === key) {
            return { data, index: i };
          }
          if (row.children?.length > 0) {
            const result = findRow(row.children, key);
            if (result != null) {
              return result;
            }
          }
        }
        return null;
      }
    }

    for (const key of rowKeys) {
      deleteRow(dataSourceRef.value, key);
      deleteRow(unref(propsRef).dataSource, key);
    }
    setPagination({
      total: unref(propsRef).dataSource?.length,
    });
  }

  function insertTableDataRecord(record: Recordable | Recordable[], index?: number): Recordable[] | undefined {
    // if (!dataSourceRef.value || dataSourceRef.value.length == 0) return;
    index = index ?? dataSourceRef.value?.length;
    const _record = isObject(record) ? [record as Recordable] : (record as Recordable[]);
    unref(dataSourceRef).splice(index, 0, ..._record);
    return unref(dataSourceRef);
  }

  function findTableDataRecord(rowKey: number | string) {
    if (!dataSourceRef.value || dataSourceRef.value.length === 0) return;

    const rowKeyName = unref(getRowKey);
    if (!rowKeyName) return;

    const { childrenColumnName = 'children' } = unref(propsRef);

    const findRow = (array: any[]) => {
      let ret;
      array.some(function iter(r) {
        if (typeof rowKeyName === 'function') {
          if ((rowKeyName(r) as string) === rowKey) {
            ret = r;
            return true;
          }
        } else {
          if (Reflect.has(r, rowKeyName) && r[rowKeyName] === rowKey) {
            ret = r;
            return true;
          }
        }
        return r[childrenColumnName] && r[childrenColumnName].some((e) => iter(e));
      });
      return ret;
    };

    // const row = dataSourceRef.value.find(r => {
    //   if (typeof rowKeyName === 'function') {
    //     return (rowKeyName(r) as string) === rowKey
    //   } else {
    //     return Reflect.has(r, rowKeyName) && r[rowKeyName] === rowKey
    //   }
    // })
    return findRow(dataSourceRef.value);
  }

  async function fetch(opt?: FetchParams) {
    const { afterFetch, api, beforeFetch, defSort, fetchSetting, pagination, searchInfo, useSearchForm } = unref(propsRef);
    if (!api || !isFunction(api)) return;
    try {
      setLoading(true);
      const { pageField, sizeField, totalField, listField } = Object.assign({}, FETCH_SETTING, fetchSetting);
      let pageParams: Recordable = {};

      const { current = 1, pageSize = PAGE_SIZE } = unref(getPaginationInfo) as PaginationProps;
      const isNoPagination = (isBoolean(pagination) && !pagination) || isBoolean(getPaginationInfo);

      if (isNoPagination) {
        pageParams = {};
      } else {
        pageParams[pageField] = (opt && opt.page) || current;
        pageParams[sizeField] = pageSize;
      }

      const { filterInfo, sortInfo = {} } = searchState;

      let params: Recordable = merge(
        pageParams,
        useSearchForm ? getFieldsValue() : {},
        searchInfo,
        opt?.searchInfo ?? {},
        defSort,
        sortInfo,
        filterInfo,
        opt?.sortInfo ?? {},
        opt?.filterInfo ?? {},
      );
      if (beforeFetch && isFunction(beforeFetch)) {
        params = (await beforeFetch(params)) || params;
      }
      fetchParams.value = params;

      const res = await api(params);
      const data = res.data;
      rawDataSourceRef.value = data;

      const isArrayResult = Array.isArray(data);

      let resultItems: Recordable[] = isArrayResult ? data : get(data, listField);
      let resultTotal: number = 0;
      if (!isNoPagination) resultTotal = isArrayResult ? data.length : get(data, totalField);

      // 假如数据变少，导致总页数变少并小于当前选中页码，通过getPaginationRef获取到的页码是不正确的，需获取正确的页码再次执行
      if (Number(resultTotal)) {
        const currentTotalPage = Math.ceil(resultTotal / pageSize);
        if (current > currentTotalPage) {
          setPagination({
            current: currentTotalPage,
          });
          return await fetch(opt);
        }
      }

      if (afterFetch && isFunction(afterFetch)) {
        resultItems = (await afterFetch(resultItems)) || resultItems;
      }
      dataSourceRef.value = resultItems;
      setPagination({
        total: resultTotal || 0,
      });
      if (opt && opt.page) {
        setPagination({
          current: opt.page || 1,
        });
      }
      emit('fetchSuccess', {
        total: resultTotal,
        items: unref(resultItems),
      });
      return resultItems;
    } catch (error) {
      emit('fetchError', error);
      dataSourceRef.value = [];
      setPagination({
        total: 0,
      });
    } finally {
      const { isTreeTable } = unref(propsRef);
      nextTick(() => {
        if (isTreeTable && unref(getTreeConfig).expandAll) expandAll();
      });
      setLoading(false);
    }
  }

  function setTableData<T = Recordable>(values: T[]) {
    dataSourceRef.value = values as [];
  }

  function getDataSource<T = Recordable>() {
    return getDataSourceRef.value as T[];
  }

  function getRawDataSource<T = Recordable>() {
    return rawDataSourceRef.value as T;
  }

  function getFetchParams<T = Recordable>() {
    return fetchParams.value as T;
  }

  async function reload(opt?: FetchParams) {
    clearSelectedRowKeys();
    return await fetch(opt);
  }

  onMounted(() => {
    useTimeoutFn(() => {
      unref(propsRef).immediate && fetch();
    }, 16);
  });

  return {
    deleteTableDataRecord,
    fetch,
    findTableDataRecord,
    getAutoCreateKey,
    getDataSource,
    getDataSourceRef,
    getFetchParams,
    getProtoFilterInfo,
    getProtoSortInfo,
    getRawDataSource,
    getRowKey,
    handleTableChange,
    insertTableDataRecord,
    reload,
    setTableData,
    updateTableData,
    updateTableDataRecord,
  };
}
