/* Core React components — factored small, exposed on window */

const BrandMark = ({ size = 32 }) => (
  <img
    src="assets/logo_isotype.png"
    alt="Aguilera Ingenieros"
    style={{ display: "block", height: size, width: "auto", maxWidth: "none", flex: "none" }}
  />
);

const Logo = ({ dark = true, size = 30 }) => {
  const src = dark
    ? "assets/logo_main_dark.png"
    : "assets/logo_main_light.png";
  // Native PNG is 1055×467 — aspect ratio LOCKED via aspect-ratio CSS prop
  // so no global img rule can stretch one axis. Height is driven by the
  // --logo-h custom property when an ancestor sets one (the nav scales the
  // mark up on large screens); `size` is the fallback everywhere else, so
  // callers that don't care about responsive sizing keep working unchanged.
  const h = Math.round(size * 1.6);
  const w = Math.round(h * (1055 / 467));
  return (
    <img
      src={src}
      alt="Aguilera Ingenieros"
      width={w}
      height={h}
      className="ai-logo"
      style={{
        display: "block",
        height: `var(--logo-h, ${h}px)`,
        width: "auto",
        maxWidth: "none",
        flex: "none",
        aspectRatio: "1055 / 467",
        objectFit: "contain",
      }}
    />
  );
};

/* NavLogo — the header lockup, which is the one place the brand mark
   changes shape with the viewport.
 *
 *   < 1280px  stacked PNG (1055×467) — mark above the wordmark
 *   >= 1280px horizontal SVG (545.63×83.09) — wordmark beside the mark
 *
 * The horizontal lockup puts the mark at the full height of the asset,
 * whereas the stacked one spends roughly a third of its height on the
 * wordmark underneath. So at 52px tall the side version renders a mark
 * about 1.5x the stacked one's, which is the "bigger on big screens"
 * effect, without the nav bar having to grow to accommodate it.
 *
 * <picture> rather than two <img>s with display:none: the browser
 * evaluates the source media query before fetching, so only the variant
 * actually shown is downloaded. Sizing (and the matching aspect-ratio,
 * which differs sharply between the two) is CSS-driven — see the
 * .ai-nav__logo rules in site.css. */
const NavLogo = () => (
  <picture>
    <source srcSet="assets/logo_side.svg" media="(min-width: 1280px)" />
    <img
      src="assets/logo_main_dark.png"
      alt="Aguilera Ingenieros"
      width={122}
      height={54}
      className="ai-logo ai-logo--nav"
    />
  </picture>
);

const Eyebrow = ({ children, onDark, muted, style }) => (
  <span className={"eyebrow" + (onDark ? " eyebrow--on-dark" : "") + (muted ? " eyebrow--muted" : "")} style={style}>
    {children}
  </span>
);

const ArrowCTA = ({ children, dark, outline, primary, onClick, href }) => {
  const Tag = href ? "a" : "button";
  let cls = "btn ";
  if (primary) cls += "btn-primary";
  else if (outline) cls += dark ? "btn-outline-white" : "btn-outline-dark";
  else cls += "btn-ghost";
  return (
    <Tag className={cls} onClick={onClick} href={href}>
      <span className={outline || primary ? "" : "u"}>{children}</span>
      <span className="arr">→</span>
    </Tag>
  );
};

const LangToggle = ({ onDark = true }) => {
  const { lang, setLang } = useLang();
  return (
    <div className={"lang-toggle" + (onDark ? " lang-toggle--dark" : "")}>
      {["es", "en"].map(l => (
        <span key={l} className={l === lang ? "on" : ""} onClick={() => setLang(l)}>{l.toUpperCase()}</span>
      ))}
    </div>
  );
};

// Animated stat — counts up on reveal
const Stat = ({ num, desc }) => {
  const { ref, display } = useCountUp(num, { duration: 1400 });
  return (
    <div className="stat-row" ref={ref}>
      <div className="stat-num">{display}</div>
      <div className="stat-desc">{desc}</div>
    </div>
  );
};

const SectionLabel = ({ children }) => (
  <div className="section-label">{children}</div>
);

