import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { isPrerenderCrawler } from "./lib/isPrerenderCrawler";

// Bot visibility override.
// framer-motion's `whileInView` only animates when a section scrolls into
// view. Prerender (kalstein.plus:3002) runs headless Chrome WITHOUT
// scrolling, so any motion.* with `initial={{ opacity: 0 }}` + `whileInView`
// stays stuck at opacity:0 in the captured HTML. Even though the text is in
// the DOM, Encited and similar audit tools that parse rendered HTML
// penalise this as low visibility.
// For bots only, inject a global CSS override that forces full opacity and
// neutral transforms on every element framer-motion has set inline styles
// on. Real users see animations exactly as before.
if (typeof window !== "undefined" && isPrerenderCrawler()) {
  document.documentElement.setAttribute("data-bot", "true");
  const style = document.createElement("style");
  style.id = "prerender-visibility-override";
  style.textContent = `
    html[data-bot="true"] [style*="opacity"] { opacity: 1 !important; }
    html[data-bot="true"] [style*="transform"] { transform: none !important; }
  `;
  document.head.appendChild(style);
}

// Workaround for React "removeChild" error caused by external DOM modifications
// (browser extensions, auto-translate, preview overlays). This is a well-known
// React issue: https://github.com/facebook/react/issues/11538
if (typeof Node !== "undefined") {
  const originalRemoveChild = Node.prototype.removeChild;
  // @ts-ignore — intentional monkey-patch
  Node.prototype.removeChild = function <T extends Node>(child: T): T {
    if (child.parentNode !== this) {
      return child;
    }
    return originalRemoveChild.call(this, child) as T;
  };

  const originalInsertBefore = Node.prototype.insertBefore;
  // @ts-ignore — intentional monkey-patch
  Node.prototype.insertBefore = function <T extends Node>(newNode: T, refNode: Node | null): T {
    if (refNode && refNode.parentNode !== this) {
      return newNode;
    }
    return originalInsertBefore.call(this, newNode, refNode) as T;
  };
}

createRoot(document.getElementById("root")!).render(<App />);

// ============================================================
// Trailing-slash normalizer (SEO safety net)
// ------------------------------------------------------------
// Backend/sitemap serve URLs WITHOUT trailing slash. Ensure:
// 1. <a href="..."> tags never carry a trailing slash on internal paths
// 2. history.pushState / replaceState strip trailing slashes so React
//    Router navigations don't leave "/foo/" in the address bar.
// Excludes: root "/", language-only prefixes (/es/, /en/, etc.) and
// paths that look like assets.
// ============================================================
if (typeof window !== "undefined") {
  const LANG_ONLY = /^\/(es|en|fr|it|nl|de|pt|pl|ar|se|sv)\/?$/i;
  const ASSET_RE = /\.[a-z0-9]{2,5}$/i;

  const stripTrailingSlash = (path: string): string => {
    if (!path || path === "/") return path;
    if (LANG_ONLY.test(path)) return path;
    if (ASSET_RE.test(path)) return path;
    if (path.length > 1 && path.endsWith("/")) return path.replace(/\/+$/, "");
    return path;
  };

  const normalizeUrlString = (url: string): string => {
    try {
      // Relative path (no protocol)
      if (/^\/[^/]/.test(url) || url === "/") {
        const [p, q = ""] = url.split("?");
        const [path, hash = ""] = p.split("#");
        const cleaned = stripTrailingSlash(path);
        return cleaned + (q ? `?${q}` : "") + (hash ? `#${hash}` : (p.includes("#") ? "#" : ""));
      }
      const u = new URL(url, window.location.origin);
      if (u.origin !== window.location.origin) return url; // external untouched
      u.pathname = stripTrailingSlash(u.pathname);
      return u.pathname + u.search + u.hash;
    } catch {
      return url;
    }
  };

  // Patch history methods so React Router updates the bar without trailing slash.
  const origPush = history.pushState;
  const origReplace = history.replaceState;
  history.pushState = function (data: any, unused: string, url?: string | URL | null) {
    if (typeof url === "string") url = normalizeUrlString(url);
    return origPush.call(this, data, unused, url as any);
  };
  history.replaceState = function (data: any, unused: string, url?: string | URL | null) {
    if (typeof url === "string") url = normalizeUrlString(url);
    return origReplace.call(this, data, unused, url as any);
  };

  // Clean current URL on initial load.
  const cleanCurrent = stripTrailingSlash(window.location.pathname);
  if (cleanCurrent !== window.location.pathname) {
    history.replaceState(history.state, "", cleanCurrent + window.location.search + window.location.hash);
  }

  // MutationObserver: scrub <a href> trailing slashes from internal links.
  const cleanAnchor = (a: HTMLAnchorElement) => {
    const raw = a.getAttribute("href");
    if (!raw) return;
    if (/^(mailto:|tel:|javascript:|#)/i.test(raw)) return;
    const cleaned = normalizeUrlString(raw);
    if (cleaned !== raw) a.setAttribute("href", cleaned);
  };
  const scan = (root: ParentNode) => {
    root.querySelectorAll?.("a[href]").forEach((el) => cleanAnchor(el as HTMLAnchorElement));
  };
  const obs = new MutationObserver((mutations) => {
    for (const m of mutations) {
      if (m.type === "attributes" && m.target instanceof HTMLAnchorElement) {
        cleanAnchor(m.target);
      }
      m.addedNodes.forEach((n) => {
        if (n instanceof HTMLAnchorElement) cleanAnchor(n);
        else if (n instanceof Element) scan(n);
      });
    }
  });
  const startObs = () => {
    scan(document);
    obs.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["href"] });
  };
  if (document.body) startObs();
  else document.addEventListener("DOMContentLoaded", startObs);
}

