/* eslint-disable @typescript-eslint/no-explicit-any */

import _, { maxBy } from 'lodash';
// @ts-ignore
import numeral from 'numeral';
import type { FunctionComponent } from 'react';
import React, { useRef, useState } from 'react';
import type { TextInput } from 'react-native';
import { useWindowDimensions, View } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import type { Dayjs } from 'dayjs';
import dayjs from 'dayjs';
import { withTheme } from '../../branding';
import type { ThemeProps } from '../../branding/hoc/types';
import { STREAM_COUNT_FORMAT } from '../../constants';
import useUpdateEffect from '../../hooks/utils/useUpdateEffect';
import type { Timeseries } from '../../types/types';
import MultilineChartLabel from '../LineChart/components/MultilineChartLabel';
import Chart, {
    MULTILINE_CHART_LABEL_MODE_CHARTS_DATA,
    MULTILINE_CHART_LABEL_MODE_NONE,
    MULTILINE_CHART_LABEL_MODE_NO_DATA
} from '../LineChart';
import themedStyles from './styles';
import { track } from '../../services/analytics.service';
import { formatMessage } from '../../i18n';
import {
    ALL_OTHERS,
    ROW_KEY_TOTAL
} from '../../screens/SoundRecordingTikTokPOTScreen/constants';
import { DETAILED_SOS_CHART_LABELS } from '../../constants/detailed-sos-keys';
import useStoreSelectionFilters from '../../hooks/useStoreSelectionFilters';
import useHasFeature from '../../hooks/auth/useHasFeature';
import { MOBILE_CHART_LEGEND_LONG_PRESS } from '../../constants/features';

export enum LabelMode {
    none,
    chartsData,
    noData
}

export interface ChartConfig {
    key: string;
    timeseries: Timeseries[];
    color: string;
    displayLabel: boolean;
}
export interface LineChartDataConfig {
    key: string;
    point: LabelPoint[];
    type: string;
    displayLabel: boolean;
}

export interface LabelPoint {
    x: Dayjs;
    y: number | null;
}

export interface LineChartElement {
    point: LabelPoint[];
    highestPoint?: LabelPoint;
}

interface Props {
    chartConfigs: ChartConfig[];
    lineOpacity?: number;
    lineStrokeWidth?: number;
    labelMode?: LabelMode;
    toggleScroll: (enabled: boolean) => void;
    onInteractionStart?: () => void;
    isStackedCharts?: boolean | undefined;
    showOriginMinValue?: boolean | undefined;
    chartInteractionEventName?: string;
    customLabelElement?: React.ReactElement;
    shouldExcludeAllOthers?: boolean;
    customCursorMove?: (
        selectedDate: number,
        lineChartData: LineChartDataConfig[]
    ) => void;
}

const CHART_HEIGHT = 170;
const labelModes: Record<LabelMode, string> = {
    [LabelMode.none]: MULTILINE_CHART_LABEL_MODE_NONE,
    [LabelMode.chartsData]: MULTILINE_CHART_LABEL_MODE_CHARTS_DATA,
    [LabelMode.noData]: MULTILINE_CHART_LABEL_MODE_NO_DATA
};

const filterAllOthers =
    (shouldExcludeAllOthers: boolean = false) =>
    (item: { key: string }) =>
        !shouldExcludeAllOthers || item.key !== ALL_OTHERS;

const getRawLabelMode = (mode: LabelMode | undefined) => {
    const defaultMode = MULTILINE_CHART_LABEL_MODE_CHARTS_DATA;
    const foundMode = mode ? labelModes[mode] : undefined;
    const existingMode = foundMode ?? defaultMode;
    return existingMode;
};

const timeseriesToPoint = ({ date, value }: Timeseries) => {
    return {
        x: dayjs(date),
        y: value
    };
};

const chartConfigToLineChartDataItem = (
    { key, color, timeseries, displayLabel }: ChartConfig,
    labelTextRef: React.MutableRefObject<TextInput | undefined>,
    selectedStoreName: string,
    isStackedCharts: boolean
) => {
    const nameKey =
        DETAILED_SOS_CHART_LABELS[key] && selectedStoreName
            ? DETAILED_SOS_CHART_LABELS[key]
            : key;

    return {
        key: nameKey,
        color,
        point: timeseries.map(timeseriesToPoint),
        type: isStackedCharts ? 'area' : 'line',
        labelTextRef,
        displayLabel
    };
};

