<script lang="ts">
  /**
   * The Zwift Click, as two pods (FR-1.4, FR-9.1–9.2).
   *
   * A Click v2 is **two peripherals**, and until now the app showed one line
   * for both: connect, and you got whichever pod answered first, with no way to
   * tell which one that was or that the other was missing entirely. Each pod
   * now has a card of its own — its own state, battery, address and proof that
   * its buttons arrive.
   *
   * They are named for the shift paddle each carries, not for the side of the
   * bar. Nothing a pod advertises says which end of the handlebar it is
   * clamped to, so left and right would be a guess; the paddle is printed on
   * the pod, and pressing it settles the question on screen (`confirmed`).
   *
   * The second job of this panel is to say what to *do* when a pod is missing.
   * A Click sleeps within seconds and only advertises while awake (A-4), which
   * no rider can guess from the words "not connected" — and which is also why
   * connecting is not a button they have to win a race with: the running scan
   * picks a pod up the moment it wakes and connects it (FR-1.5). The buttons
   * here are for overriding that, not for driving it.
   */
  import { app } from '../lib/app.svelte';
  import { api, type Pod, type PodState, type PodStatus } from '../lib/bridge';

  const controller = $derived(app.controller);
  const pods = $derived(controller ? [controller.minus, controller.plus] : []);
  /** Auto-connect rides on the device scan, so a stopped scan is a reason a
   *  pod stays missing — and the fix belongs next to the symptom. */
  const scanning = $derived(app.devices.scanning);
  const anyConnected = $derived(pods.some((p) => p.state === 'connected'));
  const bothConnected = $derived(pods.length === 2 && pods.every((p) => p.state === 'connected'));
  const busy = $derived(pods.some((p) => p.state === 'searching'));
  /** A pod reporting the other's paddle: the pair may be filed the wrong way
   *  round, and the rider is the only one who can say. */
  const mixedUp = $derived(pods.some((p) => p.contradicted));

  const STATE_TEXT: Record<PodState, string> = {
    idle: 'Not connected',
    searching: 'Searching…',
    connected: 'Connected',
    reconnecting: 'Reconnecting…',
    gaveUp: 'Gave up',
  };

  const STATE_TONE: Record<PodState, string> = {
    idle: 'tone-idle',
    searching: 'tone-warn',
    connected: 'tone-ok',
    reconnecting: 'tone-warn',
    gaveUp: 'tone-bad',
  };

  /** What each pod is for, so a rider who has lost one knows what they lost. */
  const PURPOSE: Record<Pod, string> = {
    minus: 'Shift down · D-pad',
    plus: 'Shift up · A B Y Z',
  };

  function connect(pod: Pod) {
    app.run(() => api.connectController(pod));
  }

  function disconnect(pod: Pod) {
    app.run(() => api.disconnectController(pod));
  }
</script>

