<script lang="ts">
  import { onMount } from 'svelte';
  import { MonitorSmartphone, TriangleAlert } from 'lucide-svelte';
  import {
    listSessions,
    revokeSession,
    revokeOtherSessions,
    type ActiveSession
  } from '$lib/api/me';
  import { errCode } from '$lib/api/errors';
  import { pushToast } from '$lib/stores/toast.svelte';

  // Self-contained: nothing else in the app reads this data, so it holds its
  // own state and reloads explicitly rather than joining the query cache.

  let sessions = $state<ActiveSession[] | null>(null);
  let loadError = $state(false);
  let busy = $state(false);
  let confirmingLogoutOthers = $state(false);

  const others = $derived((sessions ?? []).filter((s) => !s.current).length);

  async function load() {
    try {
      sessions = await listSessions();
      loadError = false;
    } catch {
      loadError = true;
    }
  }

  onMount(load);

  async function onRevoke(s: ActiveSession) {
    busy = true;
    try {
      await revokeSession(s.id);
      pushToast('Signed that device out.');
      await load();
    } catch (e: unknown) {
      // Already gone — revoked from another device, or expired. Reloading
      // shows the truth, so it isn't worth an error. The code is
      // `session_not_found`: apierror.NotFound("session") prefixes it.
      if (errCode(e) === 'session_not_found') {
        await load();
      } else {
        pushToast("Couldn't sign that device out.", 'error');
      }
    } finally {
      busy = false;
    }
  }

  async function onLogoutOthers() {
    busy = true;
    try {
      const n = await revokeOtherSessions();
      pushToast(n === 1 ? 'Signed out 1 other device.' : `Signed out ${n} other devices.`);
      confirmingLogoutOthers = false;
      await load();
    } catch {
      pushToast("Couldn't sign the other devices out.", 'error');
    } finally {
      busy = false;
    }
  }

  // Deliberately coarse. A full UA parser would be a dependency and a
  // maintenance burden for a string whose only job is "do you recognise
  // this?" — the IP columns carry the actual signal.
  function describeAgent(ua: string): string {
    if (!ua) return 'Unknown device';
    if (/Minstrel/i.test(ua)) return 'Minstrel for Android';
    if (/Android/i.test(ua)) return 'Android browser';
    if (/iPhone|iPad|iOS/i.test(ua)) return 'iOS browser';
    const browser = /Edg\//.test(ua)
      ? 'Edge'
      : /Firefox\//.test(ua)
        ? 'Firefox'
        : /Chrome\//.test(ua)
          ? 'Chrome'
          : /Safari\//.test(ua)
            ? 'Safari'
            : '';
    const os = /Windows/.test(ua)
      ? 'Windows'
      : /Mac OS X/.test(ua)
        ? 'macOS'
        : /Linux/.test(ua)
          ? 'Linux'
          : '';
    if (browser && os) return `${browser} on ${os}`;
    if (browser) return browser;
    return ua.length > 40 ? `${ua.slice(0, 40)}…` : ua;
  }

  function when(iso: string): string {
    const then = new Date(iso).getTime();
    if (Number.isNaN(then)) return 'unknown';
    const mins = Math.round((Date.now() - then) / 60000);
    if (mins < 1) return 'just now';
    if (mins < 60) return `${mins} min ago`;
    const hours = Math.round(mins / 60);
    if (hours < 24) return hours === 1 ? '1 hour ago' : `${hours} hours ago`;
    const days = Math.round(hours / 24);
    if (days < 30) return days === 1 ? 'yesterday' : `${days} days ago`;
    return new Date(iso).toLocaleDateString();
  }

  // The reason IP is stored at all. Rather than making someone eyeball two
  // addresses per row, say plainly when a session is being used from
  // somewhere other than where it was created.
  function hasMoved(s: ActiveSession): boolean {
    return !!s.created_ip && !!s.last_ip && s.created_ip !== s.last_ip;
  }

  function addr(ip: string): string {
    return ip || 'unknown';
  }
</script>

<section class="space-y-3 rounded border border-border bg-surface p-4">
  <h2 class="text-lg font-semibold">Active sessions</h2>
  <p class="text-sm text-text-secondary">
    Every device signed in to your account. If you see one you don't recognise — especially
    one marked as having moved — sign it out and change your password.
  </p>

  {#if loadError}
    <p class="text-sm text-action-destructive">
      Couldn't load your sessions.
      <button type="button" class="underline hover:no-underline" onclick={load}>Try again</button>
    </p>
  {:else if sessions === null}
    <p class="text-sm text-text-secondary">Loading…</p>
  {:else if sessions.length === 0}
    <!-- Practically unreachable: listing requires an authenticated request,
         which means at least one session exists. Handled rather than assumed. -->
    <p class="text-sm text-text-secondary">No active sessions.</p>
  {:else}
    <ul class="divide-y divide-border">
      {#each sessions as s (s.id)}
        <li class="flex items-start gap-3 py-3">
          <MonitorSmartphone
            size={18}
            class="mt-0.5 flex-shrink-0 text-text-secondary"
            aria-hidden="true"
          />
          <div class="min-w-0 flex-1">
            <div class="flex flex-wrap items-center gap-2">
              <span class="font-medium text-text-primary">{describeAgent(s.user_agent)}</span>
              {#if s.current}
                <span class="rounded bg-surface-hover px-1.5 py-0.5 text-xs text-text-secondary">
                  This device
                </span>
              {/if}
              {#if hasMoved(s)}
                <span
                  class="inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-xs text-action-destructive"
                >
                  <TriangleAlert size={12} aria-hidden="true" />
                  Address changed
                </span>
              {/if}
            </div>
            <div class="text-xs text-text-secondary">
              Last seen {when(s.last_seen_at)} from <span class="font-mono">{addr(s.last_ip)}</span>
            </div>
            <div class="text-xs text-text-secondary">
              Signed in {when(s.created_at)} from <span class="font-mono">{addr(s.created_ip)}</span>
            </div>
          </div>
          {#if !s.current}
            <button
              type="button"
              class="flex-shrink-0 rounded border border-border px-2 py-1 text-sm
                     hover:bg-surface-hover focus-visible:ring-2 focus-visible:ring-accent
                     disabled:opacity-50"
              disabled={busy}
              onclick={() => onRevoke(s)}
            >
              Sign out
            </button>
          {/if}
        </li>
      {/each}
    </ul>

    {#if others > 0}
      {#if confirmingLogoutOthers}
        <div class="flex flex-wrap items-center gap-2">
          <span class="text-sm text-text-secondary">
            Sign out {others === 1 ? '1 other device' : `${others} other devices`}? You'll stay
            signed in here.
          </span>
          <button
            type="button"
            class="rounded bg-action-destructive px-3 py-1 text-sm text-action-fg
                   focus-visible:ring-2 focus-visible:ring-accent disabled:opacity-50"
            disabled={busy}
            onclick={onLogoutOthers}
          >
            Sign them out
          </button>
          <button
            type="button"
            class="rounded border border-border px-3 py-1 text-sm hover:bg-surface-hover
                   focus-visible:ring-2 focus-visible:ring-accent"
            onclick={() => (confirmingLogoutOthers = false)}
          >
            Cancel
          </button>
        </div>
      {:else}
        <button
          type="button"
          class="rounded border border-border px-3 py-1 text-sm hover:bg-surface-hover
                 focus-visible:ring-2 focus-visible:ring-accent disabled:opacity-50"
          disabled={busy}
          onclick={() => (confirmingLogoutOthers = true)}
        >
          Sign out all other devices
        </button>
      {/if}
    {/if}
  {/if}
</section>
