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

import type { SelectInputProps } from '~/src/components/SelectInput';
import type { StoreProduct } from '~/src/lib/pageMetadata/types';
import type { FC } from 'react';
import type { PageSectionComponent } from '../../types';
import type { MerchSectionProps } from '../types';

import Box from '~/src/components/Box';
import Clickable from '~/src/components/Clickable';
import DialogBox from '~/src/components/DialogBox';
import { DialogBoxHeader } from '~/src/components/DialogBox/DialogBoxHeader';
import ErrorText from '~/src/components/ErrorText';
import FadeOnMount from '~/src/components/FadeOnMount';
import Form from '~/src/components/Form';
import ShoppingBagIcon from '~/src/components/Icon/ShoppingBagIcon';
import InputLabel from '~/src/components/InputLabel';
import { useEditActions } from '~/src/components/ItemPage/ItemPageEdit/useEditActions';
import Loading from '~/src/components/Loading';
import {
  useAppAlert,
  useAppConfirm,
} from '~/src/components/NextApp/lib/CoreUi';
import SelectInput from '~/src/components/SelectInput';
import { SortableItem } from '~/src/components/Sortable';
import Text from '~/src/components/Text';
import TextInput from '~/src/components/TextInput';
import useFetchSessionUser from '~/src/hooks/useFetchSessionUser';
import useHash from '~/src/hooks/useHash';
import { useI18n } from '~/src/lib/i18n';
import uploadImageByUrl from '~/src/lib/image/uploadImageByUrl';
import { getPageProductMetadataApi } from '~/src/lib/pageMetadata';
import { getCurrencySymbols } from '~/src/lib/utils/currency';
import { usePageTheme } from '../../../hooks/theme';
import { EditWrapper } from '../../lib/EditWrapper';
import HorizontalItem from '../../lib/HorizontalItemsSection/HorizontalItem';
import PickTerritory, { DEFAULT_TERRITORY } from '../../lib/PickTerritory';
import RemoveButton from '../../lib/RemoveButton';
import SectionTitle from '../../lib/SectionTitle';
import SortableHorizontalScroller from '../../lib/SortableHorizontalScroller';
import TextInputWithIconButton from '../../lib/TextInputWithIconButton';
import { MerchText } from '../utils';
import MerchSettings from './MerchSettings';

const debug = Debug('songwhip/MerchEdit');
const HASH_PARAM = 'merch';

/**
 * Cap the number of items in the product list to keep
 * layouts clean and performance fast.
 */
const MAX_ITEMS = 8;

