#!/usr/bin/env node
import { mkdirSync, writeFileSync } from "node:fs";
import { dirname, relative, resolve } from "node:path";
import { clone } from "@bufbuild/protobuf";
import { ORDERLY_SSR_HIDDEN_CRITICAL_CSS, ORDERLY_SSR_VISIBLE_CRITICAL_CSS, createOrderlySsrContext, mergeShopSearchQuery } from "../server/ssr.mjs";

const root = process.cwd();
const args = parseArgs(process.argv.slice(2));

if (args.help || args.h) {
  printHelp();
  process.exit(0);
}

const distDir = resolve(root, args.dist ?? "dist");
const productRoot = args["product-root"] ?? "/products/";
const pageRoot = args["page-root"] ?? "/categories/";
const context = await createOrderlySsrContext({
  root,
  navigation: args.navigation,
  navigationExport: args.export,
  shopQuery: args["shop-query"],
  shopQueryExport: args["shop-query-export"],
  pageRoot,
  productRoot,
  baseUrl: args["base-url"],
  protocol: args.protocol,
  siteTitle: args["site-title"],
  imageBaseUrl: args["image-base-url"],
  productsPerCategory: args["products-per-category"],
  cacheTtlMs: 0
});
const manifest = createManifest(context);

write("orderly-ssr-manifest.json", `${JSON.stringify(manifest, null, 2)}\n`);
write("product.php", productPhpSource());
write("category.php", categoryPhpSource());
write(".htaccess", htaccessSource(productRoot, pageRoot));

console.log(`Generated SSR renderers in ${relative(root, distDir) || "."}.`);

function createManifest(ssr) {
  return {
    baseUrl: ssr.baseUrl,
    imageBaseUrl: ssr.imageBaseUrl,
    pageRoot: ssr.pageRoot,
    productOrigin: ssr.productOrigin,
    productRoot: ssr.productRoot,
    protocol: "grpc-web",
    siteTitle: ssr.siteTitle,
    productsPerCategory: ssr.productsPerCategory,
    defaultQuery: plainSearchQuery(ssr.defaultQuery),
    categories: ssr.categories.map((category) => ({
      id: category.id,
      label: category.label,
      href: category.href,
      description: category.metadata.description,
      heroImage: category.metadata.heroImage,
      pathSegments: category.metadata.pathSegments,
      query: plainSearchQuery(mergeShopSearchQuery(clone(ssr.schemas.SearchQuerySchema, category.metadata.query), ssr.defaultQuery))
    }))
  };
}

function plainSearchQuery(query) {
  if (!query) {
    return {};
  }
  return {
    Query: query.Query,
    HiddenQuery: query.HiddenQuery,
    OrderBy: [...query.OrderBy ?? []],
    ShareUrl: query.ShareUrl,
    StoreId: query.StoreId,
    Group: query.Group,
    SourceAccountId: query.SourceAccountId?.Id?.Value,
    ExcludeAccountId: query.ExcludeAccountId?.Id?.Value,
    Tags: (query.Tags ?? []).map((tag) => tag.Value).filter(Boolean),
    Featured: query.Featured || undefined
  };
}

function write(fileName, content) {
  const file = resolve(distDir, fileName);
  mkdirSync(dirname(file), { recursive: true });
  writeFileSync(file, content, "utf8");
}

function htaccessSource(productRootValue, pageRootValue) {
  const product = escapeRewriteRoot(productRootValue);
  const category = escapeRewriteRoot(pageRootValue);
  return `Options -MultiViews
RewriteEngine On

<IfModule mod_headers.c>
  <FilesMatch "\\.(?:js|mjs|css|png|jpe?g|webp|avif|gif|svg|ico|woff2?)$">
    Header set Cache-Control "public, max-age=31536000, immutable"
  </FilesMatch>
  <FilesMatch "\\.(?:html|php)$">
    Header set Cache-Control "no-cache"
  </FilesMatch>
</IfModule>

RewriteRule ^${product}(.+)/?$ product.php?url=$1 [L,QSA,B]
RewriteRule ^${category}(.+)/?$ category.php?slug=$1 [L,QSA,B]

RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
`;
}

