<template>
    <axis-chart class="trends-chart-container" ref="axisChart" values-field="values"
                :skeleton="skeleton" :type="chartDisplayType" :stacked="stacked" :sorting-type="sortingType"
                :show-value="showValue"
                :show-total-amount-in-tooltip="showTotalAmountInTooltip" :total-name-in-tooltip="tt('Total Amount')"
                :category-type-name="tt('Date')" :all-category-names="allDisplayDateRanges" :items="allSeriesData"
                :id-field="idField" :name-field="nameField" :color-field="colorField" :hidden-field="hiddenField"
                :display-orders-field="displayOrdersField"
                :translate-name="translateName"
                :amount-value="true" :default-currency="defaultCurrency"
                :enable-click-item="enableClickItem"
                :tooltip-extra-column-names="allTooltipExtraColumnNames"
                :tooltip-extra-column-total-values="showYearOverYear || showPeriodOverPeriod ? getTooltipExtraColumnTotalValues : undefined"
                :tooltip-extra-column-values="showYearOverYear || showPeriodOverPeriod ? getTooltipExtraColumnValues : undefined"
                @click="clickItem"
                v-if="chartDisplayType"
    />
</template>

<script setup lang="ts">
import AxisChart, { type AxisChartDisplayType } from './AxisChart.vue';

import { computed, useTemplateRef } from 'vue';

import { useI18n } from '@/locales/helpers.ts';
import {
    type TrendsChartDateType,
    type CommonTrendsChartProps,
    type TrendsBarChartClickEvent,
    useTrendsChartBase
} from '@/components/base/TrendsChartBase.ts'

import { useUserStore } from '@/stores/user.ts';

import {
    itemAndIndex
} from '@/core/base.ts';
import {
    type Year1BasedMonth,
    type YearMonthDay,
    type YearUnixTime,
    type YearQuarterUnixTime,
    type YearMonthUnixTime,
    type YearMonthDayUnixTime,
    DateRangeScene
} from '@/core/datetime.ts';
import {
    type FiscalYearUnixTime
} from '@/core/fiscalyear.ts';
import {
    ChartDataAggregationType,
    TrendChartType,
    ChartDateAggregationType
} from '@/core/statistics.ts';

import {
    isArray,
    isNumber
} from '@/lib/common.ts';
import {
    parseDateTimeFromUnixTime,
    getYearMonthFirstUnixTime,
    getYearMonthLastUnixTime,
    getDateTypeByDateRange,
    getFiscalYearFromUnixTime
} from '@/lib/datetime.ts';

type AxisChartType = InstanceType<typeof AxisChart>;

interface DesktopTrendsChartProps<T extends TrendsChartDateType> extends CommonTrendsChartProps<T> {
    skeleton?: boolean;
    type?: number;
    showValue?: boolean;
    showTotalAmountInTooltip?: boolean;
    showYearOverYear?: boolean;
    showPeriodOverPeriod?: boolean;
}

const props = defineProps<DesktopTrendsChartProps<TrendsChartDateType>>();

const emit = defineEmits<{
    (e: 'click', value: TrendsBarChartClickEvent): void;
}>();

const {
    tt,
    formatDateTimeToShortDate,
    formatDateTimeToGregorianLikeShortYear,
    formatDateTimeToGregorianLikeShortYearMonth,
    formatYearQuarterToGregorianLikeYearQuarter,
    formatDateTimeToGregorianLikeFiscalYear,
    formatPercentToLocalizedNumerals
} = useI18n();

const { allDateRanges } = useTrendsChartBase(props);

const userStore = useUserStore();

const axisChart = useTemplateRef<AxisChartType>('axisChart');

const chartDisplayType = computed<AxisChartDisplayType | undefined>(() => {
    if (props.type === TrendChartType.Area.type) {
        return 'area';
    } else if (props.type === TrendChartType.Column.type) {
        return 'column';
    } else if (props.type === TrendChartType.Bubble.type) {
        return 'bubble';
    } else {
        return undefined;
    }
});

const allTooltipExtraColumnNames = computed<string[]>(() => {
    const extraColumnNames: string[] = [];

    if (props.showYearOverYear) {
        extraColumnNames.push(tt('Year-over-Year'));
    }

    if (props.showPeriodOverPeriod) {
        extraColumnNames.push(tt('Period-over-Period'));
    }

    return extraColumnNames;
});

