/* Landing V2 — components in one file (loaded as Babel) */

/* Skip-link target id, used by the screen-reader bypass link in index.html. */
const MAIN_ID = "main";

/* Render i18n strings that use [[token]] markers as <mark> elements.
   Used by Guardian's bad/good demo so the highlighted phrases translate. */
const renderWithMarks = (str) => {
  if (!str) return null;
  const parts = str.split(/(\[\[[^\]]+\]\])/g);
  return parts.map((p, i) => {
    const m = p.match(/^\[\[(.+)\]\]$/);
    return m
      ? <mark key={i}>{m[1]}</mark>
      : <React.Fragment key={i}>{p}</React.Fragment>;
  });
};

const Nav = ({ user, onLogin, onLogout, onCta }) => {
  const { code, set, t } = useLocale();
  return (
    <nav className="nav">
      <div className="wrap nav-inner">
        <a href="#top" className="brand">scenera</a>
        <div className="nav-links">
          <a href="#work">{t("nav.work")}</a>
          <a href="#how">{t("nav.how")}</a>
          <a href="#guardian">{t("nav.guardian")}</a>
          <a href="#pricing">{t("nav.pricing")}</a>
          <a href="#faq">{t("nav.faq")}</a>
        </div>
        <div className="nav-right">
          <div className="locale-switch" role="group" aria-label="Language">
            {LOCALES.map(l => (
              <button key={l.code} type="button"
                className={`locale-btn ${code===l.code?"on":""}`}
                aria-pressed={code===l.code}
                onClick={()=>set(l.code)}>{l.label}</button>
            ))}
          </div>
          {user
            ? <button className="btn btn-ghost" onClick={onLogout}>{t("nav.logout")}</button>
            : <button className="btn btn-ghost" onClick={onLogin}>{t("nav.login")}</button>}
        </div>
      </div>
    </nav>
  );
};

/* Hero showcases a curated subset of brand reels. First entry renders as
   the large hero tile; the rest fall into a 2x2 thumb grid on the right.
   Cards in the Customers section reuse reel.1-4 by index; reel.5/6 only
   surface here as extra brand teasers without a dedicated case story.
   Once real video assets land, swap the placeholder <div> for <video>
   and remove the Sample badge. */
const HERO_REELS = [
  { gradient: "reel-r1", tagKey: "reel.1", role: "hero"  },
  { gradient: "reel-r2", tagKey: "reel.2", role: "thumb" },
  { gradient: "reel-r4", tagKey: "reel.4", role: "thumb" },
  { gradient: "reel-r5", tagKey: "reel.5", role: "thumb" },
  { gradient: "reel-r6", tagKey: "reel.6", role: "thumb" },
];

const Hero = ({ onCta }) => {
  const { t } = useLocale();
  const [active, setActive] = React.useState(0);
  const [paused, setPaused] = React.useState(false);
  const prefersReducedMotion = React.useMemo(
    () => typeof window !== "undefined" &&
          window.matchMedia &&
          window.matchMedia("(prefers-reduced-motion: reduce)").matches,
    []
  );
  React.useEffect(() => {
    if (paused || prefersReducedMotion) return;
    const id = setInterval(() => {
      setActive(i => (i + 1) % HERO_REELS.length);
    }, 4500);
    return () => clearInterval(id);
  }, [paused, prefersReducedMotion]);

  return (
    <section className="hero">
      <div className="wrap hero-inner">
        <div>
          <h1 className="display">
            {t("hero.h1.a")} <span className="accent">{t("hero.h1.b")}</span>
          </h1>
          <p className="hero-sub">{t("hero.sub")}</p>
          <div className="hero-ctas">
            <button className="btn btn-primary btn-lg" onClick={onCta}>{t("hero.cta")}</button>
          </div>
        </div>
        <div className="hero-reels"
             role="region"
             aria-roledescription="carousel"
             aria-label="Sample work"
             onMouseEnter={() => setPaused(true)}
             onMouseLeave={() => setPaused(false)}
             onFocus={() => setPaused(true)}
             onBlur={() => setPaused(false)}>
          {HERO_REELS.map((r, i) => (
            <div key={i}
                 className={`reel reel-${r.role} ${r.gradient}${i === active ? " is-active" : ""}`}
                 aria-hidden={i !== active}>
              <div className="reel-play">▶</div>
              <div className="reel-tag">{t(r.tagKey)}</div>
            </div>
          ))}
          <div className="hero-dots" role="tablist">
            {HERO_REELS.map((_, i) => (
              <button key={i}
                      type="button"
                      role="tab"
                      className={`hero-dot${i === active ? " on" : ""}`}
                      onClick={() => setActive(i)}
                      aria-selected={i === active}
                      aria-label={`Show sample ${i + 1} of ${HERO_REELS.length}`}/>
            ))}
          </div>
        </div>
      </div>
    </section>
  );
};

const Ticker = () => {
  const { t } = useLocale();
  const items = [t("tick.1"), t("tick.2"), t("tick.3"), t("tick.4")];
  const doubled = [...items, ...items];
  return (
    <div className="ticker">
      <div className="ticker-track">
        {doubled.map((s,i) => <span key={i}>{s}</span>)}
      </div>
    </div>
  );
};

