import uuid from 'uuid/v4';
import pickBy from 'lodash/pickBy';
import negate from 'lodash/negate';
import isUndefined from 'lodash/isUndefined';
import uniqueId from 'lodash/uniqueId';
import { getKeyName, parseMetas, normMetas, parseQueryResult } from '../../../utils/meta';
import { datasourceApi } from '../../../services/datasource';

const prefix = uuid();

const getSource = (property) => {
  if (property.source) {
    return (property.source.items || []).map((item) => {
      try {
        return JSON.parse(item);
      } catch (err) {
        return item;
      }
    });
  } else {
    return [];
  }
};

const makeProps = (meta) => {
  // noinspection JSUnresolvedVariable
  if (!meta || !meta.metas) {
    return {};
  }
  const props = normMetas(meta.metas);
  if (!props.rowKey) {
    props.rowKey = getKeyName(meta);
  }
  return props;
};

const getColumnIdx = (columns, name) => {
  return (columns || []).findIndex(column => name === column.dataIndex);
};

const makeColumns = (meta) => {
  return (meta.properties || [])
    .filter(property => !property.skip)
    .map((property) => {
      // noinspection JSUnresolvedVariable
      const props = normMetas(property.metas);
      if (!props.title && props.label) {
        props.title = props.label;
      }
      if (props.order === undefined) {
        props.order = 0;
      }
      if ((props.fixed === true || props.fixed === 'left' || props.fixed === 'right') && props.width === undefined) {
        props.width = 150;
      }
      return pickBy({
        ...props,
        dataIndex: property.name,
        key: property.name,
        sorter: property.sort,
        filterType: property.filterType,
        filterEnums: getSource(property),
      }, negate(isUndefined));
    })
    .filter((c) => {
      for (const key of Object.keys(c)) {
        if (key.startsWith('meta:')) {
          return false;
        }
      }
      return c.visible !== false;
    })
    .sort((c1, c2) => {
      const c1Left = c1.fixed === true || c1.fixed === 'left';
      const c1Right = c1.fixed === 'right';
      const c2Left = c2.fixed === true || c2.fixed === 'left';
      const c2Right = c2.fixed === 'right';
      if (c1Left && !c2Left) {
        return -1;
      }
      if (!c1Left && c2Left) {
        return 1;
      }
      if (c1Right && !c2Right) {
        return 1;
      }
      if (!c1Right && c2Right) {
        return -1;
      }
      return c1.order - c2.order;
    });
};

const getSort = (columns) => {
  const column = (columns || []).find(c => c.sortOrder);
  return column ? {
    field: column.key,
    order: column.sortOrder,
  } : undefined;
};

