import React, { useRef, useEffect, useState } from "react";
import ReactECharts from "echarts-for-react";

const EmbedTimeline = React.memo(function EmbedTimeline({
    chartRef,
    data_in,
    onTimelineClick,
    authenticatedFetch,
    markerTime,
    hideDataZoom,
}) {
    // Use useRef instead of props/state to avoid re-renders
    const zoomed_range = useRef([null, null]);
    const lineDataRef = useRef(null);
    const isZoomedInRef = useRef(false);
    const hasLineSeriesRef = useRef(false);

    // --- Early return if loading ---
    if (!data_in) return <div>Loading....</div>;

    // --- Constants ---
    const BREAK_GAP = 0;
    const ZOOM_THRESHOLD = 4 * 60 * 60 * 1000; // 1 hour in ms
    const BREAK_THRESHOLD_SECONDS = 60 * 15;

    const fetchLineData = async (startTime, endTime) => {
        try {
            const params = new URLSearchParams();
            params.append("start_time", new Date(startTime).toISOString());
            params.append("end_time", new Date(endTime).toISOString());
            const response = await authenticatedFetch(
                "/api/line_data.json?" + params.toString()
            );

            if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
            const data = await response.json();

            return data["line_data"].map((d) => [d[0] * 1000, d[1]]);
        } catch (error) {
            console.error("Failed to fetch line data:", error);
            return null;
        }
    };

    const addLineToChart = (chart, lineData) => {
        let vs = chart.getOption().series;

        var ser_mod = vs[2];
        ser_mod.data = fillNulls(lineData).map((d) => [
            virtualTime(new Date(d[0])),
            d[1],
        ]);
        chart.setOption({
            series: [{}, {}, ser_mod],
        });
    };

    const removeLineFromChart = (chart) => {
        let vs = chart.getOption().series;

        var ser_mod = vs[2];
        ser_mod.data = [];
        chart.setOption({
            series: [{}, {}, ser_mod],
        });
    };

    const handleDataZoom = async (params, chart) => {
        if (!params || !chart) return;

        // console.log(zoomed_range.current);
        let plot_start_time = zoomed_range.current[0];
        let plot_end_time = zoomed_range.current[1];

        const option = chart.getOption();
        const dataZoom = option.dataZoom?.[0];
        if (
            dataZoom &&
            dataZoom.startValue !== undefined &&
            dataZoom.endValue !== undefined
        ) {
            const zoomRange = dataZoom.endValue - dataZoom.startValue;
            const shouldShowLine = zoomRange < ZOOM_THRESHOLD;

            let offset_start = dataZoom.startValue - plot_start_time;
            let offset_end = dataZoom.endValue - plot_end_time;
            // console.log(offset_start, offset_end);
            if (offset_start < 0 || offset_end > 0) {
                isZoomedInRef.current = false;
            }

            if (shouldShowLine && !isZoomedInRef.current) {
                // console.log("ZOOMED IN");
                isZoomedInRef.current = true;
                let off = 1000 * 60 * 60;
                zoomed_range.current = [
                    dataZoom.startValue - off,
                    dataZoom.endValue + off,
                ];
                const startTime = mapVirtualToRealTime(
                    dataZoom.startValue - off,
                    breaks,
                    virtualTime
                );
                const endTime = mapVirtualToRealTime(
                    dataZoom.endValue + off,
                    breaks,
                    virtualTime
                );

                const fetchedLineData = await fetchLineData(startTime, endTime);
                lineDataRef.current = fetchedLineData;

                // console.log("Fetched updated data");
                // Add line directly to chart without React re-render
                addLineToChart(chart, fetchedLineData);
            } else if (!shouldShowLine && isZoomedInRef.current) {
                // console.log("zoomed out");
                isZoomedInRef.current = false;
                lineDataRef.current = null;
                zoomed_range.current = [null, null];
                // Remove line directly from chart
                removeLineFromChart(chart);
            }
        }
    };

    const timeFormatOptions = {
        withSeconds: {
            month: "short",
            day: "numeric",
            hour: "numeric",
            minute: "2-digit",
            second: "numeric",
            hour12: true,
        },
        edges: {
            month: "short",
            day: "numeric",
            hour: "numeric",
            minute: "2-digit",
            hour12: true,
        },
        within: {
            hour: "numeric",
            minute: "2-digit",
            hour12: true,
        },
    };

    const mean = (data) => {
        if (data.length < 1) {
            return;
        }
        return data.reduce((prev, current) => prev + current) / data.length;
    };

    function prepareVideoData(videos) {
        let new_data = [];
        videos.forEach((item) => {
            let start_time = new Date(1000 * item["start_time"]);
            if ("embed_scores" in item) {
                var mean_val = item["embed_scores"]["time"] / 2;
                // var max_score = Math.max(...item['embed_scores']['score'])
                var max_score = item["embed_scores"]["score"][1];
                var max_score_time = new Date(
                    start_time.getTime() +
                        1000 * item["embed_scores"]["score"][2]
                );
                var new_time = new Date(
                    start_time.getTime() + 1000 * 2 * mean_val
                );
                new_data.push([
                    new Date(start_time.getTime()),
                    new_time,
                    max_score,
                    max_score_time,
                ]);
                // new_data.push([new_time, item['embed_scores']['score'][idx]]);

                // Math.max.apply(Math, item['embed_scores']['time'].map(function(o) { return o.y; }))
                // item['embed_scores']['time'].forEach((sec, idx) => {
                // let new_time = new Date(start_time.getTime() + 1000 * sec);

                // new_data.push([new_time, item['embed_scores']['score'][idx]]);
                // });
            }
        });

        // Remove duplicates and sort
        return Array.from(
            new Set(new_data.map(JSON.stringify)),
            JSON.parse
        ).sort((a, b) => new Date(a[0]) - new Date(b[0]));
    }

    function calculateDayBreaks(videos) {
        const breaks = [];
        if (videos.length < 3) {
            return breaks;
        }
    
        for (let i = 0; i < videos.length - 1; i++) {
            let end_now = new Date(videos[i]["end_time"]*1000)
            let start_next = new Date(videos[i + 1]["start_time"]*1000)

            
            Math.abs(start_next.getDay() - end_now.getDay())
            if (Math.abs(start_next.getDay() - end_now.getDay()) > 0) {
                // console.log("Day break:", end_now, start_next)
                let c_st = end_now.getTime();
                let e_st = start_next.getTime();
                breaks.push([c_st, e_st,  e_st/2 + c_st/2]);
            }
        }

        return breaks;
    }

    function calculateBreaks(videos) {
        const breaks = [];
        if (videos.length < 3) {
            return breaks;
        }
        let t_diff = videos.at(-1)["end_time"] - videos[0]["start_time"];

        for (let i = 0; i < videos.length - 1; i++) {
            let end_now = videos[i]["end_time"];
            let start_next = videos[i + 1]["start_time"];
            if (start_next - end_now > BREAK_THRESHOLD_SECONDS) {
                breaks.push([end_now*1000, start_next*1000,  (end_now/2 + start_next/2)*1000]);
            }
        }

        return breaks;
    }

    function fillNulls(data) {
        const with_nulls = [];
        for (let i = 0; i < data.length; i++) {
            with_nulls.push(data[i]);
            if (i < data.length - 1) {
                const curr_time = new Date(data[i][0]).getTime();
                const next_time = new Date(data[i + 1][0]).getTime();
                if (next_time - curr_time > 2000) {
                    with_nulls.push([new Date(curr_time + 1), null]);
                    // with_nulls.push([new Date(curr_time + 1), 0]);
                }
            }
        }
        return with_nulls;
    }

    function prepareBreaks(breaksRaw) {
        return breaksRaw.map(([start, end]) => ({
            start: new Date(start),
            end: new Date( end),
            gap: BREAK_GAP,
            isExpanded: false,
        }));
    }
    function buildVirtualTimeMapper(breaks) {
        const sortedBreaks = breaks.slice().sort((a, b) => a.start - b.start);
        return function (realDate) {
            let offset = 0;
            let realMs = realDate.getTime();
            for (const br of sortedBreaks) {
                if (realMs >= br.end.getTime()) {
                    offset += br.end.getTime() - br.start.getTime();
                } else if (realMs > br.start.getTime()) {
                    offset += realMs - br.start.getTime();
                    break;
                }
            }
            return realMs - offset;
        };
    }

    function mapVirtualToRealTime(virtualMs, breaks, virtualTime) {
        let realMs = virtualMs;
        for (const br of breaks) {
            const breakStartVirtual = virtualTime(br.start);
            const breakDuration = br.end.getTime() - br.start.getTime();
            if (virtualMs >= breakStartVirtual) {
                realMs += breakDuration;
            }
        }
        return realMs;
    }

    const ymin = Math.floor((data_in.threshold ?? 0.0) * 100) / 100;
    function buildSeries(item, idx) {
        const data = item.map(function (item, index) {
            return {
                value: item,
            };
        });

        return {
            type: "custom",
            renderItem: function (params, api) {
                var yValue = api.value(2);
                var start = api.coord([api.value(0), yValue]);
                var size = api.size([
                    api.value(1) - api.value(0),
                    yValue - ymin,
                ]);
                var style = api.style();
                var maxTime = api.coord([api.value(3), yValue]);
                return {
                    type: "group",
                    children: [
                        {
                            type: "rect",
                            shape: {
                                x: start[0],
                                y: start[1],
                                width: size[0],
                                height: size[1],
                            },
                            style: { fill: "#00F0003F" },
                        },
                        {
                            type: "circle",
                            shape: { cx: maxTime[0], cy: maxTime[1], r: 1 },
                            style: { fill: "#FFFF00" },
                        },
                    ],
                };
            },
            symbol: "none",
            smooth: true,
            large: true,
            lineStyle: { normal: { color: "green", width: 1 } },
            //   data: item.map(d => [d[0], d[1], d[2], d[3]]),
            data: data,
            // sampling: 'lttb',
            triggerLineEvent: true,
            z: 11,
        };
    }

    function buildBlankSeries() {
        return {
            type: "line",
            symbol: "none",
            lineStyle: { width: 100, opacity: 0 },
            data: [],
            smooth: true,
            sampling: "lttb",
            z: 4,
        };
    }

    // --- Data Processing ---
    const videoData = prepareVideoData(data_in["videos"]);
    const withNulls = videoData;
    data_in["calc_breaks"] = calculateBreaks(data_in["videos"]);
    data_in["day_breaks"] = calculateDayBreaks(data_in["videos"]);
    window.data_in = data_in
    // const withNulls = fillNulls(videoData);
    const breaks = prepareBreaks(data_in["day_breaks"]);

    window.breaks = breaks;
    const virtualTime = buildVirtualTimeMapper(breaks);

    const breaks_split = data_in["calc_breaks"].flat(1).map(function (x) {
        return x * 1000;
    });
    // if (videoData.length > 2) {
    //     breaks_split.unshift(new Date(videoData[0][0]).getTime())
    //     breaks_split.push(new Date(videoData.at(-1)[0]).getTime())
    // }
    const paired_splits = data_in['calc_breaks']
    // for (let i = 0; i < breaks_split.length; i += 2) {
    //     paired_splits.push([
    //         breaks_split[i],
    //         breaks_split[i + 1],
    //         breaks_split[i] / 2 + breaks_split[i + 1] / 2,
    //     ]);
    // }
    const split_centers = paired_splits.map((d) => new Date(d[2]));
    const splitCenterVirtualTimes = split_centers.map((d) => virtualTime(d));
    const splitCenterLabels = split_centers.map((d) =>
        new Date(d).toLocaleTimeString("en-US", timeFormatOptions.edges)
    );

    const splitCenterMarkLines = splitCenterVirtualTimes.map((vt, i) => ({
        xAxis: vt,
        // make the line invisible
        lineStyle: { width: 0, color: "transparent" },
        // show the precomputed text
        label: {
            show: true,
            formatter: splitCenterLabels[i],
            position: "end", // try other values if overlap; 'end', 'insideStartTop', etc.
            color: "#FFFFFF",
            fontSize: 11,
            rotate: 90,
        },
    }));

    const virtualData = withNulls.map(
        ([realStartTime, realEndTime, value, realMaxTime]) => [
            virtualTime(new Date(realStartTime)),
            virtualTime(new Date(realEndTime)),
            value,
            virtualTime(new Date(realMaxTime)),
        ]
    );
    const result = [virtualData];

    const ymax = Math.max(...virtualData.map((d) => d[2]));
    // --- Series ---
    const seriesNormal = result.map(buildSeries);
    // const seriesInvisible = result.map(buildInvisibleHitBoxSeries);

    const lineSeries = {
        id: "zoomLine",
        type: "line",
        data: [],
        lineStyle: { color: "#00FFFF", width: 1 },
        symbol: "none",
        z: 20,
        animation: false,
        triggerLineEvent: true,
        xAxisIndex: 0,
        connectNulls: false,
        yAxisIndex: 0,
    };

    const series_out = [].concat(seriesNormal, buildBlankSeries(), lineSeries);

    // --- Break MarkLines ---
    const breakMarkLines = breaks.map((br) => ({
        xAxis: virtualTime(br.start),
        lineStyle: { type: "dashed", color: "#888", width: 2 },
        label: {
            show: true,
            formatter: "Break",
            position: "bottom",
            color: "#888",
            fontSize: 10,
        },
    }));
    // console.log(breakMarkLines)
    // Attach break mark lines to the first series
    if (seriesNormal[0]) {
        seriesNormal[0].markLine = {
            symbol: ["none", "none"],
            data: [...(breakMarkLines)],
            lineStyle: { type: "dashed", color: "#888", width: 2 },
            label: {
                show: true,
                position: "bottom",
                color: "#888",
                fontSize: 10,
            },
        };
    }

    // --- Axis & Chart Option ---
    const virtual_x_min =
        (virtualData.length > 0 ? virtualData[0][0] : 0) - 100000;
    const virtual_x_max =
        (virtualData.length > 0 ? virtualData[virtualData.length - 1][0] : 1) +
        100000;

    const option = {
        animation: false,
        //   progressive: 0, // Disable progressive rendering
        progressiveThreshold: 100000, // Disable progressive threshold
        mappers: {
            virtual_to_real: mapVirtualToRealTime,
            real_to_virtual: virtualTime,
        },
        response: true,
        grid: {
            top: 30,
            left: 10,
            right: 20,
            bottom: hideDataZoom ? 10 : 10,
            containLabel: true,
        },
        dataZoom: [
            {
                type: "slider",
                // show: !hideDataZoom,
                show: false,
                height: hideDataZoom ? 0 : undefined,
                xAxisIndex: [0],
                startValue: virtual_x_min,
                endValue: virtual_x_max,
                filterMode: "weakFilter",
            },
            {
                type: "inside",
                xAxisIndex: [0],
                startValue: virtual_x_min,
                endValue: virtual_x_max,
                filterMode: "weakFilter",
            },
        ],
        xAxis: {
            type: "value",
            min: virtual_x_min,
            max: virtual_x_max,
            splitLine: { show: false },
            axisLabel: {
                formatter: function (virtualMs) {
                    let range = virtual_x_max - virtual_x_min;
                    if (
                        chartRef &&
                        chartRef.current &&
                        chartRef.current.getEchartsInstance
                    ) {
                        const chart = chartRef.current.getEchartsInstance();
                        const dz = chart.getOption().dataZoom?.[0];
                        if (
                            dz &&
                            dz.startValue !== undefined &&
                            dz.endValue !== undefined
                        ) {
                            range = dz.endValue - dz.startValue;
                        }
                    }
                    const realTime = mapVirtualToRealTime(
                        virtualMs,
                        breaks,
                        virtualTime
                    );
                    if (realTime) {
                        const useSeconds = range < 5 * 60 * 1000;
                        const fmt = useSeconds
                            ? timeFormatOptions.withSeconds
                            : timeFormatOptions.edges;
                        return new Date(realTime).toLocaleTimeString(
                            "en-US",
                            fmt
                        );
                    }
                    return "";
                },
            },
        },
        yAxis: {
            type: "value",
            min: Math.floor(ymin * 100) / 100,
            max: Math.ceil(ymax * 100) / 100,
            splitLine: { show: false },
        },
        series: series_out.map((s) => ({
            ...s,
            animation: false, // Disable animation for each series
            animationDuration: 0,
            xAxisIndex: 0, // Explicitly set axis index for all series
            yAxisIndex: 0,
        })),
    };

    // --- Chart Event Handlers ---
    async function onChartClick(params, echarts) {
        let pixel;
        const nativeEvent = params.event.event;

        // Support both mouse and touch events
        if (nativeEvent.touches && nativeEvent.touches.length > 0) {
            // Touch event
            const rect = nativeEvent.target.getBoundingClientRect();
            const touch = nativeEvent.touches[0];
            pixel = [touch.clientX - rect.left, touch.clientY - rect.top];
        } else {
            // Mouse event
            pixel = [nativeEvent.offsetX, nativeEvent.offsetY];
        }

        const dataCoord = echarts.convertFromPixel({ seriesIndex: 0 }, pixel);

        const res = await authenticatedFetch("/api/events/click", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({
                timestamp:
                    mapVirtualToRealTime(dataCoord[0], breaks, virtualTime) /
                    1000,
            }),
        });
        if (!res.ok) throw new Error(`HTTP error: ${res.status}`);
        const { path, timeoffset } = await res.json();
        if (onTimelineClick)
            onTimelineClick(path, virtualTime(new Date(timeoffset)));
    }

    function onChartReady(echarts) {
        // Chart is ready
    }

    const onEvents = { click: onChartClick, dataZoom: handleDataZoom };
    window.chartRef2 = chartRef;
    // --- Render ---
    return (
        <ReactECharts
            ref={chartRef}
            onChartReady={onChartReady}
            onEvents={onEvents}
            option={option}
            style={{ width: "100%", height: "100%" }}
        />
    );
});
export default EmbedTimeline;
