import { BaseCommand } from '../BaseCommand.js';
import { PromptHelper } from '../PromptHelper.js';
import { GovernanceScaffolder } from '../scaffolding/GovernanceScaffolder.js';
import { ProjectScaffolder } from '../scaffolding/ProjectScaffolder.js';
import { SpawnUtil } from '../utils/spawn.js';
import { readEnvString } from '../../common/ExternalServiceUtils.js';
import { extractErrorMessage, resolvePackageManager } from '../../common/index.js';
import { appConfig } from '../../config/app.js';
import { ErrorFactory } from '../../exceptions/ZintrustError.js';
import { execFileSync } from '../../node-singletons/child-process.js';
import fs from '../../node-singletons/fs.js';
import * as path from '../../node-singletons/path.js';
import chalk from 'chalk';
const getGitBinary = () => 'git';
const checkGitInstalled = () => {
    try {
        execFileSync(getGitBinary(), ['--version'], { stdio: 'ignore', env: appConfig.getSafeEnv() });
        return true;
    }
    catch (error) {
        ErrorFactory.createCliError('Git check failed', error);
        return false;
    }
};
const initializeGitRepo = (projectPath, log) => {
    try {
        const git = getGitBinary();
        const env = appConfig.getSafeEnv();
        execFileSync(git, ['init'], { cwd: projectPath, stdio: 'ignore', env });
        execFileSync(git, ['add', '.'], { cwd: projectPath, stdio: 'ignore', env });
        execFileSync(git, ['commit', '-m', 'Initial commit from ZinTrust'], {
            cwd: projectPath,
            stdio: 'ignore',
            env,
        });
        log.info('✅ Git repository initialized');
    }
    catch (error) {
        ErrorFactory.createCliError('Git initialization failed', error);
        log.warn('Could not initialize git repository');
    }
};
const getStringOption = (options, key, fallback) => {
    const value = options[key];
    return typeof value === 'string' && value !== '' ? value : fallback;
};
const getBooleanOption = (options, key, fallback) => {
    const value = options[key];
    return typeof value === 'boolean' ? value : fallback;
};
const getProjectDefaults = (name, options) => {
    const template = getStringOption(options, 'template', 'basic');
    const normalizeDatabase = (value) => {
        const v = value.trim();
        if (v === 'd1-proxy')
            return 'd1-remote';
        if (v === 'd1')
            return 'd1';
        if (v === 'd1-remote')
            return 'd1-remote';
        if (v === 'sqlite')
            return 'sqlite';
        if (v === 'mysql')
            return 'mysql';
        if (v === 'postgresql' || v === 'postgres')
            return 'postgresql';
        if (v === 'mongodb')
            return 'mongodb';
        return 'sqlite';
    };
    const database = normalizeDatabase(getStringOption(options, 'database', 'sqlite'));
    const portRaw = getStringOption(options, 'port', '7777');
    const portParsed = Number.parseInt(portRaw, 10);
    const port = Number.isFinite(portParsed) && portParsed > 0 ? portParsed : 7777;
    const author = getStringOption(options, 'author', '');
    const description = getStringOption(options, 'description', `A new ZinTrust project: ${name}`);
    const interactive = getBooleanOption(options, 'interactive', true);
    return { name, template, database, port, author, description, interactive };
};
const toConfigResult = (config) => ({
    template: config.template,
    database: config.database,
    port: config.port,
    author: config.author,
    description: config.description,
});
const getQuestions = (name, defaults) => {
    return [
        {
            type: 'rawlist',
            name: 'template',
            message: 'Project template:',
            choices: ['basic', 'api', 'microservice', 'fullstack'],
            default: defaults.template,
        },
        {
            type: 'rawlist',
            name: 'database',
            message: 'Database driver:',
            choices: [
                { name: 'sqlite — Local dev (file-based)', value: 'sqlite' },
                { name: 'postgresql — Production-ready relational DB', value: 'postgresql' },
                { name: 'mysql — Production-ready relational DB', value: 'mysql' },
                { name: 'd1 — Cloudflare D1 database', value: 'd1' },
                { name: 'd1-proxy — Cloudflare D1 via HTTPS proxy', value: 'd1-remote' },
                { name: 'mongodb — Document DB (may require additional setup)', value: 'mongodb' },
            ],
            default: defaults.database,
        },
        {
            type: 'input',
            name: 'port',
            message: 'Default port number:',
            default: String(defaults.port),
            validate: (value) => {
                const port = Number.parseInt(value, 10);
                return Number.isFinite(port) && port > 0 && port < 65536;
            },
        },
        {
            type: 'input',
            name: 'author',
            message: 'Project author:',
            default: defaults.author,
        },
        {
            type: 'input',
            name: 'description',
            message: 'Project description:',
            default: defaults.description === '' ? `A new ZinTrust project: ${name}` : defaults.description,
        },
    ];
};
const mergePromptAnswers = (defaults, answers) => {
    const template = typeof answers['template'] === 'string'
        ? answers['template']
        : defaults.template;
    const database = typeof answers['database'] === 'string'
        ? answers['database']
        : defaults.database;
    let port = defaults.port;
    const portAnswer = answers['port'];
    if (typeof portAnswer === 'string') {
        const parsed = Number.parseInt(portAnswer, 10);
        if (Number.isFinite(parsed))
            port = parsed;
    }
    else if (typeof portAnswer === 'number') {
        if (Number.isFinite(portAnswer))
            port = portAnswer;
    }
    const author = typeof answers['author'] === 'string' ? answers['author'] : defaults.author;
    const description = typeof answers['description'] === 'string' ? answers['description'] : defaults.description;
    return {
        ...defaults,
        template,
        database,
        port,
        author,
        description,
    };
};
const promptForConfig = async (name, options) => {
    const defaults = getProjectDefaults(name, options);
    if (defaults.interactive === false)
        return defaults;
    const questions = getQuestions(name, defaults);
    const answers = await PromptHelper.prompt(questions);
    return mergePromptAnswers(defaults, answers);
};
const isFailureResult = (result) => {
    if (typeof result !== 'object' || result === null)
        return false;
    const maybe = result;
    return maybe.success === false;
};
const promptForPackageManager = async (defaultPm) => {
    const choices = [
        { name: 'npm', value: 'npm' },
        { name: 'yarn', value: 'yarn' },
        { name: 'pnpm', value: 'pnpm' },
        { name: 'bun', value: 'bun' },
        { name: 'Skip installation', value: null },
    ];
    const answer = await PromptHelper.prompt([
        {
            type: 'rawlist',
            name: 'packageManager',
            message: 'Select package manager for dependency installation:',
            choices,
            default: defaultPm,
        },
    ]);
    if (typeof answer !== 'object' || answer === null)
        return null;
    return answer['packageManager'] ?? null;
};
const installDependencies = async (projectPath, log, packageManager, force = false) => {
    // Respect CI by default — avoid network installs in CI unless explicitly allowed
    const ciRaw = readEnvString('CI').trim().toLowerCase();
    const isCi = ciRaw !== '' && ciRaw !== '0' && ciRaw !== 'false';
    const allowAuto = readEnvString('ZINTRUST_ALLOW_AUTO_INSTALL') === '1' || force;
    if (isCi && !allowAuto) {
        log.info('Skipping automatic dependency installation in CI environment.');
        return;
    }
    if (packageManager === null) {
        log.info('⏭️  Skipping dependency installation (not selected).');
        return;
    }
    log.info('📦 Installing dependencies (this may take a minute)...');
    const pm = packageManager ?? resolvePackageManager();
    const args = ['install'];
    try {
        const exitCode = await SpawnUtil.spawnAndWait({ command: pm, args, cwd: projectPath });
        if (exitCode !== 0) {
            throw ErrorFactory.createCliError(`'${pm} install' failed with exit code ${exitCode}`);
        }
        log.info('✅ Dependencies installed successfully');
    }
    catch (error) {
        ErrorFactory.createCliError('Dependency installation failed', error);
        log.warn(`Please run "${pm} install" manually in the project directory`);
    }
};
const addOptions = (command) => {
    command.argument('<name>', 'Project name');
    command.option('--template <type>', 'Project template (basic, api, microservice, fullstack)', 'basic');
    command.option('--database <type>', 'Database driver (sqlite, mysql, postgresql, d1, d1-remote, mongodb)', 'sqlite');
    command.option('--port <number>', 'Default port number', '7777');
    command.option('--author <name>', 'Project author');
    command.option('--description <text>', 'Project description');
    command.option('--interactive', 'Run in interactive mode', true);
    command.option('--no-interactive', 'Disable interactive mode');
    command.option('--no-git', 'Skip git initialization');
    command.option('--no-install', 'Skip dependency installation');
    command.option('--install', 'Force dependency installation (useful to override CI defaults)');
    command.option('--package-manager <manager>', 'Package manager to use (npm, yarn, pnpm)');
    command.option('--governance', 'Install governance tooling (ESLint + architecture tests)', false);
    command.option('--with-d1-proxy', 'Add @zintrust/cloudflare-d1-proxy to package.json (for deploying the D1 proxy worker)', false);
    command.option('--force', 'Overwrite existing directory');
    command.option('--overwrite', 'Overwrite existing directory');
};
const maybeAddD1ProxyDependency = (projectPath, options, log) => {
    const enabled = options['withD1Proxy'] === true || options['with-d1-proxy'] === true;
    if (!enabled)
        return;
    const packageJsonPath = path.join(projectPath, 'package.json');
    if (!fs.existsSync(packageJsonPath)) {
        log.warn(`Could not add @zintrust/cloudflare-d1-proxy (missing package.json at ${packageJsonPath})`);
        return;
    }
    try {
        const raw = fs.readFileSync(packageJsonPath, 'utf8');
        const pkg = JSON.parse(raw);
        const dependencies = { ...pkg.dependencies };
        if (typeof dependencies['@zintrust/cloudflare-d1-proxy'] !== 'string') {
            // Avoid pinning to core version (they can differ). Use a safe caret range.
            dependencies['@zintrust/cloudflare-d1-proxy'] = '^0.1.42';
        }
        const next = {
            ...pkg,
            dependencies,
        };
        fs.writeFileSync(packageJsonPath, JSON.stringify(next, null, 2) + '\n');
        log.info('✅ Added @zintrust/cloudflare-d1-proxy to dependencies');
    }
    catch (error) {
        ErrorFactory.createCliError('Failed to update package.json with D1 proxy dependency', error);
        log.warn('Please add @zintrust/cloudflare-d1-proxy manually if you need the proxy worker.');
    }
};
const resolveProjectName = async (options) => {
    const argName = options.args?.[0];
    const projectName = argName ?? (await PromptHelper.projectName('my-zintrust-app', true));
    if (!projectName)
        throw ErrorFactory.createCliError('Project name is required');
    return projectName;
};
const resolveProjectTarget = async (options) => {
    const input = await resolveProjectName(options);
    const isPathLike = input.includes('/') || input.includes('\\');
    if (!isPathLike) {
        return {
            basePath: process.cwd(),
            name: input,
            projectPath: path.resolve(process.cwd(), input),
            display: input,
            cdPath: input,
        };
    }
    const projectPath = path.resolve(process.cwd(), input);
    const name = path.basename(projectPath);
    const basePath = path.dirname(projectPath);
    const cdPath = input.startsWith('/') ? projectPath : input;
    return {
        basePath,
        name,
        projectPath,
        display: input,
        cdPath,
    };
};
const createProject = async (target, options, command) => {
    const config = await command.getProjectConfig(target.name, options);
    command.info(chalk.bold(`\n🚀 Creating new ZinTrust project in ${target.display}...\n`));
    const overwrite = options['overwrite'] === true || options['force'] === true ? true : undefined;
    const result = await command.runScaffolding(target.basePath, target.name, config, overwrite);
    if (isFailureResult(result)) {
        throw ErrorFactory.createCliError(result.message ?? 'Project scaffolding failed', result);
    }
    if (options['governance'] === true) {
        const gov = await GovernanceScaffolder.scaffold(target.projectPath, {
            writeEslintConfig: true,
            writeArchTests: true,
            install: false,
        });
        if (gov.success === false) {
            throw ErrorFactory.createCliError(gov.message ?? 'Governance scaffolding failed', gov);
        }
    }
    // Optional dependency injection must happen before `npm/yarn/pnpm install`.
    maybeAddD1ProxyDependency(target.projectPath, options, command);
};
const maybeInitializeGit = (options, command, target) => {
    if (options['git'] !== false) {
        command.initializeGit(target.projectPath);
    }
};
const maybeInstallDependencies = async (options, command, target) => {
    if (options['install'] === false)
        return;
    const projectPath = target.projectPath;
    let pm = options['packageManager'] ??
        options['package-manager'];
    // If no package manager specified and not in non-interactive mode, prompt user
    if (pm === undefined && options['interactive'] !== false && options['no-interactive'] !== true) {
        const defaultPm = resolvePackageManager();
        const selectedPm = await command.promptForPackageManager(defaultPm);
        if (selectedPm === null) {
            command.info('⏭️  Skipping dependency installation.');
            pm = '__skip__'; // Signal to skip installation
        }
        else {
            pm = selectedPm;
        }
    }
    const force = options['install'] === true;
    if (pm === '__skip__')
        return;
    const effectivePm = pm ?? resolvePackageManager();
    await installDependencies(projectPath, command, pm, force);
    return effectivePm;
};
const executeNewCommand = async (options, command) => {
    try {
        const target = await resolveProjectTarget(options);
        await createProject(target, options, command);
        maybeInitializeGit(options, command, target);
        await maybeInstallDependencies(options, command, target);
        command.success(`\n✨ Project ${target.name} created successfully!`);
        const runDevCmd = 'zin start';
        command.info(`\nNext steps:\n  cd ${target.cdPath}\n  ${runDevCmd}\n`);
    }
    catch (error) {
        throw ErrorFactory.createCliError(`Project creation failed: ${extractErrorMessage(error)}`, error);
    }
};
const createNewCommandInstance = () => {
    // BaseCommand.getCommand() closes over the execute callback passed at creation time.
    // NewCommand needs its final implementation (which depends on the constructed instance),
    // so we use a mutable indirection.
    let executeImpl = async () => {
        // replaced below with NewCommand-aware execute implementation
    };
    const base = BaseCommand.create({
        name: 'new',
        description: 'Create a new ZinTrust project',
        addOptions,
        execute: async (options) => {
            await executeImpl(options);
        },
    });
    const commandInstance = {
        ...base,
        promptForConfig: async (name, options) => {
            const config = await promptForConfig(name, options);
            return toConfigResult(config);
        },
        getSafeEnv: () => appConfig.getSafeEnv(),
        getGitBinary: () => getGitBinary(),
        getQuestions: (name, defaults) => {
            const fullDefaults = {
                name,
                template: defaults.template,
                database: defaults.database,
                port: defaults.port,
                author: defaults.author,
                description: defaults.description,
                interactive: true,
            };
            return getQuestions(name, fullDefaults);
        },
        getProjectConfig: async (name, options) => {
            return commandInstance.promptForConfig(name, options);
        },
        runScaffolding: async (basePath, name, config, overwrite) => {
            return ProjectScaffolder.scaffold(basePath, {
                name,
                force: overwrite === true,
                template: config.template,
                database: config.database,
                port: config.port,
                author: config.author,
                description: config.description,
            });
        },
        initializeGit: (projectPath) => {
            if (checkGitInstalled()) {
                initializeGitRepo(projectPath, commandInstance);
            }
        },
        promptForPackageManager: async (defaultPm) => {
            return promptForPackageManager(defaultPm);
        },
        execute: async (options) => {
            await executeNewCommand(options, commandInstance);
        },
    };
    executeImpl = commandInstance.execute;
    return commandInstance;
};
/**
 * New Command Factory
 * Sealed namespace for immutability
 */
export const NewCommand = Object.freeze({
    /**
     * Create a new project command instance
     */
    create() {
        return createNewCommandInstance();
    },
});
