// Hooks come from the React global. Destructured at top level so the
// AppCard / DownloadButton components below can use the bare names.
const { useState, useEffect, useMemo } = React;

/* =========================================================================
 *  CTWCAD — Useful Apps catalog page (/usefulapps)
 *  -----------------------------------------------------------------------
 *  Gated behind sign-in like /install. Showcases small, free utility
 *  apps the operator publishes — each entry in USEFUL_APPS below
 *  becomes a card with download buttons per platform.
 *
 *  To add a new app:
 *    1. Drop the installer file into the hosting tree (e.g.
 *       `installers/foo-setup.exe`) so Firebase Hosting serves it
 *       at `https://ctwcad.com/installers/foo-setup.exe`. The
 *       hosting `ignore` list in firebase.json doesn't exclude
 *       /installers, so anything you put there ships on the next
 *       deploy.
 *    2. Append an entry to USEFUL_APPS below with the public URL.
 *    3. Deploy: `firebase deploy --only hosting`.
 * =======================================================================*/

const USEFUL_APPS = [
  {
    id: "ai-audio-recorder",
    name: "AI Audio Recorder",
    tagline: "One-click record, transcribe, and summarize anything you hear.",
    description: "Captures system audio + mic, runs it through a local pipeline, and spits out a clean transcript with timestamps and a TL;DR. The kind of thing transcription services charge per-minute for.",
    icon: "mic",
    coverHue: 264,
    wasPrice: "$15/mo elsewhere",
    platforms: [
      { os: "windows", url: "/installers/AIAudioRecorder-Setup-1.0.2.exe",
        sizeBytes: 70_420_768, version: "1.0.2" },
    ],
  },
];