/* Customers — merged section (replaces Proof + Stories). Four story-rich
   cards in a 2x2 grid. Each card pairs the brand reel with the owner's
   quote and outcome metrics. Cards 1-4 map to reel.1-4 by index. */
const STORY_KEYS = [
  { k:"a", avatar:"a1", initial:"L" },
  { k:"b", avatar:"a2", initial:"C" },
  { k:"c", avatar:"a3", initial:"M" },
  { k:"d", avatar:"a4", initial:"A" },
];

const Customers = () => {
  const { t } = useLocale();
  return (
    <section className="section tone" id="work">
      <div className="wrap">
        <div className="section-head">
          <span className="eyebrow">{t("st.eye")}</span>
          <h2 className="display">{t("st.title")}</h2>
          <p className="lede">{t("st.lede")}</p>
        </div>
        <div className="customers-grid">
          {STORY_KEYS.map(({k, avatar, initial}, i) => {
            const n = i + 1;
            return (
              <article className="story-card" key={k}>
                <div className={`reel reel-r${n}`} role="img" aria-label={t(`st.${k}.name`)}>
                  <div className="reel-play" aria-hidden="true">▶</div>
                </div>
                <div className="story-body">
                  <div className="story-tag">{t(`st.${k}.tag`)}</div>
                  <p className="story-quote">{t(`st.${k}.quote`)}</p>
                  <div className="story-meta">
                    <div className={`story-avatar ${avatar}`}>{initial}</div>
                    <div className="story-meta-text">
                      <span className="story-name">{t(`st.${k}.name`)}</span>
                      <span className="story-role">{t(`st.${k}.role`)}</span>
                    </div>
                  </div>
                  <p className="story-context">{t(`st.${k}.ctx`)}</p>
                  <div className="story-stats">
                    <div><div className="stat-num">{t(`st.${k}.s1.n`)}</div><div className="stat-label">{t(`st.${k}.s1.l`)}</div></div>
                    <div><div className="stat-num">{t(`st.${k}.s2.n`)}</div><div className="stat-label">{t(`st.${k}.s2.l`)}</div></div>
                    <div><div className="stat-num">{t(`st.${k}.s3.n`)}</div><div className="stat-label">{t(`st.${k}.s3.l`)}</div></div>
                  </div>
                </div>
              </article>
            );
          })}
        </div>
      </div>
    </section>
  );
};

/* IntersectionObserver hook — flip `is-in-view` on the wrapping element the
   first time it scrolls past the threshold. Used by Workflow step demos so
   their CSS animations play once on first sight, not on mount (when the
   user may not have reached the section yet) and not on every scroll. */
const useInView = (threshold = 0.35) => {
  const ref = React.useRef(null);
  const [seen, setSeen] = React.useState(false);
  React.useEffect(() => {
    if (seen || !ref.current) return;
    if (typeof IntersectionObserver === "undefined") { setSeen(true); return; }
    const io = new IntersectionObserver((entries) => {
      entries.forEach(e => { if (e.isIntersecting) { setSeen(true); io.disconnect(); } });
    }, { threshold });
    io.observe(ref.current);
    return () => io.disconnect();
  }, [seen, threshold]);
  return [ref, seen];
};

/* Inline-SVG micro demos under each Workflow step. Pure CSS keyframes,
   one-shot, gated by .is-in-view set by IntersectionObserver above. SVGs
   are decorative (aria-hidden); the step's h4 + p still carry semantics. */
const StepDemo1 = () => (
  /* Brief us — form skeleton: selected tier chip, three rows of "typing"
     stripes that scale-x from 0 to varying widths, each followed by a
     check that fades in once its row finishes. */
  <svg className="wf-demo" viewBox="0 0 320 110" aria-hidden="true" preserveAspectRatio="xMidYMid meet">
    <rect className="wf-d-card" x="0.5" y="0.5" width="319" height="109" rx="10"/>
    <g>
      <rect className="wf-d1-chip on" x="12"  y="14" width="62" height="20" rx="10"/>
      <rect className="wf-d1-chip"    x="82"  y="14" width="74" height="20" rx="10"/>
      <rect className="wf-d1-chip"    x="164" y="14" width="62" height="20" rx="10"/>
    </g>
    {[0.72, 0.88, 0.55].map((stop, i) => (
      <g key={i} style={{"--i": i, "--stop": stop}}>
        <rect className="wf-d1-field" x="12" y={48 + i*20} width="260" height="12" rx="3"/>
        <rect className="wf-d1-fill"  x="12" y={48 + i*20} width="260" height="12" rx="3"/>
        <circle className="wf-d1-check" cx="290" cy={54 + i*20} r="7"/>
        <path className="wf-d1-tick" d={`M286 ${54 + i*20} l3 3 l6 -6`}/>
      </g>
    ))}
  </svg>
);

