<script lang="ts">
  import { Disc3, Album, Music2, Check, X, SlidersHorizontal } from 'lucide-svelte';
  import { useQueryClient } from '@tanstack/svelte-query';
  import {
    createAdminRequestsQuery,
    createQualityProfilesQuery,
    createRootFoldersQuery,
    approveRequest,
    rejectRequest
  } from '$lib/api/admin';
  import { copyForCode } from '$lib/api/error-copy';
  import StatusPill from '$lib/components/StatusPill.svelte';
  import type { LidarrRequest, LidarrRequestKind, LidarrRequestStatus } from '$lib/api/types';

  // Approval queue. Tabs filter by status; rows expose Override/Approve/Reject
  // per spec. The override modal collapses by default — most requests get the
  // snapshot defaults, so we don't make the operator stare at two dropdowns
  // they almost never touch.

  const client = useQueryClient();

  // The four tab filters. "Pending" is default — that's the actionable bucket;
  // the rest are review-only views.
  const tabs: { status: LidarrRequestStatus; label: string }[] = [
    { status: 'pending',   label: 'Pending'   },
    { status: 'approved',  label: 'Approved'  },
    { status: 'completed', label: 'Completed' },
    { status: 'rejected',  label: 'Rejected'  }
  ];

  let activeStatus = $state<LidarrRequestStatus>('pending');

  // The query factory is re-created on every status flip so TanStack treats
  // each status as its own cache key. We don't need a single shared store.
  const queryStore = $derived(createAdminRequestsQuery(activeStatus));
  const query = $derived($queryStore);
  const rows = $derived((query.data ?? []) as LidarrRequest[]);

  // Quality + root dropdowns for the override modal. Always enabled; if Lidarr
  // is disabled the modal is unreachable from this page anyway (the queue
  // would be empty), and on this page we'd rather see the dropdown options
  // than block the modal on a separate gate.
  const profilesStore = createQualityProfilesQuery(true);
  const profiles = $derived($profilesStore);
  const foldersStore = createRootFoldersQuery(true);
  const folders = $derived($foldersStore);

  // Modal state. `overrideOpen` and `rejectOpen` carry the request id when
  // open; null otherwise. The current row is looked up by id at render time.
  let overrideOpen = $state<string | null>(null);
  let qualityOverride = $state<number | ''>('');
  let rootOverride = $state<string>('');

  let rejectOpen = $state<string | null>(null);
  let rejectNotes = $state<string>('');

  // Toast surface — single line, fixed bottom-right, auto-clears after 5s.
  // No third-party lib for v1; this is enough to surface the lidarr-unreachable
  // error per spec §7 without dragging in a toast framework.
  let toast = $state<string | null>(null);
  let toastTimer: ReturnType<typeof setTimeout> | null = null;

  function showToast(msg: string) {
    if (toastTimer) clearTimeout(toastTimer);
    toast = msg;
    toastTimer = setTimeout(() => {
      toast = null;
    }, 5000);
  }

  function fallbackIcon(kind: LidarrRequestKind) {
    if (kind === 'artist') return Disc3;
    if (kind === 'album') return Album;
    return Music2;
  }

  function rowTitle(r: LidarrRequest): string {
    if (r.kind === 'artist') return r.artist_name;
    if (r.kind === 'album')  return r.album_title ?? '—';
    return r.track_title ?? '—';
  }

  function rowAccessibleName(r: LidarrRequest): string {
    if (r.kind === 'artist') return r.artist_name;
    if (r.kind === 'album')  return r.album_title  ?? `this album by ${r.artist_name}`;
    return r.track_title ?? `this track by ${r.artist_name}`;
  }

  // The id is a UUID — first 8 chars is plenty to disambiguate without a real
  // username, and matches what the polish pass will replace with the real
  // username surface.
  function rowMeta(r: LidarrRequest): string {
    const when = new Date(r.requested_at).toLocaleDateString();
    const userBit = `by user ${r.user_id.slice(0, 8)}`;
    return `${userBit} · ${when}`;
  }

  async function invalidate() {
    // Invalidate every status bucket so e.g. an approve flips a row from the
    // pending list to the approved list once the operator switches tabs.
    await client.invalidateQueries({ queryKey: ['adminRequests'] });
  }

  async function onApprove(
    r: LidarrRequest,
    overrides?: { quality_profile_id?: number; root_folder_path?: string }
  ) {
    try {
      if (overrides && (overrides.quality_profile_id !== undefined || overrides.root_folder_path !== undefined)) {
        await approveRequest(r.id, overrides);
      } else {
        await approveRequest(r.id);
      }
      await invalidate();
    } catch (e) {
      const code = (e as { code?: string }).code ?? 'unknown';
      showToast(copyForCode(code));
    }
  }

  function openOverride(r: LidarrRequest) {
    overrideOpen = r.id;
    // Reset to "Use defaults" each time — operators should consciously opt in
    // to an override, not inherit the previous row's choice.
    qualityOverride = '';
    rootOverride = '';
  }

  function cancelOverride() {
    overrideOpen = null;
  }

  async function confirmOverride(r: LidarrRequest) {
    const overrides: { quality_profile_id?: number; root_folder_path?: string } = {};
    if (qualityOverride !== '') overrides.quality_profile_id = Number(qualityOverride);
    if (rootOverride !== '')    overrides.root_folder_path   = rootOverride;
    overrideOpen = null;
    await onApprove(r, overrides);
  }

  function openReject(r: LidarrRequest) {
    rejectOpen = r.id;
    rejectNotes = '';
  }

  function cancelReject() {
    rejectOpen = null;
  }

  async function confirmReject(r: LidarrRequest) {
    const notes = rejectNotes.trim();
    rejectOpen = null;
    try {
      await rejectRequest(r.id, notes ? notes : undefined);
      await invalidate();
    } catch (e) {
      const code = (e as { code?: string }).code ?? 'unknown';
      showToast(copyForCode(code));
    }
  }

  // Modal lookups: if the active tab has been refetched while a modal is
  // open, the row may no longer be in `rows`. Guard against that by treating
  // missing-row as closed.
  const overrideRow = $derived(
    overrideOpen ? rows.find((r) => r.id === overrideOpen) ?? null : null
  );
  const rejectRow = $derived(
    rejectOpen ? rows.find((r) => r.id === rejectOpen) ?? null : null
  );
