// @mostly-tiny/ui — shared app-chrome kit. Exposes window.ForgeKit.
//
//   const { AppHeader, AccountMenu, SignInButton } = window.ForgeKit;
//
// Authored the same way an app's shared.jsx is: plain JSX against window.React +
// the design-system globals, compiled in-browser by Babel-standalone. NOT bundled
// and NOT part of the generated _ds_bundle.js — `mt-install-kit` copies it into
// each app's web/_kit/. Pairs with forge-kit.css.
//
// PRESENTATIONAL ONLY — no firebase, no billing. Identity data + actions are
// passed in as props (the app wires them from its auth/api layer); billing actions
// are injected via AccountMenu's `items`. The same prop contract is mirrored by the
// ES-module @mostly-tiny/auth/client AccountMenu for future bundler products.
//
// Everything lives inside an IIFE so the top-level `const React` can't collide with
// the same declaration in an app's shared.jsx (both run in global script scope).
(function () {
  const React = window.React;

  // i18n: optional `t` prop → window.ForgeI18n.t → identity. (SignInForm pattern.)
  const translator = (t) =>
    t || (window.ForgeI18n ? window.ForgeI18n.t : (k) => k);

  // DS primitives are read lazily (inside render), so a load-order race can't
  // capture an undefined namespace at module-eval time.
  const ds = () => window.ForgeDesignSystem_e40d74 || {};

  // Minimal self-contained icon set (Lucide geometry) so menu items can name an
  // icon without coupling the kit to any one product's icon set. `icon` may also
  // be a ReactNode, which passes through untouched.
  const ICONS = {
    sliders: "M4 21v-7 M4 10V3 M12 21v-9 M12 8V3 M20 21v-5 M20 12V3 M1 14h6 M9 8h6 M17 16h6",
    zap: "M13 2 3 14h9l-1 8 10-12h-9l1-8z",
    card: "M3 5h18v14H3z M3 10h18 M7 15h4",
    user: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2 M12 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8",
    logout: "M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4 M16 17l5-5-5-5 M21 12H9",
  };
  function KitIcon({ name, size = 16 }) {
    const d = ICONS[name];
    if (!d) return null;
    return (
      <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
        strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
        {d.split(" M").map((p, i) => <path key={i} d={(i ? "M" : "") + p} />)}
      </svg>
    );
  }
  const renderIcon = (icon) =>
    icon == null ? null : typeof icon === "string" ? <KitIcon name={icon} /> : icon;

  /* ── AppHeader — slot host: brand / nav / actions ───────────────────────── */
  // `nav` may be a ReactNode, or a descriptor array [{key,label,active,onClick,href}].
  function AppHeader({ brand, nav, actions, sticky = true, className = "" }) {
    const cls = ["forge-appheader", sticky ? "forge-appheader--sticky" : "", className]
      .filter(Boolean).join(" ");
    let navContent = nav;
    if (Array.isArray(nav)) {
      navContent = nav.map((it) =>
        it.href ? (
          <a key={it.key} href={it.href} data-on={it.active ? "true" : undefined}
            onClick={it.onClick}>{it.label}</a>
        ) : (
          <button key={it.key} type="button" data-on={it.active ? "true" : undefined}
            onClick={it.onClick}>{it.label}</button>
        )
      );
    }
    return (
      <header className={cls}>
        {brand}
        {nav && <nav className="forge-appheader__nav">{navContent}</nav>}
        {actions && <div className="forge-appheader__actions">{actions}</div>}
      </header>
    );
  }

  /* ── AccountMenu — avatar trigger + dropdown ────────────────────────────── */
  // Props: name, email, photoURL, plan (label or ReactNode), items[], onSignOut, align, t.
  // items: [{ id, label, icon?, onClick, href?, variant?, disabled? }] — DS MenuItems.
  // The component owns open/close (no DS Menu behavior) and always renders sign-out
  // last, after a separator, as the destructive row.
  function AccountMenu({ name, email, photoURL, plan, items = [], onSignOut, align = "end", side = "down", t }) {
    const tr = translator(t);
    const F = ds();
    const { Avatar, Menu, MenuItem, MenuSeparator, Badge } = F;
    const [open, setOpen] = React.useState(false);
    const ref = React.useRef(null);

    React.useEffect(() => {
      if (!open) return undefined;
      const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
      const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
      document.addEventListener("mousedown", onDoc);
      document.addEventListener("keydown", onKey);
      return () => {
        document.removeEventListener("mousedown", onDoc);
        document.removeEventListener("keydown", onKey);
      };
    }, [open]);

    const display = name || email || "";
    const initial = (display || "?").trim().charAt(0).toUpperCase() || "?";
    const close = () => setOpen(false);

    const onItem = (it) => () => {
      close();
      if (it.disabled) return;
      if (it.href) window.location.assign(it.href);
      else if (it.onClick) it.onClick();
    };

    return (
      <div className="forge-account" ref={ref}>
        <button type="button" className="forge-account__trigger"
          aria-haspopup="menu" aria-expanded={open} onClick={() => setOpen((v) => !v)}>
          {Avatar
            ? <Avatar name={display} src={photoURL || ""} size="sm" />
            : <span className="forge-avatar forge-avatar--sm">{initial}</span>}
          {email && <span className="forge-account__email">{email}</span>}
        </button>

        {open && Menu && (
          <Menu className={"forge-account__menu forge-account__menu--" + align + (side === "up" ? " forge-account__menu--up" : "")}>
            <div className="forge-account__head">
              {Avatar
                ? <Avatar name={display} src={photoURL || ""} />
                : <span className="forge-avatar">{initial}</span>}
              <span className="forge-account__head-text">
                {name && <span className="forge-account__head-name">{name}</span>}
                {email && <span className="forge-account__head-email">{email}</span>}
              </span>
              {plan && Badge && (
                <span className="forge-account__head-plan"><Badge tone="accent">{plan}</Badge></span>
              )}
            </div>
            <MenuSeparator />
            {items.map((it) => (
              <MenuItem key={it.id} icon={renderIcon(it.icon)}
                danger={it.variant === "danger"} disabled={it.disabled} onClick={onItem(it)}>
                {it.label}
              </MenuItem>
            ))}
            {onSignOut && <MenuSeparator />}
            {onSignOut && (
              <MenuItem icon={renderIcon("logout")} danger
                onClick={() => { close(); onSignOut(); }}>
                {tr("auth.account.signout")}
              </MenuItem>
            )}
          </Menu>
        )}
      </div>
    );
  }

  /* ── SignInButton — thin DS Button wrapper for marketing/landing chrome ──── */
  function SignInButton({ onClick, href, variant = "secondary", size = "md", label, t, ...rest }) {
    const tr = translator(t);
    const { Button } = ds();
    const text = label || tr("auth.account.signin");
    const handle = (e) => {
      if (href) { e.preventDefault(); window.location.assign(href); }
      if (onClick) onClick(e);
    };
    return Button
      ? <Button variant={variant} size={size} onClick={handle} {...rest}>{text}</Button>
      : <button className="forge-btn forge-btn--secondary" onClick={handle} {...rest}>{text}</button>;
  }

  /* ── SignupSurvey — post-signup onboarding modal ────────────────────────────
     Shown ONCE on first dashboard load after sign-up (never in the magic-link
     form — an extra field on the conversion step costs signups). Collects display
     name + self-reported "how did you hear about us?" (catches what utm can't:
     word-of-mouth, podcasts). Presentational: the app wires onSubmit → the
     setSignupSurvey callable and onSkip → dismiss.
       channels: [{ value, label }] — value MUST match auth.config signupSurveyChannels.
       onSubmit({ display_name?, heard_about_us?, heard_about_us_detail? }) → Promise
       onSkip() → void */
  function SignupSurvey({ channels = [], onSubmit, onSkip, t }) {
    const tr = translator(t);
    const { Button } = ds();
    const [name, setName] = React.useState("");
    const [channel, setChannel] = React.useState("");
    const [detail, setDetail] = React.useState("");
    const [busy, setBusy] = React.useState(false);
    const [error, setError] = React.useState(null);
    const showDetail = channel === "other";

    const submit = async (e) => {
      e.preventDefault();
      setBusy(true);
      setError(null);
      const payload = {};
      if (name.trim()) payload.display_name = name.trim();
      if (channel) payload.heard_about_us = channel;
      if (showDetail && detail.trim()) payload.heard_about_us_detail = detail.trim();
      try {
        // Empty form → Continue behaves as Skip (don't fire an empty callable).
        if (Object.keys(payload).length === 0) { if (onSkip) onSkip(); return; }
        if (onSubmit) await onSubmit(payload);
      } catch (err) {
        if (window.console) console.error("SignupSurvey: save failed", err);
        setError(tr("auth.survey.error"));
        setBusy(false);
        return;
      }
      setBusy(false);
    };

    return (
      <div className="forge-survey" role="dialog" aria-modal="true">
        <form className="forge-survey__card" onSubmit={submit}>
          <h2 className="forge-survey__title">{tr("auth.survey.title")}</h2>
          <p className="forge-survey__body">{tr("auth.survey.body")}</p>

          <label className="forge-survey__label" htmlFor="forge-survey-name">{tr("auth.survey.nameLabel")}</label>
          <input id="forge-survey-name" className="forge-survey__input" type="text" autoComplete="name" value={name}
            placeholder={tr("auth.survey.namePlaceholder")} disabled={busy}
            onChange={(e) => setName(e.target.value)} />

          <label className="forge-survey__label" htmlFor="forge-survey-channel">{tr("auth.survey.channelLabel")}</label>
          <select id="forge-survey-channel" className="forge-survey__select" value={channel}
            disabled={busy} onChange={(e) => setChannel(e.target.value)}>
            <option value="">{tr("auth.survey.channelPlaceholder")}</option>
            {channels.map((c) => <option key={c.value} value={c.value}>{c.label}</option>)}
          </select>

          {showDetail && (
            <input className="forge-survey__input" type="text" value={detail} autoFocus disabled={busy}
              placeholder={tr("auth.survey.detailPlaceholder")}
              onChange={(e) => setDetail(e.target.value)} />
          )}

          {error && <p className="forge-survey__error">{error}</p>}

          <div className="forge-survey__actions">
            <button type="button" className="forge-survey__skip" onClick={onSkip} disabled={busy}>
              {tr("auth.survey.skip")}
            </button>
            {Button
              ? <Button type="submit" variant="primary" disabled={busy}>{busy ? tr("auth.survey.submitting") : tr("auth.survey.submit")}</Button>
              : <button type="submit" className="forge-btn forge-btn--primary" disabled={busy}>{busy ? tr("auth.survey.submitting") : tr("auth.survey.submit")}</button>}
          </div>
        </form>
      </div>
    );
  }

  /* ── PlanCards — generic, config-driven pricing grid ─────────────────────────
     The single source of plan-card UI for the whole factory: the marketing
     pricing section renders it inline, the in-app UpgradeDialog wraps it in a
     modal. PRESENTATIONAL — all data + copy come from props, so every product
     drives it from its own billing/i18n.
       plans: [{ key, name, priceMonthly, priceAnnual?, tagline?, features?, featured?,
                 badge?, cta? }]   (prices = NUMBERS, per-month, whole units)
       features — array of items, each a plain string OR { text, highlight } to
                 emphasise a differentiating feature (e.g. Flows on Pro/Team, so it's
                 obvious cheaper tiers lack it).
       annual / onAnnualChange — controlled monthly⇄annual toggle (annual shows
                 priceAnnual as the per-month figure billed yearly).
       currentPlan — key to mark as the active plan (its CTA becomes disabled).
       onSelect(key, annual) — fired by a card CTA; MAY return a Promise. While it's
                 pending the clicked button shows a spinner (e.g. the checkout
                 round-trip) and all CTAs disable. No onSelect & no plan.href ⇒ the
                 card renders without a button (waitlist / display-only).
       labels — all chrome copy, already localized by the caller (English fallbacks). */
  const CheckIcon = () => (
    <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
      strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
  );
  const Spinner = () => <span className="forge-spinner" aria-hidden="true" />;
  function PlanCards({ plans = [], annual = false, onAnnualChange, currentPlan, onSelect, labels = {}, currency = "$", showToggle = true }) {
    const { Button } = ds();
    const [busyKey, setBusyKey] = React.useState(null);
    const L = Object.assign({
      monthly: "Monthly", annual: "Annual", save: "", perMonth: "/ mo",
      billedYearly: "/ mo, billed yearly", current: "Current plan",
      choose: (n) => "Choose " + n, popular: "Most popular",
    }, labels);
    const cta = (p) => (typeof p.cta === "string" ? p.cta : (typeof L.choose === "function" ? L.choose(p.name) : L.choose));
    // Click → show immediate feedback. If onSelect returns a promise we keep the
    // spinner until it settles (usually the page navigates to Checkout first).
    const handleSelect = (p) => {
      if (busyKey || !onSelect) return;
      setBusyKey(p.key);
      let r;
      try { r = onSelect(p.key, annual); } catch (e) { setBusyKey(null); return; }
      if (r && typeof r.then === "function") r.then(() => {}, () => {}).then(() => setBusyKey(null));
      else if (!r) setBusyKey(null); // sync no-op → don't get stuck
    };
    return (
      <div className="forge-plans-wrap">
        {showToggle && onAnnualChange && (
          <div className="forge-billtoggle" role="tablist">
            <button type="button" role="tab" data-on={!annual} onClick={() => onAnnualChange(false)}>{L.monthly}</button>
            <button type="button" role="tab" data-on={annual} onClick={() => onAnnualChange(true)}>
              {L.annual}{L.save ? <span className="forge-billtoggle__save"> {L.save}</span> : null}
            </button>
          </div>
        )}
        <div className="forge-plans" style={{ "--forge-plan-cols": plans.length || 1 }}>
          {plans.map((p) => {
            const isCurrent = currentPlan && p.key === currentPlan;
            const amt = annual && p.priceAnnual != null ? p.priceAnnual : p.priceMonthly;
            const paid = (p.priceMonthly || 0) > 0;
            const showCta = isCurrent || onSelect || p.href;
            return (
              <div key={p.key} className={"forge-plan" + (p.featured ? " forge-plan--featured" : "") + (isCurrent ? " forge-plan--current" : "")}>
                {p.featured && <span className="forge-plan__tag">{p.badge || L.popular}</span>}
                <div className="forge-plan__name">{p.name}</div>
                <div className="forge-plan__price">
                  <span className="forge-plan__amt">{currency}{amt}</span>
                  <span className="forge-plan__per">{paid && annual ? L.billedYearly : L.perMonth}</span>
                </div>
                {p.tagline && <p className="forge-plan__desc">{p.tagline}</p>}
                {Array.isArray(p.features) && p.features.length > 0 && (
                  <ul className="forge-plan__feats">
                    {p.features.map((f, i) => {
                      const text = typeof f === "string" ? f : (f && f.text);
                      const hl = typeof f === "object" && f && f.highlight;
                      return <li key={i} className={hl ? "forge-plan__feat--hl" : undefined}><CheckIcon />{text}</li>;
                    })}
                  </ul>
                )}
                {showCta && (() => {
                  const busy = busyKey === p.key;
                  const anyBusy = !!busyKey;
                  if (isCurrent) {
                    return <div className="forge-plan__cta">{Button
                      ? <Button variant="secondary" block disabled>{L.current}</Button>
                      : <button className="forge-btn forge-btn--secondary" disabled>{L.current}</button>}</div>;
                  }
                  const variant = p.featured ? "primary" : "secondary";
                  return (
                    <div className="forge-plan__cta">
                      {Button
                        ? <Button variant={variant} block disabled={anyBusy} iconLeft={busy ? <Spinner /> : undefined} onClick={() => handleSelect(p)}>{cta(p)}</Button>
                        : <button className={"forge-btn forge-btn--" + variant} disabled={anyBusy} onClick={() => handleSelect(p)}>{busy ? "…" : cta(p)}</button>}
                    </div>
                  );
                })()}
              </div>
            );
          })}
        </div>
      </div>
    );
  }

  /* ── UpgradeDialog — in-app modal wrapper around PlanCards ───────────────────
     Opens from any "Upgrade" entry point; the interval is chosen HERE (Stripe
     Checkout can't switch it). Defaults to monthly so a click is never a surprise
     annual charge. onSelect(key, annual) → the app's checkout. */
  function UpgradeDialog({ plans = [], currentPlan, defaultAnnual = false, onSelect, onClose, labels = {}, title }) {
    const [annual, setAnnual] = React.useState(!!defaultAnnual);
    React.useEffect(() => {
      const onKey = (e) => { if (e.key === "Escape" && onClose) onClose(); };
      document.addEventListener("keydown", onKey);
      return () => document.removeEventListener("keydown", onKey);
    }, [onClose]);
    return (
      <div className="forge-upgrade" role="dialog" aria-modal="true">
        <div className="forge-upgrade__scrim" onClick={onClose} />
        <div className="forge-upgrade__panel">
          <button type="button" className="forge-upgrade__close" aria-label="Close" onClick={onClose}>
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M18 6 6 18M6 6l12 12" /></svg>
          </button>
          {title && <h2 className="forge-upgrade__title">{title}</h2>}
          <PlanCards plans={plans} annual={annual} onAnnualChange={setAnnual}
            currentPlan={currentPlan} onSelect={onSelect} labels={labels} />
        </div>
      </div>
    );
  }

  /* ── Endorsement — the shared "A Mostly Tiny product" footer line ───────────
     One source of truth for the umbrella endorsement every product shows in its
     footer (design-system constant, see ui readme). Renders the umbrella mark +
     the linked "A Mostly Tiny product" → mostlytiny.io, then a trailing bit.
     Props (all optional):
       size  — mark size (px), default 16
       year  — © year for the default tail, default 2026
       label — override the linked text (e.g. a product's localized string);
               defaults to "A <strong>Mostly Tiny</strong> product"
       tail  — override the trailing text after the link; defaults to
               "· © <year> Mostly Tiny Ltd". Pass e.g. a terms line on auth. */
  function MostlyTinyMark({ size = 16 }) {
    return (
      <svg width={size} height={size} viewBox="0 0 100 100" aria-hidden="true">
        <rect x="3" y="3" width="94" height="94" rx="27" fill="var(--fg)" />
        <circle cx="74" cy="74" r="5" fill="var(--bg-elev)" />
      </svg>
    );
  }
  function Endorsement({ size = 16, year = 2026, label, tail }) {
    const lbl = label !== undefined ? label : (<>A <strong>Mostly Tiny</strong> product</>);
    const trail = tail !== undefined ? tail : `· © ${year} Mostly Tiny Ltd`;
    return (
      <span className="forge-endorse">
        <a className="forge-endorse__link" href="https://mostlytiny.io">
          <MostlyTinyMark size={size} /> {lbl}
        </a>
        {trail && <span className="forge-endorse__tail">{trail}</span>}
      </span>
    );
  }

  window.ForgeKit = { AppHeader, AccountMenu, SignInButton, SignupSurvey, PlanCards, UpgradeDialog, Endorsement, MostlyTinyMark };
})();
