/* eslint-disable react/prop-types */
/**
 * visuals.jsx: the four working visuals that carry the page.
 *
 * HeroMorph     one markdown file typing itself into a deployed site
 * PipelineFlow  the five stages a file passes through, on one rail
 * PackSwitcher  the same content under six design packs
 * DeployPanel   the route table and the deploy log
 *
 * No build step here: these are classic scripts compiled by Babel in the
 * browser, so every top-level binding is global. Names are prefixed to keep
 * them from colliding with sections.jsx.
 */

const { useState: useStateV, useEffect: useEffectV, useRef: useRefV, useMemo: useMemoV } = React;

function vPrefersReducedMotion() {
  return (
    typeof window !== "undefined" &&
    window.matchMedia &&
    window.matchMedia("(prefers-reduced-motion: reduce)").matches
  );
}

/** One-shot "scrolled into view" flag. Reduced motion resolves immediately. */
function useReveal(threshold) {
  const ref = useRefV(null);
  const [revealed, setRevealed] = useStateV(false);

  useEffectV(() => {
    if (vPrefersReducedMotion() || typeof IntersectionObserver === "undefined") {
      setRevealed(true);
      return;
    }
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver(
      (entries) => {
        if (entries[0] && entries[0].isIntersecting) {
          setRevealed(true);
          io.disconnect();
        }
      },
      { threshold: threshold || 0.25, rootMargin: "0px 0px -8% 0px" },
    );
    io.observe(el);
    return () => io.disconnect();
  }, [threshold]);

  return [ref, revealed];
}

/** Step counter that advances once `active` turns true. */
function useStagger(active, steps, stepMs) {
  const [step, setStep] = useStateV(0);
  useEffectV(() => {
    if (!active) return;
    if (vPrefersReducedMotion()) {
      setStep(steps);
      return;
    }
    const timers = [];
    for (let i = 1; i <= steps; i += 1) {
      timers.push(setTimeout(() => setStep(i), i * (stepMs || 420)));
    }
    return () => timers.forEach(clearTimeout);
  }, [active, steps, stepMs]);
  return step;
}

/* ─────────────── Browser frame ─────────────── */

function BrowserFrame({ url, live = true, children }) {
  return (
    <div className="frame">
      <div className="frame__bar">
        <div className="frame__nav">
          <Chevron dir="left" />
          <Chevron dir="right" />
          <Refresh />
        </div>
        <div className="frame__url">
          <Lock live={live} />
          <span>
            <span className="frame__scheme">https://</span>
            {url}
          </span>
          {live && (
            <span className="frame__live">
              <i className="dot dot--live" />
              live
            </span>
          )}
        </div>
      </div>
      {children}
    </div>
  );
}

function Chevron({ dir }) {
  return (
    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d={dir === "left" ? "M15.75 19.5L8.25 12l7.5-7.5" : "M8.25 4.5l7.5 7.5-7.5 7.5"} />
    </svg>
  );
}

function Refresh() {
  return (
    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
      <path d="M16.023 9.348h4.992V4.356M2.985 19.644v-4.992h4.992m11.286 0a8.25 8.25 0 01-13.803 3.7l-3.181-3.182m0-4.991a8.25 8.25 0 0113.803-3.7l3.181 3.182" />
    </svg>
  );
}

function Lock({ live }) {
  return (
    <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke={live ? "var(--live)" : "currentColor"} strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" style={{ flexShrink: 0 }}>
      <path d="M16.5 10.5V6.75a4.5 4.5 0 10-9 0v3.75m-.75 11.25h10.5a2.25 2.25 0 002.25-2.25v-6.75a2.25 2.25 0 00-2.25-2.25H6.75a2.25 2.25 0 00-2.25 2.25v6.75a2.25 2.25 0 002.25 2.25z" />
    </svg>
  );
}

/* ─────────────── Hero morph ─────────────── */