const chartConfigsToLineChartData = (
    configs: ChartConfig[],
    labelTextRefs: React.MutableRefObject<TextInput | undefined>[],
    selectedStoreName: string,
    isStackedCharts: boolean
) => {
    const lineData = configs.map((value, index) =>
        chartConfigToLineChartDataItem(
            value,
            labelTextRefs[index],
            selectedStoreName,
            isStackedCharts
        )
    );

    return lineData.length > 1
        ? lineData.filter(item => item.key !== ROW_KEY_TOTAL)
        : lineData;
};

const valueForDate = (
    data: {
        point: { x: dayjs.Dayjs; y: number }[];
    },
    selectedDate: number
) => data.point.find(point => point.x?.valueOf() === selectedDate)?.y;

const formatValue = (value: number) =>
    `${numeral(value).format(STREAM_COUNT_FORMAT).toUpperCase()}`;

export const MultilineStreamsChart = ({
    chartConfigs = [],
    toggleScroll,
    onInteractionStart,
    theme,
    colors,
    lineOpacity,
    labelMode,
    isStackedCharts,
    showOriginMinValue,
    chartInteractionEventName,
    customLabelElement,
    customCursorMove,
    lineStrokeWidth,
    shouldExcludeAllOthers
}: Props & ThemeProps) => {
    const rawLabelMode = getRawLabelMode(labelMode);
    const labelTextRefs = useRef<
        React.MutableRefObject<TextInput | undefined>[]
    >(Array.from({ length: 15 }, () => ({ current: undefined })));
    const navigation = useNavigation();
    const styles = (themedStyles as any)[theme];
    const { width: windowWidth } = useWindowDimensions();
    const marginHorizontal = 10;
    const width = windowWidth - marginHorizontal * 2;
    const labelPaddingHorizontalValue = 30;
    const [isInteracting, setIsInteracting] = useState(false);
    const hasLongPressLegend = useHasFeature(MOBILE_CHART_LEGEND_LONG_PRESS);
    const { selectedStoreName } = useStoreSelectionFilters();
    const [lineChartData, setLineChartData] = useState(
        chartConfigsToLineChartData(
            chartConfigs,
            labelTextRefs.current,
            selectedStoreName,
            isStackedCharts ?? false
        )
    );
    const chartInteractionEventTriggered = useRef(false);
    const multilineModeDayTextRef = useRef<TextInput>(undefined);
    const multilineModeTotalTextRef = useRef<TextInput>(undefined);

    const debouncedToggleScroll = useRef(
        _.debounce((isEnabled: boolean) => {
            toggleScroll(isEnabled === true);
        }, 100)
    );

    useUpdateEffect(() => {
        setLineChartData(
            chartConfigsToLineChartData(
                chartConfigs,
                labelTextRefs.current,
                selectedStoreName,
                isStackedCharts ?? false
            )
        );
    }, [chartConfigs, isStackedCharts, selectedStoreName]);

    const formatMultilineModeDay = (date: number) => {
        const text = dayjs(date).format('DD MMM');
        return text.toUpperCase();
    };

    const handleCursorMove = (selectedDate: number) => {
        if (customCursorMove) {
            return customCursorMove(selectedDate, lineChartData);
        }
        lineChartData.forEach(it => {
            const value = valueForDate(it, selectedDate);
            const text = typeof value === 'number' ? formatValue(value) : '-';
            it.labelTextRef?.current?.setNativeProps?.({
                text
            });
        });
        multilineModeDayTextRef.current?.setNativeProps?.({
            text: formatMultilineModeDay(selectedDate)
        });

        const values = lineChartData.map(it => valueForDate(it, selectedDate));
        const validValues = values.filter(
            (v): v is number => typeof v === 'number'
        );

        let totalText: string;
        if (!validValues.length) {
            totalText = formatMessage('pot.total', {
                total: '-'
            });
        } else {
            const total = validValues.reduce((acc, val) => acc + val, 0);
            totalText = formatMessage('pot.total', {
                total: formatValue(total)
            });
        }

        multilineModeTotalTextRef.current?.setNativeProps?.({
            text: totalText
        });
    };

    const handleInteractionStart = () => {
        setIsInteracting(true);
        if (
            chartInteractionEventName &&
            !chartInteractionEventTriggered.current
        ) {
            track(chartInteractionEventName);
        }
        if (onInteractionStart) {
            onInteractionStart();
        }
    };

    const handleInteractionFinish = () => {
        setIsInteracting(false);
        chartInteractionEventTriggered.current = true;
    };

    useUpdateEffect(() => {
        const scrollEnabled = !isInteracting;
        // @ts-ignore
        navigation.setParams({ scrollEnabled });
        debouncedToggleScroll.current(scrollEnabled);
    }, [isInteracting]);

    const renderMultilineLabel = () => {
        const filteredData = lineChartData
            .filter(filterAllOthers(shouldExcludeAllOthers))
            .filter(({ displayLabel }) => displayLabel);

        return (
            <MultilineChartLabel
                dayTextRef={multilineModeDayTextRef as React.Ref<TextInput>}
                totalTextRef={multilineModeTotalTextRef as React.Ref<TextInput>}
                chartConfigs={filteredData.map(
                    ({ key, color, labelTextRef }) => ({
                        key,
                        color,
                        chartTextRef: labelTextRef as React.Ref<TextInput>
                    })
                )}
                vertical
            />
        );
    };

    const getStackedChartPoints = (
        currentChartPoints: [LabelPoint],
        nextChartPoints: [LabelPoint]
    ) => {
        return currentChartPoints.map((elem, i: number) => {
            const isDataGap = elem.y === null || nextChartPoints[i].y === null;
            const currentY = elem.y as number;
            const nextY = nextChartPoints[i].y as number;
            return {
                ...elem,
                y: isDataGap ? null : currentY + nextY
            };
        });
    };

    const getStackedChartsData = () => {
        return lineChartData.reduceRight((acc, elem, i) => {
            let currentElement;
            const currentChartPoints = elem.point;
            const highestPoint = maxBy(currentChartPoints as [LabelPoint], 'y');

            if (i === lineChartData.length - 1) {
                currentElement = elem;
            } else {
                const prevChartPoints = acc[0]?.point;
                currentElement = {
                    ...elem,
                    point: getStackedChartPoints(
                        currentChartPoints as [LabelPoint],
                        prevChartPoints as [LabelPoint]
                    )
                };
            }

            acc.unshift({ ...currentElement, highestPoint });
            return acc;
        }, [] as unknown as [LineChartElement]);
    };

    const data = isStackedCharts
        ? getStackedChartsData()
        : lineChartData.filter(filterAllOthers(shouldExcludeAllOthers));

    return (
        <View
            style={{
                ...styles.container,
                marginHorizontal,
                ...styles.containerPadding
            }}
        >
            <Chart
                height={CHART_HEIGHT}
                width={width}
                isStackedCharts={isStackedCharts}
                showOriginMinValue={showOriginMinValue}
                onCursorMove={handleCursorMove}
                cursorWidth={2}
                lineOpacity={lineOpacity}
                onInteractionStart={handleInteractionStart}
                onInteractionFinish={handleInteractionFinish}
                cursorColor={colors.midnight950}
                evenBackgroundColor={colors.midnight950}
                oddBackgroundColor={colors.midnight950}
                colorLabelText={colors.gray0}
                hasLegend={false}
                isLegendHiddenOutsideInteraction={hasLongPressLegend}
                snapToDate
                labelCustomElement={
                    customLabelElement || renderMultilineLabel()
                }
                labelPaddingHorizontal={labelPaddingHorizontalValue}
                labelPaddingTop={-20}
                labelBackgroundColor={colors.midnight1000}
                data={data}
                multipleChart={undefined}
                multilineChartLabelMode={rawLabelMode}
                multilineCursorHasBorder
                hasYAxisShadow
                labelOpacity={0.8}
                lineStrokeWidth={lineStrokeWidth}
            />
        </View>
    );
};

export default withTheme(MultilineStreamsChart) as FunctionComponent<Props>;
