#!/usr/bin/env node
// Copyright (c) 2026 Hellmai Ltd
// SPDX-License-Identifier: AGPL-3.0-only
/**
 * @file lumenflow-disable.ts
 * Reversible opt-out: flip enforcement flags to false and regenerate hooks as
 * no-ops (WU-2590).
 *
 * Pairs with `lumenflow:enable`. The prior enforcement state is persisted to
 * `.lumenflow/state/enforcement-prior.json` so enable can restore it.
 *
 * Usage:
 *   pnpm lumenflow:disable
 *   pnpm lumenflow:disable --client claude-code
 *   pnpm lumenflow:disable --json
 */
import * as fs from 'node:fs';
import * as path from 'node:path';
// eslint-disable-next-line security/detect-child-process -- legitimate CLI subprocess
import { spawnSync } from 'node:child_process';
import * as yaml from 'yaml';
import { createWUParser, LUMENFLOW_CLIENT_IDS, WORKSPACE_CONFIG_FILE_NAME, WORKSPACE_V2_KEYS, } from '@lumenflow/core';
import { runCLI } from './cli-entry-point.js';
import { integrateClaudeCode } from './commands/integrate.js';
const LOG_PREFIX = '[lumenflow:disable]';
const ENFORCEMENT_FLAGS = [
    'hooks',
    'block_outside_worktree',
    'require_wu_for_edits',
    'warn_on_stop_without_wu_done',
];
const PRIOR_STATE_REL_PATH = path.join('.lumenflow', 'state', 'enforcement-prior.json');
export function parseDisableOptions() {
    const opts = createWUParser({
        name: 'lumenflow-disable',
        description: 'Flip LumenFlow enforcement flags to false and regenerate hooks as no-ops. Reversible via lumenflow:enable.',
        options: [
            {
                name: 'client',
                flags: '--client <type>',
                description: 'Scope to a single client (default: claude-code)',
            },
            {
                name: 'json',
                flags: '--json',
                description: 'Emit machine-readable JSON output',
                type: 'boolean',
            },
        ],
    });
    return {
        client: opts.client ?? LUMENFLOW_CLIENT_IDS.CLAUDE_CODE,
        json: opts.json ?? false,
    };
}
function readWorkspaceYaml(projectDir) {
    const configPath = path.join(projectDir, WORKSPACE_CONFIG_FILE_NAME);
    if (!fs.existsSync(configPath))
        return {};
    const content = fs.readFileSync(configPath, 'utf-8');
    const parsed = yaml.parse(content);
    return parsed && typeof parsed === 'object' ? parsed : {};
}
/**
 * Return the enforcement block for a client, or null if not configured.
 */
export function readClientEnforcement(projectDir, client) {
    const ws = readWorkspaceYaml(projectDir);
    const sd = ws[WORKSPACE_V2_KEYS.SOFTWARE_DELIVERY];
    const agents = sd?.agents;
    const clients = agents?.clients;
    const entry = clients?.[client];
    const enforcement = entry?.enforcement;
    return enforcement ?? null;
}
/**
 * Returns `true` when all enforcement flags are already false / unset.
 */
export function isAlreadyDisabled(enforcement) {
    if (!enforcement)
        return true;
    return ENFORCEMENT_FLAGS.every((flag) => enforcement[flag] !== true);
}
/**
 * Persist prior enforcement state so lumenflow:enable can restore it.
 * Idempotent: if a prior state file already exists, do not overwrite.
 */
export function savePriorState(projectDir, client, enforcement) {
    const priorPath = path.join(projectDir, PRIOR_STATE_REL_PATH);
    fs.mkdirSync(path.dirname(priorPath), { recursive: true });
    let existing = {};
    if (fs.existsSync(priorPath)) {
        try {
            existing = JSON.parse(fs.readFileSync(priorPath, 'utf-8'));
        }
        catch {
            existing = {};
        }
    }
    if (!existing[client]) {
        existing[client] = enforcement;
    }
    fs.writeFileSync(priorPath, JSON.stringify(existing, null, 2) + '\n', 'utf-8');
    return priorPath;
}
/**
 * Write the four enforcement flags to workspace.yaml via `config:set`.
 * Using config:set respects constraint 9 (no raw YAML edits).
 */
