// Build-time prerenderer.
//
// Renders every route to static HTML so crawlers that do not execute JavaScript
// (Meta, LinkedIn, Slack, Bing) receive real content, correct per-route <title>,
// meta description, canonical, and Open Graph tags. Google also benefits: pages
// no longer sit in its deferred JS-rendering queue.
//
// Output layout (consumed by server/index.js):
//   dist/index.html               -> /
//   dist/about/index.html         -> /about
//   dist/services/<slug>/index.html
//   dist/404.html                 -> served with a real 404 status
//
// Run automatically as part of `npm run build`.

import { execFileSync } from 'child_process'
import { mkdirSync, readFileSync, writeFileSync } from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'

import { render } from '../dist-ssr/entry-server.js'
import { services } from '../src/data/services.js'
import { industries } from '../src/data/industries.js'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const distDir = path.join(__dirname, '../dist')

const STATIC_ROUTES = [
  '/',
  '/about',
  '/services',
  '/industries',
  '/contact',
  '/support',
  '/privacy-policy',
]

const routes = [
  ...STATIC_ROUTES,
  ...services.map((s) => `/services/${s.id}`),
  ...industries.map((i) => `/industries/${i.id}`),
]

// Tags the SEO component owns per-route. They are stripped from the template so
// Helmet's values replace them instead of duplicating them.
const TEMPLATE_TAGS_TO_STRIP = [
  /<title>[\s\S]*?<\/title>\s*/,
  /<meta name="description"[^>]*>\s*/,
  /<meta property="og:[^"]*"[^>]*>\s*/g,
  /<meta name="twitter:[^"]*"[^>]*>\s*/g,
  /<!-- Open Graph fallback for crawlers that don't execute JavaScript -->\s*/,
  /<!-- Twitter \/ X Card fallback -->\s*/,
]

// Metadata React renders inside the component tree. React 19 hoists these into
// <head> in the browser and in its streaming renderer, but renderToString leaves
// them inline, so the prerenderer performs the same hoist. Leaving them in <body>
// would put every title, canonical, and og: tag somewhere crawlers ignore.
const HOISTABLE_TAGS =
  /<title[^>]*>[\s\S]*?<\/title>|<meta\b[^>]*?\/?>|<link\b[^>]*?\/?>|<script[^>]*type="application\/ld\+json"[^>]*>[\s\S]*?<\/script>/g

const buildPage = (template, url) => {
  const { html } = render(url)

  const hoisted = html.match(HOISTABLE_TAGS) || []
  const body = html.replace(HOISTABLE_TAGS, '')

  // Preload this route's own LCP image. The hero is the one marked eager during
  // render, so the hint always matches what the page actually paints first.
  const heroSrc = html
    .match(/<img[^>]*loading="eager"[^>]*>/)?.[0]
    .match(/src="([^"]+)"/)?.[1]

  const head = []
  if (heroSrc) {
    head.push(`<link rel="preload" as="image" href="${heroSrc}" fetchpriority="high" />`)
  }
  // React emits its own image preload for the eager hero; drop it so the hint
  // above isn't duplicated.
  for (const tag of hoisted) {
    if (/rel="preload"[^>]*as="image"/.test(tag)) continue
    head.push(tag)
  }

  let page = template
  for (const pattern of TEMPLATE_TAGS_TO_STRIP) {
    page = page.replace(pattern, '')
  }

  page = page.replace('</head>', `  ${head.join('\n    ')}\n  </head>`)
  page = page.replace('<div id="root"></div>', `<div id="root">${body}</div>`)

  return page
}

const outputPathFor = (url) =>
  url === '/'
    ? path.join(distDir, 'index.html')
    : path.join(distDir, url, 'index.html')

const template = readFileSync(path.join(distDir, 'index.html'), 'utf8')

const written = []
for (const url of routes) {
  const page = buildPage(template, url)
  const outPath = outputPathFor(url)
  mkdirSync(path.dirname(outPath), { recursive: true })
  writeFileSync(outPath, page)
  written.push([url, page.length])
}

// Dedicated 404 document. Rendering an unmatched path hits the catch-all route,
// which carries `noindex, follow` — now visible to crawlers in static HTML.
const notFoundPage = buildPage(template, '/__not_found__')
writeFileSync(path.join(distDir, '404.html'), notFoundPage)
written.push(['404.html', notFoundPage.length])

console.log(`\nPrerendered ${written.length} pages:`)
for (const [url, size] of written) {
  console.log(`  ${url.padEnd(42)} ${(size / 1024).toFixed(1)} KB`)
}

// --- sitemap.xml -------------------------------------------------------------
// Generated from the same route list that drives prerendering, so the sitemap can
// never drift out of sync with what the site actually serves.

const SITE_URL = 'https://queuenorth.com'

// lastmod comes from git history for the files that produce each route. A build
// timestamp would mark every page as changed on every deploy, which search engines
// learn to distrust.
const lastModifiedFor = (files) => {
  let newest = null
  for (const file of files) {
    try {
      const iso = execFileSync('git', ['log', '-1', '--format=%cI', '--', file], {
        cwd: path.join(__dirname, '..'),
        encoding: 'utf8',
        stdio: ['ignore', 'pipe', 'ignore'],
      }).trim()
      if (iso && (!newest || iso > newest)) newest = iso
    } catch {
      // git unavailable or file untracked — fall through
    }
  }
  return newest ? newest.slice(0, 10) : null
}

const ROUTE_SOURCES = {
  '/': ['src/pages/Home.jsx'],
  '/about': ['src/pages/About.jsx'],
  '/services': ['src/pages/Services.jsx', 'src/data/services.js'],
  '/industries': ['src/pages/Industries.jsx', 'src/data/industries.js'],
  '/contact': ['src/pages/Contact.jsx'],
  '/support': ['src/pages/Support.jsx'],
  '/privacy-policy': ['src/pages/PrivacyPolicy.jsx', 'src/data/privacyPolicy.js'],
}

const PRIORITY = {
  '/': '1.0',
  '/services': '0.9',
  '/contact': '0.9',
  '/about': '0.8',
  '/industries': '0.8',
  '/support': '0.8',
  '/privacy-policy': '0.3',
}

const CHANGEFREQ = { '/': 'weekly', '/privacy-policy': 'yearly' }

const sourcesFor = (url) => {
  if (ROUTE_SOURCES[url]) return ROUTE_SOURCES[url]
  if (url.startsWith('/services/')) return ['src/pages/ServiceDetail.jsx', 'src/data/services.js']
  return ['src/pages/IndustryDetail.jsx', 'src/data/industries.js']
}

const entries = routes.map((url) => {
  const loc = url === '/' ? SITE_URL : `${SITE_URL}${url}`
  const lastmod = lastModifiedFor(sourcesFor(url))
  return [
    '  <url>',
    `    <loc>${loc}</loc>`,
    lastmod ? `    <lastmod>${lastmod}</lastmod>` : null,
    `    <changefreq>${CHANGEFREQ[url] || 'monthly'}</changefreq>`,
    `    <priority>${PRIORITY[url] || '0.7'}</priority>`,
    '  </url>',
  ]
    .filter(Boolean)
    .join('\n')
})

const sitemap = [
  '<?xml version="1.0" encoding="UTF-8"?>',
  '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
  ...entries,
  '</urlset>',
  '',
].join('\n')

writeFileSync(path.join(distDir, 'sitemap.xml'), sitemap)
console.log(`\nGenerated sitemap.xml with ${routes.length} URLs`)