const MORPH_BLOCKS = [
  "---\ntitle: Acme\npack: aurora\n---\n\n",
  "# Invoice your clients in under a minute.\n\nMulti-currency, auto-reminders, and a Stripe\ncheckout that just works.\n\n[Start free](/signup)\n\n",
  "## Why teams switch\n\n- Six hours a week back from admin\n- Stripe checkout with zero setup\n- Thirteen languages, one click\n\n",
  "## Pricing\n\n- Starter: $0\n- Pro: $19/mo\n- Team: $49/mo\n",
];

const MORPH_MD = MORPH_BLOCKS.join("");

/** Character offset at which each block is fully typed. */
const MORPH_ENDS = MORPH_BLOCKS.reduce((acc, b) => {
  acc.push((acc[acc.length - 1] || 0) + b.length);
  return acc;
}, []);

function HeroMorph() {
  const [ref, revealed] = useReveal(0.2);
  const [typed, setTyped] = useStateV(0);
  const [run, setRun] = useStateV(0);
  const timer = useRefV(null);

  useEffectV(() => {
    if (!revealed) return;
    if (vPrefersReducedMotion()) {
      setTyped(MORPH_MD.length);
      return;
    }
    setTyped(0);
    timer.current = setInterval(() => {
      setTyped((n) => {
        const next = n + 4;
        if (next >= MORPH_MD.length && timer.current) {
          clearInterval(timer.current);
          timer.current = null;
          return MORPH_MD.length;
        }
        return next;
      });
    }, 16);
    return () => {
      if (timer.current) clearInterval(timer.current);
      timer.current = null;
    };
  }, [revealed, run]);

  const done = typed >= MORPH_MD.length;
  const landed = useMemoV(
    () => MORPH_ENDS.filter((end) => typed >= end).length,
    [typed],
  );

  return (
    <div className="morph" ref={ref}>
      <SourcePane text={MORPH_MD.slice(0, typed)} caret={!done} />
      <div className="morph__right">
        <BrowserFrame url="acme.mdcp.io" live={landed >= 2}>
          <AssembledSite landed={landed} />
        </BrowserFrame>
        <DeployStrip done={done} onReplay={() => setRun((r) => r + 1)} />
      </div>
    </div>
  );
}

function SourcePane({ text, caret }) {
  const lines = text.split("\n");
  return (
    <div className="morph__source ink">
      <div className="panel__bar">
        <span>page.md</span>
        <span style={{ color: "var(--inkfg3)" }}>/</span>
        <span style={{ color: "var(--inkfg3)" }}>markdown</span>
        <span className="panel__bar-right">source</span>
      </div>
      <pre className="morph__code">
        <code>
          {lines.map((line, i) => (
            <span key={i} style={{ display: "block" }}>
              <MarkdownLine text={line} caret={caret && i === lines.length - 1} />
            </span>
          ))}
        </code>
      </pre>
    </div>
  );
}

/**
 * Line-level colouring. A real tokenizer would need the whole document; this
 * pane only ever shows a prefix of one, so prefix matching is both enough and
 * immune to half-typed syntax.
 */
function MarkdownLine({ text, caret }) {
  const tail = caret ? <i className="caret" /> : null;

  const link = text.match(/^(\[)([^\]]*)(\]\()([^)]*)(\)?)$/);
  if (link) {
    return (
      <React.Fragment>
        <span className="tok-punct">{link[1]}</span>
        <span className="tok-h2">{link[2]}</span>
        <span className="tok-punct">{link[3]}</span>
        <span className="tok-meta">{link[4]}</span>
        <span className="tok-punct">{link[5]}</span>
        {tail}
      </React.Fragment>
    );
  }

  let cls = "tok-text";
  if (text.startsWith("---")) cls = "tok-front";
  else if (text.startsWith("##")) cls = "tok-h2";
  else if (text.startsWith("#")) cls = "tok-h1";
  else if (/^[a-z]+:/.test(text)) cls = "tok-meta";

  return (
    <React.Fragment>
      <span className={cls}>{text}</span>
      {tail}
    </React.Fragment>
  );
}

