#!/usr/bin/env node

import { readdir, readFile } from 'node:fs/promises';
import path from 'node:path';

const workspaceRoot = process.cwd();

const MAX_CONFIG_APP_LINES = 450;
// Raised from 750 to accommodate the login-flows / run-groups feature set. When a
// locale file approaches this ceiling again, split the locale modules rather than
// bumping further.
const MAX_LOCALE_FILE_LINES = 823;
const MAX_UNUSED_LOCALE_KEYS = 154;

const configFilePath = path.join(workspaceRoot, 'src/config/app.ts');
const localeFiles = [
    { locale: 'en', path: path.join(workspaceRoot, 'src/i18n/locales/en.ts') },
    { locale: 'zh-Hans', path: path.join(workspaceRoot, 'src/i18n/locales/zh-hans.ts') },
    { locale: 'zh-Hant', path: path.join(workspaceRoot, 'src/i18n/locales/zh-hant.ts') },
];

function countLines(source) {
    return source.split('\n').length;
}

function extractLocaleKeys(source) {
    const keys = [];
    const keyPattern = /^\s*"([^"]+)":/gm;
    let match = keyPattern.exec(source);
    while (match) {
        keys.push(match[1]);
        match = keyPattern.exec(source);
    }
    return keys;
}

function extractReferencedI18nKeys(source) {
    const keys = new Set();
    const patterns = [
        /\bt\(\s*'([^']+)'/g,
        /\bt\(\s*"([^"]+)"/g,
        /\bt\(\s*`([^$`]+)`/g,
        /\btc\(\s*'([^']+)'/g,
        /\btc\(\s*"([^"]+)"/g,
        /\btc\(\s*`([^$`]+)`/g,
    ];

    for (const pattern of patterns) {
        let match = pattern.exec(source);
        while (match) {
            keys.add(match[1]);
            match = pattern.exec(source);
        }
    }

    return keys;
}

async function listSourceFiles(dirPath) {
    const entries = await readdir(dirPath, { withFileTypes: true });
    const files = [];

    for (const entry of entries) {
        const fullPath = path.join(dirPath, entry.name);

        if (entry.isDirectory()) {
            files.push(...await listSourceFiles(fullPath));
            continue;
        }

        if (!entry.isFile()) {
            continue;
        }

        if (!/\.(ts|tsx)$/.test(entry.name)) {
            continue;
        }

        if (/\.(test|spec)\.(ts|tsx)$/.test(entry.name)) {
            continue;
        }

        files.push(fullPath);
    }

    return files;
}

async function main() {
    const violations = [];

    const configSource = await readFile(configFilePath, 'utf8');
    const configLines = countLines(configSource);
    if (configLines > MAX_CONFIG_APP_LINES) {
        violations.push(`src/config/app.ts is ${configLines} lines (max ${MAX_CONFIG_APP_LINES})`);
    }

    const localeKeyMap = new Map();

    for (const localeFile of localeFiles) {
        const source = await readFile(localeFile.path, 'utf8');
        const lineCount = countLines(source);
        if (lineCount > MAX_LOCALE_FILE_LINES) {
            violations.push(`${path.relative(workspaceRoot, localeFile.path)} is ${lineCount} lines (max ${MAX_LOCALE_FILE_LINES})`);
        }

        const keys = extractLocaleKeys(source);
        localeKeyMap.set(localeFile.locale, new Set(keys));
    }

    const baseLocale = 'en';
    const baseKeys = localeKeyMap.get(baseLocale);
    if (!baseKeys) {
        throw new Error('Base locale en.ts is required for consistency check.');
    }

    for (const [locale, keys] of localeKeyMap.entries()) {
        if (locale === baseLocale) {
            continue;
        }

        for (const key of baseKeys) {
            if (!keys.has(key)) {
                violations.push(`${locale} locale is missing key: ${key}`);
            }
        }

        for (const key of keys) {
            if (!baseKeys.has(key)) {
                violations.push(`${locale} locale has key not found in en locale: ${key}`);
            }
        }
    }

    const sourceFiles = await listSourceFiles(path.join(workspaceRoot, 'src'));
    const referencedKeys = new Set();

    for (const sourceFile of sourceFiles) {
        if (sourceFile.includes(`${path.sep}i18n${path.sep}locales${path.sep}`)) {
            continue;
        }

        const source = await readFile(sourceFile, 'utf8');
        const fileKeys = extractReferencedI18nKeys(source);
        for (const key of fileKeys) {
            referencedKeys.add(key);
        }
    }

    const missingReferencedKeys = Array.from(referencedKeys).filter((key) => !baseKeys.has(key));
    for (const key of missingReferencedKeys) {
        violations.push(`Referenced i18n key not found in en locale: ${key}`);
    }

    const unusedKeys = Array.from(baseKeys).filter((key) => !referencedKeys.has(key));
    if (unusedKeys.length > MAX_UNUSED_LOCALE_KEYS) {
        violations.push(`Unused en locale keys: ${unusedKeys.length} (max ${MAX_UNUSED_LOCALE_KEYS})`);
    }

    if (violations.length > 0) {
        console.error('Config/I18n guardrail check failed:');
        for (const violation of violations) {
            console.error(`- ${violation}`);
        }
        process.exit(1);
    }

    console.log('Config/I18n guardrail check passed:');
    console.log(`- src/config/app.ts lines: ${configLines}/${MAX_CONFIG_APP_LINES}`);
    for (const localeFile of localeFiles) {
        const source = await readFile(localeFile.path, 'utf8');
        const lineCount = countLines(source);
        console.log(`- ${path.relative(workspaceRoot, localeFile.path)} lines: ${lineCount}/${MAX_LOCALE_FILE_LINES}`);
    }
    console.log(`- Unused en locale keys: ${unusedKeys.length}/${MAX_UNUSED_LOCALE_KEYS}`);
}

main().catch((error) => {
    console.error(error instanceof Error ? error.message : String(error));
    process.exit(1);
});
