import {
    Chart as ChartJS,
    ChartData,
    ChartDataset,
    ChartOptions,
    Filler,
    LinearScale,
    LineElement,
    PointElement,
} from 'chart.js';
import { DeepPartial } from 'ts-essentials';
import { useState } from 'react';
import { deepmerge, deepmergeCustom } from 'deepmerge-ts';
import { theme } from 'twin.macro';
import { hexToRgba } from '@/lib/helpers';

ChartJS.register(LineElement, PointElement, Filler, LinearScale);

const options: ChartOptions<'line'> = {
    responsive: true,
    animation: false,
    plugins: {
        legend: { display: false },
        title: { display: false },
        tooltip: { enabled: false },
    },
    layout: {
        padding: 0,
    },
    scales: {
        x: {
            min: 0,
            max: 19,
            type: 'linear',
            grid: {
                display: false,
                drawBorder: false,
            },
            ticks: {
                display: false,
            },
        },
        y: {
            min: 0,
            type: 'linear',
            grid: {
                display: true,
                color: theme('colors.gray.700'),
                drawBorder: false,
            },
            ticks: {
                display: true,
                count: 3,
                color: theme('colors.gray.200'),
                font: {
                    family: theme('fontFamily.sans'),
                    size: 11,
                    weight: '400',
                },
            },
        },
    },
    elements: {
        point: {
            radius: 0,
        },
        line: {
            tension: 0.3,
        },
    },
};

function getOptions (opts?: DeepPartial<ChartOptions<'line'>> | undefined): ChartOptions<'line'> {
    return deepmerge(options, opts || {});
}

type ChartDatasetCallback = (value: ChartDataset<'line'>, index: number) => ChartDataset<'line'>;

function getEmptyData (label: string, sets = 1, callback?: ChartDatasetCallback | undefined): ChartData<'line'> {
    const next = callback || (value => value);

    return {
        labels: Array(20).fill(0).map((_, index) => index),
        datasets: Array(sets).fill(0).map((_, index) => next({
            fill: true,
            label,
            data: Array(20).fill(0),
            borderColor: theme('colors.cyan.400'),
            backgroundColor: hexToRgba(theme('colors.cyan.700'), 0.5),
        }, index)),
    };
}

const merge = deepmergeCustom({ mergeArrays: false });

interface UseChartOptions {
    sets: number;
    options?: DeepPartial<ChartOptions<'line'>> | number | undefined;
    callback?: ChartDatasetCallback | undefined;
}

function useChart (label: string, opts?: UseChartOptions) {
    const options = getOptions(typeof opts?.options === 'number' ? { scales: { y: { min: 0, suggestedMax: opts.options } } } : opts?.options);
    const [ data, setData ] = useState(getEmptyData(label, opts?.sets || 1, opts?.callback));

    const push = (items: number | null | ((number | null)[])) => setData(state => merge(state, {
        datasets: (Array.isArray(items) ? items : [ items ]).map((item, index) => ({
            ...state.datasets[index],
            data: state.datasets[index].data.slice(1).concat(item),
        })),
    }));

    const clear = () => setData(state => merge(state, {
        datasets: state.datasets.map(value => ({
            ...value,
            data: Array(20).fill(0),
        })),
    }));

    return { props: { data, options }, push, clear };
}

function useChartTickLabel (label: string, max: number, tickLabel: string) {
    return useChart(label, {
        sets: 1,
        options: {
            scales: {
                y: {
                    suggestedMax: max,
                    ticks: {
                        callback (value) {
                            return `${value}${tickLabel}`;
                        },
                    },
                },
            },
        },
    });
}

export { useChart, useChartTickLabel, getOptions, getEmptyData };