<script lang="ts">
  import { goto } from '$app/navigation';
  import { page } from '$app/state';
  import { ApiError } from '$lib/api';
  import { auth } from '$lib/auth.svelte';
  import PriceChart from '$lib/PriceChart.svelte';
  import {
    lists,
    listsApi,
    type Collaborator,
    type Invite,
    type Item,
    type ItemStatus,
    type List,
    type ListRole,
    type PricePoint
  } from '$lib/lists.svelte';

  const id = $derived(page.params.id);

  let list = $state<List | null>(null);
  let items = $state<Item[]>([]);
  let loaded = $state(false);
  let loadError = $state('');

  // Role gates the controls: owners see everything, editors can edit items,
  // crossers can only cross items off.
  const role = $derived<ListRole>(list?.role ?? 'owner');
  const canEdit = $derived(role === 'owner' || role === 'editor');
  const isOwner = $derived(role === 'owner');

  // form
  let title = $state('');
  let url = $state('');
  let note = $state('');
  let targetPrice = $state('');
  let busy = $state(false);
  let formError = $state('');
  let showDetails = $state(false);

  // sharing
  let sharing = $state(false);
  let copied = $state(false);
  const shareUrl = $derived(
    list?.share_token ? `${page.url.origin}/shared/${list.share_token}` : ''
  );

  async function share() {
    if (!list) return;
    sharing = true;
    formError = '';
    try {
      const updated = await listsApi.share(list.id);
      list = updated;
      lists.replace(updated);
    } catch (err) {
      formError = err instanceof ApiError ? err.message : 'failed to share';
    } finally {
      sharing = false;
    }
  }

  async function unshare() {
    if (!list || !confirm('revoke the share link? anyone holding it loses access.')) return;
    sharing = true;
    formError = '';
    try {
      const updated = await listsApi.unshare(list.id);
      list = updated;
      lists.replace(updated);
    } catch (err) {
      formError = err instanceof ApiError ? err.message : 'failed to unshare';
    } finally {
      sharing = false;
    }
  }

  async function copyShare() {
    if (!shareUrl) return;
    try {
      await navigator.clipboard.writeText(shareUrl);
      copied = true;
      setTimeout(() => (copied = false), 1500);
    } catch {
      /* clipboard blocked — the field is selectable as a fallback */
    }
  }

  // cross off / claim
  let claimBusy = $state<string | null>(null);
  async function toggleClaim(item: Item) {
    claimBusy = item.id;
    formError = '';
    try {
      const updated = item.claimed_at
        ? await listsApi.unclaim(item.id)
        : await listsApi.claim(item.id);
      const i = items.findIndex((x) => x.id === item.id);
      if (i >= 0) items[i] = updated;
    } catch (err) {
      formError = err instanceof ApiError ? err.message : 'failed to update';
    } finally {
      claimBusy = null;
    }
  }

  // list settings + collaboration (owner only)
  let showSettings = $state(false);
  let settingsBusy = $state(false);
  let invites = $state<Invite[]>([]);
  let collaborators = $state<Collaborator[]>([]);
  let collabLoaded = $state(false);
  let copiedInvite = $state<string | null>(null);
  const inviteUrl = (token: string) => `${page.url.origin}/invite/${token}`;

  async function loadCollab() {
    if (!list || collabLoaded) return;
    try {
      [invites, collaborators] = await Promise.all([
        listsApi.invites(list.id),
        listsApi.collaborators(list.id)
      ]);
      collabLoaded = true;
    } catch (err) {
      formError = err instanceof ApiError ? err.message : 'failed to load collaborators';
    }
  }

  async function toggleSettings() {
    showSettings = !showSettings;
    if (showSettings) await loadCollab();
  }

  async function toggleGuestCrossoff() {
    if (!list) return;
    settingsBusy = true;
    formError = '';
    try {
      const updated = await listsApi.update(list.id, {
        allow_guest_crossoff: !list.allow_guest_crossoff
      });
      list = updated;
      lists.replace(updated);
    } catch (err) {
      formError = err instanceof ApiError ? err.message : 'failed to save setting';
    } finally {
      settingsBusy = false;
    }
  }

  async function makeInvite(r: 'editor' | 'crosser') {
    if (!list) return;
    settingsBusy = true;
    formError = '';
    try {
      const inv = await listsApi.createInvite(list.id, r);
      invites.push(inv);
    } catch (err) {
      formError = err instanceof ApiError ? err.message : 'failed to create invite';
    } finally {
      settingsBusy = false;
    }
  }

  async function dropInvite(inviteId: string) {
    if (!list) return;
    try {
      await listsApi.revokeInvite(list.id, inviteId);
      invites = invites.filter((i) => i.id !== inviteId);
    } catch (err) {
      formError = err instanceof ApiError ? err.message : 'failed to revoke';
    }
  }

  async function dropCollaborator(userId: string) {
    if (!list || !confirm('remove this collaborator? they lose access immediately.')) return;
    try {
      await listsApi.removeCollaborator(list.id, userId);
      collaborators = collaborators.filter((c) => c.user_id !== userId);
    } catch (err) {
      formError = err instanceof ApiError ? err.message : 'failed to remove';
    }
  }

  async function copyInvite(token: string) {
    try {
      await navigator.clipboard.writeText(inviteUrl(token));
      copiedInvite = token;
      setTimeout(() => (copiedInvite = null), 1500);
    } catch {
      /* clipboard blocked */
    }
  }

  // inline edit
  let editingId = $state<string | null>(null);
  let edit = $state({ title: '', url: '', note: '', target: '', currency: '' });
  let editError = $state('');
  let editBusy = $state(false);

  // tracking
  let refetchingId = $state<string | null>(null);
  let historyFor = $state<string | null>(null);
  let history = $state<PricePoint[]>([]);
  let historyLoading = $state(false);

  let lastId = '';
  $effect(() => {
    if (auth.loaded && !auth.user) {
      goto('/login');
      return;
    }
    if (auth.loaded && auth.user && id && id !== lastId) {
      lastId = id;
      load(id);
    }
  });

  async function load(listId: string) {
    loaded = false;
    loadError = '';
    try {
      if (!lists.loaded) await lists.load();
      list = lists.items.find((l) => l.id === listId) ?? null;
      items = await listsApi.items(listId);
    } catch (e) {
      loadError = e instanceof ApiError ? e.message : 'failed to load';
    } finally {
      loaded = true;
    }
  }

  async function addItem(e: SubmitEvent) {
    e.preventDefault();
    if (!title.trim() || !id) return;
    formError = '';
    busy = true;
    try {
      const tp = targetPrice.trim() ? Number(targetPrice) : null;
      const created = await listsApi.addItem(id, {
        title,
        url: url.trim() || null,
        note: note.trim() || null,
        target_price: Number.isFinite(tp as number) ? tp : null
      });
      items.push(created);
      title = '';
      url = '';
      note = '';
      targetPrice = '';
    } catch (err) {
      formError = err instanceof ApiError ? err.message : 'failed to add';
    } finally {
      busy = false;
    }
  }

  const cycle: Record<ItemStatus, ItemStatus> = {
    coveted: 'acquired',
    acquired: 'renounced',
    renounced: 'coveted'
  };

  async function cycleStatus(item: Item) {
    const next = cycle[item.status];
    try {
      const updated = await listsApi.updateItem(item.id, { status: next });
      const i = items.findIndex((x) => x.id === item.id);
      if (i >= 0) items[i] = updated;
    } catch (err) {
      formError = err instanceof ApiError ? err.message : 'failed to update';
    }
  }

  function startEdit(item: Item) {
    editingId = item.id;
    editError = '';
    edit = {
      title: item.title,
      url: item.url ?? '',
      note: item.note ?? '',
      target: item.target_price != null ? String(item.target_price) : '',
      currency: item.currency ?? ''
    };
  }

  function cancelEdit() {
    editingId = null;
    editError = '';
  }

  async function saveEdit(item: Item) {
    if (!edit.title.trim()) {
      editError = 'title is required';
      return;
    }
    editBusy = true;
    editError = '';
    try {
      const t = edit.target.trim();
      const tp = t ? Number(t) : null; // null clears the target → notify on any drop
      if (t && !Number.isFinite(tp as number)) {
        editError = 'target must be a number';
        return;
      }
      const cur = edit.currency.trim().toUpperCase();
      const updated = await listsApi.updateItem(item.id, {
        title: edit.title.trim(),
        url: edit.url.trim() || null,
        note: edit.note.trim() || null,
        target_price: tp,
        ...(cur ? { currency: cur } : {})
      });
      const i = items.findIndex((x) => x.id === item.id);
      if (i >= 0) items[i] = updated;
      editingId = null;
    } catch (err) {
      editError = err instanceof ApiError ? err.message : 'failed to save';
    } finally {
      editBusy = false;
    }
  }

  async function removeItem(item: Item) {
    if (!confirm(`remove “${item.title}”?`)) return;
    try {
      await listsApi.removeItem(item.id);
      items = items.filter((x) => x.id !== item.id);
    } catch (err) {
      formError = err instanceof ApiError ? err.message : 'failed to delete';
    }
  }

  async function refetchItem(item: Item) {
    refetchingId = item.id;
    formError = '';
    try {
      const updated = await listsApi.refetch(item.id);
      const i = items.findIndex((x) => x.id === item.id);
      if (i >= 0) items[i] = updated;
      if (historyFor === item.id) history = await listsApi.history(item.id);
    } catch (err) {
      formError = err instanceof ApiError ? err.message : 'failed to check price';
    } finally {
      refetchingId = null;
    }
  }

  async function toggleHistory(item: Item) {
    if (historyFor === item.id) {
      historyFor = null;
      return;
    }
    historyFor = item.id;
    history = [];
    historyLoading = true;
    try {
      history = await listsApi.history(item.id);
    } catch (err) {
      formError = err instanceof ApiError ? err.message : 'failed to load history';
    } finally {
      historyLoading = false;
    }
  }

  // A want is "answered" when its watched price falls to/under the target.
  function onSale(item: Item): boolean {
    return (
      item.current_price != null &&
      item.target_price != null &&
      item.current_price <= item.target_price
    );
  }

  function fmtDate(s: string): string {
    return new Date(s).toLocaleDateString('en-GB', {
      day: '2-digit',
      month: 'short',
      hour: '2-digit',
      minute: '2-digit'
    });
  }

  const STATUS_STYLE: Record<ItemStatus, string> = {
    coveted: 'border-iris text-iris',
    acquired: 'border-mint text-mint',
    renounced: 'border-smoke text-mute'
  };
  const STATUS_LABEL: Record<ItemStatus, string> = {
    coveted: 'want',
    acquired: 'bought',
    renounced: 'skip'
  };

  function money(v: number | null, cur: string | null) {
    if (v == null) return null;
    return `${cur ?? 'EUR'} ${v.toFixed(2)}`;
  }
