import { memo, useCallback, useMemo, useRef, useState } from 'react';
import Debug from 'debug';

import type { ServiceTypes } from '~/lib/types';
import type { PageSectionComponent } from '../../types';
import type { PresaveButtonsProps, PresaveReleaseItem } from '../types';

import Box from '~/src/components/Box';
import { useEditActions } from '~/src/components/ItemPage/ItemPageEdit/useEditActions';
import { useAppConfirm } from '~/src/components/NextApp/lib/CoreUi';
import { SortableContainer, SortableItem } from '~/src/components/Sortable';
import useHash from '~/src/hooks/useHash';
import { useI18n } from '~/src/lib/i18n';
import { useItemContext } from '../../../ItemPageContext';
import { resolveLink } from '../../lib';
import { EditWrapper } from '../../lib/EditWrapper';
import PickTerritory, { DEFAULT_TERRITORY } from '../../lib/PickTerritory';
import SectionTitle from '../../lib/SectionTitle';
import ServiceButton from '../../lib/ServiceButton';
import { useDefaultItems } from '../useDefaultItems';
import { sortPresaveReleaseItems } from '../utils';
import AddPresaveItemButton from './AddPresaveItemButton';
import { EditItemDialog } from './EditItemDialog';
import PresaveButtonsSettings from './PresaveButtonsSettings';

const debug = Debug('songwhip/PresaveButtons2Edit');