// Prerender.io readiness signaling — content-aware gating so bots receive
// the FULL HTML (h2, microdata, product description) instead of just the
// page shell with a slug-derived <title>.
//
// History v2: the previous gate fired at 5s when document.title changed
// from the default. But document.title is updated immediately from the
// URL slug BEFORE the WooCommerce/Blog data finishes loading via the
// Supabase Edge Function bridge. Result: Prerender captured a snapshot
// with title + h1 derived from the slug but ZERO content (no h2, no
// itemprop, no YR code, no real description). Google indexed empty pages.
//
// New strategy: identify routes that depend on async content (product +
// blog detail pages) and require RICH content signals before marking
// ready. For static routes (home, listings) keep the fast 5s path.
const DEFAULT_TITLE =
  "Kalstein+ | Plataforma Integral de Equipamiento Científico e Industrial";

const markReady = () => {
  if (!(window as any).prerenderReady) {
    (window as any).prerenderReady = true;
  }
};

const LANG_PREFIXES = new Set(["es", "en", "fr", "it", "nl", "de", "pt", "pl", "ar", "se"]);
const STATIC_SINGLE_SEGMENTS = new Set([
  "", "usuarios", "users", "utilisateurs", "utenti", "gebruikers", "benutzer", "utilizadores", "uzytkownicy", "anvandare",
  "distribuidores", "distributors", "distributeurs", "distributori", "distributoren", "dystrybutorzy", "distributorer",
  "fabricantes", "manufacturers", "fabricants", "produttori", "fabrikanten", "hersteller", "producenci", "tillverkare",
  "recomendacion-de-modelo-pc", "model-recommendation-pc", "recommandation-de-modele-pc", "raccomandazione-modello-pc", "model-aanbeveling-pc", "modellempfehlung-pc", "recomendacao-de-modelo-pc", "rekomendacja-modelu-pc", "modellrekommendation-pc",
  "productos", "products", "produits", "prodotti", "partner", "producten", "produkte", "produtos", "produkty", "produkter",
  "cotizacion", "quote", "devis", "preventivo", "offerte", "angebot", "orcamento", "wycena", "offert",
  "contacto", "contact", "contatto", "contatti", "kontakt", "blog", "blogg", "empresa", "company", "entreprise", "azienda", "bedrijf", "unternehmen", "firma", "foretag", "videos", "video", "wideo", "videor", "faq",
  "terminos-y-condiciones", "terms-and-conditions", "conditions-generales", "termini-e-condizioni", "algemene-voorwaarden", "allgemeine-geschaeftsbedingungen", "termos-e-condicoes", "regulamin", "villkor",
  "politica-de-privacidad", "privacy-policy", "politique-de-confidentialite", "informativa-sulla-privacy", "privacybeleid", "datenschutzrichtlinie", "politica-de-privacidade", "polityka-prywatnosci", "integritetspolicy",
]);

