/**
 * prerender.mjs — Genera HTML statico per le route pubbliche.
 *
 * Uso:
 *   pnpm build:static   →  quasar build  +  node scripts/prerender.mjs
 *
 * Output:
 *   dist/spa/index.html          (route /)
 *   dist/spa/someroute/index.html (any additional public route)
 *
 * ─── Handler Go (esempio GoFiber) ────────────────────────────────────────────
 *
 *   app.Static("/", "./dist/spa", fiber.Static{
 *       Index: "index.html",
 *       Browse: false,
 *       MaxAge: 0,     // nessuna cache per l'HTML
 *   })
 *
 *   // Fallback: serve index.html per qualsiasi percorso sconosciuto (routing client SPA)
 *   app.Use(func(c *fiber.Ctx) error {
 *       // Serve l'HTML prerenderizzato se esiste, altrimenti ripiega sulla shell SPA
 *       candidate := "./dist/spa" + c.Path() + "/index.html"
 *       if _, err := os.Stat(candidate); err == nil {
 *           return c.SendFile(candidate)
 *       }
 *       return c.SendFile("./dist/spa/index.html")
 *   })
 *
 * ─── Esempio net/http ─────────────────────────────────────────────────────────
 *
 *   fs := http.FileServer(http.Dir("./dist/spa"))
 *   http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 *       candidate := "./dist/spa" + path.Clean(r.URL.Path) + "/index.html"
 *       if _, err := os.Stat(candidate); err == nil {
 *           http.ServeFile(w, r, candidate)
 *           return
 *       }
 *       // Serve la root prerenderizzata o la shell SPA
 *       r2 := r.Clone(r.Context())
 *       r2.URL.Path = "/"
 *       fs.ServeHTTP(w, r2)
 *   })
 *
 * ─────────────────────────────────────────────────────────────────────────────
 */

