"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.fhirResourceBundle = exports.extractOrgId = void 0;

var _object = require("../utils/object");

var _reduxBundler = require("redux-bundler");

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }

function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

const RECEIVED = 'FHIR_RESOURCE_RECEIVED';
const LOADING = 'FHIR_RESOURCE_LOADING';
const DELETING = 'FHIR_RESOURCE_DELETING';
const DELETED = 'FHIR_RESOURCE_DELETED';
const UPDATING = 'FHIR_RESOURCE_UPDATING';
const CLEARED = 'FHIR_RESOURCES_CLEARED';
const CLEARED_NOT_IN_ORG = 'FHIR_RESOURCES_CLEARED_EXCEPT';
/**
 * @param {string} fullUrl Full google resource URL for resource
 * @returns {string} Extracted OrgId
 */

const extractOrgId = fullUrl => {
  const result = /\/fhirStores\/([a-zA-Z0-9._-]+)\/fhir/i.exec(fullUrl);
  return result && result[1] || '';
};

exports.extractOrgId = extractOrgId;
const emptyObj = {};

const createFhirResourceSelector = resourceName => {
  let lastResult;
  let lastResourceCollection;
  return state => {
    const resourceCollection = state.fhirResourceCache[resourceName] || emptyObj;

    if (resourceCollection === lastResourceCollection) {
      return lastResult;
    }

    const result = {};

    for (const key in resourceCollection) {
      /** @type {ResourceEntry} */
      const entry = resourceCollection[key];
      const {
        resource,
        activeOperation,
        orgId
      } = entry;

      if (activeOperation) {
        result[resource.id] = _objectSpread(_objectSpread({}, resource), {}, {
          activeOperation,
          orgId
        });
      } else {
        result[resource.id] = _objectSpread(_objectSpread({}, resource), {}, {
          orgId
        });
      }
    }

    lastResourceCollection = resourceCollection;
    lastResult = result;
    return result;
  };
};
/** @typedef {'delete' | 'update' | 'load' | 'deleted'} OperationStatus */

/**
 * @typedef ResourceEntry
 * @property {any} resource
 * @property {OperationStatus} activeOperation
 * @property {string} orgId
 */

/**
 * @param {any} state
 * @param {{
 *   id: string
 *   resourceType: string
 *   orgId: string
 * }} payload
 * @param {OperationStatus} status
 */


const updateState = (state, payload, status) => {
  const {
    id,
    resourceType,
    orgId
  } = payload;
  const resourceCollection = state[resourceType] || {};

  if (status === 'deleted') {
    return _objectSpread(_objectSpread({}, state), {}, {
      [resourceType]: (0, _object.omit)(resourceCollection, [id])
    });
  }

  const existingResource = resourceCollection[id] && resourceCollection[id].resource;
  return _objectSpread(_objectSpread({}, state), {}, {
    [resourceType]: _objectSpread(_objectSpread({}, resourceCollection), {}, {
      [id]: {
        resource: existingResource || {
          resourceType,
          id: id
        },
        activeOperation: status,
        orgId
      }
    })
  });
};

