/* global channelIcon, IconEngine, IconRoute, IconLayers, IconSpark, IconShield, IconStays */
/* ============================================================
 * Integrations catalogue
 * ============================================================
 *
 * Data lives in integrations-data.jsx. See
 * docs/integrations-catalogue-provenance.md (not served) for how the
 * list is derived and what must never be added to it.
 *
 * The design brief, in one line: our visitor does not recognise most of
 * these names. Zapier and Retool can build a logo wall because the logo
 * IS the lookup key — you scan for the one you know. Thirty unfamiliar
 * regional PMS wordmarks is not a lookup key, it is wallpaper. The real
 * question a hotelier arrives with is "do you connect to MY system?",
 * which is a search, not a browse. So:
 *
 *   - the search field is the hero control, not a filter in a sidebar
 *   - the NAME leads each card; the logo is a small uniform tile
 *   - region is a first-class filter, because a Spanish independent is
 *     scanning for a Spanish PMS and that is a real axis here
 *   - everything renders at once; 72 items is a filter-in-memory
 *     problem, and pagination would only hide the answer
 *   - no "Install" or "Connect" buttons. Connecting these is a
 *     conversation with us, and a button that lies about that is worse
 *     than no button
 *   - a failed search is the most valuable event on the page, so it
 *     returns a prefilled request form rather than a shrug
 *
 * Logos sit on white cards on purpose. Most of the collected assets are
 * PNGs with a baked white plate, which would show as a bright rectangle
 * against the warm off-white page. A white tile makes that invisible and
 * gives thirty logos of wildly different aspect ratios one shape.
 */

const LOGO_BASE = "https://cdn.guestmaker.ai/marketing/homepage-v3/assets/logos/";

/* Three rungs, not two: hosted vendor logo, then a drawn icon, then a
 * monogram. A missing logo is a supported state, not a defect — the name
 * leads the card and the tile is 52px precisely so it is not the identity
 * of the row.
 *
 * The middle rung exists because "two initials" was answering two very
 * different questions with the same shrug.
 *
 * A third of the cards with no logo file are not third-party vendors at
 * all — the REST API, outbound webhooks, the MCP server, the web chat
 * widget, your own sending domain. There is no logo to source for those,
 * and "RA" in a mono tile says nothing a small icon would not say better.
 *
 * WhatsApp, Instagram and Facebook Messenger stay on the drawn icon: the
 * site already draws these marks in icons.jsx and ships them on the inbox
 * and homepage today, and Meta's consumer-messaging apps are the one
 * family here whose usage terms this catalogue deliberately does not test
 * by rehosting fresh artwork. Meta Ads, Google Ads, Gmail, Outlook and
 * Ringover moved OFF this fallback once a real, brand-guideline-compliant
 * mark was sourced for each (Meta/Google Ads: the same official marks
 * already shipped in components/ads/platform-logos.tsx; Gmail/Outlook:
 * each vendor's own current brand asset; Ringover: their own favicon) —
 * see LOGO_FILES.
 *
 * Leaving the hotel-tech vendors (TravelClick, ReviewPro, …) on the
 * monogram is deliberate: those DO have a sourceable logo, and a generic
 * icon would paper over a gap that should stay visible until the asset
 * is collected. */
const ICON_FALLBACK = {
  // platform brands the site already draws
  whatsapp: channelIcon.whatsapp,
  instagram: channelIcon.instagram,
  messenger: channelIcon.facebook,
  // ours — no logo exists to source, an icon is the honest answer
  "web-chat": channelIcon.webchat,
  imap: channelIcon.email,
  "ses-domain": channelIcon.emailOut,
  "sip-transfer": channelIcon.voice,
  "rest-api": IconEngine,
  webhooks: IconRoute,
  mcp: IconLayers,
  "conversion-tag": IconSpark,
  "loyalty-sso": IconShield,
  "loyalty-credit": IconStays,
};