// Pages that fetch their content via async API → need stricter gating.
const isAsyncContentRoute = (): boolean => {
  const path = window.location.pathname.toLowerCase();
  const segments = path.split("/").filter(Boolean);
  const contentSegments = LANG_PREFIXES.has(segments[0]) ? segments.slice(1) : segments;
  // Product detail: /producto/, /product/, /produit/, /prodotto/
  // Blog detail:    /blog/<slug>, /noticia/<slug>, /article/<slug>
  // Heuristic: any route with a slug ≥ 3 segments OR known prefixes.
  if (/\/(producto|product|produit|prodotto|productos)\/[^/]+/.test(path)) return true;
  if (/\/(blog|article|noticia|noticias|articulo|articolo)\/[^/]+/.test(path)) return true;
  // Category landing pages (10 localized prefixes, all served by CategoryProducts.tsx)
  if (/\/(categoria-producto|category-product|categorie-produit|categoria-prodotto|productcategorie|produktkategorie|categoria-de-produto|categoria-produto|kategoria-produktu|produktkategori)\//.test(path)) return true;
  // Bare slugs that look like product slugs (contain "yr" + digits)
  if (/yr[\-_]?\d{3,}/i.test(path)) return true;
  // Root catch-all blog/barbacoa articles: /my-article-slug and /es/my-article-slug
  if (contentSegments.length === 1 && !STATIC_SINGLE_SEGMENTS.has(contentSegments[0])) return true;
  return false;
};

// Has the page rendered REAL content (not just shell)?
const hasRealContent = (): boolean => {
  // 1) No loading shell visible
  if (document.querySelector('[data-loading-shell="true"]')) return false;
  // 2) Explicit page marker emitted only by fully-rendered detail views.
  //    For barbacoa the sentinel is mounted AFTER all sections render, so its
  //    presence is a strong signal the full content tree is in the DOM.
  const barbacoaSentinel = document.querySelector('[data-prerender-ready="barbacoa"]') as HTMLElement | null;
  const hasRenderedDetailMarker = !!document.querySelector('[data-prerender-ready="article"], [data-prerender-ready="product"], [data-prerender-ready="category"]') || !!barbacoaSentinel;

  // 2b) Barbacoa-specific extra gate: ensure the sections container has at
  //     least as many direct children as declared by the template, and that
  //     visible text is substantial (intro + sections + FAQ).
  if (barbacoaSentinel) {
    const declared = parseInt(barbacoaSentinel.getAttribute('data-barbacoa-sections-count') || '0', 10);
    const sectionsHost = document.querySelector('[data-prerender-content="barbacoa-sections"]');
    const renderedSections = sectionsHost ? sectionsHost.children.length : 0;
    if (declared > 0 && renderedSections < declared) return false;
    const visibleSections = document.querySelectorAll('[data-prerender-content="barbacoa-sections"] [data-prerender-section="true"]').length;
    if (declared > 0 && visibleSections < declared) return false;
    const lastVisibleSection = document.querySelector('[data-prerender-content="barbacoa-sections"] [data-prerender-section="true"]:last-of-type');
    if (!lastVisibleSection) return false;
    const visibleText = (document.querySelector('main, #root')?.textContent || '').replace(/\s+/g, ' ').trim();
    if (visibleText.length < 1500) return false;
  }
  // 3) JSON-LD/microdata must be paired with visible body depth; metadata alone
  // was the exact failure mode where Google received only the capture shell.
  const lds = document.querySelectorAll('script[type="application/ld+json"]');
  let hasSpecificStructuredData = !!document.querySelector('[itemtype*="schema.org/Product"], [itemtype*="schema.org/Article"]');
  for (const ld of Array.from(lds)) {
    const txt = ld.textContent || "";
    if (/"@type"\s*:\s*"(Product|Article|NewsArticle|BreadcrumbList)"/.test(txt)) {
      hasSpecificStructuredData = true;
      break;
    }
  }
  // 4) Require real visible article/product content, not just header + spinner.
  const contentNodes = Array.from(document.querySelectorAll('[data-prerender-content]'));
  const rootText = (contentNodes.length > 0
    ? contentNodes.map((node) => node.textContent || "").join(" ")
    : (document.querySelector('main, article, #root')?.textContent || ""))
    .replace(/\s+/g, " ")
    .trim();
  const h1Count = document.querySelectorAll('h1').length;
  const h2Count = document.querySelectorAll('main h2, article h2, [data-prerender-content] h2, #root h2').length;
  const pCount = document.querySelectorAll('main p, article p, [data-prerender-content] p, #root p').length;
  // Thresholds lowered so short but valid articles/products still flip ready
  // instead of being indexed as the loading shell.
  if (hasRenderedDetailMarker && h1Count >= 1 && rootText.length >= 300 && (h2Count >= 1 || pCount >= 2)) return true;
  if (hasSpecificStructuredData && h1Count >= 1 && rootText.length >= 500 && (h2Count >= 1 || pCount >= 2)) return true;
  if (h1Count >= 1 && h2Count >= 1 && pCount >= 3 && rootText.length >= 700) return true;
  return false;
};

const checkAndMark = () => {
  const root = document.getElementById("root");
  if (!root) return false;
  if (document.querySelector('[data-loading-shell="true"]')) return false;

  if (isAsyncContentRoute()) {
    // Detail views (blog/product/barbacoa) have page-local readiness gates
    // that verify the actual body text. Do not let this generic fallback
    // flip `prerenderReady` from early metadata or a partially mounted tree.
    if (document.querySelector('[data-prerender-ready="article"], [data-prerender-ready="product"], [data-prerender-ready="barbacoa"], [data-prerender-ready="category"]')) {
      return false;
    }
    // Strict gating: require real product/article content
    if (hasRealContent()) {
      markReady();
      return true;
    }
    return false;
  }

  // Static route → SAME strict contract as detail views.
  // Require BOTH:
  //  a) page-level marker `data-prerender-ready="static"` emitted by
  //     useStaticPagePrerender only after <main> has real text + headings + footer.
  //  b) substantive body (≥2 headings, ≥800 chars, footer present).
  // We deliberately removed the old `titleChanged` and `rootHasDepth`
  // fast-paths: HomeSEO sets document.title via useEffect BEFORE the rest
  // of the React tree mounts, which let Prerender capture an empty shell
  // with the correct <title> (Googlebot indexed it as low-content).
  const staticMarker = document.querySelector('[data-prerender-ready="static"]');
  if (!staticMarker) return false;
  const mainEl = document.querySelector('main') || root;
  const bodyText = (mainEl.textContent || '').replace(/\s+/g, ' ').trim();
  const headings = mainEl.querySelectorAll('h1, h2, h3').length;
  const hasFooter = !!document.querySelector('footer');
  if (hasFooter && headings >= 2 && bodyText.length >= 800) {
    markReady();
    return true;
  }
  return false;
};

// Poll every 500ms — fires as soon as content arrives.
// Extended to ~85s so slow async detail pages (WooCommerce + barbacoa
// templates) can finish hydrating before Prerender snapshots the DOM.
// Prerender on kalstein.plus:3002 has a generous page-load timeout that
// accommodates this window.
let pollAttempts = 0;
const pollInterval = setInterval(() => {
  pollAttempts++;
  if (checkAndMark() || pollAttempts > 170) {
    clearInterval(pollInterval);
  }
}, 500);

// Hard fallback. For async detail routes we still attempt a final mark so
// crawlers don't get stuck on prerenderReady=false; for static routes we
// flip ready unconditionally (the static crawler middleware already served
// a content-rich HTML to bots, so this only matters for human-facing SPA).
setTimeout(() => {
  clearInterval(pollInterval);
  // Unified hard fallback (detail + static): if a loading shell is still
  // visible OR we never reached the strict content gate, leave
  // prerenderReady=false so the page-level <meta name="prerender-status-code"
  // content="503"> tells Prerender to NOT cache the shell. Google will retry.
  if (document.querySelector('[data-loading-shell="true"]')) return;
  if (!checkAndMark()) {
    // Last-resort: only mark ready if there's substantive visible content.
    // Otherwise leave the 503 in place rather than caching an empty snapshot.
    const root = document.getElementById("root");
    const txt = (root?.textContent || "").replace(/\s+/g, " ").trim();
    const headings = document.querySelectorAll('h1, h2, h3').length;
    if (txt.length >= 800 && headings >= 2) markReady();
  }
}, 85000);
