import { isMarketCandle } from "@/lib/market-api";
import { useI18n } from "@/lib/i18n";
import { useEffect, useRef, useState } from "react";
import {
    CandlestickSeries,
    ColorType,
    CrosshairMode,
    HistogramSeries,
    LineSeries,
    LineStyle,
    createChart,
    type IChartApi,
    type IPriceLine,
    type ISeriesApi,
    type LineWidth,
    type MouseEventParams,
    type Time,
    type UTCTimestamp,
} from "lightweight-charts";
import {
    accumulationZones,
    bollinger,
    ema,
    fairValueGaps,
    formatPrice,
    formatVolume,
    orderBlocks,
    precisionFor,
    rsi,
    sma,
    structureEvents,
    supportResistance,
    swingPoints,
    type Candle,
    type IndicatorState,
    type MarketType,
    type Point,
} from "@/lib/market";
import { EMPTY_SMC, SmcPrimitive, type SmcData } from "../../lib/SmcPrimitive";
import { SupportResistancePrimitive } from "./SupportResistancePrimitive";
import {
    isVolumeProfile,
    type VolumeProfileResult,
} from "@/lib/volume-profile";

export type CustomChartHandle = {
    series: ISeriesApi<"Candlestick">;
    resetView: () => void;
};

interface CustomChartProps {
    symbol: string;
    market: MarketType;
    interval: string;
    wsUrl: string;
    wsDriver?: string;
    provider?: string;
    indicators: IndicatorState;
    focusTime?: number | null;
    onLastPrice?: (price: number, time?: number) => void;
    onChartReady?: (handle: CustomChartHandle | null) => void;
    onCandleTimesChange?: (times: number[]) => void;
    onVolumeProfile?: (profile: VolumeProfileResult | null) => void;
    volumeProfileEnabled?: boolean;
}

type LineKey = "sma20" | "ema50" | "bbU" | "bbM" | "bbL" | "rsi";

type Extra = Partial<Record<LineKey, ISeriesApi<"Line">>> & {
    volume?: ISeriesApi<"Histogram">;
};

const UP = "#26a69a";
const DOWN = "#ef5350";

function normalizeCandles(candles: Candle[]): Candle[] {
    const byTime = new Map<number, Candle>();
    for (const candle of candles) byTime.set(candle.time, candle);
    return [...byTime.values()].sort((a, b) => a.time - b.time);
}

function nearestCandleIndex(candles: Candle[], time: number): number {
    if (!candles.length) return -1;
    let lo = 0;
    let hi = candles.length - 1;

    while (lo <= hi) {
        const mid = (lo + hi) >> 1;
        if (candles[mid].time === time) return mid;
        if (candles[mid].time < time) lo = mid + 1;
        else hi = mid - 1;
    }

    if (lo <= 0) return 0;
    if (lo >= candles.length) return candles.length - 1;

    return Math.abs(candles[lo].time - time) <
        Math.abs(candles[lo - 1].time - time)
        ? lo
        : lo - 1;
}

function findCandle(candles: Candle[], time: number): Candle | undefined {
    let lo = 0;
    let hi = candles.length - 1;

    while (lo <= hi) {
        const mid = (lo + hi) >> 1;
        if (candles[mid].time === time) return candles[mid];
        if (candles[mid].time < time) lo = mid + 1;
        else hi = mid - 1;
    }

    return undefined;
}

