import { v4 } from "uuid";
import { createSelector } from "redux-bundler";
import {
  createNode,
  insertNodeAt,
  isParentOf,
  removeFromParent,
} from "./tree-node-utils";

/**
 * @module layers-bundle
 * The Layers bundle provides a way to manage layers and folders for your map.
 * You don't have to add layers to the bundle to get them on the map, from your
 * plugin you may add layers to the map directly, however, they will not be accessable
 * to the tree view plugin and won't show up there.  If you wan to get layers to show
 * up in the tree, add them using the methods here.
 */
export default {
  name: "layers",

  getReducer: () => {
    const initialData = {
      root: {
        id: "root",
        displayName: "root",
        children: [],
        mapLayer: null,
        collapsed: false,
        show: true,
      },
    };

    return (state = initialData, { type, payload }) => {
      switch (type) {
        case "LAYERS_ADDED":
        case "LAYERS_ADDED_BULK":
        case "LAYERS_REMOVED":
        case "LAYERS_FILTER_CHANGED":
          return Object.assign({}, state, payload);
        default:
          return state;
      }
    };
  },

  /**
   * Adds a single layer to our store.
   * @param {object} mapLayer
   * @param {object} options
   * @param {string} options.id An ID for the layer, the bundle will create a GUID by default
   * @param {object} options.parentId The parent node of the layer, null to display at the tree root,
   * id of the parent to add it to a folder
   * @param {string} options.displayName The name of the layer to display on the tree
   * @param {object} options.mapLayer An instance of an openlayers layer to be added to the map.
   * Omit the mapLayer to create a folder
   * @param {boolean} options.collapsed If a folder this will set if it is collapsed or expanded.displayName
   * @param {any} options.legend Add a legend below the layer in the tree, can be anything that React can render
   */
  doLayersAdd:
    ({
      id = v4(),
      parentId = "root",
      displayName = "undefined",
      mapLayer = null,
      collapsed = true,
      show = true,
      legend = null,
    }) =>
    ({ dispatch, store }) => {
      const map = store.selectMap();
      const layers = store.selectLayersItemsObj();
      const parent = layers[parentId];
      const node = {
        id,
        parent: parent,
        children: [],
        displayName,
        mapLayer,
        collapsed,
        show,
        legend,
      };
      // add layer to the top, on the layer tree and in the map
      parent.children.unshift(node);
      if (mapLayer) map.addLayer(mapLayer);
      dispatch({
        type: "LAYERS_ADDED",
        payload: {
          [id]: node,
        },
      });
    },

  /**
   * Add a bunch of layers and folders to the map
   * The config should match the config passed to `doLayersAdd`, but be in an array.
   * @param {array} layerItems An array of layer configuration items.
   */
  doLayersAddBulk:
    (layerItems) =>
    ({ dispatch, store }) => {
      const map = store.selectMap();
      const parentMap = {};
      const layers = store.selectLayersItemsObj();
      layerItems.forEach(
        ({
          id = v4(),
          parentId = "root",
          displayName = "undefined",
          mapLayer = null,
          collapsed = true,
          show = true,
          legend = null,
        }) => {
          const node = {
            id,
            parent: null,
            children: [],
            displayName,
            mapLayer,
            collapsed,
            show,
            legend,
          };
          parentMap[id] = parentId;
          layers[id] = node;
          if (mapLayer) map.addLayer(mapLayer);
        }
      );

      Object.keys(parentMap).forEach((id) => {
        const layer = layers[id];
        const parent = layers[parentMap[id]];
        parent.children.push(layer);
      });

      dispatch({
        type: "LAYERS_ADDED_BULK",
        payload: layers,
      });
    },

  doLayersRemove:
    (layer) =>
    ({ dispatch, store }) => {
      const layers = store.selectLayersItemsObj();
      const map = store.selectMap();
      map.removeLayer(layer.mapLayer);
      delete layers[layer.id];
      dispatch({
        type: "LAYERS_REMOVED",
        payload: layers,
      });
    },

  doLayersFilter:
    (filterString) =>
    ({ dispatch, store }) => {
      const layers = store.selectLayersItemsObj();

      function matchDownstream(n) {
        n.show = true;
        if (n.children && n.children.length) {
          n.children.forEach(matchDownstream);
        }
      }

      function matchUpstream(n) {
        n.show = true;
        if (!!filterString) n.collapsed = false;
        if (n.parent) {
          matchUpstream(n.parent);
        }
      }

      Object.keys(layers).forEach((id) => {
        const lyr = layers[id];
        const hasString =
          lyr.displayName.toUpperCase().indexOf(filterString.toUpperCase()) !==
          -1;
        if (hasString) {
          lyr.show = true;
        } else {
          lyr.show = false;
        }
      });

      Object.keys(layers).forEach((id) => {
        const lyr = layers[id];
        if (lyr.show) {
          matchDownstream(lyr);
          matchUpstream(lyr);
        }
      });

      dispatch({
        type: "LAYERS_FILTER_CHANGED",
        payload: layers,
      });
    },

  selectLayersRoot: (state) => {
    // need to return a copy to trigger re-renders
    return Object.assign({}, state.layers.root);
  },

  selectLayersItems: (state) => {
    const out = [];
    Object.keys(state.layers).forEach((key) => {
      if (key.indexOf("_") !== 0) out.push(state.layers[key]);
    });
    return out;
  },

  selectLayersItemsObj: (state) => {
    const out = {};
    Object.keys(state.layers).forEach((key) => {
      if (key.indexOf("_") !== 0) out[key] = state.layers[key];
    });
    return out;
  },

  selectLayersFlags: (state) => {
    const out = [];
    Object.keys(state.layers).forEach((key) => {
      if (key.indexOf("_") === 0) out.push(state.layers[key]);
    });
    return out;
  },
};
