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

  export let spatialForce: HudSpatialForce | null = null;
  export let side: "left" | "right" = "right";
  export let panelIndex = 0;
  export let locale: "zh-CN" | "en-US" = "zh-CN";

  function formatValue(value: number | null, digits = 1): string {
    if (value === null || !Number.isFinite(value)) {
      return "--";
    }

    return value.toFixed(digits);
  }

  function normalizeAngle(value: number): number {
    return ((value % 360) + 360) % 360;
  }

  function shortestAngleDelta(from: number, to: number): number {
    const delta = ((to - from + 540) % 360) - 180;
    return delta === -180 ? 180 : delta;
  }

  const jumpAngleThresholdDeg = 72;

  let visualAngleDeg = 0;
  let previousRawAngleDeg: number | null = null;
  let snapVector = false;
  let snapResetFrame: number | null = null;

  function setSnapVector(): void {
    snapVector = true;

    if (typeof window === "undefined") {
      return;
    }

    if (snapResetFrame !== null) {
      window.cancelAnimationFrame(snapResetFrame);
    }

    snapResetFrame = window.requestAnimationFrame(() => {
      snapVector = false;
      snapResetFrame = null;
    });
  }

  function updateVisualAngle(rawAngleDeg: number, active: boolean): void {
    if (!active) {
      previousRawAngleDeg = null;
      visualAngleDeg = 0;
      return;
    }

    if (previousRawAngleDeg === null) {
      previousRawAngleDeg = rawAngleDeg;
      visualAngleDeg = rawAngleDeg;
      return;
    }

    const delta = shortestAngleDelta(previousRawAngleDeg, rawAngleDeg);
    if (Math.abs(delta) < 0.001) {
      return;
    }

    if (Math.abs(delta) >= jumpAngleThresholdDeg) {
      setSnapVector();
    }

    visualAngleDeg += delta;
    previousRawAngleDeg = rawAngleDeg;
  }

  $: i18n =
    locale === "zh-CN"
      ? {
          title: "切向力方向",
          waiting: "等待数据",
          angle: "ANGLE",
          heading: "方向角",
          strength: "强度",
          confidence: "置信度"
        }
      : {
          title: "Tangential Direction",
          waiting: "Waiting",
          angle: "ANGLE",
          heading: "Heading",
          strength: "Strength",
          confidence: "Confidence"
        };

  $: hasData =
    spatialForce !== null &&
    Number.isFinite(spatialForce.angleDeg) &&
    Number.isFinite(spatialForce.magnitude);
  $: angleDeg = hasData ? normalizeAngle(spatialForce?.angleDeg ?? 0) : 0;
  $: updateVisualAngle(angleDeg, hasData);
  $: magnitude = hasData ? spatialForce?.magnitude ?? 0 : null;
  $: confidence = hasData ? (spatialForce?.confidence ?? 0) * 100 : null;
</script>

<article
  class="signal-panel spatial-panel side-{side}"
  class:is-empty={!hasData}
  aria-hidden={false}
  style="--panel-index: {panelIndex};"
