import { useMemo, useRef, useState } from 'react';

import type { SelectOption } from '~/src/components/Select2';
import type { TextInputApi } from '~/src/components/TextInput';

import { locales } from '~/locales/fragments/contentLink';
import Box from '~/src/components/Box';
import { Clickable } from '~/src/components/Clickable';
import HoverBackground from '~/src/components/HoverBackground';
import ChevronIcon from '~/src/components/Icon/ChevronIcon';
import RandomizeIcon from '~/src/components/Icon/RandomizeIcon';
import InputLabel from '~/src/components/InputLabel';
import Loading from '~/src/components/Loading';
import { Select } from '~/src/components/Select2';
import Text from '~/src/components/Text';
import TextInput from '~/src/components/TextInput';
import TextInputInnerBox from '~/src/components/TextInput/TextInputInnerBox';
import { useApi } from '~/src/hooks/useApi';
import useDebouncedValue from '~/src/hooks/useDebouncedValue';
import useFetchAccount from '~/src/hooks/useFetchAccount';
import useFetchSessionUser from '~/src/hooks/useFetchSessionUser';
import useIsLargeScreen from '~/src/hooks/useIsLargeScreen';
import {
  DEFAULT_DOMAIN_ID,
  resolveCustomDomains,
} from '~/src/lib/customDomain';
import { useI18n, useI18nStatic } from '~/src/lib/i18n';
import { checkCustomLinkAvailabilityApi } from '~/src/lib/songwhipApi/customLinks/check';
import { sanitizeUrlPath } from '~/src/lib/utils/url';
import { generateRandomSlug, resolveContentLinkDomain } from '../utils';

type Link = { domain: number; path?: string };
type AvailabilityState = 'checking' | 'available' | 'unavailable' | 'hidden';

interface LinkInputProps {
  isLoading: boolean;
  artistId: number;
  artistPath: string;
  defaultDomainId?: number;
  defaultPath?: string;
}