const StepDemo2 = ({ tags }) => (
  /* Read intent — four photo thumbs (rounded rect with sun + horizon
     glyph), vertical scan line sweeps left-to-right across them once,
     four tag chips fade in below. Tag text comes from i18n. */
  <svg className="wf-demo" viewBox="0 0 320 110" aria-hidden="true" preserveAspectRatio="xMidYMid meet">
    <rect className="wf-d-card" x="0.5" y="0.5" width="319" height="109" rx="10"/>
    <g>
      {[0,1,2,3].map(i => (
        <g key={i} className="wf-d2-thumb" style={{"--i": i}}>
          <rect x={12 + i*76} y="14" width="60" height="38" rx="4"/>
          <circle cx={42 + i*76} cy="29" r="4"/>
          <path d={`M${12 + i*76 + 4} 46 l10 -10 l8 6 l8 -4 l12 8 z`}/>
        </g>
      ))}
      <line className="wf-d2-scan" x1="0" x2="0" y1="14" y2="52"/>
    </g>
    {tags.map((tag, i) => (
      /* 4-in-a-row tags below the 4 thumbs — each thumb visually "produces"
         one tag. Uniform rect width sidesteps per-locale string-width math. */
      <g key={i} className="wf-d2-tag" style={{"--i": i}}>
        <rect x={12 + i*76} y="68" width="60" height="18" rx="9"/>
        <text x={12 + i*76 + 30} y="77">{tag}</text>
      </g>
    ))}
  </svg>
);

/* Step 3 track labels are "machine metadata" — channel names + ISO language
   codes that stay neutral across the page's UI locale. Intentionally not in
   i18n: the editorial intent is "you're looking at the pipeline's internals". */
const STEP3_LABELS = ["VIDEO", "VOICE", "SUBS", "EN", "ZH", "ES"];

const StepDemo3 = () => (
  <svg className="wf-demo" viewBox="0 0 320 110" aria-hidden="true" preserveAspectRatio="xMidYMid meet">
    <rect className="wf-d-card" x="0.5" y="0.5" width="319" height="109" rx="10"/>
    {STEP3_LABELS.map((label, i) => {
      const y = 10 + i * 15;
      /* More aggressive stop spread — 0.55 to 1.0 — so "parallel, not
         synced" reads at a glance. Previous 0.85+ all looked full. */
      const stop = [1.00, 0.72, 0.95, 0.58, 0.85, 1.00][i];
      return (
        <g key={i} style={{"--i": i, "--stop": stop}}>
          <text className="wf-d3-label" x="12" y={y + 9}>{label}</text>
          <rect className="wf-d3-bar-bg" x="56" y={y + 2} width="244" height="8" rx="4"/>
          <rect className="wf-d3-bar-fl" x="56" y={y + 2} width="244" height="8" rx="4"/>
        </g>
      );
    })}
  </svg>
);

const StepDemo4 = ({ stamp }) => (
  /* Editor finishes — NLE timeline (six clips of varying widths) with a
     playhead that sweeps left to right; once it lands, a Guardian stamp
     scales in centered below. Decorative; semantics live in h4 + p. */
  <svg className="wf-demo" viewBox="0 0 320 110" aria-hidden="true" preserveAspectRatio="xMidYMid meet">
    <rect className="wf-d-card" x="0.5" y="0.5" width="319" height="109" rx="10"/>
    <g>
      {[
        {x:12, w:36, h:20}, {x:52, w:64, h:22}, {x:120, w:28, h:18},
        {x:152, w:80, h:24}, {x:236, w:44, h:20}, {x:284, w:24, h:18},
      ].map((c, i) => {
        /* clip body sits in a 24-unit lane (y=22..46), vertically centered.
           the inner thumb-strip reads as "first frame thumbnail", giving
           each clip something to be other than a placeholder rectangle. */
        const y = 22 + (24 - c.h) / 2;
        const thumbW = Math.min(10, c.w / 3);
        return (
          <g key={i} className="wf-d4-clip" style={{"--i": i}}>
            <rect className="wf-d4-clip-body"  x={c.x} y={y} width={c.w} height={c.h} rx="3"/>
            <rect className="wf-d4-clip-thumb" x={c.x + 1.5} y={y + 1.5} width={thumbW} height={c.h - 3} rx="1.5"/>
          </g>
        );
      })}
      <line className="wf-d4-playhead" x1="12" x2="12" y1="14" y2="52"/>
    </g>
    <g className="wf-d4-stamp">
      <rect x="92" y="68" width="136" height="26" rx="13"/>
      <text x="160" y="86" textAnchor="middle">{stamp}</text>
    </g>
  </svg>
);

