<script lang="ts">
  import { snap } from "../stores/snapshot.svelte";
  import { selectUser, selectWorker } from "../stores/selection.svelte";
  import {
    formatHashrate,
    formatDifficulty,
    formatAgo,
    detectHardware,
    isOpenSource,
    btcAddressOf,
  } from "../format";
  import type { StratumClient, Worker } from "../types";

  type Row = {
    workerName: string;
    btcAddress: string;
    sourceIp: string;
    hardware: string;
    openSource: boolean;
    tls: boolean;
    hashrate1m: number;
    hashrate1h: number;
    bestSession: number;
    bestEver: number;
    lastShare: number;
    difficulty: number;
    idle: boolean;
    online: boolean;
  };

  // The "address" field on a ckpool stratum_instance is the SOURCE IP
  // the connection arrived on (see connector.c:311 — it's set with
  // inet_ntop). The miner's BTC payout address comes from the worker
  // name (which is the stratum username, format <btc>[.label]) or
  // from the joined worker_instance, which ckpool keys by full
  // workername with user = the BTC.
  //
  // ckpool's serverurl array gives us a second piece of info: TLS
  // traffic comes in via stunnel on the loopback-only bind (server
  // index 1), so client.server === 1 means this miner is using TLS.
  const rows = $derived.by<Row[]>(() => {
    const clients = snap.data?.clients ?? [];
    const workers = snap.data?.workers ?? [];
    const byWorker = new Map<string, Worker>();
    for (const w of workers) byWorker.set(w.worker, w);

    const seen = new Set<string>();
    const out: Row[] = [];

    for (const c of clients as StratumClient[]) {
      const wname = c.workername || `${c.address}.unnamed`;
      seen.add(wname);
      const w = byWorker.get(wname);
      const btcAddress = w?.user ?? btcAddressOf(wname);
      out.push({
        workerName: wname,
        btcAddress,
        sourceIp: c.address,
        hardware: detectHardware(c.useragent),
        openSource: isOpenSource(c.useragent),
        tls: c.server === 1,
        hashrate1m: c.dsps1 * 2 ** 32,
        hashrate1h: c.dsps60 * 2 ** 32,
        bestSession: c.bestdiff,
        bestEver: w?.bestever ?? 0,
        lastShare: c.lastshare,
        difficulty: c.diff,
        idle: c.idle,
        online: true,
      });
    }

    for (const w of workers) {
      if (seen.has(w.worker)) continue;
      out.push({
        workerName: w.worker,
        btcAddress: w.user,
        sourceIp: "",
        hardware: "offline",
        openSource: false,
        tls: false,
        hashrate1m: 0,
        hashrate1h: 0,
        bestSession: 0,
        bestEver: w.bestever,
        lastShare: w.lastshare,
        difficulty: w.mindiff,
        idle: w.idle,
        online: false,
      });
    }

    out.sort((a, b) => b.hashrate1h - a.hashrate1h);
    return out;
  });
</script>

