import {
  memo, useEffect, useMemo,
  useRef, useState,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';

import type {
  ApiChannelStatistics,
  ApiChat,
  ApiGroupStatistics,
  ApiMessage,
  ApiTypeStory,
} from '../../../api/types';

import {
  selectChat,
  selectChatFullInfo,
  selectChatMessages,
  selectPeerStories,
  selectStatistics,
} from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName';
import { callApi } from '../../../api/gramjs';
import { isGraph } from './helpers/isGraph';

import useForceUpdate from '../../../hooks/useForceUpdate';
import useOldLang from '../../../hooks/useOldLang';

import Island from '../../gili/layout/Island';
import Loading from '../../ui/Loading';
import StatisticsOverview from './StatisticsOverview';
import StatisticsRecentMessage from './StatisticsRecentMessage';
import StatisticsRecentStory from './StatisticsRecentStory';

import styles from './Statistics.module.scss';

type ILovelyChart = { create: (el: HTMLElement, params: AnyLiteral) => void };
let lovelyChartPromise: Promise<ILovelyChart> | undefined;
let LovelyChart: ILovelyChart;

async function ensureLovelyChart() {
  if (!lovelyChartPromise) {
    lovelyChartPromise = import('../../../lib/lovely-chart/LovelyChart') as Promise<ILovelyChart>;
    LovelyChart = await lovelyChartPromise;
  }

  return lovelyChartPromise;
}

const CHANNEL_GRAPHS_TITLES = {
  growthGraph: 'ChannelStats.Graph.Growth',
  followersGraph: 'ChannelStats.Graph.Followers',
  muteGraph: 'ChannelStats.Graph.Notifications',
  topHoursGraph: 'ChannelStats.Graph.ViewsByHours',
  viewsBySourceGraph: 'ChannelStats.Graph.ViewsBySource',
  newFollowersBySourceGraph: 'ChannelStats.Graph.NewFollowersBySource',
  languagesGraph: 'ChannelStats.Graph.Language',
  interactionsGraph: 'ChannelStats.Graph.Interactions',
  reactionsByEmotionGraph: 'ChannelStats.Graph.Reactions',
  storyInteractionsGraph: 'ChannelStats.Graph.Stories',
  storyReactionsByEmotionGraph: 'ChannelStats.Graph.StoriesReactions',
};
const CHANNEL_GRAPHS = Object.keys(CHANNEL_GRAPHS_TITLES) as (keyof ApiChannelStatistics)[];

const GROUP_GRAPHS_TITLES = {
  growthGraph: 'Stats.GroupGrowthTitle',
  membersGraph: 'Stats.GroupMembersTitle',
  languagesGraph: 'Stats.GroupLanguagesTitle',
  messagesGraph: 'Stats.GroupMessagesTitle',
  actionsGraph: 'Stats.GroupActionsTitle',
  topHoursGraph: 'Stats.GroupTopHoursTitle',
};
const GROUP_GRAPHS = Object.keys(GROUP_GRAPHS_TITLES) as (keyof ApiGroupStatistics)[];

export type OwnProps = {
  chatId: string;
};

export type StateProps = {
  chat?: ApiChat;
  statistics: ApiChannelStatistics | ApiGroupStatistics;
  dcId?: number;
  isGroup: boolean;
  messagesById: Record<string, ApiMessage>;
  storiesById?: Record<string, ApiTypeStory>;
};

const Statistics = ({
  chatId,
  chat,
  statistics,
  dcId,
  isGroup,
  messagesById,
  storiesById,
}: OwnProps & StateProps) => {
  const lang = useOldLang();
  const containerRef = useRef<HTMLDivElement>();
  const [isReady, setIsReady] = useState(false);
  const loadedChartsRef = useRef<Set<string>>(new Set());
  const errorChartsRef = useRef<Set<string>>(new Set());

  const { loadStatistics, loadStatisticsAsyncGraph } = getActions();
  const forceUpdate = useForceUpdate();

  useEffect(() => {
    loadStatistics({ chatId, isGroup });
  }, [chatId, loadStatistics, isGroup]);

  const graphs = useMemo(() => {
    return isGroup ? GROUP_GRAPHS : CHANNEL_GRAPHS;
  }, [isGroup]);

  const graphTitles = useMemo(() => {
    return isGroup ? GROUP_GRAPHS_TITLES : CHANNEL_GRAPHS_TITLES;
  }, [isGroup]);

  // Load async graphs
  useEffect(() => {
    if (!statistics) {
      return;
    }

    graphs.forEach((name) => {
      const graph = statistics[name as keyof typeof statistics];
      if (!isGraph(graph)) {
        return;
      }
      const isAsync = graph.graphType === 'async';

      if (isAsync) {
        loadStatisticsAsyncGraph({
          name,
          chatId,
          token: graph.token,
          // Hardcode percentage for languages graph, since API does not return `percentage` flag
          isPercentage: name === 'languagesGraph',
        });
      }
    });
  }, [graphs, chatId, statistics, loadStatisticsAsyncGraph]);

  useEffect(() => {
    (async () => {
      await ensureLovelyChart();

      if (!isReady) {
        setIsReady(true);
        return;
      }

      if (!statistics || !containerRef.current) {
        return;
      }

      graphs.forEach((name, index: number) => {
        const graph = statistics[name as keyof typeof statistics];
        if (!isGraph(graph)) {
          return;
        }

        const isAsync = graph.graphType === 'async';
        const isError = graph.graphType === 'error';

        if (isAsync || loadedChartsRef.current.has(name)) {
          return;
        }

        if (isError) {
          loadedChartsRef.current.add(name);
          errorChartsRef.current.add(name);

          return;
        }

        const { zoomToken } = graph;

        LovelyChart.create(
          containerRef.current!.children[index] as HTMLElement,
          {
            title: lang((graphTitles as Record<string, string>)[name]),
            ...zoomToken ? {
              onZoom: (x: number) => callApi('fetchStatisticsAsyncGraph', { token: zoomToken, x, dcId }),
              zoomOutLabel: lang('Graph.ZoomOut'),
            } : {},
            ...graph,
          },
        );

        loadedChartsRef.current.add(name);

        containerRef.current!.children[index].classList.remove(styles.hidden);
      });

      forceUpdate();
    })();
  }, [
    graphs, graphTitles, isReady, statistics, lang, chatId, loadStatisticsAsyncGraph, dcId, forceUpdate,
  ]);

  return (
    <div className={buildClassName(styles.root, 'panel-content custom-scroll', isReady && styles.ready)}>
      {statistics && (
        <Island>
          <StatisticsOverview
            statistics={statistics}
            type={isGroup ? 'group' : 'channel'}
            title={lang('StatisticOverview')}
          />
        </Island>
      )}

      {!loadedChartsRef.current.size && <Loading />}

      <div ref={containerRef} data-stricterdom-ignore className={styles.graphContainer}>
        {graphs.map((graph) => {
          const isGraphReady = loadedChartsRef.current.has(graph) && !errorChartsRef.current.has(graph);
          return (
            <div className={buildClassName(styles.graph, !isGraphReady && styles.hidden)} />
          );
        })}
      </div>

      {Boolean((statistics as ApiChannelStatistics)?.recentPosts?.length) && (
        <Island className={styles.messages}>
          <h2 className={styles.messagesTitle}>{lang('ChannelStats.Recent.Header')}</h2>

          {(statistics as ApiChannelStatistics).recentPosts.map((postStatistic) => {
            if ('msgId' in postStatistic) {
              const message = messagesById[postStatistic.msgId];
              if (!message || !('content' in message)) return undefined;

              return (
                <StatisticsRecentMessage
                  key={`statistic_message_${postStatistic.msgId}`}
                  message={message}
                  postStatistic={postStatistic}
                />
              );
            }

            if ('storyId' in postStatistic && chat) {
              const story = storiesById?.[postStatistic.storyId];

              return (
                <StatisticsRecentStory
                  key={`statistic_story_${postStatistic.storyId}`}
                  chat={chat}
                  story={story}
                  postStatistic={postStatistic}
                />
              );
            }

            return undefined;
          })}
        </Island>
      )}
    </div>
  );
};

export default memo(withGlobal<OwnProps>(
  (global, { chatId }): Complete<StateProps> => {
    const statistics = selectStatistics(global, chatId);
    const chat = selectChat(global, chatId);
    const dcId = selectChatFullInfo(global, chatId)?.statisticsDcId;
    const isGroup = chat?.type === 'chatTypeSuperGroup';
    const messagesById = selectChatMessages(global, chatId);
    const storiesById = selectPeerStories(global, chatId)?.byId;

    return {
      statistics, dcId, isGroup, chat, messagesById, storiesById,
    };
  },
)(Statistics));
