import { User as FirebaseUser } from "@firebase/auth";
import Conversation, {
  ConversationMember,
  AudioClip,
  Link,
  ConversationMemberState,
} from "@nirvana/common/models/conversation";
import * as NirvanaUser from "@nirvana/common/models/user";
import { conversationService, userService } from "@nirvana/common/services";

import { atom, DefaultValue, selector, selectorFamily } from "recoil";
import {
  today,
  yesterday,
  Days7Ago,
  earlierThisMonth as earlyMonthDate,
} from "@nirvana/common/helpers/dateTime";

export enum RecoilActions {
  TEST = "TEST",

  CURR_PAGE_PATH = "CURR_PAGE_PATH",

  ALL_COMPLETE_CONVERSATIONS = "ALL_COMPLETE_CONVERSATIONS",
  ALL_USERS_CONVERSATION_RELATIONSHIPS = "ALL_USERS_CONVERSATION_RELATIONSHIPS",
  USER_CONVO_ASSOCIATION_SELECTOR = "USER_CONVO_ASSOCIATION_SELECTOR",

  ALL_RELEVANT_CONVERSATIONS = "ALL_RELEVANT_CONVERSATIONS",

  SORTED_CONVERSATIONS = "SORTED_CONVERSATIONS",
  LIVE_ROOMS = "LIVE_ROOMS",
  LATER_CONVERSATIONS_SELECTOR = "LATER_CONVERSATIONS_SELECTOR",
  PRIORITY_CONVERSATIONS_SELECTOR = "PRIORITY_CONVERSATIONS_SELECTOR",
  DONE_CONVERSATIONS_SELECTOR = "DONE_CONVERSATIONS_SELECTOR",
  RELATIVE_TIME_SECTIONS_CONVOS = "RELATIVE_TIME_SECTIONS_CONVOS",

  CHECK_INCOMING_MESSAGE = "CHECK_INCOMING_MESSAGE",

  ALL_RELEVANT_CONTACTS = "ALL_RELEVANT_CONTACTS",
  RELEVANT_CONTACTS_SELECTOR_CACHE = "RELEVANT_CONTACTS_SELECTOR_CACHE",

  USER_DATA = "USER_DATA",

  SELECTED_CONVERSATION_ATOM = "SELECTED_CONVERSATION_ATOM",

  COUNT_DEFAULT_CONVOS_SELECTOR = "COUNT_DEFAULT_CONVOS_SELECTOR",
  COUNT_NAVIGATION_ITEMS = "COUNT_NAVIGATION_ITEMS",

  IS_RECORDING_ATOM = "IS_RECORDING_ATOM",
  HAS_MIC_PERMISSIONS = "HAS_MIC_PERMISSIONS",

  AUDIO_QUEUE_ATOM = "AUDIO_QUEUE_ATOM",
  AUDIO_QUEUE_CURRENT_ITEM_SELECTOR = "AUDIO_QUEUE_CURRENT_ITEM_SELECTOR",
}

export const nirvanaUserDataAtom = atom<NirvanaUser.default | null>({
  key: RecoilActions.ALL_COMPLETE_CONVERSATIONS,
  default: null,
});

// testing if we can import data in server side, but since it's next js we need firebase calls in client side
// import { conversationService } from "@nirvana/common/services";
import { earlierThisMonth } from "@nirvana/common/helpers/dateTime";

// const getTest = (async () => await conversationService.test())();

// export const test = atom({
//   key: RecoilActions.TEST, // unique ID (with respect to other atoms/selectors)
//   default: getTest, // default value (aka initial value)
// });

// approach: fill in all "bottom" level atoms, and then build the tree later with selectors
// convo id -> userMember object
export const allUsersConversationsAtom = atom<Map<string, ConversationMember>>({
  key: RecoilActions.ALL_USERS_CONVERSATION_RELATIONSHIPS,
  default: new Map<string, ConversationMember>(),
});

export const userConvoAssociationSelector = selectorFamily({
  key: RecoilActions.USER_CONVO_ASSOCIATION_SELECTOR,
  get:
    (convoId: string) =>
    ({ get }) => {
      const userConvoMap = get(allUsersConversationsAtom);
      const userConvoAssoc = userConvoMap.get(convoId);

      return userConvoAssoc;
    },
});

export const allRelevantConversationsAtom = atom({
  key: RecoilActions.ALL_RELEVANT_CONVERSATIONS,
  default: new Map<string, Conversation>(),
  effects: [
    // ({ onSet }) => {
    //   onSet((newMap) => {
    //     //go through and make sure that our relevant user's cache is up to date
    //     //  const allUsersInConvos = newMap.values()
    //   });
    // },
  ],
});