const IntegrationLogo = ({ item }) => {
  const [failed, setFailed] = useState(false);
  const file = LOGO_FILES[item.slug];
  const initials = (item.name.match(/\b[A-Za-z0-9]/g) || ["?"]).slice(0, 2).join("");

  if (!file || failed) {
    const Icon = ICON_FALLBACK[item.slug];
    if (Icon) {
      return (
        <div className="ig-logo ig-logo-icon" aria-hidden="true">
          <Icon size={22} />
        </div>
      );
    }
    return (
      <div className="ig-logo ig-logo-mono" aria-hidden="true">
        <span>{initials.toUpperCase()}</span>
      </div>
    );
  }
  return (
    <div className="ig-logo">
      <img
        src={`${LOGO_BASE}${file}?v=${LOGO_VERSION}`}
        alt={`${item.name} logo`}
        loading="lazy"
        onError={() => setFailed(true)}
      />
    </div>
  );
};

/* Accent-insensitive so "Timon" finds "Timón". */
const normalize = (s) =>
  (s || "")
    .toLowerCase()
    .normalize("NFD")
    .replace(/[̀-ͯ]/g, "");

const IntegrationCard = ({ item, categoryLabel }) => {
  const meta = [categoryLabel, item.region].filter(Boolean).join(" · ");
  return (
    <article className="ig-card">
      <div className="ig-card-top">
        <IntegrationLogo item={item} />
        {item.status === "available" && (
          <span className="ig-status" title="Built and ready, not yet switched on by a hotel">
            Available
          </span>
        )}
      </div>

      <h3 className="ig-name">{item.name}</h3>
      <p className="ig-meta">{meta}</p>

      {item.description && <p className="ig-desc">{item.description}</p>}

      {/* `liveBrands` is deliberately NOT rendered. It is in the data
       * because it drives ordering and the live/available split, but
       * printing "280 brands connected" on a public card tells a
       * competitor exactly how much of our book sits on one vendor, and
       * commits us to a number that moves every week. The data stays,
       * the disclosure does not. */}
      {item.connectors > 1 && (
        <p className="ig-facts">
          <span>{item.connectors} connectors</span>
        </p>
      )}
    </article>
  );
};

