"use client";

import { useState, useTransition, useRef, useCallback } from "react";
import { createPortal } from "react-dom";
import confetti from "canvas-confetti";
import {
  Dialog,
  DialogContent,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";
import type { BangStatsDto } from "../server/queries";
import { addBang } from "../server/actions";

// ─── Celebration engine ───────────────────────────────────────────────────────

function launchFirework(originX: number, originY: number) {
  confetti({
    particleCount: 80,
    startVelocity: 45,
    spread: 360,
    origin: { x: originX, y: originY },
    colors: ["#ff0", "#f0f", "#0ff", "#f60", "#0f6", "#06f"],
    shapes: ["circle", "square"],
    gravity: 0.8,
    scalar: 1.2,
    ticks: 200,
  });
}

function launchSideCannon(side: "left" | "right") {
  confetti({
    particleCount: 120,
    angle: side === "left" ? 60 : 120,
    spread: 55,
    origin: { x: side === "left" ? 0 : 1, y: 0.65 },
    colors: ["#ff4e50", "#fc913a", "#f9d423", "#ede574", "#e1f5c4"],
    shapes: ["circle", "square", "star"],
    scalar: 1.1,
    ticks: 300,
  });
}

function launchStarBurst() {
  const defaults = {
    spread: 360,
    ticks: 100,
    gravity: 0,
    decay: 0.94,
    startVelocity: 30,
    shapes: ["star"] as confetti.Shape[],
    colors: ["FFE400", "FFBD00", "E89400", "FFCA6C", "FDFFB8"],
  };
  function shoot() {
    confetti({ ...defaults, particleCount: 40, scalar: 1.2, shapes: ["star"] });
    confetti({ ...defaults, particleCount: 15, scalar: 0.75, shapes: ["circle"] });
  }
  shoot();
  setTimeout(shoot, 100);
  setTimeout(shoot, 200);
}

function synthFireworkSound() {
  try {
    const ctx = new AudioContext();
    // Rising "pew" tone
    const osc = ctx.createOscillator();
    const gain = ctx.createGain();
    osc.connect(gain);
    gain.connect(ctx.destination);
    osc.frequency.setValueAtTime(200, ctx.currentTime);
    osc.frequency.exponentialRampToValueAtTime(900, ctx.currentTime + 0.12);
    gain.gain.setValueAtTime(0.35, ctx.currentTime);
    gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.18);
    osc.start(ctx.currentTime);
    osc.stop(ctx.currentTime + 0.18);

    // Noise "boom"
    const bufferSize = ctx.sampleRate * 0.4;
    const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
    const data = buffer.getChannelData(0);
    for (let i = 0; i < bufferSize; i++) data[i] = Math.random() * 2 - 1;
    const noise = ctx.createBufferSource();
    noise.buffer = buffer;
    const noiseGain = ctx.createGain();
    const bpFilter = ctx.createBiquadFilter();
    bpFilter.type = "bandpass";
    bpFilter.frequency.value = 150;
    noise.connect(bpFilter);
    bpFilter.connect(noiseGain);
    noiseGain.connect(ctx.destination);
    noiseGain.gain.setValueAtTime(0.6, ctx.currentTime + 0.15);
    noiseGain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.6);
    noise.start(ctx.currentTime + 0.15);
    noise.stop(ctx.currentTime + 0.6);
  } catch {
    // Autoplay policy blocked — silently skip
  }
}

type EmojiParticle = {
  id: number;
  emoji: string;
  x: number;
  y: number;
  size: number;
  delay: number;
};

const CELEBRATION_EMOJIS = [
  "💥",
  "🎉",
  "🔥",
  "✨",
  "🎊",
  "🥳",
  "💦",
  "😈",
  "🎆",
  "⭐",
  "🍆",
  "💫",
  "🌟",
  "🎇",
  "🍒",
];

function EmojiOverlay({ particles }: { particles: EmojiParticle[] }) {
  if (particles.length === 0) return null;
  return createPortal(
    <div className="pointer-events-none fixed inset-0 z-[9999] overflow-hidden" aria-hidden>
      {particles.map((p) => (
        <span
          key={p.id}
          className="absolute select-none animate-emoji-float"
          style={{
            left: `${p.x}%`,
            top: `${p.y}%`,
            fontSize: `${p.size}rem`,
            animationDelay: `${p.delay}ms`,
          }}
        >
          {p.emoji}
        </span>
      ))}
    </div>,
    document.body,
  );
}

// ─── Component ────────────────────────────────────────────────────────────────

type Props = {
  stats: BangStatsDto;
  maxRecentBangs: number;
};

