import { useEffect, useMemo, useRef, useState } from 'react';
import { Spinner } from '@/components/ui/spinner';
import { formatSensorMetricValue } from '@/lib/sensor-metrics';

type ChartPoint = {
    timestamp?: string | null;
    label: string | null;
    value: number;
};

type LarapexChartPayload = {
    cdn: string;
    height: number;
    type: string;
    options: Record<string, unknown>;
    series: Array<{
        name: string;
        data: number[];
    }>;
};

type ApexChartInstance = {
    render: () => Promise<void> | void;
    destroy: () => void;
};

type ApexChartsConstructor = new (
    element: HTMLElement,
    options: Record<string, unknown>,
) => ApexChartInstance;

declare global {
    interface Window {
        ApexCharts?: ApexChartsConstructor;
    }
}

let apexChartsPromise: Promise<void> | null = null;

function loadApexCharts(cdn: string): Promise<void> {
    if (window.ApexCharts) {
        return Promise.resolve();
    }

    if (apexChartsPromise) {
        return apexChartsPromise;
    }

    apexChartsPromise = new Promise((resolve, reject) => {
        const existingScript = document.querySelector<HTMLScriptElement>(
            `script[src="${cdn}"]`,
        );

        if (existingScript) {
            existingScript.addEventListener('load', () => resolve(), { once: true });
            existingScript.addEventListener('error', () => reject(), { once: true });
            return;
        }

        const script = document.createElement('script');
        script.src = cdn;
        script.async = true;
        script.onload = () => resolve();
        script.onerror = () => reject();
        document.head.appendChild(script);
    });

    return apexChartsPromise;
}

export default function SensorHistoryChart({
    metricKey,
    points,
    chart,
    color,
    average,
}: {
    metricKey: string;
    points: ChartPoint[];
    chart: LarapexChartPayload;
    color?: string;
    average?: number | null;
}) {
    const chartRef = useRef<HTMLDivElement | null>(null);
    const [chartError, setChartError] = useState(false);
    const [chartLoading, setChartLoading] = useState(points.length > 0);

    const values = points.map((point) => point.value);
    const averageValue =
        average ?? values.reduce((total, value) => total + value, 0) / Math.max(values.length, 1);
    const chartColor =
        color ??
        ((chart.options.colors as string[] | undefined)?.[0] ?? '#0f766e');
    const chartOptions = useMemo(
        () => ({
            ...chart.options,
            series: chart.series,
            chart: {
                ...((chart.options.chart as Record<string, unknown> | undefined) ?? {}),
                animations: {
                    enabled: true,
                    easing: 'easeinout',
                    speed: 450,
                },
                height: chart.height,
                toolbar: { show: false },
                type: chart.type,
                zoom: { enabled: true },
            },
            colors: [chartColor],
            dataLabels: { enabled: false },
            fill: {
                type: 'gradient',
                gradient: {
                    opacityFrom: 0.28,
                    opacityTo: 0.03,
                    shadeIntensity: 0.35,
                    stops: [0, 90, 100],
                },
            },
            grid: {
                borderColor: 'rgba(148, 163, 184, 0.22)',
                strokeDashArray: 4,
            },
            markers: {
                hover: { size: 5 },
                size: points.length <= 72 ? 3 : 0,
                strokeWidth: 0,
            },
            stroke: {
                curve: 'smooth',
                lineCap: 'round',
                width: points.length > 250 ? 2 : 3,
            },
            tooltip: {
                x: {
                    formatter: (_value: unknown, context: { dataPointIndex: number }) =>
                        points[context.dataPointIndex]?.label ?? 'Recorded reading',
                },
                y: {
                    formatter: (value: number) => formatSensorMetricValue(metricKey, value),
                },
            },
            xaxis: {
                ...((chart.options.xaxis as Record<string, unknown> | undefined) ?? {}),
                labels: {
                    hideOverlappingLabels: true,
                    rotate: 0,
                    style: {
                        colors: '#64748b',
                        fontSize: '11px',
                    },
                    trim: true,
                },
                tickAmount: Math.min(points.length, 8),
                tooltip: { enabled: false },
            },
            yaxis: {
                labels: {
                    formatter: (value: number) => formatSensorMetricValue(metricKey, value),
                    style: {
                        colors: '#64748b',
                        fontSize: '11px',
                    },
                },
            },
            annotations: {
                yaxis: [
                    {
                        borderColor: '#f97316',
                        label: {
                            borderColor: '#f97316',
                            style: {
                                background: '#f97316',
                                color: '#fff',
                                fontSize: '11px',
                            },
                            text: `Avg ${formatSensorMetricValue(metricKey, averageValue)}`,
                        },
                        y: averageValue,
                    },
                ],
            },
        }),
        [averageValue, chart, chartColor, metricKey, points],
    );

    useEffect(() => {
        let isMounted = true;
        let apexChart: ApexChartInstance | null = null;

        setChartError(false);

        if (points.length === 0) {
            setChartLoading(false);

            return () => {
                isMounted = false;
            };
        }

        setChartLoading(true);

        loadApexCharts(chart.cdn)
            .then(() => {
                if (!isMounted || !chartRef.current || !window.ApexCharts) {
                    return;
                }

                apexChart = new window.ApexCharts(chartRef.current, chartOptions);
                return apexChart.render();
            })
            .then(() => {
                if (isMounted) {
                    setChartLoading(false);
                }
            })
            .catch(() => {
                if (isMounted) {
                    setChartError(true);
                    setChartLoading(false);
                }
            });

        return () => {
            isMounted = false;
            apexChart?.destroy();
        };
    }, [chart.cdn, chartOptions, points.length]);

    if (points.length === 0) {
        return (
            <div className="rounded-lg border border-dashed p-4 text-sm text-muted-foreground">
                No chart data available.
            </div>
        );
    }

    return (
        <div className="space-y-3">
            <div className="relative min-h-64 w-full">
                <div
                    className={chartLoading ? 'opacity-25' : undefined}
                    ref={chartRef}
                />

                {chartLoading ? (
                    <div className="absolute inset-0 flex items-center justify-center rounded-lg bg-background/70 backdrop-blur-sm">
                        <div className="flex items-center gap-2 rounded-md border bg-background px-3 py-2 text-sm text-muted-foreground shadow-sm">
                            <Spinner />
                            Loading chart
                        </div>
                    </div>
                ) : null}
            </div>

            {chartError ? (
                <div className="rounded-lg border border-dashed p-4 text-sm text-muted-foreground">
                    Chart failed to load.
                </div>
            ) : null}

            <div className="flex items-center justify-between text-xs text-muted-foreground">
                <span>{points[0]?.label ?? 'Start'}</span>
                <span className="text-orange-600">
                    Average {formatSensorMetricValue(metricKey, averageValue)}
                </span>
                <span>{points.at(-1)?.label ?? 'Latest'}</span>
            </div>
        </div>
    );
}