>
  <header class="panel-head">
    <div class="head-text">
      <p class="panel-code">TAN</p>
      <p class="panel-title">{i18n.title}</p>
    </div>

    <div class="icon-layer" aria-hidden="true">
      <span class="icon-chip tone-cyan">{i18n.angle}</span>
    </div>
  </header>

  <div class="panel-body">
    <div class="compass-stage">
      <div class="compass-core">
        <div class="compass-ring compass-ring-outer"></div>
        <div class="compass-ring compass-ring-inner"></div>
        <div class="compass-axis axis-horizontal"></div>
        <div class="compass-axis axis-vertical"></div>
        {#if hasData}
          <div
            class="compass-vector"
            class:is-snap={snapVector}
            style="transform: translateY(-50%) rotate({-visualAngleDeg}deg);"
          >
            <span class="vector-shaft"></span>
            <span class="vector-head"></span>
          </div>
        {/if}
        <div class="compass-center"></div>
        <span class="compass-label label-top">90</span>
        <span class="compass-label label-right">0</span>
        <span class="compass-label label-bottom">270</span>
        <span class="compass-label label-left">180</span>
      </div>

      {#if !hasData}
        <div class="empty-state">
          <span>{i18n.waiting}</span>
        </div>
      {/if}
    </div>

    <div class="angle-stage">
      <p class="angle-label">{i18n.heading}</p>
      <p class="angle-meta">{i18n.strength}: {formatValue(magnitude, 2)}</p>
      <p class="angle-meta">{i18n.confidence}: {hasData ? `${formatValue(confidence, 0)}%` : "--"}</p>
    </div>
  </div>
</article>

<style>
  .signal-panel {
    --offset-x: 12%;
    --enter-ms: 1800ms;
    --fade-ms: 1000ms;
    overflow: hidden;
    inline-size: min(100%, clamp(34rem, 44vw, 44rem));
    justify-self: start;
    display: grid;
    grid-template-rows: auto 1fr;
    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;
  }

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

  .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);
  }

  .panel-body {
    display: grid;
    grid-template-columns: minmax(0, 1.1fr) minmax(10rem, 0.9fr);
    gap: 0.72rem;
    block-size: clamp(12rem, 15.5vw, 15rem);
    min-block-size: clamp(12rem, 15.5vw, 15rem);
  }

  .compass-stage {
    position: relative;
    min-block-size: 0;
    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%);
    display: grid;
    place-items: center;
  }

  .compass-core {
    position: relative;
    inline-size: min(72%, 13rem);
    aspect-ratio: 1;
  }

  .compass-ring,
  .compass-axis,
  .compass-center,
  .compass-vector {
    position: absolute;
    top: 50%;
    left: 50%;
  }

  .compass-ring {
    border-radius: 50%;
    transform: translate(-50%, -50%);
  }

  .compass-ring-outer {
    inline-size: 100%;
    block-size: 100%;
    border: 1px solid rgb(var(--hud-cyan-rgb) / 0.28);
    box-shadow: 0 0 18px rgb(var(--hud-glow-rgb) / 0.08);
  }

  .compass-ring-inner {
    inline-size: 62%;
    block-size: 62%;
    border: 1px dashed rgb(var(--hud-border-strong-rgb) / 0.3);
  }

  .compass-axis {
    background: rgb(var(--hud-border-strong-rgb) / 0.18);
    transform: translate(-50%, -50%);
  }

  .axis-horizontal {
    inline-size: 86%;
    block-size: 1px;
  }

  .axis-vertical {
    inline-size: 1px;
    block-size: 86%;
  }

  .compass-vector {
    inline-size: 42%;
    block-size: 0.9rem;
    transform-origin: 0 50%;
    transition: transform 220ms ease;
  }

  .compass-vector.is-snap {
    transition-duration: 0ms;
  }

  .vector-shaft {
    position: absolute;
    top: 50%;
    left: 0;
    right: 0.7rem;
    block-size: 2px;
    transform: translateY(-50%);
    border-radius: 999px;
    background: linear-gradient(90deg, rgb(var(--hud-cyan-rgb) / 0.18), rgb(var(--hud-cyan-rgb) / 0.96));
    box-shadow: 0 0 14px rgb(var(--hud-cyan-rgb) / 0.2);
  }

  .vector-head {
    position: absolute;
    top: 50%;
    right: 0;
    inline-size: 0;
    block-size: 0;
    transform: translateY(-50%);
    border-top: 0.36rem solid transparent;
    border-bottom: 0.36rem solid transparent;
    border-left: 0.7rem solid rgb(var(--hud-lime-rgb) / 0.96);
    filter: drop-shadow(0 0 8px rgb(var(--hud-lime-rgb) / 0.24));
  }

  .compass-center {
    inline-size: 0.56rem;
    block-size: 0.56rem;
    transform: translate(-50%, -50%);
    border-radius: 50%;
    background: rgb(var(--hud-text-main-rgb) / 0.92);
    box-shadow: 0 0 10px rgb(var(--hud-text-main-rgb) / 0.12);
  }

  .compass-label {
    position: absolute;
    font-size: 0.58rem;
    color: rgb(var(--hud-text-dim-rgb) / 0.8);
    letter-spacing: 0.08em;
  }

  .label-top {
    top: -0.9rem;
    left: 50%;
    transform: translateX(-50%);
  }

  .label-right {
    top: 50%;
    right: -1rem;
    transform: translateY(-50%);
  }

  .label-bottom {
    bottom: -0.9rem;
    left: 50%;
    transform: translateX(-50%);
  }

  .label-left {
    top: 50%;
    left: -1.35rem;
    transform: translateY(-50%);
  }

  .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));
  }

  .angle-stage {
    border: 1px solid rgb(var(--hud-border-rgb) / 0.26);
    border-radius: 0.62rem;
    padding: 0.9rem 0.85rem;
    block-size: 100%;
    min-block-size: 0;
    overflow: hidden;
    background:
      linear-gradient(180deg, rgb(var(--hud-surface-rgb) / 0.72), rgb(var(--hud-surface-deep-rgb) / 0.84)),
      radial-gradient(circle at 50% 0, rgb(var(--hud-glow-rgb) / 0.05), transparent 58%);
    display: grid;
    grid-template-rows: auto auto auto;
    align-content: center;
    justify-items: start;
    gap: 0.36rem;
  }

  .angle-label {
    margin: 0;
    color: rgb(var(--hud-text-dim-rgb) / 0.82);
    font-size: 0.68rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
  }

  .angle-meta {
    margin: 0;
    inline-size: 10rem;
    min-block-size: 1rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    color: rgb(var(--hud-text-dim-rgb) / 0.84);
    font-size: 0.68rem;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.05em;
  }

  @media (max-width: 1180px) {
    .signal-panel {
      inline-size: min(100%, clamp(28rem, 40vw, 38rem));
    }
  }

  @media (max-height: 900px) {
    .signal-panel {
      inline-size: min(100%, clamp(28rem, 38vw, 36rem));
      padding: 0.7rem 0.76rem 0.8rem;
    }
  }

  @media (max-height: 760px) {
    .signal-panel {
      inline-size: min(100%, clamp(24rem, 34vw, 30rem));
      padding: 0.62rem 0.68rem 0.72rem;
      gap: 0.48rem;
    }

    .panel-body {
      block-size: clamp(9rem, 10vw, 10.8rem);
      min-block-size: clamp(9rem, 10vw, 10.8rem);
    }
  }

  @media (max-height: 680px) {
    .signal-panel {
      inline-size: min(100%, clamp(20rem, 28vw, 26rem));
      padding: 0.52rem 0.58rem 0.6rem;
      gap: 0.36rem;
    }
  }

  @media (max-width: 900px) {
    .signal-panel {
      inline-size: 100%;
    }

    .panel-body {
      grid-template-columns: 1fr;
      block-size: auto;
      min-block-size: auto;
    }

    .compass-core {
      inline-size: min(58vw, 12rem);
    }
  }
</style>