const MerchSectionEdit: PageSectionComponent<MerchSectionProps> = ({
  title,
  items: initialItems = [],
  sectionPath,
  territoryOverrides,
}) => {
  const { hashParams, backToBeforeFirstHash, setHashParam } = useHash();
  const [activeTerritory, setActiveTerritory] = useState<string>();

  const editItemHashParam = `${HASH_PARAM}:${sectionPath}:edit`;
  const editHashValue = hashParams[editItemHashParam];
  const editItemIndex = editHashValue ? Number(editHashValue) : undefined;

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

  const itemToEdit =
    editItemIndex !== undefined ? items[editItemIndex] : undefined;

  const { updateLayoutSection } = useEditActions();
  const { t } = useI18n('itemEdit');

  const confirm = useAppConfirm();

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

  const updateTerritoryProps = useCallback(
    (
      params: Partial<Pick<MerchSectionProps, 'title' | 'items'>>,
      territory = activeTerritory
    ) => {
      const changedProps = territory
        ? {
            territoryOverrides: {
              ...territoryOverrides,

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

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

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

  const onItemClick = useCallback(
    ({ data: { index } }) => {
      setHashParam({ [editItemHashParam]: index });
    },
    [editItemHashParam]
  );

  return (
    <Box testId="merchEdit">
      <EditWrapper
        sectionPath={sectionPath}
        renderSettingsContent={useCallback(
          ({ close }) => (
            <MerchSettings
              title={titleResolved}
              onTitleChange={(value) => {
                updateTerritoryProps({
                  title: value,
                });
              }}
              sectionPath={sectionPath}
              onSubmit={close}
              activeTerritory={activeTerritory}
              withTerritoryOverrides={!!territoryOverrides}
              onTerritoryRemove={(territory) => {
                const territoryOverridesNext = {
                  ...territoryOverrides,
                };

                delete territoryOverridesNext[territory];

                updateLayoutSection({
                  sectionPath,

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

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

                if (!value && hasTerritoryOverrides) {
                  const confirmed = await confirm({
                    content: 'Remove all territory customizations?',
                  });

                  if (!confirmed) {
                    updateValue(true);
                    return;
                  }
                }

                if (!value) {
                  updateLayoutSection<MerchSectionProps>({
                    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: {},
                  },
                });
              }}
            />
          ),
          [
            titleResolved,
            territoryOverrides,
            sectionPath,
            activeTerritory,
            updateTerritoryProps,
          ]
        )}
        withInlineHeader={!!territoryOverrides}
        renderHeaderContent={useCallback(() => {
          if (!territoryOverrides) 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({}, country);
              }}
            />
          );
        }, [
          territoryOverrides,
          activeTerritory,
          addedTerritories,
          updateTerritoryProps,
        ])}
      >
        <MerchEditContent
          title={titleResolved}
          items={items}
          sectionPath={sectionPath}
          onItemClick={onItemClick}
          onAddItem={({ item }) => {
            updateTerritoryProps({
              items: [item, ...items],
            });
          }}
          onDropItem={(items) => {
            updateTerritoryProps({
              items,
            });
          }}
        />
      </EditWrapper>
      {itemToEdit && (
        <DialogBox
          onClose={backToBeforeFirstHash}
          fillViewportOnSmallScreen
          renderContent={({ close, paddingX, paddingY }) => {
            const currencySelectItems: SelectInputProps['items'] =
              Object.entries(getCurrencySymbols()).map(([code, symbol]) => ({
                id: code,
                text: `${symbol}${code}`,
              }));

            return (
              <Form<{
                name: string;
                price: string;
                currency: string;
                link: string;
              }>
                onSubmit={({ values }) => {
                  if (editItemIndex === undefined) {
                    close();
                    return;
                  }

                  if (!activeTerritory) {
                    const itemsNext = [...items];

                    itemsNext[editItemIndex] = {
                      ...itemToEdit,
                      name: values.name,

                      price: values.price
                        ? parseFloat(values.price)
                        : undefined,

                      priceCurrency: values.currency,

                      link: values.link,
                    };

                    updateLayoutSection({
                      sectionPath,

                      changedProps: {
                        items: itemsNext,
                      },
                    });
                  } else {
                    const newItem = {
                      ...itemToEdit,
                      name: values.name,
                      price: values.price
                        ? parseFloat(values.price)
                        : undefined,
                      priceCurrency: values.currency,
                      link: values.link,
                    };

                    const formerItems =
                      territoryOverrides?.[activeTerritory]?.items ?? items;

                    updateTerritoryProps(
                      {
                        items: formerItems.map((item, index) => {
                          return index === editItemIndex ? newItem : item;
                        }),
                      },
                      activeTerritory
                    );
                  }

                  close();
                }}
              >
                <DialogBoxHeader
                  onCloseClick={close}
                  title={t('merch.edit.title')}
                  renderRight={({ textProps }) => (
                    <Clickable isSubmit testId="submit">
                      <Text {...textProps}>Save</Text>
                    </Clickable>
                  )}
                />
                <Box padding={`0 ${paddingX} ${paddingY}`}>
                  <InputLabel value={t('merch.edit.name')}>
                    <TextInput
                      testId="nameInput"
                      defaultValue={itemToEdit.name}
                      placeholder="Enter product title"
                      required
                      name="name"
                    />
                  </InputLabel>
                  <InputLabel
                    value={t('merch.edit.price')}
                    margin={`${paddingX} 0 0`}
                  >
                    <TextInput
                      required
                      name="price"
                      testId="priceInput"
                      defaultValue={itemToEdit.price?.toFixed(2)}
                      placeholder="Enter price"
                      onChange={({ setValue, value }) => {
                        let cleaned = value
                          .replace(/[^0-9.]/g, '')
                          .replace(/\.+/, '.');

                        const decimalIndex = value.indexOf('.');

                        const totalDecimalPlaces =
                          decimalIndex > -1
                            ? value.slice(decimalIndex + 1).length
                            : 0;

                        if (totalDecimalPlaces > 2) {
                          cleaned = cleaned.slice(0, decimalIndex + 3);
                        }

                        if (cleaned !== value) {
                          setValue(cleaned);
                        }
                      }}
                    />
                  </InputLabel>
                  <InputLabel
                    value={t('merch.edit.currency')}
                    margin={`${paddingX} 0 0`}
                  >
                    <SelectInput
                      name="currency"
                      testId="currencySelect"
                      items={currencySelectItems}
                      title={t('merch.edit.currency')}
                      defaultValue={itemToEdit.priceCurrency}
                    />
                  </InputLabel>
                  <InputLabel value="Link" margin={`${paddingX} 0 0`}>
                    <TextInput
                      testId="linkInput"
                      defaultValue={itemToEdit.link}
                      placeholder="Enter product link"
                      required
                      name="link"
                    />
                  </InputLabel>
                  <RemoveButton
                    margin={`${paddingX} 0 0`}
                    text={t('merch.edit.remove')}
                    testId="removeProduct"
                    onClick={async () => {
                      if (editItemIndex === undefined) {
                        return;
                      }

                      const itemsNext = [...items];

                      itemsNext.splice(editItemIndex, 1);

                      await close();

                      updateTerritoryProps({
                        items: itemsNext,
                      });
                    }}
                  />
                </Box>
              </Form>
            );
          }}
          maxHeight="47rem"
        />
      )}
    </Box>
  );
};

const MerchEditContent = memo<{
  items: StoreProduct[];
  title: string;
  sectionPath: string;
  onItemClick: (params: { data: { index: number } }) => void;
  onAddItem: (params: { item: StoreProduct }) => void;
  onDropItem: (nextItems: StoreProduct[]) => void;
}>(({ items, title, sectionPath, onItemClick, onAddItem, onDropItem }) => {
  const { updateLayoutSection } = useEditActions();
  const hasMoreThan2 = items.length > 3;
  const pageTheme = usePageTheme();

  return (
    <Box padding="1.6rem 1rem 2rem">
      <SectionTitle text={title} minHeight="3.5rem" isSticky={false} />
      <SortableHorizontalScroller
        margin="0.5rem -.9rem -"
        centerContent
        arrowOffsetY={30}
        gradientColor={pageTheme.backgroundColor}
        contentStyle={{
          padding: '0 1rem',
        }}
        renderContent={useCallback(
          ({ itemStyle, itemClassName }) => {
            return items.map(
              ({ link, name, image, price, priceCurrency }, index) => {
                return (
                  <SortableItem
                    key={link}
                    className={`${itemClassName} merchItem`}
                    style={{
                      ...itemStyle,
                      padding: '0 0.5rem',
                      width: hasMoreThan2 ? '14rem' : '33%',
                    }}
                  >
                    <HorizontalItem
                      image={image}
                      testId="merchListItem"
                      text={
                        <MerchText
                          name={name}
                          price={price}
                          priceCurrency={priceCurrency}
                        />
                      }
                      data={{ index }}
                      onClick={onItemClick as any}
                      withHoverOpacityFrom={0.9}
                      imageStyle={{
                        padding: '1rem',
                      }}
                    />
                  </SortableItem>
                );
              }
            );
          },
          [items]
        )}
        onDrop={useCallback(
          ({ removedIndex, addedIndex }) => {
            const itemsNext = [...items];
            const [removed] = itemsNext.splice(removedIndex, 1);

            itemsNext.splice(addedIndex, 0, removed);
            debug('order change', itemsNext);

            onDropItem(itemsNext);
          },
          [items, updateLayoutSection]
        )}
      />
      <AddProductButton
        sectionPath={sectionPath}
        items={items}
        onSubmit={onAddItem}
      />
    </Box>
  );
});

export const toAddProductDialogHashParam = (sectionPath: string) =>
  `${HASH_PARAM}:${sectionPath}:add`;

const AddProductButton: FC<{
  sectionPath: string;
  items: StoreProduct[];
  onSubmit: (params: { item: StoreProduct }) => void;
}> = ({ sectionPath, onSubmit, items }) => {
  const { backToBeforeFirstHash, setHashParam, hashParams } = useHash();
  const hashParam = toAddProductDialogHashParam(sectionPath);
  const maxItemsReached = items.length >= MAX_ITEMS;
  const [isLoading, setIsLoading] = useState(false);
  const dialogOpen = hashParam in hashParams;
  const appAlert = useAppAlert();
  const { t } = useI18n();
  const { userAccountId } = useFetchSessionUser();

  const alreadyHasProduct = (link: string) =>
    items.some((item) => item.link === link);

  return (
    <>
      <Clickable
        testId="addProduct"
        withActiveStyle={!maxItemsReached}
        onClick={useCallback(() => {
          if (maxItemsReached) {
            appAlert({
              content: t('itemEdit.itemLimitReached'),
            });

            return;
          }

          setHashParam({ [hashParam]: '' });
        }, [maxItemsReached])}
        margin="2rem 0 0 0"
        style={{
          opacity: maxItemsReached ? 0.3 : 1,
        }}
      >
        <Text size="1.7rem" isBold centered>
          {t('itemEdit.merch.addProduct')}
        </Text>
      </Clickable>
      {dialogOpen && (
        <DialogBox
          onClose={() => backToBeforeFirstHash()}
          fillViewportOnSmallScreen
          renderContent={({ paddingX, paddingY, close }) => {
            if (isLoading) {
              return (
                <FadeOnMount>
                  <Loading padding={paddingY} />
                </FadeOnMount>
              );
            }

            return (
              <Form<{ link: string }>
                onSubmit={async ({ values }) => {
                  try {
                    debug('on submit', values);
                    setIsLoading(true);

                    const product = await getPageProductMetadataApi(
                      values.link
                    );

                    if (!product) {
                      throw new Error(t('itemEdit.merch.errors.invalidLink'));
                    }

                    if (alreadyHasProduct(product.link)) {
                      throw new Error(t('itemEdit.merch.errors.productExists'));
                    }

                    if (product.image) {
                      debug('copying image to songwhip-images …');

                      // We copy the product image to our songwhip-images service to avoid
                      // image requests being blocked (403) by the Shopify CDN. This is likely
                      // because we render/load product images via the cloudflare Image Resize
                      // service which Shopify servers are blocking/rate-limiting.
                      const songwhipImageUrl = await uploadImageByUrl({
                        url: product.image,

                        // Use png not jpeg else songwhip-images will convert any
                        // alpha to black. Keep the alpha and let it show through to
                        // the white background below.
                        asType: 'png',
                        accountId: userAccountId,
                      });

                      product.image = songwhipImageUrl;
                      debug('… image copy complete', songwhipImageUrl);
                    }

                    debug('got product metadata', product);

                    await close();

                    onSubmit({
                      item: product,
                    });
                  } catch (error) {
                    appAlert({
                      title: 'Error',
                      content: (
                        <ErrorText
                          error={error}
                          // use the default 404 message from server
                          toText={({ status }) =>
                            status === 404
                              ? "We couldn't find a product at that link"
                              : undefined
                          }
                        />
                      ),
                    });
                  } finally {
                    setIsLoading(false);
                  }
                }}
              >
                <DialogBoxHeader
                  title={t('itemEdit.merch.addProduct')}
                  onCloseClick={close}
                  renderRight={({ textProps }) => (
                    <Clickable isSubmit testId="submit" isDisabled={isLoading}>
                      <Text {...textProps}>{t('app.actions.add')}</Text>
                    </Clickable>
                  )}
                />
                <Box padding={`0 ${paddingX} ${paddingY}`}>
                  <TextInputWithIconButton
                    Icon={ShoppingBagIcon}
                    name="link"
                    autoFocus
                    type="url"
                    required
                    isDisabled={isLoading}
                    placeholder={t('itemEdit.merch.linkInputPlaceholder')}
                    testId="linkInput"
                  />
                </Box>
              </Form>
            );
          }}
        />
      )}
    </>
  );
};

export default MerchSectionEdit;
