<script>
  import { auth, savedIds, toggleSave } from '$lib/auth.svelte.js';
  import { track } from '$lib/analytics.js';

  let { article, onaction, onreplace, ontag, onsource, onimageerror, onview, hero = false, thumb = false } = $props();

  function opened() {
    // Records history; the /a page itself fires the summary_viewed event on load.
    onview?.(article);
  }
  let isSaved = $derived(savedIds.has(article.id));

  const humanize = (s) => (s || '').replace(/-/g, ' ');
  // Grouping tags are the doorways; cap at 3 so cards stay calm. Fall back to
  // the primary topic for articles the re-tag hasn't reached yet.
  let pills = $derived(
    (article.tags?.length ? article.tags : article.topic ? [article.topic] : []).slice(0, 3)
  );

  // The accent line is color-coded by primary topic — a quiet bit of variety
  // across the grid. Muted tones in the sand/sea/sun family; calm, not loud.
  const TOPIC_COLOR = {
    science: '#0083ad',     // azure (sea)
    technology: '#5f72a8',  // muted indigo
    environment: '#5a9d3e', // leaf green (clearly distinct from health's teal)
    health: '#1f9e95',      // teal
    community: '#c79a3a',   // warm gold
    culture: '#c4795a',     // terracotta
    animals: '#9a8246',     // earthy tan
    learning: '#8a6da3',    // soft violet
  };
  let accentColor = $derived(TOPIC_COLOR[article.topic] ?? 'var(--accent)');

  // Reset image-failure when the article changes (cards swap in place).
  let failed = $state(false);
  $effect(() => {
    void article.image_url;
    failed = false;
  });
  let hasImg = $derived(!!article.image_url && !failed);
  // Hero shows a large image. A `thumb` card (brief rest cards) ALWAYS carries a
  // banner so the grid stays uniform: a real image when it has one, otherwise a
  // calm topic-colored placeholder. Every other card stays typographic.
  let showImage = $derived(hasImg && (hero || thumb));
  let isThumb = $derived(thumb && !hero);
  let usePlaceholder = $derived(isThumb && !hasImg);
  let safeHref = $derived(
    typeof article.url === 'string' && /^https?:\/\//.test(article.url) ? article.url : '#'
  );
  // Default click → our summary page (the calm gist). Source is one tap further.
  let summaryHref = $derived('/a/' + article.id);
  function openedSource() {
    onview?.(article);
    track('full_story', article.id);
    if (article.paywalled) track('paywalled_source_open', article.id);
  }

  function act(kind, value) {
    if (value) onaction?.(kind, value);
  }

  // Sharing: share the branded Upbeat Bytes card page (default), with copy-source.
  let shareOpen = $state(false);
  let copied = $state('');
  const canNativeShare = typeof navigator !== 'undefined' && !!navigator.share;
  let shareUrl = $derived(
    (typeof location !== 'undefined' ? location.origin : 'https://upbeatbytes.com') + '/a/' + article.id
  );
  async function nativeShare() {
    try {
      await navigator.share({ title: article.title, url: shareUrl });
      track('native_share', article.id);
    } catch {
      /* user cancelled */
    }
    shareOpen = false;
  }
  // Kick off summary generation when the user signals intent to share, so it's
  // cached by the time a recipient opens the link.
  function warmSummary() {
    fetch(`/api/summary/${article.id}`).catch(() => {});
  }
  async function copy(text, label) {
    try {
      await navigator.clipboard.writeText(text);
      copied = label;
      setTimeout(() => (copied = ''), 1500);
    } catch {
      /* clipboard blocked */
    }
  }
</script>

<article
  class:hero={hero && hasImg}
  class:herotype={hero && !hasImg}
  class:tile={!hero}
  class:hasthumb={isThumb}
  data-topic={article.topic ?? ''}