export const sortedRoomSelector = selector<Conversation[]>({
  key: RecoilActions.SORTED_CONVERSATIONS,
  get: ({ get }) => {
    const relConvos = get(allRelevantConversationsAtom);
    const convosArr: Conversation[] = Array.from(relConvos.values());

    convosArr.sort((a, b) => {
      if (!a.lastActivityDate) {
        return -1;
      }
      if (!b.lastActivityDate) {
        return 1;
      }

      if (a.lastActivityDate.toDate() < b.lastActivityDate.toDate()) {
        return 1;
      }

      return -1;
    });

    return convosArr;
  },
});

// selectors for convos: inbox/default, live, later, done, priority

// get all rooms with active members in the room
export const liveRoomsSelector = selector<Conversation[]>({
  key: RecoilActions.LIVE_ROOMS,
  get: ({ get }) => {
    const sortedConvos = get(sortedRoomSelector);
    const liveRooms = sortedConvos.filter(
      (conv) => conv.membersInLiveRoom?.length > 0
    );

    return liveRooms;
  },
});

export const laterConvosSelector = selector<Conversation[]>({
  key: RecoilActions.LATER_CONVERSATIONS_SELECTOR,
  get: ({ get }) => {
    const sortedConvos = get(sortedRoomSelector);
    const convoUserMap = get(allUsersConversationsAtom);

    const laterConvos: Conversation[] = sortedConvos.filter((conv) => {
      // get the userconvoAssoc and see the state
      if (convoUserMap.get(conv.id)?.state == ConversationMemberState.later) {
        return true;
      }

      return false;
    });

    return laterConvos;
  },
});

export const priorityConvosSelector = selector<Conversation[]>({
  key: RecoilActions.PRIORITY_CONVERSATIONS_SELECTOR,
  get: ({ get }) => {
    const sortedConvos = get(sortedRoomSelector);
    const convoUserMap = get(allUsersConversationsAtom);

    const priorityConvos: Conversation[] = sortedConvos.filter((conv) => {
      // get the userconvoAssoc and see the state
      if (
        convoUserMap.get(conv.id)?.state == ConversationMemberState.priority
      ) {
        return true;
      }

      return false;
    });

    // todo: sort by createdDate, not lastActivityDate
    priorityConvos.sort((a: Conversation, b: Conversation) => {
      if (a.createdDate.toDate() > b.createdDate.toDate()) {
        return -1;
      }
      return 0;
    });

    return priorityConvos;
  },
});

export const doneConvosSelector = selector<Conversation[]>({
  key: RecoilActions.DONE_CONVERSATIONS_SELECTOR,
  get: ({ get }) => {
    const sortedConvos = get(sortedRoomSelector);
    const convoUserMap = get(allUsersConversationsAtom);

    const doneConvos: Conversation[] = sortedConvos.filter((conv) => {
      if (!convoUserMap.has(conv.id)) {
        return false;
      }

      // if convo lastActivityDate > lastInteractionDate
      // move to default/inbox convos
      const userConvoAssoc = convoUserMap.get(conv.id);

      // get the userconvoAssoc and see the state
      if (userConvoAssoc?.state == ConversationMemberState.done) {
        if (
          userConvoAssoc?.lastInteractionDate &&
          conv.lastActivityDate > userConvoAssoc?.lastInteractionDate
        ) {
          conversationService.updateUserConvoRelationship(
            userConvoAssoc.id,
            conv.id,
            ConversationMemberState.default,
            false
          );

          return false;
        }

        return true;
      }

      return false;
    });

    return doneConvos;
  },
});

export const countDefaultConvosSelector = selector<number>({
  key: RecoilActions.COUNT_DEFAULT_CONVOS_SELECTOR,
  get: ({ get }) => {
    const sortedConvos = get(sortedRoomSelector);

    const userMemberMap = get(allUsersConversationsAtom);
    const defaultConvos = sortedConvos.filter((convo) => {
      return (
        userMemberMap.get(convo.id)?.state == ConversationMemberState.default &&
        !convo.membersInLiveRoom?.length
      );
    });

    return defaultConvos.length;
  },
});

export interface NavigationContentCount {
  liveCount: number;
  doneCount: number;
  defaultCount: number;
  laterCount: number;
  drawerCount: number;
}

export const countNavigationContentSelector = selector<NavigationContentCount>({
  key: RecoilActions.COUNT_NAVIGATION_ITEMS,
  get: ({ get }) => {
    const laterConvos = get(laterConvosSelector);
    const doneConvos = get(doneConvosSelector);
    const defaultCount = get(countDefaultConvosSelector);

    const liveRooms = get(liveRoomsSelector);

    const navCountObject: NavigationContentCount = {
      liveCount: liveRooms.length,
      defaultCount,
      laterCount: laterConvos.length,
      doneCount: doneConvos.length,
      drawerCount: 0,
    };

    return navCountObject;
  },
});

export enum RelativeTimeConvosSections {
  today = "TODAY",

  last7Days = "LAST 7 DAYS",

  earlierThisMonth = "EARLIER THIS MONTH",

  oldJunk = "OLD JUNK",
}

// map from the section title to the list of conversations, all sorted desc lastActivity date
export const RelativeTimeSeparatedConvosSelector = selector<
  Map<RelativeTimeConvosSections, Conversation[]>
