<script lang="ts">
  import type { HudSummary } from "$lib/types/hud";

  export let summary: HudSummary;
  export let side: "left" | "right" = "right";
  export let panelIndex = 0;
  export let xValues: number[] | null = null;
  export let yValues: number[] | null = null;

  const viewportWidth = 120;
  const viewportHeight = 48;
  const plotInsetLeft = 13;
  const plotInsetRight = 4;
  const plotInsetTop = 4;
  const plotInsetBottom = 9;
  const fixedYBounds = { min: 0, max: 25 };

  interface CurveSample {
    x: number;
    y: number;
  }

  interface PlotPoint {
    x: number;
    y: number;
  }

  interface AxisTick {
    value: number;
    label: string;
    plotX: number;
    plotY: number;
  }

  function clamp(value: number, min: number, max: number): number {
    return Math.min(max, Math.max(min, value));
  }

  function formatValue(value: number | null): string {
    if (value === null) {
      return "--";
    }

    return value.toFixed(1);
  }

  function formatAxisValue(value: number, axis: "x" | "y"): string {
    if (!Number.isFinite(value)) {
      return "--";
    }

    if (axis === "x") {
      return String(Math.round(value));
    }

    return `${Math.round(value)} N`;
  }

  function resolveDataBounds(values: number[]): { min: number; max: number } {
    if (values.length === 0) {
      return { min: 0, max: 1 };
    }

    return {
      min: Math.min(...values),
      max: Math.max(...values)
    };
  }

  function resolveBounds(values: number[]): { min: number; max: number } {
    if (values.length === 0) {
      return { min: 0, max: 1 };
    }

    const min = Math.min(...values);
    const max = Math.max(...values);

    if (Math.abs(max - min) < 0.001) {
      const padding = Math.max(Math.abs(max) * 0.05, 1);
      return { min: min - padding, max: max + padding };
    }

    return { min, max };
  }

  function mapXToViewport(value: number, bounds: { min: number; max: number }): number {
    const span = bounds.max - bounds.min;
    const chartWidth = viewportWidth - plotInsetLeft - plotInsetRight;
    const ratio = span <= 0 ? 0.5 : (value - bounds.min) / span;
    const mappedX = plotInsetLeft + ratio * chartWidth;
    return Math.round(clamp(mappedX, plotInsetLeft, viewportWidth - plotInsetRight) * 100) / 100;
  }

  function mapYToViewport(value: number, bounds: { min: number; max: number }): number {
    const span = bounds.max - bounds.min;
    const chartHeight = viewportHeight - plotInsetTop - plotInsetBottom;
    const ratio = span <= 0 ? 0.5 : (value - bounds.min) / span;
    const mappedY = viewportHeight - plotInsetBottom - ratio * chartHeight;
    return Math.round(clamp(mappedY, plotInsetTop, viewportHeight - plotInsetBottom) * 100) / 100;
  }

  function buildSamples(rawYValues: number[], rawXValues: number[]): CurveSample[] {
    if (!rawYValues.length) {
      return [];
    }

    return rawYValues.map((rawY, index) => {
      const x = rawXValues[index];
      const y = Number.isFinite(rawY) ? Number(rawY) : 0;
      const resolvedX = Number.isFinite(x) ? Number(x) : index + 1;
      return { x: resolvedX, y };
    });
  }

  function convertPoints(
    samples: CurveSample[],
    xBounds: { min: number; max: number },
    yBounds: { min: number; max: number }
  ): PlotPoint[] {
    if (samples.length === 0) {
      return [];
    }

    if (samples.length === 1) {
      return [{ x: viewportWidth / 2, y: viewportHeight / 2 }];
    }

    return samples.map((sample) => {
      return {
        x: mapXToViewport(sample.x, xBounds),
        y: mapYToViewport(sample.y, yBounds)
      };
    });
  }

  function buildYAxisTicks(
    yScaleBounds: { min: number; max: number },
    _yDataBounds: { min: number; max: number }
  ): AxisTick[] {
    const tickValues = [25, 20, 15, 10, 5, 0];
    return tickValues.map((value) => ({
      value,
      label: formatAxisValue(value, "y"),
      plotX: plotInsetLeft - 1.8,
      plotY: mapYToViewport(value, yScaleBounds)
    }));
  }

  function buildXAxisTicks(samples: CurveSample[], xScaleBounds: { min: number; max: number }): AxisTick[] {
    if (!samples.length) {
      return [];
    }

    const first = samples[0].x;
    const middle = samples[Math.floor((samples.length - 1) / 2)].x;
    const last = samples[samples.length - 1].x;
    const tickValues = [first, middle, last];
    return tickValues.map((value) => ({
      value,
      label: formatAxisValue(value, "x"),
      plotX: mapXToViewport(value, xScaleBounds),
      plotY: viewportHeight - 0.9
    }));
  }

  function createLinePath(points: PlotPoint[]): string {
    if (points.length === 0) {
      return "";
    }

    return points.map((point, index) => `${index === 0 ? "M" : "L"} ${point.x} ${point.y}`).join(" ");
  }

  function createAreaPath(points: PlotPoint[]): string {
    if (points.length < 2) {
      return "";
    }

    const linePath = createLinePath(points);
    const firstPoint = points[0];
    const lastPoint = points[points.length - 1];

    return `${linePath} L ${lastPoint.x} ${viewportHeight - plotInsetBottom} L ${firstPoint.x} ${viewportHeight - plotInsetBottom} Z`;
  }

  $: sourceYValues = yValues && yValues.length ? yValues : summary.points;
  $: sourceXValues = xValues && xValues.length ? xValues : summary.xValues ?? [];
  $: samples = buildSamples(sourceYValues, sourceXValues);
  $: sampleCount = samples.length;
  $: xScaleBounds = resolveBounds(samples.map((sample) => sample.x));
  $: yScaleBounds = fixedYBounds;
  $: xDataBounds = resolveDataBounds(samples.map((sample) => sample.x));
  $: yDataBounds = resolveDataBounds(samples.map((sample) => sample.y));
  $: plotPoints = convertPoints(samples, xScaleBounds, yScaleBounds);
  $: linePath = createLinePath(plotPoints);
  $: areaPath = createAreaPath(plotPoints);
  $: lastPoint = plotPoints.length > 0 ? plotPoints[plotPoints.length - 1] : null;
  $: yAxisTicks = sampleCount > 0 ? buildYAxisTicks(yScaleBounds, yDataBounds) : [];
  $: xAxisTicks = sampleCount > 0 ? buildXAxisTicks(samples, xScaleBounds) : [];
  $: latestValue = formatValue(summary.latest);
  $: minValue = formatValue(summary.min);
  $: maxValue = formatValue(summary.max);
