<script lang="ts">
  import { pageTitle } from '$lib/branding';
  import { Disc3, Album, Music2, Check, X, SlidersHorizontal } from 'lucide-svelte';
  import { useQueryClient } from '@tanstack/svelte-query';
  import RowActionsMenu, { type RowAction } from '$lib/components/RowActionsMenu.svelte';
  import {
    createAdminRequestsQuery,
    createQualityProfilesQuery,
    createRootFoldersQuery,
    approveRequest,
    rejectRequest
  } from '$lib/api/admin';
  import { errMessage } from '$lib/api/errors';
  import { pushToast } from '$lib/stores/toast.svelte';
  import StatusPill from '$lib/components/StatusPill.svelte';
  import Modal from '$lib/components/Modal.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>('');

  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) {
      pushToast(errMessage(e), 'error');
    }
  }

  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) {
      pushToast(errMessage(e), 'error');
    }
  }

  // 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>

<svelte:head><title>{pageTitle('Admin · Requests')}</title></svelte:head>

<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'}
            {@const primary: RowAction = {
              icon: Check,
              label: 'Approve',
              ariaLabel: `Approve ${rowAccessibleName(r)}`,
              onclick: () => onApprove(r)
            }}
            {@const secondary: RowAction[] = [
              {
                icon: SlidersHorizontal,
                label: 'Override',
                ariaLabel: `Override ${rowAccessibleName(r)}`,
                onclick: () => openOverride(r)
              },
              {
                icon: X,
                label: 'Reject',
                ariaLabel: `Reject ${rowAccessibleName(r)}`,
                onclick: () => openReject(r),
                danger: true
              }
            ]}
            <div class="shrink-0">
              <RowActionsMenu {primary} {secondary} />
            </div>
          {/if}
        </li>
      {/each}
    </ul>
  {/if}
</div>

<Modal
  title="Approve with override"
  open={overrideRow !== null}
  onClose={cancelOverride}
>
  <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-action-fg"
      onclick={() => overrideRow && confirmOverride(overrideRow)}
    >
      <Check size={14} strokeWidth={2} />
      Approve with override
    </button>
  </div>
</Modal>

<Modal
  title="Reject request?"
  open={rejectRow !== null}
  onClose={cancelReject}
>
  <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-action-fg"
      onclick={() => rejectRow && confirmReject(rejectRow)}
    >
      <X size={14} strokeWidth={2} />
      Confirm reject
    </button>
  </div>
</Modal>

<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>