>
  {#if showImage}
    <a class="media" href={summaryHref} onclick={opened}>
      <img src={article.image_url} alt="" loading="lazy" referrerpolicy="no-referrer"
           onerror={() => { failed = true; onimageerror?.(); }} />
    </a>
  {:else if usePlaceholder}
    <a class="media placeholder" href={summaryHref} onclick={opened} style="--c:{accentColor}" tabindex="-1" aria-hidden="true">
      <span class="ph-word">{humanize(article.topic) || 'upbeat bytes'}</span>
    </a>
  {/if}

  <div class="body">
    <div class="cardhead">
      <span class="accent" style="background:{accentColor}"></span>
      <div class="pillrow">
        {#each pills as t, i (t)}
          {#if ontag}
            <button type="button" class="tag" class:soft={i > 0} onclick={() => ontag(t)}>{humanize(t)}</button>
          {:else}
            <span class="tag" class:soft={i > 0}>{humanize(t)}</span>
          {/if}
        {/each}
      </div>
    </div>
    <div class="src">
      {#if onsource && article.source_id}
        <button type="button" class="srclink" onclick={() => onsource(article.source_id, article.source)}>{article.source}</button>
      {:else}
        {article.source}
      {/if}
    </div>

    <h3><a href={summaryHref} onclick={opened}>{article.title}</a></h3>

    {#if article.summary}
      <p class="summary">{article.summary}</p>
    {:else if hero && article.description}
      <p class="desc">{article.description}</p>
    {/if}

    {#if article.paywalled}
      <p class="paywall">May need a subscription to read</p>
    {/if}

    <div class="actions">
      {#if auth.user}
        <button class="save" class:on={isSaved} onclick={() => toggleSave(article.id)}
                aria-pressed={isSaved} title={isSaved ? 'Saved' : 'Save for later'}>
          <svg viewBox="0 0 24 24" aria-hidden="true" width="13" height="13">
            <path d="M6 3h12v18l-6-4-6 4z" fill={isSaved ? 'currentColor' : 'none'}
                  stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" />
          </svg>
          {isSaved ? 'Saved' : 'Save'}
        </button>
      {/if}
      <a class="source" href={safeHref} target="_blank" rel="noopener" onclick={openedSource}>
        {article.paywalled ? 'Full story 🔒' : 'Full story'}
      </a>
      {#if onreplace}
        <button class="replace" onclick={() => onreplace(article)}>Replace</button>
      {/if}
      <span class="sharewrap">
        <button class="share" onclick={() => { shareOpen = !shareOpen; if (shareOpen) warmSummary(); }} aria-expanded={shareOpen}>Share</button>
        {#if shareOpen}
          <button class="backdrop" aria-label="Close" onclick={() => (shareOpen = false)}></button>
          <div class="sharemenu" role="menu">
            {#if canNativeShare}
              <button role="menuitem" onclick={nativeShare}>Share…</button>
            {/if}
            <button role="menuitem" onclick={() => { copy(shareUrl, 'link'); track('share_ub', article.id); }}>
              {copied === 'link' ? 'Copied!' : 'Copy link'}
            </button>
            <button role="menuitem" onclick={() => { copy(article.url, 'source'); track('copy_source', article.id); }}>
              {copied === 'source' ? 'Copied!' : 'Copy source link'}
            </button>
          </div>
        {/if}
      </span>
      {#if onaction && article.topic}<button class="mute" onclick={() => act('notToday', article.topic)}>Not today</button>{/if}
      {#if onaction && article.flavor}<button class="mute" onclick={() => act('lessLikeThis', article.flavor)}>Less like this</button>{/if}
      {#if onaction && article.topic}<button class="mute" onclick={() => act('alwaysHide', article.topic)}>Hide {article.topic}</button>{/if}
    </div>
  </div>
</article>

<style>
  article {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
    box-shadow: var(--shadow);
    display: flex;
    flex-direction: column;
    height: 100%;
  }

  .body { padding: 16px 18px 14px; display: flex; flex-direction: column; gap: 8px; flex: 1; }
  .cardhead { display: flex; align-items: center; }
  .pillrow { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
  .tag {
    background: var(--accent); color: #fff; border: none; border-radius: 999px;
    padding: 3px 11px 2px; font-family: var(--label); font-weight: 300; font-size: 0.66rem;
    text-transform: uppercase; letter-spacing: 0.055em; line-height: 1.5;
    transition: background 0.14s ease, color 0.14s ease;
  }
  .tag.soft { background: var(--accent-soft); color: var(--accent-deep); }
  button.tag { cursor: pointer; }
  button.tag:hover { background: var(--accent-deep); color: #fff; }
  button.tag.soft:hover { background: var(--accent); color: #fff; }

  /* On tiles, accent + pills + divider are ONE header unit with a fixed height,
     so the centered band is exactly the band the eye measures (accent line →
     divider). The accent is the top grid row; the pill row centers in the rest,
     giving even margins for 1-, 2-, and 3-pill cards with everything aligned. */
  .tile .cardhead {
    display: grid; grid-template-rows: 3px 1fr; height: 94px;
    padding-top: 18px; border-bottom: 1px solid var(--line);
  }
  .tile .accent {
    width: 30px; height: 3px; border-radius: 2px; background: var(--accent); opacity: 0.85;
  }
  .tile .pillrow { align-self: center; }
  /* Source on its own line below the tags, left-justified, for uniformity. */
  .src { color: var(--muted); font-size: 0.78rem; margin: -2px 0 2px; }
  .srclink {
    background: none; border: none; padding: 0; font: inherit; color: var(--muted);
    cursor: pointer; border-bottom: 1px dotted transparent;
  }
  .srclink:hover { color: var(--accent-deep); border-bottom-color: var(--accent-soft); }

  h3 { font-size: 1.18rem; }
  h3 a:hover { color: var(--accent-deep); }
  .desc { margin: 2px 0 0; color: #3b4754; }
  .summary { margin: 2px 0 0; color: var(--ink); font-size: 0.92rem; }
  .tile .summary {
    display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; -webkit-line-clamp: 5;
  }
  .hero .summary, .herotype .summary { font-size: 1rem; }
  .paywall {
    margin: 0; font-size: 0.78rem; color: var(--gold);
    display: inline-flex; align-items: center; gap: 5px;
  }
  .paywall::before { content: '🔒'; font-size: 0.72rem; filter: grayscale(0.3); }
  /* Keep heights even: clamp variable-length text on tiles. */
  .tile h3 {
    display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; -webkit-line-clamp: 3;
  }
  .hero .desc, .herotype .desc {
    display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; -webkit-line-clamp: 6;
  }

  .actions { margin-top: auto; padding-top: 12px; display: flex; gap: 14px; flex-wrap: wrap; }
  .actions button {
    background: none; border: none; padding: 0; color: var(--muted);
    font-size: 0.76rem; border-bottom: 1px dotted var(--line);
  }
  .actions button:hover { color: var(--accent-deep); border-bottom-color: var(--accent); }
  .actions .replace { color: var(--accent-deep); border-bottom-color: var(--accent-soft); }
  .actions .save {
    display: inline-flex; align-items: center; gap: 5px;
    color: var(--accent-deep); border-bottom-color: var(--accent-soft);
  }
  .actions .save.on { color: var(--accent); }
  .actions .source {
    color: var(--accent-deep); text-decoration: none; font-size: 0.76rem;
    border-bottom: 1px dotted var(--accent-soft);
  }
  .actions .source:hover { border-bottom-color: var(--accent); }
  .actions .share { color: var(--accent-deep); border-bottom-color: var(--accent-soft); }
  .sharewrap { position: relative; display: inline-flex; }
  .sharemenu {
    position: absolute; bottom: 130%; left: 0; z-index: 12;
    background: var(--surface); border: 1px solid var(--line); border-radius: 12px;
    box-shadow: var(--shadow); padding: 6px; min-width: 150px;
    display: flex; flex-direction: column;
  }
  .sharemenu button {
    text-align: left; padding: 8px 10px; border: none; border-radius: 8px;
    background: none; color: var(--ink); font-size: 0.82rem; cursor: pointer;
  }
  .sharemenu button:hover { background: var(--accent-soft); color: var(--accent-deep); }
  .backdrop { position: fixed; inset: 0; z-index: 11; background: none; border: none; cursor: default; }

  /* Save, Replace, Share stay visible; only the boundary actions reveal on hover. */
  @media (hover: hover) {
    .actions .mute { opacity: 0; transition: opacity 0.16s ease; }
    article:hover .actions .mute,
    article:focus-within .actions .mute { opacity: 1; }
  }

  /* ---- Compact top image for brief rest cards (thumb) ---- */
  /* A modest, fixed-ratio banner — selective image confidence, not a media wall.
     Light desaturation for calm cohesion; never a heavy tint. */
  .tile .media {
    display: block; position: relative; width: 100%;
    aspect-ratio: 16 / 9; overflow: hidden; border-bottom: 1px solid var(--line);
    background: linear-gradient(135deg, var(--accent-soft), #f1ece0);
  }
  .tile .media img {
    width: 100%; height: 100%; object-fit: cover; display: block;
    filter: saturate(0.9);
  }
  /* Image-less cards get a calm placeholder banner in the card's topic color
     (--c), so every card in the grid is the same size — uniform, not lacking. */
  .tile .media.placeholder {
    display: flex; align-items: center; justify-content: center; text-decoration: none;
    /* Flat fill in the card's topic color. */
    background: color-mix(in srgb, var(--c) 14%, var(--surface));
  }
  .tile .media.placeholder .ph-word {
    font-family: var(--serif); font-size: 2rem; line-height: 1;
    /* A deep shade of the topic color so the word reads clearly (health vs
       environment easy to tell apart). */
    color: color-mix(in srgb, var(--c) 64%, var(--ink)); opacity: 0.92;
    text-transform: lowercase; letter-spacing: -0.01em;
  }
  /* A thumb card's banner carries the topic identity, so drop the faint corner
     watermark there to avoid a doubled word. */
  .tile.hasthumb::after { display: none; }
  /* The "why" now lives only on the article page, so the summary gets the room. */
  .tile.hasthumb .summary { -webkit-line-clamp: 4; }

  /* ---- Typographic editorial tile (every non-hero card) ---- */
  .tile { position: relative; }
  /* The .cardhead owns the top padding (above the accent); avoid doubling it. */
  .tile .body { padding-top: 0; }
  .tile::after {
    content: attr(data-topic);
    position: absolute; right: 10px; bottom: -8px;
    font-family: var(--serif); font-size: 3.4rem; line-height: 1;
    color: var(--accent); opacity: 0.05; text-transform: lowercase; letter-spacing: -0.02em;
    pointer-events: none; user-select: none;
  }
  .tile .cardhead, .tile .src, .tile h3, .tile .actions { position: relative; }

  /* ---- Hero WITH image: two columns, with an atmospheric overlay ---- */
  .hero { display: grid; grid-template-columns: 1.1fr 1fr; }
  .hero .media {
    position: relative; display: block; height: 100%; min-height: 300px;
    background: linear-gradient(135deg, var(--accent-soft), #f1ece0);
  }
  .hero .media img { width: 100%; height: 100%; object-fit: cover; }
  /* Soft feed images read as atmospheric, not broken. */
  .hero .media::after {
    content: ''; position: absolute; inset: 0; pointer-events: none;
    background: linear-gradient(180deg, rgba(10, 22, 38, 0) 55%, rgba(10, 22, 38, 0.18));
  }
  .hero .body { padding: 30px 32px; justify-content: center; gap: 12px; }
  .hero h3 { font-size: 1.95rem; }

  /* ---- Hero WITHOUT image: fully typographic lead ---- */
  .herotype {
    position: relative; overflow: hidden;
    background:
      radial-gradient(120% 140% at 100% 0%, var(--accent-soft) 0%, transparent 55%),
      var(--surface);
  }
  .herotype::after {
    content: attr(data-topic);
    position: absolute; right: 12px; bottom: -18px;
    font-family: var(--serif); font-size: clamp(4rem, 12vw, 8rem); line-height: 1;
    color: var(--accent); opacity: 0.07; text-transform: lowercase; letter-spacing: -0.02em;
    pointer-events: none; user-select: none;
  }
  .herotype .body { position: relative; padding: 40px 36px; gap: 12px; }
  .herotype h3 { font-size: 2.15rem; }
  .herotype .desc { font-size: 1.05rem; }

  @media (max-width: 640px) {
    .hero { grid-template-columns: 1fr; }
    .hero .media { min-height: 200px; }
    .hero h3, .herotype h3 { font-size: 1.6rem; }
    .herotype .body { padding: 30px 24px; }
  }
</style>
