<script lang="ts">
  /**
   * One status, dressed as a MySpace blog entry.
   *
   * Vocabulary mapping, applied consistently across the app:
   *   favourite -> Kudos      (MySpace blogs really did call them that)
   *   reblog    -> Repost
   *   reply     -> Comment
   *
   * Toggling kudos/repost updates optimistically and rolls back on failure —
   * a federated round trip is slow enough that waiting feels broken.
   */
  import type { Status } from '$lib/api/types'
  import { useAppServices } from '$lib/app-services'
  import { displayNameOf, formatCount, fullHandle, profilePath } from '$lib/util/profile'
  import { renderDisplayName } from '$lib/util/html'
  import { isoDate, longDate, stampDate } from '$lib/util/time'
  import { extractYouTubeVideoIds } from '$lib/util/youtube'
  import Avatar from '../common/Avatar.svelte'
  import RichText from '../common/RichText.svelte'
  import Attachments from './Attachments.svelte'
  import PollView from './PollView.svelte'
  import PreviewCardView from './PreviewCardView.svelte'
  import YouTubeEmbeds from './YouTubeEmbeds.svelte'

  interface Props {
    status: Status
    /** Called with the updated status after an action succeeds. */
    onupdate?: (status: Status) => void
    /** Called after the viewer deletes their own entry. */
    ondelete?: (id: string) => void
    /** Drop the avatar gutter — used inside threads. */
    compact?: boolean
    /** Show the full date rather than a short stamp. */
    longFormDate?: boolean
  }

  let { status, onupdate, ondelete, compact = false, longFormDate = false }: Props = $props()

  const { endpoints, session } = useAppServices()
  /** The status actually being displayed; a boost renders its target. */
  const entry = $derived(status.reblog ?? status)
  const booster = $derived(status.reblog ? status.account : null)

  const author = $derived(entry.account)
  const authorName = $derived(renderDisplayName(displayNameOf(author), author.emojis))
  const handle = $derived(fullHandle(author, session.host))
  const permalink = $derived(`#/blog/${entry.id}`)
  const isMine = $derived(session.me?.id === entry.account.id)
  const youtubeVideoIds = $derived(extractYouTubeVideoIds(entry.content))

  let busy = $state(false)
  let actionError = $state<string | null>(null)

  const VISIBILITY_ICON: Record<string, string> = {
    public: '🌐',
    unlisted: '🔓',
    private: '🔒',
    direct: '✉',
  }

  const VISIBILITY_LABEL: Record<string, string> = {
    public: 'Public',
    unlisted: 'Unlisted',
    private: 'Friends only',
    direct: 'Private message',
  }

  async function toggleKudos(): Promise<void> {
    if (!session.signedIn || busy) return
    const next = !entry.favourited
    busy = true
    actionError = null

    // Optimistic: reflect the new state before the request settles.
    onupdate?.(applyLocal(status, { favourited: next, favourites_count: entry.favourites_count + (next ? 1 : -1) }))

    try {
      const updated = await endpoints.favouriteStatus(session.api, entry.id, next)
      onupdate?.(rewrap(status, updated))
    } catch (cause) {
      onupdate?.(status)
      actionError = cause instanceof Error ? cause.message : 'Could not save that.'
    } finally {
      busy = false
    }
  }

  async function toggleRepost(): Promise<void> {
    if (!session.signedIn || busy) return
    const next = !entry.reblogged
    busy = true
    actionError = null

    onupdate?.(applyLocal(status, { reblogged: next, reblogs_count: entry.reblogs_count + (next ? 1 : -1) }))

    try {
      const updated = await endpoints.reblogStatus(session.api, entry.id, next)
      // Reblogging returns the *wrapper* status; unwrap to the original.
      onupdate?.(rewrap(status, updated.reblog ?? updated))
    } catch (cause) {
      onupdate?.(status)
      actionError = cause instanceof Error ? cause.message : 'Could not save that.'
    } finally {
      busy = false
    }
  }

  async function remove(): Promise<void> {
    if (!isMine || busy) return
    if (!confirm('Delete this entry? This cannot be undone.')) return
    busy = true
    try {
      await endpoints.deleteStatus(session.api, entry.id)
      ondelete?.(status.id)
    } catch (cause) {
      actionError = cause instanceof Error ? cause.message : 'Could not delete that.'
    } finally {
      busy = false
    }
  }

  /** Patch the inner status, preserving the boost wrapper if there is one. */
  function applyLocal(wrapper: Status, patch: Partial<Status>): Status {
    if (wrapper.reblog) return { ...wrapper, reblog: { ...wrapper.reblog, ...patch } }
    return { ...wrapper, ...patch }
  }

  function rewrap(wrapper: Status, fresh: Status): Status {
    return wrapper.reblog ? { ...wrapper, reblog: fresh } : fresh
  }