export function setEnforcementFlags(projectDir, client, value, run = defaultRunConfigSet) {
    for (const flag of ENFORCEMENT_FLAGS) {
        const key = `${WORKSPACE_V2_KEYS.SOFTWARE_DELIVERY}.agents.clients.${client}.enforcement.${flag}`;
        run(projectDir, [key, String(value)]);
    }
}
function defaultRunConfigSet(projectDir, args) {
    const [key, value] = args;
    if (key == null || value == null) {
        throw new Error('defaultRunConfigSet: expected [key, value]');
    }
    const binPath = path.join(projectDir, 'packages', '@lumenflow', 'cli', 'dist', 'config-set.js');
    const res = spawnSync(process.execPath, [binPath, '--key', key, '--value', value], {
        cwd: projectDir,
        stdio: 'inherit',
    });
    if (res.status !== 0) {
        throw new Error(`config:set failed for key ${key}`);
    }
}
/**
 * Core disable routine. Exposed for testing.
 */
export async function disableForClient(projectDir, client, runConfigSet = defaultRunConfigSet) {
    const enforcement = readClientEnforcement(projectDir, client);
    const alreadyDisabled = isAlreadyDisabled(enforcement);
    if (alreadyDisabled) {
        return { client, alreadyDisabled, priorStateSaved: false };
    }
    // Save prior state (idempotent)
    const priorStatePath = savePriorState(projectDir, client, enforcement);
    // Flip flags to false via config:set
    setEnforcementFlags(projectDir, client, false, runConfigSet);
    // Regenerate hooks as no-ops. With hooks:false, integrateClaudeCode short-
    // circuits and prints "Enforcement hooks not enabled, skipping" — settings
    // are left with existing hook references which become no-ops because the
    // scripts are preserved but flags are false. For a stricter no-op we also
    // clear the settings.json hooks section.
    if (client === LUMENFLOW_CLIENT_IDS.CLAUDE_CODE) {
        await integrateClaudeCode(projectDir, { enforcement: { hooks: false } });
        clearClaudeHooksSection(projectDir);
    }
    return { client, alreadyDisabled: false, priorStateSaved: true, priorStatePath };
}
/**
 * Clear the LumenFlow-managed hooks section from .claude/settings.json while
 * preserving all user-authored content. Keeping this surgical means `enable`
 * can re-populate without clobbering.
 */
export function clearClaudeHooksSection(projectDir) {
    const settingsPath = path.join(projectDir, '.claude', 'settings.json');
    if (!fs.existsSync(settingsPath))
        return;
    const raw = fs.readFileSync(settingsPath, 'utf-8');
    let parsed;
    try {
        parsed = JSON.parse(raw);
    }
    catch {
        return;
    }
    delete parsed.hooks;
    fs.writeFileSync(settingsPath, JSON.stringify(parsed, null, 2) + '\n', 'utf-8');
}
export async function main() {
    const opts = parseDisableOptions();
    const projectDir = process.cwd();
    const result = await disableForClient(projectDir, opts.client);
    if (opts.json) {
        process.stdout.write(JSON.stringify(result, null, 2) + '\n');
        return;
    }
    if (result.alreadyDisabled) {
        console.log(`${LOG_PREFIX} ${opts.client} enforcement is already disabled — nothing to do.`);
    }
    else {
        console.log(`${LOG_PREFIX} Disabled enforcement for ${opts.client}. Prior state saved to ${result.priorStatePath}.`);
    }
    console.log(`${LOG_PREFIX} Restore with: pnpm lumenflow:enable --client ${opts.client}`);
}
if (import.meta.main) {
    void runCLI(main);
}
//# sourceMappingURL=lumenflow-disable.js.map