const allDisplayDateRanges = computed<string[]>(() => {
    const allDisplayDateRanges: string[] = [];

    for (const dateRange of allDateRanges.value) {
        const minDateTime = parseDateTimeFromUnixTime(dateRange.minUnixTime);

        if (props.dateAggregationType === ChartDateAggregationType.Year.type) {
            allDisplayDateRanges.push(formatDateTimeToGregorianLikeShortYear(minDateTime));
        } else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type && 'year' in dateRange) {
            allDisplayDateRanges.push(formatDateTimeToGregorianLikeFiscalYear(minDateTime));
        } else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type && 'quarter' in dateRange) {
            allDisplayDateRanges.push(formatYearQuarterToGregorianLikeYearQuarter(dateRange.year, dateRange.quarter));
        } else if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
            allDisplayDateRanges.push(formatDateTimeToGregorianLikeShortYearMonth(minDateTime));
        } else if (props.dateAggregationType === ChartDateAggregationType.Day.type && props.chartMode === 'daily') {
            allDisplayDateRanges.push(formatDateTimeToShortDate(minDateTime));
        }
    }

    return allDisplayDateRanges;
});

const allSeriesData = computed<Record<string, unknown>[]>(() => {
    const result: Record<string, unknown>[] = [];

    for (const item of props.items) {
        if (props.hiddenField && item[props.hiddenField]) {
            continue;
        }

        const finalItem: Record<string, unknown> = {};

        if (props.idField) {
            finalItem[props.idField] = item[props.idField];
        }

        if (props.nameField) {
            finalItem[props.nameField] = item[props.nameField];
        }

        if (props.colorField) {
            finalItem[props.colorField] = item[props.colorField];
        }

        if (props.hiddenField) {
            finalItem[props.hiddenField] = item[props.hiddenField];
        }

        if (props.displayOrdersField) {
            finalItem[props.displayOrdersField] = item[props.displayOrdersField];
        }

        const allAmounts: number[] = [];
        const dateRangeAmountMap: Record<string, (Year1BasedMonth | YearMonthDay)[]> = {};

        for (const dataItem of item.items) {
            let dateRangeKey = '';

            if (props.chartMode === 'daily' && 'month' in dataItem) {
                if (props.dateAggregationType === ChartDateAggregationType.Year.type) {
                    dateRangeKey = dataItem.year.toString();
                } else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type) {
                    const fiscalYear = getFiscalYearFromUnixTime(
                        getYearMonthFirstUnixTime({ year: dataItem.year, month1base: dataItem.month }),
                        props.fiscalYearStart
                    );
                    dateRangeKey = fiscalYear.toString();
                } else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type) {
                    dateRangeKey = `${dataItem.year}-${Math.floor((dataItem.month - 1) / 3) + 1}`;
                } else if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
                    dateRangeKey = `${dataItem.year}-${dataItem.month}`;
                } else { // if (props.dateAggregationType === ChartDateAggregationType.Day.type) {
                    dateRangeKey = `${dataItem.year}-${dataItem.month}-${dataItem.day}`;
                }
            } else if (props.chartMode === 'monthly' && 'month1base' in dataItem) {
                if (props.dateAggregationType === ChartDateAggregationType.Year.type) {
                    dateRangeKey = dataItem.year.toString();
                } else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type) {
                    const fiscalYear = getFiscalYearFromUnixTime(
                        getYearMonthFirstUnixTime({ year: dataItem.year, month1base: dataItem.month1base }),
                        props.fiscalYearStart
                    );
                    dateRangeKey = fiscalYear.toString();
                } else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type) {
                    dateRangeKey = `${dataItem.year}-${Math.floor((dataItem.month1base - 1) / 3) + 1}`;
                } else { // if (props.dateAggregationType === ChartDateAggregationType.Month.type) {
                    dateRangeKey = `${dataItem.year}-${dataItem.month1base}`;
                }
            }

            const dataItems = dateRangeAmountMap[dateRangeKey] || [];
            dataItems.push(dataItem);

            dateRangeAmountMap[dateRangeKey] = dataItems;
        }

        for (const dateRange of allDateRanges.value) {
            const dateRangeKey = getDateRangeKey(dateRange) ?? '';
            const dataItems = dateRangeAmountMap[dateRangeKey];
            let amount = 0;

            if (isArray(dataItems)) {
                for (const dataItem of dataItems) {
                    const value = (dataItem as unknown as Record<string, unknown>)[props.valueField];

                    if (isNumber(value)) {
                        if (props.dataAggregationType === ChartDataAggregationType.Sum) {
                            amount += value;
                        } else if (props.dataAggregationType === ChartDataAggregationType.Last) {
                            amount = value;
                        }
                    }
                }
            }

            allAmounts.push(amount);
        }

        finalItem['values'] = allAmounts;
        result.push(finalItem);
    }

    return result;
});

