<script lang="ts">
  /** Mastodon/Pleroma poll choices, voting and results. */
  import { untrack } from 'svelte'
  import type { Poll } from '$lib/api/types'
  import { useAppServices } from '$lib/app-services'
  import { formatCount } from '$lib/util/profile'

  interface Props {
    poll: Poll
    authorId?: string
    onupdate?: (poll: Poll) => void
  }

  let { poll, authorId, onupdate }: Props = $props()
  const { endpoints, session } = useAppServices()

  function pollSignature(value: Poll): string {
    return [
      value.id,
      value.expired,
      value.voted,
      value.votes_count,
      value.own_votes?.join(',') ?? '',
      value.options.map((option) => option.votes_count ?? '').join(','),
    ].join(':')
  }

  let incomingSignature = $state(untrack(() => pollSignature(poll)))
  let currentPoll = $state(untrack(() => poll))
  let selectedChoices = $state<number[]>([])
  let singleChoice = $state<number | null>(null)
  let busy = $state(false)
  let error = $state<string | null>(null)

  const total = $derived(currentPoll.votes_count || 0)
  const isAuthor = $derived(Boolean(authorId && session.me?.id === authorId))
  const showResults = $derived(currentPoll.expired || Boolean(currentPoll.voted) || isAuthor)
  const canChoose = $derived(
    session.signedIn && !isAuthor && !currentPoll.expired && !currentPoll.voted && !busy,
  )
  const choices = $derived(
    currentPoll.multiple
      ? selectedChoices
      : singleChoice === null
        ? []
        : [singleChoice],
  )

  $effect(() => {
    const next = poll
    const signature = pollSignature(next)
    if (signature !== incomingSignature) {
      incomingSignature = signature
      currentPoll = next
      selectedChoices = []
      singleChoice = null
      error = null
    }
  })

  function share(votes: number | null): number {
    if (!total || votes === null) return 0
    return Math.round((votes / total) * 100)
  }

  function closesIn(value: string): string {
    const seconds = Math.max(0, Math.ceil((new Date(value).getTime() - Date.now()) / 1000))
    const units: Array<[string, number]> = [
      ['day', 86_400],
      ['hour', 3_600],
      ['minute', 60],
    ]
    for (const [label, size] of units) {
      if (seconds >= size) {
        const count = Math.ceil(seconds / size)
        return `${count} ${label}${count === 1 ? '' : 's'}`
      }
    }
    return seconds > 0 ? `${seconds} second${seconds === 1 ? '' : 's'}` : 'soon'
  }

  async function submitVote(event: SubmitEvent): Promise<void> {
    event.preventDefault()
    if (!canChoose || choices.length === 0) return

    busy = true
    error = null
    try {
      const updated = await endpoints.votePoll(session.api, currentPoll.id, choices)
      currentPoll = updated
      selectedChoices = []
      singleChoice = null
      onupdate?.(updated)
    } catch (cause) {
      error = cause instanceof Error ? cause.message : 'Could not submit your vote.'
    } finally {
      busy = false
    }
  }
</script>

<div
  class="poll"
  data-expired={currentPoll.expired ? 'true' : 'false'}
  data-voted={currentPoll.voted ? 'true' : 'false'}
  data-multiple={currentPoll.multiple ? 'true' : 'false'}
>
  {#if showResults}
    {#each currentPoll.options as option, index (index)}
      <div class="poll-option" data-own-vote={currentPoll.own_votes?.includes(index) ? 'true' : 'false'}>
        <div class="poll-option-label">
          <span class="poll-option-title">
            {#if currentPoll.own_votes?.includes(index)}
              <span class="poll-own-vote" aria-label="Your vote">✓</span>
            {/if}
            {option.title}
          </span>
          <span class="poll-option-share">
            {option.votes_count === null ? '—' : `${share(option.votes_count)}%`}
          </span>
        </div>
        <div class="poll-option-bar" aria-hidden="true">
          <span class="poll-option-fill" style={`width: ${share(option.votes_count)}%`}></span>
        </div>
      </div>
    {/each}
  {:else}
    <form class="poll-vote-form" onsubmit={submitVote}>
      <fieldset class="poll-choices" disabled={!canChoose}>
        <legend class="visually-hidden">
          {currentPoll.multiple ? 'Choose one or more poll options' : 'Choose one poll option'}
        </legend>
        {#each currentPoll.options as option, index (index)}
          <label class="poll-choice">
            {#if currentPoll.multiple}
              <input type="checkbox" value={index} bind:group={selectedChoices} />
            {:else}
              <input type="radio" name={`poll-${currentPoll.id}`} value={index} bind:group={singleChoice} />
            {/if}
            <span>{option.title}</span>
          </label>
        {/each}
      </fieldset>

      {#if session.signedIn}
        <div class="poll-vote-actions">
          <button class="button button--small button--primary" type="submit" disabled={!canChoose || choices.length === 0}>
            {busy ? 'Voting…' : 'Vote'}
          </button>
          {#if currentPoll.multiple}
            <span class="field-hint">Choose one or more.</span>
          {/if}
        </div>
      {:else}
        <p class="poll-sign-in"><a href="#/login">Sign in to vote</a></p>
      {/if}
    </form>
  {/if}

  {#if isAuthor && !currentPoll.expired}
    <p class="poll-notice">You created this poll.</p>
  {/if}
  {#if error}
    <p class="error-note" role="alert">{error}</p>
  {/if}

  <p class="poll-meta">
    {formatCount(total)} vote{total === 1 ? '' : 's'}
    {#if currentPoll.expired}
      &middot; closed
    {:else if currentPoll.expires_at}
      &middot; closes in {closesIn(currentPoll.expires_at)}
    {/if}
  </p>
</div>