const modelCreator = () => {
  const name = 'model';
  const namespace = uniqueId(prefix);
  const loadMeta = function* loadMeta({ select, call, put }) {
    const { coordinate, params } = yield select(state => state[namespace]);
    const api = datasourceApi(coordinate);
    const meta = yield call(api.queryMeta, { params });
    yield put({ type: 'applyMeta', payload: meta });
  };
  const loadData = function* loadData({ select, put, call, start, end }) {
    if (start) {
      yield put({ type: start });
    }
    try {
      const { coordinate, params, meta, columns, filters, current, pageSize } = yield select(state => state[namespace]);
      const api = datasourceApi(coordinate);
      const psz = pageSize;
      const pst = (current - 1) * psz;
      const sort = getSort(columns);
      const sortBys = sort ? [sort.field] : [];
      const sortTypes = sort ? [sort.order] : [];
      const options = { pst, psz, params, filters, sortBys, sortTypes };
      const num = yield call(api.count, options);
      const dsb = yield call(api.query, options);
      const list = parseQueryResult(dsb, meta);
      yield put({ type: 'applyData', payload: { num, list } });
    } finally {
      if (end) {
        yield put({ type: end });
      }
    }
  };
  return {
    name,
    model: {
      namespace,
      state: {
        coordinate: null,
        params: {},
        meta: {},
        parsedMeta: {
          global: {
            columnData: {},
            rowData: {},
          },
          properties: {},
        },
        columns: [],
        props: {},
        num: 0,
        list: [],
        filters: [],
        current: 1,
        pageSize: 10,
      },
      reducers: {
        applyTarget(state, { payload: { coordinate, params } }) {
          return {
            ...state,
            coordinate,
            params,
          };
        },
        applyFilters(state, { payload: filters }) {
          return {
            ...state,
            filters,
          };
        },
        changeSorts(state, { payload: { field, order } }) {
          const idx = getColumnIdx(state.columns, field);
          if (idx !== -1) {
            const columns = [...state.columns];
            for (let i = 0; i < columns.length; ++i) {
              if (i !== idx) {
                columns[i].sortOrder = false;
              } else {
                columns[i].sortOrder = order;
              }
            }
            return {
              ...state,
              columns,
            };
          } else {
            return state;
          }
        },
        applyCurrentPage(state, { payload: current }) {
          return {
            ...state,
            current,
          };
        },
        applyPageSize(state, { payload: pageSize }) {
          return {
            ...state,
            pageSize,
          };
        },
        applyMeta(state, { payload: meta }) {
          return {
            ...state,
            meta,
            parsedMeta: parseMetas(meta),
            props: makeProps(meta),
            columns: makeColumns(meta),
          };
        },
        applyData(state, { payload: { num, list } }) {
          return {
            ...state,
            num,
            list,
          };
        },
        queryMetaSuccess(state, { payload: { props, columns } }) {
          return {
            ...state,
            props,
            columns,
          };
        },
        queryCountSuccess(state, { payload: num }) {
          return {
            ...state,
            num,
          };
        },
        queryTasksSuccess(state, { payload: list }) {
          return {
            ...state,
            list,
          };
        },
      },
      effects: {
        *doInit({ payload: { coordinate, params, current = 1, start, end } }, { put, call, select }) {
          yield put({ type: 'applyTarget', payload: { coordinate, params } });
          yield put({ type: 'applyFilters', payload: [] });
          yield put({ type: 'applyCurrentPage', payload: current });
          yield call(loadMeta, { put, call, select });
          yield call(loadData, { put, call, select, start, end });
        },
        *doFilter({ payload: filters }, { put, call, select }) {
          yield put({ type: 'applyFilters', payload: filters });
          yield put({ type: 'applyCurrentPage', payload: 1 });
          yield call(loadData, { put, call, select });
        },
        *doSort({ payload: { field, order } }, { put, call, select }) {
          yield put({ type: 'changeSorts', payload: { field, order } });
          yield put({ type: 'applyCurrentPage', payload: 1 });
          yield call(loadData, { put, call, select });
        },
        *changeCurrentPage({ payload: current }, { put, call, select }) {
          yield put({ type: 'applyCurrentPage', payload: current });
          yield call(loadData, { put, call, select });
        },
        *changePageSize({ payload: pageSize }, { put, call, select }) {
          yield put({ type: 'applyCurrentPage', payload: 1 });
          yield put({ type: 'applyPageSize', payload: pageSize });
          yield call(loadData, { put, call, select });
        },
        // *fetchData({ payload: { coordinate, pst, psz, params, filters} }, { put, call }) {
        //   const options = { pst, psz, params, filters };
        //   const api = datasourceApi(coordinate);
        //   const meta = yield call(api.meta);
        //   const props = makeProps(meta);
        //   yield put({ type: 'queryMetaSuccess', payload: { props, columns: makeColumns(meta) } });
        //   const num = yield call(api.count, options);
        //   yield put({ type: 'queryCountSuccess', payload: num });
        //   const dsb = yield call(api.query, options);
        //   yield put({ type: 'queryTasksSuccess', payload: getArrayData(dsb, meta) });
        // },
      },
      subscriptions: {},
    },
  };
};

export default modelCreator;