export function BangWidget({ stats, maxRecentBangs }: Props) {
  const [open, setOpen] = useState(false);
  const [dateValue, setDateValue] = useState(todayValue());
  const [isPending, startTransition] = useTransition();
  const [error, setError] = useState<string | null>(null);
  const [emojiParticles, setEmojiParticles] = useState<EmojiParticle[]>([]);
  const [countPop, setCountPop] = useState(false);
  const [shake, setShake] = useState(false);
  const [flash, setFlash] = useState(false);
  const particleIdRef = useRef(0);

  const spawnEmojis = useCallback(() => {
    const batch: EmojiParticle[] = Array.from({ length: 50 }, () => ({
      id: ++particleIdRef.current,
      emoji: CELEBRATION_EMOJIS[Math.floor(Math.random() * CELEBRATION_EMOJIS.length)]!,
      x: Math.random() * 100,
      y: 20 + Math.random() * 70, // keep out of very top/bottom edges so float-up is visible
      size: 1.5 + Math.random() * 2,
      delay: Math.random() * 600, // stagger spawning over 600ms so it feels like a cascade
    }));
    setEmojiParticles((prev) => [...prev, ...batch]);
    setTimeout(() => {
      const ids = new Set(batch.map((p) => p.id));
      setEmojiParticles((prev) => prev.filter((p) => !ids.has(p.id)));
    }, 2400);
  }, []);

  const triggerCelebration = useCallback(() => {
    setFlash(true);
    setTimeout(() => setFlash(false), 120);

    setShake(true);
    setTimeout(() => setShake(false), 350);

    setCountPop(true);
    setTimeout(() => setCountPop(false), 400);

    synthFireworkSound();
    spawnEmojis();
    launchStarBurst();

    launchSideCannon("left");
    setTimeout(() => launchSideCannon("right"), 150);

    setTimeout(() => launchFirework(0.3, 0.7), 200);
    setTimeout(() => launchFirework(0.7, 0.65), 450);
    setTimeout(() => launchFirework(0.5, 0.6), 700);
    setTimeout(() => launchFirework(0.2, 0.75), 950);
    setTimeout(() => launchFirework(0.8, 0.7), 1100);
    setTimeout(() => {
      launchSideCannon("left");
      launchSideCannon("right");
    }, 1300);
    setTimeout(() => launchFirework(0.5, 0.5), 1600);
  }, [spawnEmojis]);

  function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    setError(null);
    startTransition(async () => {
      try {
        await addBang({ occurredOn: dateValue });
        setOpen(false);
        triggerCelebration();
      } catch {
        setError("Something went wrong. Try again.");
      }
    });
  }

  return (
    <div className={`relative flex flex-col gap-3 overflow-hidden ${shake ? "animate-shake" : ""}`}>
      {flash && (
        <div className="pointer-events-none absolute inset-0 z-20 rounded-xl bg-white/70" />
      )}

      <EmojiOverlay particles={emojiParticles} />

      <div className="flex items-center justify-between">
        <div className="flex flex-col">
          <span
            className={`text-5xl font-bold leading-none tabular-nums ${countPop ? "animate-count-pop" : ""}`}
          >
            {stats.total}
          </span>
          <span className="text-xs text-[var(--ink-mute)] mt-1">
            {stats.total === 1 ? "bang" : "bangs"}
          </span>
        </div>

        <Dialog open={open} onOpenChange={setOpen}>
          <DialogTrigger
            render={
              <button
                type="button"
                className="btn btn-primary flex items-center gap-1.5 text-sm px-3 py-1.5"
              />
            }
          >
            <span aria-hidden>💥</span> Add Bang
          </DialogTrigger>
          <DialogContent>
            <DialogHeader>
              <DialogTitle>Record a bang</DialogTitle>
            </DialogHeader>
            <form id="bang-form" onSubmit={handleSubmit} className="flex flex-col gap-3 pt-1">
              <div className="flex flex-col gap-1">
                <label htmlFor="bang-date" className="text-sm font-medium">
                  Date
                </label>
                <input
                  id="bang-date"
                  type="date"
                  value={dateValue}
                  onChange={(e) => setDateValue(e.target.value)}
                  className="input input-sm"
                  required
                />
                <p className="text-xs text-[var(--ink-mute)]">
                  Change if you&apos;re documenting a bang from a previous day.
                </p>
              </div>
              {error && <p className="text-sm text-red-500">{error}</p>}
            </form>
            <DialogFooter showCloseButton>
              <button
                type="submit"
                form="bang-form"
                disabled={isPending}
                className="btn btn-primary btn-sm"
              >
                {isPending ? "Recording…" : "🎆 Record it"}
              </button>
            </DialogFooter>
          </DialogContent>
        </Dialog>
      </div>

      {stats.recent.length > 0 && (
        <div className="flex flex-col gap-0.5 border-t border-[var(--surface-3)] pt-2">
          <p className="text-xs font-medium text-[var(--ink-mute)] mb-1">
            Last {Math.min(stats.recent.length, maxRecentBangs)}
          </p>
          {stats.recent.map((bang) => (
            <div key={bang.id} className="flex items-center justify-between gap-2 py-0.5">
              <span className="text-sm">{formatBangDate(bang.occurredOn)}</span>
              {bang.recordedByName && (
                <span className="max-w-[40%] truncate text-xs text-[var(--ink-mute)]">
                  {bang.recordedByName}
                </span>
              )}
            </div>
          ))}
        </div>
      )}

      {stats.total === 0 && (
        <p className="text-sm text-[var(--ink-mute)]">No bangs yet. Add the first one!</p>
      )}
    </div>
  );
}

function todayValue(): string {
  const d = new Date();
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
}

function formatBangDate(iso: string): string {
  const [year, month, day] = iso.split("-").map(Number);
  const d = new Date(year!, month! - 1, day!);
  return d.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" });
}
