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

import type { TransitionInOut2Api } from '~/src/components/TransitionInOut2';
import type { I18nFormatter } from '~/src/lib/i18n';
import type { ReactNode } from 'react';

import Box from '~/src/components/Box';
import Button from '~/src/components/Button';
import LineChart from '~/src/components/Charts/LineChart';
import ErrorText from '~/src/components/ErrorText';
import RefreshIcon from '~/src/components/Icon/RefreshIcon';
import PageLoading from '~/src/components/PageLoading';
import Text from '~/src/components/Text';
import TransitionInOut2 from '~/src/components/TransitionInOut2';
import getServiceDisplayData from '~/src/lib/getServiceDisplayData';
import { useI18n } from '~/src/lib/i18n';
import { stringToColor } from '~/src/lib/utils/color';

const HEIGHT = 240;

interface TimelineHeroData {
  total: number;

  items: {
    timestamp: number;
    total: number;

    referrers?: {
      [serviceKey: string]: number;
    };
  }[];

  topReferrers?: string[];
}

interface TimelineHeroProps {
  isLoading: boolean;
  analytics?: TimelineHeroData;
  error?: { message: string; status?: number };
  onRetry?: () => void;
  maxWidth?: number | string;
  minWidth?: number | string;
  renderDefaultText?: (params: { totalDays: number | undefined }) => string;
}

const TimelineHero = ({
  isLoading,
  analytics,
  error,
  onRetry,
  renderDefaultText,
  maxWidth = '800px',
  minWidth,
}: TimelineHeroProps) => {
  const { t, tx } = useI18n('dashboard');
  const transitionRef = useRef<TransitionInOut2Api>(null);

  const [textOverride, setTextOverrides] = useState<{
    total: number;
    subtitle: ReactNode;
  }>();

  const total = analytics?.total;
  const topReferrers = analytics?.topReferrers;
  const items = analytics?.items;
  const hasContent = items?.length;
  const totalDays = items?.length;

  const { chartItems, chartLabels, totalSessionsItems } = useMemo(() => {
    const totalSessionsItems = items && {
      withPoints: true,
      color: '#000',

      items: items?.map(({ total }, index) => ({
        value: total,
        index,
      })),
    };

    const chartLabels = items?.map(({ timestamp }) => timestamp);

    // Create a line for each of the top referrers. Each line value is
    // stacked/summed on top of the last one to create a 'banding' appearance.
    const topReferrersItems = topReferrers?.reverse().reduce(
      (result, serviceKey) => {
        const { lineBelow } = result;
        const color = getServiceDisplayData(serviceKey)?.color;

        const line = {
          color: color || stringToColor(serviceKey),
          withPoints: false,
          withLine: false,
          serviceKey,

          items: items?.map(({ referrers }, index) => {
            const lineBelowValue = lineBelow?.items[index] || 0;
            const value = referrers?.[serviceKey] || 0;

            return lineBelowValue + value;
          }),
        };

        result.lines.push(line);
        result.lineBelow = line;

        return result;
      },
      { lineBelow: undefined, lines: [] } as { lineBelow: any; lines: any[] }
    );

    const chartItems = totalSessionsItems
      ? [
          {
            ...totalSessionsItems,
            withPoints: false,
            withLine: false,
          },
          ...(topReferrersItems?.lines.reverse() || []),
          { ...totalSessionsItems, withArea: false },
        ]
      : undefined;

    return {
      chartItems,
      chartLabels,
      totalSessionsItems,
    };
  }, [items, topReferrers]);

  const content = (() => {
    if (isLoading) {
      return (
        <PageLoading
          testId="dashboardTimelineLoading"
          size="2.5rem"
          text={t('buildingAnalytics')}
        />
      );
    }

    if (error && onRetry) {
      return <AnalyticsError error={error} onRetry={onRetry} />;
    }

    if (!hasContent) {
      return null;
    }

    return (
      <>
        {total !== undefined && (
          <TransitionInOut2
            coverParent
            pointerEvents="none"
            style={{ textAlign: 'center' }}
            apiRef={transitionRef}
            isVisibleInitial={false}
          >
            <div className="heroText">
              <Text
                isBold
                size="1em"
                centered
                opacity={0.9}
                shadow="0 1px 10px black"
              >
                {textOverride?.total !== undefined
                  ? textOverride?.total
                  : total}
              </Text>
              <Text
                size="0.33em"
                color="#777"
                margin="0.2em 0 0"
                isCentered
                maxWidth="11em"
                centered
                lineHeight={1.3}
                shadow="black 0px 1px 3px,black 0px 1px 16px,black 0px 1px 17px,black 0px 1px 17px"
              >
                {textOverride?.subtitle || renderDefaultText?.({ totalDays })}
              </Text>
              <style jsx>{`
                .heroText {
                  display: inline-block;
                  margin-top: 0.2em;
                  font-size: 3.6em;
                  cursor: default;
                  transition: opacity 0.5s;
                }

                .heroText:hover {
                  opacity: 0.5;
                }
              `}</style>
            </div>
          </TransitionInOut2>
        )}
        <LineChart
          height={HEIGHT}
          labels={chartLabels}
          items={chartItems}
          onVisible={() => {
            transitionRef.current?.setVisible(true);
          }}
          onFocalPointChange={(event) => {
            if (!event) {
              setTextOverrides(undefined);
              return;
            }

            if (event.series && event.series.serviceKey) {
              const { index, series } = event;
              const { serviceKey } = series;

              const service = getServiceDisplayData(serviceKey);
              const displayTime = toDaysAgo(items[index].timestamp, t);
              const total = items[index].referrers?.[serviceKey] || 0;

              setTextOverrides({
                total,
                subtitle: tx('visitsFrom', {
                  source: service?.name || serviceKey,
                  displayTime,
                }),
              });
            } else {
              const { index } = event;
              const item = totalSessionsItems!.items[index];
              const displayTime = toDaysAgo(items[index].timestamp, t);

              setTextOverrides({
                total: item.value,
                subtitle: tx('visitsTimestamp', { displayTime }),
              });
            }
          }}
        />
      </>
    );
  })();

  return (
    <Box
      testId="dashboardTimeline"
      height={HEIGHT}
      maxWidth={maxWidth}
      minWidth={minWidth}
      positionRelative
      margin="0 auto"
      padding="1em 0 0"
      style={{ boxSizing: 'content-box', fontSize: '1.15em' }}
    >
      {content}
    </Box>
  );
};