const PresaveButtonsEdit: PageSectionComponent<PresaveButtonsProps> = ({
  title: defaultTitle,
  items: initialItems,
  layoutData,
  sectionPath,
  withColoredIcons,
  withAlphabeticalSort,
  withEmailAndOptInsBeforePresave,
  territoryOverrides,
  isMandatory,
}) => {
  const defaultItems = useDefaultItems(initialItems);
  const { hashParams, backToBeforeFirstHash, setHashParam } = useHash();
  const [activeTerritory, setActiveTerritory] = useState<string>();
  const editLinkHashParam = `subscribeButtons:${sectionPath}:edit`;
  const withTerritoryOverrides = !!territoryOverrides;
  const editHashParam = hashParams[editLinkHashParam];
  const itemToEditIndex = Number(editHashParam);
  const confirm = useAppConfirm();
  const { t } = useI18n('itemEdit');

  const {
    updateLayoutSection,
    updateCustomObject,
    addCustomLinkObject: addCustomLink,
  } = useEditActions();

  const addedTerritories = useMemo(
    () => (territoryOverrides ? Object.keys(territoryOverrides) : []),
    [territoryOverrides]
  );

  const items =
    (activeTerritory && territoryOverrides?.[activeTerritory]?.items) ||
    defaultItems;

  const sortedItems = useMemo(
    () =>
      withAlphabeticalSort
        ? [...items].sort(sortPresaveReleaseItems(layoutData))
        : items,
    [items, withAlphabeticalSort]
  );

  const titleResolved =
    (activeTerritory && territoryOverrides?.[activeTerritory]?.title) ||
    defaultTitle;

  const itemToEdit = editHashParam ? sortedItems[itemToEditIndex] : undefined;

  const updateTerritoryProps = useCallback(
    (
      params: Partial<Pick<PresaveButtonsProps, 'title' | 'items'>>,
      settings: Pick<
        PresaveButtonsProps,
        | 'withColoredIcons'
        | 'withAlphabeticalSort'
        | 'withEmailAndOptInsBeforePresave'
      > = {},
      territory = activeTerritory
    ) => {
      const changedProps = territory
        ? {
            ...settings,
            territoryOverrides: {
              ...territoryOverrides,

              [territory]: {
                ...territoryOverrides?.[territory],
                ...params,
              },
            },
          }
        : {
            ...settings,
            ...params,
          };

      updateLayoutSection<PresaveButtonsProps>({
        sectionPath,
        changedProps,
      });
    },
    [activeTerritory, territoryOverrides, updateLayoutSection, sectionPath]
  );

  return (
    <div data-testid="PresaveButtons2Edit">
      <EditWrapper
        sectionPath={sectionPath}
        withInlineHeader={!!territoryOverrides}
        isMandatory={isMandatory}
        renderHeaderContent={useCallback(() => {
          if (!withTerritoryOverrides) return;

          return (
            <PickTerritory
              items={addedTerritories}
              value={activeTerritory || DEFAULT_TERRITORY}
              onChange={(country) =>
                setActiveTerritory(
                  country === DEFAULT_TERRITORY ? undefined : country
                )
              }
              onAdd={(country) => {
                setActiveTerritory(country);

                // add an empty entry to `territoryOverrides`
                updateTerritoryProps({}, undefined, country);
              }}
            />
          );
        }, [
          withTerritoryOverrides,
          activeTerritory,
          addedTerritories,
          updateTerritoryProps,
        ])}
        renderSettingsContent={useCallback(
          ({ close }) => (
            <PresaveButtonsSettings
              title={defaultTitle}
              withColoredIcons={withColoredIcons}
              withAlphabeticalSort={withAlphabeticalSort}
              sectionPath={sectionPath}
              activeTerritory={activeTerritory}
              withTerritoryOverrides={withTerritoryOverrides}
              withEmailAndOptInsBeforePresave={withEmailAndOptInsBeforePresave}
              onSubmit={close}
              onTitleChange={(value) => {
                updateTerritoryProps({
                  title: value,
                });
              }}
              onTerritoryRemove={(territory) => {
                const territoryOverridesNext = {
                  ...territoryOverrides,
                };

                delete territoryOverridesNext[territory];

                updateLayoutSection({
                  sectionPath,

                  changedProps: {
                    territoryOverrides: territoryOverridesNext,
                  },
                });

                setActiveTerritory(undefined);
                close();
              }}
              onTerritoryOverridesChange={async (value) => {
                const hasTerritoryOverrides = !!(
                  territoryOverrides && Object.keys(territoryOverrides).length
                );

                if (!value && hasTerritoryOverrides) {
                  const confirmed = await confirm({
                    content: t('actions.removeTerritoryOverrides'),
                  });

                  if (!confirmed) return;
                }

                if (!value) {
                  updateLayoutSection<PresaveButtonsProps>({
                    sectionPath,

                    changedProps: {
                      territoryOverrides: undefined,
                    },
                  });

                  setActiveTerritory(undefined);

                  // when territories are cleared, close the modal to show the user
                  if (hasTerritoryOverrides) {
                    close();
                  }

                  return;
                }

                // Create an empty object to store overrides. This is
                // also used to infer when the feature is enabled.
                updateLayoutSection({
                  sectionPath,

                  changedProps: {
                    territoryOverrides: {},
                  },
                });
              }}
            />
          ),
          [
            activeTerritory,
            defaultTitle,
            withColoredIcons,
            withAlphabeticalSort,
            withTerritoryOverrides,
            updateLayoutSection,
            updateTerritoryProps,
          ]
        )}
      >
        <PresaveButtonsEditContentMemo
          title={titleResolved}
          items={sortedItems}
          editLinkHashParam={editLinkHashParam}
          sectionPath={sectionPath}
          withColoredIcons={!!withColoredIcons}
          onButtonClick={useCallback(
            ({ data: itemIndex }) => {
              setHashParam({
                [editLinkHashParam]: String(itemIndex),
              });
            },
            [editLinkHashParam]
          )}
          onButtonOrderChange={useCallback(
            (itemsNext) => {
              updateTerritoryProps(
                { items: itemsNext },
                // NOTE: Button order change means sorting is not applied anymore
                { withAlphabeticalSort: false }
              );
            },
            [updateTerritoryProps]
          )}
          onAddLink={useCallback(
            (dataPath) => {
              const itemsNext = [...sortedItems];

              itemsNext.push({
                type: 'link',
                dataPath,
              });

              updateTerritoryProps({
                items: itemsNext,
              });
            },

            [updateTerritoryProps, sortedItems]
          )}
          onAddService={useCallback(
            (serviceType) => {
              const itemsNext = [...sortedItems];

              itemsNext.push({
                type: 'subscribe',
                service: serviceType,
              });

              updateTerritoryProps({
                items: itemsNext,
              });
            },

            [updateTerritoryProps, sortedItems]
          )}
        />
      </EditWrapper>
      {itemToEdit && (
        <EditItemDialog
          item={itemToEdit}
          layoutData={layoutData}
          onClose={() => {
            debug('on edit item close');

            backToBeforeFirstHash();
          }}
          onDelete={() => {
            debug('on item delete');

            const itemsNext = [...sortedItems];

            itemsNext.splice(itemToEditIndex, 1);

            updateTerritoryProps({
              items: itemsNext,
            });
          }}
          onLinkItemSubmit={({
            customObject,
            dataPath,
            withEmailAndOptIns,
          }) => {
            const updateButtonEntryFlag = () => {
              const currentItem = sortedItems[itemToEditIndex];

              if (
                currentItem?.type !== 'link' ||
                !!currentItem.withEmailAndOptIns === !!withEmailAndOptIns
              ) {
                return;
              }

              const itemsNext = [...sortedItems];

              itemsNext[itemToEditIndex] = {
                ...currentItem,
                withEmailAndOptIns: !!withEmailAndOptIns,
              };

              updateTerritoryProps({
                items: itemsNext,
              });
            };

            // When not in the default territory we have special handling to
            // ensure any link edits only affect the active territory and
            // don't surface in any other territories. Territory edit are
            // designed so that edits to items in the default territory
            // propagate/sync with items in other territories that haven't
            // been directly edited. This means that editors can treat the
            // Default territory as a base and override in specific territories
            // when required, this should make constructing pages less work.
            if (activeTerritory) {
              // When a custom object is referenced in another territory then
              // we must create a new copy that includes the changes so we
              // don't update links/titles/icons in other territories.
              if (
                customObjectUsedInOtherTerritories({
                  defaultItems,
                  territoryOverrides,
                  activeTerritory,
                  dataPath,
                })
              ) {
                // create a new custom link from the edited data
                const nextDataPath = addCustomLink({
                  link: customObject.link,
                  text: customObject.text,
                });

                // replace the item in the list with the clone, also
                // applying the per-item email-collection flag in the
                // same update so we don't trigger a second territory write
                const itemsNext = sortedItems.map((item) => {
                  if (item.type === 'link' && item.dataPath === dataPath) {
                    return {
                      ...item,
                      dataPath: nextDataPath,
                      withEmailAndOptIns: !!withEmailAndOptIns,
                    };
                  }

                  return item;
                });

                // update the component to use the links with
                // the new custom object instead of the default link
                updateTerritoryProps({
                  items: itemsNext,
                });

                return;
              }
            }

            // when the item is only used in the current/active territory
            // then we can safely update the underlying custom object without
            // risk of changing other territories
            updateCustomObject({
              data: customObject,
              path: dataPath,
            });

            // the email-collection flag lives on the button entry, not the
            // shared customObject, so update items separately
            updateButtonEntryFlag();
          }}
          onSubscribeItemSubmit={(itemNext) => {
            debug('on submit', itemNext);

            const itemsNext = [...sortedItems];

            // update the item that was edited
            itemsNext[itemToEditIndex] = itemNext;

            // update the component to use the links with
            // the new custom object instead of the default link
            updateTerritoryProps({
              items: itemsNext,
            });
          }}
        />
      )}
    </div>
  );
};