export default function CustomChart({
    symbol,
    market,
    interval,
    wsUrl,
    wsDriver = "binance",
    provider = "binance",
    indicators,
    focusTime = null,
    onLastPrice,
    onChartReady,
    onCandleTimesChange,
    onVolumeProfile,
    volumeProfileEnabled = false,
}: CustomChartProps) {
    const { t } = useI18n();
    const containerRef = useRef<HTMLDivElement>(null);
    const chartRef = useRef<IChartApi | null>(null);
    const candleSeriesRef = useRef<ISeriesApi<"Candlestick"> | null>(null);
    const extraRef = useRef<Extra>({});
    const dataRef = useRef<Candle[]>([]);
    const hoveringRef = useRef(false);
    const loadOlderRef = useRef<(() => void) | null>(null);
    const srRef = useRef<SupportResistancePrimitive | null>(null);
    const srKeyRef = useRef<string | null>(null);
    const smcRef = useRef<SmcPrimitive | null>(null);
    const smcDataRef = useRef<SmcData>(EMPTY_SMC);
    const smcKeyRef = useRef<string | null>(null);
    const chartLifecycleRef = useRef(0);
    const volumeProfileRef = useRef<VolumeProfileResult | null>(null);
    const volumeProfileLinesRef = useRef<
        Partial<Record<"poc" | "vah" | "val", IPriceLine>>
    >({});

    const indicatorsRef = useRef(indicators);
    indicatorsRef.current = indicators;
    const onLastPriceRef = useRef(onLastPrice);
    onLastPriceRef.current = onLastPrice;
    const onChartReadyRef = useRef(onChartReady);
    onChartReadyRef.current = onChartReady;
    const onCandleTimesChangeRef = useRef(onCandleTimesChange);
    onCandleTimesChangeRef.current = onCandleTimesChange;
    const publishedCandleRangeRef = useRef<string>("");
    const onVolumeProfileRef = useRef(onVolumeProfile);
    onVolumeProfileRef.current = onVolumeProfile;

    const [status, setStatus] = useState<"loading" | "ready" | "error">(
        "loading",
    );
    const [dataStale, setDataStale] = useState(false);
    const [dataAge, setDataAge] = useState(0);
    const [marketDataStatus, setMarketDataStatus] = useState<
        "live" | "delayed" | "historical" | "unknown"
    >("unknown");
    const [marketDataSource, setMarketDataSource] = useState("unknown");
    const [streamState, setStreamState] = useState<
        "offline" | "live" | "reconnecting"
    >("offline");
    const [legend, setLegend] = useState<Candle | null>(null);

    const sync = (full: boolean) => {
        const chart = chartRef.current;
        const candleSeries = candleSeriesRef.current;
        const data = dataRef.current;

        if (!chart || !candleSeries || data.length === 0) return;

        const ind = indicatorsRef.current;
        const x = extraRef.current;
        const shown = data;
        const last = shown[shown.length - 1];

        // Lightweight Charts v5 expects a UTCTimestamp for
        // CandlestickData<Time>. Our Candle model stores Unix seconds
        // as a plain number, so explicitly narrow it here.
        const candle = {
            time: last.time as UTCTimestamp,
            open: last.open,
            high: last.high,
            low: last.low,
            close: last.close,
        };

        if (full) {
            candleSeries.setData(
                shown.map(({ time, open, high, low, close }) => ({
                    time: time as UTCTimestamp,
                    open,
                    high,
                    low,
                    close,
                })),
            );
        } else {
            candleSeries.update(candle);
        }

        // Publish candle-time changes to dependent overlays. Avoid React updates
        // on every websocket tick within the same candle; notify only when the
        // loaded range changes (initial load, new bar, or historical pagination).
        const rangeSignature = `${shown.length}:${shown[0]?.time ?? ""}:${last.time}`;
        if (rangeSignature !== publishedCandleRangeRef.current) {
            publishedCandleRangeRef.current = rangeSignature;
            onCandleTimesChangeRef.current?.(shown.map((item) => item.time));
        }

        candleSeries.priceScale().applyOptions({
            scaleMargins: { top: 0.08, bottom: ind.volume ? 0.25 : 0.08 },
        });

        if (!ind.volume && x.volume) {
            chart.removeSeries(x.volume);
            delete x.volume;
        }

        if (ind.volume) {
            let created = false;

            if (!x.volume) {
                x.volume = chart.addSeries(HistogramSeries, {
                    priceFormat: { type: "volume" },
                    priceScaleId: "",
                    lastValueVisible: false,
                    priceLineVisible: false,
                });
                x.volume.priceScale().applyOptions({
                    scaleMargins: { top: 0.8, bottom: 0 },
                });
                created = true;
            }

            const bar = (c: (typeof shown)[number]) => ({
                time: c.time as UTCTimestamp,
                value: c.volume,
                color: c.close >= c.open ? UP + "66" : DOWN + "66",
            });

            if (full || created) x.volume.setData(shown.map(bar));
            else x.volume.update(bar(last));
        }

        const setLine = (
            key: LineKey,
            on: boolean,
            style: {
                color: string;
                width?: LineWidth;
                dashed?: boolean;
                pane?: number;
            },
            points: () => Point[],
        ) => {
            let series = x[key];

            if (!on) {
                if (series) {
                    chart.removeSeries(series);
                    delete x[key];
                }
                return;
            }

            let created = false;

            if (!series) {
                series = chart.addSeries(
                    LineSeries,
                    {
                        color: style.color,
                        lineWidth: style.width ?? 1,
                        lineStyle: style.dashed
                            ? LineStyle.Dashed
                            : LineStyle.Solid,
                        priceLineVisible: false,
                        lastValueVisible: style.pane !== undefined,
                        crosshairMarkerVisible: false,
                    },
                    style.pane ?? 0,
                );
                x[key] = series;
                created = true;
            }

            const pts = points().map((p) => ({
                time: p.time as UTCTimestamp,
                value: p.value,
            }));

            if (full || created) series.setData(pts);
            else if (pts.length) series.update(pts[pts.length - 1]);
        };

        const bands = ind.bb ? bollinger(shown, 20, 2) : null;

        setLine("sma20", ind.sma20, { color: "#f5b942", width: 2 }, () =>
            sma(shown, 20),
        );
        setLine("ema50", ind.ema50, { color: "#4c9aff", width: 2 }, () =>
            ema(shown, 50),
        );
        setLine("bbU", ind.bb, { color: "#a78bfa" }, () => bands!.upper);
        setLine(
            "bbM",
            ind.bb,
            { color: "#a78bfa", dashed: true },
            () => bands!.middle,
        );
        setLine("bbL", ind.bb, { color: "#a78bfa" }, () => bands!.lower);

        const hadRsi = !!x.rsi;
        setLine("rsi", ind.rsi, { color: "#e879f9", width: 2, pane: 1 }, () =>
            rsi(shown, 14),
        );

        if (ind.rsi && !hadRsi && x.rsi) {
            x.rsi.createPriceLine({
                price: 70,
                color: "#6b7280",
                lineWidth: 1,
                lineStyle: LineStyle.Dashed,
                axisLabelVisible: false,
                title: "",
            });
            x.rsi.createPriceLine({
                price: 30,
                color: "#6b7280",
                lineWidth: 1,
                lineStyle: LineStyle.Dashed,
                axisLabelVisible: false,
                title: "",
            });
            try {
                chart.panes()[1]?.setHeight(140);
            } catch {
                // Ignore
            }
        }

        if (!ind.sr) {
            if (srRef.current) {
                candleSeries.detachPrimitive(srRef.current);
                srRef.current = null;
                srKeyRef.current = null;
            }
        } else {
            if (!srRef.current) {
                srRef.current = new SupportResistancePrimitive(() => {
                    const d = dataRef.current;
                    return d.length ? d[d.length - 1].close : null;
                });
                candleSeries.attachPrimitive(srRef.current);
                srKeyRef.current = null;
            }

            const key = `${data[0].time}:${data.length}`;
            if (full || key !== srKeyRef.current) {
                const closed = data.length > 1 ? data.slice(0, -1) : data;
                srRef.current.setZones(supportResistance(closed));
                srKeyRef.current = key;
            }
        }

        const clearVolumeProfileLines = () => {
            Object.values(volumeProfileLinesRef.current).forEach((line) => {
                if (line) candleSeries.removePriceLine(line);
            });
            volumeProfileLinesRef.current = {};
        };

        if (!ind.volumeProfile || !volumeProfileRef.current) {
            clearVolumeProfileLines();
        } else {
            const profile = volumeProfileRef.current;
            const definitions = [
                {
                    key: "vah" as const,
                    price: profile.vah.high,
                    title: "VAH",
                    color: "#ef4444",
                    style: LineStyle.Dashed,
                },
                {
                    key: "poc" as const,
                    price: profile.poc.price,
                    title: "POC",
                    color: "#60a5fa",
                    style: LineStyle.Solid,
                },
                {
                    key: "val" as const,
                    price: profile.val.low,
                    title: "VAL",
                    color: "#22c55e",
                    style: LineStyle.Dashed,
                },
            ];

            for (const definition of definitions) {
                const existing = volumeProfileLinesRef.current[definition.key];
                if (existing) {
                    existing.applyOptions({
                        price: definition.price,
                        color: definition.color,
                        title: definition.title,
                        lineStyle: definition.style,
                    });
                } else {
                    volumeProfileLinesRef.current[definition.key] =
                        candleSeries.createPriceLine({
                            price: definition.price,
                            color: definition.color,
                            lineWidth: 1,
                            lineStyle: definition.style,
                            axisLabelVisible: true,
                            title: definition.title,
                        });
                }
            }
        }

        const smcVisible = {
            accumulation: ind.accumulation,
            ob: ind.ob,
            fvg: ind.fvg,
            structure: ind.structure,
            swing: ind.swing,
        };
        const smcOn = Object.values(smcVisible).some(Boolean);

        if (!smcOn) {
            if (smcRef.current) {
                candleSeries.detachPrimitive(smcRef.current);
                smcRef.current = null;
                smcKeyRef.current = null;
            }
        } else {
            if (!smcRef.current) {
                smcRef.current = new SmcPrimitive();
                candleSeries.attachPrimitive(smcRef.current);
                smcKeyRef.current = null;
            }

            const key = `${data[0].time}:${data.length}`;
            if (full || key !== smcKeyRef.current) {
                const closed = data.length > 1 ? data.slice(0, -1) : data;
                const swings = swingPoints(closed);
                const structure = structureEvents(closed, swings);

                smcDataRef.current = {
                    swings,
                    structure,
                    orderBlocks: orderBlocks(closed, structure),
                    fvgs: fairValueGaps(closed),
                    accumulation: accumulationZones(closed),
                    now: closed.length ? closed[closed.length - 1].time : 0,
                };
                smcKeyRef.current = key;
            }

            smcRef.current.update(smcDataRef.current, smcVisible);
        }
    };

    useEffect(() => {
        const container = containerRef.current;
        if (!container) return;

        const chart = createChart(container, {
            autoSize: true,
            layout: {
                background: { type: ColorType.Solid, color: "#131722" },
                textColor: "#d1d4dc",
                panes: { separatorColor: "#2b3139" },
            },
            grid: {
                vertLines: { color: "#1f2937" },
                horzLines: { color: "#1f2937" },
            },
            crosshair: { mode: CrosshairMode.Normal },
            rightPriceScale: { borderColor: "#374151" },
            timeScale: {
                borderColor: "#374151",
                timeVisible: true,
                secondsVisible: false,
                rightOffset: 6,
            },
        });

        const candleSeries = chart.addSeries(CandlestickSeries, {
            upColor: UP,
            downColor: DOWN,
            borderUpColor: UP,
            borderDownColor: DOWN,
            wickUpColor: UP,
            wickDownColor: DOWN,
        });

        chartRef.current = chart;
        candleSeriesRef.current = candleSeries;
        onChartReadyRef.current?.({
            series: candleSeries,
            resetView: () => {
                const activeChart = chartRef.current;
                if (!activeChart) return;
                activeChart.timeScale().fitContent();
                candleSeries.priceScale().applyOptions({ autoScale: true });
            },
        });

        chart.subscribeCrosshairMove((param: MouseEventParams<Time>) => {
            if (param.time === undefined) {
                hoveringRef.current = false;
                const data = dataRef.current;
                if (data.length) setLegend(data[data.length - 1]);
                return;
            }

            hoveringRef.current = true;
            const hovered = findCandle(dataRef.current, param.time as number);
            if (hovered) setLegend(hovered);
        });

        chart.timeScale().subscribeVisibleLogicalRangeChange((range) => {
            if (range && range.from < 15) loadOlderRef.current?.();
        });

        return () => {
            onChartReadyRef.current?.(null);
            onCandleTimesChangeRef.current?.([]);
            chart.remove();
            chartRef.current = null;
            candleSeriesRef.current = null;
            extraRef.current = {};
            srRef.current = null;
            srKeyRef.current = null;
            smcRef.current = null;
            smcKeyRef.current = null;
        };
    }, []);

    useEffect(() => {
        const chart = chartRef.current;
        const candleSeries = candleSeriesRef.current;
        if (!chart || !candleSeries) return;

        // Setiap kombinasi symbol/market/interval mendapatkan lifecycle ID baru.
        // Ini mencegah request candle atau reconnect WebSocket dari konfigurasi
        // sebelumnya menulis data ke chart setelah user berpindah pair/timeframe.
        const lifecycle = ++chartLifecycleRef.current;
        let cancelled = false;
        let ws: WebSocket | null = null;
        let retryTimer: ReturnType<typeof setTimeout> | undefined;
        let attempts = 0;
        let loadingOlder = false;
        let exhausted = false;
        const requestController = new AbortController();
        const isCurrent = () =>
            !cancelled && chartLifecycleRef.current === lifecycle;

        dataRef.current = [];
        publishedCandleRangeRef.current = "";
        onCandleTimesChangeRef.current?.([]);
        setDataStale(false);
        setDataAge(0);
        setMarketDataStatus("unknown");
        setMarketDataSource("unknown");
        setStreamState("offline");
        candleSeries.setData([]);
        for (const series of Object.values(extraRef.current)) {
            if (series) chart.removeSeries(series);
        }
        extraRef.current = {};
        srRef.current?.setZones([]);
        srKeyRef.current = null;
        smcDataRef.current = EMPTY_SMC;
        smcKeyRef.current = null;
        volumeProfileRef.current = null;
        Object.values(volumeProfileLinesRef.current).forEach((line) => {
            if (line) candleSeries.removePriceLine(line);
        });
        volumeProfileLinesRef.current = {};
        onVolumeProfileRef.current?.(null);
        hoveringRef.current = false;
        setLegend(null);
        setStatus("loading");

        const fetchCandles = async (
            endTimeMs?: number,
            limit = 300,
        ): Promise<Candle[]> => {
            const qs = new URLSearchParams({
                symbol,
                market,
                interval,
                provider,
                limit: String(limit),
            });
            if (endTimeMs !== undefined) qs.set("end_time", String(endTimeMs));

            const res = await fetch(`/market/candles?${qs}`, {
                headers: {
                    Accept: "application/json",
                    "X-Requested-With": "XMLHttpRequest",
                },
                credentials: "same-origin",
                cache: "default",
                signal: requestController.signal,
            });

            if (!res.ok) {
                let detail = `HTTP ${res.status}`;
                try {
                    const payload = (await res.json()) as { message?: unknown; code?: unknown };
                    if (typeof payload?.message === "string" && payload.message.trim() !== "") {
                        detail = payload.message;
                    }
                    if (typeof payload?.code === "string" && payload.code.trim() !== "") {
                        detail = `${detail} [${payload.code}]`;
                    }
                } catch {
                    // Keep the HTTP status when the backend response is not JSON.
                }
                throw new Error(detail);
            }
            // Only the latest-window request represents current market freshness.
            // Historical pagination must not overwrite the live/delayed badge.
            if (endTimeMs === undefined) {
                const statusHeader = res.headers.get("X-Market-Data-Status");
                const sourceHeader = res.headers.get("X-Market-Data-Source");
                const stale =
                    res.headers.get("X-Market-Data-Fresh") === "false" ||
                    statusHeader === "delayed";
                const ageHeader = res.headers.get("X-Market-Data-Age");
                const age =
                    ageHeader && ageHeader !== "unknown"
                        ? Number(ageHeader)
                        : 0;

                setMarketDataStatus(
                    statusHeader === "live" ||
                        statusHeader === "delayed" ||
                        statusHeader === "historical"
                        ? statusHeader
                        : "unknown",
                );
                setMarketDataSource(sourceHeader || provider || "unknown");
                setDataStale(stale);
                setDataAge(Number.isFinite(age) ? Math.max(0, age) : 0);
            }
            const payload: unknown = await res.json();
            if (!Array.isArray(payload))
                throw new Error("Format candle tidak valid");
            return normalizeCandles(payload.filter(isMarketCandle));
        };

        loadOlderRef.current = async () => {
            if (loadingOlder || exhausted || dataRef.current.length === 0)
                return;
            loadingOlder = true;

            try {
                const oldest = dataRef.current[0].time;
                const older = await fetchCandles(oldest * 1000 - 1);
                if (!isCurrent()) return;

                const known = new Set(dataRef.current.map((c) => c.time));
                const fresh = older.filter((c) => !known.has(c.time));

                if (fresh.length === 0) {
                    exhausted = true;
                    return;
                }

                const timeScale = chart.timeScale();
                const range = timeScale.getVisibleLogicalRange();

                dataRef.current = normalizeCandles([
                    ...fresh,
                    ...dataRef.current,
                ]);
                sync(true);

                if (range) {
                    timeScale.setVisibleLogicalRange({
                        from: range.from + fresh.length,
                        to: range.to + fresh.length,
                    });
                }
            } catch {
                // Ignore
            } finally {
                loadingOlder = false;
            }
        };

        const resync = async () => {
            try {
                const latest = await fetchCandles();
                if (!isCurrent() || latest.length === 0) return;

                const firstLatest = latest[0].time;
                const older = dataRef.current.filter(
                    (c) => c.time < firstLatest,
                );
                dataRef.current = normalizeCandles([...older, ...latest]);
                sync(true);
            } catch {
                // Ignore
            }
        };

        let heartbeat: ReturnType<typeof setInterval> | undefined;

        const streamConfig = () => {
            const driver = wsDriver.toLowerCase();
            if (driver === "bybit") {
                const map: Record<string, string> = {
                    "1m": "1",
                    "5m": "5",
                    "15m": "15",
                    "30m": "30",
                    "1h": "60",
                    "4h": "240",
                    "1d": "D",
                    "1w": "W",
                };
                return {
                    driver,
                    topic: `kline.${map[interval] ?? interval}.${symbol.toUpperCase()}`,
                };
            }
            if (driver === "okx") {
                const map: Record<string, string> = {
                    "1m": "candle1m",
                    "5m": "candle5m",
                    "15m": "candle15m",
                    "30m": "candle30m",
                    "1h": "candle1H",
                    "4h": "candle4H",
                    "1d": "candle1D",
                    "1w": "candle1W",
                };
                const instId =
                    market === "futures"
                        ? symbol.toUpperCase().endsWith("-SWAP")
                            ? symbol.toUpperCase()
                            : symbol.toUpperCase().replace("USDT", "-USDT-SWAP")
                        : symbol.toUpperCase().includes("-")
                          ? symbol.toUpperCase()
                          : symbol.toUpperCase().replace("USDT", "-USDT");
                return {
                    driver,
                    topic: map[interval] ?? `candle${interval}`,
                    instId,
                };
            }
            return { driver: "binance" };
        };

        const parseMessage = (payload: unknown): Candle | null => {
            const cfg = streamConfig();
            if (cfg.driver === "bybit") {
                const data =
                    payload && typeof payload === "object"
                        ? (payload as { data?: unknown }).data
                        : null;
                const k = Array.isArray(data) ? data[0] : data;
                if (!k || typeof k !== "object") return null;
                const row = k as Record<string, unknown>;
                return {
                    time: Math.floor(Number(row.start) / 1000),
                    open: Number(row.open),
                    high: Number(row.high),
                    low: Number(row.low),
                    close: Number(row.close),
                    volume: Number(row.volume),
                };
            }
            if (cfg.driver === "okx") {
                const data =
                    payload && typeof payload === "object"
                        ? (payload as { data?: unknown }).data
                        : null;
                const k = Array.isArray(data) ? data[0] : null;
                if (!Array.isArray(k)) return null;
                return {
                    time: Math.floor(Number(k[0]) / 1000),
                    open: Number(k[1]),
                    high: Number(k[2]),
                    low: Number(k[3]),
                    close: Number(k[4]),
                    volume: Number(k[5]),
                };
            }
            const k =
                payload && typeof payload === "object"
                    ? (payload as { k?: unknown }).k
                    : null;
            if (!k || typeof k !== "object") return null;
            const row = k as Record<string, unknown>;
            return {
                time: Math.floor(Number(row.t) / 1000),
                open: Number(row.o),
                high: Number(row.h),
                low: Number(row.l),
                close: Number(row.c),
                volume: Number(row.v),
            };
        };

        const connect = () => {
            if (!isCurrent()) return;
            const cfg = streamConfig();
            const socketUrl = (() => {
                if (cfg.driver !== "binance") return wsUrl;
                const base = wsUrl.replace(/\/$/, "").replace(/\/market$/, "");
                const stream = `${symbol.toLowerCase()}@kline_${interval}`;
                return base.endsWith("/ws") ? `${base}/${stream}` : `${base}/ws/${stream}`;
            })();
            const socket = new WebSocket(socketUrl);
            ws = socket;

            socket.onopen = () => {
                if (!isCurrent() || ws !== socket) return;
                if (cfg.driver === "bybit")
                    socket.send(
                        JSON.stringify({ op: "subscribe", args: [cfg.topic] }),
                    );
                if (cfg.driver === "okx")
                    socket.send(
                        JSON.stringify({
                            op: "subscribe",
                            args: [{ channel: cfg.topic, instId: cfg.instId }],
                        }),
                    );
                setStreamState("live");
                heartbeat = setInterval(() => {
                    if (ws !== socket || socket.readyState !== WebSocket.OPEN)
                        return;
                    if (cfg.driver === "bybit")
                        socket.send(JSON.stringify({ op: "ping" }));
                    if (cfg.driver === "okx") socket.send("ping");
                }, 20000);
                if (attempts > 0) void resync();
                attempts = 0;
            };

            socket.onmessage = (event) => {
                if (!isCurrent() || ws !== socket) return;

                let payload: unknown;
                try {
                    payload = JSON.parse(event.data);
                } catch {
                    return;
                }

                const candle = parseMessage(payload);
                if (
                    !candle ||
                    !Number.isFinite(candle.time) ||
                    !Number.isFinite(candle.open) ||
                    !Number.isFinite(candle.high) ||
                    !Number.isFinite(candle.low) ||
                    !Number.isFinite(candle.close) ||
                    !Number.isFinite(candle.volume)
                )
                    return;

                const data = dataRef.current;
                const last = data[data.length - 1];
                const existingIndex = data.findIndex(
                    (c) => c.time === candle.time,
                );

                if (existingIndex >= 0) {
                    data[existingIndex] = candle;
                } else if (!last || candle.time > last.time) {
                    data.push(candle);
                } else {
                    // Out-of-order websocket data is ignored rather than corrupting
                    // the chronological candle series.
                    return;
                }

                sync(existingIndex < 0);
                onLastPriceRef.current?.(candle.close, candle.time as number);
                if (!hoveringRef.current) setLegend(candle);
            };

            socket.onclose = () => {
                if (heartbeat) {
                    clearInterval(heartbeat);
                    heartbeat = undefined;
                }
                if (!isCurrent() || ws !== socket) return;
                ws = null;
                setStreamState("reconnecting");
                attempts += 1;
                clearTimeout(retryTimer);
                retryTimer = setTimeout(
                    connect,
                    Math.min(1000 * 2 ** attempts, 15000),
                );
            };

            socket.onerror = () => {
                if (isCurrent() && ws === socket) socket.close();
            };
        };

        (async () => {
            try {
                const first = await fetchCandles(undefined, 200);
                if (!isCurrent()) return;

                if (first.length === 0) {
                    setStatus("error");
                    return;
                }

                dataRef.current = normalizeCandles(first);
                const last = first[first.length - 1];
                const precision = precisionFor(last.close);

                candleSeries.applyOptions({
                    priceFormat: {
                        type: "price",
                        precision,
                        minMove: 1 / 10 ** precision,
                    },
                });

                sync(true);

                if (focusTime != null && Number.isFinite(focusTime)) {
                    // Notifications can point to an event older than the initial
                    // 300 candles. Load older batches until the event candle is
                    // available, then center the chart around it.
                    let guard = 0;
                    while (
                        dataRef.current.length > 0 &&
                        focusTime < dataRef.current[0].time &&
                        !exhausted &&
                        guard < 10
                    ) {
                        await loadOlderRef.current?.();
                        guard += 1;
                    }

                    const targetIndex = nearestCandleIndex(
                        dataRef.current,
                        focusTime,
                    );

                    if (targetIndex >= 0) {
                        chart.timeScale().setVisibleLogicalRange({
                            from: Math.max(0, targetIndex - 70),
                            to: Math.min(
                                dataRef.current.length + 6,
                                targetIndex + 70,
                            ),
                        });
                    }
                } else {
                    chart.timeScale().setVisibleLogicalRange({
                        from: Math.max(0, first.length - 140),
                        to: first.length + 6,
                    });
                }

                setLegend(last);
                setStatus("ready");
                onLastPriceRef.current?.(last.close, last.time);
                connect();
            } catch {
                if (!requestController.signal.aborted && isCurrent()) {
                    setStatus("error");
                }
            }
        })();

        return () => {
            cancelled = true;
            clearTimeout(retryTimer);
            requestController.abort();
            loadOlderRef.current = null;
            Object.values(volumeProfileLinesRef.current).forEach((line) => {
                if (line) candleSeries.removePriceLine(line);
            });
            volumeProfileLinesRef.current = {};
            volumeProfileRef.current = null;
            onVolumeProfileRef.current?.(null);
            if (ws) {
                ws.onopen = null;
                ws.onmessage = null;
                ws.onclose = null;
                ws.onerror = null;
                ws.close();
                ws = null;
            }
        };
    }, [symbol, market, interval, provider, wsUrl, wsDriver, focusTime]);

    useEffect(() => {
        if (!volumeProfileEnabled) {
            volumeProfileRef.current = null;
            onVolumeProfileRef.current?.(null);
            sync(true);
            return;
        }

        const controller = new AbortController();
        let cancelled = false;

        const loadProfile = async () => {
            try {
                const qs = new URLSearchParams({
                    symbol,
                    market,
                    interval,
                    provider,
                    lookback: "300",
                    bins: "48",
                    value_area: "0.70",
                });
                const response = await fetch(`/market/volume-profile?${qs}`, {
                    headers: {
                        Accept: "application/json",
                        "X-Requested-With": "XMLHttpRequest",
                    },
                    credentials: "same-origin",
                    cache: "default",
                    signal: controller.signal,
                });

                if (!response.ok) throw new Error(`HTTP ${response.status}`);
                const payload: unknown = await response.json();
                if (!isVolumeProfile(payload))
                    throw new Error("Format Volume Profile tidak valid");
                if (cancelled) return;

                volumeProfileRef.current = payload;
                onVolumeProfileRef.current?.(payload);
                sync(true);
            } catch {
                if (cancelled || controller.signal.aborted) return;
                // Keep the last valid profile during a transient API failure.
                // The chart remains usable and the next refresh retries automatically.
                sync(true);
            }
        };

        void loadProfile();
        const refreshTimer = window.setInterval(loadProfile, 30000);

        return () => {
            cancelled = true;
            controller.abort();
            window.clearInterval(refreshTimer);
        };
    }, [symbol, market, interval, provider, volumeProfileEnabled]);

    useEffect(() => {
        // Perubahan indikator adalah perubahan konfigurasi visual, bukan data candle.
        // Full sync memastikan series baru/yang diaktifkan kembali mendapat seluruh
        // history, bukan hanya candle terakhir.
        sync(true);
    }, [indicators]);

    const change = legend
        ? ((legend.close - legend.open) / legend.open) * 100
        : 0;
    const tone =
        legend && legend.close >= legend.open
            ? "text-emerald-400"
            : "text-red-400";

    return (
        <div className="relative w-full min-w-0 overflow-hidden rounded-xl bg-[#131722]">
            {legend && (
                <div className="pointer-events-none absolute left-2 top-2 z-10 flex max-w-[calc(100%-1rem)] flex-wrap gap-x-2 gap-y-0.5 text-[10px] text-gray-400 tabular-nums sm:left-3 sm:gap-x-3 sm:text-xs">
                    <span>
                        O{" "}
                        <span className={tone}>{formatPrice(legend.open)}</span>
                    </span>
                    <span>
                        H{" "}
                        <span className={tone}>{formatPrice(legend.high)}</span>
                    </span>
                    <span>
                        L{" "}
                        <span className={tone}>{formatPrice(legend.low)}</span>
                    </span>
                    <span>
                        C{" "}
                        <span className={tone}>
                            {formatPrice(legend.close)}
                        </span>
                    </span>
                    <span className={tone}>
                        {change >= 0 ? "+" : ""}
                        {change.toFixed(2)}%
                    </span>
                    <span>
                        Vol{" "}
                        <span className="text-gray-200">
                            {formatVolume(legend.volume)}
                        </span>
                    </span>
                </div>
            )}

            {status === "ready" && (
                <div
                    className={`absolute left-2 top-11 z-10 flex max-w-[calc(100%-1rem)] items-center gap-1.5 rounded-md border bg-black/70 px-2 py-1.5 text-[10px] backdrop-blur sm:left-3 sm:top-7 sm:gap-2 sm:px-3 sm:text-xs ${
                        streamState === "reconnecting" || dataStale
                            ? "border-amber-500/30 text-amber-200"
                            : "border-emerald-500/20 text-emerald-200"
                    }`}
                >
                    <span className="font-medium">
                        {streamState === "reconnecting"
                            ? t(
                                  "chart.reconnecting",
                                  "Realtime reconnecting...",
                              )
                            : dataStale
                              ? t("chart.stale_data", "Market data delayed")
                              : marketDataStatus === "live"
                                ? t("chart.live_data", "Live data")
                                : marketDataStatus === "historical"
                                  ? t(
                                        "chart.historical_data",
                                        "Historical data",
                                    )
                                  : t(
                                        "chart.data_status_unknown",
                                        "Data status unknown",
                                    )}
                    </span>
                    <span className="hidden max-w-28 truncate text-gray-300/80 sm:inline">
                        {marketDataSource}
                    </span>
                    {dataAge > 0 && marketDataStatus !== "historical" && (
                        <span className="text-gray-400">
                            {Math.floor(dataAge / 60) > 0
                                ? `${Math.floor(dataAge / 60)}m ${dataAge % 60}s ago`
                                : `${dataAge}s ago`}
                        </span>
                    )}
                </div>
            )}

            <div
                ref={containerRef}
                className="h-[clamp(20rem,62vh,35rem)] w-full min-w-0 sm:h-140"
            />

            {status !== "ready" && (
                <div className="absolute inset-0 z-20 flex items-center justify-center bg-[#131722]/90 px-6 text-center text-sm text-gray-400">
                    {status === "loading"
                        ? t("chart.loading", "Loading :symbol :interval...", {
                              symbol,
                              interval,
                          })
                        : t(
                              "chart.load_error",
                              "Unable to load chart data, Your internet connection is blocking our API. ",
                          )}
                </div>
            )}
        </div>
    );
}
