import dynamic from 'next/dynamic';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';

import { IRootState } from '@/store';
import { getThemeChartColors, getThemeChartDataLabelColor, getThemeChartFont } from '@/lib/theme-colors';
import { journeyChartGrid } from './journey-chart-options';
import styles from './journey-stats.module.css';

const ReactApexChart = dynamic(() => import('react-apexcharts'), { ssr: false });

export type FunnelStepInput = {
    key: string;
    label: string;
    count: number;
};

type BuiltStep = FunnelStepInput & {
    fromPreviousPercent: number | null;
    abandonCount: number | null;
    abandonPercent: number | null;
};

type DropPolygon = {
    key: string;
    points: string;
    title: string;
};

const ZOOM_MIN = 1;
const ZOOM_MAX = 5;
const ZOOM_STEP = 0.5;

function formatCount(n: number) {
    if (n >= 1000) {
        const k = n / 1000;
        return Number.isInteger(k) || k >= 10 ? `${Math.round(k)}K` : `${Math.round(k * 10) / 10}K`;
    }
    return String(n);
}

function stepsSignature(steps: FunnelStepInput[]) {
    return steps.map((s) => `${s.key}:${s.label}:${s.count}`).join('|');
}

function samePolygons(a: DropPolygon[], b: DropPolygon[]) {
    if (a.length !== b.length) return false;
    return a.every(
        (p, i) => p.key === b[i].key && p.points === b[i].points && p.title === b[i].title
    );
}

function sameCenters(a: number[], b: number[]) {
    if (a.length !== b.length) return false;
    return a.every((v, i) => Math.abs(v - b[i]) < 0.5);
}

function buildSteps(steps: FunnelStepInput[]): BuiltStep[] {
    return steps.map((step, idx) => {
        const prev = idx === 0 ? null : steps[idx - 1].count;
        const next = idx < steps.length - 1 ? steps[idx + 1].count : null;
        const fromPreviousPercent =
            idx === 0 ? 100 : prev && prev > 0 ? Math.round((step.count / prev) * 1000) / 10 : 0;
        const abandonCount = next == null ? null : Math.max(step.count - next, 0);
        const abandonPercent =
            abandonCount == null || !step.count
                ? null
                : Math.round((abandonCount / step.count) * 1000) / 10;
        return {
            ...step,
            fromPreviousPercent,
            abandonCount,
            abandonPercent,
        };
    });
}

function gapCentersFromBars(
    barRights: number[],
    barLefts: number[],
    plotLeft: number,
    plotWidth: number,
    stepCount: number
) {
    const centers: number[] = [];
    for (let i = 0; i < stepCount - 1; i += 1) {
        const x1 = barRights[i];
        const x2 = barLefts[i + 1];
        centers.push(
            x1 != null && x2 != null
                ? (x1 + x2) / 2
                : plotLeft + (plotWidth * (i + 1)) / stepCount
        );
    }
    return centers;
}

function gapCentersFromPlotWidth(plotLeft: number, plotWidth: number, stepCount: number) {
    return Array.from({ length: Math.max(stepCount - 1, 0) }, (_, i) =>
        plotLeft + (plotWidth * (i + 1)) / stepCount
    );
}

interface JourneyFunnelChartProps {
    steps: FunnelStepInput[];
}

