import { createServer as createViteServer, createLogger, type ViteDevServer } from 'vite';
import type http from 'http';
import fs from 'fs';
import path from 'path';
import { PKG_DIR, WORKSPACE_DIR } from '../shared/paths.js';
import { log } from '../shared/logger.js';
import { appendFrontendLog } from './frontend-log.js';

let dashboardVite: ViteDevServer | null = null;

const stripAnsi = (s: string) => String(s).replace(/\x1b\[[0-9;]*m/g, '');

/** A Vite logger that mirrors error/warn to stdout (preserving the human-facing boot/HMR logs) AND
 *  captures them into the server-side frontend ring, so GET /__bloby/control/logs/frontend surfaces
 *  COMPILE/transform errors even when the browser never ran a line of JS (hard compile failure). */
function makeCaptureLogger() {
  const logger = createLogger('info');
  const origError = logger.error.bind(logger);
  const origWarn = logger.warn.bind(logger);
  const origWarnOnce = logger.warnOnce.bind(logger);
  logger.error = (msg, opts) => {
    try { appendFrontendLog('vite-error', stripAnsi(msg), opts?.error?.stack ? stripAnsi(opts.error.stack) : undefined); } catch {}
    origError(msg, opts);
  };
  logger.warn = (msg, opts) => {
    try { appendFrontendLog('vite-warn', stripAnsi(msg)); } catch {}
    origWarn(msg, opts);
  };
  logger.warnOnce = (msg, opts) => {
    try { appendFrontendLog('vite-warn', stripAnsi(msg)); } catch {}
    origWarnOnce(msg, opts);
  };
  return logger;
}

export async function startViteDevServers(supervisorPort: number, hmrServer: http.Server): Promise<{ dashboard: number }> {
  const ports = {
    dashboard: supervisorPort + 2,
  };

  // vite.config.ts keys the dep-prebundle cache dir by package version (.vite-v<version>) so
  // each release re-optimizes exactly once. Sweep the previous releases' dirs (best-effort,
  // async — recharts-sized prebundles are ~10 MB each and would otherwise accumulate forever).
  try {
    let current = 'dev';
    try { current = JSON.parse(fs.readFileSync(path.join(PKG_DIR, 'package.json'), 'utf-8')).version || 'dev'; } catch {}
    const nmDir = path.join(WORKSPACE_DIR, 'client', 'node_modules');
    for (const name of fs.readdirSync(nmDir)) {
      if (name.startsWith('.vite-v') && name !== '.vite-v' + current) {
        fs.rm(path.join(nmDir, name), { recursive: true, force: true }, () => {});
      }
    }
  } catch { /* no node_modules yet / first boot — nothing to sweep */ }

  try {
    dashboardVite = await createViteServer({
      configFile: path.join(PKG_DIR, 'vite.config.ts'),
      server: {
        port: ports.dashboard,
        host: '127.0.0.1',
        strictPort: true,
        allowedHosts: true,
        // HMR WebSocket is attached directly to the supervisor's HTTP server.
        // No clientPort — the browser connects on the same origin the page is served from,
        // so it works both locally (localhost:3000) and through the relay (riven.bloby.bot:443).
        hmr: { server: hmrServer },
      },
      // customLogger captures compile/transform errors into the frontend ring (and still prints
      // them); it supersedes logLevel, which Vite ignores when a customLogger is provided.
      customLogger: makeCaptureLogger(),
    });
    await dashboardVite.listen();
  } catch (err) {
    console.error('[vite-dev] ✗ Dashboard Vite failed:', err);
    throw err;
  }

  log.ok(`Vite HMR active — dashboard :${ports.dashboard}`);

  // Warm up: fetch the dashboard entry to pre-transform modules
  fetch(`http://127.0.0.1:${ports.dashboard}/`).then(r => r.text()).then(async (html) => {
    const scriptRe = /src="([^"]+\.tsx)"/g;
    let m;
    while ((m = scriptRe.exec(html)) !== null) {
      const url = `http://127.0.0.1:${ports.dashboard}${m[1].startsWith('/') ? '' : '/'}${m[1]}`;
      await fetch(url).then(r => r.text()).catch(() => {});
    }
    console.log('__VITE_WARM__');
  }).catch(() => {
    console.log('__VITE_WARM__');
  });

  return ports;
}

/** Tell connected browsers to full-reload via Vite's HMR WebSocket */
export function reloadDashboard(): void {
  if (!dashboardVite) return;
  dashboardVite.hot.send({ type: 'full-reload', path: '*' });
}

export async function stopViteDevServers(): Promise<void> {
  if (dashboardVite) {
    await dashboardVite.close();
    dashboardVite = null;
  }
}