import {
  copyFileSync,
  cpSync,
  createReadStream,
  existsSync,
  mkdirSync,
  readFileSync,
  readdirSync,
  rmSync,
  statSync,
  writeFileSync,
} from 'node:fs';
import { createServer } from 'node:http';
import { extname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import puppeteer from 'puppeteer';

const __dirname = fileURLToPath(new URL('.', import.meta.url));

const DIST_DIR = resolve(__dirname, '../dist/spa');
const ENV_FILE = resolve(__dirname, '../.env');
const PORT = 4173;

/**
 * Route pubbliche da prerenderizzare.
 * Ogni voce produrra dist/spa{route}/index.html
 */
export const PUBLIC_ROUTES = [
  '/',
  '/login',
  '/signup',
  '/recoverpassword',
  '/about',
  '/services',
  '/doctors',
  '/doctordetails',
  '/contact',
  // '/about',
  // '/terms',
  // '/privacy',
];

const MIME_TYPES = {
  '.html': 'text/html; charset=utf-8',
  '.js': 'application/javascript',
  '.mjs': 'application/javascript',
  '.css': 'text/css',
  '.png': 'image/png',
  '.jpg': 'image/jpeg',
  '.jpeg': 'image/jpeg',
  '.svg': 'image/svg+xml',
  '.ico': 'image/x-icon',
  '.json': 'application/json',
  '.woff2': 'font/woff2',
  '.woff': 'font/woff',
  '.ttf': 'font/ttf',
};

function startServer() {
  const server = createServer((req, res) => {
    const url = req.url?.split('?')[0] ?? '/';
    let filePath = join(DIST_DIR, url === '/' ? 'index.html' : url);

    // Fallback SPA: percorsi sconosciuti o senza estensione -> index.html
    if (!existsSync(filePath) || !extname(filePath)) {
      filePath = join(DIST_DIR, 'index.html');
    }

    const mime = MIME_TYPES[extname(filePath)] ?? 'application/octet-stream';
    res.setHeader('Content-Type', mime);
    res.setHeader('Cache-Control', 'no-store');

    createReadStream(filePath)
      .on('error', () => {
        res.writeHead(404);
        res.end('Not found');
      })
      .pipe(res);
  });

  return new Promise((resolve) => server.listen(PORT, () => resolve(server)));
}

function parseEnvFile(filePath) {
  if (!existsSync(filePath)) {
    return {};
  }

  const data = readFileSync(filePath, 'utf-8');
  const env = {};

  for (const line of data.split('\n')) {
    const trimmed = line.trim();
    if (!trimmed || trimmed.startsWith('#')) {
      continue;
    }

    const idx = trimmed.indexOf('=');
    if (idx <= 0) {
      continue;
    }

    const key = trimmed.slice(0, idx).trim();
    let value = trimmed.slice(idx + 1).trim();

    if (
      (value.startsWith('"') && value.endsWith('"')) ||
      (value.startsWith("'") && value.endsWith("'"))
    ) {
      value = value.slice(1, -1);
    }

    env[key] = value;
  }

  return env;
}

function copyDirectoryContent(srcDir, destDir) {
  mkdirSync(destDir, { recursive: true });

  for (const entry of readdirSync(srcDir)) {
    const srcPath = join(srcDir, entry);
    const destPath = join(destDir, entry);
    const stat = statSync(srcPath);

    if (stat.isDirectory()) {
      cpSync(srcPath, destPath, { recursive: true, force: true });
    } else {
      copyFileSync(srcPath, destPath);
    }
  }
}

function copySpaToGoTarget() {
  const envFromFile = parseEnvFile(ENV_FILE);

  const goProjectDir = process.env.GO_PROJECT_DIR ?? envFromFile.GO_PROJECT_DIR;
  const goSpaTargetDir =
    process.env.GO_SPA_TARGET_DIR ??
    envFromFile.GO_SPA_TARGET_DIR ??
    (goProjectDir ? join(goProjectDir, 'spa') : null);

  if (!goSpaTargetDir) {
    console.log('│  copy     : skipped (set GO_PROJECT_DIR or GO_SPA_TARGET_DIR in .env)');
    return;
  }

  const resolvedTarget = resolve(goSpaTargetDir);

  rmSync(resolvedTarget, { recursive: true, force: true });
  mkdirSync(resolvedTarget, { recursive: true });
  copyDirectoryContent(DIST_DIR, resolvedTarget);

  console.log(`│  copy     : ${DIST_DIR} -> ${resolvedTarget}`);
}

async function prerender() {
  console.log('┌─ Static prerender ────────────────────────────────────────────');
  console.log(`│  dist dir : ${DIST_DIR}`);
  console.log(`│  routes   : ${PUBLIC_ROUTES.join(', ')}`);
  console.log('│');

  if (!existsSync(DIST_DIR)) {
    console.error('│  ERROR: dist/spa not found — run `pnpm build` first.');
    process.exit(1);
  }

  const server = await startServer();

  const browser = await puppeteer.launch({
    headless: true,
    args: ['--no-sandbox', '--disable-setuid-sandbox'],
  });

  for (const route of PUBLIC_ROUTES) {
    process.stdout.write(`│  ${route.padEnd(30)} → `);

    const page = await browser.newPage();

    // Silenzia il rumore della console del browser
    page.on('console', () => {});

    await page.goto(`http://localhost:${PORT}${route}`, {
      waitUntil: 'networkidle0',
      timeout: 30_000,
    });

    const html = await page.content();

    const outDir = route === '/' ? DIST_DIR : join(DIST_DIR, route);
    mkdirSync(outDir, { recursive: true });
    writeFileSync(join(outDir, 'index.html'), html, 'utf-8');

    console.log(`${outDir}/index.html`);
    await page.close();
  }

  await browser.close();
  server.close();

  copySpaToGoTarget();

  console.log('│');
  console.log('└─ Done ✓');
}

prerender().catch((err) => {
  console.error(err);
  process.exit(1);
});