function UsefulAppsView({ signedIn }) {
  const M = window.FramerMotion?.motion;
  if (!M) return null;

  // Parallax mouse-track on the hero background — same pattern as
  // InstallView so the two pages feel like siblings. Springs smooth
  // out the jitter.
  const mx = window.FramerMotion.useMotionValue(0);
  const my = window.FramerMotion.useMotionValue(0);
  const sx = window.FramerMotion.useSpring(mx, { stiffness: 90, damping: 22 });
  const sy = window.FramerMotion.useSpring(my, { stiffness: 90, damping: 22 });
  const onMove = (e) => {
    mx.set((e.clientX / window.innerWidth - 0.5) * 24);
    my.set((e.clientY / window.innerHeight - 0.5) * 18);
  };

  const goHome = (e) => {
    if (e) e.preventDefault();
    if (typeof window.CTWCAD_NAV === "function") window.CTWCAD_NAV("/");
    else window.location.assign("/");
  };

  // Letter-staggered hero headline. Splits the title into spans so each
  // letter can animate in independently. Whitespace stays as a real
  // space so the line breaks naturally on narrow screens.
  const HERO_TITLE = "Useful Apps.";
  const titleLetters = HERO_TITLE.split("").map((ch, i) => ({ ch, key: i }));

  return (
    <div className="ct-apps-page" onMouseMove={onMove}>
      {/* Animated background — matches the InstallView vibe. */}
      <div className="ct-apps-bg" aria-hidden="true">
        <M.div className="ct-apps-orb ct-apps-orb-a"
          animate={{ x: [0, 60, -20, 0], y: [0, -30, 20, 0] }}
          transition={{ duration: 18, repeat: Infinity, ease: "easeInOut" }} />
        <M.div className="ct-apps-orb ct-apps-orb-b"
          animate={{ x: [0, -50, 40, 0], y: [0, 40, -20, 0] }}
          transition={{ duration: 22, repeat: Infinity, ease: "easeInOut" }} />
        <M.svg className="ct-apps-grid" aria-hidden="true"
          style={{ x: sx, y: sy }}>
          <defs>
            <pattern id="apps-grid-pattern" width="40" height="40" patternUnits="userSpaceOnUse">
              <path d="M 40 0 L 0 0 0 40" fill="none" stroke="currentColor"
                    strokeWidth="0.4" opacity="0.18"/>
            </pattern>
          </defs>
          <rect width="100%" height="100%" fill="url(#apps-grid-pattern)"/>
        </M.svg>
      </div>

      {/* Top nav bar — minimal so the page stays focused. */}
      <header className="ct-apps-nav">
        <a href="/" onClick={goHome} className="ct-apps-brand">
          <Brandmark size={22}/>
        </a>
        <a href="/" onClick={goHome} className="ct-apps-back">
          {signedIn ? "Back to app" : "Sign in"} <Icon name="arrow-right" size={12}/>
        </a>
      </header>

      {/* Hero */}
      <section className="ct-apps-hero">
        <M.div className="ct-eyebrow ct-mono"
          initial={{ opacity: 0, y: 6 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.5, ease: [0.22, 1, 0.36, 1] }}>
          UTILITY DROPS · FREE
        </M.div>
        <h1 className="ct-apps-title" aria-label={HERO_TITLE}>
          {titleLetters.map((l, i) => (
            <M.span key={l.key}
              initial={{ opacity: 0, y: 18 }}
              animate={{ opacity: 1, y: 0 }}
              transition={{ delay: 0.18 + i * 0.04, duration: 0.55, ease: [0.22, 1, 0.36, 1] }}
              style={{ display: "inline-block", whiteSpace: l.ch === " " ? "pre" : "normal" }}>
              {l.ch}
            </M.span>
          ))}
        </h1>
        <M.p className="ct-apps-sub"
          initial={{ opacity: 0, y: 10 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ delay: 0.7, duration: 0.55, ease: [0.22, 1, 0.36, 1] }}>
          A growing set of small free tools for the problems that take an
          hour and usually cost twenty bucks. CAD-adjacent, sometimes
          random, always trying to save you time. New ones land here
          whenever I finish them — bookmark the page.
        </M.p>
      </section>

      {/* Apps grid OR empty state */}
      {USEFUL_APPS.length === 0 ? (
        <M.section className="ct-apps-empty"
          initial={{ opacity: 0, y: 14 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ delay: 0.9, duration: 0.6, ease: [0.22, 1, 0.36, 1] }}>
          <div className="ct-apps-empty-pulse" aria-hidden="true">
            <M.div className="ct-apps-empty-ring"
              animate={{ scale: [1, 1.4, 1], opacity: [0.6, 0, 0.6] }}
              transition={{ duration: 2.4, repeat: Infinity, ease: "easeOut" }}/>
            <Icon name="package" size={28}/>
          </div>
          <div className="ct-apps-empty-title">First apps land soon.</div>
          <div className="ct-apps-empty-sub ct-dim">
            I'm finishing the first batch of installers — Windows builds
            first, others where it makes sense. This page will fill up as
            things ship. Each tool is free, no account required to use it
            once installed.
          </div>
          <div className="ct-apps-empty-bullets">
            <div className="ct-apps-empty-bullet">
              <Icon name="zap" size={14}/>
              <span>One tool per problem. No subscriptions.</span>
            </div>
            <div className="ct-apps-empty-bullet">
              <Icon name="lock" size={14}/>
              <span>Local-first. Your files stay on your machine.</span>
            </div>
            <div className="ct-apps-empty-bullet">
              <Icon name="package" size={14}/>
              <span>Signed installers, no nag screens.</span>
            </div>
          </div>
        </M.section>
      ) : (
        <section className="ct-apps-grid" aria-label="Useful apps">
          {USEFUL_APPS.map((app, i) => (
            <AppCard key={app.id} app={app} index={i}/>
          ))}
        </section>
      )}

      {/* Footer CTA — invite ideas. */}
      <section className="ct-apps-cta">
        <M.div
          initial={{ opacity: 0, y: 8 }}
          whileInView={{ opacity: 1, y: 0 }}
          viewport={{ once: true, margin: "-60px" }}
          transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
          className="ct-apps-cta-card">
          <Icon name="lightbulb" size={20}/>
          <div className="ct-apps-cta-text">
            <div className="ct-apps-cta-h">Have an idea for one?</div>
            <div className="ct-dim ct-apps-cta-sub">
              If there's a tedious thing you do every week and the existing
              software for it costs more than it should, send me a note.
              The good ones turn into entries on this page.
            </div>
          </div>
          <a className="ct-btn ct-btn-primary"
             href="mailto:sethbenricks@gmail.com?subject=Useful%20app%20idea">
            <Icon name="mail" size={13}/>
            <span>Pitch an app</span>
          </a>
        </M.div>
      </section>

      <footer className="ct-apps-footer">
        <Brandmark size={18}/>
        <span className="ct-dim ct-mono">© 2026 CTWCAD · Useful Apps</span>
        <span className="ct-apps-footer-links ct-dim">
          <a href="/legal/privacy/" target="_blank" rel="noopener noreferrer">
            Privacy
          </a>
          <a href="mailto:sethbenricks@gmail.com">Contact</a>
        </span>
      </footer>
    </div>
  );
}