function escapeRewriteRoot(value) {
  return String(value || "/").replace(/^\/+|\/+$/g, "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "/";
}

function productPhpSource() {
  return `${phpPreamble()}
$manifest = orderly_manifest();
$shareUrl = orderly_product_share_url($manifest);
$query = orderly_merge_query($manifest['defaultQuery'] ?? [], ['ShareUrl' => $shareUrl]);
$products = orderly_search($manifest, $query);
$product = $products[0] ?? null;
$html = orderly_template('product.html', '<orderly-product-detail-page></orderly-product-detail-page>');
$html = orderly_inject_component($html, 'orderly-product-detail-page', orderly_render_product_detail($product, $shareUrl, $manifest), ' share-url="' . orderly_e($shareUrl) . '"');
$title = $product['Title'] ?? orderly_title_from_share_url($shareUrl);
$html = orderly_inject_meta($html, $title, $product['Description'] ?? $title, orderly_current_url(), $product['ImageUrl'] ?? '');
$html = orderly_inject_critical_ssr_style($html);
orderly_response($html);
${phpRuntime()}
`;
}

function categoryPhpSource() {
  return `${phpPreamble()}
$manifest = orderly_manifest();
$slug = orderly_requested_slug();
$category = orderly_find_category($manifest, $slug);
if (!$category) {
    http_response_code(404);
    orderly_response('Category not found.');
}
$products = orderly_search($manifest, $category['query'] ?? []);
$html = orderly_template('category.html', '<orderly-category-page></orderly-category-page>');
$html = orderly_inject_component($html, 'orderly-category-page', orderly_render_category($category, $products, $manifest), ' slug="' . orderly_e($category['id'] ?? $slug) . '"');
$title = ($category['label'] ?? 'Kategori') . ' | ' . ($manifest['siteTitle'] ?? 'Shop');
$html = orderly_inject_meta($html, $title, $category['description'] ?? '', orderly_current_url(), $category['heroImage'] ?? '');
$html = orderly_inject_critical_ssr_style($html);
orderly_response($html);
${phpRuntime()}
`;
}

function phpPreamble() {
  return `<?php
declare(strict_types=1);
`;
}

function phpRuntime() {
  return String.raw`

function orderly_manifest(): array
{
    $path = getenv('ORDERLY_SSR_MANIFEST') ?: __DIR__ . '/orderly-ssr-manifest.json';
    $json = is_file($path) ? file_get_contents($path) : '{}';
    $manifest = json_decode($json ?: '{}', true);
    return is_array($manifest) ? $manifest : [];
}

function orderly_template(string $name, string $fallback): string
{
    $path = __DIR__ . '/' . $name;
    if (!is_file($path)) {
        return '<!doctype html><html lang="da"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Shop</title></head><body>' . $fallback . '</body></html>';
    }
    $html = file_get_contents($path);
    return $html === false ? $fallback : $html;
}

function orderly_product_share_url(array $manifest): string
{
    $value = $_GET['url'] ?? '';
    $value = is_string($value) ? trim(rawurldecode($value)) : '';
    if ($value === '') {
        $path = parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH);
        $root = $manifest['productRoot'] ?? '/products/';
        $value = is_string($path) && str_starts_with($path, $root) ? trim(substr($path, strlen($root)), '/') : '';
    }
    if ($value === '' || preg_match('~^https?://~i', $value) === 1) {
        return $value;
    }
    return rtrim($manifest['productOrigin'] ?? 'https://orderly.shop/', '/') . '/' . ltrim($value, '/');
}

function orderly_requested_slug(): string
{
    $value = $_GET['slug'] ?? '';
    if (is_string($value) && $value !== '') {
        return trim(rawurldecode($value), '/');
    }
    $path = parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH);
    return is_string($path) ? trim($path, '/') : '';
}

function orderly_find_category(array $manifest, string $slug): ?array
{
    $root = trim($manifest['pageRoot'] ?? 'categories', '/');
    $slug = preg_replace('~^' . preg_quote($root, '~') . '/~', '', trim($slug, '/')) ?? $slug;
    foreach (($manifest['categories'] ?? []) as $category) {
        if (($category['id'] ?? '') === $slug || trim((string) ($category['href'] ?? ''), '/') === $root . '/' . $slug) {
            return $category;
        }
    }
    return null;
}

function orderly_search(array $manifest, array $query): array
{
    $payload = orderly_encode_search_query($query);
    $baseUrl = rtrim(getenv('ORDERLY_BASE_URL') ?: ($manifest['baseUrl'] ?? 'https://service.orderly.shop'), '/');
    $body = "\x00" . pack('N', strlen($payload)) . $payload;
    $headers = "Content-Type: application/grpc-web+proto\r\nAccept: application/grpc-web+proto\r\nx-grpc-web: 1\r\n";
    $extraHeaders = getenv('ORDERLY_SSR_HEADERS');
    if (is_string($extraHeaders) && $extraHeaders !== '') {
        $headers .= $extraHeaders . "\r\n";
    }
    $context = stream_context_create(['http' => [
        'method' => 'POST',
        'timeout' => (float) (getenv('ORDERLY_SSR_TIMEOUT') ?: '4'),
        'header' => $headers,
        'content' => $body,
        'ignore_errors' => true,
    ]]);
    $response = @file_get_contents($baseUrl . '/SearchService/Search', false, $context);
    if (!is_string($response) || $response === '') {
        return [];
    }
    $message = orderly_grpc_web_message($response);
    return $message === '' ? [] : orderly_decode_page_result($message, $manifest);
}

function orderly_merge_query(array $base, array $next): array
{
    foreach ($next as $key => $value) {
        if ($value !== null && $value !== '' && $value !== []) {
            $base[$key] = $value;
        }
    }
    return $base;
}

function orderly_encode_search_query(array $query): string
{
    $out = '';
    foreach (['Query' => 2, 'HiddenQuery' => 15, 'ShareUrl' => 14, 'StoreId' => 16, 'Group' => 27] as $key => $field) {
        if (!empty($query[$key])) {
            $out .= orderly_pb_message($field, orderly_pb_string(1, (string) $query[$key]));
        }
    }
    foreach (($query['Tags'] ?? []) as $tag) {
        if ($tag !== '') {
            $out .= orderly_pb_message(3, orderly_pb_string(1, (string) $tag));
        }
    }
    foreach (($query['OrderBy'] ?? []) as $orderBy) {
        if ($orderBy !== '') {
            $out .= orderly_pb_string(7, (string) $orderBy);
        }
    }
    if (!empty($query['SourceAccountId'])) {
        $out .= orderly_pb_message(4, orderly_pb_message(1, orderly_pb_string(1, (string) $query['SourceAccountId'])));
    }
    if (!empty($query['ExcludeAccountId'])) {
        $out .= orderly_pb_message(9, orderly_pb_message(1, orderly_pb_string(1, (string) $query['ExcludeAccountId'])));
    }
    if (!empty($query['Featured'])) {
        $out .= orderly_pb_varint_field(13, 1);
    }
    return $out;
}

function orderly_grpc_web_message(string $response): string
{
    $offset = 0;
    $length = strlen($response);
    while ($offset + 5 <= $length) {
        $flag = ord($response[$offset]);
        $size = unpack('N', substr($response, $offset + 1, 4))[1];
        $offset += 5;
        $chunk = substr($response, $offset, $size);
        $offset += $size;
        if (($flag & 0x80) === 0) {
            return $chunk;
        }
    }
    return '';
}

function orderly_decode_page_result(string $data, array $manifest): array
{
    $products = [];
    foreach (orderly_pb_fields($data)[3] ?? [] as $field) {
        $any = orderly_pb_fields($field);
        $value = $any[2][0] ?? '';
        if ($value !== '') {
            $products[] = orderly_decode_product($value, $manifest);
        }
    }
    return $products;
}

function orderly_decode_product(string $data, array $manifest): array
{
    $fields = orderly_pb_fields($data);
    $product = [
        'Title' => orderly_wrapper_string($fields[7][0] ?? null),
        'Description' => orderly_wrapper_string($fields[10][0] ?? null),
        'Brand' => orderly_wrapper_string($fields[8][0] ?? null),
        'Size' => orderly_wrapper_string($fields[9][0] ?? null),
        'Condition' => orderly_wrapper_string($fields[12][0] ?? null),
        'Active' => !empty($fields[6][0]),
    ];
    if (!empty($fields[1][0])) {
        $target = orderly_pb_fields($fields[1][0]);
        $id = orderly_pb_fields($target[1][0] ?? '')[1][0] ?? '';
        if ($id !== '') {
            $product['Id'] = $id;
        }
    }
    if (!empty($fields[11][0])) {
        $sku = orderly_pb_fields($fields[11][0]);
        $product['SKU'] = $sku[2][0] ?? '';
    }
    if (!empty($fields[4][0])) {
        $product['Price'] = orderly_decode_credit($fields[4][0]);
    }
    if (!empty($fields[19][0])) {
        $link = orderly_pb_fields($fields[19][0]);
        $product['ShareURL'] = $link[1][0] ?? '';
    }
    if (!empty($fields[2])) {
        $images = [];
        foreach ($fields[2] as $imageData) {
            $image = orderly_pb_fields($imageData);
            $name = $image[2][0] ?? '';
            if ($name !== '') {
                $images[] = $name;
            }
        }
        $product['Images'] = $images;
        $product['ImageUrl'] = orderly_image_url($images[0] ?? '', $manifest);
    }
    return array_filter($product, static fn($value) => $value !== null && $value !== '' && $value !== []);
}

function orderly_decode_credit(string $data): array
{
    $fields = orderly_pb_fields($data, true);
    return [
        'Currency' => $fields[1][0] ?? 'DKK',
        'Amount' => $fields[2][0] ?? 0,
    ];
}

function orderly_pb_fields(string $data, bool $decodeDoubles = false): array
{
    $fields = [];
    $offset = 0;
    $length = strlen($data);
    while ($offset < $length) {
        $key = orderly_read_varint($data, $offset);
        $number = $key >> 3;
        $wire = $key & 7;
        if ($wire === 0) {
            $value = orderly_read_varint($data, $offset);
        } elseif ($wire === 1) {
            $raw = substr($data, $offset, 8);
            $offset += 8;
            $value = $decodeDoubles ? unpack('d', $raw)[1] : $raw;
        } elseif ($wire === 2) {
            $size = orderly_read_varint($data, $offset);
            $value = substr($data, $offset, $size);
            $offset += $size;
        } elseif ($wire === 5) {
            $offset += 4;
            $value = null;
        } else {
            break;
        }
        $fields[$number][] = $value;
    }
    return $fields;
}

function orderly_wrapper_string(?string $data): string
{
    if (!$data) {
        return '';
    }
    $fields = orderly_pb_fields($data);
    return $fields[1][0] ?? '';
}

function orderly_read_varint(string $data, int &$offset): int
{
    $result = 0;
    $shift = 0;
    while ($offset < strlen($data)) {
        $byte = ord($data[$offset++]);
        $result |= ($byte & 0x7f) << $shift;
        if (($byte & 0x80) === 0) {
            return $result;
        }
        $shift += 7;
    }
    return $result;
}

function orderly_pb_varint(int $value): string
{
    $out = '';
    while ($value > 0x7f) {
        $out .= chr(($value & 0x7f) | 0x80);
        $value >>= 7;
    }
    return $out . chr($value);
}

function orderly_pb_varint_field(int $field, int $value): string
{
    return orderly_pb_varint(($field << 3) | 0) . orderly_pb_varint($value);
}

function orderly_pb_string(int $field, string $value): string
{
    return orderly_pb_varint(($field << 3) | 2) . orderly_pb_varint(strlen($value)) . $value;
}

function orderly_pb_message(int $field, string $value): string
{
    return orderly_pb_string($field, $value);
}

function orderly_render_category(array $category, array $products, array $manifest): string
{
    $hero = !empty($category['heroImage']) ? '<img src="' . orderly_e($category['heroImage']) . '" alt="" loading="eager" fetchpriority="high" decoding="async">' : '';
    $cards = orderly_render_product_cards($products, $manifest, empty($category['heroImage']));
    return '<section slot="content-before" class="orderly-ssr-category" data-orderly-ssr-fallback data-orderly-category="' . orderly_e($category['id'] ?? '') . '">
        <header class="orderly-ssr-category__hero">' . $hero . '<div class="orderly-ssr-category__text"><h1 data-orderly-field="title">' . orderly_e($category['label'] ?? '') . '</h1><p data-orderly-field="description">' . orderly_e($category['description'] ?? '') . '</p></div></header>
        <div class="orderly-ssr-product-grid" data-orderly-products>' . $cards . '</div>
      </section>';
}

function orderly_render_product_detail(?array $product, string $shareUrl, array $manifest): string
{
    $product = $product ?? ['Title' => orderly_title_from_share_url($shareUrl), 'ShareURL' => $shareUrl];
    $href = $product['ShareURL'] ?? $shareUrl;
    $image = !empty($product['ImageUrl']) ? orderly_sized_image_url((string) $product['ImageUrl'], 900, 900) : '';
    return '<article slot="content-before" class="orderly-ssr-product-detail" data-orderly-ssr-fallback data-orderly-product ' . orderly_product_attrs($product, $manifest) . ' itemscope itemtype="https://schema.org/Product">
        ' . ($href ? '<link itemprop="url" href="' . orderly_e($href) . '">' : '') . '
        ' . ($image ? '<link itemprop="image" href="' . orderly_e($image) . '">' : '') . '
        ' . (!empty($product['SKU']) ? '<meta itemprop="sku" content="' . orderly_e($product['SKU']) . '">' : '') . '
        ' . ($image ? '<div class="orderly-ssr-product-detail__image"><img src="' . orderly_e($image) . '" alt="' . orderly_e($product['Title'] ?? 'Produkt') . '" loading="eager" fetchpriority="high" decoding="async"></div>' : '') . '
        <section class="orderly-ssr-product-detail__text"><h1 data-orderly-field="title" itemprop="name">' . orderly_e($product['Title'] ?? 'Produkt') . '</h1>' . orderly_offer($product, $href) . '<p data-orderly-field="description" itemprop="description">' . orderly_e($product['Description'] ?? '') . '</p></section>
      </article>';
}

function orderly_render_product_cards(array $products, array $manifest, bool $priorityFirst = false): string
{
    $cards = [];
    foreach ($products as $index => $product) {
        $cards[] = orderly_render_product_card($product, $manifest, $priorityFirst && $index === 0);
    }
    return implode("\n", $cards);
}

function orderly_render_product_card(array $product, array $manifest, bool $priority = false): string
{
    $title = $product['Title'] ?? 'Produkt';
    $href = $product['ShareURL'] ?? '';
    $image = !empty($product['ImageUrl']) ? orderly_sized_image_url((string) $product['ImageUrl'], 500, 500) : '';
    $description = $product['Description'] ?? '';
    $sku = $product['SKU'] ?? '';
    $linkOpen = $href ? '<a class="orderly-ssr-product__link" href="' . orderly_e($href) . '">' : '';
    $linkClose = $href ? '</a>' : '';
    return '<article class="orderly-ssr-product" data-orderly-product ' . orderly_product_attrs($product, $manifest) . ' itemscope itemtype="https://schema.org/Product">' .
        ($href ? '<link itemprop="url" href="' . orderly_e($href) . '">' : '') .
        ($image ? '<link itemprop="image" href="' . orderly_e($image) . '">' : '') .
        ($description ? '<meta itemprop="description" content="' . orderly_e($description) . '">' : '') .
        ($sku ? '<meta itemprop="sku" content="' . orderly_e($sku) . '">' : '') .
        $linkOpen .
          '<div class="orderly-ssr-product__image">' . ($image ? '<img src="' . orderly_e($image) . '" alt="' . orderly_e($title) . '" ' . orderly_ssr_image_attrs($priority) . '>' : '') . '</div>' .
          '<div class="orderly-ssr-product__text"><h3 data-orderly-field="title" itemprop="name">' . orderly_e($title) . '</h3>' .
          (!empty($product['Brand']) ? '<p data-orderly-field="brand" itemprop="brand" itemscope itemtype="https://schema.org/Brand"><span itemprop="name">' . orderly_e($product['Brand']) . '</span></p>' : '') .
          orderly_offer($product, $href) . '</div>' .
        $linkClose .
      '</article>';
}

function orderly_sized_image_url(string $url, int $width, int $height): string
{
    if ($url === '' || stripos($url, 'data:') === 0) {
        return $url;
    }
    $hash = '';
    $hashPos = strpos($url, '#');
    if ($hashPos !== false) {
        $hash = substr($url, $hashPos);
        $url = substr($url, 0, $hashPos);
    }
    $query = [];
    $queryPos = strpos($url, '?');
    $path = $url;
    if ($queryPos !== false) {
        $path = substr($url, 0, $queryPos);
        parse_str(substr($url, $queryPos + 1), $query);
    }
    $query['width'] = (string) $width;
    $query['height'] = (string) $height;
    return $path . '?' . http_build_query($query) . $hash;
}

function orderly_ssr_image_attrs(bool $priority): string
{
    return $priority
        ? 'loading="eager" fetchpriority="high" decoding="async"'
        : 'loading="lazy" decoding="async"';
}

function orderly_product_attrs(array $product, array $manifest): string
{
    $attrs = [];
    foreach (['Id' => 'data-orderly-product-id', 'SKU' => 'data-orderly-sku-value', 'ShareURL' => 'data-orderly-share-url', 'ImageUrl' => 'data-orderly-image-url'] as $key => $attr) {
        if (!empty($product[$key])) {
            $attrs[] = $attr . '="' . orderly_e((string) $product[$key]) . '"';
        }
    }
    if (!empty($product['Price'])) {
        $attrs[] = 'data-orderly-price-amount="' . orderly_e((string) ($product['Price']['Amount'] ?? 0)) . '"';
        $attrs[] = 'data-orderly-price-currency="' . orderly_e((string) ($product['Price']['Currency'] ?? 'DKK')) . '"';
    }
    return implode(' ', $attrs);
}

function orderly_offer(array $product, string $href = ''): string
{
    if (empty($product['Price'])) {
        return '';
    }
    $amount = (float) ($product['Price']['Amount'] ?? 0);
    $currency = (string) ($product['Price']['Currency'] ?? 'DKK');
    $availability = !empty($product['Active']) ? '<link itemprop="availability" href="https://schema.org/InStock">' : '';
    return '<p data-orderly-field="price" data-orderly-price-amount="' . orderly_e((string) $amount) . '" data-orderly-price-currency="' . orderly_e($currency) . '" itemprop="offers" itemscope itemtype="https://schema.org/Offer">' . ($href ? '<link itemprop="url" href="' . orderly_e($href) . '">' : '') . '<meta itemprop="priceCurrency" content="' . orderly_e($currency) . '"><meta itemprop="price" content="' . orderly_e(number_format($amount, 2, '.', '')) . '">' . $availability . orderly_e(orderly_format_credit($product['Price'])) . '</p>';
}

function orderly_image_url(string $name, array $manifest): string
{
    if ($name === '' || preg_match('~^https?://|^/~i', $name) === 1) {
        return $name;
    }
    return rtrim($manifest['imageBaseUrl'] ?? 'https://orderlyproduction.azureedge.net/', '/') . '/thumbnails/' . rawurlencode($name);
}

function orderly_format_credit(array $credit): string
{
    $currency = (string) ($credit['Currency'] ?? 'DKK');
    $prefix = strtoupper($currency) === 'DKK' ? 'kr.' : trim($currency);
    return trim($prefix . ' ' . number_format((float) ($credit['Amount'] ?? 0), 2, ',', '.'));
}

function orderly_inject_component(string $html, string $tag, string $fallback, string $extraAttributes): string
{
    $html = preg_replace('~\s*<!-- orderly-ssr-fallback-start -->[\s\S]*?<!-- orderly-ssr-fallback-end -->\s*~', "\n", $html) ?? $html;
    return preg_replace('~<' . preg_quote($tag, '~') . '\b([^>]*)>~i', '<' . $tag . '$1 data-orderly-ssr' . $extraAttributes . '>' . "\n      <!-- orderly-ssr-fallback-start -->" . $fallback . "\n      <!-- orderly-ssr-fallback-end -->", $html, 1) ?? $html;
}

function orderly_inject_meta(string $html, string $title, string $description, string $canonical, string $image): string
{
    $html = preg_replace('~<title>.*?</title>~is', '<title>' . orderly_e($title) . '</title>', $html, 1) ?? $html;
    $html = orderly_upsert_head($html, '~<meta\s+name=["\']description["\'][^>]*>~i', '<meta name="description" content="' . orderly_e($description) . '">');
    $html = orderly_upsert_head($html, '~<link\s+rel=["\']canonical["\'][^>]*>~i', '<link rel="canonical" href="' . orderly_e($canonical) . '">');
    if ($image !== '') {
        $html = orderly_upsert_head($html, '~<meta\s+property=["\']og:image["\'][^>]*>~i', '<meta property="og:image" content="' . orderly_e($image) . '">');
    }
    return $html;
}

function orderly_inject_critical_ssr_style(string $html): string
{
    $html = preg_replace('~\s*<noscript>\s*<style\b[^>]*data-orderly-ssr-noscript[^>]*>[\s\S]*?</style>\s*</noscript>\s*~i', "\n", $html) ?? $html;
    $html = preg_replace('~\s*<style\b[^>]*data-orderly-ssr-critical[^>]*>[\s\S]*?</style>\s*~i', "\n", $html) ?? $html;
    return str_replace('</head>', '    ' . orderly_critical_ssr_style() . "\n  </head>", $html);
}

function orderly_critical_ssr_style(): string
{
    return orderly_is_googlebot() ? ${phpSingleQuoted(ORDERLY_SSR_VISIBLE_CRITICAL_CSS)} : ${phpSingleQuoted(ORDERLY_SSR_HIDDEN_CRITICAL_CSS)};
}

function orderly_is_googlebot(): bool
{
    $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
    return is_string($userAgent) && preg_match('~Googlebot|Google-InspectionTool|AdsBot-Google|Mediapartners-Google|Storebot-Google~i', $userAgent) === 1;
}

function orderly_upsert_head(string $html, string $pattern, string $tag): string
{
    return preg_match($pattern, $html) === 1 ? (preg_replace($pattern, $tag, $html, 1) ?? $html) : str_replace('</head>', '    ' . $tag . "\n  </head>", $html);
}

function orderly_current_url(): string
{
    $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
    return $scheme . '://' . ($_SERVER['HTTP_HOST'] ?? 'localhost') . ($_SERVER['REQUEST_URI'] ?? '/');
}

function orderly_title_from_share_url(string $shareUrl): string
{
    $path = parse_url($shareUrl, PHP_URL_PATH);
    $name = is_string($path) ? basename($path, '.html') : 'Produkt';
    $name = preg_replace('~-[a-z0-9]{8,}$~i', '', $name) ?? $name;
    return ucwords(str_replace(['-', '_'], ' ', $name ?: 'Produkt'));
}

function orderly_response(string $html): void
{
    header('Content-Type: text/html; charset=UTF-8');
    echo $html;
    exit;
}

function orderly_e(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
`;
}

function phpSingleQuoted(value) {
  return `'${String(value).replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
}

function parseArgs(values) {
  const parsed = {};
  for (let index = 0; index < values.length; index += 1) {
    const value = values[index];
    if (!value.startsWith("--")) {
      continue;
    }
    const [name, inlineValue] = value.slice(2).split("=", 2);
    if (inlineValue !== undefined) {
      parsed[name] = inlineValue;
    } else if (values[index + 1] && !values[index + 1].startsWith("--")) {
      parsed[name] = values[index + 1];
      index += 1;
    } else {
      parsed[name] = true;
    }
  }
  return parsed;
}

function printHelp() {
  console.log(`Usage: orderly-generate-server-renderers [options]

Generate PHP/Apache SSR renderers for product and category URLs.

Options:
  --dist <dir>                 Build output directory. Defaults to dist.
  --navigation <file>          Navigation module. Defaults to src/navigation.ts.
  --export <name>              Export name for navigation definitions. Defaults to navigationDefinitions.
  --shop-query <file>          Optional shop-wide SearchQuery module. Defaults to src/shop-query.ts.
  --shop-query-export <name>   Export name for shop query. Defaults to defaultQuery.
  --page-root <path>           Category URL root. Defaults to /categories/.
  --product-root <path>        Product URL root. Defaults to /products/.
  --base-url <url>             Orderly backend URL. Defaults to ORDERLY_BASE_URL or production.
  --site-title <title>         Site title suffix.
  --products-per-category <n>  Product cards rendered per category. Defaults to 24.
  --help, -h                   Show this help.
`);
}