</script>

<article
  class="signal-panel summary-panel side-{side}"
  class:is-empty={sampleCount === 0}
  aria-hidden={sampleCount === 0}
  style="--panel-index: {panelIndex};"
>
  <header class="panel-head">
    <div class="head-text">
      <p class="panel-code">RF</p>
      <p class="panel-title">{summary.label}</p>
    </div>

    <div class="icon-layer" aria-hidden="true">
      <span class="icon-chip tone-cyan">NOW</span>
      <span class="icon-chip tone-lime">MIN</span>
      <span class="icon-chip tone-orange">MAX</span>
    </div>
  </header>

  <div class="chart-stage">
    <svg viewBox="0 0 {viewportWidth} {viewportHeight}" preserveAspectRatio="none" role="img" aria-label={summary.label}>
      <defs>
        <linearGradient id="summary-fill" x1="0%" y1="0%" x2="0%" y2="100%">
          <stop offset="0%" stop-color="rgb(62 232 255 / 0.28)" />
          <stop offset="100%" stop-color="rgb(62 232 255 / 0.02)" />
        </linearGradient>
      </defs>

      <g class="grid-lines" aria-hidden="true">
        {#each yAxisTicks as tick (`grid-${tick.value}`)}
          <line x1={plotInsetLeft} y1={tick.plotY} x2={viewportWidth - plotInsetRight} y2={tick.plotY}></line>
        {/each}
      </g>

      {#if areaPath}
        <path d={areaPath} class="summary-area"></path>
      {/if}

      {#if linePath}
        <path d={linePath} class="summary-line"></path>
      {/if}

      {#if lastPoint}
        <circle cx={lastPoint.x} cy={lastPoint.y} r="1.7" class="summary-dot"></circle>
      {/if}

      <g class="axis-labels" aria-hidden="true">
        {#each yAxisTicks as tick, index (`y-${index}`)}
          <text class="axis-label y-axis-label" x={tick.plotX} y={tick.plotY + 1.1} text-anchor="end">
            {tick.label}
          </text>
        {/each}

        {#each xAxisTicks as tick, index (`x-${index}`)}
          <text
            class="axis-label x-axis-label"
            x={tick.plotX}
            y={tick.plotY}
            text-anchor={index === 0 ? "start" : index === xAxisTicks.length - 1 ? "end" : "middle"}
          >
            {tick.label}
          </text>
        {/each}
      </g>
    </svg>

    {#if sampleCount === 0}
      <div class="empty-state">
        <span>Waiting</span>
      </div>
    {/if}
  </div>

  <footer class="panel-foot">
    <p class="foot-item">
      <span class="dot tone-cyan"></span>
      <span class="metric-text">Now</span>
      <span class="value">{latestValue}</span>
    </p>
    <p class="foot-item">
      <span class="dot tone-lime"></span>
      <span class="metric-text">Min</span>
      <span class="value">{minValue}</span>
    </p>
    <p class="foot-item">
      <span class="dot tone-orange"></span>
      <span class="metric-text">Max</span>
      <span class="value">{maxValue}</span>
    </p>
  </footer>
</article>

<style>
  .signal-panel {
    --offset-x: 12%;
    --enter-ms: 1800ms;
    --fade-ms: 1000ms;
    overflow: hidden;
    inline-size: min(100%, clamp(29rem, 38vw, 37rem));
    aspect-ratio: 1.42 / 1;
    min-block-size: 20.5rem;
    justify-self: start;
    display: grid;
    grid-template-rows: auto auto auto;
    gap: 0.68rem;
    padding: 0.88rem 0.96rem 1rem;
    border: 1px solid rgb(var(--hud-border-strong-rgb) / 0.42);
    border-radius: 0.92rem;
    background:
      linear-gradient(160deg, rgb(var(--hud-surface-alt-rgb) / 0.76) 0%, rgb(var(--hud-surface-rgb) / 0.62) 48%, rgb(var(--hud-surface-deep-rgb) / 0.76) 100%),
      radial-gradient(circle at 12% 0, rgb(var(--hud-glow-rgb) / 0.1), transparent 40%);
    box-shadow:
      inset 0 0 0 1px rgb(var(--hud-border-strong-rgb) / 0.08),
      inset 0 -24px 32px rgb(0 0 0 / 0.48),
      0 0 14px rgb(var(--hud-glow-rgb) / 0.14);
    opacity: 1;
    transform: translateX(0) scale(1) rotate(0);
    transition:
      opacity var(--fade-ms) cubic-bezier(0.18, 0.88, 0.3, 1),
      transform var(--enter-ms) cubic-bezier(0.2, 0.9, 0.28, 1),
      border-color 460ms ease,
      filter 760ms ease;
    transition-delay: calc(var(--panel-index) * 140ms);
  }

  .signal-panel.side-left {
    --offset-x: -132%;
  }

  .signal-panel.side-right {
    --offset-x: 132%;
    justify-self: end;
  }

  .summary-panel.is-empty {
    opacity: 0.82;
  }

  .summary-panel {
    margin-block-end: clamp(0.8rem, 1.8vh, 1.4rem);
  }

  .panel-head {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 0.4rem;
    margin-block-end: 0;
  }

  .head-text {
    min-width: 0;
  }

  .panel-code {
    margin: 0;
    font-size: 0.63rem;
    color: rgb(var(--hud-text-dim-rgb) / 0.88);
    letter-spacing: 0.12em;
    text-transform: uppercase;
  }

  .panel-title {
    margin: 0.12rem 0 0;
    font-size: 1.08rem;
    color: rgb(var(--hud-text-main-rgb) / 0.96);
    letter-spacing: 0.05em;
  }

  .icon-layer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: flex-end;
    gap: 0.26rem;
  }

  .icon-chip {
    border: 1px solid rgb(var(--hud-border-strong-rgb) / 0.44);
    border-radius: 999px;
    padding: 0.08rem 0.36rem;
    font-size: 0.58rem;
    letter-spacing: 0.08em;
    color: rgb(var(--hud-text-main-rgb) / 0.94);
    background: rgb(var(--hud-surface-rgb) / 0.66);
  }

  .icon-chip.tone-cyan {
    border-color: rgb(var(--hud-cyan-rgb) / 0.54);
  }

  .icon-chip.tone-lime {
    border-color: rgb(var(--hud-lime-rgb) / 0.56);
  }

  .icon-chip.tone-orange {
    border-color: rgb(var(--hud-orange-rgb) / 0.58);
  }

  .chart-stage {
    position: relative;
    block-size: clamp(12rem, 15.5vw, 15rem);
    overflow: hidden;
    border: 1px solid rgb(var(--hud-border-strong-rgb) / 0.32);
    border-radius: 0.62rem;
    background:
      linear-gradient(180deg, rgb(var(--hud-surface-alt-rgb) / 0.68), rgb(var(--hud-surface-deep-rgb) / 0.78)),
      radial-gradient(circle at 50% 0, rgb(var(--hud-glow-rgb) / 0.09), transparent 45%);
  }

  svg {
    display: block;
    inline-size: 100%;
    block-size: 100%;
  }

  .grid-lines line {
    stroke: rgb(var(--hud-border-strong-rgb) / 0.16);
    stroke-width: 0.45;
  }

  .summary-area {
    fill: url(#summary-fill);
  }

  .summary-line {
    fill: none;
    stroke: rgb(var(--hud-cyan-rgb) / 0.96);
    stroke-width: 1.35;
    stroke-linecap: round;
    stroke-linejoin: round;
    filter: drop-shadow(0 0 4px rgb(var(--hud-cyan-rgb) / 0.22));
  }

  .summary-dot {
    fill: rgb(var(--hud-lime-rgb) / 0.98);
    filter: drop-shadow(0 0 6px rgb(var(--hud-lime-rgb) / 0.3));
  }

  .axis-label {
    fill: rgb(var(--hud-text-main-rgb) / 0.88);
    font-size: 3.2px;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-shadow:
      0 1px 0 rgb(0 0 0 / 0.46),
      0 0 4px rgb(0 0 0 / 0.3);
  }

  .y-axis-label {
    fill: rgb(var(--hud-text-dim-rgb) / 0.84);
  }

  .x-axis-label {
    fill: rgb(var(--hud-text-dim-rgb) / 0.9);
  }

  .empty-state {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: rgb(var(--hud-text-dim-rgb) / 0.76);
    font-size: 0.66rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    background: linear-gradient(180deg, rgb(var(--hud-surface-deep-rgb) / 0.06), rgb(var(--hud-surface-deep-rgb) / 0.18));
  }

  .panel-foot {
    display: flex;
    flex-wrap: wrap;
    gap: 0.8rem;
    margin: 0;
    padding: 0;
  }

  .foot-item {
    margin: 0;
    display: inline-flex;
    align-items: center;
    gap: 0.28rem;
    color: rgb(var(--hud-text-main-rgb) / 0.9);
    font-size: 0.76rem;
    letter-spacing: 0.04em;
  }

  .metric-text {
    color: rgb(var(--hud-text-dim-rgb) / 0.82);
    text-transform: uppercase;
  }

  .dot {
    inline-size: 0.34rem;
    block-size: 0.34rem;
    border-radius: 50%;
  }

  .dot.tone-cyan {
    background: rgb(var(--hud-cyan-rgb));
  }

  .dot.tone-lime {
    background: rgb(var(--hud-lime-rgb));
  }

  .dot.tone-orange {
    background: rgb(var(--hud-orange-rgb));
  }

  .value {
    min-inline-size: 2.6rem;
  }

  @media (max-width: 1180px) {
    .signal-panel {
      inline-size: min(100%, clamp(24rem, 36vw, 31rem));
      aspect-ratio: 1.48 / 1;
      min-block-size: 17rem;
    }
  }

  @media (max-height: 900px) {
    .signal-panel {
      inline-size: min(100%, clamp(24rem, 33vw, 30rem));
      min-block-size: 16.8rem;
    }

    .chart-stage {
      block-size: clamp(9.8rem, 12vw, 11.8rem);
    }
  }

  @media (max-height: 760px) {
    .signal-panel {
      inline-size: min(100%, clamp(21rem, 29vw, 26rem));
      min-block-size: 14.4rem;
      padding: 0.7rem 0.76rem 0.8rem;
    }

    .panel-foot {
      margin-block-start: 0.28rem;
    }

    .chart-stage {
      block-size: clamp(8.3rem, 9.6vw, 9.8rem);
    }
  }

  @media (max-height: 680px) {
    .signal-panel {
      inline-size: min(100%, clamp(18.5rem, 24vw, 22.5rem));
      min-block-size: 12.4rem;
      padding: 0.62rem 0.66rem 0.68rem;
    }

    .panel-head {
      margin-block-end: 0.26rem;
    }

    .panel-foot {
      margin-block-start: 0.18rem;
      gap: 0.56rem;
    }

    .chart-stage {
      block-size: clamp(7rem, 7.8vw, 8rem);
    }
  }

  @media (max-width: 900px) {
    .signal-panel {
      inline-size: 100%;
      aspect-ratio: 1.7 / 1;
      min-block-size: 0;
    }
  }
</style>