export const LinkInput = ({
  isLoading: isOuterLoading,
  artistId,
  artistPath,
  defaultDomainId,
  defaultPath,
}: LinkInputProps) => {
  const { t } = useI18nStatic<'contentLink'>(locales);

  const isLargeScreen = useIsLargeScreen();
  const inputApiRef = useRef<TextInputApi>(null);
  const { userAccountId } = useFetchSessionUser();
  const { result: account, isLoading } = useFetchAccount(userAccountId);

  const initialLink = useMemo(
    () => ({
      domain: defaultDomainId || DEFAULT_DOMAIN_ID,
      path: defaultPath?.replace(/^\//, '') ?? generateRandomSlug(),
    }),
    [defaultDomainId, defaultPath]
  );

  const [link, setLink] = useState<Link>(initialLink);
  const debouncedLink = useDebouncedValue(link, { wait: 300 });

  // In edit mode, the pre-filled link is the user's own — the backend would
  // report it as unavailable until the user changes it.
  const isEmpty = !debouncedLink.path;
  const isUnchanged =
    defaultPath !== undefined &&
    debouncedLink.path === initialLink.path &&
    debouncedLink.domain === initialLink.domain;

  const checkArgs = useMemo((): Parameters<
    typeof checkCustomLinkAvailabilityApi
  > | null => {
    if (isUnchanged) return null;

    const domain = resolveContentLinkDomain(debouncedLink.domain);

    return [
      {
        path: `/${debouncedLink.path}`,
        ...('defaultDomain' in domain ? { ...domain, artistId } : domain),
      },
    ];
  }, [debouncedLink.path, debouncedLink.domain, artistId, isUnchanged]);

  const {
    isValidating: isChecking,
    data: checkData,
    error: checkError,
  } = useApi(checkCustomLinkAvailabilityApi, checkArgs);

  const availabilityState = resolveAvailabilityState({
    isHidden: isEmpty || isUnchanged || Boolean(checkError),
    isChecking,
    isAvailable: Boolean(checkData?.available),
  });

  const options = useMemo((): SelectOption[] => {
    if (!account) return [];

    const customDomains = resolveCustomDomains({
      account,
      artistPath,
    });

    return customDomains.map(({ id, domain }) => ({
      id: id.toString(),
      text: domain,
    }));
  }, [account, artistPath]);

  return (
    <InputLabel
      value={t('detailsForm.linkLabel')}
      description={
        <Description
          link={link}
          options={options}
          availability={availabilityState}
          setLinkValue={(value) => {
            inputApiRef.current?.setValue(value);
            setLink((prev) => ({ ...prev, path: value }));
          }}
        />
      }
    >
      <Select
        testId="domainSelect"
        name="domain"
        label="Domain"
        width="100%"
        defaultValue={String(link.domain)}
        options={options}
        isLoading={isLoading}
        isDisabled={isOuterLoading}
        onChange={({ value }) => {
          if (!value) return;
          setLink((prev) => ({ ...prev, domain: Number(value) }));
        }}
        renderButton={({ option, open, isDisabled }) => {
          if (!option) return;

          return (
            <TextInput
              testId="pathInput"
              apiRef={inputApiRef}
              name="path"
              placeholder={t('detailsForm.linkPlaceholder')}
              fontSize={isLargeScreen ? '1.5rem' : '1.7rem'}
              required
              maxLength={254} // 255 minus the leading slash added in the API call
              defaultValue={link.path}
              isDisabled={isOuterLoading}
              onChange={({ value, setValue }) => {
                const sanitizedValue = sanitizeUrlPath(value);

                setValue(sanitizedValue);
                setLink((prev) => ({ ...prev, path: sanitizedValue }));
              }}
              renderBefore={() => (
                <TextInputInnerBox
                  testId="domainSelectButton"
                  maxWidth="min(30rem, 50%)"
                  padding="0"
                  pointerEvents={isDisabled ? 'none' : 'all'}
                  onClick={open}
                >
                  <HoverBackground>
                    <Box flexRow alignCenter padding="0 1rem" fullHeight>
                      <Text
                        withEllipsis
                        padding="0 .4rem 0 .2rem"
                        letterSpacing={0.02}
                        size={isLargeScreen ? '1.4rem' : '1.7rem'}
                        weight="bold"
                        color="#fff"
                      >
                        {option.text}
                      </Text>
                      <ChevronIcon
                        isInline
                        noFlexShrink
                        size="1.4rem"
                        direction="down"
                        margin="0 0 0 0.5rem"
                        color="#fff"
                      />
                    </Box>
                  </HoverBackground>
                </TextInputInnerBox>
              )}
            />
          );
        }}
      />
    </InputLabel>
  );
};

const Description = ({
  link,
  options,
  availability,
  setLinkValue,
}: {
  link: Link;
  options: SelectOption[];
  availability: AvailabilityState;
  setLinkValue(value: string): void;
}) => {
  const { t } = useI18nStatic<'contentLink'>(locales);

  const displayDomain = options.find(({ id }) => id === String(link.domain));
  const displayLink =
    displayDomain && link.path
      ? `${displayDomain.text}/${link.path}`.toLowerCase()
      : '';

  return (
    <Box flexBox alignStart>
      <Box flexGrow>
        {displayLink && (
          <>
            <Text
              isInline
              margin="0 1rem 0 0"
              style={{ wordBreak: 'break-all' }}
            >
              {displayLink}
            </Text>
            <Availability state={availability} />
          </>
        )}
      </Box>
      <Clickable
        isInline
        style={{ color: '#fff', marginLeft: '1rem', border: 'none' }}
        onClick={() => {
          const randomPath = generateRandomSlug();
          setLinkValue(randomPath);
        }}
      >
        <Box flexBox alignCenter>
          <RandomizeIcon size="1.4rem" margin="0 0.5rem 0 0" />
          <Text size="1.4rem">{t('actions.randomize')}</Text>
        </Box>
      </Clickable>
    </Box>
  );
};

const Availability = ({ state }: { state: AvailabilityState }) => {
  const { t } = useI18n('app');

  if (state === 'hidden') return null;

  return (
    <Text
      size="1rem"
      color="#b2b2b2"
      style={{
        display: 'inline-flex',
        alignItems: 'center',
      }}
    >
      {state === 'checking' ? (
        <Loading size="0.7rem" margin="0 0.5rem 0 0" />
      ) : (
        <Box
          isInline
          width="0.7rem"
          height="0.7rem"
          margin="0 0.5rem 0 0"
          style={{
            borderRadius: '50%',
            backgroundColor: state === 'available' ? '#00aa4e' : '#d95959',
          }}
        />
      )}
      {t(`states.${state}`)}
    </Text>
  );
};

const resolveAvailabilityState = ({
  isHidden,
  isChecking,
  isAvailable,
}: {
  isHidden: boolean;
  isChecking: boolean;
  isAvailable: boolean;
}): AvailabilityState => {
  if (isHidden) return 'hidden';
  if (isChecking) return 'checking';
  return isAvailable ? 'available' : 'unavailable';
};