function AssembledSite({ landed }) {
  const on = (n) => "slot" + (landed >= n ? " slot--on" : "");
  return (
    <div className="site">
      <div className="site__bar">
        <span className="site__brand" style={{ color: landed >= 1 ? "var(--fg)" : "var(--fg3)" }}>
          Acme
        </span>
        <span className="site__nav">
          <span>Product</span>
          <span>Pricing</span>
          <span className={"site__signin" + (landed >= 1 ? " site__signin--on" : "")}>Sign in</span>
        </span>
      </div>

      <div className={on(2)}>
        <h3 className="site__h1">Invoice your clients in under a minute.</h3>
        <p className="site__p">
          Multi-currency, auto-reminders, and a Stripe checkout that just works.
        </p>
        <div className="site__cta">
          <span className="site__cta-primary">Start free</span>
          <span className="site__cta-ghost">See a sample</span>
        </div>
      </div>

      <div className={on(3)}>
        <div className="site__cards">
          <div className="site__card">Six hours a week back</div>
          <div className="site__card">Stripe, zero setup</div>
          <div className="site__card">Thirteen languages</div>
        </div>
      </div>

      <div className={on(4)}>
        <div className="site__cards" style={{ marginTop: 12 }}>
          {[
            { name: "Starter", price: "$0" },
            { name: "Pro", price: "$19" },
            { name: "Team", price: "$49" },
          ].map((t, i) => (
            <div key={t.name} className={"site__tier" + (i === 1 ? " site__tier--on" : "")}>
              <div className="site__tier-k">{t.name}</div>
              <div className="site__tier-v">{t.price}</div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function DeployStrip({ done, onReplay }) {
  return (
    <div className="strip">
      <i className={"dot " + (done ? "dot--live" : "dot--idle")} />
      <span className={"strip__state" + (done ? " strip__state--done" : "")}>
        {done ? "deployed in 8.3s" : "building…"}
      </span>
      <span>·</span>
      <span>auto-SSL · atomic · rollback</span>
      <button type="button" className="strip__replay" onClick={onReplay}>
        replay
      </button>
    </div>
  );
}

/* ─────────────── Pipeline ─────────────── */

const PIPELINE_STAGES = [
  { id: "write", index: "01", title: "Write", detail: "One markdown file per page. Frontmatter on top, prose below." },
  { id: "detect", index: "02", title: "Detect", detail: "Headings, lists and tables resolve to typed sections." },
  { id: "theme", index: "03", title: "Theme", detail: "A design pack, or a DESIGN.md lifted from a site you like." },
  { id: "translate", index: "04", title: "Translate", detail: "Thirteen languages from the same source, diffed on change." },
  { id: "ship", index: "05", title: "Ship", detail: "Atomic deploy to your subdomain, with rollback." },
];

function PipelineFlow() {
  const [ref, revealed] = useReveal(0.2);
  const step = useStagger(revealed, PIPELINE_STAGES.length, 220);

  return (
    <div className="pipeline" ref={ref}>
      <div className="pipeline__rail" aria-hidden="true" />
      <div className="pipeline__sheen" aria-hidden="true" style={{ opacity: revealed ? 1 : 0 }}>
        <i />
      </div>
      <ol className="pipeline__list">
        {PIPELINE_STAGES.map((stage, i) => (
          <li key={stage.id} className={"pipeline__item" + (step > i ? " pipeline__item--on" : "")}>
            <span className="pipeline__node">
              <StageGlyph id={stage.id} />
            </span>
            <div className="pipeline__body">
              <span className="pipeline__index">{stage.index}</span>
              <h3 className="pipeline__title">{stage.title}</h3>
              <p className="pipeline__detail">{stage.detail}</p>
            </div>
          </li>
        ))}
      </ol>
    </div>
  );
}

/**
 * Drawn rather than pulled from an icon set: each glyph states what its stage
 * does to the document, which a generic file/globe/rocket set cannot.
 */
function StageGlyph({ id }) {
  const p = {
    width: 22,
    height: 22,
    viewBox: "0 0 24 24",
    fill: "none",
    stroke: "currentColor",
    strokeWidth: 1.5,
    strokeLinecap: "round",
    strokeLinejoin: "round",
    "aria-hidden": "true",
  };
  if (id === "write") {
    return (
      <svg {...p}>
        <path d="M6 3h8l4 4v14H6z" />
        <path d="M14 3v4h4" />
        <path d="M9 12h6M9 15.5h6M9 19h3" />
      </svg>
    );
  }
  if (id === "detect") {
    return (
      <svg {...p}>
        <rect x="3" y="4" width="18" height="5.5" rx="1.5" />
        <rect x="3" y="12.5" width="8" height="7" rx="1.5" />
        <rect x="13" y="12.5" width="8" height="7" rx="1.5" strokeDasharray="2.5 2.5" />
      </svg>
    );
  }
  if (id === "theme") {
    return (
      <svg {...p}>
        <circle cx="12" cy="12" r="8.5" />
        <path d="M12 3.5v17" />
        <path d="M12 3.5a8.5 8.5 0 010 17" fill="currentColor" opacity="0.22" stroke="none" />
      </svg>
    );
  }
  if (id === "translate") {
    return (
      <svg {...p}>
        <circle cx="12" cy="12" r="8.5" />
        <path d="M3.5 12h17" />
        <ellipse cx="12" cy="12" rx="3.9" ry="8.5" />
      </svg>
    );
  }
  return (
    <svg {...p}>
      <path d="M12 3.5l7 3.8v9.4l-7 3.8-7-3.8V7.3z" />
      <path d="M12 12l7-4.7M12 12v8.5M12 12L5 7.3" />
    </svg>
  );
}

/* ─────────────── Pack switcher ─────────────── */

const PACKS = [
  {
    slug: "aurora", name: "Aurora", dot: "#a855f7",
    vars: {
      "--p-bg": "#0a0716", "--p-surface": "#150e2c", "--p-line": "#2b1d55",
      "--p-fg": "#f3eeff", "--p-fg2": "#a596cc", "--p-accent": "#a855f7",
      "--p-accent-fg": "#ffffff", "--p-radius": "16px",
      "--p-font": '"Inter", system-ui, sans-serif',
      "--p-display": '"Bricolage Grotesque", system-ui, sans-serif',
      "--p-tracking": "-0.03em", "--p-weight": "600", "--p-pad": "28px",
    },
  },
  {
    slug: "minimal", name: "Minimal", dot: "#3b82f6",
    vars: {
      "--p-bg": "#ffffff", "--p-surface": "#f6f7f9", "--p-line": "#e4e7ec",
      "--p-fg": "#0b1220", "--p-fg2": "#5b6575", "--p-accent": "#2563eb",
      "--p-accent-fg": "#ffffff", "--p-radius": "8px",
      "--p-font": '"Inter", system-ui, sans-serif',
      "--p-display": '"Inter", system-ui, sans-serif',
      "--p-tracking": "-0.022em", "--p-weight": "650", "--p-pad": "26px",
    },
  },
  {
    slug: "editorial", name: "Editorial", dot: "#fbbf24",
    vars: {
      "--p-bg": "#fbf7ef", "--p-surface": "#f3ece0", "--p-line": "#e0d5c2",
      "--p-fg": "#1c1712", "--p-fg2": "#6b5f4f", "--p-accent": "#a8620f",
      "--p-accent-fg": "#fbf7ef", "--p-radius": "2px",
      "--p-font": 'Georgia, "Times New Roman", serif',
      "--p-display": 'Georgia, "Times New Roman", serif',
      "--p-tracking": "-0.012em", "--p-weight": "600", "--p-pad": "30px",
    },
  },
  {
    slug: "bold", name: "Bold", dot: "#f97316", upper: true,
    vars: {
      "--p-bg": "#140c04", "--p-surface": "#231405", "--p-line": "#3d2409",
      "--p-fg": "#fff6ec", "--p-fg2": "#c39a72", "--p-accent": "#ff6a00",
      "--p-accent-fg": "#140c04", "--p-radius": "4px",
      "--p-font": '"Bricolage Grotesque", system-ui, sans-serif',
      "--p-display": '"Bricolage Grotesque", system-ui, sans-serif',
      "--p-tracking": "-0.045em", "--p-weight": "700", "--p-pad": "24px",
    },
  },
  {
    slug: "obsidian", name: "Obsidian", dot: "#ca8a04",
    vars: {
      "--p-bg": "#0b0b0b", "--p-surface": "#141414", "--p-line": "#2a2620",
      "--p-fg": "#f0ead9", "--p-fg2": "#8d8677", "--p-accent": "#c9a227",
      "--p-accent-fg": "#0b0b0b", "--p-radius": "0px",
      "--p-font": '"Inter", system-ui, sans-serif',
      "--p-display": 'Georgia, "Times New Roman", serif',
      "--p-tracking": "-0.008em", "--p-weight": "500", "--p-pad": "34px",
    },
  },
  {
    slug: "verdant", name: "Verdant", dot: "#16a34a",
    vars: {
      "--p-bg": "#f4f8f2", "--p-surface": "#e6efe3", "--p-line": "#cfe0c8",
      "--p-fg": "#12261a", "--p-fg2": "#4f6a56", "--p-accent": "#15803d",
      "--p-accent-fg": "#ffffff", "--p-radius": "22px",
      "--p-font": '"Inter", system-ui, sans-serif',
      "--p-display": '"Bricolage Grotesque", system-ui, sans-serif',
      "--p-tracking": "-0.02em", "--p-weight": "600", "--p-pad": "28px",
    },
  },
];

/** Obsidian: dark, so the first frame is not another light page, and far
 *  enough from the site's own amber that it reads as a different design. */
const PACK_START = PACKS.findIndex((p) => p.slug === "obsidian");

function PackSwitcher() {
  const [ref, revealed] = useReveal(0.3);
  const [index, setIndex] = useStateV(PACK_START);
  const [held, setHeld] = useStateV(false);

  useEffectV(() => {
    if (!revealed || held || vPrefersReducedMotion()) return;
    const id = setInterval(() => setIndex((i) => (i + 1) % PACKS.length), 3400);
    return () => clearInterval(id);
  }, [revealed, held]);

  const pack = PACKS[index];

  return (
    <div ref={ref}>
      <div className="packs__chips" role="tablist" aria-label="Design packs">
        {PACKS.map((p, i) => (
          <button
            key={p.slug}
            type="button"
            role="tab"
            aria-selected={i === index}
            className={"chip" + (i === index ? " chip--on" : "")}
            onClick={() => {
              setIndex(i);
              setHeld(true);
            }}
          >
            <span
              className="chip__dot"
              style={{ background: p.dot, boxShadow: i === index ? "0 0 8px " + p.dot : undefined }}
            />
            {p.name}
          </button>
        ))}
        <span className="packs__state">
          {held ? "held" : "cycling"} · {index + 1}/{PACKS.length}
        </span>
      </div>

      <BrowserFrame url={"acme.mdcp.io/?pack=" + pack.slug}>
        <PackedSite pack={pack} />
      </BrowserFrame>

      <p className="packs__note">Same markdown. Only the tokens changed.</p>
    </div>
  );
}

function PackedSite({ pack }) {
  return (
    <div className="packed" style={pack.vars}>
      <div className="packed__bar">
        <span className="packed__brand">Acme</span>
        <span className="packed__nav">
          <span>Product</span>
          <span>Pricing</span>
          <span className="packed__signin">Sign in</span>
        </span>
      </div>

      <h3 className={"packed__h1" + (pack.upper ? " packed__h1--upper" : "")}>
        Invoice your clients in under a minute.
      </h3>
      <p className="packed__p">
        Multi-currency, auto-reminders, and a Stripe checkout that just works.
      </p>

      <div className="packed__cta">
        <span className="packed__btn">Start free</span>
        <span className="packed__btn packed__btn--ghost">See a sample</span>
      </div>

      <div className="packed__stats">
        {[
          { k: "Invoices sent", v: "12,500+" },
          { k: "Avg. send time", v: "47s" },
          { k: "Auto-reminders", v: "On" },
        ].map((s) => (
          <div className="packed__stat" key={s.k}>
            <div className="packed__stat-k">{s.k}</div>
            <div className="packed__stat-v">{s.v}</div>
          </div>
        ))}
      </div>
    </div>
  );
}

/* ─────────────── Deploy panel ─────────────── */

const DEPLOY_ROUTES = [
  { route: "/", title: "Home", state: "live" },
  { route: "/pricing", title: "Pricing", state: "live" },
  { route: "/docs/intro", title: "Getting started", state: "live" },
  { route: "/docs/api", title: "API reference", state: "live" },
  { route: "/blog/launch", title: "Launch post", state: "draft" },
];

const DEPLOY_LOG = [
  { at: "0.0s", text: "collecting published snapshots: 4 pages, 2 languages" },
  { at: "0.6s", text: "rendering html (pack: aurora)", tone: "accent" },
  { at: "2.1s", text: "inlining critical css, hashing assets" },
  { at: "4.4s", text: "uploading bundle (318 KB) to edge" },
  { at: "6.9s", text: "issuing certificate for acme.mdcp.io" },
  { at: "8.3s", text: "live, previous build kept for rollback", tone: "live" },
];

function DeployPanel() {
  const [ref, revealed] = useReveal(0.25);
  const [shown, setShown] = useStateV(0);

  useEffectV(() => {
    if (!revealed) return;
    if (vPrefersReducedMotion()) {
      setShown(DEPLOY_LOG.length);
      return;
    }
    const timers = DEPLOY_LOG.map((_, i) =>
      setTimeout(() => setShown(i + 1), 260 + i * 460),
    );
    return () => timers.forEach(clearTimeout);
  }, [revealed]);

  const finished = shown >= DEPLOY_LOG.length;
  const liveCount = DEPLOY_ROUTES.filter((r) => r.state === "live").length;
  const draftCount = DEPLOY_ROUTES.length - liveCount;

  return (
    <div className="deploy" ref={ref}>
      <div className="deploy__routes">
        <div className="panel__bar">
          <span>pages</span>
          <span className="panel__bar-right">
            {liveCount} live · {draftCount} draft
          </span>
        </div>
        <ul className="routes">
          {DEPLOY_ROUTES.map((r) => (
            <li className="route" key={r.route}>
              <span className="route__path">{r.route}</span>
              <span className="route__title">{r.title}</span>
              <span className={"route__state route__state--" + r.state}>
                <i className={"dot dot--" + r.state} />
                {r.state}
              </span>
            </li>
          ))}
        </ul>
      </div>

      <div className="ink">
        <div className="panel__bar">
          <span>deploy.log</span>
          <span
            className="panel__bar-right"
            style={{ color: finished ? "var(--inklive)" : undefined }}
          >
            {finished ? "exit 0" : "running"}
          </span>
        </div>
        <ol className="log">
          {DEPLOY_LOG.map((line, i) => (
            <li key={line.at} className={"log__line" + (i < shown ? " log__line--on" : "")}>
              <span className="log__at">{line.at}</span>
              <span className={"log__text" + (line.tone ? " log__text--" + line.tone : "")}>
                {line.text}
              </span>
            </li>
          ))}
        </ol>
        {/* Width tracks the log, so the bar is the same fact. */}
        <div className="progress">
          <i
            className={finished ? "is-done" : ""}
            style={{ width: (shown / DEPLOY_LOG.length) * 100 + "%" }}
          />
        </div>
      </div>
    </div>
  );
}
