import 'react-native-get-random-values';
import { v4 as uuidv4 } from 'uuid';
import { produce } from 'immer';
import { clone, groupBy, flatten } from 'ramda';
import { compareDesc, format } from 'date-fns';

import { actionTypes } from '../action-types';
import { PLURAL_RESOURCE_TYPES, TYPES_SORTED_BY_LABEL } from '../../constants/resource-types';
import { DEFAULT_COLLECTION_NAME } from '../../constants';
import { UNMARKED, MARKED, FOCUSED } from '../../constants/marked-status';
import { SORT_ASC, SORT_DESC, sortFields } from '../../constants/sorting';

const preloadedResources = {};

export const flattenedResourcesReducer = (state = preloadedResources, { type, payload }) => {
  switch (type) {
    case actionTypes.CLEAR_PATIENT_DATA: {
      return preloadedResources;
    }
    case actionTypes.RESOURCE_BATCH: {
      Object.entries(payload.resources).forEach(([id, resource]) => {
        if (state[id]) {
          console.warn(`resource ${id} of type ${resource.resourceType} already added.`); // eslint-disable-line no-console
        }
      });
      return {
        ...state,
        ...payload.resources,
      };
    }
    default:
      return state;
  }
};

const defaultAssociations = {
  encounters: {},
};