</script>

<svelte:head><title>{list?.name ?? 'list'} · consume·rs</title></svelte:head>

{#if auth.loaded && auth.user}
  <section class="space-y-8">
    <div>
      <a href="/lists" class="label hover:text-iris">← all lists</a>
      <div class="mt-2 flex items-start gap-3">
        <span class="text-4xl leading-none">{list?.emoji ?? '✦'}</span>
        <div class="min-w-0 flex-1">
          <h1 class="font-display text-4xl font-bold">{list?.name ?? '…'}</h1>
          {#if list?.description}<p class="gospel mt-1 text-lg">{list.description}</p>{/if}
        </div>
        {#if list}
          <div class="flex shrink-0 items-center gap-2">
            {#if !isOwner}
              <span class="tag border-iris text-iris" title="you collaborate on this list">
                {role === 'editor' ? '✎ editor' : '☑ cross-off'}
              </span>
            {/if}
            {#if isOwner}
              {#if list.share_token}
                <button
                  class="tag border-mint text-mint"
                  title="this list is shared — manage below"
                  onclick={() => document.getElementById('share-box')?.scrollIntoView({ behavior: 'smooth' })}
                >
                  ◈ shared
                </button>
              {:else}
                <button class="tag border-smoke text-mute hover:text-iris" disabled={sharing} onclick={share}>
                  {sharing ? '…' : '◈ share'}
                </button>
              {/if}
              <button
                class="tag border-smoke text-mute hover:text-iris"
                title="list settings, invites & collaborators"
                onclick={toggleSettings}
              >
                ⚙ settings
              </button>
            {/if}
          </div>
        {/if}
      </div>

      {#if list?.share_token}
        <div id="share-box" class="panel mt-4 flex flex-wrap items-center gap-2 p-3 text-sm">
          <span class="label shrink-0">public link</span>
          <input class="field flex-1 text-xs" readonly value={shareUrl} onclick={(e) => e.currentTarget.select()} />
          <button class="rounded border border-smoke px-3 py-1.5 text-xs text-mute transition hover:border-iris hover:text-iris" onclick={copyShare}>
            {copied ? '✓ copied' : 'copy'}
          </button>
          <a href={shareUrl} target="_blank" rel="noopener noreferrer" class="rounded border border-smoke px-3 py-1.5 text-xs text-mute transition hover:border-iris hover:text-iris">open ↗</a>
          <button class="rounded border border-smoke px-3 py-1.5 text-xs text-mute transition hover:border-rose hover:text-rose" disabled={sharing} onclick={unshare}>
            {sharing ? '…' : 'unshare'}
          </button>
        </div>
      {/if}

      {#if isOwner && showSettings}
        <div class="panel mt-4 space-y-5 p-4 text-sm">
          <div class="flex items-center justify-between">
            <p class="label">list settings</p>
            <button class="text-xs text-mute hover:text-iris" onclick={() => (showSettings = false)}>close</button>
          </div>

          <!-- Guest cross-off -->
          <div class="flex items-start justify-between gap-4 border-t border-smoke pt-4">
            <div class="min-w-0">
              <p class="font-bold">let anyone with the link cross items off</p>
              <p class="mt-0.5 text-xs text-mute">
                for gift/birthday lists — visitors don't need an account to mark something as taken.
                {#if !list?.share_token}<span class="text-rose/80">share the list first to use this.</span>{/if}
              </p>
            </div>
            <button
              class="tag shrink-0 {list?.allow_guest_crossoff ? 'border-mint text-mint' : 'border-smoke text-mute hover:text-iris'}"
              disabled={settingsBusy || !list?.share_token}
              onclick={toggleGuestCrossoff}
            >
              {list?.allow_guest_crossoff ? '● on' : '○ off'}
            </button>
          </div>

          <!-- Invite links -->
          <div class="border-t border-smoke pt-4">
            <p class="font-bold">invite collaborators</p>
            <p class="mt-0.5 text-xs text-mute">
              create a link, then share it. whoever opens it (and logs in) joins with that role.
            </p>
            <div class="mt-3 flex flex-wrap gap-2">
              <button class="rounded border border-smoke px-3 py-1.5 text-xs text-mute transition hover:border-iris hover:text-iris" disabled={settingsBusy} onclick={() => makeInvite('editor')}>
                + editor link <span class="text-mute/70">(add & edit items)</span>
              </button>
              <button class="rounded border border-smoke px-3 py-1.5 text-xs text-mute transition hover:border-iris hover:text-iris" disabled={settingsBusy} onclick={() => makeInvite('crosser')}>
                + cross-off link <span class="text-mute/70">(only tick items off)</span>
              </button>
            </div>

            {#if invites.length}
              <ul class="mt-3 space-y-2">
                {#each invites as inv (inv.id)}
                  <li class="flex flex-wrap items-center gap-2 rounded border border-smoke p-2">
                    <span class="tag shrink-0 {inv.role === 'editor' ? 'border-iris text-iris' : 'border-mint text-mint'}">{inv.role}</span>
                    <input class="field flex-1 text-xs" readonly value={inviteUrl(inv.token)} onclick={(e) => e.currentTarget.select()} />
                    <button class="rounded border border-smoke px-2 py-1 text-xs text-mute hover:border-iris hover:text-iris" onclick={() => copyInvite(inv.token)}>
                      {copiedInvite === inv.token ? '✓' : 'copy'}
                    </button>
                    <button class="rounded border border-smoke px-2 py-1 text-xs text-mute hover:border-rose hover:text-rose" onclick={() => dropInvite(inv.id)}>revoke</button>
                  </li>
                {/each}
              </ul>
            {/if}
          </div>

          <!-- Current collaborators -->
          {#if collaborators.length}
            <div class="border-t border-smoke pt-4">
              <p class="font-bold">collaborators</p>
              <ul class="mt-2 space-y-2">
                {#each collaborators as c (c.user_id)}
                  <li class="flex items-center justify-between gap-2 rounded border border-smoke p-2">
                    <span class="min-w-0 truncate">
                      {c.display_name ?? c.email}
                      <span class="tag ml-1 {c.role === 'editor' ? 'border-iris text-iris' : 'border-mint text-mint'}">{c.role}</span>
                    </span>
                    <button class="shrink-0 rounded border border-smoke px-2 py-1 text-xs text-mute hover:border-rose hover:text-rose" onclick={() => dropCollaborator(c.user_id)}>remove</button>
                  </li>
                {/each}
              </ul>
            </div>
          {/if}
        </div>
      {/if}
    </div>

    <!-- Add item — compact quick-add; tracking details on demand -->
    {#if canEdit}
    <form class="panel space-y-3 p-4" onsubmit={addItem}>
      <div class="flex gap-2">
        <input class="field" bind:value={title} maxlength="200" placeholder="add an item — what do you want?" />
        <button class="btn btn-acid shrink-0" disabled={busy || !title.trim()}>{busy ? '…' : 'add +'}</button>
      </div>
      <button
        type="button"
        class="label transition hover:text-iris"
        onclick={() => (showDetails = !showDetails)}
      >
        {showDetails ? '− fewer' : '+ link, target price & note'}
      </button>
      {#if showDetails}
        <div class="space-y-3 border-t border-smoke pt-3">
          <div class="grid gap-3 sm:grid-cols-[1fr_8rem]">
            <input class="field" bind:value={url} placeholder="product link (we'll track the price)" />
            <input class="field" bind:value={targetPrice} inputmode="decimal" placeholder="target price" />
          </div>
          <input class="field" bind:value={note} maxlength="1000" placeholder="note to self (optional)" />
        </div>
      {/if}
      {#if formError}<p class="border-2 border-rose bg-rose/10 px-3 py-2 text-sm text-rose">{formError}</p>{/if}
    </form>
    {:else if formError}
      <p class="border-2 border-rose bg-rose/10 px-3 py-2 text-sm text-rose">{formError}</p>
    {/if}

    {#if !loaded}
      <p class="text-center text-mute flicker">loading items…</p>
    {:else if loadError}
      <p class="border-2 border-rose bg-rose/10 px-3 py-2 text-sm text-rose">{loadError}</p>
    {:else if items.length === 0}
      <div class="panel p-10 text-center">
        <p class="gospel text-2xl">this list is empty</p>
        <p class="mt-2 text-mute">add something you want above to begin.</p>
      </div>
    {:else}
      <ul class="space-y-3">
        {#each items as item (item.id)}
          <li
            class="panel flex flex-col gap-3 p-4"
            class:opacity-60={item.status === 'renounced'}
            class:ring-1={onSale(item)}
            class:ring-mint={onSale(item)}
          >
            <!-- Line 1: identity + state -->
            <div class="flex items-start gap-4">
              {#if item.image_url}
                <img src={item.image_url} alt="" class="size-14 shrink-0 rounded-lg object-cover" />
              {/if}
              <div class="min-w-0 flex-1">
                <div class="flex items-center gap-2">
                  <h3
                    class="truncate font-display font-bold"
                    class:line-through={item.status === 'renounced' || !!item.claimed_at}
                  >
                    {item.title_fetched ?? item.title}
                  </h3>
                  {#if item.in_stock === true}
                    <span class="tag shrink-0 border-mint text-mint">in stock</span>
                  {:else if item.in_stock === false}
                    <span class="tag shrink-0 border-rose text-rose">sold out</span>
                  {/if}
                </div>
                <div class="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-mute">
                  {#if money(item.current_price, item.currency)}
                    <span class="text-sm font-bold" class:text-mint={onSale(item)} class:text-ink={!onSale(item)}>
                      {money(item.current_price, item.currency)}
                    </span>
                  {/if}
                  {#if item.target_price != null}
                    <span>target {money(item.target_price, item.currency)}</span>
                  {/if}
                  {#if item.url}
                    <a href={item.url} target="_blank" rel="noopener noreferrer" class="hover:text-iris" title="open product page">visit ↗</a>
                  {/if}
                  {#if item.note}<span class="italic">“{item.note}”</span>{/if}
                  {#if !item.url && item.current_price == null}
                    <span class="text-mute">not tracked</span>
                  {/if}
                  {#if item.checked_at}
                    <span class="text-mute" title="last price check">checked {fmtDate(item.checked_at)}</span>
                  {/if}
                </div>
                {#if item.claimed_at}
                  <p class="mt-1 text-xs font-bold text-mint">
                    ☑ taken{#if item.claimed_by_name} by {item.claimed_by_name}{/if}
                  </p>
                {/if}
              </div>

              <!-- Right controls: cross-off (everyone) + status (editors). -->
              <div class="flex shrink-0 flex-col items-end gap-2">
                <button
                  class="tag cursor-pointer transition hover:brightness-125 {item.claimed_at ? 'border-mint bg-mint/10 text-mint' : 'border-smoke text-mute hover:text-iris'}"
                  title={item.claimed_at ? 'crossed off — click to undo' : 'cross off / claim this item'}
                  disabled={claimBusy === item.id}
                  onclick={() => toggleClaim(item)}
                >
                  {claimBusy === item.id ? '…' : item.claimed_at ? '☑ taken' : '☐ cross off'}
                </button>
                {#if canEdit}
                  <button
                    class="tag cursor-pointer transition hover:brightness-125 {STATUS_STYLE[item.status]}"
                    title="click to cycle: want → bought → skip"
                    onclick={() => cycleStatus(item)}
                  >
                    ⇄ {STATUS_LABEL[item.status]}
                  </button>
                {:else}
                  <span class="tag {STATUS_STYLE[item.status]}">{STATUS_LABEL[item.status]}</span>
                {/if}
              </div>
            </div>

            <!-- Line 2: utility actions — plain verbs, real button chrome -->
            <div class="flex flex-wrap items-center justify-end gap-2 text-xs">
              {#if item.url}
                <button
                  class="rounded border border-smoke px-2 py-1 text-mute transition hover:border-iris hover:text-iris disabled:opacity-40"
                  title="check the price now"
                  disabled={refetchingId === item.id}
                  onclick={() => refetchItem(item)}
                >
                  {refetchingId === item.id ? '↻ checking…' : '↻ refresh'}
                </button>
                <button
                  class="rounded border border-smoke px-2 py-1 text-mute transition hover:border-iris hover:text-iris"
                  title="price history"
                  onclick={() => toggleHistory(item)}
                >
                  {historyFor === item.id ? 'hide history' : 'history'}
                </button>
              {/if}
              {#if canEdit}
                <button
                  class="rounded border border-smoke px-2 py-1 text-mute transition hover:border-iris hover:text-iris"
                  title="edit this item"
                  onclick={() => (editingId === item.id ? cancelEdit() : startEdit(item))}
                >
                  {editingId === item.id ? 'close' : '✎ edit'}
                </button>
                <button
                  class="rounded border border-smoke px-2 py-1 text-mute transition hover:border-rose hover:text-rose"
                  title="remove from this list"
                  onclick={() => removeItem(item)}
                >
                  ✕ remove
                </button>
              {/if}
            </div>

            {#if editingId === item.id}
              <div class="space-y-3 border-t border-smoke pt-3">
                <input class="field" bind:value={edit.title} maxlength="200" placeholder="title" />
                <input class="field" bind:value={edit.url} placeholder="product URL" />
                <div class="grid gap-3 sm:grid-cols-[1fr_8rem]">
                  <input
                    class="field"
                    bind:value={edit.target}
                    inputmode="decimal"
                    placeholder="target price (blank = notify on any drop)"
                  />
                  <input
                    class="field"
                    bind:value={edit.currency}
                    maxlength="3"
                    placeholder="currency"
                    title="3-letter code, e.g. EUR — overrides the detected currency"
                  />
                </div>
                <input class="field" bind:value={edit.note} maxlength="1000" placeholder="note" />
                {#if editError}<p class="text-xs text-rose">{editError}</p>{/if}
                <div class="flex justify-end gap-2 text-xs">
                  <button class="rounded border border-smoke px-3 py-1 text-mute hover:text-ink" onclick={cancelEdit}>cancel</button>
                  <button class="btn btn-acid px-3 py-1" disabled={editBusy} onclick={() => saveEdit(item)}>
                    {editBusy ? 'saving…' : 'save'}
                  </button>
                </div>
              </div>
            {/if}

            {#if onSale(item)}
              <p class="gospel text-sm text-mint">✦ price dropped — grab it now</p>
            {/if}
            {#if item.last_error}
              <p class="text-xs text-rose">price check failed: {item.last_error}</p>
            {/if}

            {#if historyFor === item.id}
              <div class="border-t border-smoke pt-3">
                {#if historyLoading}
                  <p class="text-xs text-mute flicker">loading history…</p>
                {:else}
                  <PriceChart {history} target={item.target_price} currency={item.currency} />
                {/if}
              </div>
            {/if}
          </li>
        {/each}
      </ul>
    {/if}
  </section>
{:else}
  <p class="text-center text-mute flicker">loading…</p>
{/if}