/* ---------- One app card ----------------------------------------- */
function AppCard({ app, index = 0 }) {
  const M = window.FramerMotion.motion;
  const fmt = (n) => {
    if (!n && n !== 0) return "";
    if (n < 1024) return n + " B";
    if (n < 1024 * 1024) return (n / 1024).toFixed(0) + " KB";
    if (n < 1024 ** 3) return (n / 1024 ** 2).toFixed(1) + " MB";
    return (n / 1024 ** 3).toFixed(1) + " GB";
  };
  const osLabels = { windows: "Windows", macos: "macOS", linux: "Linux", web: "Web" };
  const osIcons  = { windows: "monitor", macos: "laptop", linux: "server", web: "globe" };
  return (
    <M.div className={"ct-apps-card" + (app.comingSoon ? " is-soon" : "")}
      initial={{ opacity: 0, y: 16 }}
      whileInView={{ opacity: 1, y: 0 }}
      viewport={{ once: true, margin: "-60px" }}
      transition={{ delay: index * 0.06, duration: 0.5, ease: [0.22, 1, 0.36, 1] }}
      whileHover={{ y: -6 }}>
      <div className="ct-apps-card-cover" style={{
        background: `linear-gradient(135deg,
          oklch(0.42 0.10 ${app.coverHue || 200}) 0%,
          oklch(0.22 0.06 ${(app.coverHue || 200) + 30}) 100%)`,
      }}>
        <div className="ct-apps-card-icon">
          <Icon name={app.icon || "tool"} size={28}/>
        </div>
        {app.wasPrice && (
          <div className="ct-apps-card-price">
            <span className="ct-apps-card-price-was">{app.wasPrice}</span>
            <span className="ct-apps-card-price-now">free</span>
          </div>
        )}
      </div>
      <div className="ct-apps-card-body">
        <div className="ct-apps-card-name">{app.name}</div>
        {app.tagline && <div className="ct-apps-card-tagline ct-dim">{app.tagline}</div>}
        {app.description && (
          <div className="ct-apps-card-desc">{app.description}</div>
        )}
        <div className="ct-apps-card-platforms">
          {app.comingSoon && (
            <span className="ct-pill ct-pill-pending" style={{ alignSelf: "flex-start" }}>
              Coming soon
            </span>
          )}
          {!app.comingSoon && (app.platforms || []).map((p) => (
            <DownloadButton key={p.os}
                            app={app}
                            platform={p}
                            label={osLabels[p.os] || p.os}
                            iconName={osIcons[p.os] || "download"}
                            sizeText={p.sizeBytes ? fmt(p.sizeBytes) : ""}/>
          ))}
        </div>
      </div>
    </M.div>
  );
}

/* ---------- Download button with confetti celebration ------------- *
 * Native <a download> still fires the browser download. On click we
 * also stage a celebration: 14 colored particles burst out from the
 * button, the icon morphs from the OS icon → spinning loader → check,
 * and a toast lands in the global toast manager. ~1.6 s total before
 * the button resets to its normal state.
 * ----------------------------------------------------------------- */