<section class="click">
  <header>
    <h2>Zwift Click</h2>
    <span class="summary" class:tone-ok={bothConnected} class:tone-warn={!bothConnected}>
      {#if bothConnected}
        Both pods connected
      {:else if anyConnected}
        One pod of two
      {:else}
        No pods connected
      {/if}
    </span>
    <div class="actions">
      {#if !scanning}
        <!-- Nothing can be picked up automatically while the scan is off, so
             the way to fix that sits here rather than only in the header. -->
        <button class="btn" onclick={() => app.run(() => api.startScan())}>Start scan</button>
      {:else if !bothConnected}
        <button class="btn" disabled={busy} onclick={() => app.run(() => api.connectController())}>
          {busy ? 'Searching…' : 'Connect now'}
        </button>
      {/if}
      {#if anyConnected}
        <button class="btn ghost" onclick={() => app.run(() => api.disconnectController())}>
          Disconnect both
        </button>
      {/if}
    </div>
  </header>

  <p class="lede">
    {#if !scanning}
      <strong>The scan is off</strong>, so pods will not be picked up. Start it and press a button
      on each pod.
    {:else if bothConnected}
      Both pods are connected and will reconnect on their own if one drops.
    {:else}
      <strong>Press any button on a missing pod.</strong> It only advertises while awake, and the
      running scan connects it as soon as it does — no need to press anything here.
    {/if}
  </p>

  <div class="pods">
    {#each pods as pod (pod.pod)}
      <article class="pod" class:live={pod.state === 'connected'}>
        <div class="title">
          <span class="paddle" class:on={pod.state === 'connected'}>{pod.symbol}</span>
          <span class="what">
            <span class="label">{pod.symbol} pod</span>
            <span class="purpose">{PURPOSE[pod.pod]}</span>
          </span>
          <span class="state {STATE_TONE[pod.state]}">
            <span class="dot"></span>{STATE_TEXT[pod.state]}
          </span>
        </div>

        <dl class="facts">
          <div>
            <dt>Battery</dt>
            <dd>{pod.batteryPercent != null ? `${pod.batteryPercent}%` : '—'}</dd>
          </div>
          <div>
            <dt>Buttons seen</dt>
            <!-- Connected and silent looks exactly like working until you press
                 something, so the count is the honest test of the link. -->
            <dd>
              {pod.buttonsSeen === 0 ? 'none yet' : `${pod.buttonsSeen}`}
              {#if pod.lastButton}<span class="last">· {pod.lastButton}</span>{/if}
            </dd>
          </div>
          <div>
            <dt>Address</dt>
            <dd class="addr">{pod.address ?? '—'}</dd>
          </div>
        </dl>

        {#if pod.confirmed}
          <p class="note tone-ok">Confirmed — this pod sent its own {pod.symbol} paddle.</p>
        {:else if pod.state === 'connected'}
          <p class="note">
            Press the <strong>{pod.symbol} paddle</strong> on this pod to confirm it is the one.
          </p>
        {/if}

        {#if pod.contradicted}
          <p class="note tone-warn">
            This pod sent the other paddle. If the pair is the wrong way round, swap them.
          </p>
        {/if}

        {#if pod.error}
          <!-- Verbatim (FR-9.2). Rust writes these as instructions, not codes. -->
          <p class="note tone-bad">{pod.error}</p>
        {/if}

        <div class="controls">
          {#if pod.state === 'connected' || pod.state === 'reconnecting'}
            <button class="btn ghost" onclick={() => disconnect(pod.pod)}>Disconnect</button>
          {:else if pod.state === 'searching'}
            <button class="btn ghost" onclick={() => disconnect(pod.pod)}>Stop searching</button>
          {:else}
            <button class="btn ghost" onclick={() => connect(pod.pod)}>
              Look for it now
            </button>
          {/if}
        </div>
      </article>
    {/each}

    {#if pods.length === 0}
      <p class="note">Waiting for the controller supervisor…</p>
    {/if}
  </div>

  {#if mixedUp || controller?.swapped}
    <div class="swap">
      <span>
        {#if mixedUp}
          A pod is sending the other pod's paddle — the pair may be filed the wrong way round.
        {:else}
          The pods are swapped from what their advertisement claims.
        {/if}
      </span>
      <button class="btn ghost" onclick={() => app.run(() => api.swapControllerPods())}>
        Swap + / −
      </button>
    </div>
  {/if}

  {#if !bothConnected}
    <!--
      FR-1.8 / FR-3.10. "Not connected" on its own reads as a broken app. Both
      real causes — a sleeping pod and a lapsed unlock — are things only the
      rider can fix, so they are spelled out here rather than left to be
      guessed at.
    -->
    <details class="help" open={!anyConnected}>
      <summary>A pod will not connect — what to try</summary>
      <ol>
        <li>
          <strong>Press any button on the pod.</strong> This is almost always the whole answer.
          A Click sleeps within seconds and only advertises while awake, so a pod that is not
          broadcasting is the normal case, not a fault. The scan below is running: press a
          button and the pod connects itself, usually within a second or two.
        </li>
        <li>
          <strong>Keep the scan on.</strong> Auto-connect works off the device scan, so a
          stopped scan means nothing gets picked up. Restart it above.
        </li>
        <li>
          <strong>Close anything else holding the pod.</strong> One app at a time — Zwift left
          running in the background keeps the link, and this app will never see the pod.
        </li>
        <li>
          <strong>Bring it closer, or charge it.</strong> A flat pod stops advertising
          altogether, and after about thirty failed attempts the app stops chasing it and says
          so on the card.
        </li>
      </ol>
      <p class="fallback">
        Meanwhile the keyboard mirrors every Click action — <span class="kbd">+</span>
        <span class="kbd">−</span> shift, <span class="kbd">↑</span>
        <span class="kbd">↓</span> trim the gradient. Press
        <span class="kbd">?</span> for the full list. A ride never depends on a pod.
      </p>
    </details>
  {/if}
</section>

<style>
  .click {
    margin: 0 var(--edge) 0.8rem;
    padding: 0.9rem 1rem 1rem;
    border-radius: 0.7rem;
    background: var(--bg-lift);
  }

  header {
    display: flex;
    align-items: baseline;
    gap: 0.75rem;
    flex-wrap: wrap;
  }

  h2 {
    margin: 0;
    font-size: 1.05rem;
    font-weight: 600;
    letter-spacing: -0.01em;
  }

  .summary {
    font-size: 0.85rem;
    font-weight: 600;
  }

  .lede {
    margin: 0.55rem 0 0;
    font-size: 0.86rem;
    line-height: 1.5;
    color: var(--ink-soft);
  }

  .lede strong {
    color: var(--ink);
  }

  .actions {
    display: flex;
    gap: 0.4rem;
    margin-left: auto;
  }

  .pods {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
    gap: 0.6rem;
    margin-top: 0.8rem;
  }

  .pod {
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
    padding: 0.8rem 0.9rem;
    border-radius: 0.55rem;
    border: 1px solid var(--hairline);
  }

  .pod.live {
    border-color: color-mix(in srgb, var(--ok) 35%, transparent);
  }

  .title {
    display: flex;
    align-items: center;
    gap: 0.65rem;
  }

  /* The paddle glyph is the pod's identity — big enough to match against the
     one printed on the hardware at arm's length. */
  .paddle {
    display: grid;
    place-items: center;
    width: 2.1rem;
    height: 2.1rem;
    border-radius: 0.45rem;
    background: var(--hairline);
    color: var(--ink-dim);
    font-size: 1.3rem;
    font-weight: 300;
    line-height: 1;
    flex: none;
  }

  .paddle.on {
    background: color-mix(in srgb, var(--ok) 18%, transparent);
    color: var(--ok);
  }

  .what {
    display: flex;
    flex-direction: column;
    min-width: 0;
  }

  .label {
    font-size: 1rem;
    font-weight: 600;
  }

  .purpose {
    font-size: 0.78rem;
    color: var(--ink-dim);
  }

  .state {
    display: inline-flex;
    align-items: center;
    gap: 0.4em;
    margin-left: auto;
    font-size: 0.88rem;
    font-weight: 600;
    white-space: nowrap;
  }

  .facts {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.4rem;
    margin: 0;
  }

  .facts div {
    display: flex;
    flex-direction: column;
    gap: 0.1rem;
    min-width: 0;
  }

  dt {
    font-size: 0.72rem;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--ink-dim);
  }

  dd {
    margin: 0;
    font-size: 0.9rem;
    color: var(--ink-soft);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .last {
    color: var(--ink-dim);
  }

  .addr {
    font-size: 0.78rem;
    color: var(--ink-dim);
  }

  .note {
    margin: 0;
    font-size: 0.84rem;
    color: var(--ink-soft);
    line-height: 1.45;
  }

  .note strong {
    color: var(--ink);
  }

  .controls {
    display: flex;
    gap: 0.4rem;
    margin-top: auto;
    padding-top: 0.15rem;
  }

  .swap {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin-top: 0.6rem;
    padding: 0.55rem 0.75rem;
    border-radius: 0.5rem;
    background: rgba(255, 207, 74, 0.07);
    color: var(--ink-soft);
    font-size: 0.85rem;
  }

  .swap button {
    margin-left: auto;
  }

  .help {
    margin-top: 0.7rem;
    font-size: 0.86rem;
    color: var(--ink-soft);
  }

  summary {
    cursor: pointer;
    color: var(--ink-soft);
    font-weight: 600;
  }

  .help ol {
    margin: 0.5rem 0 0;
    padding-left: 1.2rem;
    line-height: 1.6;
  }

  .help li {
    margin-bottom: 0.4rem;
  }

  .help strong {
    color: var(--ink);
  }

  .fallback {
    margin: 0.5rem 0 0;
    color: var(--ink-dim);
    line-height: 1.6;
  }
</style>