const Workflow = () => {
  const { t } = useLocale();
  const [ref, seen] = useInView(0.3);
  const steps = [1,2,3,4].map(i => ({
    n: String(i).padStart(2,"0"),
    t: t(`wf.${i}.t`), d: t(`wf.${i}.d`),
  }));
  const d2Tags = [t("wf.d2.t1"), t("wf.d2.t2"), t("wf.d2.t3"), t("wf.d2.t4")];
  const d4Stamp = t("wf.d4.stamp");
  const demos = [<StepDemo1/>, <StepDemo2 tags={d2Tags}/>, <StepDemo3/>, <StepDemo4 stamp={d4Stamp}/>];
  return (
    <section className="section" id="how">
      <div className="wrap">
        <div className="section-head">
          <span className="eyebrow">{t("wf.eye")}</span>
          <h2 className="display">{t("wf.title")}</h2>
          <p className="lede">{t("wf.lede")}</p>
        </div>
        <div className={`workflow ${seen ? "is-in-view" : ""}`} ref={ref}>
          {steps.map((s,i) => (
            <div key={s.n} className={`step step-${i+1}`}>
              {demos[i]}
              <div className="step-head">
                <div className="step-num">{s.n}</div>
                <h4>{s.t}</h4>
              </div>
              <p>{s.d}</p>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
};

const Guardian = () => {
  const { t } = useLocale();
  return (
    <section className="section guardian" id="guardian">
      <div className="wrap">
        <div className="section-head">
          <span className="eyebrow">{t("gd.eye")}</span>
          <h2 className="display">{t("gd.title")}</h2>
          <p className="lede">{t("gd.lede")}</p>
        </div>
        <div className="guardian-stage">
          <div className="gd-box bad">
            <span className="gd-label">{t("gd.bad")}</span>
            <span className="gd-quote">{renderWithMarks(t("gd.demo.bad"))}</span>
          </div>
          <div className="gd-arrow" aria-hidden="true">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
              <path d="M5 12h14M13 5l7 7-7 7"/>
            </svg>
            {t("gd.arrow")}
          </div>
          <div className="gd-box good">
            <span className="gd-label">{t("gd.good")}</span>
            <ul>
              <li>{renderWithMarks(t("gd.demo.good.1"))}</li>
              <li>{renderWithMarks(t("gd.demo.good.2"))}</li>
              <li>{renderWithMarks(t("gd.demo.good.3"))}</li>
            </ul>
          </div>
        </div>
      </div>
    </section>
  );
};

const Pricing = () => {
  const { t } = useLocale();
  const tiers = [
    { id:"light",   k:"l" },
    { id:"standard",k:"s", featured:true },
    { id:"custom",  k:"c", contact:true },
  ];
  const pick = (id) => {
    document.getElementById("submit")?.scrollIntoView({behavior:"smooth"});
    window.dispatchEvent(new CustomEvent("scenera:tier", { detail: id }));
  };
  return (
    <section className="section tone" id="pricing">
      <div className="wrap">
        <div className="section-head">
          <span className="eyebrow">{t("pr.eye")}</span>
          <h2 className="display">{t("pr.title")}</h2>
          <p className="lede">{t("pr.lede")}</p>
        </div>
        <div className="pricing">
          {tiers.map(tier => (
            <div key={tier.id} className={`tier ${tier.featured?"featured":""}`}>
              {tier.featured && <span className="tier-flag">{t("pr.flag")}</span>}
              <span className="tier-label">{t(`pr.${tier.k}.lbl`)}</span>
              <div className="tier-name">{t(`pr.${tier.k}.name`)}</div>
              <div className="tier-price">{t(`pr.${tier.k}.prc`)}</div>
              <p className="tier-desc">{t(`pr.${tier.k}.desc`)}</p>
              <ul className="tier-items">
                {[1,2,3,4].map(n => {
                  const v = t(`pr.${tier.k}.${n}`);
                  return v && v !== `pr.${tier.k}.${n}` ? <li key={n}>{v}</li> : null;
                })}
              </ul>
              <button
                type="button"
                className={`btn ${tier.featured?"btn-primary":"btn-ghost"} btn-block`}
                onClick={()=>pick(tier.id)}
              >{tier.contact ? t("pr.ctaC") : t("pr.cta")}</button>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
};

const Chip = ({ on, onClick, label, sub }) => (
  <button type="button" className={`chip ${on?"on":""}`} onClick={onClick}>
    <span>{label}</span>
    <span className="chip-sub">{sub}</span>
  </button>
);

/* Tiny {var} substitutor for i18n error strings. */
const fillVars = (s, vars) => {
  if (!vars) return s;
  return Object.keys(vars).reduce((acc, k) => acc.split(`{${k}}`).join(String(vars[k])), s);
};

/* Build an ASCII-safe Supabase Storage key for ANY filename. Never rejects:
   produces a valid key whether the input is CJK ("图.jpg"), Cyrillic
   ("Логотип.png"), Spanish accents ("Café.svg"), emoji ("🌟 LOGO.png"),
   pathological control chars, or empty stems. The original name is
   preserved in assets.filename for admin / customer display — only the
   Storage path uses the sanitized form.

   Algorithm:
     1. Split on the last "." → (stem, ext). Hidden files like ".gitignore"
        (no other dot) keep the whole name as stem to preserve them.
     2. Stem: collapse non-[A-Za-z0-9._-] runs to "_", squash consecutive
        underscores, trim leading/trailing "_", cap at 100 chars.
     3. Ext: keep alphanumerics + . _ - and cap at 20 chars.
     4. If sanitized stem is empty (all-non-ASCII input), fall back to "file".
     5. Prefix with 6 base36 chars (~31 bits entropy) to avoid collisions
        when two different originals sanitize to the same string within an
        order. */
const safeStorageName = (name) => {
  const dot  = name.lastIndexOf(".");
  const stem = dot > 0 ? name.slice(0, dot) : name;
  const ext  = dot > 0 ? name.slice(dot)    : "";
  const cleanStem = stem
    .replace(/[^A-Za-z0-9._-]+/g, "_")
    .replace(/_+/g, "_")
    .replace(/^_+|_+$/g, "")
    .slice(0, 100);
  const cleanExt = ext.replace(/[^A-Za-z0-9._-]+/g, "").slice(0, 20);
  const prefix = Math.random().toString(36).slice(2, 8);
  return `${prefix}-${cleanStem || "file"}${cleanExt}`;
};

const Form = ({ user, onRequireLogin, onSubmit }) => {
  const { t, code } = useLocale();
  const [data, setData] = React.useState({
    tier:"standard", brand:"", address:"", website:"", brief:"", customDetail:"", deadline:"",
    files: [],
  });
  const [busy, setBusy] = React.useState(false);
  const [errKey, setErrKey] = React.useState("");
  const [errVars, setErrVars] = React.useState(null);
  const fileInputRef = React.useRef(null);
  const set = (k,v) => setData(d => ({...d, [k]:v}));
  React.useEffect(() => {
    const h = (e) => set("tier", e.detail);
    window.addEventListener("scenera:tier", h);
    return () => window.removeEventListener("scenera:tier", h);
  }, []);
  const isCustom = data.tier === "custom";
  /* chip sub = "<price> · <one-liner>" so the customer sees the price before
     committing. Custom keeps the localized "talk to us" copy from pr.c.prc
     instead of a number. */
  const subLight    = code==="zh"?"单平台":code==="es"?"1 plataforma":"single platform";
  const subStandard = code==="zh"?"多语 + 合规":code==="es"?"multi-idioma + cumplimiento":"multi-lang + compliance";
  const subCustom   = code==="zh"?"特殊需求":code==="es"?"a medida":"custom";
  const tiers = [
    { id:"light",    label: t("pr.l.lbl"), sub: `${t("pr.l.prc")} · ${subLight}` },
    { id:"standard", label: t("pr.s.lbl"), sub: `${t("pr.s.prc")} · ${subStandard}` },
    { id:"custom",   label: t("pr.c.lbl"), sub: `${t("pr.c.prc")} · ${subCustom}` },
  ];

  const onPickFiles = (e) => {
    const cfg = window.SCENERA_CONFIG || { MAX_FILE_MB: 50, MAX_FILES: 20 };
    const incoming = Array.from(e.target.files || []);
    const maxBytes = cfg.MAX_FILE_MB * 1024 * 1024;
    for (const f of incoming) {
      if (f.size > maxBytes) {
        setErrKey("fm.upload.tooBig");
        setErrVars({ name: f.name, size: (f.size/1024/1024).toFixed(1), max: cfg.MAX_FILE_MB });
        e.target.value = "";
        return;
      }
    }
    const merged = [...data.files, ...incoming];
    if (merged.length > cfg.MAX_FILES) {
      setErrKey("fm.upload.tooMany");
      setErrVars({ max: cfg.MAX_FILES });
      e.target.value = "";
      return;
    }
    setData(d => ({ ...d, files: merged }));
    setErrKey(""); setErrVars(null);
    e.target.value = ""; /* allow re-picking the same filename */
  };

  const removeFile = (idx) => {
    setData(d => ({ ...d, files: d.files.filter((_, i) => i !== idx) }));
  };

  const openPicker = () => {
    if (!user) { onRequireLogin(); return; }
    fileInputRef.current?.click();
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    setErrKey(""); setErrVars(null);
    if (!user) { onRequireLogin(); return; }
    /* Required-field gate. Pipeline + admin UX both rely on these three. */
    if (!data.brand.trim() || !data.address.trim() || !data.brief.trim() ||
        (isCustom && !data.customDetail.trim())) {
      setErrKey("fm.err.required");
      return;
    }

    const sb = window.sceneraSupabase;
    const cfg = window.SCENERA_CONFIG || {};
    if (!sb) { setErrKey("fm.err.submit"); return; }

    setBusy(true);
    try {
      /* Re-fetch session: trust server-side claims, not the App-level user
         prop, in case the JWT expired between login and submit. */
      const { data: sessionData } = await sb.auth.getSession();
      const uid = sessionData?.session?.user?.id;
      if (!uid) { setErrKey("fm.err.session"); onRequireLogin(); return; }

      /* 1. INSERT order. RLS allows because user_id = auth.uid(). */
      const { data: orderRow, error: orderErr } = await sb
        .from("orders")
        .insert({
          user_id:       uid,
          tier:          data.tier,
          brand:         data.brand.trim(),
          address:       data.address.trim(),
          website:       data.website.trim() || null,
          brief:         data.brief.trim(),
          custom_detail: isCustom ? data.customDetail.trim() : null,
          deadline:      data.deadline || null,
        })
        .select()
        .single();
      if (orderErr) throw orderErr;

      /* 2. Upload each file → INSERT corresponding asset row. Failure on
         any single file aborts the rest; the order row stays in 'new'
         status and admin can retry. */
      const bucket = cfg.UPLOAD_BUCKET || "client-uploads";
      for (const file of data.files) {
        /* Storage key gets sanitized to ASCII-safe; assets.filename keeps the
           original (possibly CJK / accented) name for admin-UI display. */
        const safeKey = safeStorageName(file.name);
        const path = `${orderRow.id}/${safeKey}`;
        const { error: upErr } = await sb.storage
          .from(bucket).upload(path, file, { upsert: false });
        if (upErr) {
          setErrKey("fm.err.upload"); setErrVars({ name: file.name });
          throw upErr;
        }
        const { error: aErr } = await sb.from("assets").insert({
          order_id:     orderRow.id,
          storage_path: `${bucket}/${path}`,
          filename:     file.name,
          size_bytes:   file.size,
          mime_type:    file.type || null,
        });
        if (aErr) { setErrKey("fm.err.upload"); setErrVars({ name: file.name }); throw aErr; }
      }

      /* 3. Payment routing.
         - light + standard: redirect to Stripe Checkout immediately. The
           backend creates the session via Stripe REST; Stripe handles the
           card-entry UX; webhook flips orders.status='paid' once charged.
         - custom: no Checkout (admin will email a manual quote). Stays on
           the SuccessView as before. */
      if (data.tier === "light" || data.tier === "standard") {
        const apiBase = (cfg.API_BASE || "").replace(/\/+$/, "");
        if (!apiBase) {
          setErrKey("fm.err.payConfig");
          return;
        }
        const accessToken = sessionData?.session?.access_token;
        if (!accessToken) {
          setErrKey("fm.err.session"); onRequireLogin(); return;
        }
        const checkoutResp = await fetch(
          `${apiBase}/api/orders/${orderRow.id}/checkout`,
          {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "Authorization": `Bearer ${accessToken}`,
            },
            body: JSON.stringify({}),
          }
        );
        const checkoutJson = await checkoutResp.json().catch(() => ({}));
        if (!checkoutResp.ok || !checkoutJson?.url) {
          console.error("[scenera] checkout session failed", checkoutResp.status, checkoutJson);
          setErrKey("fm.err.payCreate");
          return;
        }
        /* Hand off to Stripe-hosted Checkout. The success_url returns the
           customer to '/?payment=success&order={id}&session_id={SID}',
           caught by App-level useEffect to flip into SuccessView. */
        window.location.href = checkoutJson.url;
        return;
      }

      /* Custom tier: no immediate payment. Show success and wait for the
         admin's quote email. orderId surfaced for future "view my order". */
      onSubmit({ tier: data.tier, orderId: orderRow.id });
    } catch (err) {
      console.error("[scenera] order submit failed", err);
      if (!errKey) setErrKey("fm.err.submit");
    } finally {
      setBusy(false);
    }
  };
  return (
    <section className="section" id="submit">
      <div className="wrap">
        <div className="section-head">
          <span className="eyebrow">{t("fm.eye")}</span>
          <h2 className="display">{t("fm.title")}</h2>
          <p className="lede">{t("fm.lede")}</p>
        </div>
        <form className="form" onSubmit={handleSubmit}>
          {/* Group 1: Package selection */}
          <div className="form-group">
            <h3 className="form-group-label">{t("fm.group.package")}</h3>
            <div className="field">
              <label>{t("fm.tier")} <span className="req">*</span></label>
              <div className="chips">
                {tiers.map(tt => (
                  <Chip key={tt.id} on={data.tier===tt.id} onClick={()=>set("tier",tt.id)} label={tt.label} sub={tt.sub}/>
                ))}
              </div>
            </div>
          </div>
          {/* Group 2: Contact */}
          <div className="form-group">
            <h3 className="form-group-label">{t("fm.group.contact")}</h3>
            <div className="field">
              <label>{t("fm.email")} <span className="req">*</span></label>
              <input type="email" value={user || t("fm.email.placeholder")} readOnly />
              <span className="help">{t("fm.email.help")}</span>
            </div>
            <div className="field-row">
              <div className="field">
                <label>{t("fm.brand")} <span className="req">*</span></label>
                <input value={data.brand} onChange={e=>set("brand",e.target.value)} placeholder="Café Olé Kreuzberg"/>
              </div>
              <div className="field">
                <label>{t("fm.address")} <span className="req">*</span></label>
                <input value={data.address} onChange={e=>set("address",e.target.value)} placeholder="Wienerstraße 12, 10999 Berlin"/>
                <span className="help">{t("fm.address.help")}</span>
              </div>
            </div>
            <div className="field">
              <label>{t("fm.website")}</label>
              <input type="url" inputMode="url" value={data.website} onChange={e=>set("website",e.target.value)} placeholder="https://cafeolekreuzberg.de"/>
              <span className="help">{t("fm.website.help")}</span>
            </div>
          </div>
          {/* Group 3: Project brief + files */}
          <div className="form-group">
            <h3 className="form-group-label">{t("fm.group.project")}</h3>
            <div className="field">
              <label>{t("fm.brief")} <span className="req">*</span></label>
              <textarea value={data.brief} onChange={e=>set("brief",e.target.value)} placeholder={t("fm.brief.placeholder")} />
              <span className="help">{t("fm.brief.help")}</span>
            </div>
            {isCustom && (
              <div className="field">
                <label>{t("fm.custom")} <span className="req">*</span></label>
                <textarea
                  className="textarea-lg"
                  value={data.customDetail}
                  onChange={e=>set("customDetail",e.target.value)}
                  placeholder={t("fm.custom.placeholder")}
                />
                <span className="help">{t("fm.custom.help")}</span>
              </div>
            )}
            <div className="field">
              <label>{t("fm.deadline")}</label>
              <input type="date" value={data.deadline} onChange={e=>set("deadline",e.target.value)}/>
              <span className="help">{isCustom ? t("fm.deadline.help.c") : t("fm.deadline.help")}</span>
            </div>
            <input
              ref={fileInputRef}
              type="file"
              multiple
              style={{display:"none"}}
              onChange={onPickFiles}
              aria-hidden="true"
            />
            <button
              type="button"
              className="upload"
              onClick={openPicker}
              aria-label={t("fm.upload.t")}
              disabled={busy}
            >
              <div className="upload-icon" aria-hidden="true">📁</div>
              <div className="upload-title">{t("fm.upload.t")}</div>
              <p className="upload-sub">{t("fm.upload.s")}</p>
            </button>
            {data.files.length === 0 ? (
              <p className="help upload-empty">{t("fm.upload.empty")}</p>
            ) : (
              <ul className="upload-list" aria-label={t("fm.upload.t")}>
                {data.files.map((f, i) => (
                  <li key={`${f.name}-${i}`} className="upload-list-item">
                    <span className="upload-list-name">{f.name}</span>
                    <span className="upload-list-size">{(f.size/1024/1024).toFixed(1)} MB</span>
                    <button
                      type="button"
                      className="upload-list-remove"
                      onClick={()=>removeFile(i)}
                      aria-label={`${t("fm.upload.remove")}: ${f.name}`}
                      disabled={busy}
                    >×</button>
                  </li>
                ))}
              </ul>
            )}
          </div>
          {errKey && (
            <p className="help help-error" role="alert">
              {fillVars(t(errKey), errVars)}
            </p>
          )}
          <button type="submit" className="btn btn-primary btn-lg btn-block" disabled={busy}>
            {busy
              ? t("fm.submit.loading")
              : user ? (isCustom ? t("fm.submit.c") : t("fm.submit")) : t("fm.submit.g")}
          </button>
          {isCustom && <p className="help help-center">{t("fm.foot.c")}</p>}
        </form>
      </div>
    </section>
  );
};

const FAQ = () => {
  const { t } = useLocale();
  const items = [1,2,3,4,5,6,7].map(i => [t(`faq.q${i}`), t(`faq.a${i}`)]);
  return (
    <section className="section tone" id="faq">
      <div className="wrap">
        <div className="section-head">
          <h2 className="display">{t("faq.eye")}</h2>
        </div>
        <div className="faq">
          {items.map(([q,a]) => (
            <details className="faq-item" key={q}>
              <summary>{q}</summary>
              <p>{a}</p>
            </details>
          ))}
        </div>
      </div>
    </section>
  );
};

const Footer = () => {
  const { t } = useLocale();
  return (
    <footer className="footer">
      <div className="wrap">
        <div className="footer-grid">
          <div>
            <div className="brand">scenera</div>
            <p className="footer-tag">{t("ft.tag")}</p>
          </div>
          <div>
            <h5>{t("ft.contact")}</h5>
            <ul>
              <li><a href="mailto:hello@scenera.io">hello@scenera.io</a></li>
              <li><a href="mailto:support@scenera.io">support@scenera.io</a></li>
              <li><a href="#">LinkedIn</a></li>
            </ul>
          </div>
        </div>
      </div>
    </footer>
  );
};

/* Map Supabase Auth errors to user-facing i18n keys. Keeps prose in the
   strings table where it's already translated; the modal stays terse. */
const mapAuthError = (err) => {
  const msg = (err && (err.message || err.error_description || "")).toLowerCase();
  if (!msg) return "lg.err.network";
  if (msg.includes("invalid login") || msg.includes("invalid credentials")) return "lg.err.invalid";
  if (msg.includes("already registered") || msg.includes("user already")) return "lg.err.exists";
  if (msg.includes("password") && (msg.includes("short") || msg.includes("weak") || msg.includes("at least"))) return "lg.err.weak";
  return "lg.err.network";
};

const LoginModal = ({ onClose, onLogin }) => {
  const { t } = useLocale();
  const [email, setEmail] = React.useState("");
  const [pass, setPass] = React.useState("");
  const [touched, setTouched] = React.useState(false);
  const [busy, setBusy] = React.useState(false);
  const [errKey, setErrKey] = React.useState("");
  const emailRef = React.useRef(null);
  const previouslyFocused = React.useRef(null);

  const sb = typeof window !== "undefined" ? window.sceneraSupabase : null;
  const valid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim()) && pass.length >= 6;

  React.useEffect(() => {
    previouslyFocused.current = document.activeElement;
    emailRef.current?.focus();
    const onKey = (e) => { if (e.key === "Escape" && !busy) onClose(); };
    document.addEventListener("keydown", onKey);
    const prevOverflow = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => {
      document.removeEventListener("keydown", onKey);
      document.body.style.overflow = prevOverflow;
      previouslyFocused.current?.focus?.();
    };
  }, [onClose, busy]);

  const run = async (mode) => {
    /* mode: "signin" | "signup" | "google" */
    setErrKey("");
    if (!sb) { setErrKey("lg.err.network"); return; }
    if (mode !== "google" && !valid) {
      setTouched(true); emailRef.current?.focus(); return;
    }
    setBusy(true);
    try {
      if (mode === "google") {
        const { error } = await sb.auth.signInWithOAuth({
          provider: "google",
          options: { redirectTo: window.location.origin + "/" },
        });
        if (error) throw error;
        /* OAuth flow redirects away; UI state continues after redirect-back
           via the App-level onAuthStateChange subscription. */
        return;
      }
      const fn = mode === "signin" ? "signInWithPassword" : "signUp";
      const { data, error } = await sb.auth[fn]({
        email: email.trim(),
        password: pass,
      });
      if (error) throw error;
      const sessionEmail = data?.user?.email || email.trim();
      onLogin(sessionEmail);
    } catch (err) {
      setErrKey(mapAuthError(err));
    } finally {
      setBusy(false);
    }
  };

  const onKeyDown = (e) => { if (e.key === "Enter" && !busy) { e.preventDefault(); run("signin"); } };

  return (
    <div className="modal-mask" onClick={busy ? undefined : onClose}>
      <div
        className="modal"
        role="dialog"
        aria-modal="true"
        aria-labelledby="login-title"
        onClick={e=>e.stopPropagation()}
      >
        <h3 id="login-title">{t("lg.title")}</h3>
        <p className="modal-sub">{t("lg.sub")}</p>
        <button
          type="button"
          className="btn btn-dark btn-block btn-lg"
          onClick={()=>run("google")}
          disabled={busy}
        >{busy ? t("lg.loading") : t("lg.google")}</button>
        <div className="modal-divider">{t("lg.or")}</div>
        <div className="field modal-field">
          <label htmlFor="lg-email">{t("fm.email")}</label>
          <input
            id="lg-email"
            ref={emailRef}
            type="email"
            autoComplete="email"
            value={email}
            onChange={e=>setEmail(e.target.value)}
            onBlur={()=>setTouched(true)}
            onKeyDown={onKeyDown}
            aria-invalid={touched && !valid}
            disabled={busy}
          />
        </div>
        <div className="field modal-field">
          <label htmlFor="lg-pass">{t("lg.pass")}</label>
          <input
            id="lg-pass"
            type="password"
            autoComplete="current-password"
            value={pass}
            onChange={e=>setPass(e.target.value)}
            onKeyDown={onKeyDown}
            disabled={busy}
          />
        </div>
        {errKey && (
          <p className="help help-error" role="alert">{t(errKey)}</p>
        )}
        <div className="modal-actions">
          <button type="button" className="btn btn-primary" onClick={()=>run("signin")} disabled={busy || !valid}>
            {busy ? t("lg.loading") : t("lg.in")}
          </button>
          <button type="button" className="btn btn-ghost" onClick={()=>run("signup")} disabled={busy || !valid}>
            {busy ? t("lg.loading") : t("lg.up")}
          </button>
        </div>
      </div>
    </div>
  );
};

const SuccessView = ({ email, tier, onAgain }) => {
  const { t } = useLocale();
  const bodyKey = tier === "custom" ? "sc.body1.c" : "sc.body1";
  return (
    <section className="section">
      <div className="success">
        <div className="success-check">✓</div>
        <h2>{t("sc.title")}</h2>
        <p>{t(bodyKey).replace("{email}", email)}</p>
        <p>{t("sc.body2")}</p>
        <p className="success-foot">{t("sc.foot")} <a href="mailto:support@scenera.io" className="success-foot-link">support@scenera.io</a>.</p>
        <div className="success-actions">
          <button className="btn btn-primary" onClick={onAgain}>{t("sc.again")}</button>
          <a className="btn btn-ghost" href="#top">{t("sc.home")}</a>
        </div>
      </div>
    </section>
  );
};

const Toast = ({ msg }) => msg ? <div className="toast">{msg}</div> : null;

window.SceneraV2 = { Nav, Hero, Ticker, Customers, Workflow, Guardian, Pricing, Form, FAQ, Footer, LoginModal, SuccessView, Toast };