const _CONFETTI_COLORS = [
  "var(--ct-accent)",
  "#fcd34d", // amber
  "#6ee7b7", // mint
  "#93c5fd", // sky
  "#fca5a5", // rose
  "#c4b5fd", // lavender
];
function DownloadButton({ app, platform, label, iconName, sizeText }) {
  const M = window.FramerMotion.motion;
  const A = window.FramerMotion.AnimatePresence;
  const [phase, setPhase] = useState("idle"); // idle | celebrating | done
  // Stable particle config so each render of the burst plays out
  // identically — recomputing on every animate frame would jitter.
  const particles = useMemo(() => {
    const out = [];
    for (let i = 0; i < 14; i++) {
      const angle = (i / 14) * Math.PI * 2 + (Math.random() - 0.5) * 0.4;
      const dist = 60 + Math.random() * 100;
      out.push({
        i,
        dx: Math.cos(angle) * dist,
        dy: Math.sin(angle) * dist - 20, // bias upward so they "pop"
        rot: (Math.random() - 0.5) * 360,
        size: 4 + Math.random() * 4,
        color: _CONFETTI_COLORS[i % _CONFETTI_COLORS.length],
        duration: 0.8 + Math.random() * 0.5,
      });
    }
    return out;
  }, [phase === "celebrating"]); // re-roll every burst

  const onClick = () => {
    if (phase !== "idle") return;
    setPhase("celebrating");
    // Toast for in-app feedback. The global toast manager picks this
    // up and renders it bottom-right with the matching icon.
    try {
      window.dispatchEvent(new CustomEvent("ctwcad:toast", {
        detail: {
          message: `Downloading ${app.name}${platform.version ? ` v${platform.version}` : ""} — enjoy!`,
          icon: "download",
        },
      }));
    } catch {}
    setTimeout(() => setPhase("done"), 900);
    setTimeout(() => setPhase("idle"), 1700);
  };

  return (
    <a
      className={"ct-apps-platform-btn ct-apps-dl" +
        (phase !== "idle" ? " is-celebrating" : "")}
      href={platform.url}
      download
      title={`Download for ${label}${platform.version ? ` (v${platform.version})` : ""}`}
      onClick={onClick}>
      {/* Confetti particles. Only mounted while celebrating so they
          play once per click and unmount cleanly. */}
      <A>
        {phase === "celebrating" && (
          <span className="ct-apps-confetti" aria-hidden="true">
            {particles.map((p) => (
              <M.span key={p.i}
                className="ct-apps-confetti-dot"
                style={{
                  width: p.size, height: p.size, background: p.color,
                }}
                initial={{ x: 0, y: 0, opacity: 1, scale: 1, rotate: 0 }}
                animate={{ x: p.dx, y: p.dy, opacity: 0, scale: 0.4, rotate: p.rot }}
                transition={{ duration: p.duration, ease: [0.22, 1, 0.36, 1] }}
              />
            ))}
            {/* Expanding accent ring behind the particles. */}
            <M.span className="ct-apps-confetti-ring"
              initial={{ scale: 0, opacity: 0.6 }}
              animate={{ scale: 2.4, opacity: 0 }}
              transition={{ duration: 0.7, ease: "easeOut" }} />
          </span>
        )}
      </A>
      {/* Icon morph: idle → loader (briefly) → check → idle. */}
      {phase === "idle" && <Icon name={iconName} size={14}/>}
      {phase === "celebrating" && (
        <M.span style={{ display: "inline-flex" }}
          animate={{ rotate: 360 }}
          transition={{ duration: 0.9, ease: "linear", repeat: Infinity }}>
          <Icon name="loader" size={14}/>
        </M.span>
      )}
      {phase === "done" && (
        <M.span style={{ display: "inline-flex", color: "var(--ct-accent)" }}
          initial={{ scale: 0, rotate: -90 }}
          animate={{ scale: [0, 1.3, 1], rotate: 0 }}
          transition={{ duration: 0.45, ease: [0.22, 1, 0.36, 1] }}>
          <Icon name="check" size={14}/>
        </M.span>
      )}
      <span className="ct-apps-platform-os">
        {phase === "done" ? "Sent!" : label}
      </span>
      {sizeText && phase === "idle" &&
        <span className="ct-apps-platform-size ct-mono">{sizeText}</span>}
    </a>
  );
}

window.UsefulAppsView = UsefulAppsView;
window.USEFUL_APPS = USEFUL_APPS;