<section class="card">
  <h2>Miners</h2>
  {#if rows.length === 0}
    <div class="empty">No miners connected.</div>
  {:else}
    <div class="table-wrap">
      <table>
        <thead>
          <tr>
            <th>Worker</th>
            <th>Hardware</th>
            <th class="num">Hashrate (1m)</th>
            <th class="num">Hashrate (1h)</th>
            <th class="num">Diff</th>
            <th class="num">Best (session)</th>
            <th class="num">Best (ever)</th>
            <th class="num">Last share</th>
          </tr>
        </thead>
        <tbody>
          {#each rows as r (r.workerName)}
            <tr class:offline={!r.online} class:idle={r.idle}>
              <td>
                <div class="wname mono">
                  <button
                    type="button"
                    class="worker-btn"
                    onclick={() => selectWorker(r.workerName)}
                    title="View per-worker stats"
                  >{r.workerName}</button>
                  {#if r.tls}
                    <span class="badge tls" title="Connected via TLS">
                      <svg viewBox="0 0 16 16" aria-hidden="true">
                        <path d="M8 1a3.5 3.5 0 0 0-3.5 3.5V7H4a1.5 1.5 0 0 0-1.5 1.5v5A1.5 1.5 0 0 0 4 15h8a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 12 7h-.5V4.5A3.5 3.5 0 0 0 8 1Zm2 6H6V4.5a2 2 0 1 1 4 0V7Z"/>
                      </svg>
                      <span>TLS</span>
                    </span>
                  {/if}
                </div>
                <button
                  type="button"
                  class="addr-btn mono"
                  onclick={() => selectUser(r.btcAddress)}
                  title="View per-user stats"
                >{r.btcAddress}</button>
                {#if r.sourceIp}
                  <div class="src-ip mono" title="Source IP">{r.sourceIp}</div>
                {/if}
              </td>
              <td>
                {r.hardware}
                {#if r.openSource}
                  <span class="oss" title="Open source hardware or firmware">★</span>
                {/if}
              </td>
              <td class="num">{formatHashrate(r.hashrate1m)}</td>
              <td class="num">{formatHashrate(r.hashrate1h)}</td>
              <td class="num">{formatDifficulty(r.difficulty)}</td>
              <td class="num">{formatDifficulty(r.bestSession)}</td>
              <td class="num">{formatDifficulty(r.bestEver)}</td>
              <td class="num">{formatAgo(r.lastShare)}</td>
            </tr>
          {/each}
        </tbody>
      </table>
    </div>
  {/if}
</section>

<style>
  h2 {
    margin: 0 0 1rem;
    font-size: 1.05rem;
    font-weight: 600;
  }
  .table-wrap {
    overflow-x: auto;
  }
  .empty {
    color: var(--fg-dim);
    padding: 1rem 0;
  }
  .wname {
    font-size: 0.95em;
    display: flex;
    align-items: center;
    gap: 0.4em;
    flex-wrap: wrap;
  }
  .worker-btn {
    font: inherit;
    color: inherit;
    background: transparent;
    border: none;
    padding: 0;
    cursor: pointer;
    text-align: left;
    border-bottom: 1px dashed transparent;
    transition: color 0.15s, border-bottom-color 0.15s;
  }
  .worker-btn:hover,
  .worker-btn:focus-visible {
    color: var(--accent);
    border-bottom-color: var(--accent);
    outline: none;
  }
  .addr-btn {
    display: inline-block;
    color: var(--accent);
    font-size: 0.82em;
    background: transparent;
    border: none;
    padding: 0;
    margin-top: 0.1em;
    cursor: pointer;
    text-align: left;
    text-decoration: none;
    border-bottom: 1px dashed var(--accent-dim);
    transition: color 0.15s, border-bottom-color 0.15s;
  }
  .addr-btn:hover,
  .addr-btn:focus-visible {
    color: #ff9a5f;
    border-bottom-color: var(--accent);
    background: transparent;
    outline: none;
  }
  .src-ip {
    color: var(--fg-dim);
    font-size: 0.72em;
    margin-top: 0.2em;
  }
  .badge.tls {
    display: inline-flex;
    align-items: center;
    gap: 0.3em;
    font-size: 0.72em;
    font-weight: 600;
    letter-spacing: 0.04em;
    color: var(--good);
    border: 1px solid #3a7558;
    background: #112e22;
    padding: 0.15em 0.5em;
    border-radius: 4px;
    text-transform: uppercase;
    line-height: 1;
    vertical-align: middle;
  }
  .badge.tls svg {
    width: 0.9em;
    height: 0.9em;
    fill: currentColor;
    flex-shrink: 0;
  }
  .oss {
    color: var(--accent);
    font-size: 0.95em;
    margin-left: 0.3em;
    cursor: help;
  }
  tr.offline {
    opacity: 0.4;
  }
  tr.idle td {
    color: var(--fg-dim);
  }
</style>