>({
  key: RecoilActions.RELATIVE_TIME_SECTIONS_CONVOS,
  get: ({ get }) => {
    const sortedConvos = get(sortedRoomSelector);

    // find the convos where the user convo membership state is "inbox" or default
    const userMemberMap = get(allUsersConversationsAtom);

    // inbox/default Convos: already sorted
    const defaultConvos = sortedConvos.filter((convo) => {
      return (
        userMemberMap.get(convo.id)?.state == ConversationMemberState.default &&
        !convo.membersInLiveRoom?.length
      );
    });

    // go through these and organize in the different sections

    const organizedMapRelConvos = new Map<
      RelativeTimeConvosSections,
      Conversation[]
    >(new Map());

    // putting in sections in map so we can just add later
    organizedMapRelConvos.set(
      RelativeTimeConvosSections.today,
      [] as Conversation[]
    );
    organizedMapRelConvos.set(
      RelativeTimeConvosSections.last7Days,
      [] as Conversation[]
    );
    organizedMapRelConvos.set(
      RelativeTimeConvosSections.earlierThisMonth,
      [] as Conversation[]
    );
    organizedMapRelConvos.set(
      RelativeTimeConvosSections.oldJunk,
      [] as Conversation[]
    );

    defaultConvos.map((convo) => {
      let sectionForConvo = RelativeTimeConvosSections.today;

      // put it in the junk with this
      if (!convo.lastActivityDate) {
        sectionForConvo = RelativeTimeConvosSections.oldJunk;
      } else if (convo.lastActivityDate.toDate() > yesterday) {
        sectionForConvo = RelativeTimeConvosSections.today;
      } else if (convo.lastActivityDate.toDate() > Days7Ago) {
        sectionForConvo = RelativeTimeConvosSections.last7Days;
      } else if (convo.lastActivityDate.toDate() > earlyMonthDate) {
        sectionForConvo = RelativeTimeConvosSections.earlierThisMonth;
      } else {
        sectionForConvo = RelativeTimeConvosSections.oldJunk;
      }

      const currValInSection = organizedMapRelConvos.get(sectionForConvo);

      // already set, but just making sure
      if (currValInSection) {
        organizedMapRelConvos.set(sectionForConvo, [
          ...currValInSection,
          convo,
        ]);
      }
    });

    return organizedMapRelConvos;
  },
});

export const checkIncomingMessageSelector = selectorFamily({
  key: RecoilActions.CHECK_INCOMING_MESSAGE,
  get:
    (convoId: string) =>
    ({ get }) => {
      const currConvosMap: Map<string, Conversation> = get(
        allRelevantConversationsAtom
      );

      const currUserConvoMembersMap = get(allUsersConversationsAtom);

      // true if our last interaction date is < lastActivityDate
      const currConvo = currConvosMap.get(convoId);
      const currUserConvoAssoc = currUserConvoMembersMap.get(convoId);

      if (!currConvo || !currUserConvoAssoc) {
        return false;
      }

      if (!currUserConvoAssoc?.lastInteractionDate) {
        return true;
      }

      if (!currConvo?.lastActivityDate) {
        return false;
      }

      if (
        currConvo.lastActivityDate.toDate() >
        currUserConvoAssoc.lastInteractionDate?.toDate()
      ) {
        return true;
      }

      return false;

      // todo: if it's incoming and it's in done/archive, then move it into the inbox by updating userConvoMember relationship
    },
});

// map cache of all relevant users
// get all of the users in all of the conversations, once through a simple service call
export const allRelevantContactsAtom = atom<Map<string, NirvanaUser.default>>({
  key: RecoilActions.ALL_RELEVANT_CONTACTS,
  default: new Map<string, NirvanaUser.default>(),
});

// todo: useful selector where a component can pass in a list of users
// and return their full information...either get from cache if there or fetch from database if not and update cache

// RECORDING, SHORTCUTS, PLAYING, MIC, HEADPHONES, etc.
export const isRecordingAtom = atom<boolean>({
  key: RecoilActions.IS_RECORDING_ATOM,
  default: false,
});

export const selectedConvoAtom = atom<string | null>({
  // convoId
  key: RecoilActions.SELECTED_CONVERSATION_ATOM,
  default: null,
});

export const hasMicPermissionsAtom = atom<boolean>({
  key: RecoilActions.HAS_MIC_PERMISSIONS,
  default: false,
});

export const audioQueueAtom = atom<AudioClip[]>({
  key: RecoilActions.AUDIO_QUEUE_ATOM,
  default: [] as AudioClip[],
});

export const audioQueueCurrentClipSelector = selector<AudioClip | null>({
  key: RecoilActions.AUDIO_QUEUE_CURRENT_ITEM_SELECTOR,
  get: ({ get }) => {
    const currentQueue = get(audioQueueAtom);

    if (currentQueue[0]) {
      return currentQueue[0];
    }

    return null;
  },
});
