/**
 * Cross-platform test runner.
 *
 * Runs the platform-sensitive test subset defined in cross-platform-tests.ts
 * via vitest. Used by `npm run test:cross-platform` and by the CI cross-
 * platform matrix (ci-tests.yml).
 *
 * The main vitest.config.ts is used, so lbug-db project files get
 * sequential execution and other safety constraints are preserved.
 */

import { execFileSync } from 'child_process';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { ALL_CROSS_PLATFORM } from './cross-platform-tests.js';
import { parseShardArg } from './shard-arg.js';
import { shardFiles, shardWeight } from './cross-platform-shard.js';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, '..');

// Verify all files exist
const missing = ALL_CROSS_PLATFORM.filter((f) => !fs.existsSync(path.resolve(ROOT, f)));
if (missing.length > 0) {
  console.error(`Cross-platform test files not found (${missing.length}):`);
  for (const f of missing) console.error(`  ${f}`);
  console.error('\nUpdate scripts/cross-platform-tests.ts if files were moved or removed.');
  process.exit(1);
}

// Optional sharding (CI): `--shard=<i>/<n>` splits the fixed file list across
// parallel matrix shards. The split is computed HERE, by measured weight, and
// only this shard's files are handed to vitest — it is NOT passed through,
// because vitest partitions by file COUNT and this suite's runtimes span three
// orders of magnitude (see cross-platform-shard.ts). The
// Windows runner is ~5x slower than macOS/Linux on this spawn-heavy suite (~50
// CLI/worker process spawns), so a single shard was creeping past the watchdog
// below; sharding keeps each runner well under it (see ci-tests.yml matrix).
// Fail loud on a malformed --shard arg (mirrors the missing-files check above):
// a silently-dropped shard flag would run the full unsharded suite and re-trip
// the watchdog. Kept outside the execFileSync try/catch below so the message
// isn't swallowed by that catch's watchdog-only branch.
let shardArg: string | undefined;
try {
  shardArg = parseShardArg(process.argv.slice(2));
} catch (err) {
  console.error(err instanceof Error ? err.message : String(err));
  process.exit(1);
}

// Per-shard watchdog, default 15 min. Sharding splits the file list by COUNT, not
// runtime, so the heaviest spawn suites can cluster on one shard — what this
// bounds is the *busiest* shard, not an even 1/n of wall-clock. The busiest
// Windows shard has grown to the default (14m57s on the v1.6.10-rc.19 green
// run, one observed timeout since — #2449), so CI raises the budget to 20
// minutes via GITNEXUS_CROSS_PLATFORM_TIMEOUT_MINUTES; the default stays 15
// for local runs.
const DEFAULT_TIMEOUT_MIN = 15;
const timeoutMinutes = Number.parseInt(
  process.env.GITNEXUS_CROSS_PLATFORM_TIMEOUT_MINUTES ?? String(DEFAULT_TIMEOUT_MIN),
  10,
);
const timeoutMs =
  Number.isFinite(timeoutMinutes) && timeoutMinutes > 0
    ? timeoutMinutes * 60 * 1000
    : DEFAULT_TIMEOUT_MIN * 60 * 1000;

// Resolve the shard to an explicit file list. `--shard=i/n` is consumed here,
// never forwarded: forwarding it as well would re-partition this slice a second
// time and silently drop most of it.
const shardParts = shardArg?.replace('--shard=', '').split('/');
const shardIndex = shardParts ? Number(shardParts[0]) : 1;
const shardTotal = shardParts ? Number(shardParts[1]) : 1;
const files = shardFiles(ALL_CROSS_PLATFORM, shardIndex, shardTotal);

console.log(
  `Running ${files.length} of ${ALL_CROSS_PLATFORM.length} platform-sensitive tests` +
    `${shardArg ? ` (shard ${shardIndex}/${shardTotal}, ~${shardWeight(files)}s measured weight)` : ''}...\n`,
);

const startedAt = Date.now();
try {
  execFileSync('npx', ['vitest', 'run', ...files], {
    cwd: ROOT,
    stdio: 'inherit',
    timeout: timeoutMs,
    shell: true,
  });
} catch (err) {
  // execFileSync sets `killed`/`signal` when the watchdog above kills vitest.
  const e = err as {
    killed?: boolean;
    signal?: NodeJS.Signals | null;
    status?: number | null;
    code?: string;
  };
  if (e.killed || e.signal) {
    console.error(`vitest timed out after ${Math.round(timeoutMs / 60_000)} minutes`);
  }
  // #2449: Windows shards have died with a bare `status: null`, empty stderr
  // and nothing to triage from. Always leave the child's exit facts behind.
  const elapsedSec = Math.round((Date.now() - startedAt) / 1000);
  console.error(
    `vitest exited abnormally: status=${e.status ?? 'null'} signal=${e.signal ?? 'none'} ` +
      `killed=${e.killed === true} spawnCode=${e.code ?? 'none'} elapsed=${elapsedSec}s ` +
      `budget=${Math.round(timeoutMs / 60_000)}min`,
  );
  process.exit(1);
}
