import { useReducer, useEffect, useRef, useCallback } from 'react';
import isEqual from 'react-fast-compare';

import { reporter } from '../../utils';
import { loadCookieState as loadFromCookie, saveCookieState as saveToCookie } from './load-and-save-cookies';
import { conditionallyLoadAnalytics, getIntegrations as getDestinations } from './load-from-segment';
import { isInEuOrCalifornia } from './check-region';

export function buildDestinationFlags(destinations, cookieDestinationFlags) {
  /*
    LOGIC
    - receive list of destinations
    - compare against destinations flagged in cookie
    - default to false
  */
  return destinations.reduce((flags, destination) => {
    flags[destination.id] =
      destination.id in cookieDestinationFlags ? Boolean(cookieDestinationFlags[destination.id]) : false;
    return flags;
  }, {});
}

export function buildCategoryFlags(categories, destinationFlags) {
  /*
    LOGIC
    - receive list of categories
    - for each category, look at destinations
    - flag category true, if each of its destinations are either (A) flagged true or (B) not used
  */
  return categories.reduce((flags, category) => {
    flags[category.id] = category.destinationIDs.every((id) =>
      id in destinationFlags ? destinationFlags[id] : true,
    );
    return flags;
  }, {});
}

export function buildUiFlags(categories, destinations, cookieDestinationFlags) {
  let destinationFlags, categoryFlags;
  destinationFlags = buildDestinationFlags(destinations, cookieDestinationFlags);
  categoryFlags = buildCategoryFlags(categories, destinationFlags);
  return { destinationFlags, categoryFlags };
}

export function implyDestinationFlags(categories, categoryFlags, destinations) {
  /*
    LOGIC
    - receive list of categories and list of destinations
    - for each destination, find its category
    - if the category is true, the destination is true
   */
  console.log({ destinations });

  return destinations.reduce((flags, destination) => {
    const destinationCategory = categories.find(
      ({ destinationIDs }) => ~destinationIDs.indexOf(destination.id),
    );
    flags[destination.id] = Boolean(categoryFlags[destinationCategory.id]);
    return flags;
  }, {});
}