const seriesIdValuesMap = computed<Record<string, number[]>>(() => {
    const result: Record<string, number[]> = {};

    for (const item of allSeriesData.value) {
        const id = getSeriesId(item);
        const values = item['values'] as number[];

        if (id && values) {
            result[id] = values;
        }
    }

    return result;
});

const yoyIndexMap = computed<Record<number, number>>(() => {
    const result: Record<number, number> = {};
    const dateKeyToIndex: Record<string, number> = {};

    for (const [dateRange, index] of itemAndIndex(allDateRanges.value)) {
        const key = getDateRangeKey(dateRange);

        if (key) {
            dateKeyToIndex[key] = index;
        }
    }

    for (const [dateRange, index] of itemAndIndex(allDateRanges.value)) {
        const yoyKey = getDateRangeKey(dateRange, -1);

        if (yoyKey && isNumber(dateKeyToIndex[yoyKey])) {
            result[index] = dateKeyToIndex[yoyKey];
        }
    }

    return result;
});

function getSeriesId(item: Record<string, unknown>): string {
    if (props.idField && item[props.idField]) {
        return item[props.idField] as string;
    }

    const name = item[props.nameField] as string;
    return props.translateName ? tt(name) : name;
}

function getDateRangeKey(dateRange: YearUnixTime | FiscalYearUnixTime | YearQuarterUnixTime | YearMonthUnixTime | YearMonthDayUnixTime, yearOffset?: number): string | undefined {
    if (props.dateAggregationType === ChartDateAggregationType.Year.type) {
        return (dateRange.year + (yearOffset ?? 0)).toString();
    } else if (props.dateAggregationType === ChartDateAggregationType.FiscalYear.type && 'year' in dateRange) {
        return (dateRange.year + (yearOffset ?? 0)).toString();
    } else if (props.dateAggregationType === ChartDateAggregationType.Quarter.type && 'quarter' in dateRange) {
        return `${dateRange.year + (yearOffset ?? 0)}-${dateRange.quarter}`;
    } else if (props.dateAggregationType === ChartDateAggregationType.Month.type && 'month0base' in dateRange) {
        return `${dateRange.year + (yearOffset ?? 0)}-${dateRange.month0base + 1}`;
    } else if (props.dateAggregationType === ChartDateAggregationType.Day.type && 'day' in dateRange && props.chartMode === 'daily') {
        return `${dateRange.year + (yearOffset ?? 0)}-${dateRange.month}-${dateRange.day}`;
    } else {
        return undefined;
    }
}

function formatDisplayChangeRate(current: number, reference: number): string {
    if (reference === 0 && current === 0) {
        return formatPercentToLocalizedNumerals(0, 2, '<0.01');
    }

    if (reference === 0) {
        return '-';
    }

    const rate = (current - reference) / reference * 100;
    return formatPercentToLocalizedNumerals(rate, 2, '<0.01');
}

