text: import { createSelector } from 'reselect';
  import { fuzzyMatchAll } from '../../common/misc';
  import * as models from '../../models';
  
  // ~~~~~~~~~ //
  // Selectors //
  // ~~~~~~~~~ //
  
  export const selectEntitiesLists = createSelector(
    state => state.entities,
    entities => {
      const entitiesLists = {};
  
      for (const k of Object.keys(entities)) {
        const entityMap = entities[k];
        entitiesLists[k] = Object.keys(entityMap).map(id => entityMap[id]);
      }
  
      return entitiesLists;
    },
  );
  
  export const selectEntitiesChildrenMap = createSelector(selectEntitiesLists, entities => {
    const parentLookupMap = {};
    for (const k of Object.keys(entities)) {
      for (const e of entities[k]) {
        if (!e.parentId) {
          continue;
        }
  
        if (parentLookupMap[e.parentId]) {
          parentLookupMap[e.parentId].push(e);
        } else {
          parentLookupMap[e.parentId] = [e];
        }
      }
    }
  
    return parentLookupMap;
  });
  
  export const selectActiveWorkspace = createSelector(
    state => selectEntitiesLists(state).workspaces,
    state => state.entities,
    state => state.global.activeWorkspaceId,
    (workspaces, entities, activeWorkspaceId) => {
      return entities.workspaces[activeWorkspaceId] || workspaces[0];
    },
  );
  
  export const selectActiveWorkspaceMeta = createSelector(
    selectActiveWorkspace,
    selectEntitiesLists,
    (activeWorkspace, entities) => {
      const id = activeWorkspace ? activeWorkspace._id : 'n/a';
      return entities.workspaceMetas.find(m => m.parentId === id);
    },
  );
  
  export const selectActiveWorkspaceClientCertificates = createSelector(
    selectEntitiesLists,
    selectActiveWorkspace,
    (entities, activeWorkspace) => {
      return entities.clientCertificates.filter(c => c.parentId === activeWorkspace._id);
    },
  );
  
  export const selectActiveGitRepository = createSelector(
    selectEntitiesLists,
    selectActiveWorkspaceMeta,
    (entities, activeWorkspaceMeta) => {
      if (!activeWorkspaceMeta) {
        return null;
      }
  
      const id = activeWorkspaceMeta ? activeWorkspaceMeta.gitRepositoryId : 'n/a';
      const repo = entities.gitRepositories.find(r => r._id === id);
      return repo || null;
    },
  );
  
  export const selectCollapsedRequestGroups = createSelector(selectEntitiesLists, entities => {
    const collapsed = {};
  
    // Default all to collapsed
    for (const requestGroup of entities.requestGroups) {
      collapsed[requestGroup._id] = true;
    }
  
    // Update those that have metadata (not all do)
    for (const meta of entities.requestGroupMetas) {
      collapsed[meta.parentId] = meta.collapsed;
    }
  
    return collapsed;
  });
  
  export const selectActiveWorkspaceEntities = createSelector(
    selectActiveWorkspace,
    selectEntitiesChildrenMap,
    (activeWorkspace, childrenMap) => {
      const descendants = [activeWorkspace];
      const addChildrenOf = parent => {
        // Don't add children of requests (eg. auth requests)
        if (parent.type === models.request.type) {
          return [];
        }
  
        const children = childrenMap[parent._id] || [];
        for (const child of children) {
          descendants.push(child);
          addChildrenOf(child);
        }
      };
  
      // Kick off the recursion
      addChildrenOf(activeWorkspace);
  
      return descendants;
    },
  );
  
  export const selectPinnedRequests = createSelector(selectEntitiesLists, entities => {
    const pinned = {};
  
    // Default all to unpinned
    for (const request of entities.requests) {
      pinned[request._id] = false;
    }
  
    // Update those that have metadata (not all do)
    for (const meta of entities.requestMetas) {
      pinned[meta.parentId] = meta.pinned;
    }
  
    return pinned;
  });
  
  export const selectSidebarChildren = createSelector(
    selectCollapsedRequestGroups,
    selectPinnedRequests,
    selectActiveWorkspace,
    selectActiveWorkspaceMeta,
    selectEntitiesChildrenMap,
    (collapsed, pinned, activeWorkspace, activeWorkspaceMeta, childrenMap) => {
      const sidebarFilter = activeWorkspaceMeta ? activeWorkspaceMeta.sidebarFilter : '';
  
      function next(parentId, pinnedChildren) {
        const children = (childrenMap[parentId] || [])
          .filter(doc => {
            return doc.type === models.request.type || doc.type === models.requestGroup.type;
          })
          .sort((a, b) => {
            if (a.metaSortKey === b.metaSortKey) {
              return a._id > b._id ? -1 : 1;
            } else {
              return a.metaSortKey < b.metaSortKey ? -1 : 1;
            }
          });
  
        if (children.length > 0) {
          return children.map(c => {
            const child = {
              doc: c,
              hidden: false,
              collapsed: !!collapsed[c._id],
              pinned: !!pinned[c._id],
            };
  
            if (child.pinned) {
              pinnedChildren.push(child);
            }
  
            // Don't add children of requests
            child.children = c.type === models.request.type ? [] : next(c._id, pinnedChildren);
  
            return child;
          });
        } else {
          return children;
        }
      }
  
      function matchChildren(children, parentNames = []) {
        // Bail early if no filter defined
        if (!sidebarFilter) {
          return children;
        }
  
        for (const child of children) {
          // Gather all parents so we can match them too
          matchChildren(child.children, [...parentNames, child.doc.name]);
  
          const hasMatchedChildren = child.children.find(c => c.hidden === false);
  
          // Try to match request attributes
          const { name, method } = child.doc;
          const match = fuzzyMatchAll(sidebarFilter, [name, method, ...parentNames], {
            splitSpace: true,
          });
  
          // Update hidden state depending on whether it matched
          const matched = hasMatchedChildren || match;
          child.hidden = !matched;
        }
  
        return children;
      }
  
      const pinnedChildren = [];
      const childrenTree = next(activeWorkspace._id, pinnedChildren);
      const matchedChildren = matchChildren(childrenTree);
  
      return { pinned: pinnedChildren, all: matchedChildren };
    },
  );
  
  export const selectWorkspaceRequestsAndRequestGroups = createSelector(
    selectActiveWorkspaceEntities,
    entities => {
      return entities.filter(
        e => e.type === models.request.type || e.type === models.requestGroup.type,
      );
    },
  );
  
  export const selectActiveRequest = createSelector(
    state => state.entities,
    selectActiveWorkspaceMeta,
    (entities, workspaceMeta) => {
      const id = workspaceMeta ? workspaceMeta.activeRequestId : 'n/a';
      return entities.requests[id] || null;
    },
  );
  
  export const selectActiveCookieJar = createSelector(
    selectEntitiesLists,
    selectActiveWorkspace,
    (entities, workspace) => {
      const cookieJar = entities.cookieJars.find(cj => cj.parentId === workspace._id);
      return cookieJar || null;
    },
  );
  
  export const selectActiveOAuth2Token = createSelector(
    selectEntitiesLists,
    selectActiveWorkspaceMeta,
    (entities, workspaceMeta) => {
      const id = workspaceMeta ? workspaceMeta.activeRequestId : 'n/a';
      return entities.oAuth2Tokens.find(t => t.parentId === id);
    },
  );
  
  export const selectUnseenWorkspaces = createSelector(selectEntitiesLists, entities => {
    const { workspaces, workspaceMetas } = entities;
    return workspaces.filter(workspace => {
      const meta = workspaceMetas.find(m => m.parentId === workspace._id);
      return !!(meta && !meta.hasSeen);
    });
  });
  
  export const selectActiveRequestMeta = createSelector(
    selectActiveRequest,
    selectEntitiesLists,
    (activeRequest, entities) => {
      const id = activeRequest ? activeRequest._id : 'n/a';
      return entities.requestMetas.find(m => m.parentId === id);
    },
  );
  
  export const selectActiveRequestResponses = createSelector(
    selectActiveRequest,
    selectEntitiesLists,
    selectActiveWorkspaceMeta,
    (activeRequest, entities, meta) => {
      const requestId = activeRequest ? activeRequest._id : 'n/a';
      const settings = entities.settings[0];
  
      // Filter responses down if the setting is enabled
      return entities.responses
        .filter(response => {
          const requestMatches = requestId === response.parentId;
  
          if (settings.filterResponsesByEnv) {
            const activeEnvironmentId = meta ? meta.activeEnvironmentId : 'n/a';
            const environmentMatches = response.environmentId === activeEnvironmentId;
            return requestMatches && environmentMatches;
          } else {
            return requestMatches;
          }
        })
        .sort((a, b) => (a.created > b.created ? -1 : 1));
    },
  );
  
  export const selectActiveResponse = createSelector(
    selectActiveRequestMeta,
    selectActiveRequestResponses,
    (activeRequestMeta, responses) => {
      const activeResponseId = activeRequestMeta ? activeRequestMeta.activeResponseId : 'n/a';
      const activeResponse = responses.find(response => response._id === activeResponseId);
  
      if (activeResponse) {
        return activeResponse;
      }
  
      return responses[0] || null;
    },
  );
  
  export const selectSyncItems = createSelector(selectActiveWorkspaceEntities, workspaceEntities =>
    workspaceEntities.filter(models.canSync).map(doc => ({
      key: doc._id,
      name: doc.name || '',
      document: doc,
    })),
  );
  