import { promises as fs } from 'fs';
import path from 'path';
import { JsonConverter } from '../core/converters/json-converter.js';
import { Validator } from '../core/validation/validator.js';
import { ChangeParser } from '../core/parsers/change-parser.js';
import { isInteractive } from '../utils/interactive.js';
import { getActiveChangeIds } from '../utils/item-discovery.js';
// Constants for better maintainability
const ARCHIVE_DIR = 'archive';
const TASK_PATTERN = /^[-*]\s+\[[\sx]\]/i;
const COMPLETED_TASK_PATTERN = /^[-*]\s+\[x\]/i;
export class ChangeCommand {
    converter;
    constructor() {
        this.converter = new JsonConverter();
    }
    /**
     * Show a change proposal.
     * - Text mode: raw markdown passthrough (no filters)
     * - JSON mode: minimal object with deltas; --deltas-only returns same object with filtered deltas
     *   Note: --requirements-only is deprecated alias for --deltas-only
     */
    async show(changeName, options) {
        const changesPath = path.join(process.cwd(), 'openspec', 'changes');
        if (!changeName) {
            const canPrompt = isInteractive(options);
            const changes = await this.getActiveChanges(changesPath);
            if (canPrompt && changes.length > 0) {
                const { select } = await import('@inquirer/prompts');
                const selected = await select({
                    message: 'Select a change to show',
                    choices: changes.map(id => ({ name: id, value: id })),
                });
                changeName = selected;
            }
            else {
                if (changes.length === 0) {
                    console.error('No change specified. No active changes found.');
                }
                else {
                    console.error(`No change specified. Available IDs: ${changes.join(', ')}`);
                }
                console.error('Hint: use "openspec change list" to view available changes.');
                process.exitCode = 1;
                return;
            }
        }
        const proposalPath = path.join(changesPath, changeName, 'proposal.md');
        try {
            await fs.access(proposalPath);
        }
        catch {
            throw new Error(`Change "${changeName}" not found at ${proposalPath}`);
        }
        if (options?.json) {
            const jsonOutput = await this.converter.convertChangeToJson(proposalPath);
            if (options.requirementsOnly) {
                console.error('Flag --requirements-only is deprecated; use --deltas-only instead.');
            }
            const parsed = JSON.parse(jsonOutput);
            const contentForTitle = await fs.readFile(proposalPath, 'utf-8');
            const title = this.extractTitle(contentForTitle, changeName);
            const id = parsed.name;
            const deltas = parsed.deltas || [];
            if (options.requirementsOnly || options.deltasOnly) {
                const output = { id, title, deltaCount: deltas.length, deltas };
                console.log(JSON.stringify(output, null, 2));
            }
            else {
                const output = {
                    id,
                    title,
                    deltaCount: deltas.length,
                    deltas,
                };
                console.log(JSON.stringify(output, null, 2));
            }
        }
        else {
            const content = await fs.readFile(proposalPath, 'utf-8');
            console.log(content);
        }
    }
    /**
     * List active changes.
     * - Text default: IDs only; --long prints minimal details (title, counts)
     * - JSON: array of { id, title, deltaCount, taskStatus }, sorted by id
     */
    async list(options) {
        const changesPath = path.join(process.cwd(), 'openspec', 'changes');
        const changes = await this.getActiveChanges(changesPath);
        if (options?.json) {
            const changeDetails = await Promise.all(changes.map(async (changeName) => {
                const proposalPath = path.join(changesPath, changeName, 'proposal.md');
                const tasksPath = path.join(changesPath, changeName, 'tasks.md');
                try {
                    const content = await fs.readFile(proposalPath, 'utf-8');
                    const changeDir = path.join(changesPath, changeName);
                    const parser = new ChangeParser(content, changeDir);
                    const change = await parser.parseChangeWithDeltas(changeName);
                    let taskStatus = { total: 0, completed: 0 };
                    try {
                        const tasksContent = await fs.readFile(tasksPath, 'utf-8');
                        taskStatus = this.countTasks(tasksContent);
                    }
                    catch (error) {
                        // Tasks file may not exist, which is okay
                        if (process.env.DEBUG) {
                            console.error(`Failed to read tasks file at ${tasksPath}:`, error);
                        }
                    }
                    return {
                        id: changeName,
                        title: this.extractTitle(content, changeName),
                        deltaCount: change.deltas.length,
                        taskStatus,
                    };
                }
                catch (error) {
                    return {
                        id: changeName,
                        title: 'Unknown',
                        deltaCount: 0,
                        taskStatus: { total: 0, completed: 0 },
                    };
                }
            }));
            const sorted = changeDetails.sort((a, b) => a.id.localeCompare(b.id));
            console.log(JSON.stringify(sorted, null, 2));
        }
        else {
            if (changes.length === 0) {
                console.log('No items found');
                return;
            }
            const sorted = [...changes].sort();
            if (!options?.long) {
                // IDs only
                sorted.forEach(id => console.log(id));
                return;
            }
            // Long format: id: title and minimal counts
            for (const changeName of sorted) {
                const proposalPath = path.join(changesPath, changeName, 'proposal.md');
                const tasksPath = path.join(changesPath, changeName, 'tasks.md');
                try {
                    const content = await fs.readFile(proposalPath, 'utf-8');
                    const title = this.extractTitle(content, changeName);
                    let taskStatusText = '';
                    try {
                        const tasksContent = await fs.readFile(tasksPath, 'utf-8');
                        const { total, completed } = this.countTasks(tasksContent);
                        taskStatusText = ` [tasks ${completed}/${total}]`;
                    }
                    catch (error) {
                        if (process.env.DEBUG) {
                            console.error(`Failed to read tasks file at ${tasksPath}:`, error);
                        }
                    }
                    const changeDir = path.join(changesPath, changeName);
                    const parser = new ChangeParser(await fs.readFile(proposalPath, 'utf-8'), changeDir);
                    const change = await parser.parseChangeWithDeltas(changeName);
                    const deltaCountText = ` [deltas ${change.deltas.length}]`;
                    console.log(`${changeName}: ${title}${deltaCountText}${taskStatusText}`);
                }
                catch {
                    console.log(`${changeName}: (unable to read)`);
                }
            }
        }
    }
    async validate(changeName, options) {
        const changesPath = path.join(process.cwd(), 'openspec', 'changes');
        if (!changeName) {
            const canPrompt = isInteractive(options);
            const changes = await getActiveChangeIds();
            if (canPrompt && changes.length > 0) {
                const { select } = await import('@inquirer/prompts');
                const selected = await select({
                    message: 'Select a change to validate',
                    choices: changes.map(id => ({ name: id, value: id })),
                });
                changeName = selected;
            }
            else {
                if (changes.length === 0) {
                    console.error('No change specified. No active changes found.');
                }
                else {
                    console.error(`No change specified. Available IDs: ${changes.join(', ')}`);
                }
                console.error('Hint: use "openspec change list" to view available changes.');
                process.exitCode = 1;
                return;
            }
        }
        const changeDir = path.join(changesPath, changeName);
        try {
            await fs.access(changeDir);
        }
        catch {
            throw new Error(`Change "${changeName}" not found at ${changeDir}`);
        }
        const validator = new Validator(options?.strict || false);
        const report = await validator.validateChangeDeltaSpecs(changeDir);
        if (options?.json) {
            console.log(JSON.stringify(report, null, 2));
        }
        else {
            if (report.valid) {
                console.log(`Change "${changeName}" is valid`);
            }
            else {
                console.error(`Change "${changeName}" has issues`);
                report.issues.forEach(issue => {
                    const label = issue.level === 'ERROR' ? 'ERROR' : 'WARNING';
                    const prefix = issue.level === 'ERROR' ? '✗' : '⚠';
                    console.error(`${prefix} [${label}] ${issue.path}: ${issue.message}`);
                });
                // Next steps footer to guide fixing issues
                this.printNextSteps();
                if (!options?.json) {
                    process.exitCode = 1;
                }
            }
        }
    }
    async getActiveChanges(changesPath) {
        try {
            const entries = await fs.readdir(changesPath, { withFileTypes: true });
            const result = [];
            for (const entry of entries) {
                if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name === ARCHIVE_DIR)
                    continue;
                const proposalPath = path.join(changesPath, entry.name, 'proposal.md');
                try {
                    await fs.access(proposalPath);
                    result.push(entry.name);
                }
                catch {
                    // skip directories without proposal.md
                }
            }
            return result.sort();
        }
        catch {
            return [];
        }
    }
    extractTitle(content, changeName) {
        const match = content.match(/^#\s+(?:Change:\s+)?(.+)$/im);
        return match ? match[1].trim() : changeName;
    }
    countTasks(content) {
        const lines = content.split('\n');
        let total = 0;
        let completed = 0;
        for (const line of lines) {
            if (line.match(TASK_PATTERN)) {
                total++;
                if (line.match(COMPLETED_TASK_PATTERN)) {
                    completed++;
                }
            }
        }
        return { total, completed };
    }
    printNextSteps() {
        const bullets = [];
        bullets.push('- Ensure change has deltas in specs/: use headers ## ADDED/MODIFIED/REMOVED/RENAMED Requirements');
        bullets.push('- Each requirement MUST include at least one #### Scenario: block');
        bullets.push('- Debug parsed deltas: openspec change show <id> --json --deltas-only');
        console.error('Next steps:');
        bullets.forEach(b => console.error(`  ${b}`));
    }
}
//# sourceMappingURL=change.js.map