function getTooltipExtraColumnTotalValues(categoryIndex: number, totalValue: number, visibleSeriesIds: string[]): string[] {
    const extraColumnValues: string[] = [];

    if (!props.showYearOverYear && !props.showPeriodOverPeriod) {
        return extraColumnValues;
    }

    if (props.showYearOverYear) {
        const yoyReferenceIndex = yoyIndexMap.value[categoryIndex];
        let displayChangeRate = '-';

        if (isNumber(yoyReferenceIndex)) {
            let referenceTotalValue = 0;

            for (const seriesId of visibleSeriesIds) {
                const values = seriesIdValuesMap.value[seriesId];

                if (values) {
                    referenceTotalValue += values[yoyReferenceIndex] ?? 0;
                }
            }

            displayChangeRate = formatDisplayChangeRate(totalValue, referenceTotalValue);
        }

        extraColumnValues.push(displayChangeRate);
    }

    if (props.showPeriodOverPeriod) {
        const popReferenceIndex = categoryIndex - 1;
        let displayChangeRate = '-';

        if (popReferenceIndex >= 0) {
            let referenceTotalValue = 0;

            for (const seriesId of visibleSeriesIds) {
                const values = seriesIdValuesMap.value[seriesId];

                if (values) {
                    referenceTotalValue += values[popReferenceIndex] ?? 0;
                }
            }

            displayChangeRate = formatDisplayChangeRate(totalValue, referenceTotalValue);
        }

        extraColumnValues.push(displayChangeRate);
    }

    return extraColumnValues;
}

function getTooltipExtraColumnValues(seriesId: string, categoryIndex: number, currentValue: number): string[] {
    const extraColumnValues: string[] = [];

    if (!props.showYearOverYear && !props.showPeriodOverPeriod) {
        return extraColumnValues;
    }

    const values = seriesIdValuesMap.value[seriesId];

    if (!values) {
        return extraColumnValues;
    }

    if (props.showYearOverYear) {
        const yoyReferenceIndex = yoyIndexMap.value[categoryIndex];
        let displayChangeRate = '-';

        if (isNumber(yoyReferenceIndex) && yoyReferenceIndex >= 0 && yoyReferenceIndex < values.length) {
            displayChangeRate = formatDisplayChangeRate(currentValue, values[yoyReferenceIndex] ?? 0);
        }

        extraColumnValues.push(displayChangeRate);
    }

    if (props.showPeriodOverPeriod) {
        const popReferenceIndex = categoryIndex - 1;
        let displayChangeRate = '-';

        if (popReferenceIndex >= 0 && popReferenceIndex < values.length) {
            displayChangeRate = formatDisplayChangeRate(currentValue, values[popReferenceIndex] ?? 0);
        }

        extraColumnValues.push(displayChangeRate);
    }

    return extraColumnValues;
}

function clickItem(itemId: string, categoryIndex: number): void {
    const dateRange = allDateRanges.value[categoryIndex];

    if (!dateRange) {
        return;
    }

    let minUnixTime = dateRange.minUnixTime;
    let maxUnixTime = dateRange.maxUnixTime;

    if (props.chartMode === 'daily') {
        if (props.startTime) {
            if (props.startTime > minUnixTime) {
                minUnixTime = props.startTime;
            }
        }

        if (props.endTime) {
            if (props.endTime < maxUnixTime) {
                maxUnixTime = props.endTime;
            }
        }
    } else if (props.chartMode === 'monthly') {
        if (props.startYearMonth) {
            const startMinUnixTime = getYearMonthFirstUnixTime(props.startYearMonth);

            if (startMinUnixTime > minUnixTime) {
                minUnixTime = startMinUnixTime;
            }
        }

        if (props.endYearMonth) {
            const endMaxUnixTime = getYearMonthLastUnixTime(props.endYearMonth);

            if (endMaxUnixTime < maxUnixTime) {
                maxUnixTime = endMaxUnixTime;
            }
        }
    }

    const dateRangeType = getDateTypeByDateRange(minUnixTime, maxUnixTime, userStore.currentUserFirstDayOfWeek, userStore.currentUserFiscalYearStart, DateRangeScene.Normal);

    emit('click', {
        itemId: itemId,
        dateRange: {
            minTime: minUnixTime,
            maxTime: maxUnixTime,
            dateType: dateRangeType
        }
    });
}

function exportData(): { headers: string[], data: string[][] } {
    return axisChart.value?.exportData() ?? { headers: [], data: [] };
}

defineExpose({
    exportData
})
</script>

<style scoped>
.trends-chart-container {
    width: 100%;
    height: 720px;
    margin-top: 10px;
}

@media (min-width: 600px) {
    .trends-chart-container {
        height: 790px;
    }
}
</style>
