"use client";

import { CommentsModal } from "./CommentsModal";
import { BadgeTip, BADGE_MEANINGS } from "./BadgeTip";
import { PostMenu } from "./PostMenu";
import {
  PostVideoEmbed,
  InstagramEmbed,
  FacebookEmbed,
  youtubeId,
  instagramId,
  facebookVideo,
  withoutYoutube,
} from "./PostVideoEmbed";
import { PostMusic } from "./PostMusic";
import { RepostMenu } from "./RepostMenu";
import { QuoteModal } from "./QuoteModal";
import { useSite } from "@/lib/site-context";

import { useEffect, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import {
  MessageCircle,
  Repeat2,
  Bookmark,
  MoreHorizontal,
  Pin,
  Flame,
  Eye,
  Crown,
  Trash2,
  Pencil,
  Link as LinkIcon,
  EyeOff,
  Ban,
  Flag, Lock, UserPlus, UserCheck, BarChart3, SmilePlus, MapPin, Music2 } from "lucide-react";
import { Avatar } from "./Avatar";
import { LikeAction, CommentAction, RepostAction, SaveAction } from "./PostActionIcons";
import { PostLikers } from "./PostLikers";
import { PollVotersModal } from "./PollVotersModal";
import { PostPoll } from "./PostPoll";
import { ProfileHoverCard } from "./ProfileHoverCard";
import { CommentPreview } from "./CommentPreview";
import { PostText } from "./PostText";
import { ReactionPicker, ReactionBurst, useReactionBurst, REACTIONS, type ReactionKey } from "./Reactions";
import { ShareSheet } from "./ShareSheet";
import { sfx } from "@/lib/sfx";
import { useConnectionWords } from "@/lib/site-context";
import { CommentThread } from "./CommentThread";
import { Share2 } from "lucide-react";
import { useAuth } from "@/lib/auth-context";
import { useClickOutside } from "@/lib/use-click-outside";
import { timeAgo, formatCount } from "@/lib/utils";
import type { Post } from "@/lib/types";
import { BadgeChips } from "./BadgeChips";
import clsx from "clsx";

export function PostCard({
  post: initial,
  onDeleted,
  inSavedList,
  onAuthorBlocked,
}: {
  post: Post;
  onDeleted?: (id: string) => void;
  /** True on the saved list, where unsaving removes the post from view. */
  inSavedList?: boolean;
  onAuthorBlocked?: (authorId: string) => void;
}) {
  const cw = useConnectionWords();
  // Whether a track starts muted is the admin's choice.
  const { posts: postSettings } = useSite();
  const musicStartsMuted = postSettings?.musicStartsMuted !== false;
  const [wideImage, setWideImage] = useState(false);
  const [quoting, setQuoting] = useState(false);
  const [translated, setTranslated] = useState<string | null>(null);

  const router = useRouter();
  const { user: me } = useAuth();
  const [post, setPost] = useState(initial);

  // A YouTube link plays in place, and the raw URL comes out of the words —
  // showing both says the same thing twice.
  const videoId = post.text ? youtubeId(post.text) : null;
  const instaId = post.text ? instagramId(post.text) : null;
  const fbVideo = post.text ? facebookVideo(post.text) : null;
  const bodyText =
    videoId || instaId || fbVideo ? withoutYoutube(post.text) : post.text;
  const [unlockError, setUnlockError] = useState<string | null>(null);
  const [menuOpen, setMenuOpen] = useState(false);
  const [reaction, setReaction] = useState<ReactionKey | null>(null);
  const [localCommentCount, setLocalCommentCount] = useState(post.commentCount);
  const [shareOpen, setShareOpen] = useState(false);
  const [likersVersion, setLikersVersion] = useState(0);
  const [votersOpen, setVotersOpen] = useState(false);
  const [voters, setVoters] = useState<Record<string, { id: string; username: string; name: string; avatarColor: string }[]>>({});

  useEffect(() => {
    if (!post.poll) return;
    fetch(`/api/posts/${post.id}/voters`)
      .then((r) => (r.ok ? r.json() : null))
      .then((d) => d && setVoters(d.byOption || {}))
      .catch(() => {});
  }, [post.id, post.poll, post.myVote]);
  const [showComments, setShowComments] = useState(false);
  const { burst, fire } = useReactionBurst();
  const [confirmingDelete, setConfirmingDelete] = useState(false);
  const [deleting, setDeleting] = useState(false);
  const [blocking, setBlocking] = useState(false);
  const [reported, setReported] = useState(false);
  const [showReportOptions, setShowReportOptions] = useState(false);
  const [reporting, setReporting] = useState(false);
  const [spoilerRevealed, setSpoilerRevealed] = useState(false);
  const [voting, setVoting] = useState(false);
  const [editing, setEditing] = useState(false);
  const [editText, setEditText] = useState(post.text);
  const [saving, setSaving] = useState(false);
  const [editError, setEditError] = useState<string | null>(null);
  const isOwn = me?.id === post.author.id;

  // Whether the reader already follows the author, worked out on the
  // server so a fresh page load doesn't offer to follow someone twice.
  const [followed, setFollowed] = useState(
    Boolean(post.author.followedByViewer)
  );

  /** Follows or unfollows the author, and keeps every card in step. */
  async function follow() {
    const wasFollowed = followed;
    setFollowed(!wasFollowed);

    // The endpoint toggles, so the same call follows and unfollows.
    const res = await fetch(`/api/users/${post.author.username}/follow`, {
      method: "POST",
    }).catch(() => null);

    if (!res || !res.ok) {
      setFollowed(wasFollowed);
      return;
    }

    // Every other card by the same author follows suit.
    window.dispatchEvent(
      new CustomEvent("xr-followed", {
        detail: { id: post.author.id, following: !wasFollowed },
      })
    );
  }

  useEffect(() => {
    const onFollowed = (e: Event) => {
      const d = (e as CustomEvent).detail as { id: string; following: boolean };
      if (d?.id === post.author.id) setFollowed(d.following);
    };
    window.addEventListener("xr-followed", onFollowed);
    return () => window.removeEventListener("xr-followed", onFollowed);
  }, [post.author.id]);
  const menuRef = useClickOutside<HTMLDivElement>(menuOpen, () => {
    setMenuOpen(false);
    setConfirmingDelete(false);
    setShowReportOptions(false);
  });

  async function blockAuthor() {
    setBlocking(true);
    const res = await fetch(`/api/users/${post.author.username}/block`, {
      method: "POST",
    }).catch(() => null);
    setBlocking(false);
    setMenuOpen(false);
    if (res && res.ok) onAuthorBlocked?.(post.author.id);
  }

  async function submitReport(reason: string) {
    setReporting(true);
    const res = await fetch("/api/reports", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ targetType: "post", targetId: post.id, reason }),
    }).catch(() => null);
    setReporting(false);
    setShowReportOptions(false);
    setMenuOpen(false);
    if (res && res.ok) {
      setReported(true);
      setTimeout(() => setReported(false), 2200);
    }
  }

  async function toggleLike(reactionKey?: string) {
    setPost((p) => ({ ...p, liked: !p.liked, likes: p.likes + (p.liked ? -1 : 1) }));
    await fetch(`/api/posts/${post.id}/like`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ reaction: reactionKey }),
    }).catch(() => {});
    setLikersVersion((v) => v + 1);
  }

  async function reactOnly(reactionKey: string) {
    await fetch(`/api/posts/${post.id}/like`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ reaction: reactionKey }),
    }).catch(() => {});
    setLikersVersion((v) => v + 1);
  }

  function votePoll(optionId: string, el: HTMLElement, hue: number) {
    // Spray particles from the tapped bar, matching the reference.
    const host = el.querySelector(".ptrack");
    if (host) {
      for (let i = 0; i < 14; i++) {
        const p = document.createElement("span");
        p.className = "pt";
        const size = 3 + Math.random() * 4;
        p.style.width = `${size}px`;
        p.style.height = `${size}px`;
        p.style.background = `hsl(${hue + (Math.random() * 40 - 20)} 90% ${55 + Math.random() * 20}%)`;
        const ang = Math.random() * Math.PI * 2;
        const dist = 26 + Math.random() * 48;
        p.animate(
          [
            { transform: "translate(-50%,-50%) scale(1)", opacity: 1 },
            {
              transform: `translate(calc(-50% + ${Math.cos(ang) * dist}px), calc(-50% + ${
                Math.sin(ang) * dist
              }px)) scale(0)`,
              opacity: 0,
            },
          ],
          { duration: 600 + Math.random() * 300, easing: "cubic-bezier(.2,.8,.3,1)" }
        ).onfinish = () => p.remove();
        host.appendChild(p);
      }
    }
    sfx.reaction("party");
    vote(optionId);
  }

  async function toggleSave() {
    const wasSaved = post.saved;
    // The number moves with the icon, so a click reads as having worked.
    setPost((p) => ({
      ...p,
      saved: !p.saved,
      saveCount: Math.max(0, (p.saveCount ?? 0) + (p.saved ? -1 : 1)),
    }));

    const res = await fetch(`/api/posts/${post.id}/save`, {
      method: "POST",
    }).catch(() => null);

    if (!res || !res.ok) {
      setPost((p) => ({
        ...p,
        saved: wasSaved,
        saveCount: Math.max(0, (p.saveCount ?? 0) + (wasSaved ? 1 : -1)),
      }));
      return;
    }

    // Only on the saved list does unsaving mean it no longer belongs
    // there. On the feed it's still a post worth reading.
    if (wasSaved && inSavedList && onDeleted) onDeleted(post.id);
  }

  async function toggleRepost() {
    const wasReposted = post.reposted;
    setPost((p) => ({
      ...p,
      reposted: !p.reposted,
      reposts: p.reposts + (p.reposted ? -1 : 1),
    }));
    await fetch(`/api/posts/${post.id}/repost`, { method: "POST" }).catch(() => {});
    // If this post is only showing up here *because* it was reposted (e.g.
    // on a profile page's merged posts+reposts list) and we just un-reposted
    // it, it shouldn't keep sitting there — remove it from view immediately
    // rather than waiting for the next full reload to notice it's stale.
    if (wasReposted && post.repostedBy) onDeleted?.(post.id);
  }

  async function vote(optionId: string) {
    if (voting || !post.poll) return;
    if (post.myVote === optionId) return; // already this option
    const previousVote = post.myVote;
    setVoting(true);
    setPost((p) => ({
      ...p,
      myVote: optionId,
      poll: p.poll?.map((o) => {
        if (o.id === optionId) return { ...o, voteCount: o.voteCount + 1 };
        if (previousVote && o.id === previousVote) {
          return { ...o, voteCount: Math.max(0, o.voteCount - 1) };
        }
        return o;
      }),
    }));
    const res = await fetch(`/api/posts/${post.id}/vote`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ optionId }),
    }).catch(() => null);
    setVoting(false);
    if (res && res.ok) {
      const updated = await res.json();
      setPost(updated);
    }
  }

  async function deletePost() {
    setDeleting(true);
    const res = await fetch(`/api/posts/${post.id}`, { method: "DELETE" }).catch(() => null);
    setDeleting(false);
    if (res && res.ok) onDeleted?.(post.id);
  }

  async function saveEdit() {
    if (!editText.trim()) return;
    setSaving(true);
    setEditError(null);
    const res = await fetch(`/api/posts/${post.id}`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ text: editText }),
    }).catch(() => null);
    setSaving(false);
    if (!res || !res.ok) {
      const data = res ? await res.json().catch(() => ({})) : {};
      setEditError(data.error || "Couldn't save that edit — try again.");
      return;
    }
    setPost(await res.json());
    setEditing(false);
  }

  const totalVotes = post.poll?.reduce((sum, o) => sum + o.voteCount, 0) ?? 0;
  // A paid post stays hidden until bought; the author always sees their own.
  const locked = Boolean(post.price) && !post.unlocked && post.author.id !== me?.id;

  async function unlock() {
    const res = await fetch(`/api/posts/${post.id}/unlock`, { method: "POST" }).catch(
      () => null
    );
    const d = res ? await res.json() : null;
    if (res && res.ok) {
      sfx.fanfare();
      window.location.reload();
      return;
    }
    setUnlockError(d?.error ?? "Couldn't unlock that post");
    setTimeout(() => setUnlockError(null), 3000);
  }

  const hasMedia = Boolean(post.imageUrl || post.image);
  const POLL_ICONS = ["⚡", "🎵", "🎬", "👥", "🌙", "🤖"];
  const allVoters = Object.values(voters).flat();

  const pollEl = post.poll ? (
    <PostPoll
      poll={post.poll}
      myVote={post.myVote}
      totalVotes={totalVotes}
      voters={voters}
      layout={post.pollLayout}
      overPhoto={hasMedia}
      voting={voting}
      onVote={votePoll}
      onOpenVoters={() => setVotersOpen(true)}
    />
  ) : null;

  return (
    <div className="relative border-b border-neutral-200 dark:border-neutral-800 py-4 px-4">
      <ReactionBurst burst={burst} />
      {post.repostedBy && (
        <Link
          href={`/${post.repostedBy.username}`}
          className="flex items-center gap-1.5 text-xs text-neutral-500 mb-2 px-1 hover:underline w-fit"
        >
          <Repeat2 size={13} /> {post.repostedBy.name} reposted
        </Link>
      )}
      {post.pinned && (
        <div className="flex items-center gap-1.5 text-xs text-neutral-500 mb-2 px-1">
          <Pin size={12} /> Pinned by admin
        </div>
      )}

      <div className="flex items-start justify-between px-1">
        <ProfileHoverCard username={post.author.username} user={post.author}>
        <Link
          href={`/${post.author.username}`}
          onClick={(e) => {
            // Navigate explicitly: the hover card and card-level handlers can
            // otherwise swallow the anchor's default action.
            e.stopPropagation();
            e.preventDefault();
            router.push(`/${post.author.username}`);
          }}
          className="flex items-center gap-2.5 group"
        >
          <Avatar user={post.author} ring size={40} />
          <div className="leading-tight">
            <span className="flex items-center gap-1 font-semibold text-[15px] group-hover:underline">
              {post.author.name}
            {post.author.tier && BADGE_MEANINGS[post.author.tier] && (
              <BadgeTip {...BADGE_MEANINGS[post.author.tier]}>
                <span className={`tier-badge ${post.author.tier}`}>
                  {post.author.tier === "ultra" ? "◆" : "●"}
                </span>
              </BadgeTip>
            )}
              <BadgeChips badges={post.author.awards} size="sm" />
              
              {post.author.premium && (
                <Crown size={13} className="text-amber-500 fill-amber-500" />
              )}


            {post.feeling && (
              <span className="pc-feeling">
                is feeling <b>{post.feeling.emoji} {post.feeling.word}</b>
              </span>
            )}

            {post.place && (
              <span className="pc-place">
                <MapPin size={12} /> {post.place}
              </span>
            )}
              
              <span className="text-neutral-400 font-normal text-sm">
                {/* The separator is its own element so a theme that puts
                    the time on its own line can drop the dot. */}
                <span className="pc-sep">&nbsp;· </span>
                {timeAgo(post.createdAt)}
              </span>
            </span>
          </div>
        </Link>
        </ProfileHoverCard>

        <div className="relative flex items-center gap-2" ref={menuRef}>
          {reported && (
            <div className="absolute right-0 top-9 w-40 bg-neutral-900 text-white text-xs rounded-lg px-3 py-2 z-20 shadow-xl">
              Reported. Thanks for letting us know.
            </div>
          )}
          {post.editedAt && (
            <span className="pa-edited mr-1">
              <svg viewBox="0 0 24 24" className="pa-pencil">
                <path d="M17 3a2.8 2.8 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5z" />
              </svg>
              <span className="pa-etxt">Edited</span>
            </span>
          )}
          {/* Reads Following once you do, and clicking it again undoes
              that — a button that simply vanished left no way back. */}
          {!isOwn && (
            <button
              onClick={(e) => {
                e.preventDefault();
                e.stopPropagation();
                follow();
              }}
              className={clsx("pc-follow", followed && "on")}
              title={
                followed
                  ? `Unfollow ${post.author.name}`
                  : `Follow ${post.author.name}`
              }
            >
              {followed ? (
                <UserCheck size={14} strokeWidth={2.2} />
              ) : (
                <UserPlus size={14} strokeWidth={2.2} />
              )}
              {followed ? cw.connected : cw.connect}
            </button>
          )}

          <PostMenu
            post={post}
            isMine={isOwn}
            onSave={toggleSave}
            onCopyLink={() => {
              navigator.clipboard?.writeText(
                `${location.origin}/post/${post.id}`
              );
            }}
            onHide={() => onDeleted?.(post.id)}
            onBlock={blockAuthor}
            onReport={() => setReporting(true)}
            onEdit={() => setEditing(true)}
            onDelete={deletePost}
            onPin={() => {
              // Pinning belongs to the profile, which owns the list.
              fetch(`/api/posts/${post.id}/pin`, { method: "POST" }).catch(() => {});
            }}
          />
        </div>
      </div>

      {(post.hotTake || post.premium) && (
        <div className="flex gap-2 mt-2 px-1">
          {post.hotTake && (
            <span className="inline-flex items-center gap-1 text-xs font-medium px-2 py-1 rounded-full bg-orange-100 text-orange-700 dark:bg-orange-950 dark:text-orange-300">
              <Flame size={12} /> Hot Take
            </span>
          )}
          {post.premium && (
            <span className="inline-flex items-center gap-1 text-xs font-medium px-2 py-1 rounded-full bg-amber-100 text-amber-700 dark:bg-amber-950 dark:text-amber-300">
              <Crown size={12} /> Premium
            </span>
          )}
        </div>
      )}

      {post.locked ? (
        <div className="mt-2 mx-1 relative rounded-2xl overflow-hidden">
          <div className="h-40 bg-gradient-to-br from-amber-200 via-amber-300 to-orange-300 dark:from-amber-900 dark:via-amber-800 dark:to-orange-900 blur-sm" />
          <div className="absolute inset-0 flex flex-col items-center justify-center gap-2 bg-black/25 text-white text-center px-4">
            <Crown size={22} />
            <p className="font-semibold text-sm">Premium-only content</p>
            <Link
              href="/wallet"
              className="mt-1 bg-white text-black rounded-full px-4 py-1.5 text-xs font-semibold hover:bg-neutral-100"
            >
              Upgrade to Premium
            </Link>
          </div>
        </div>
      ) : post.spoiler && !spoilerRevealed ? (
        <button
          onClick={() => setSpoilerRevealed(true)}
          className="mt-2 mx-1 w-[calc(100%-8px)] py-6 rounded-xl bg-neutral-100 dark:bg-neutral-900 flex flex-col items-center gap-1 text-neutral-500 text-sm"
        >
          <Eye size={18} />
          Spoiler — tap to reveal
        </button>
      ) : editing ? (
        <div className="mt-2 px-1">
          {editError && <p className="text-sm text-red-500 mb-1.5">{editError}</p>}
          <textarea
            autoFocus
            value={editText}
            onChange={(e) => setEditText(e.target.value)}
            maxLength={2000}
            rows={4}
            className="w-full resize-none rounded-xl border border-neutral-200 dark:border-neutral-800 bg-transparent px-3 py-2.5 text-[15px] outline-none focus:border-neutral-400"
          />
          <div className="flex items-center gap-2 mt-2">
            <button
              onClick={saveEdit}
              disabled={saving || !editText.trim()}
              className="bg-black text-white dark:bg-white dark:text-black rounded-full px-4 py-1.5 text-sm font-semibold disabled:opacity-40"
            >
              {saving ? "Saving…" : "Save"}
            </button>
            <button
              onClick={() => setEditing(false)}
              className="text-sm font-medium text-neutral-500 px-2"
            >
              Cancel
            </button>
          </div>
        </div>
      ) : (
        <div className="relative">
          {/* Only the text and image link out; the poll must stay
              interactive, so it sits outside the Link. */}
          {locked ? (
            <div className="pc-locked">
              <span className="pc-lockicon">
                <Lock size={22} />
              </span>
              <b>This post is locked</b>
              <em>Unlock it to read the post and see the media.</em>
              <button onClick={unlock} className="pc-unlock">
                Unlock for ${post.price}
              </button>
              {unlockError && <p className="pc-lockerr">{unlockError}</p>}
            </div>
          ) : (
            <div className={clsx("pc-body", post.background && "on-colour")}>
              {post.background ? (
                // Written on a colour: bigger, centred, its own thing.
                <p className={`pc-bg bg-${post.background}`}>{bodyText}</p>
              ) : (
                <PostText text={bodyText} />
              )}

              

              
            </div>
          )}

          {hasMedia && !locked && (
            <Link
              href={`/post/${post.id}`}
              // Full width: the frame inside decides how the photo sits,
              // and the track badge is pinned to that frame rather than
              // this link, so it still lands on the picture.
              className="relative block w-full"
            >
              {post.imageUrl ? (
                <span className={clsx("pf-frame", wideImage && "wide")}>
                  {/* An enlarged, blurred copy of the same photo fills the
                      frame behind it, so a tall picture sits at a proper
                      size without being trimmed. */}
                  <span
                    className="pf-blur"
                    style={{ backgroundImage: `url(${post.imageUrl})` }}
                    aria-hidden
                  />
                  {/* eslint-disable-next-line @next/next/no-img-element */}
                  <img
                    src={post.imageUrl}
                    alt=""
                    className="pf-photo"
                    onLoad={(e) => {
                      const img = e.currentTarget;
                      if (img.naturalHeight) {
                        setWideImage(img.naturalWidth / img.naturalHeight > 0.8);
                      }
                    }}
                  />
                </span>
              ) : (
                post.image && (
                  <div
                    className={`mt-3 rounded-2xl h-56 bg-gradient-to-br ${post.image}`}
                  />
                )
              )}

              {/* The track sits over the photo. Tapping it unmutes, and
                  stops the link opening the post. */}
              {post.music && (
                <span onClick={(e) => e.preventDefault()}>
                  <PostMusic music={post.music} startMuted={musicStartsMuted} />
                </span>
              )}
            </Link>
          )}

          {/* The post being quoted, shown inside this one and linking
              through to the original. */}
          {videoId && <PostVideoEmbed id={videoId} />}
          {instaId && <InstagramEmbed id={instaId} />}
          {fbVideo && <FacebookEmbed url={fbVideo} />}

          {/* An uploaded clip or track, played in place. */}
          {post.videoUrl && !post.videoUrl.includes("youtu") && (
            <video src={post.videoUrl} controls className="pc-video" />
          )}

          {post.audioUrl && (
            <div className="pc-audio">
              <Music2 size={17} />
              <audio src={post.audioUrl} controls />
            </div>
          )}

          {post.quoted && (
            <Link
              href={`/post/${post.quoted.id}`}
              className="pc-quoted block"
            >
              <span className="qt-who">
                <Avatar user={post.quoted.author} size={22} />
                <b>{post.quoted.author.name}</b>
                <em>@{post.quoted.author.username}</em>
              </span>

              {post.quoted.text && <p>{post.quoted.text}</p>}

              {post.quoted.imageUrl && (
                // eslint-disable-next-line @next/next/no-img-element
                <img src={post.quoted.imageUrl} alt="" />
              )}
            </Link>
          )}

          {hasMedia && pollEl}
        </div>
      )}

      {!hasMedia && pollEl}


      <div className="pa-row mt-3 px-1 text-neutral-500 text-sm">
        <ReactionPicker
          // Tapping opens the picker rather than liking outright, so which
          // reaction to leave is always a deliberate choice. Removing one
          // is the exception — tapping again takes it back.
          onQuickPick={
            post.liked
              ? () => {
                  setReaction(null);
                  toggleLike();
                }
              : undefined
          }
          current={reaction}
          onPick={(r) => {
            if (!r) {
              setReaction(null);
              if (post.liked) toggleLike();
              return;
            }
            setReaction(r.key);
            fire(r.emoji, r.color);
            // Always POST — the API keeps the like and swaps the emoji when
            // it's a change rather than a fresh reaction.
            if (post.liked) reactOnly(r.key);
            else toggleLike(r.key);
          }}
        >
          {reaction ? (
            <span className="pa-btn" title="Change reaction">
              <span className={`rx-chip rx-${reaction}`}>
                {REACTIONS.find((x) => x.key === reaction)?.emoji}
              </span>
              {post.likes > 0 && (
                <b className="pa-count tabular-nums">{formatCount(post.likes)}</b>
              )}
            </span>
          ) : (
            <span
              className={`pa-btn pa-like ${post.liked ? "on" : ""}`}
              title="React"
            >
              <SmilePlus size={21} strokeWidth={2} />
              {/* How many reactions this post has. It used to be left off
                  because the row of faces further along carries the same
                  number, but that only appears on wide screens and every
                  other action here shows its count — so the one people
                  press first was the only one that looked like it hadn't
                  registered. */}
              {post.likes > 0 && (
                <b className="pa-count tabular-nums">{formatCount(post.likes)}</b>
              )}
            </span>
          )}
        </ReactionPicker>
        <button
          onClick={() => setShowComments((v) => !v)}
          className={`pa-btn pa-comment ${showComments ? "talking" : ""}`}
          title="Comments"
        >
          <svg viewBox="0 0 24 24">
            <path className="pa-bubble" d="M20 3H4a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2h3v4l5-4h8a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" />
            <circle className="pa-dot" cx="8" cy="10.5" r="1.4" />
            <circle className="pa-dot" cx="12" cy="10.5" r="1.4" />
            <circle className="pa-dot" cx="16" cy="10.5" r="1.4" />
          </svg>
          <span className="tabular-nums">{formatCount(localCommentCount)}</span>
        </button>
        <SaveAction saved={post.saved} count={post.saveCount} onToggle={toggleSave} />
        <RepostMenu
          post={post}
          onReposted={(reposted, count) =>
            setPost((x) => ({ ...x, reposted, reposts: count }))
          }
          onQuote={() => setQuoting(true)}
        />
        <button
          onClick={() => {
            sfx.shareOpen();
            setShareOpen(true);
          }}
          className="pa-btn"
          title="Share"
        >
          <Share2 size={19} strokeWidth={2} />
        </button>

        {/* How many people have seen it — distinct readers, not glances. */}
        {(post.views ?? 0) > 0 && (
          <span className="pa-views" title={`${post.views} people have seen this`}>
            <BarChart3 size={19} strokeWidth={2} />
            <b className="pa-count">{formatCount(post.views ?? 0)}</b>
          </span>
        )}

        {/* Who reacted, beside the actions. It carries the count, which is
            why the heart no longer repeats it. */}
        {post.likes > 0 && (
          <PostLikers
            postId={post.id}
            likeCount={post.likes}
            refreshKey={likersVersion}
          />
        )}


        <CommentPreview
          postId={post.id}
          commentCount={localCommentCount}
          // So the number beside the icon moves as soon as someone comments,
          // rather than waiting for a reload.
          onCountChange={setLocalCommentCount}
        />
      </div>

      {showComments && (
        <CommentsModal
          post={post}
          onClose={() => setShowComments(false)}
          onCountChange={(n) => {
            // Both, so the number beside the icon moves as soon as a
            // comment is posted rather than on the next load.
            setLocalCommentCount(n);
            setPost((x) => ({ ...x, commentCount: n }));
          }}
        />
      )}

      {votersOpen && post.poll && (
        <PollVotersModal
          poll={post.poll}
          voters={voters}
          icons={POLL_ICONS}
          onClose={() => setVotersOpen(false)}
        />
      )}

      {shareOpen && (
        <ShareSheet
          url={typeof window !== "undefined" ? `${window.location.origin}/post/${post.id}` : `/post/${post.id}`}
          text={post.text.slice(0, 100)}
          onClose={() => setShareOpen(false)}
        />
      )}

      {quoting && (
        <QuoteModal
          post={post}
          onClose={() => setQuoting(false)}
          onPosted={() => {
            setQuoting(false);
            setPost((x) => ({ ...x, reposts: x.reposts + 1, reposted: true }));
          }}
        />
      )}
    </div>
  );
}