const Icon = {
  Menu:   ({ s=20, c="currentColor" }) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.6"><path d="M3 6h18M3 12h18M3 18h18"/></svg>,
  Close:  ({ s=20, c="currentColor" }) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.6"><path d="M6 6l12 12M18 6l-12 12"/></svg>,
  Arrow:  ({ s=18, c="currentColor" }) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.5"><path d="M5 12h14M13 5l7 7-7 7"/></svg>,
  Chevron:({ s=18, c="currentColor", dir="right" }) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.5" style={{transform:dir==="left"?"rotate(180deg)":""}}><path d="M9 6l6 6-6 6"/></svg>,
  Plus:   ({ s=20, c="currentColor" }) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.5"><path d="M12 5v14M5 12h14"/></svg>,
  LinkedIn: ({ s=16, c="currentColor" }) => <svg width={s} height={s} viewBox="0 0 24 24" fill={c}><path d="M4.98 3.5C4.98 4.88 3.87 6 2.5 6S0 4.88 0 3.5 1.12 1 2.5 1s2.48 1.12 2.48 2.5zM0 8h5v16H0zm7.5 0h4.78v2.2h.07c.67-1.2 2.3-2.47 4.74-2.47 5.07 0 6 3.34 6 7.68V24h-5v-7.1c0-1.7-.03-3.88-2.37-3.88-2.37 0-2.73 1.85-2.73 3.75V24h-5z"/></svg>,
  /* The YouTube glyph kept the same outer-shape signature as the Vimeo
     icon it replaces — rounded rectangle frame + centred triangle —
     so it slots into the same 36×36 social circle without retuning
     the surrounding chrome. Brand channel: youtube.com/@aguilera. */
  YouTube:  ({ s=16, c="currentColor" }) => <svg width={s} height={s} viewBox="0 0 24 24" fill={c}><path d="M23.5 6.2a3 3 0 0 0-2.1-2.1C19.5 3.5 12 3.5 12 3.5s-7.5 0-9.4.6A3 3 0 0 0 .5 6.2C0 8.1 0 12 0 12s0 3.9.5 5.8a3 3 0 0 0 2.1 2.1c1.9.6 9.4.6 9.4.6s7.5 0 9.4-.6a3 3 0 0 0 2.1-2.1c.5-1.9.5-5.8.5-5.8s0-3.9-.5-5.8zM9.6 15.6V8.4l6.3 3.6-6.3 3.6z"/></svg>,
  /* Kept as an alias so any older code that still imports Icon.Vimeo
     keeps rendering instead of breaking — the SVG is now identical to
     YouTube's. Remove once we've audited and there are no more
     Vimeo-named callers. */
  Vimeo:    ({ s=16, c="currentColor" }) => <svg width={s} height={s} viewBox="0 0 24 24" fill={c}><path d="M23.5 6.2a3 3 0 0 0-2.1-2.1C19.5 3.5 12 3.5 12 3.5s-7.5 0-9.4.6A3 3 0 0 0 .5 6.2C0 8.1 0 12 0 12s0 3.9.5 5.8a3 3 0 0 0 2.1 2.1c1.9.6 9.4.6 9.4.6s7.5 0 9.4-.6a3 3 0 0 0 2.1-2.1c.5-1.9.5-5.8.5-5.8s0-3.9-.5-5.8zM9.6 15.6V8.4l6.3 3.6-6.3 3.6z"/></svg>,
  MapPin:   ({ s=16, c="currentColor" }) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.5"><path d="M12 22s8-8 8-13a8 8 0 10-16 0c0 5 8 13 8 13z"/><circle cx="12" cy="9" r="3"/></svg>,
  Mail:     ({ s=16, c="currentColor" }) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 7 9-7"/></svg>,
  Phone:    ({ s=16, c="currentColor" }) => <svg width={s} height={s} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07 19.5 19.5 0 01-6-6 19.79 19.79 0 01-3.07-8.67A2 2 0 014.11 2h3a2 2 0 012 1.72 12.84 12.84 0 00.7 2.81 2 2 0 01-.45 2.11L8.09 9.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45 12.84 12.84 0 002.81.7A2 2 0 0122 16.92z"/></svg>,
};

/* Brand social URLs — locale-independent, so a single global constant
   beats threading them through every t.{...}.social block. Footer and
   the Contact card both pull from here, so changing a handle is a
   one-line edit. */
const AI_SOCIAL = {
  linkedin: "https://www.linkedin.com/company/aguileraingenieros-sa/",
  youtube:  "https://www.youtube.com/channel/UCnkvtM0EULfGn8ub2isUtCg",
};

/* RichText — the safe way to render the little bit of inline HTML that
   content is allowed to carry (<strong>, <em>, <a>, <br>).

   Several fields are editable from the CMS, so their text is not ours:
   whoever holds an editor token writes it. Handing such a string to
   dangerouslySetInnerHTML means a <script>, an onerror= attribute or a
   javascript: link written into a form field would run on our origin.
   Here the string is parsed with DOMParser — which builds an inert
   document, nothing loads and nothing executes — and then rebuilt as
   real React elements. Only whitelisted tags survive; anything else
   loses its tag and keeps its text, so a mistake degrades to plain
   prose instead of breaking the page. No HTML string ever reaches the
   live DOM.

   This is not a general-purpose sanitiser and should not be reused as
   one. It covers exactly the four call sites we have. */

