// tools/configure-permissions.ts — MCP tool handler for configure_permissions (SPEC-594)
//
// Writes (or dry-runs) an idempotent merge into .claude/settings.json for the project.
// NEVER touches ~/.claude/settings.json (user-level settings).
import { readFile, writeFile, mkdir, rename } from 'node:fs/promises';
import { join, dirname } from 'node:path';
import { mergePermissions } from '../engine/permissions-merger/merger.js';
import { getTemplate } from '../engine/permissions-merger/templates.js';
import { collectStackDenyRules, stackDenyRulesToStrings, } from '../engine/permissions-merger/stack-detectors.js';
// ---------------------------------------------------------------------------
// Atomic JSON write: read → merge → write-to-tmp → rename
// This ensures we never leave a partially written settings file.
// ---------------------------------------------------------------------------
async function readSettingsFile(settingsPath) {
    let raw;
    try {
        raw = await readFile(settingsPath, 'utf-8');
    }
    catch {
        // File does not exist — start fresh
        return {};
    }
    try {
        const parsed = JSON.parse(raw);
        if (typeof parsed === 'object' && parsed !== null) {
            return parsed;
        }
    }
    catch {
        // Malformed JSON — treat as empty to avoid leaking content in errors
    }
    return {};
}
async function writeSettingsAtomic(settingsPath, settings) {
    const dir = dirname(settingsPath);
    await mkdir(dir, { recursive: true });
    const tmpPath = `${settingsPath}.tmp-${Date.now().toString()}`;
    await writeFile(tmpPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
    await rename(tmpPath, settingsPath);
}
export async function handleConfigurePermissions(input) {
    const { projectPath, mode, dryRun = false } = input;
    const settingsPath = join(projectPath, '.claude', 'settings.json');
    // Collect stack-specific deny rules (best-effort — never fails the tool)
    const stackRules = await collectStackDenyRules(projectPath).catch(() => []);
    const extraDeny = stackDenyRulesToStrings(stackRules);
    const template = getTemplate(mode);
    const existing = await readSettingsFile(settingsPath);
    const mergeResult = mergePermissions(existing, template, extraDeny);
    let applied = false;
    if (!dryRun && mergeResult.modified) {
        await writeSettingsAtomic(settingsPath, mergeResult.final);
        applied = true;
    }
    const result = {
        projectPath,
        mode,
        settingsPath,
        dryRun,
        plan: mergeResult.plan,
        applied,
        stackRulesInjected: stackRules,
        message: buildMessage(mode, dryRun, mergeResult.modified, applied, stackRules.length),
    };
    return { content: [{ type: 'text', text: formatResult(result) }] };
}
// ---------------------------------------------------------------------------
// Output formatting (concise, < 300 tokens)
// ---------------------------------------------------------------------------
function buildMessage(mode, dryRun, modified, applied, stackRulesCount) {
    if (dryRun) {
        return modified
            ? `Dry-run complete (${mode} mode). Changes would be applied.`
            : `Dry-run complete (${mode} mode). Already up-to-date — no changes needed.`;
    }
    if (!modified) {
        return `Already up-to-date (${mode} mode). No changes needed.`;
    }
    const stackNote = stackRulesCount > 0 ? ` + ${stackRulesCount.toString()} stack-specific deny rules` : '';
    return applied
        ? `Permissions configured (${mode} mode${stackNote}). .claude/settings.json updated.`
        : `Permissions merge computed but not written (unexpected state).`;
}
function formatResult(r) {
    const lines = [
        r.message,
        '',
        `| Field | Value |`,
        `|-------|-------|`,
        `| Mode | ${r.mode} |`,
        `| Settings | ${r.settingsPath} |`,
        `| Dry run | ${String(r.dryRun)} |`,
        `| Applied | ${String(r.applied)} |`,
        `| Allow added | ${r.plan.allowToAdd.length.toString()} |`,
        `| Deny added | ${r.plan.denyToAdd.length.toString()} |`,
        `| Stack deny rules | ${r.stackRulesInjected.length.toString()} |`,
    ];
    if (r.plan.defaultModeAction === 'set' || r.plan.defaultModeAction === 'upgrade') {
        lines.push(`| defaultMode | ${r.plan.defaultModeValue ?? 'unchanged'} (${r.plan.defaultModeAction}) |`);
    }
    if (r.stackRulesInjected.length > 0) {
        lines.push('', '**Stack deny rules injected:**');
        const byStack = new Map();
        for (const sr of r.stackRulesInjected) {
            const existing = byStack.get(sr.stack) ?? [];
            existing.push(sr.rule);
            byStack.set(sr.stack, existing);
        }
        for (const [stack, rules] of byStack) {
            lines.push(`- **${stack}**: ${rules.join(', ')}`);
        }
    }
    return lines.join('\n');
}
//# sourceMappingURL=configure-permissions.js.map