</script>

<div class="space-y-6">
  <header class="space-y-1">
    <h2 class="font-display text-2xl font-medium text-text-primary">Requests</h2>
    <p class="text-text-secondary">Approve or set aside what users have asked for.</p>
  </header>

  <nav aria-label="Request status filters" class="border-b border-border">
    <ul class="flex gap-2">
      {#each tabs as tab (tab.status)}
        {@const isActive = activeStatus === tab.status}
        <li>
          <button
            type="button"
            role="tab"
            aria-selected={isActive}
            class="border-b-2 px-3 py-2 text-sm transition-colors {isActive
              ? 'border-accent text-text-primary'
              : 'border-transparent text-text-secondary hover:text-text-primary'}"
            onclick={() => (activeStatus = tab.status)}
          >
            {tab.label}
            {#if isActive}
              <span
                class="ml-1.5 inline-flex items-center rounded-full bg-accent-tint px-2 py-0.5 text-xs text-accent"
                data-testid="active-tab-count"
              >
                {rows.length}
              </span>
            {/if}
          </button>
        </li>
      {/each}
    </ul>
  </nav>

  {#if query.isPending}
    <p class="text-text-secondary">Reading the queue…</p>
  {:else if query.isError}
    <p class="text-error">Couldn't load requests.</p>
  {:else if rows.length === 0}
    <p class="text-text-secondary">Nothing here.</p>
  {:else}
    <ul class="divide-y divide-border rounded-lg border border-border bg-surface">
      {#each rows as r (r.id)}
        {@const Icon = fallbackIcon(r.kind)}
        <li class="flex items-start gap-4 p-3" data-testid="admin-request-row" data-status={r.status}>
          <div
            class="flex h-14 w-14 shrink-0 items-center justify-center rounded-md bg-surface-hover"
            aria-hidden="true"
          >
            <Icon size={24} strokeWidth={1} class="text-text-muted" />
          </div>

          <div class="min-w-0 flex-1 space-y-1">
            <div class="flex flex-wrap items-center gap-2">
              <span class="kind-pill">{r.kind}</span>
              <StatusPill status={r.status} />
            </div>
            <div class="truncate text-base font-medium text-text-primary">
              {rowTitle(r)}
            </div>
            <div class="truncate text-sm text-text-secondary">
              {rowMeta(r)}
            </div>
            {#if r.imported_album_count > 0 || r.imported_track_count > 0}
              <div class="text-sm text-accent" data-testid="ingest-progress">
                {#if r.kind === 'artist'}
                  {r.imported_album_count} {r.imported_album_count === 1 ? 'album' : 'albums'}
                  · {r.imported_track_count} {r.imported_track_count === 1 ? 'track' : 'tracks'} ingested
                {:else if r.kind === 'album'}
                  {r.imported_track_count} {r.imported_track_count === 1 ? 'track' : 'tracks'} ingested
                {:else}
                  Track ingested
                {/if}
              </div>
            {/if}
            {#if r.kind === 'track' && r.album_title}
              <div class="text-sm text-text-secondary" data-testid="track-disclosure">
                Approving will add the album <em class="font-medium text-text-primary">{r.album_title}</em>.
              </div>
            {/if}
            {#if r.notes}
              <div class="text-sm text-text-secondary">{r.notes}</div>
            {/if}
          </div>

          {#if r.status === 'pending'}
            <div class="flex shrink-0 items-center gap-2">
              <button
                type="button"
                aria-label={`Override ${rowAccessibleName(r)}`}
                class="inline-flex items-center gap-1 rounded-md border border-border bg-transparent px-3 py-1.5 text-sm text-text-secondary hover:text-text-primary hover:bg-surface-hover"
                onclick={() => openOverride(r)}
              >
                <SlidersHorizontal size={14} strokeWidth={1.5} />
                Override
              </button>
              <button
                type="button"
                aria-label={`Approve ${rowAccessibleName(r)}`}
                class="inline-flex items-center gap-1 rounded-md bg-action-primary px-3 py-1.5 text-sm text-text-primary"
                onclick={() => onApprove(r)}
              >
                <Check size={14} strokeWidth={2} />
                Approve
              </button>
              <button
                type="button"
                aria-label={`Reject ${rowAccessibleName(r)}`}
                class="inline-flex items-center gap-1 rounded-md bg-action-secondary px-3 py-1.5 text-sm text-text-primary"
                onclick={() => openReject(r)}
              >
                <X size={14} strokeWidth={2} />
                Reject
              </button>
            </div>
          {/if}
        </li>
      {/each}
    </ul>
  {/if}
</div>

{#if overrideRow}
  <!-- svelte-ignore a11y_click_events_have_key_events -->
  <!-- svelte-ignore a11y_no_static_element_interactions -->
  <div
    class="fixed inset-0 z-50 flex items-center justify-center"
    style="background: rgba(0,0,0,0.5);"
    onclick={cancelOverride}
  >
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby="override-title"
      class="w-full max-w-md rounded-xl border border-border bg-surface p-5 shadow-lg"
      onclick={(e) => e.stopPropagation()}
      tabindex="-1"
    >
      <h3 id="override-title" class="font-display text-lg font-medium text-text-primary">
        Approve with override
      </h3>
      <p class="mt-2 text-sm text-text-secondary">
        Leave fields blank to use the saved defaults.
      </p>

      <label class="mt-4 block">
        <span class="block text-sm text-text-secondary">Quality profile</span>
        <select
          bind:value={qualityOverride}
          class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-accent"
        >
          <option value="">Use default</option>
          {#each profiles.data ?? [] as p (p.id)}
            <option value={p.id}>{p.name}</option>
          {/each}
        </select>
      </label>

      <label class="mt-3 block">
        <span class="block text-sm text-text-secondary">Root folder</span>
        <select
          bind:value={rootOverride}
          class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 font-mono text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-accent"
        >
          <option value="">Use default</option>
          {#each folders.data ?? [] as f (f.path)}
            <option value={f.path}>{f.path}{f.accessible ? '' : ' (not accessible)'}</option>
          {/each}
        </select>
      </label>

      <div class="mt-5 flex justify-end gap-2">
        <button
          type="button"
          class="rounded-md border border-border px-3 py-1.5 text-sm text-text-secondary hover:text-text-primary"
          onclick={cancelOverride}
        >
          Cancel
        </button>
        <button
          type="button"
          class="inline-flex items-center gap-1 rounded-md bg-action-primary px-3 py-1.5 text-sm text-text-primary"
          onclick={() => confirmOverride(overrideRow!)}
        >
          <Check size={14} strokeWidth={2} />
          Approve with override
        </button>
      </div>
    </div>
  </div>
{/if}

{#if rejectRow}
  <!-- svelte-ignore a11y_click_events_have_key_events -->
  <!-- svelte-ignore a11y_no_static_element_interactions -->
  <div
    class="fixed inset-0 z-50 flex items-center justify-center"
    style="background: rgba(0,0,0,0.5);"
    onclick={cancelReject}
  >
    <div
      role="dialog"
      aria-modal="true"
      aria-labelledby="reject-title"
      class="w-full max-w-md rounded-xl border border-border bg-surface p-5 shadow-lg"
      onclick={(e) => e.stopPropagation()}
      tabindex="-1"
    >
      <h3 id="reject-title" class="font-display text-lg font-medium text-text-primary">
        Reject request?
      </h3>
      <p class="mt-2 text-sm text-text-secondary">
        Notes are shown to the requester.
      </p>
      <label class="mt-3 block">
        <span class="block text-sm text-text-secondary">Notes</span>
        <textarea
          bind:value={rejectNotes}
          rows="3"
          placeholder="Optional — why this is being set aside"
          class="mt-1 w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-accent"
        ></textarea>
      </label>
      <div class="mt-5 flex justify-end gap-2">
        <button
          type="button"
          class="rounded-md border border-border px-3 py-1.5 text-sm text-text-secondary hover:text-text-primary"
          onclick={cancelReject}
        >
          Cancel
        </button>
        <button
          type="button"
          class="inline-flex items-center gap-1 rounded-md bg-action-secondary px-3 py-1.5 text-sm text-text-primary"
          onclick={() => confirmReject(rejectRow!)}
        >
          <X size={14} strokeWidth={2} />
          Confirm reject
        </button>
      </div>
    </div>
  </div>
{/if}

{#if toast}
  <div
    role="status"
    aria-live="polite"
    class="fixed bottom-4 right-4 z-50 max-w-sm rounded-md border border-border bg-surface px-4 py-3 text-sm text-text-primary shadow-lg"
    data-testid="toast"
  >
    {toast}
  </div>
{/if}

<style>
  .kind-pill {
    display: inline-flex;
    align-items: center;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 11px;
    line-height: 14px;
    background: color-mix(in srgb, var(--fs-accent) 15%, transparent);
    color: var(--fs-accent);
    text-transform: capitalize;
  }
</style>
