"use client";

import { useCallback, useEffect, useState, type ReactNode } from "react";
import { ThumbDown, ThumbUp } from "./icons";

type Reaction = "like" | "dislike";

interface Tally {
  likes: number;
  dislikes: number;
  myReaction: Reaction | null;
}

const VOTER_KEY_STORAGE = "wdt_voter_key";

/**
 * The voter key is a random id kept in this browser's localStorage. It is not an
 * identity and is never shown or sent anywhere else — it exists so a second
 * click from the same browser moves that browser's vote instead of stacking a
 * new one. crypto.randomUUID only landed in Safari 15.4, and this site still has
 * to work on iOS 15.0, hence the fallback.
 */
function getVoterKey(): string {
  let key = "";
  try {
    key = window.localStorage.getItem(VOTER_KEY_STORAGE) || "";
  } catch {
    // Private mode / storage disabled — fall through to a per-session key.
  }
  if (key) return key;

  const uuid =
    typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
      ? crypto.randomUUID()
      : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;

  try {
    window.localStorage.setItem(VOTER_KEY_STORAGE, uuid);
  } catch {
    // Not persisting is fine; the vote still records, it just won't be
    // recognised as the same voter next visit.
  }
  return uuid;
}

export function ArticleFeedback({ postSlug }: { postSlug: string }) {
  const [tally, setTally] = useState<Tally | null>(null);
  const [voterKey, setVoterKey] = useState("");
  const [pending, setPending] = useState(false);
  const [failed, setFailed] = useState(false);

  // Load current totals (and this browser's existing vote, if any) on mount.
  useEffect(() => {
    const key = getVoterKey();
    setVoterKey(key);

    let cancelled = false;
    const query = new URLSearchParams({ postSlug, voterKey: key });

    fetch(`/api/post-reactions?${query.toString()}`)
      .then((r) => (r.ok ? r.json() : Promise.reject(new Error(String(r.status)))))
      .then((data: Tally) => {
        if (!cancelled) setTally(data);
      })
      .catch(() => {
        // Counts are non-essential — show the buttons at zero rather than
        // hiding the widget or surfacing an error for a failed read.
        if (!cancelled) setTally({ likes: 0, dislikes: 0, myReaction: null });
      });

    return () => {
      cancelled = true;
    };
  }, [postSlug]);

  const vote = useCallback(
    async (reaction: Reaction) => {
      if (pending || !voterKey) return;
      if (tally && tally.myReaction === reaction) return; // already their vote

      setPending(true);
      setFailed(false);
      try {
        const response = await fetch("/api/post-reactions", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ postSlug, reaction, voterKey }),
        });
        if (!response.ok) throw new Error(String(response.status));
        setTally(await response.json());
      } catch {
        setFailed(true);
      } finally {
        setPending(false);
      }
    },
    [pending, voterKey, tally, postSlug]
  );

  const voted = tally?.myReaction ?? null;

  // Renders before the fetch resolves too, so the section doesn't pop in.
  const counts = tally ?? { likes: 0, dislikes: 0, myReaction: null };

  // w-fit, not a full-width bar: the box is only as wide as the prompt plus the
  // buttons. gap-6 between the question and the buttons is exactly twice the
  // gap-3 between the two buttons, so the pair reads as one group that belongs
  // to the question rather than as two ends of a row.
  return (
    <div className="mx-auto flex w-fit flex-col items-center gap-4 rounded-[1.5rem] border border-line bg-paper px-6 py-4 text-center shadow-soft sm:flex-row sm:gap-6 sm:text-left">
      <div>
        <p className="font-display text-base font-bold text-ink">
          Was this helpful?
        </p>
      </div>

      <div className="flex shrink-0 items-center gap-3">
        <FeedbackButton
          label="Yes, this was helpful"
          active={voted === "like"}
          disabled={pending}
          count={counts.likes}
          onClick={() => vote("like")}
        >
          <ThumbUp className="h-5 w-5" />
        </FeedbackButton>

        <FeedbackButton
          label="No, this was not helpful"
          active={voted === "dislike"}
          disabled={pending}
          count={counts.dislikes}
          onClick={() => vote("dislike")}
        >
          <ThumbDown className="h-5 w-5" />
        </FeedbackButton>
      </div>

      {failed && (
        <p className="w-full text-sm text-red-500 sm:w-auto">
          Could not save that — please try again.
        </p>
      )}
    </div>
  );
}

function FeedbackButton({
  label,
  active,
  disabled,
  count,
  onClick,
  children,
}: {
  label: string;
  active: boolean;
  disabled: boolean;
  count: number;
  onClick: () => void;
  children: ReactNode;
}) {
  return (
    <button
      type="button"
      onClick={onClick}
      disabled={disabled}
      aria-label={label}
      aria-pressed={active}
      className={`btn-pill flex items-center gap-2 border px-4 py-2 text-sm font-semibold transition-colors disabled:opacity-60 ${
        active
          ? "border-transparent bg-gradient-to-r from-leaf-deep via-teal to-sky text-white"
          : "border-line bg-paper text-ink hover:border-leaf/60"
      }`}
    >
      {children}
      <span className="tabular-nums">{count}</span>
    </button>
  );
}
