import { existsSync, readFileSync } from 'node:fs';
import path from 'node:path';
import process from 'node:process';

const browserRoot = path.resolve(import.meta.dirname, '..');
const shellPath = path.join(browserRoot, 'index.html');
const bootstrapPath = path.join(browserRoot, 'src', 'browser-runtime-bootstrap.js');
const fallbackPath = path.join(browserRoot, 'src', 'browser-spatial-runtime-fallback.js');
const bootStatePath = path.join(browserRoot, 'src', 'runtime', 'boot-state.ts');
const bundledShellSourcePath = path.join(browserRoot, 'src', 'runtime', 'shell.ts');
const registryPath = path.join(browserRoot, 'src', 'runtime', 'registry.ts');
const packagePath = path.join(browserRoot, 'package.json');
const packageLockPath = path.join(browserRoot, 'package-lock.json');
const repoRoot = path.resolve(browserRoot, '..', '..');

const failures = [];

function assert(condition, message)
{
  if (!condition)
  {
    failures.push(message);
  }
}

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

function verifyPhaseOneQueue(sourcePath)
{
  if (!existsSync(sourcePath))
  {
    return;
  }

  const source = readFileSync(sourcePath, 'utf8');
  const positions = [...source.matchAll(/queuePosition:\s*(\d+)/g)]
    .map((match) => Number.parseInt(match[1], 10))
    .sort((left, right) => left - right);
  const expectedPositions = Array.from({ length: 29 }, (_, index) => index + 1);

  assert(
    JSON.stringify(positions) === JSON.stringify(expectedPositions),
    `${path.basename(sourcePath)} must expose each Phase 1 queue position exactly once from 1 through 29.`
  );

  for (const row of phaseOneQueue)
  {
    const rowPattern = new RegExp(
      `queuePosition:\\s*${row.position},[\\s\\S]{0,240}repo:\\s*'${escapeRegExp(row.repo)}'`
    );
    assert(
      rowPattern.test(source),
      `${path.basename(sourcePath)} is missing Phase 1 queue row ${row.position}: ${row.repo}.`
    );
  }
}

const phaseOneQueue = [
  { position: 1, repo: 'cahidenes/rubiks-cube-solver' },
  { position: 2, repo: 'tentone/rubix-solver' },
  { position: 3, repo: 'NuiLab/code-vr' },
  { position: 4, repo: 'pissang/claygl' },
  { position: 5, repo: 'pissang/clay-viewer' },
  { position: 6, repo: 'pmndrs/postprocessing' },
  { position: 7, repo: 'pmndrs/react-postprocessing' },
  { position: 8, repo: 'pmndrs/drei' },
  { position: 9, repo: 'pmndrs/uikit' },
  { position: 10, repo: 'pmndrs/three-stdlib' },
  { position: 11, repo: 'pmndrs/maath' },
  { position: 12, repo: 'pmndrs/zustand' },
  { position: 13, repo: 'pmndrs/leva' },
  { position: 14, repo: 'pmndrs/use-gesture' },
  { position: 15, repo: 'pmndrs/react-spring' },
  { position: 16, repo: 'google/model-viewer/packages/model-viewer-effects' },
  { position: 17, repo: 'google/model-viewer/packages/modelviewer.dev' },
  { position: 18, repo: 'google/model-viewer/packages/render-fidelity-tools' },
  { position: 19, repo: 'google/model-viewer/packages/space-opera' },
  { position: 20, repo: 'ecomfe/zrender' },
  { position: 21, repo: 'ecomfe/echarts-gl' },
  { position: 22, repo: 'KhronosGroup/glTF-Sample-Viewer' },
  { position: 23, repo: 'KhronosGroup/glTF-Sample-Renderer' },
  { position: 24, repo: '@react-spring/core' },
  { position: 25, repo: '@react-spring/shared' },
  { position: 26, repo: '@react-spring/types' },
  { position: 27, repo: '@react-spring/parallax' },
  { position: 28, repo: '@react-spring/rafz' },
  { position: 29, repo: '@react-spring/animated' }
];

assert(existsSync(shellPath), `Missing authoritative browser shell: ${shellPath}`);
assert(existsSync(bootstrapPath), `Missing bootstrap runtime script: ${bootstrapPath}`);
assert(existsSync(fallbackPath), `Missing clean-checkout fallback runtime: ${fallbackPath}`);
assert(existsSync(bootStatePath), `Missing browser boot-state helper: ${bootStatePath}`);
assert(existsSync(bundledShellSourcePath), `Missing bundled runtime shell source: ${bundledShellSourcePath}`);
assert(existsSync(registryPath), `Missing browser adapter registry: ${registryPath}`);
assert(existsSync(packagePath), `Missing browser package manifest: ${packagePath}`);
assert(existsSync(packageLockPath), `Missing browser package lock: ${packageLockPath}`);