export const associationsReducer = (state = defaultAssociations, { type, payload }) => {
  switch (type) {
    case actionTypes.CLEAR_PATIENT_DATA: {
      return defaultAssociations;
    }
    case actionTypes.RESOURCE_BATCH: {
      const encounters = {};
      Object.entries(payload.resources).forEach(([id, resource]) => {
        if (state[id]) {
          console.warn(`resource ${id} of type ${resource.resourceType} already processed.`); // eslint-disable-line no-console
        }
        const encounterUrn = resource.encounter?.reference;
        if (encounterUrn) {
          const matches = encounterUrn.match(/(#|\/)(.+)/);
          const encounterId = matches.pop();
          if (encounterId) {
            encounters[id] = encounterId;
          }
        }
      });

      return produce(state, (draft) => {
        // eslint-disable-next-line no-param-reassign
        draft.encounters = { ...state.encounters, ...encounters };
      });
    }
    default:
      return state;
  }
};

const { RECORD_TYPE, RECORD_DATE, TIME_SAVED } = sortFields;

const defaultDetailsPanelSortingState = {
  activeSortField: RECORD_TYPE,
  sortDirections: {
    [RECORD_TYPE]: SORT_DESC,
    [RECORD_DATE]: SORT_DESC,
    [TIME_SAVED]: SORT_DESC,
  },
};

// prune items whose values are 0, null, undefined, or empty string:
// const pruneEmpty = ((o) => Object.entries(o)
//   .filter(([, v]) => v)
//   .reduce((acc, [id, v]) => ({ ...acc, [id]: v }), {}));

export const createCollection = (options = {}) => {
  const {
    label = DEFAULT_COLLECTION_NAME,
    id = uuidv4(),
    preBuilt = false,
    showCollectionOnly = false,
    selectedResourceType = TYPES_SORTED_BY_LABEL[0],
    purpose = '',
    current = false,
    urgent = false,
    tags = [],
  } = options;
  const timeCreated = new Date();

  return {
    id,
    preBuilt,
    created: timeCreated,
    lastUpdated: timeCreated,
    label,
    selectedResourceType,
    resourceTypeFilters: TYPES_SORTED_BY_LABEL
      .reduce((acc, resourceType) => ({
        ...acc,
        [resourceType]: true,
      }), {}),
    dateRangeFilter: {
      dateRangeStart: undefined,
      dateRangeEnd: undefined,
    },
    showCollectionOnly,
    showMarkedOnly: false,
    focusedSubtype: '',
    records: {},
    detailsPanelSortingState: defaultDetailsPanelSortingState,
    notes: {},
    tags,
    purpose,
    current,
    urgent,

  };
};

const createNewCollectionRecord = () => ({
  saved: false,
  dateSaved: new Date(),
  highlight: UNMARKED,
  // highlight:
  //   0 -- unmarked
  //   1 -- marked
  //   2 -- focused
});

let defaultCollection = createCollection();

const preloadCollections = {
  [defaultCollection.id]: defaultCollection,
};

const getNextEnabledType = (resourceType, enabledTypes) => enabledTypes
  .map((type, index, array) => ({
    type,
    next: array[(index === array.length - 1) ? 0 : index + 1],
  }))
  .find(({ type }) => type === resourceType)
  ?.next;

const createNote = (text) => {
  const newDate = new Date();
  return {
    id: uuidv4(),
    text,
    dateCreated: newDate,
    dateEdited: newDate,
  };
};

const sortByDateDesc = ({ timelineDate: t1 }, { timelineDate: t2 }) => compareDesc(t1, t2);

const groupRecordsByDay = groupBy((record) => {
  const isoDate = format(record.timelineDate, 'yyyy-MM-dd');
  return isoDate;
});

const lastNRecordIdsGroupedByDay = (records, count) => {
  const lastNSorted = Object.entries(groupRecordsByDay(records))
    .sort(([k1], [k2]) => ((k1 > k2) ? -1 : 1))
    .slice(0, count)
    .map(([, recordsOnDay]) => recordsOnDay)
    .map((recordsOnDay) => recordsOnDay);

  return flatten(lastNSorted).map((recordsOnDay) => recordsOnDay.id);
};

const disabledActionsForPreBuilt = [
  actionTypes.TOGGLE_SHOW_COLLECTION_ONLY,
  actionTypes.ADD_RESOURCE_TO_COLLECTION,
  actionTypes.REMOVE_RESOURCE_FROM_COLLECTION,
  actionTypes.RENAME_COLLECTION,
  actionTypes.CLEAR_COLLECTION,
  actionTypes.DELETE_COLLECTION,

];

export const PREBUILT_COLLECTIONS_LABELS = {
  lastEncounters: 'lastEncounters',
  lastLabResults: 'lastLabResults',
  lastVitalSigns: 'lastVitalSigns',
};

export const collectionsReducer = (state = preloadCollections, action) => {
  const { collectionId } = action.payload || {};

  if (collectionId && state[collectionId]) {
    const { preBuilt } = state[collectionId];
    if (preBuilt && disabledActionsForPreBuilt.includes(action.type)) {
      console.warn(`Collection ${collectionId} is pre-built -- cannot apply action ${action.type}`); // eslint-disable-line no-console
      return state;
    }
  }

  switch (action.type) {
    case actionTypes.CLEAR_PATIENT_DATA: {
      const { id } = defaultCollection;
      defaultCollection = createCollection();
      defaultCollection.id = id;
      return {
        [defaultCollection.id]: defaultCollection,
      };
    }
    case actionTypes.BUILD_DEFAULT_COLLECTIONS: {
      return produce(state, (draft) => {
        const { resources, associations: { encounters } } = action.payload;

        const sortedResources = Object.values(resources)
          .filter(({ type }) => PLURAL_RESOURCE_TYPES[type])
          .filter((r) => r.timelineDate) // must have timelineDate
          .sort(sortByDateDesc);

        const updateOrCreateCollection = ({
          id, label, selectedResourceType, recordIds,
        }) => {
          /* eslint-disable no-param-reassign */
          draft[id] = draft[id] ?? createCollection({
            id,
            label,
            preBuilt: true,
            showCollectionOnly: true,
            selectedResourceType,
          });

          const { records } = draft[id];
          Object.values(records).forEach((record) => {
            // un-save, in case it is no longer part of preBuilt:
            record.saved = recordIds.includes(record.id);
          });

          recordIds.forEach((rId) => {
            records[rId] = records[rId] ?? createNewCollectionRecord();
            records[rId].saved = true;
          });
          /* eslint-enable no-param-reassign */
        };

        const lastEncounters = sortedResources.filter((item) => item.type === 'Encounter').slice(0, 3).map(({ id }) => id);
        const referencesEncounters = Object.entries(encounters)
          .reduce((acc, [recordId, encounterId]) => {
            if (lastEncounters.includes(encounterId)) {
              return acc.concat(recordId);
            }
            return acc;
          }, []);
        updateOrCreateCollection({
          id: PREBUILT_COLLECTIONS_LABELS.lastEncounters,
          label: 'Last Encounters',
          selectedResourceType: 'Encounter',
          recordIds: lastEncounters.concat(referencesEncounters),
        });

        const laboratories = sortedResources.filter((item) => item.type === 'laboratory');
        updateOrCreateCollection({
          id: PREBUILT_COLLECTIONS_LABELS.lastLabResults,
          label: 'Last Lab Results',
          selectedResourceType: 'laboratory',
          recordIds: lastNRecordIdsGroupedByDay(laboratories, 5),
        });

        const vitalSigns = sortedResources.filter((item) => item.type === 'vital-signs');
        updateOrCreateCollection({
          id: PREBUILT_COLLECTIONS_LABELS.lastVitalSigns,
          label: 'Last Vital Signs',
          selectedResourceType: 'vital-signs',
          recordIds: lastNRecordIdsGroupedByDay(vitalSigns, 5),
        });
      });
    }
    case actionTypes.ADD_RESOURCE_TO_COLLECTION: {
      const { resourceIds } = action.payload;
      return produce(state, (draft) => {
        resourceIds.forEach((id) => {
          const { records } = draft[collectionId]; // eslint-disable-line no-param-reassign
          records[id] = records[id] ?? createNewCollectionRecord();
          records[id].saved = true;
          records[id].dateSaved = new Date();
        });
        // eslint-disable-next-line no-param-reassign
        draft[collectionId].lastUpdated = new Date();
      });
    }
    case actionTypes.REMOVE_RESOURCE_FROM_COLLECTION: {
      const { resourceIds } = action.payload;
      return produce(state, (draft) => {
        resourceIds.forEach((id) => {
          const { records } = draft[collectionId]; // eslint-disable-line no-param-reassign
          records[id] = records[id] ?? {};
          records[id].saved = false;
          records[id].dateSaved = null;
          // eslint-disable-next-line no-param-reassign
          draft[collectionId].lastUpdated = new Date();
        });
      });
    }
    case actionTypes.SELECT_RESOURCE_TYPE: {
      const { resourceType } = action.payload;
      return produce(state, (draft) => {
        // eslint-disable-next-line no-param-reassign
        draft[collectionId].selectedResourceType = resourceType;
      });
    }
    case actionTypes.TOGGLE_RESOURCE_TYPE_FILTERS: {
      const { resourceType } = action.payload;
      return produce(state, (draft) => {
        const collection = draft[collectionId];
        const { selectedResourceType, resourceTypeFilters } = collection;
        const filterIsEnabled = resourceTypeFilters[resourceType];
        const nextValue = !filterIsEnabled;
        if (nextValue && !resourceTypeFilters[selectedResourceType]) { // eg: all types were off
          collection.selectedResourceType = resourceType;
        }
        if (!nextValue && selectedResourceType === resourceType) { // toggling off the active type
          const enabledTypes = TYPES_SORTED_BY_LABEL.filter((type) => resourceTypeFilters[type]);
          const nextEnabledType = getNextEnabledType(resourceType, enabledTypes);
          if (nextEnabledType) {
            collection.selectedResourceType = nextEnabledType;
          }
        }
        collection.resourceTypeFilters[resourceType] = nextValue; // eslint-disable-line no-param-reassign, max-len
      });
    }
    case actionTypes.UPDATE_DATE_RANGE_FILTER: {
      const { dateRangeStart, dateRangeEnd } = action.payload;
      return produce(state, (draft) => {
        if (dateRangeStart) {
          // eslint-disable-next-line no-param-reassign
          draft[collectionId].dateRangeFilter.dateRangeStart = dateRangeStart.toISOString();
        }
        if (dateRangeEnd) {
          // eslint-disable-next-line no-param-reassign
          draft[collectionId].dateRangeFilter.dateRangeEnd = dateRangeEnd.toISOString();
        }
      });
    }
    case actionTypes.UPDATE_MARKED_RESOURCES: {
      const { subType, resourceIdsMap } = action.payload;

      return produce(state, (draft) => {
        const collection = draft[collectionId];
        const { records } = collection;
        const deFocus = (!subType || subType !== collection.focusedSubtype);
        collection.focusedSubtype = subType;
        if (deFocus) {
          Object.values(records).forEach((attributes) => {
            const prevValue = attributes.highlight;
            attributes.highlight = (prevValue === FOCUSED ? MARKED : prevValue); // eslint-disable-line max-len, no-param-reassign
          });
        }
        Object.entries(resourceIdsMap)
          .forEach(([id, next]) => {
            records[id] = records[id] ?? createNewCollectionRecord();
            const { highlight: prev } = records[id];
            records[id].highlight = ((prev === MARKED && next === FOCUSED) ? FOCUSED : next);
          });
      });
    }
    case actionTypes.CLEAR_MARKED_RESOURCES: {
      return produce(state, (draft) => {
        Object.values(draft[collectionId].records).forEach((attributes) => {
          attributes.highlight = UNMARKED; // eslint-disable-line no-param-reassign
        });
        draft[collectionId].showMarkedOnly = false; // eslint-disable-line no-param-reassign
      });
    }
    case actionTypes.CREATE_COLLECTION: {
      const newCollection = createCollection({
        label: action.payload,
      });
      return {
        ...state,
        [newCollection.id]: newCollection,
      };
    }

    case actionTypes.DELETE_COLLECTION: {
      const newState = { ...state };
      delete newState[action.payload.collectionId];
      return newState;
    }
    case actionTypes.RENAME_COLLECTION: {
      const updatedCollection = { ...state[action.payload.collectionId] };
      updatedCollection.label = action.payload.collectionName;
      return { ...state, [action.payload.collectionId]: updatedCollection };
    }
    case actionTypes.CLEAR_COLLECTION: {
      return produce(state, (draft) => {
        Object.values(draft[collectionId].records).forEach((attributes) => {
          attributes.saved = false; // eslint-disable-line no-param-reassign
          attributes.dateSaved = null; // eslint-disable-line no-param-reassign
        });
        // eslint-disable-next-line no-param-reassign
        draft[collectionId].lastUpdated = new Date();
        draft[collectionId].showCollectionOnly = false; // eslint-disable-line no-param-reassign
      });
    }
    case actionTypes.DUPLICATE_COLLECTION: {
      const { collectionName } = action.payload;
      const originalCollection = state[collectionId];
      const newCollection = clone(originalCollection);
      newCollection.id = uuidv4();
      newCollection.label = collectionName;
      newCollection.preBuilt = false;
      return {
        ...state,
        [newCollection.id]: newCollection,
      };
    }
    case actionTypes.TOGGLE_SHOW_COLLECTION_ONLY: {
      const { showCollectionOnly } = action.payload;
      return produce(state, (draft) => {
        // eslint-disable-next-line no-param-reassign
        draft[collectionId].showCollectionOnly = showCollectionOnly;
      });
    }
    case actionTypes.TOGGLE_SHOW_MARKED_ONLY: {
      const { showMarkedOnly } = action.payload;
      return produce(state, (draft) => {
        // eslint-disable-next-line no-param-reassign
        draft[collectionId].showMarkedOnly = showMarkedOnly;
      });
    }
    case actionTypes.TOGGLE_SORTING_STATE: {
      const { sortField } = action.payload;
      return produce(state, (draft) => {
        if (state[collectionId].detailsPanelSortingState.activeSortField === sortField) {
          const prevDir = state[collectionId].detailsPanelSortingState.sortDirections[sortField];
          // eslint-disable-next-line no-param-reassign
          draft[collectionId]
            .detailsPanelSortingState.sortDirections[sortField] = (
              (prevDir === SORT_ASC) ? SORT_DESC : SORT_ASC
            );
        }
        // eslint-disable-next-line no-param-reassign
        draft[collectionId]
          .detailsPanelSortingState.activeSortField = sortField;
      });
    }
    case actionTypes.CREATE_RECORD_NOTE: {
      const { resourceId, text } = action.payload;

      return produce(state, (draft) => {
        // eslint-disable-next-line no-param-reassign
        draft[collectionId].records[resourceId] = draft[collectionId].records[resourceId] || {};
        // eslint-disable-next-line no-param-reassign
        draft[collectionId].records[resourceId].notes = (
          draft[collectionId].records[resourceId].notes || {}
        );
        const newNote = createNote(text);
        // eslint-disable-next-line no-param-reassign
        draft[collectionId].records[resourceId].notes[newNote.id] = newNote;
        // eslint-disable-next-line no-param-reassign
        draft[collectionId].lastUpdated = new Date();
      });
    }
    case actionTypes.DELETE_RECORD_NOTE: {
      const { resourceId, noteId } = action.payload;
      return produce(state, (draft) => {
        // eslint-disable-next-line no-param-reassign
        delete draft[collectionId].records[resourceId].notes[noteId];
        // eslint-disable-next-line no-param-reassign
        draft[collectionId].lastUpdated = new Date();
      });
    }
    case actionTypes.EDIT_RECORD_NOTE: {
      const { resourceId, noteId, text } = action.payload;
      return produce(state, (draft) => {
        // eslint-disable-next-line no-param-reassign
        draft[collectionId].records[resourceId].notes[noteId].text = text;
        // eslint-disable-next-line no-param-reassign
        draft[collectionId].records[resourceId].notes[noteId].dateEdited = new Date();
        // eslint-disable-next-line no-param-reassign
        draft[collectionId].lastUpdated = new Date();
      });
    }
    case actionTypes.CREATE_COLLECTION_NOTE: {
      const { text } = action.payload;
      const newNote = createNote(text);
      return produce(state, (draft) => {
        // eslint-disable-next-line no-param-reassign
        draft[collectionId].notes[newNote.id] = newNote;
        // eslint-disable-next-line no-param-reassign
        draft[collectionId].lastUpdated = new Date();
      });
    }
    case actionTypes.DELETE_COLLECTION_NOTE: {
      const { noteId } = action.payload;
      return produce(state, (draft) => {
        // eslint-disable-next-line no-param-reassign
        delete draft[collectionId].notes[noteId];
        // eslint-disable-next-line no-param-reassign
        draft[collectionId].lastUpdated = new Date();
      });
    }
    case actionTypes.EDIT_COLLECTION_NOTE: {
      const { noteId, text } = action.payload;
      return produce(state, (draft) => {
        // eslint-disable-next-line no-param-reassign
        draft[collectionId].notes[noteId].text = text;
        // eslint-disable-next-line no-param-reassign
        draft[collectionId].notes[noteId].dateEdited = new Date();
        // eslint-disable-next-line no-param-reassign
        draft[collectionId].lastUpdated = new Date();
      });
    }

    case actionTypes.EDIT_COLLECTION_DETAILS: {
      const {
        purpose,
        tags,
        current,
        urgent,
      } = action.payload;
      return produce(state, (draft) => {
        draft[collectionId] = { // eslint-disable-line no-param-reassign
          ...draft[collectionId],
          purpose,
          tags,
          current,
          urgent,
          lastUpdated: new Date(),
        };
      });
    }
    default:
      return state;
  }
};

export const activeCollectionIdReducer = (state = null, action) => {
  switch (action.type) {
    case actionTypes.CLEAR_PATIENT_DATA: {
      return defaultCollection.id;
    }
    case actionTypes.SELECT_COLLECTION: {
      return action.payload;
    }
    case actionTypes.DELETE_COLLECTION: {
      return null;
    }
    default:
      return state;
  }
};

export const isCreatingNewCollectionReducer = (state = false, action) => {
  switch (action.type) {
    case actionTypes.ADDING_NEW_COLLECTION: {
      return action.payload;
    }

    default:
      return state;
  }
};