export function ManagerBuilder({ categories, cdnHost, writeKey, domain, children } = {}) {
  const { info, table } = reporter();
  /*
    STATES
  */
  const [uiState, setUiState] = useReducer((oldState, newState) => ({ ...oldState, ...newState }), {
    initialized: false,
    destinations: [],
    newDestinations: [],
    destinationFlags: {},
    categoryFlags: {},
    isConsentRequired: true,
    haveFlagsChanged: false,
  });

  const [cookieState, setCookieState] = useReducer((oldState, newState) => ({ ...oldState, ...newState }), {
    initialized: false,
    destinationFlags: {},
    categoryFlags: {},
  });

  /*
    ASYNC UPDATES
   */
  const cookiesOutdated = useRef(false);
  const analyticsOutdated = useRef(true);

  useEffect(() => {
    if (!uiState.initialized) return;
    info('%c-> ui state set', 'color: lightblue; font-weight: bold;');
    table(uiState);
    table(uiState.destinationFlags);
    table(uiState.categoryFlags);
    // if analytics flagged as outdate, re-run analytics loader
    if (analyticsOutdated.current) {
      const {
        destinations,
        destinationFlags,
        isConsentRequired,
        haveFlagsChanged: shouldReload,
        categoryFlags,
      } = uiState;
      conditionallyLoadAnalytics({
        writeKey,
        destinations,
        destinationFlags,
        isConsentRequired,
        shouldReload,
        categoryFlags,
      });
      analyticsOutdated.current = false;
    }
  }, [uiState]);

  useEffect(() => {
    if (!cookieState.initialized) return;
    info('%c-> cookie state set', 'color: pink; font-weight: bold;');
    table(cookieState);
    // if cookies are flagged as out of date WRT state, save cookieState to cookie
    if (cookiesOutdated.current) {
      const { destinationFlags, categoryFlags } = cookieState;
      saveToCookie({ destinationFlags, categoryFlags, domain });
      cookiesOutdated.current = false;
    }
  }, [cookieState]);

  /*
    INIT
   */
  useEffect(() => {
    (async () => {
      // NB: don't try to set and use state in same call
      const cookieValue = loadFromCookie({ categories });
      const isConsentRequired = isInEuOrCalifornia();
      // re-fetched from segment, on each load -- NB: can accept multiple writeKeys as spread
      const destinations = await getDestinations(cdnHost, writeKey);
      info({ destinations });
      // new destinations are any whose id is not an index in the cookie destination flags
      const newDestinations = destinations.filter(
        (destination) => !(destination.id in cookieValue.destinationFlags),
      );
      const { destinationFlags, categoryFlags } = buildUiFlags(
        categories,
        destinations,
        cookieValue.destinationFlags,
      );
      setCookieState({ initialized: true, ...cookieValue });
      setUiState({
        initialized: true,
        isConsentRequired,
        destinations,
        newDestinations,
        destinationFlags,
        categoryFlags,
      });
      info('%c-> init completed', 'color: lime; font-weight: bold;');
    })();
  }, []);

  const resetFlagsFromCookie = useCallback(() => {
    // rebuild the flags, based on cookieState destination flags
    const { destinationFlags, categoryFlags } = buildUiFlags(
      categories,
      uiState.destinations,
      cookieState.destinationFlags,
    );
    info('%c-> resetting flags from cookie', 'color: fuchsia; font-weight: bold;');
    table({ destinationFlags, categoryFlags });
    setUiState({ destinationFlags, categoryFlags, haveFlagsChanged: false });
  }, [categories, uiState.destinations, cookieState.destinationFlags]);

  const saveFlagsToCookie = useCallback(
    ({ destinationFlags, categoryFlags } = uiState) => {
      analyticsOutdated.current = true; // set analytic-prefs as out of date
      cookiesOutdated.current = true; // set cookies as out of date
      setCookieState({ destinationFlags, categoryFlags });
      setUiState({
        newDestinations: [], // no more diff between stateFlags and cookieFlags !
      });
      info('%c-> saving flags to cookie', 'color: fuchsia; font-weight: bold;');
      table({ destinationFlags, categoryFlags });
    },
    [uiState.destinationFlags, uiState.categoryFlags],
  );

  const setCategoryFlag = useCallback(
    (flag) => {
      const categoryFlags = { ...uiState.categoryFlags, ...flag };
      const destinationFlags = implyDestinationFlags(categories, categoryFlags, uiState.destinations);
      const haveFlagsChanged = !isEqual(destinationFlags, cookieState.destinationFlags);
      setUiState({
        categoryFlags,
        destinationFlags,
        haveFlagsChanged,
      });
      return {
        categoryFlags,
        destinationFlags,
      };
    },
    [categories, uiState.categoryFlags, cookieState.destinationFlags, uiState.destinations],
  );

  const setAllCategoryFlags = useCallback(
    (bool) => {
      const categoryFlags = categories.reduce((obj, category) => {
        obj[category.id] = Boolean(bool);
        return obj;
      }, {});
      const destinationFlags = implyDestinationFlags(categories, categoryFlags, uiState.destinations);
      table(destinationFlags);
      const haveFlagsChanged = !isEqual(destinationFlags, cookieState.destinationFlags);
      setUiState({
        categoryFlags,
        destinationFlags,
        haveFlagsChanged,
      });
      return {
        categoryFlags,
        destinationFlags,
      };
    },
    [categories, cookieState.destinationFlags, uiState.destinations],
  );

  if (!uiState.initialized) return null;

  return children({
    ...uiState,
    // cookieDestinationFlags: cookieState.destinationFlags,
    // cookieCategoryFlags: cookieState.categoryFlags,
    categories,
    setCategoryFlag,
    setAllCategoryFlags,
    resetFlagsFromCookie,
    saveFlagsToCookie,
  });
}