const IntegrationsCatalogue = () => {
  const [query, setQuery] = useState("");
  const [category, setCategory] = useState("all");
  const [region, setRegion] = useState("all");

  const categoryLabel = useMemo(() => {
    const map = {};
    INTEGRATION_CATEGORIES.forEach((c) => (map[c.id] = c.label));
    return map;
  }, []);

  /* Regions are derived from the data, never hand-listed — a hand-listed
   * filter silently stops offering a region the moment one is added. */
  const regions = useMemo(() => {
    const counts = {};
    INTEGRATIONS.forEach((i) => {
      if (i.region) counts[i.region] = (counts[i.region] || 0) + 1;
    });
    return Object.keys(counts).sort((a, b) => counts[b] - counts[a]);
  }, []);

  const byQuery = useMemo(() => {
    const q = normalize(query).trim();
    if (!q) return INTEGRATIONS;
    return INTEGRATIONS.filter((i) =>
      normalize(
        [i.name, i.vendor, i.region, categoryLabel[i.category], i.description].join(" ")
      ).includes(q)
    );
  }, [query, categoryLabel]);

  const visible = useMemo(
    () =>
      byQuery.filter(
        (i) =>
          (category === "all" || i.category === category) &&
          (region === "all" || i.region === region)
      ),
    [byQuery, category, region]
  );

  /* Counts on the pills reflect the CURRENT search, so a pill reading 0
   * tells you the truth before you click it. */
  const catCounts = useMemo(() => {
    const c = {};
    byQuery.forEach((i) => (c[i.category] = (c[i.category] || 0) + 1));
    return c;
  }, [byQuery]);

  const grouped = useMemo(() => {
    const out = [];
    INTEGRATION_CATEGORIES.forEach((c) => {
      const items = visible.filter((i) => i.category === c.id);
      if (items.length) out.push({ ...c, items });
    });
    return out;
  }, [visible]);

  const reset = () => {
    setQuery("");
    setCategory("all");
    setRegion("all");
  };

  return (
    <>
      <SectionNav current="integrations" />

      <main className="ig-page">
        <header className="ig-hero">
          <div className="ig-hero-inner">
            <div className="eyebrow">INTEGRATIONS</div>
            <h1 className="ig-h1">
              It plugs into the systems you<br />already run.
            </h1>
            <p className="ig-sub">
              {INTEGRATIONS.length} integrations across your PMS, booking engine, messaging channels and
              guest tools: connected by us, not by your IT team.
            </p>

            <div className="ig-search-wrap">
              <input
                className="ig-search"
                type="search"
                autoFocus
                value={query}
                onChange={(e) => setQuery(e.target.value)}
                placeholder="Search for your PMS or booking engine…"
                aria-label="Search integrations"
              />
            </div>

            {/* Qualitative on purpose. A live connection count is both a
              * disclosure (how much of the book sits on one vendor) and a
              * hostage to fortune (it moves weekly).
              *
              * Note the wording: "built and working", NOT "running in
              * production". Four of these are marked Available — real,
              * callable, nobody has switched them on yet — so a
              * production claim would be false for them. The whole point
              * of the status chip is that we do not have to fudge this. */}
            <p className="ig-stat">
              All built and working. Nothing here is a roadmap item.
            </p>
          </div>
        </header>

        <div className="ig-filters">
          <div className="ig-filter-row">
            <button
              className={`ig-pill ${category === "all" ? "on" : ""}`}
              onClick={() => setCategory("all")}
            >
              All <span className="ig-pill-n">{byQuery.length}</span>
            </button>
            {INTEGRATION_CATEGORIES.map((c) => (
              <button
                key={c.id}
                className={`ig-pill ${category === c.id ? "on" : ""}`}
                onClick={() => setCategory(c.id)}
                disabled={!catCounts[c.id]}
              >
                {c.label} <span className="ig-pill-n">{catCounts[c.id] || 0}</span>
              </button>
            ))}
          </div>

          <div className="ig-filter-row ig-filter-row-sub">
            <span className="ig-filter-label">Region</span>
            <button
              className={`ig-pill sm ${region === "all" ? "on" : ""}`}
              onClick={() => setRegion("all")}
            >
              Anywhere
            </button>
            {regions.map((r) => (
              <button
                key={r}
                className={`ig-pill sm ${region === r ? "on" : ""}`}
                onClick={() => setRegion(r)}
              >
                {r}
              </button>
            ))}
          </div>
        </div>

        <div className="ig-results">
          {visible.length === 0 ? (
            /* The most useful moment on the page: they searched for their
             * system and we do not have it. Capture it. */
            <div className="ig-empty">
              <h2>We don’t connect {query.trim() ? `"${query.trim()}"` : "that"} yet.</h2>
              <p>
                That’s usually a matter of weeks, not quarters, and it’s the fastest way to get
                it prioritised. Tell us what you run and we’ll come back with a timeline.
              </p>
              <a className="btn btn-primary" href={DEMO_URL}>
                Ask us about {query.trim() ? query.trim() : "your system"}{" "}
                <IconArrow size={14} className="arrow" />
              </a>
              <button className="ig-clear" onClick={reset}>
                Clear filters
              </button>
            </div>
          ) : (
            grouped.map((group) => (
              <section key={group.id} className="ig-group">
                <div className="ig-group-head">
                  <h2>{group.label}</h2>
                  <span>{group.items.length}</span>
                </div>
                <div className="ig-grid">
                  {group.items.map((item) => (
                    <IntegrationCard
                      key={item.slug}
                      item={item}
                      categoryLabel={categoryLabel[item.category]}
                    />
                  ))}
                </div>
              </section>
            ))
          )}
        </div>

        <section className="ig-closers">
          <div className="ig-closer">
            <h3>Don’t see your system?</h3>
            <p>
              Most of this catalogue exists because a hotel asked. Tell us what you run and we’ll
              tell you honestly how long it takes.
            </p>
            <a className="btn btn-primary" href={DEMO_URL}>
              Request an integration <IconArrow size={14} className="arrow" />
            </a>
          </div>
          <div className="ig-closer">
            <h3>Or build it yourself.</h3>
            <p>
              A REST API, signed outbound webhooks and an MCP server, so your own systems can read
              and write guest data directly.
            </p>
            <a className="btn btn-ghost" href="/developers">
              Read the docs <IconArrow size={14} className="arrow" />
            </a>
          </div>
        </section>
      </main>

      <SectionFooter />

      <style>{`
        .ig-page { background: var(--bg); }
        .ig-hero { padding: 96px var(--gutter) 40px; }
        .ig-hero-inner { max-width: 780px; margin: 0 auto; text-align: center; }
        .ig-h1 {
          font-family: var(--f-display); font-weight: 400;
          font-size: clamp(38px, 5.4vw, 64px); line-height: 1.06;
          letter-spacing: -0.015em; color: var(--ink); margin: 14px 0 0;
        }
        .ig-sub {
          margin: 20px auto 0; max-width: 560px;
          font-size: 17px; line-height: 1.6; color: var(--ink-2);
        }
        .ig-search-wrap { margin: 34px auto 0; max-width: 560px; }
        .ig-search {
          width: 100%; padding: 17px 22px;
          font-family: var(--f-sans); font-size: 16px; color: var(--ink);
          background: var(--bg-elev-1);
          border: 1px solid var(--hairline-strong);
          border-radius: 999px; outline: none;
          transition: border-color .18s ease, box-shadow .18s ease;
        }
        .ig-search::placeholder { color: var(--ink-3); }
        .ig-search:focus {
          border-color: var(--accent);
          box-shadow: 0 0 0 4px var(--accent-glow);
        }
        .ig-stat {
          margin: 18px 0 0; font-size: 13px; color: var(--ink-3);
          letter-spacing: .02em;
        }
        .ig-stat strong { color: var(--ink-2); font-weight: 600; }

        .ig-filters {
          position: sticky; top: 0; z-index: 20;
          padding: 16px var(--gutter);
          background: color-mix(in srgb, var(--bg) 88%, transparent);
          backdrop-filter: blur(10px);
          border-bottom: 1px solid var(--hairline);
        }
        .ig-filter-row {
          max-width: var(--max-w); margin: 0 auto;
          display: flex; flex-wrap: wrap; gap: 8px; align-items: center;
        }
        .ig-filter-row-sub { margin-top: 10px; }
        .ig-filter-label {
          font-size: 11px; letter-spacing: .12em; text-transform: uppercase;
          color: var(--ink-3); margin-right: 4px;
        }
        .ig-pill {
          display: inline-flex; align-items: center; gap: 7px;
          padding: 8px 15px; border-radius: 999px;
          font-family: var(--f-sans); font-size: 13.5px; color: var(--ink-2);
          background: var(--bg-elev-1);
          border: 1px solid var(--hairline);
          cursor: pointer; transition: all .16s ease;
        }
        .ig-pill.sm { padding: 6px 13px; font-size: 12.5px; }
        .ig-pill:hover:not(:disabled) { border-color: var(--hairline-strong); color: var(--ink); }
        .ig-pill.on {
          background: var(--accent); border-color: var(--accent);
          color: var(--accent-ink); font-weight: 500;
        }
        .ig-pill:disabled { opacity: .34; cursor: default; }
        .ig-pill-n { font-size: 11.5px; opacity: .62; font-variant-numeric: tabular-nums; }

        .ig-results { max-width: var(--max-w); margin: 0 auto; padding: 44px var(--gutter) 0; }
        .ig-group { margin-bottom: 52px; }
        .ig-group-head {
          display: flex; align-items: baseline; gap: 10px;
          margin-bottom: 18px; padding-bottom: 12px;
          border-bottom: 1px solid var(--hairline);
        }
        .ig-group-head h2 {
          font-family: var(--f-sans); font-size: 13px; font-weight: 600;
          letter-spacing: .1em; text-transform: uppercase; color: var(--ink-2); margin: 0;
        }
        .ig-group-head span {
          font-size: 12px; color: var(--ink-3); font-variant-numeric: tabular-nums;
        }

        .ig-grid {
          display: grid; gap: 14px;
          grid-template-columns: repeat(auto-fill, minmax(268px, 1fr));
        }
        .ig-card {
          padding: 20px; border-radius: var(--radius-md);
          background: var(--bg-elev-1); border: 1px solid var(--hairline);
          transition: border-color .18s ease, transform .18s ease;
          /* Column + auto-margin on the facts row keeps the bottom line
           * aligned across a row even though 17 of these have no
           * description. Without it the grid looks broken rather than
           * sparse. */
          display: flex; flex-direction: column;
        }
        .ig-card:hover { border-color: var(--hairline-strong); transform: translateY(-2px); }
        .ig-card-top {
          display: flex; align-items: flex-start; justify-content: space-between;
          gap: 12px; margin-bottom: 15px;
        }
        /* A WIDE plate, not a square one. Nearly every asset here is a
         * horizontal wordmark — Winhotel, SIHOT, Tecsoft, DF Sistemes —
         * and a 52px square renders those about 13px tall, which is a
         * grey smudge. 112x48 lets a 4:1 wordmark be read while an
         * icon-shaped mark still centres happily.
         *
         * White, because most of the collected PNGs carry a baked white
         * plate that would otherwise show as a bright rectangle against
         * the warm page. */
        .ig-logo {
          width: 112px; height: 48px; border-radius: 9px;
          background: var(--bg-elev-2); border: 1px solid var(--hairline);
          display: flex; align-items: center; justify-content: center;
          padding: 7px 11px; overflow: hidden; flex: none;
        }
        .ig-logo img { max-width: 100%; max-height: 100%; object-fit: contain; }
        /* Same 48px square as the monogram so a row mixing the two does
           not look ragged. The mark carries its own brand colour where it
           has one; the ones that do not inherit --ink-2 like the initials. */
        .ig-logo-icon {
          width: 48px; background: var(--bg-elev-2, rgba(128,128,128,.10));
          color: var(--ink-2);
        }
        .ig-logo-mono {
          width: 48px; background: var(--bg-elev-2, rgba(128,128,128,.10));
          color: var(--ink-2);
          font-family: var(--f-mono); font-size: 15px; font-weight: 500;
          letter-spacing: .02em;
        }
        .ig-status {
          padding: 4px 9px; border-radius: 999px;
          font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase;
          color: var(--ink-3); border: 1px solid var(--hairline);
        }
        .ig-name {
          margin: 0; font-family: var(--f-sans);
          font-size: 16.5px; font-weight: 600; letter-spacing: -0.01em; color: var(--ink);
        }
        .ig-meta {
          margin: 5px 0 0; font-size: 12.5px; color: var(--ink-3);
        }
        .ig-desc {
          margin: 11px 0 0; font-size: 13.5px; line-height: 1.55; color: var(--ink-2);
        }
        .ig-facts {
          margin: 13px 0 0; display: flex; flex-wrap: wrap; gap: 12px;
          font-size: 11.5px; color: var(--ink-3);
        }
        .ig-facts strong { color: var(--ink-2); font-variant-numeric: tabular-nums; }

        .ig-empty {
          max-width: 520px; margin: 40px auto 0; text-align: center;
          padding: 46px 32px; border-radius: var(--radius-lg);
          background: var(--bg-elev-1); border: 1px solid var(--hairline);
        }
        .ig-empty h2 {
          font-family: var(--f-display); font-weight: 400; font-size: 27px;
          color: var(--ink); margin: 0 0 12px;
        }
        .ig-empty p {
          font-size: 14.5px; line-height: 1.6; color: var(--ink-2); margin: 0 0 22px;
        }
        .ig-clear {
          display: block; margin: 16px auto 0; background: none; border: none;
          font-family: var(--f-sans); font-size: 13px; color: var(--ink-3);
          text-decoration: underline; cursor: pointer;
        }

        .ig-closers {
          max-width: var(--max-w); margin: 30px auto 0; padding: 0 var(--gutter) 96px;
          display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
        }
        .ig-closer {
          padding: 34px; border-radius: var(--radius-lg);
          background: var(--bg-elev-1); border: 1px solid var(--hairline);
        }
        .ig-closer h3 {
          font-family: var(--f-display); font-weight: 400; font-size: 26px;
          color: var(--ink); margin: 0 0 10px;
        }
        .ig-closer p {
          font-size: 14.5px; line-height: 1.6; color: var(--ink-2); margin: 0 0 20px;
        }

        @media (max-width: 640px) {
          .ig-hero { padding: 68px var(--gutter) 28px; }
          .ig-grid { grid-template-columns: 1fr; }
          .ig-filters { position: static; }
        }
      `}</style>
    </>
  );
};

ReactDOM.createRoot(document.getElementById("root")).render(<IntegrationsCatalogue />);