const PresaveButtonsEditContent = ({
  items,
  title,
  sectionPath,
  withColoredIcons,
  onButtonClick,
  onButtonOrderChange,
  onAddLink,
  onAddService,
}: {
  editLinkHashParam: string;
  onButtonClick: (params: { data: number }) => void;
  onButtonOrderChange: (items: PresaveReleaseItem[]) => void;
  sectionPath: string;

  /**
   * When a service is picked from the Add dialog.
   */
  onAddService: (serviceType: ServiceTypes) => void;

  /**
   * When a custom link is added via the Add dialog.
   *
   * This facilitates adding external links to stores that don't
   * invoke the usual subscribe/presave flow. This is because
   * labels may want offer pre-order of vinyl/merch as
   * well as subscribing to release on DSPs.
   */
  onAddLink: (dataPath: string) => void;
} & Omit<PresaveButtonsProps, 'testId'> & {
    items: Required<PresaveButtonsProps>['items'];
  }) => {
  const { data: layoutData } = useItemContext();

  const renderedItemsRef = useRef({
    indexByRenderedIndex: {},
    renderedIndex: 0,
  });

  return (
    <Box padding="1.8rem 1rem 1.7rem">
      <SectionTitle text={title} minHeight="3.5rem" />
      <Box tag="ul" margin=".5rem 0 0">
        <SortableContainer
          nonDragAreaSelector=".noDrag"
          onDrop={({ removedIndex, addedIndex }) => {
            debug('on drop', { removedIndex, addedIndex });

            const { indexByRenderedIndex } = renderedItemsRef.current;
            const didChange = removedIndex !== addedIndex;

            if (!didChange) {
              debug('noop: unchanged');
              return;
            }

            const removedLinksIndex = indexByRenderedIndex[removedIndex!];
            const addedLinksIndex = indexByRenderedIndex[addedIndex!];

            const itemsNext = [...items];
            const [removed] = itemsNext.splice(removedLinksIndex, 1);
            itemsNext.splice(addedLinksIndex, 0, removed);

            debug('links change', itemsNext);

            onButtonOrderChange(itemsNext);
          }}
        >
          {items.map((item, index) => {
            // reset the index refs on each new loop
            if (index === 0) {
              renderedItemsRef.current = {
                renderedIndex: 0,
                indexByRenderedIndex: {},
              };
            }

            const isLast = index === items.length - 1;
            const margin = !isLast ? `0 0 1rem` : undefined;

            // store a reference to the underlying `links` index
            // so that onDrop we can resolve it from the *rendered* index
            renderedItemsRef.current.indexByRenderedIndex[
              renderedItemsRef.current.renderedIndex
            ] = index;

            renderedItemsRef.current.renderedIndex++;

            switch (item.type) {
              case 'link': {
                const link = resolveLink(layoutData, item.dataPath);

                return (
                  <SortableItem>
                    <ServiceButton
                      serviceType={link?.serviceType}
                      Icon={link?.Icon}
                      text={link?.text}
                      margin={margin}
                      withColoredIcon={withColoredIcons}
                      onClick={() => {
                        onButtonClick({ data: index });
                      }}
                    />
                  </SortableItem>
                );
              }

              case 'subscribe':
              default:
                return (
                  <SortableItem>
                    <ServiceButton
                      // back-compat for when service defined on `type`
                      serviceType={item.service ?? item.type}
                      text={item.text}
                      margin={margin}
                      withColoredIcon={withColoredIcons}
                      onClick={() => {
                        onButtonClick({ data: index });
                      }}
                    />
                  </SortableItem>
                );
            }
          })}
        </SortableContainer>
        <AddPresaveItemButton
          sectionPath={sectionPath}
          items={items}
          onPickService={onAddService}
          onCreateLink={onAddLink}
        />
      </Box>
    </Box>
  );
};

const PresaveButtonsEditContentMemo = memo(PresaveButtonsEditContent);

/**
 * Test if a given custom object is used in another territory.
 *
 * If it is then we'll tend to want to create a new custom object
 * to ensure that we don't change the appearance of another territory.
 */
const customObjectUsedInOtherTerritories = ({
  defaultItems,
  dataPath,
  territoryOverrides,
  activeTerritory,
}: {
  defaultItems: PresaveReleaseItem[];
  dataPath: string;
  territoryOverrides: PresaveButtonsProps['territoryOverrides'];
  activeTerritory: string;
}) => {
  const territoryItems = territoryOverrides
    ? Object.entries(territoryOverrides)
        .filter(([country]) => country !== activeTerritory)
        .flatMap(([, { items }]) => items)
    : [];

  return [...defaultItems, ...territoryItems].find(
    (item) => item?.type === 'link' && item.dataPath === dataPath
  );
};

export default PresaveButtonsEdit;