const fhirResourceBundle = {
  name: 'fhirResourceCache',
  getReducer: () => {
    const initial = {// Patient: {
      //   [id]: {
      //     resource: {{ patient resource }}
      //     activeOperation: {'delete' | 'update' | 'load'}
      //     orgId: 'cfd'
      //   }
      // },
      // Location: {...},
      // Appointment: {...},
      // Practitioner: {...}
    };
    return (state = initial, {
      type,
      payload
    }) => {
      if (type === CLEARED) {
        return initial;
      }

      if (type === RECEIVED) {
        const {
          resources,
          orgId
        } = payload;

        const updatedState = _objectSpread({}, state);

        for (const id in resources) {
          const resource = resources[id];
          const {
            resourceType
          } = resource;
          updatedState[resourceType] = _objectSpread(_objectSpread({}, updatedState[resourceType]), {}, {
            [id]: {
              resource,
              orgId
            }
          });
        }

        return updatedState;
      }

      if (type === LOADING) {
        return updateState(state, payload, 'load');
      }

      if (type === DELETING) {
        return updateState(state, payload, 'delete');
      }

      if (type === DELETED) {
        return updateState(state, payload, 'deleted');
      }

      if (type === UPDATING) {
        return updateState(state, payload, 'update');
      }

      if (type === CLEARED_NOT_IN_ORG) {
        const newState = {};

        for (const resourceType in state) {
          newState[resourceType] = {};
          const resources = state[resourceType];

          for (const resourceId in resources) {
            const resourceEntry = resources[resourceId];

            if (resourceEntry.orgId === payload) {
              newState[resourceType][resourceId] = resourceEntry;
            }
          }
        }

        return newState;
      }

      return state;
    };
  },
  init: store => {
    store.utilFindFhirResourceById = id => {
      const collections = store.selectFhirResourceCacheRaw();

      for (const key in collections) {
        const items = collections[key];
        const found = items[id];

        if (found) {
          return found.resource;
        }
      }

      return null;
    };
  },
  selectFhirResourceCacheRaw: state => state.fhirResourceCache,
  selectPatientsRaw: createFhirResourceSelector('Patient'),
  selectLocationsRaw: createFhirResourceSelector('Location'),
  selectPractitionersRaw: createFhirResourceSelector('Practitioner'),
  selectAppointmentsRaw: createFhirResourceSelector('Appointment'),
  selectCompositionsRaw: createFhirResourceSelector('Composition'),
  selectFhirHasResources: (0, _reduxBundler.createSelector)('selectFhirResourceCacheRaw', cache => {
    for (const key in cache) {
      const collection = cache[key];

      for (const id in collection) {
        // if there's anything... it's true
        return true;
      }
    }

    return false;
  }),
  doClearFhirCache: () => ({
    type: CLEARED
  }),
  doClearFhirModelsNotInOrg: orgId => ({
    type: CLEARED_NOT_IN_ORG,
    payload: orgId
  }),
  doRemoveFhirResource: ({
    orgId,
    id,
    resourceType
  }) => ({
    type: DELETED,
    payload: {
      orgId,
      id,
      resourceType
    }
  }),
  doReceiveSingleFhirResource: (resource, orgId) => ({
    type: RECEIVED,
    payload: {
      orgId,
      resources: {
        [resource.id]: resource
      }
    }
  }),
  doReceiveFhirResponse: (...fhirResponses) => ({
    dispatch
  }) => {
    fhirResponses.forEach(response => {
      const {
        entry,
        total
      } = response;
      const isEmpty = total === 0;

      if (!isEmpty && entry && Array.isArray(entry)) {
        const firstEntry = entry[0];
        const orgId = firstEntry && extractOrgId(firstEntry.fullUrl) || null;
        const resources = entry.map(item => item.resource);
        dispatch({
          type: RECEIVED,
          payload: {
            orgId,
            resources: (0, _object.arrayToObject)(resources)
          }
        });
      }
    });
  },
  doFhirFetch: (...args) => ({
    fhirFetch
  }) => fhirFetch(...args),
  doFetchFhirResource: (fhirUrl, orgId) => ({
    dispatch,
    fhirFetch,
    store
  }) => {
    const [resourceType, maybeId] = fhirUrl.split('/');
    const [id, extraQueryParams] = maybeId.split('?');
    dispatch({
      type: LOADING,
      payload: {
        resourceType,
        id,
        orgId
      }
    }); // we have to do this as a search in order to include related resources

    return fhirFetch("/".concat(resourceType, "?_id=").concat(id, "&_include=*").concat(extraQueryParams ? '&' + extraQueryParams : ''), {
      orgId
    }).then(store.doReceiveFhirResponse);
  },
  doFetchPdfShareInfo: (pdfId, orgId) => ({
    gqlFetch
  }) => gqlFetch("{pdfUrl(orgId:\"".concat(orgId, "\",pdfId:\"").concat(pdfId, "\"){url text title}}")).then(res => res.data.pdfUrl),
  doCreateChartForAppointment: (appointmentId, orgId) => ({
    gqlFetch
  }) => gqlFetch("mutation createCompositionForAppointment($orgId:String!,$appointmentId:String!) {\n      createCompositionForAppointment(orgId:$orgId,appointmentId:$appointmentId) \n    }", {
    orgId,
    appointmentId
  }).then(res => res.data.createCompositionForAppointment),
  doFetchLocationIdsForLocation: locationId => ({
    fhirFetch
  }) => fhirFetch("/Location?_id=".concat(locationId, "&_revinclude=Location:partof:Location")).then(res => {
    const entry = res && res.entry || [];
    return entry.map(item => item.resource.id);
  }),
  doFetchUpcomingAndIncompleteAppointments: ({
    orgId,
    practitionerId,
    providerId,
    locationId,
    includeComposition = true
  }) => ({
    store
  }) => {
    const memberId = practitionerId || providerId;
    return Promise.resolve(locationId ? store.doFetchLocationIdsForLocation(locationId) : null).then(locationIds => Promise.all([// upcoming
    store.doFetchAppointments({
      orgId,
      gt: store.selectAppDayDate(),
      sort: 'date',
      practitionerId: memberId,
      locationIds,
      includeComposition,
      count: 50
    }), // missing date
    store.doFetchAppointments({
      orgId,
      missingDate: true,
      practitionerId: memberId,
      includeComposition,
      locationIds
    }), // fetch missed from before
    // should include everything with"
    // status other than "fullfilled"
    // OR the statuses that mean the
    // appointment did not happen.
    store.doFetchAppointments({
      orgId,
      lt: store.selectAppDayDate(),
      sort: 'date',
      practitionerId: memberId,
      locationIds,
      includeComposition,
      status: ['proposed', 'pending', 'booked', 'arrived', 'checked-in', 'waitlist' // the other possible statuses
      // that we are excluding here:
      // 'fulfilled',
      // 'cancelled',
      // 'noshow',
      // 'entered-in-error',
      ].join(',')
    })]));
  },
  doFetchAppointments: ({
    sort,
    orgId,
    includeActors = true,
    includeComposition = true,
    compositionIncomplete,
    practitionerId,
    gt,
    lt,
    missingDate = false,
    count,
    status,
    locationId,
    locationIds,
    incomingResponseArrayHandler,
    waitForAll = false,
    fetchAll = true,
    returnMeta = false
  }) => ({
    fhirFetch,
    store
  }) => Promise.resolve(locationId ? store.doFetchLocationIdsForLocation(locationId) : locationIds).then(resolvedLocationIds => {
    let params = [];

    if (resolvedLocationIds && resolvedLocationIds.length) {
      params.push("location=".concat(resolvedLocationIds.join(',')));
    }

    if (includeComposition) {
      params.push('_include=Appointment:supporting-info:Composition');
    }

    if (includeActors) {
      params.push('_include=Appointment:actor');
    }

    if (missingDate) {
      params.push('date:missing=true');
    }

    if (count) {
      params.push("_count=".concat(count));
    }

    if (sort) {
      params.push("_sort=".concat(sort));
    }

    if (gt) {
      params.push("date=gt".concat(gt.toISOString()));
    }

    if (lt) {
      params.push("date=lt".concat(lt.toISOString()));
    }

    if (practitionerId) {
      params.push("practitioner=".concat(practitionerId));
    }

    if (status) {
      params.push("status=".concat(status));
    }

    if (compositionIncomplete) {
      params.push('supporting-info:Composition.status=preliminary');
    }

    return fhirFetch('/Appointment?' + params.join('&'), {
      waitForAll,
      fetchAll,
      incomingResponseArrayHandler: incomingResponseArrayHandler || (res => store.doReceiveFhirResponse(...res)),
      orgId,
      returnMeta
    });
  })
};
exports.fhirResourceBundle = fhirResourceBundle;