</script>

<article
  class="blog-entry"
  data-status-id={entry.id}
  data-account={author.acct}
  data-visibility={entry.visibility}
  data-boosted={booster ? 'true' : 'false'}
  data-reply={entry.in_reply_to_id ? 'true' : 'false'}
  data-sensitive={entry.sensitive ? 'true' : 'false'}
  data-compact={compact ? 'true' : 'false'}
  data-mine={isMine ? 'true' : 'false'}
>
  {#if booster}
    <p class="blog-entry-attribution">
      <a href={profilePath(booster)}>{displayNameOf(booster)}</a> reposted this
    </p>
  {/if}

  <header class="blog-entry-header">
    {#if !compact}
      <div class="blog-entry-avatar">
        <Avatar account={author} />
      </div>
    {/if}
    <div class="blog-entry-byline">
      <a class="blog-entry-author" href={profilePath(author)}>
        <!-- eslint-disable-next-line svelte/no-at-html-tags -- escaped in renderDisplayName -->
        {@html authorName}
      </a>
      <span class="blog-entry-handle">{handle}</span>
      <span class="blog-entry-date">
        <a href={permalink}>
          <time datetime={isoDate(entry.created_at)}>
            {longFormDate ? longDate(entry.created_at) : stampDate(entry.created_at)}
          </time>
        </a>
        <span class="blog-entry-visibility" title={VISIBILITY_LABEL[entry.visibility] ?? entry.visibility}>
          {VISIBILITY_ICON[entry.visibility] ?? ''}
          <span class="visually-hidden">{VISIBILITY_LABEL[entry.visibility] ?? entry.visibility}</span>
        </span>
        {#if entry.edited_at}
          <span class="blog-entry-edited">(edited)</span>
        {/if}
      </span>
    </div>
  </header>

  <div class="blog-entry-body">
    {#if entry.spoiler_text}
      <details class="content-warning">
        <summary class="content-warning-summary">{entry.spoiler_text}</summary>
        <RichText
          html={entry.content}
          emojis={entry.emojis}
          mentions={entry.mentions}
          tags={entry.tags}
          lang={entry.language}
        />
        {#if entry.media_attachments.length > 0}
          <Attachments attachments={entry.media_attachments} sensitive={entry.sensitive} />
        {/if}
        <YouTubeEmbeds videoIds={youtubeVideoIds} sensitive={entry.sensitive} />
      </details>
    {:else}
      <RichText
        html={entry.content}
        emojis={entry.emojis}
        mentions={entry.mentions}
        tags={entry.tags}
        lang={entry.language}
      />
      {#if entry.media_attachments.length > 0}
        <Attachments attachments={entry.media_attachments} sensitive={entry.sensitive} />
      {/if}
      <YouTubeEmbeds videoIds={youtubeVideoIds} sensitive={entry.sensitive} />
    {/if}

    {#if entry.poll}
      <PollView poll={entry.poll} />
    {/if}

    {#if entry.card && entry.media_attachments.length === 0 && youtubeVideoIds.length === 0}
      <PreviewCardView card={entry.card} />
    {/if}

    {#if actionError}
      <p class="error-note" role="alert">{actionError}</p>
    {/if}

    <footer class="blog-entry-actions">
      <a class="blog-action blog-action--comment" href={permalink}>
        Comment <span class="blog-action-count">({formatCount(entry.replies_count)})</span>
      </a>

      <button
        type="button"
        class="link-button blog-action blog-action--kudos"
        aria-pressed={entry.favourited ? 'true' : 'false'}
        disabled={!session.signedIn || busy}
        title={session.signedIn ? 'Give kudos' : 'Sign in to give kudos'}
        onclick={toggleKudos}
      >
        {entry.favourited ? 'Kudos given' : 'Kudos'}
        <span class="blog-action-count">({formatCount(entry.favourites_count)})</span>
      </button>

      <button
        type="button"
        class="link-button blog-action blog-action--repost"
        aria-pressed={entry.reblogged ? 'true' : 'false'}
        disabled={!session.signedIn || busy || entry.visibility === 'direct' || entry.visibility === 'private'}
        title={entry.visibility === 'private' || entry.visibility === 'direct'
          ? 'This entry can’t be reposted'
          : 'Repost to your friends'}
        onclick={toggleRepost}
      >
        {entry.reblogged ? 'Reposted' : 'Repost'}
        <span class="blog-action-count">({formatCount(entry.reblogs_count)})</span>
      </button>

      {#if entry.url}
        <a class="blog-action blog-action--source" href={entry.url} target="_blank" rel="noopener noreferrer">
          Original
        </a>
      {/if}

      {#if isMine}
        <button type="button" class="link-button blog-action blog-action--delete" disabled={busy} onclick={remove}>
          Delete
        </button>
      {/if}
    </footer>
  </div>
</article>
