#!/usr/bin/env node

import { spawnSync } from 'node:child_process';
import { createRequire } from 'node:module';
import { existsSync, readFileSync } from 'node:fs';
import { resolve } from 'node:path';

const args = process.argv.slice(2);
const includeLint = args.includes('--lint');
const positional = args.filter(arg => !arg.startsWith('--'));
const root = resolve(positional[0] ?? process.cwd());
const require = createRequire(import.meta.url);

function fail(message) {
    throw new Error(message);
}

function readJson(relativePath) {
    const absolutePath = resolve(root, relativePath);
    if (!existsSync(absolutePath)) {
        fail(`Missing ${relativePath}`);
    }
    try {
        return JSON.parse(readFileSync(absolutePath, 'utf8'));
    } catch (error) {
        fail(`Invalid JSON in ${relativePath}: ${error.message}`);
    }
}

function runNpm(script) {
    const windows = process.platform === 'win32';
    const command = windows ? (process.env.ComSpec ?? 'cmd.exe') : 'npm';
    const commandArgs = windows ? ['/d', '/s', '/c', `npm run ${script}`] : ['run', script];
    const result = spawnSync(command, commandArgs, {
        cwd: root,
        stdio: 'inherit'
    });
    if (result.error) {
        fail(`Could not run npm script ${script}: ${result.error.message}`);
    }
    if (result.status !== 0) {
        fail(`npm run ${script} exited with ${result.status}`);
    }
}

function assertContributionPaths(packageJson) {
    const paths = [packageJson.main];
    for (const language of packageJson.contributes?.languages ?? []) {
        paths.push(language.configuration);
    }
    for (const grammar of packageJson.contributes?.grammars ?? []) {
        paths.push(grammar.path);
    }
    for (const snippet of packageJson.contributes?.snippets ?? []) {
        paths.push(snippet.path);
    }
    for (const relativePath of paths.filter(Boolean)) {
        if (!existsSync(resolve(root, relativePath))) {
            fail(`package.json references missing path ${relativePath}`);
        }
    }
}

function smokeTestCore() {
    const tokenizerPath = resolve(root, 'out/tokenizer.js');
    const parserPath = resolve(root, 'out/parser.js');
    if (!existsSync(tokenizerPath) || !existsSync(parserPath)) {
        fail('Compiled tokenizer/parser output is missing');
    }

    const { EftlTokenizer } = require(tokenizerPath);
    const { EftlParser } = require(parserPath);
    const sample = readFileSync(resolve(root, 'examples/sample.eftl'), 'utf8');
    const sampleResult = new EftlTokenizer(sample).tokenize();
    const sampleParse = new EftlParser(sampleResult.tokens).parse();
    if (sampleResult.errors.length || sampleParse.errors.length) {
        fail(`sample.eftl is not accepted: ${sampleResult.errors.length} tokenizer and ${sampleParse.errors.length} parser errors`);
    }

    const mismatched = new EftlTokenizer('[EFTL][IF][/EFTL]').tokenize();
    if (new EftlParser(mismatched.tokens).parse().errors.length === 0) {
        fail('Parser smoke case did not reject mismatched nesting');
    }

    const comment = new EftlTokenizer('[EFTL][!-- unfinished').tokenize();
    if (comment.errors.length === 0) {
        fail('Tokenizer smoke case did not reject an unterminated comment');
    }
}

try {
    const packageJson = readJson('package.json');
    readJson('language-configuration.json');
    readJson('syntaxes/eftl.tmLanguage.json');
    readJson('snippets/eftl.json');

    runNpm('compile');
    assertContributionPaths(packageJson);
    smokeTestCore();

    if (packageJson.scripts?.test) {
        runNpm('test');
    } else {
        console.warn('WARN: package.json has no test script; only smoke cases were run.');
    }

    if (includeLint) {
        runNpm('lint');
    } else if (packageJson.scripts?.lint) {
        console.warn('WARN: lint was skipped; pass --lint after configuring ESLint.');
    }

    console.log('EFTL repository baseline checks passed.');
} catch (error) {
    console.error(`FAIL: ${error.message}`);
    process.exitCode = 1;
}
