import { useInterval } from "usehooks-ts";

import SidebarHeader from "@/components/SidebarHeader";
import { useRTCStore, useUiStore } from "@/hooks/stores";

import { createChartArray, Metric } from "../Metric";
import { SettingsSectionHeader } from "../SettingsSectionHeader";

export default function ConnectionStatsSidebar() {
  const inboundVideoRtpStats = useRTCStore(state => state.inboundRtpStats);
  const iceCandidatePairStats = useRTCStore(state => state.candidatePairStats);
  const setSidebarView = useUiStore(state => state.setSidebarView);

  function isMetricSupported<T, K extends keyof T>(
    stream: Map<number, T>,
    metric: K,
  ): boolean {
    return Array.from(stream).some(([, stat]) => stat[metric] !== undefined);
  }

  const appendInboundVideoRtpStats = useRTCStore(state => state.appendInboundRtpStats);
  const appendIceCandidatePair = useRTCStore(state => state.appendCandidatePairStats);
  const appendDiskDataChannelStats = useRTCStore(
    state => state.appendDiskDataChannelStats,
  );
  const appendLocalCandidateStats = useRTCStore(state => state.appendLocalCandidateStats);
  const appendRemoteCandidateStats = useRTCStore(
    state => state.appendRemoteCandidateStats,
  );

  const peerConnection = useRTCStore(state => state.peerConnection);
  const mediaStream = useRTCStore(state => state.mediaStream);
  const sidebarView = useUiStore(state => state.sidebarView);

  useInterval(function collectWebRTCStats() {
    (async () => {
      if (!mediaStream) return;
      const stats = await peerConnection?.getStats();
      let successfulLocalCandidateId: string | null = null;
      let successfulRemoteCandidateId: string | null = null;

      stats?.forEach(report => {
        if (report.type === "inbound-rtp" && report.kind === "video") {
          appendInboundVideoRtpStats(report);
        } else if (report.type === "candidate-pair" && report.nominated) {
          if (report.state === "succeeded") {
            successfulLocalCandidateId = report.localCandidateId;
            successfulRemoteCandidateId = report.remoteCandidateId;
          }

          appendIceCandidatePair(report);
        } else if (report.type === "local-candidate") {
          // We only want to append the local candidate stats that were used in nominated candidate pair
          if (successfulLocalCandidateId === report.id) {
            appendLocalCandidateStats(report);
          }
        } else if (report.type === "remote-candidate") {
          if (successfulRemoteCandidateId === report.id) {
            appendRemoteCandidateStats(report);
          }
        } else if (report.type === "data-channel" && report.label === "disk") {
          appendDiskDataChannelStats(report);
        }
      });
    })();
  }, 500);

  const jitterBufferDelay = createChartArray(inboundVideoRtpStats, "jitterBufferDelay");
  const jitterBufferEmittedCount = createChartArray(
    inboundVideoRtpStats,
    "jitterBufferEmittedCount",
  );

  const jitterBufferAvgDelayData = jitterBufferDelay.map((d, idx) => {
    if (idx === 0) return { date: d.date, stat: null };
    const prevDelay = jitterBufferDelay[idx - 1]?.stat as number | null | undefined;
    const currDelay = d.stat as number | null | undefined;
    const prevEmitted =
      (jitterBufferEmittedCount[idx - 1]?.stat as number | null | undefined) ?? null;
    const currEmitted =
      (jitterBufferEmittedCount[idx]?.stat as number | null | undefined) ?? null;

    if (
      prevDelay == null ||
      currDelay == null ||
      prevEmitted == null ||
      currEmitted == null
    ) {
      return { date: d.date, stat: null };
    }

    const deltaDelay = currDelay - prevDelay;
    const deltaEmitted = currEmitted - prevEmitted;

    // Guard counter resets or no emitted frames
    if (deltaDelay < 0 || deltaEmitted <= 0) {
      return { date: d.date, stat: null };
    }

    const valueMs = Math.round((deltaDelay / deltaEmitted) * 1000);
    return { date: d.date, stat: valueMs };
  });

  // Rolling average over the last N seconds for the reference line
  const rollingWindowSeconds = 20;
  const recent = jitterBufferAvgDelayData
    .slice(-rollingWindowSeconds)
    .filter(x => x.stat != null) as { date: number; stat: number }[];
  const referenceValue =
    recent.length > 0
      ? Math.round(recent.reduce((sum, x) => sum + (x.stat as number), 0) / recent.length)
      : undefined;

  return (
    <div className="grid h-full grid-rows-(--grid-headerBody) shadow-xs">
      <SidebarHeader title="Connection Stats" setSidebarView={setSidebarView} />
      <div className="h-full space-y-4 overflow-y-scroll bg-white px-4 py-2 pb-8 dark:bg-slate-900">
        <div className="space-y-4">
          {sidebarView === "connection-stats" && (
            <div className="space-y-8">
              {/* Connection Group */}
              <div className="space-y-3">
                <SettingsSectionHeader
                  title="Connection"
                  description="The connection between the client and the JetKVM."
                />
                <Metric
                  title="Round-Trip Time"
                  description="Round-trip time for the active ICE candidate pair between peers."
                  stream={iceCandidatePairStats}
                  gate={inboundVideoRtpStats}
                  metric="currentRoundTripTime"
                  map={x => ({
                    date: x.date,
                    stat: x.stat ? Math.round((x.stat as number) * 1000) : null,
                  })}
                  domain={[0, 600]}
                  unit=" ms"
                />
              </div>

              {/* Video Group */}
              <div className="space-y-3">
                <SettingsSectionHeader
                  title="Video"
                  description="The video stream from the JetKVM to the client."
                />

                {/* RTP Jitter */}
                <Metric
                  title="Network Stability"
                  badge="Jitter"
                  badgeTheme="light"
                  description="How steady the flow of inbound video packets is across the network."
                  stream={inboundVideoRtpStats}
                  metric="jitter"
                  map={x => ({
                    date: x.date,
                    stat: x.stat ? Math.round((x.stat as number) * 1000) : null,
                  })}
                  domain={[0, 10]}
                  unit=" ms"
                />

                {/* Playback Delay */}
                <Metric
                  title="Playback Delay"
                  description="Delay added by the jitter buffer to smooth playback when frames arrive unevenly."
                  badge="Jitter Buffer Avg. Delay"
                  badgeTheme="light"
                  data={jitterBufferAvgDelayData}
                  gate={inboundVideoRtpStats}
                  supported={
                    isMetricSupported(inboundVideoRtpStats, "jitterBufferDelay") &&
                    isMetricSupported(inboundVideoRtpStats, "jitterBufferEmittedCount")
                  }
                  domain={[0, 30]}
                  unit=" ms"
                  referenceValue={referenceValue}
                />

                {/* Packets Lost */}
                <Metric
                  title="Packets Lost"
                  description="Count of lost inbound video RTP packets."
                  stream={inboundVideoRtpStats}
                  metric="packetsLost"
                  domain={[0, 100]}
                  unit=" packets"
                />

                {/* Frames Per Second */}
                <Metric
                  title="Frames per second"
                  description="Number of inbound video frames displayed per second."
                  stream={inboundVideoRtpStats}
                  metric="framesPerSecond"
                  domain={[0, 80]}
                  unit=" fps"
                />
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
}