const AI_RICH_TAGS = { strong: "strong", b: "strong", em: "em", i: "em", br: "br", a: "a" };

/* Resolving against our own origin normalises the exotic spellings
   (mixed case, embedded tabs) that regex checks miss: whatever the URL
   parser reports as the protocol is the protocol the browser will use. */
const aiSafeHref = (raw) => {
  try {
    const u = new URL(String(raw ?? ""), window.location.origin);
    return ["http:", "https:", "mailto:", "tel:"].includes(u.protocol) ? raw : null;
  } catch (e) {
    return null;
  }
};

const aiRichNode = (node, key) => {
  if (node.nodeType === 3) return node.nodeValue;   // texto
  if (node.nodeType !== 1) return null;             // comentarios y demás
  const tag = node.tagName.toLowerCase();
  // A script or a style contributes no prose: drop the text too.
  if (tag === "script" || tag === "style") return null;

  const kids = Array.from(node.childNodes).map(aiRichNode);
  const allowed = AI_RICH_TAGS[tag];
  const unwrap = <React.Fragment key={key}>{kids}</React.Fragment>;
  if (!allowed) return unwrap;
  if (allowed === "br") return <br key={key} />;
  if (allowed !== "a") return React.createElement(allowed, { key }, kids);

  const href = aiSafeHref(node.getAttribute("href"));
  if (!href) return unwrap;   // javascript: y compañía se quedan en texto
  const blank = node.getAttribute("target") === "_blank";
  return (
    <a key={key} href={href} {...(blank ? { target: "_blank", rel: "noopener noreferrer" } : {})}>
      {kids}
    </a>
  );
};

const RichText = ({ html, as = "span", ...rest }) => {
  const kids = React.useMemo(() => {
    const doc = new DOMParser().parseFromString(String(html ?? ""), "text/html");
    return Array.from(doc.body.childNodes).map(aiRichNode);
  }, [html]);
  return React.createElement(as, rest, kids);
};

/* MailReveal — un botón que, al pulsarlo, enseña la dirección en vez de
   abrir el cliente de correo.

   El cliente lo pidió porque un mailto es invasivo: se abre Outlook sin
   avisar, y desde el móvil a veces no se abre nada. Aquí el primer clic
   revela la dirección; a partir de ahí quien quiera puede pulsarla para
   escribir, o seleccionarla y copiarla. Nada se abre sin que lo pidas
   dos veces.

   Mantiene el vocabulario visual de ArrowCTA para que no cante al lado
   de los demás botones de la página. */
const MailReveal = ({ email, children, dark, outline, primary, subject, copiedLabel }) => {
  const [shown, setShown] = React.useState(false);
  const [copied, setCopied] = React.useState(false);
  if (!email) return null;

  let cls = "btn ";
  if (primary) cls += "btn-primary";
  else if (outline) cls += dark ? "btn-outline-white" : "btn-outline-dark";
  else cls += "btn-ghost";

  if (!shown) {
    return (
      <button type="button" className={cls} onClick={() => setShown(true)}>
        <span className={outline || primary ? "" : "u"}>{children}</span>
        <span className="arr">→</span>
      </button>
    );
  }

  const href = "mailto:" + email + (subject ? "?subject=" + encodeURIComponent(subject) : "");
  /* Copiar al portapapeles puede fallar (navegador antiguo, permiso
     denegado, contexto no seguro). Si falla no pasa nada: la dirección
     está a la vista y se puede seleccionar a mano. */
  const copy = (e) => {
    e.preventDefault();
    try {
      navigator.clipboard.writeText(email).then(
        () => { setCopied(true); setTimeout(() => setCopied(false), 2000); },
        () => {}
      );
    } catch (err) { /* sin portapapeles: queda visible igual */ }
  };

  return (
    <span className={"mailreveal " + (dark ? "mailreveal--dark" : "")} aria-live="polite">
      <a className="mailreveal__addr" href={href}>{email}</a>
      <button type="button" className="mailreveal__copy" onClick={copy}
              aria-label={copiedLabel || "Copiar"}>
        {copied ? "✓" : "⧉"}
      </button>
    </span>
  );
};

Object.assign(window, { MailReveal, BrandMark, Logo, NavLogo, Eyebrow, ArrowCTA, LangToggle, Stat, SectionLabel, Icon, AI_SOCIAL, RichText });
