/**
 * ═══════════════════════════════════════════════════════════════════
 * packages/live-charts/src/components/LiveChart.tsx
 * Molécula de gráfico em tempo real — consome useWebSocket.
 * Renderiza em Canvas SVG via Recharts, thread-safe:
 *  - atualizações em lote de 60fps via requestAnimationFrame
 *  - sem re-renders do React por ponto individual
 *  - buffer acotovelado na origem (useWebSocket)
 * ═══════════════════════════════════════════════════════════════════
 */

"use client";

import React, {
  type CSSProperties,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from "react";

import {
  Area,
  AreaChart,
  CartesianGrid,
  ResponsiveContainer,
  Tooltip,
  XAxis,
  YAxis,
} from "recharts";

import { useWebSocket } from "@pulse-libs/use-websocket";
import type { WSConfig, WSState } from "@pulse-libs/shared";

// ─────────────────────────────────────────────────────────────────
// Tipos locais
// ─────────────────────────────────────────────────────────────────

export interface ChartDatum {
  /** Eixo X — timestamp legível ou label */
  x: string | number;
  /** Eixo Y — valor numérico */
  y: number;
  /** Valor original cru antes de agregação */
  raw?: unknown;
  /** Opacity customizada por ponto (0-1) */
  opacity?: number;
}

export interface LiveChartProps<T extends ChartDatum = ChartDatum> {
  /**
   * Configuração do WebSocket — obrigatória.
   * O hook gerencia a conexão, buffet e reconexão internamente.
   */
  wsConfig: WSConfig;

  /**
   * Transforma a mensagem WS bruta em pontos do gráfico.
   * Retorne um array para múltiplas séries, ou um ponto único.
   */
  mapper: (msg: unknown) => T | T[];

  /**
   * Mapeia `x` do ChartDatum para o label do eixo X.
   * Default: timestamp → HH:mm:ss.
   */
  xFormatter?: (v: number | string) => string;

  /**
   * Mapeia `y` do ChartDatum para tooltip.
   */
  yFormatter?: (v: number) => string;

  /**
   * Máximo de pontos visíveis na janela deslizante.
   * Mais antigos são removidos automaticamente.
   * Default: 80 (aprox. 80 × intervalo do WM).
   */
  visiblePoints?: number;

  /**
   * Intervalo mínimo entre renders do React em ms.
   * Usa requestAnimationFrame internamente para não bloquear a thread.
   * Default: 16 ≈ 60fps.
   */
  renderIntervalMs?: number;

  /** Cor da área preenchida. */
  fillColor?: string;
  /** Cor da linha de traço. */
  strokeColor?: string;
  /** Exibir grade do gráfico. */
  showGrid?: boolean;
  /** Exibir tooltip. */
  showTooltip?: boolean;
  /** Altura do container. */
  height?: number;
  /** CSS customizado. */
  style?: CSSProperties;
  /** classes CSS. */
  className?: string;

  /**
   * Callback quando um novo dado é recebido via WS.
   * Útil para acionar alertas ou atualizar KPIs externos.
   */
  onDataPoint?: (datum: T) => void;

  /**
   * Callback para erros do WS.
   */
  onError?: (err: Error) => void;

  /**
   * Callback para mudança de status da conexão.
   * Útil para exibir indicadores de saúde no dashboard.
   */
  onStatusChange?: (
    status: WSState<T>["status"],
    prev: WSState<T>["status"],
  ) => void;

  /**
   * Se `true`, limpa os dados ao desmontar (última snapshot).
   * Default: `false` — mantém estado entre suspensões.
   */
  resetOnUnmount?: boolean;
}

// ─────────────────────────────────────────────────────────────────
// Constantes
// ─────────────────────────────────────────────────────────────────

const DEFAULT_VISIBLE_POINTS = 80;
const DEFAULT_RENDER_INTERVAL = 16; // ~60fps
const DEFAULT_HEIGHT = 320;

const FILL_COLOR   = "rgba(37, 99, 235, 0.15)";
const STROKE_COLOR = "#2563eb";
const GRID_STROKE  = "rgba(51, 65, 85, 0.4)";

// ─────────────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────────────

function formatTimestamp(ts: number): string {
  const d = new Date(ts);
  return `${d.getHours().toString().padStart(2, "0")}:${d
    .getMinutes()
    .toString()
    .padStart(2, "0")}:${d
    .getSeconds()
    .toString()
    .padStart(2, "0")}`;
}

function slideWindow<T>(arr: T[], max: number): T[] {
  return arr.length > max ? arr.slice(arr.length - max) : arr;
}

// ─────────────────────────────────────────────────────────────────
// Componente
// ─────────────────────────────────────────────────────────────────

export function LiveChart<T extends ChartDatum = ChartDatum>(
  props: LiveChartProps<T>,
): React.ReactElement {
  const {
    wsConfig,
    mapper,
    xFormatter = formatTimestamp,
    yFormatter = (v) => v.toFixed(2),
    visiblePoints = DEFAULT_VISIBLE_POINTS,
    renderIntervalMs = DEFAULT_RENDER_INTERVAL,
    fillColor   = FILL_COLOR,
    strokeColor = STROKE_COLOR,
    showGrid   = true,
    showTooltip = true,
    height     = DEFAULT_HEIGHT,
    style,
    className,
    onDataPoint,
    onError,
    onStatusChange,
    resetOnUnmount = false,
  } = props;

  const [displayData, setDisplayData] = useState<T[]>([]);
  const rafRef      = useRef<number>(0);
  const pendingRef  = useRef<T[]>([]);
  const lastTsRef   = useRef<number>(0);

  // Bridge WS → displayData via rAF (GPU-friendly, single re-render/frame)
  const pushPending = useCallback((incoming: T[]) => {
    pendingRef.current = [...pendingRef.current, ...incoming];
  }, []);

  // rAF loop — consome pendingRef e atualiza displayData
  useEffect(() => {
    let running = true;

    function tick(now: number) {
      if (!running) return;
      if (now - lastTsRef.current >= renderIntervalMs) {
        lastTsRef.current = now;
        const chunk = pendingRef.current;
        if (chunk.length > 0) {
          pendingRef.current = [];
          setDisplayData((prev) =>
            slideWindow([...prev, ...chunk], visiblePoints),
          );
        }
      }
      rafRef.current = requestAnimationFrame(tick);
    }

    rafRef.current = requestAnimationFrame(tick);
    return () => {
      running = false;
      cancelAnimationFrame(rafRef.current);
    };
  }, [renderIntervalMs, visiblePoints]);

  // Hook WebSocket
  const wsState = useWebSocket<T & { raw?: unknown }>({
    ...wsConfig,
    onMessage: useCallback(
      (msg) => {
        const mapped = mapper(msg.payload);
        const arr = Array.isArray(mapped) ? mapped : [mapped];
        arr.forEach((d) => onDataPoint?.(d as T));
        pushPending(arr as T[]);
      },
      [mapper, onDataPoint, pushPending],
    ),
    onError: useCallback(
      (err) => { onError?.(err); },
      [onError],
    ),
    onStatus: useCallback(
      (status, _prev) => {
        onStatusChange?.(status as WSState<T>["status"], _prev as WSState<T>["status"]);
      },
      [onStatusChange],
    ),
  });

  // cleanup no unmount
  useEffect(
    () => () => {
      if (resetOnUnmount) setDisplayData([]);
      pendingRef.current = [];
    },
    [resetOnUnmount],
  );

  // ── derived stats ───────────────────────────────────────────────
  const stats = useMemo(() => {
    if (displayData.length === 0) return null;
    const values = displayData.map((d) => d.y);
    const sum = values.reduce((a, b) => a + b, 0);
    return {
      current: values[values.length - 1]!,
      min:     Math.min(...values),
      max:     Math.max(...values),
      average: sum / values.length,
      total:   sum,
    };
  }, [displayData]);

  // ── ChartData formatada para Recharts ───────────────────────────
  const chartData = useMemo(
    () =>
      displayData.map((d) => ({
        x: typeof d.x === "number" ? xFormatter(d.x) : d.x,
        y: d.y,
        opacity: d.opacity ?? 1,
      })),
    [displayData, xFormatter],
  );

  // ── Renders ─────────────────────────────────────────────────────
  if (wsState.status === "error" || wsState.status === "closed") {
    return (
      <div
        className={className}
        style={{
          height,
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          background: "rgba(220, 38, 38, 0.05)",
          border: "1px solid rgba(220, 38, 38, 0.2)",
          borderRadius: 12,
          color: "#f87171",
          fontSize: "0.875rem",
          fontFamily: "JetBrains Mono, monospace",
          ...style,
        }}
      >
        ⚠ Conexao WS indisponível —{" "}
        {wsState.error?.message ?? "verifique o broker"}
      </div>
    );
  }

  return (
    <div
      className={className}
      role="group"
      aria-label="Grafico em tempo real"
      aria-live="polite"
      style={{ ...style }}
    >
      {/* ── Header com KPIs ── */}
      {stats && (
        <div
          style={{
            display: "flex",
            gap: "1rem",
            marginBottom: "0.75rem",
            flexWrap: "wrap",
            fontFamily: "'JetBrains Mono', monospace",
            fontSize: "0.75rem",
          }}
          aria-label="Estatisticas do grafico"
        >
          <Kpi label="Atual" value={yFormatter(stats.current)} color="#60a5fa" />
          <Kpi label="Min"    value={yFormatter(stats.min)}     color="#34d399" />
          <Kpi label="Max"    value={yFormatter(stats.max)}     color="#fbbf24" />
          <Kpi label="Media"  value={yFormatter(stats.average)} color="#a78bfa" />
          <div
            style={{
              display: "flex",
              alignItems: "center",
              gap: "0.4rem",
              color: WS_STATUS_COLOR[wsState.status],
            }}
            aria-label={`Status WS: ${wsState.status}`}
          >
            <span
              style={{
                width: 8,
                height: 8,
                borderRadius: "50%",
                background: WS_STATUS_COLOR[wsState.status],
                display: "inline-block",
              }}
            />
            {wsState.status}
            {wsState.latencyMs != null && (
              <span style={{ color: "#94a3b8" }}>
                · {wsState.latencyMs}ms
              </span>
            )}
          </div>
        </div>
      )}

      {/* ── Recharts AreaChart ── */}
      <ResponsiveContainer width="100%" height={height}>
        <AreaChart
          data={chartData}
          margin={{ top: 8, right: 16, bottom: 0, left: -12 }}
          syncId="live-chart-sync"
          aria-label="Grafico de linha em tempo real"
        >
          {showGrid && (
            <CartesianGrid
              stroke={GRID_STROKE}
              strokeDasharray="3 3"
              vertical={false}
            />
          )}
          <XAxis
            dataKey="x"
            tick={{ fill: "#64748b", fontSize: 10, fontFamily: "JetBrains Mono" }}
            axisLine={{ stroke: GRID_STROKE }}
            tickLine={false}
            minTickGap={50}
          />
          <YAxis
            tick={{ fill: "#64748b", fontSize: 10, fontFamily: "JetBrains Mono" }}
            axisLine={{ stroke: GRID_STROKE }}
            tickLine={false}
            width={44}
            domain={["auto", "auto"]}
            tickFormatter={yFormatter}
          />
          {showTooltip && (
            <Tooltip
              contentStyle={{
                background: "rgba(15, 23, 42, 0.9)",
                border: "1px solid rgba(51, 65, 85, 0.6)",
                borderRadius: 8,
                fontSize: 12,
                fontFamily: "JetBrains Mono, monospace",
                color: "#e4e4e7",
              }}
              itemStyle={{ color: strokeColor }}
              formatter={(value: number) => [yFormatter(value), ""]}
            />
          )}
          <Area
            type="monotone"
            dataKey="y"
            stroke={strokeColor}
            strokeWidth={2}
            fill={fillColor}
            dot={false}
            activeDot={{
              r: 4,
              fill: strokeColor,
              stroke: "#fff",
              strokeWidth: 2,
            }}
            isAnimationActive={false} // desativa animação Recharts; rAF cuida do smooth
          />
        </AreaChart>
      </ResponsiveContainer>

      {/* ── Legenda de buffer ── */}
      <div
        style={{
          display: "flex",
          justifyContent: "space-between",
          marginTop: "0.5rem",
          fontSize: "0.7rem",
          color: "#475569",
          fontFamily: "JetBrains Mono, monospace",
        }}
        aria-hidden="true"
      >
        <span>{displayData.length} pontos</span>
        <span>buffer {wsState.bufferSize} / {wsConfig.maxBufferSize ?? 512}</span>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────
// Sub-componente Kpi
// ─────────────────────────────────────────────────────────────────

interface KpiProps {
  label: string;
  value: string;
  color: string;
}

function Kpi({ label, value, color }: KpiProps): React.ReactElement {
  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        color,
      }}
    >
      <span style={{ color: "#64748b", marginBottom: 2 }}>{label}</span>
      <span style={{ fontWeight: 700, fontSize: "0.85rem" }}>{value}</span>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────
// Paleta de cores por status
// ─────────────────────────────────────────────────────────────────

const WS_STATUS_COLOR: Record<string, string> = {
  idle:       "#94a3b8",
  connecting: "#fbbf24",
  connected:  "#34d399",
  retrying:   "#f59e0b",
  error:      "#f87171",
  closed:     "#6b7280",
};

export default LiveChart;
