// TelemetryChart.jsx — reusable SVG telemetry chart with range selector.

(function(){
  const RANGE_OPTIONS = ["1h", "6h", "24h", "7d"];
  const STALE_BY_RANGE_MS = {
    "1h": 5 * 60 * 1000,
    "6h": 15 * 60 * 1000,
    "24h": 30 * 60 * 1000,
    "7d": 2 * 60 * 60 * 1000,
  };

  function _safeDate(value) {
    if (!value) return null;
    const d = new Date(value);
    return Number.isNaN(d.getTime()) ? null : d;
  }

  function _formatClock(value) {
    const d = _safeDate(value);
    if (!d) return "--:--:--";
    return d.toLocaleTimeString("ru-RU", { hour12: false });
  }

  function _staleThresholdMs(range) {
    return STALE_BY_RANGE_MS[range] || STALE_BY_RANGE_MS["1h"];
  }

  function _getStaleState(lastTs, range, fallbackMode) {
    if (fallbackMode) return "fallback";
    const last = _safeDate(lastTs);
    if (!last) return "fresh";
    return (Date.now() - last.getTime()) > _staleThresholdMs(range) ? "stale" : "fresh";
  }

  function _formatAge(lastTs) {
    const last = _safeDate(lastTs);
    if (!last) return "n/a";
    const diffSec = Math.max(0, Math.floor((Date.now() - last.getTime()) / 1000));
    if (diffSec < 60) return `${diffSec}s`;
    const min = Math.floor(diffSec / 60);
    if (min < 60) return `${min}m`;
    const hr = Math.floor(min / 60);
    return `${hr}h`;
  }

  function _formatAxis(value, range) {
    const d = _safeDate(value);
    if (!d) return "";
    if (range === "7d") {
      return d.toLocaleDateString("ru-RU", { day: "2-digit", month: "2-digit" });
    }
    return d.toLocaleTimeString("ru-RU", { hour: "2-digit", minute: "2-digit", hour12: false });
  }

  function _toNumber(value) {
    const n = Number(value);
    return Number.isFinite(n) ? n : null;
  }

  function _buildPolyline(points, width, height, padding) {
    if (!Array.isArray(points) || points.length === 0) return "";
    const values = points
      .map((p) => _toNumber(p.value))
      .filter((n) => n !== null);

    if (!values.length) return "";

    let min = Math.min.apply(null, values);
    let max = Math.max.apply(null, values);

    if (Math.abs(max - min) < 1e-6) {
      const spread = Math.max(Math.abs(max) * 0.05, 1);
      min -= spread;
      max += spread;
    }

    const innerW = width - padding * 2;
    const innerH = height - padding * 2;

    return points
      .map((p, i) => {
        const raw = _toNumber(p.value);
        const value = raw === null ? min : raw;
        const x = points.length === 1
          ? (padding + innerW / 2)
          : (padding + (innerW * i) / (points.length - 1));
        const y = padding + ((max - value) / (max - min)) * innerH;
        return `${x.toFixed(2)},${y.toFixed(2)}`;
      })
      .join(" ");
  }

  function _getAxisLabels(points, range) {
    if (!Array.isArray(points) || points.length === 0) {
      return ["", "", ""];
    }
    const first = points[0]?.ts;
    const mid = points[Math.floor((points.length - 1) / 2)]?.ts;
    const last = points[points.length - 1]?.ts;
    return [_formatAxis(first, range), _formatAxis(mid, range), _formatAxis(last, range)];
  }

  function _lastPointTs(points) {
    if (!Array.isArray(points) || points.length === 0) return null;
    return points[points.length - 1]?.ts || null;
  }

  function _pointValue(points) {
    if (!Array.isArray(points) || points.length === 0) return null;
    return _toNumber(points[points.length - 1]?.value);
  }

  function _formatValue(value) {
    if (value === null || value === undefined) return "—";
    if (Math.abs(value) >= 1000) return value.toLocaleString("ru-RU");
    return Number.isInteger(value) ? String(value) : value.toFixed(1).replace(".", ",");
  }

  // [TASK_START:T005]
  function useDeviceTelemetry({ deviceId, metricKey, defaultRange = "1h", autoRefreshMs = 60000 }) {
    const [range, setRange] = React.useState(defaultRange);
    const [state, setState] = React.useState({
      loading: true,
      error: false,
      points: [],
      unit: "",
      updatedAt: null,
      dataTimestamp: null,
      fallbackMode: false,
    });

    const load = React.useCallback(async (opts = {}) => {
      const silent = Boolean(opts.silent);
      if (!deviceId || !metricKey) {
        setState({
          loading: false,
          error: false,
          points: [],
          unit: "",
          updatedAt: null,
          dataTimestamp: null,
          fallbackMode: false,
        });
        return;
      }

      if (!silent) {
        setState((prev) => Object.assign({}, prev, { loading: true, error: false, fallbackMode: false }));
      }

      try {
        const payload = await Keeper.api.deviceTelemetrySeries(deviceId, metricKey, range);
        const points = payload.points || [];
        const dataTimestamp = _lastPointTs(points) || payload.generated_at || null;
        const refreshedAt = new Date().toISOString();
        setState({
          loading: false,
          error: false,
          points,
          unit: payload.unit || "",
          updatedAt: refreshedAt,
          dataTimestamp,
          fallbackMode: false,
        });
      } catch (err) {
        const nowTs = new Date().toISOString();
        // No telemetry for this period/device is an empty state, not an error card.
        if (err && err.status === 404) {
          setState({
            loading: false,
            error: false,
            points: [],
            unit: "",
            updatedAt: nowTs,
            dataTimestamp: null,
            fallbackMode: false,
          });
          return;
        }
        setState((prev) => {
          const hasFallback = Array.isArray(prev.points) && prev.points.length > 0;
          return Object.assign({}, prev, {
            loading: false,
            error: !hasFallback,
            updatedAt: nowTs,
            fallbackMode: hasFallback,
          });
        });
      }
    }, [deviceId, metricKey, range]);

    React.useEffect(() => {
      load();
    }, [load]);

    React.useEffect(() => {
      if (!deviceId || !metricKey) return undefined;
      const timer = setInterval(() => { load({ silent: true }); }, autoRefreshMs);
      return () => clearInterval(timer);
    }, [deviceId, metricKey, range, autoRefreshMs, load]);

    return {
      range,
      setRange,
      loading: state.loading,
      error: state.error,
      points: state.points,
      unit: state.unit,
      updatedAt: state.updatedAt,
      dataTimestamp: state.dataTimestamp,
      fallbackMode: state.fallbackMode,
      retry: () => load(),
    };
  }
  // [TASK_COMPLETE:T005]

  function useHiveHistoryTelemetry({ hiveId, defaultRange = "24h", autoRefreshMs = 60000 }) {
    const [range, setRange] = React.useState(defaultRange);
    const [state, setState] = React.useState({
      loading: true,
      error: false,
      points: [],
      unit: "",
      updatedAt: null,
      dataTimestamp: null,
      fallbackMode: false,
    });

    const load = React.useCallback(async (opts = {}) => {
      const silent = Boolean(opts.silent);
      if (!hiveId) {
        setState({
          loading: false,
          error: false,
          points: [],
          unit: "",
          updatedAt: null,
          dataTimestamp: null,
          fallbackMode: false,
        });
        return;
      }

      if (!silent) {
        setState((prev) => Object.assign({}, prev, { loading: true, error: false, fallbackMode: false }));
      }

      try {
        const payload = await Keeper.api.hiveActivitySeries(hiveId, range);
        const points = payload.points || [];
        const dataTimestamp = _lastPointTs(points) || payload.generated_at || null;
        const refreshedAt = new Date().toISOString();
        setState({
          loading: false,
          error: false,
          points,
          unit: payload.unit || "",
          updatedAt: refreshedAt,
          dataTimestamp,
          fallbackMode: false,
        });
      } catch (_) {
        const nowTs = new Date().toISOString();
        setState((prev) => {
          const hasFallback = Array.isArray(prev.points) && prev.points.length > 0;
          return Object.assign({}, prev, {
            loading: false,
            error: !hasFallback,
            updatedAt: nowTs,
            fallbackMode: hasFallback,
          });
        });
      }
    }, [hiveId, range]);

    React.useEffect(() => {
      load();
    }, [load]);

    React.useEffect(() => {
      if (!hiveId || range !== "1h") return undefined;
      const timer = setInterval(() => { load({ silent: true }); }, autoRefreshMs);
      return () => clearInterval(timer);
    }, [hiveId, range, autoRefreshMs, load]);

    return {
      range,
      setRange,
      loading: state.loading,
      error: state.error,
      points: state.points,
      unit: state.unit,
      updatedAt: state.updatedAt,
      dataTimestamp: state.dataTimestamp,
      fallbackMode: state.fallbackMode,
      retry: () => load(),
    };
  }

  // [TASK_START:T004]
  function TelemetryChart({
    title,
    source = "device",
    deviceId,
    hiveId,
    metricKey,
    unitLabel = "",
    accent = "#007BFB",
    defaultRange = "1h",
    ranges = RANGE_OPTIONS,
    showStaleBadge = true,
  }) {
    const telemetry = source === "hive"
      ? useHiveHistoryTelemetry({ hiveId, defaultRange })
      : useDeviceTelemetry({ deviceId, metricKey, defaultRange });

    const points = telemetry.points || [];
    const mergedUnit = unitLabel || telemetry.unit || "";
    const lastTs = _lastPointTs(points) || telemetry.dataTimestamp || null;
    const staleState = _getStaleState(lastTs, telemetry.range, telemetry.fallbackMode);
    const value = _pointValue(points);

    const width = 340;
    const height = 140;
    const padding = 14;
    const polyline = _buildPolyline(points, width, height, padding);
    const [x0, x1, x2] = _getAxisLabels(points, telemetry.range);

    return (
      <div className="telemetry-panel" data-testid="telemetry-chart">
        <div className="telemetry-head">
          <div>
            <div className="telemetry-title">{title}</div>
            <div className="telemetry-meta">{mergedUnit || "ед."}</div>
          </div>
          <div className="telemetry-range" role="tablist" aria-label={`${title} range selector`}>
            {ranges.map((r) => (
              <button
                key={r}
                className={telemetry.range === r ? "on" : ""}
                onClick={() => telemetry.setRange(r)}
                type="button"
              >
                {r}
              </button>
            ))}
          </div>
        </div>

        <div className="telemetry-value-row">
          <div className="telemetry-value">{_formatValue(value)}</div>
          <div className="telemetry-value-unit">{mergedUnit}</div>
          {showStaleBadge && staleState === "stale" && <span className="telemetry-stale">Stale ({_formatAge(lastTs)})</span>}
          {showStaleBadge && staleState === "fallback" && <span className="telemetry-stale">Fallback ({_formatAge(lastTs)})</span>}
          <span className="telemetry-updated">
            Data: {_formatClock(lastTs)} · Refreshed: {_formatClock(telemetry.updatedAt)}
          </span>
        </div>

        {telemetry.error && (
          <div className="telemetry-state telemetry-state--error">
            <span>Could not load telemetry</span>
            <button className="btn btn--secondary btn--sm" type="button" onClick={telemetry.retry}>Retry</button>
          </div>
        )}

        {!telemetry.error && telemetry.loading && points.length === 0 && (
          <div className="telemetry-state">Loading telemetry…</div>
        )}

        {!telemetry.error && !telemetry.loading && points.length === 0 && (
          <div className="telemetry-state">No data for this period</div>
        )}

        {!telemetry.error && points.length > 0 && (
          <>
            <svg className="telemetry-svg" viewBox={`0 0 ${width} ${height}`} preserveAspectRatio="none">
              <line x1={padding} y1={height * 0.25} x2={width - padding} y2={height * 0.25} className="telemetry-grid-line" />
              <line x1={padding} y1={height * 0.5} x2={width - padding} y2={height * 0.5} className="telemetry-grid-line" />
              <line x1={padding} y1={height * 0.75} x2={width - padding} y2={height * 0.75} className="telemetry-grid-line" />
              <polyline fill="none" stroke={accent} strokeWidth="2" points={polyline} />
            </svg>
            <div className="telemetry-axis">
              <span>{x0}</span>
              <span>{x1}</span>
              <span>{x2}</span>
            </div>
          </>
        )}
      </div>
    );
  }
  // [TASK_COMPLETE:T004]

  window.TelemetryChart = TelemetryChart;
  window.useDeviceTelemetry = useDeviceTelemetry;
})();