const JourneyFunnelChart = ({ steps }: JourneyFunnelChartProps) => {
    const stepsSig = stepsSignature(steps);
    const built = useMemo(() => buildSteps(steps), [stepsSig]);
    const apexWrapRef = useRef<HTMLDivElement | null>(null);
    const chartWrapRef = useRef<HTMLDivElement | null>(null);
    const [dropPolygons, setDropPolygons] = useState<DropPolygon[]>([]);
    const [abandonCenters, setAbandonCenters] = useState<number[]>([]);
    const [zoomLevel, setZoomLevel] = useState(1);
    const isDark = useSelector(
        (state: IRootState) => state.themeConfig.theme === 'dark' || state.themeConfig.isDarkMode
    );
    const colorScheme = useSelector((state: IRootState) => state.themeConfig.colorScheme);
    const chartColors = useMemo(() => getThemeChartColors(colorScheme), [colorScheme]);

    const categories = useMemo(() => built.map((s) => s.label), [built]);
    const seriesData = useMemo(() => built.map((s) => s.count), [built]);
    const maxVal = Math.max(...seriesData, 1);
    const yAxisMax = zoomLevel <= 1 ? undefined : Math.max(Math.ceil(maxVal / zoomLevel), 1);

    const measureDropAreas = useCallback(() => {
        const wrap = chartWrapRef.current;
        if (!wrap) return;

        const wrapRect = wrap.getBoundingClientRect();
        const plotArea = wrap.querySelector('.apexcharts-plot-area');
        const plotLeftDefault = 56;
        const plotRightDefault = 40;
        const fallbackPlotLeft = plotLeftDefault;
        const fallbackPlotWidth = Math.max(wrapRect.width - plotLeftDefault - plotRightDefault, 1);

        if (!plotArea) {
            const nextCenters = gapCentersFromPlotWidth(fallbackPlotLeft, fallbackPlotWidth, built.length);
            setAbandonCenters((prev) => (sameCenters(prev, nextCenters) ? prev : nextCenters));
            return;
        }

        const plotRect = plotArea.getBoundingClientRect();
        const trackTop = plotRect.top - wrapRect.top;
        const trackBottom = plotRect.bottom - wrapRect.top;

        const apexWrap = apexWrapRef.current;
        if (apexWrap) {
            apexWrap.style.setProperty('--funnel-plot-left', `${plotRect.left - wrapRect.left}px`);
            apexWrap.style.setProperty('--funnel-plot-right', `${wrapRect.right - plotRect.right}px`);
        }

        const plotLeft = plotRect.left - wrapRect.left;
        const plotWidth = plotRect.width;

        const barEls = wrap.querySelectorAll<SVGGraphicsElement>(
            '.apexcharts-bar-series .apexcharts-series path, .apexcharts-bar-series .apexcharts-series rect'
        );
        const barRects = Array.from(barEls)
            .map((el) => {
                const r = el.getBoundingClientRect();
                return {
                    left: r.left - wrapRect.left,
                    right: r.right - wrapRect.left,
                    top: r.top - wrapRect.top,
                    width: r.width,
                };
            })
            .filter((r) => r.width > 2)
            .sort((a, b) => a.left - b.left);

        const uniqueBars: typeof barRects = [];
        for (const bar of barRects) {
            const last = uniqueBars[uniqueBars.length - 1];
            if (!last || Math.abs(bar.left - last.left) > 4) {
                uniqueBars.push(bar);
            }
        }

        const bars =
            uniqueBars.length >= built.length
                ? uniqueBars.slice(0, built.length)
                : Array.from({ length: built.length }, (_, i) => {
                      const slotWidth = plotWidth / built.length;
                      const x1 = plotLeft + slotWidth * i + slotWidth * 0.29;
                      const x2 = plotLeft + slotWidth * (i + 1) - slotWidth * 0.29;
                      return {
                          left: x1,
                          right: x2,
                          top: trackTop,
                          width: x2 - x1,
                      };
                  });

        const barTops = bars.map((b) => b.top);
        const barRights = bars.map((b) => b.right);
        const barLefts = bars.map((b) => b.left);

        const nextPolygons: DropPolygon[] = [];
        for (let i = 0; i < built.length - 1; i += 1) {
            const y1 = Math.max(barTops[i] ?? trackTop, trackTop);
            const y2 = Math.max(barTops[i + 1] ?? trackTop, trackTop);
            const x1 = barRights[i] ?? plotLeft;
            const x2 = barLefts[i + 1] ?? plotLeft + plotWidth;

            const points = [
                `${x1},${y1}`,
                `${x2},${y2}`,
                `${x2},${trackBottom}`,
                `${x1},${trackBottom}`,
            ].join(' ');

            const step = built[i];
            nextPolygons.push({
                key: `${step.key}-${built[i + 1].key}`,
                points,
                title:
                    step.abandonCount != null
                        ? `Drop ${formatCount(step.abandonCount)} (${step.abandonPercent ?? 0}%)`
                        : 'Drop-off',
            });
        }
        setDropPolygons((prev) => (samePolygons(prev, nextPolygons) ? prev : nextPolygons));
        const nextCenters = gapCentersFromBars(barRights, barLefts, plotLeft, plotWidth, built.length);
        setAbandonCenters((prev) => (sameCenters(prev, nextCenters) ? prev : nextCenters));
    }, [built]);

    const scheduleMeasure = useCallback(() => {
        window.setTimeout(measureDropAreas, 80);
        window.setTimeout(measureDropAreas, 280);
    }, [measureDropAreas]);

    const chartOptions = useMemo(
        () => ({
            chart: {
                type: 'bar' as const,
                fontFamily: getThemeChartFont(colorScheme),
                toolbar: { show: false },
                zoom: { enabled: false },
                events: {
                    mounted: scheduleMeasure,
                    updated: scheduleMeasure,
                },
            },
            plotOptions: {
                bar: {
                    horizontal: false,
                    columnWidth: '42%',
                    borderRadius: 2,
                    borderRadiusApplication: 'end' as const,
                },
            },
            colors: [chartColors[0]],
            dataLabels: {
                enabled: true,
                formatter: (val: number) => formatCount(val),
                offsetY: -6,
                style: {
                    fontSize: '11px',
                    fontWeight: 700,
                    colors: [getThemeChartDataLabelColor(colorScheme)],
                },
                background: { enabled: false },
            },
            grid: journeyChartGrid(isDark),
            legend: { show: false },
            xaxis: {
                categories,
                labels: { show: false },
                axisBorder: { show: !isDark },
                axisTicks: { show: !isDark },
            },
            yaxis: {
                max: yAxisMax,
                labels: {
                    formatter: (val: number) => formatCount(val),
                },
                min: 0,
                forceNiceScale: true,
            },
            tooltip: {
                y: {
                    formatter: (val: number) => `${Number(val).toLocaleString('id-ID')} sessions`,
                },
            },
        }),
        [categories, yAxisMax, scheduleMeasure, isDark, chartColors, colorScheme]
    );

    const zoomIn = () => setZoomLevel((z) => Math.min(ZOOM_MAX, Math.round((z + ZOOM_STEP) * 10) / 10));
    const zoomOut = () => setZoomLevel((z) => Math.max(ZOOM_MIN, Math.round((z - ZOOM_STEP) * 10) / 10));

    useEffect(() => {
        scheduleMeasure();
        const onResize = () => scheduleMeasure();
        window.addEventListener('resize', onResize);
        const wrap = chartWrapRef.current;
        const ro =
            wrap && typeof ResizeObserver !== 'undefined'
                ? new ResizeObserver(() => scheduleMeasure())
                : null;
        if (wrap && ro) ro.observe(wrap);
        return () => {
            window.removeEventListener('resize', onResize);
            ro?.disconnect();
        };
    }, [zoomLevel, seriesData, scheduleMeasure, built.length]);

    return (
        <div
            ref={apexWrapRef}
            className={styles.funnelApexWrap}
            style={{ '--funnel-steps': built.length } as React.CSSProperties}
        >
            <div className={styles.funnelStepHeadRow}>
                {built.map((step) => (
                    <div key={step.key} className={styles.funnelStepHead}>
                        <div className={styles.funnelStepLabel}>{step.label}</div>
                        <div className={styles.funnelStepRate}>
                            {step.fromPreviousPercent == null ? '—' : `${step.fromPreviousPercent}%`}
                        </div>
                    </div>
                ))}
            </div>

            <div className={styles.funnelChartArea} ref={chartWrapRef}>
                <svg className={styles.funnelDropSvg} aria-hidden>
                    {dropPolygons.map((poly) => (
                        <polygon
                            key={poly.key}
                            points={poly.points}
                            className={styles.funnelDropPolygon}
                        >
                            <title>{poly.title}</title>
                        </polygon>
                    ))}
                </svg>

                <div className={styles.funnelZoomControls}>
                    <button
                        type="button"
                        className={styles.funnelZoomBtn}
                        onClick={zoomIn}
                        disabled={zoomLevel >= ZOOM_MAX}
                        aria-label="Zoom in"
                        title="Zoom in"
                    >
                        +
                    </button>
                    <button
                        type="button"
                        className={styles.funnelZoomBtn}
                        onClick={zoomOut}
                        disabled={zoomLevel <= ZOOM_MIN}
                        aria-label="Zoom out"
                        title="Zoom out"
                    >
                        −
                    </button>
                    {zoomLevel > 1 ? (
                        <span className={styles.funnelZoomLevel}>{Math.round(zoomLevel * 100)}%</span>
                    ) : null}
                </div>

                <ReactApexChart
                    type="bar"
                    height={300}
                    series={[{ name: 'Sessions', data: seriesData }]}
                    options={chartOptions}
                />
            </div>

            <div className={styles.funnelAbandonRow}>
                {built.slice(0, -1).map((step, idx) => (
                    <div
                        key={`${step.key}-abandon`}
                        className={styles.funnelAbandonSlot}
                        style={{ left: `${abandonCenters[idx] ?? 0}px` }}
                    >
                        <div className={styles.funnelAbandonCount}>
                            {formatCount(step.abandonCount ?? 0)}
                        </div>
                        <div className={styles.funnelAbandonRate}>
                            ({step.abandonPercent ?? 0}%)
                        </div>
                        <div className={styles.funnelAbandonLabel}>Abandonment</div>
                    </div>
                ))}
            </div>
        </div>
    );
};

export default JourneyFunnelChart;
