/**
 * commands/state.mjs — state / config / state-update / state-artifacts commands (C29).
 *
 * C22 — `cmdState` adds a TTY-only pretty-print sidecar to stderr after the
 * JSON envelope on stdout. Stdout is byte-identical regardless of TTY, so
 * `... | jq` and test snapshots remain stable.
 */
import { existsSync, globSync, readFileSync, statSync } from 'node:fs';
import { join, resolve } from 'node:path';
import { runGitignoreUpgrade } from '../config/upgrade-gitignore.mjs';
import { cmdConfigUpgradeLayout } from '../config/upgrade-layout.mjs';
import { kv, section, supportsColor } from '../output-style/index.mjs';
import { scanFileForSecrets } from '../risk/secrets.mjs';
import { parseFlags } from '../util/args.mjs';
import { withFileLock } from '../util/fs-lock.mjs';
import { atomicWriteJson } from '../util/io.mjs';
import { err, exitWith, ok } from '../util/result.mjs';
/**
 * Emit a human-readable preview to stderr when stdout is a TTY. Never throws;
 * downstream consumers can always redirect stderr (`2>/dev/null`) when they
 * only want the JSON envelope.
 */
function emitTtyPreview(buildLines) {
    try {
        if (!process.stdout.isTTY)
            return;
        const color = supportsColor({ stream: process.stdout, env: process.env });
        const lines = buildLines(color);
        if (!lines || lines.length === 0)
            return;
        process.stderr.write(lines.join('\n') + '\n');
    }
    catch {
        /* stderr sidecar must never crash the main command */
    }
}
export function cmdState(projectDir) {
    if (!projectDir)
        return err('Usage: apt-tools state <project-dir>');
    const targetDir = resolve(projectDir);
    const aperantDir = join(targetDir, '.aperant');
    const statePath = join(aperantDir, 'state.json');
    const configPath = join(aperantDir, 'config.json');
    if (!existsSync(aperantDir)) {
        return ok({
            status: 'not-initialized',
            projectDir: targetDir,
            message: 'No .aperant/ directory found. Run /apt to initialize.',
        });
    }
    const state = existsSync(statePath) ? JSON.parse(readFileSync(statePath, 'utf-8')) : null;
    const config = existsSync(configPath) ? JSON.parse(readFileSync(configPath, 'utf-8')) : null;
    // Normalize: support both old (active_task) and new (active_tasks) format
    let activeTasks = {};
    if (state?.active_tasks && typeof state.active_tasks === 'object') {
        activeTasks = state.active_tasks;
    }
    else if (state?.active_task) {
        const oldId = state.active_task.id || 'legacy-task';
        activeTasks = { [oldId]: state.active_task };
    }
    // C22 — TTY-only sidecar. Stdout above is unchanged.
    emitTtyPreview((color) => [
        section('Aperant State', { color }),
        kv('project', targetDir, { color }),
        kv('initialized', config !== null ? 'yes' : 'no', { color }),
        kv('active tasks', Object.keys(activeTasks).length, { color }),
        kv('completed', state?.completed_tasks?.length ?? 0, { color }),
        kv('last activity', state?.last_activity ?? null, { color }),
    ]);
    return ok({
        status: 'ok',
        command: 'state',
        projectDir: targetDir,
        initialized: config !== null,
        config_version: config?.version ?? null,
        autonomy_level: config?.autonomy?.default ?? null,
        project_type: config?.project_type ?? null,
        active_tasks: activeTasks,
        active_task_count: Object.keys(activeTasks).length,
        completed_count: state?.completed_tasks?.length ?? 0,
        last_activity: state?.last_activity ?? null,
        decisions_count: state?.decisions ? Object.keys(state.decisions).length : 0,
        state,
        config,
    });
}
export function cmdConfig(projectDir, extraArgs = []) {
    // Back-compat: `apt-tools config <project-dir>` without a subcommand still
    // prints the config. The subcommand form enables R6 helpers like
    // `apt-tools config upgrade-gitignore <project>`.
    if (projectDir === 'upgrade-gitignore') {
        return cmdConfigUpgradeGitignore(extraArgs[0], extraArgs.slice(1));
    }
    if (projectDir === 'upgrade-layout') {
        return cmdConfigUpgradeLayout(extraArgs[0], extraArgs.slice(1));
    }
    if (!projectDir)
        return err('Usage: apt-tools config <project-dir> | upgrade-gitignore <project-dir> | upgrade-layout <project-dir>');
    const targetDir = resolve(projectDir);
    const configPath = join(targetDir, '.aperant', 'config.json');
    if (!existsSync(configPath)) {
        return ok({ status: 'not-initialized', message: 'No .aperant/config.json found.' });
    }
    const config = JSON.parse(readFileSync(configPath, 'utf-8'));
    return ok({ status: 'ok', command: 'config', projectDir: targetDir, ...config });
}
export function cmdConfigUpgradeGitignore(projectDir, extraArgs = []) {
    if (!projectDir) {
        return err('Usage: apt-tools config upgrade-gitignore <project-dir> [--dry-run]');
    }
    const targetDir = resolve(projectDir);
    const flags = parseFlags(extraArgs);
    const dryRun = flags.has('dry-run');
    const result = runGitignoreUpgrade(targetDir, { dryRun });
    return ok({
        status: result.status,
        command: 'config-upgrade-gitignore',
        projectDir: targetDir,
        dryRun: result.dryRun,
        gitignorePath: result.gitignorePath,
        backupPath: result.backupPath,
        replaced: result.replaced,
        trackedButIgnored: result.trackedButIgnored,
        suggestedUntrack: result.suggestedUntrack,
        message: result.replaced
            ? 'gitignore upgraded. Review trackedButIgnored and run the suggested git rm --cached if needed.'
            : result.status === 'noop'
                ? 'gitignore already matches the R6 template — no changes needed.'
                : 'dry run: no files changed.',
    });
}
export function cmdStateUpdate(projectDir, args) {
    if (!projectDir)
        return err('Usage: apt-tools state-update <project-dir> --field <path> --value <json>');
    const targetDir = resolve(projectDir);
    const statePath = join(targetDir, '.aperant', 'state.json');
    if (!existsSync(statePath))
        return err('No .aperant/state.json found.');
    const fieldIdx = args.indexOf('--field');
    const valueIdx = args.indexOf('--value');
    if (fieldIdx < 0 || valueIdx < 0)
        return err('Required: --field <path> --value <json>');
    const field = args[fieldIdx + 1];
    const valueStr = args.slice(valueIdx + 1).join(' ');
    let value;
    try {
        value = JSON.parse(valueStr);
    }
    catch {
        value = valueStr;
    }
    withFileLock(statePath, () => {
        const state = JSON.parse(readFileSync(statePath, 'utf-8'));
        // Support dotted paths: "active_task.lifecycle_phase" -> state.active_task.lifecycle_phase
        const parts = field.split('.');
        let target = state;
        for (let i = 0; i < parts.length - 1; i++) {
            if (target[parts[i]] === undefined || target[parts[i]] === null) {
                target[parts[i]] = {};
            }
            target = target[parts[i]];
        }
        target[parts[parts.length - 1]] = value;
        // Always update last_activity
        state.last_activity = new Date().toISOString();
        atomicWriteJson(statePath, state);
    }, { projectRoot: targetDir });
    return ok({ status: 'ok', command: 'state-update', field, value, projectDir: targetDir });
}
export function cmdStateArtifacts(projectDir, args) {
    if (!projectDir)
        return err('Usage: apt-tools state-artifacts <project-dir> [--scan] [--json]');
    const targetDir = resolve(projectDir);
    const configPath = join(targetDir, '.aperant', 'config.json');
    if (!existsSync(configPath))
        return err('No .aperant/config.json found.');
    const config = JSON.parse(readFileSync(configPath, 'utf-8'));
    const stateCfg = config.state || {};
    if (stateCfg.commit_immutable_artifacts !== true) {
        return ok({
            status: 'disabled',
            command: 'state-artifacts',
            hint: 'Set state.commit_immutable_artifacts=true in .aperant/config.json to enable.',
        });
    }
    const patterns = Array.isArray(stateCfg.artifact_paths) ? stateCfg.artifact_paths : [];
    if (patterns.length === 0) {
        return ok({ status: 'ok', command: 'state-artifacts', matched: [], secrets: [] });
    }
    // Resolve every pattern via fs.globSync against the project root.
    const matched = new Set();
    for (const pattern of patterns) {
        try {
            for (const p of globSync(pattern, { cwd: targetDir })) {
                const full = join(targetDir, p);
                try {
                    if (statSync(full).isFile())
                        matched.add(p);
                }
                catch {
                    /* stat failed, skip */
                }
            }
        }
        catch {
            /* bad pattern, skip */
        }
    }
    const files = [...matched].sort();
    // Secret scan — always run when scan flag set OR config has secret_scan_on_commit.
    const scanRequested = args.includes('--scan') || stateCfg.secret_scan_on_commit === true;
    const secrets = [];
    if (scanRequested) {
        for (const rel of files) {
            const hits = scanFileForSecrets(join(targetDir, rel));
            if (hits.length > 0)
                secrets.push({ file: rel, patterns: hits });
        }
    }
    const envelope = {
        status: secrets.length > 0 ? 'secrets-found' : 'ok',
        command: 'state-artifacts',
        matched_count: files.length,
        matched: files,
        scanned: scanRequested,
        secrets,
    };
    return secrets.length > 0 ? exitWith(envelope, 2) : ok(envelope);
}
//# sourceMappingURL=state.mjs.map