import { createAction, createAsyncThunk } from "@reduxjs/toolkit";

import { agsFeatureQuery } from "../query/ags";
import { vectorFeatureQuery } from "../query/vector";
import { wfsGetFeatureQuery } from "../query/wfs";
import { wmsGetFeatureInfoQuery } from "../query/wms";
import { getMapSourceName } from "../util";
import { getQueryResults } from "../selectors/query";

import { changeTool, setSelectionBuffer, clearSelectionFeatures, addSelectionFeature } from "./map";
import { clearFeatures, addFeatures } from "./mapSource";
import { setUiHint } from "./ui";

export const changeService = createAction(
  "query/change-service",
  (serviceName, defaultValues = {}) => ({
    payload: {
      serviceName,
      defaultValues,
    },
  })
);

export const startService = createAsyncThunk(
  "query/start-service",
  (
    { serviceName, defaultTool = null, defaultValues = {}, withFeatures = [] },
    { getState, dispatch }
  ) => {
    const state = getState();
    const currentService = state.query.serviceName;
    const currentDrawTool = state.map?.interactionType;

    // if the service is changing then reset the
    //  buffer and selection geometry settings.
    if (serviceName !== currentService) {
      dispatch(clearSelectionFeatures());
      dispatch(setSelectionBuffer(0));
      dispatch(changeTool(defaultTool));
      if (state.mapSources.selection) {
        dispatch(clearFeatures("selection"));
      }
    } else if (currentDrawTool === null) {
      dispatch(changeTool(defaultTool));
    }

    // start a service "with [these] features"
    if (withFeatures.length > 0) {
      dispatch(clearSelectionFeatures());
      dispatch(setSelectionBuffer(0));
      withFeatures.forEach((feature) => {
        dispatch(addSelectionFeature(feature));
      });
      if (state.mapSources.selection) {
        dispatch(clearFeatures("selection"));
        dispatch(addFeatures("selection", withFeatures));
      }
    }

    dispatch(changeService(serviceName, defaultValues));
    dispatch(setUiHint("service-start"));
  }
);

/**
 * Clearing the service will reset the internal bits of the query state
 */
export const clearService = createAction("query/clear-service");

/**
 * Finishing the service will close out and clear the service.
 */
export const finishService = createAsyncThunk("query/finish-service", (arg, { dispatch }) => {
  dispatch(clearService());
  dispatch(removeQueryResults());
  dispatch(changeTool(""));
  dispatch(clearSelectionFeatures());
  dispatch(clearFeatures("selection"));
});

export const createQuery = createAction(
  "query/create",
  (serviceName, selection, fields, layers, runOptions = {}) => ({
    payload: {
      serviceName,
      selection,
      fields,
      layers,
      runOptions,
    },
  })
);

export const addFilter = createAction("query/add-filter");

export const removeFilter = createAction("query/remove-filter");

/** Set the filter to highlight a specific subset of features
 *  from the results. This is the "highlight the highlight"
 *  seen when floating over the grid.
 *  Set to `false` to clear any hot filtering.
 */
export const setHotFilter = createAction("query/set-hot-filter");

export const runQuery = createAsyncThunk("query/run", (queryFunc, { getState, dispatch }) => {
  const state = getState();
  const queryDef = state.query.query;
  const mapSources = state.mapSources;

  if ((!queryDef.layers || queryDef.layers.length === 0) && !queryFunc) {
    // if there are no layers defined, return
    //  a resolved query with a completely empty set.
    return new Promise((resolve) => resolve([]));
  }

  const layerQueries = queryDef.layers.map((layer) => {
    const mapSource = mapSources[getMapSourceName(layer)];
    switch (mapSource?.type) {
      case "wms":
        return wmsGetFeatureInfoQuery(layer, state.map, mapSource, queryDef);
      case "wfs":
        return wfsGetFeatureQuery(layer, state.map, mapSource, queryDef);
      case "ags":
      case "ags-vector":
        return agsFeatureQuery(layer, state.map, mapSource, queryDef);
      case "geojson":
      case "vector":
        return vectorFeatureQuery(layer, state.map, mapSource, queryDef);
      default:
        // this is an un-supported type so just bail
        return new Promise((resolve) => resolve({ layer, features: [] }));
    }
  });

  // check for a query function
  if (queryFunc) {
    layerQueries.push(queryFunc("", queryDef));
  }

  return Promise.all(layerQueries).then((allResults) => {
    const results = {};
    allResults.forEach((result) => {
      if (result) {
        results[result.layer] = result.features;
      }
    });
    dispatch(setUiHint("new-results"));
    return results;
  });
});

export const bufferResults = createAsyncThunk(
  "query/buffer-results",
  (arg, { getState, dispatch }) => {
    const state = getState();
    const results = getQueryResults(state);

    // flatten the query results down to just a
    //  list of features
    let features = [];
    for (const path in results) {
      features = features.concat(results[path]);
    }

    if (features.length > 0) {
      dispatch(changeTool(""));

      // reset the buffer since this is a new buffer set.
      dispatch(setSelectionBuffer(0));
      // dispatch the features
      dispatch(clearSelectionFeatures());
      features.forEach((f) => {
        dispatch(addSelectionFeature(f));
      });
      dispatch(clearFeatures("selection"));
      dispatch(addFeatures("selection", features));

      dispatch(changeService("buffer-select"));
    } else {
      // TODO: Dispatch an error message that it
      //       it is not possible to buffer nothing.
    }
  }
);

export const removeQueryResults = createAction("query/remove-results");

export const setServiceStep = createAction("query/set-service-step");