verifyPhaseOneQueue(registryPath);
verifyPhaseOneQueue(fallbackPath);

const requiredExternalPaths = [
  '.external/rubiks-cube-solver',
  '.external/rubix-solver',
  '.external/code-vr',
  '.external/claygl/dist/claygl.js',
  '.external/clay-viewer/dist/clay-viewer.js',
  '.external/glTF-Sample-Viewer',
  '.external/glTF-Sample-Renderer',
  '.external/model-viewer/packages/modelviewer.dev',
  '.external/model-viewer/packages/model-viewer-effects',
  '.external/model-viewer/packages/render-fidelity-tools',
  '.external/model-viewer/packages/space-opera',
  '.external/react-spring/packages/animated',
  '.external/react-spring/packages/core',
  '.external/react-spring/packages/parallax',
  '.external/react-spring/packages/rafz',
  '.external/react-spring/packages/shared',
  '.external/react-spring/packages/types'
];

for (const relativePath of requiredExternalPaths)
{
  assert(
    existsSync(path.join(repoRoot, relativePath)),
    `Missing retained Phase 1 source or sidecar evidence: ${relativePath}`
  );
}

if (existsSync(packagePath))
{
  const packageManifest = JSON.parse(readFileSync(packagePath, 'utf8'));
  const dependencies = packageManifest.dependencies ?? {};
  const requiredDirectDependencies = [
    '@google/model-viewer-effects',
    '@khronosgroup/gltf-viewer',
    '@pmndrs/uikit',
    '@react-spring/three',
    '@react-spring/web',
    '@react-three/drei',
    '@react-three/postprocessing',
    '@use-gesture/react',
    'echarts-gl',
    'leva',
    'maath',
    'postprocessing',
    'three-stdlib',
    'zrender',
    'zustand'
  ];

  for (const packageName of requiredDirectDependencies)
  {
    assert(
      typeof dependencies[packageName] === 'string',
      `Missing direct browser runtime dependency: ${packageName}`
    );
  }
}

if (existsSync(packageLockPath))
{
  const packageLock = readFileSync(packageLockPath, 'utf8');
  for (const packageName of [
    '@react-spring/animated',
    '@react-spring/core',
    '@react-spring/rafz',
    '@react-spring/shared',
    '@react-spring/types'
  ])
  {
    assert(
      packageLock.includes(`"node_modules/${packageName}"`),
      `Browser package lock is missing the retained transitive dependency ${packageName}.`
    );
  }
}

if (existsSync(shellPath))
{
  const shellHtml = readFileSync(shellPath, 'utf8');
  assert(
    shellHtml.includes('./src/browser-runtime-bootstrap.js'),
    'The authoritative browser shell must load the bootstrap runtime script.'
  );
  assert(
    !shellHtml.includes('browser-spatial-runtime.ts'),
    'The authoritative browser shell must not point directly at a TypeScript source entrypoint.'
  );
}

if (existsSync(bundledShellSourcePath))
{
  const bundledShellSource = readFileSync(bundledShellSourcePath, 'utf8');
  assert(
    bundledShellSource.includes('Browser Runtime Status'),
    'The bundled runtime shell must expose the Browser Runtime Status surface.'
  );
  assert(
    bundledShellSource.includes('queuedCommandCountAtRuntimeReady'),
    'The bundled runtime shell must surface retained queued command count at runtime-ready.'
  );
  assert(
    bundledShellSource.includes('lastShellStateReceivedAtUtc'),
    'The bundled runtime shell must surface the latest shell-state receipt timestamp.'
  );
}

if (existsSync(fallbackPath))
{
  const fallbackSource = readFileSync(fallbackPath, 'utf8');
  assert(
    fallbackSource.includes('Browser Runtime Status'),
    'The clean-checkout fallback runtime must expose the Browser Runtime Status surface.'
  );
  assert(
    fallbackSource.includes('queuedShellStateCountAtRuntimeReady'),
    'The fallback runtime must surface retained queued shell-state count at runtime-ready.'
  );
  assert(
    fallbackSource.includes('lastCommandReceivedAtUtc'),
    'The fallback runtime must surface the latest command receipt timestamp.'
  );
}

if (failures.length > 0)
{
  for (const failure of failures)
  {
    console.error(failure);
  }
  process.exit(1);
}

console.log('HyperTwist browser shell verification passed.');