const AnalyticsError = ({
  error,
  onRetry,
}: {
  error: { message: string; status?: number };
  onRetry: () => void;
}) => {
  const { t } = useI18n('dashboard');

  return (
    <Box coverParent centerContent padding="0 2rem">
      <Box
        flexColumn
        justifyCenter
        gap="1.6rem"
        maxWidth="24rem"
        testId="dashboardTimelineError"
      >
        <ErrorText
          error={error}
          centered
          color="#999"
          size="1.3rem"
          toText={() => t('analyticsError')}
        />
        <Button
          testId="dashboardTimelineRetry"
          text={t('actions.retry')}
          Icon={RefreshIcon}
          height="4rem"
          isInline
          onClick={onRetry}
        />
      </Box>
    </Box>
  );
};

const toDaysAgo = (timestamp: number, t: I18nFormatter<'dashboard'>) => {
  const ONE_DAY = 1000 * 60 * 60 * 24;
  const delta = Date.now() - timestamp;

  const fullDaysAgo = Math.floor(delta / ONE_DAY);

  switch (fullDaysAgo) {
    case 0:
      return t('labels.today');
    case 1:
      return t('labels.yesterday');
    default:
      return (
        <span style={{ whiteSpace: 'nowrap' }}>
          {t('daysAgo', { count: fullDaysAgo })}
        </span>
      );
  }
};

export default TimelineHero;
