import { execSync } from 'node:child_process';
import { lstat, readFile } from 'node:fs/promises';
import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
import { atomicWriteJson, dirExists, fileExists } from '../fs/io.js';
import { normalizeToPosixPath, validateRealPathWithinScope } from '../fs/paths.js';
import { normalizeDispatchMatrix, } from './dispatch-matrix.js';
import { parseJsonConfig } from './json.js';
export { VALID_DISPATCH_MATRIX_TIERS, isCodexMaterializedRouteTarget, isWorkflowDispatchCandidateLadder, isWorkflowDispatchFallbackRoute, toWorkflowDispatchCandidateLadder, validateDispatchRouteTarget, } from './dispatch-matrix.js';
export const MIN_GATE_TIMEOUT_MS = 1_000;
export const MAX_GATE_TIMEOUT_MS = 14_400_000;
export function isValidGateTimeoutMs(value) {
    return (typeof value === 'number' &&
        Number.isInteger(value) &&
        value >= MIN_GATE_TIMEOUT_MS &&
        value <= MAX_GATE_TIMEOUT_MS);
}
const VALID_HILL_CHECKPOINT_DEFAULTS = ['every', 'final'];
const VALID_POST_IMPLEMENT_LEGACY_SEQUENCES = ['wait', 'summary', 'pr', 'docs-pr'];
const VALID_POST_IMPLEMENT_STEPS = [
    'summary',
    'document',
    'pr',
];
const LEGACY_POST_IMPLEMENT_SEQUENCES = {
    wait: { preApproval: [], postApproval: [] },
    summary: { preApproval: ['summary'], postApproval: [] },
    pr: { preApproval: ['summary', 'pr'], postApproval: [] },
    'docs-pr': {
        preApproval: ['summary', 'document', 'pr'],
        postApproval: [],
    },
};
const VALID_REVIEW_EXECUTION_MODELS = [
    'subagent',
    'inline',
    'fresh-session',
];
const VALID_DESIGN_MODES = [
    'collaborative',
    'selective',
    'draft',
];
export const VALID_CODEX_DISPATCH_CEILINGS = ['low', 'medium', 'high', 'xhigh', 'max'];
export const VALID_CLAUDE_DISPATCH_CEILINGS = ['haiku', 'sonnet', 'opus', 'fable'];
export const VALID_DISPATCH_CEILING_PRESETS = ['balanced', 'maximum', 'cost-conscious'];
export const VALID_DISPATCH_POLICY_MODES = ['managed', 'inherit'];
export const VALID_MANAGED_DISPATCH_POLICIES = ['economy', 'balanced', 'high', 'frontier', 'uncapped'];
const VALID_GATE_ON_FAILURES = [
    'block',
    'prompt',
    'warn',
];
export const BUILTIN_EXEC_TARGETS = {
    'codex-default': {
        runtime: 'codex',
        baseCommand: ['codex', 'exec'],
        invocation: {
            model: 'provider-default',
            reasoningEffort: 'provider-default',
        },
        hostDetectionCommand: [
            'sh',
            '-c',
            '[ -n "$CODEX_THREAD_ID" ] || [ -n "$CODEX_SESSION_ID" ]',
        ],
        availabilityCommand: ['codex', '--version'],
        priority: 100,
    },
    'claude-default': {
        runtime: 'claude',
        baseCommand: ['claude', '-p'],
        invocation: {
            model: 'provider-default',
            reasoningEffort: 'provider-default',
        },
        hostDetectionCommand: ['sh', '-c', 'test -n "$CLAUDECODE"'],
        availabilityCommand: ['claude', '--version'],
        priority: 100,
    },
    'cursor-default': {
        runtime: 'cursor',
        baseCommand: ['cursor-agent', '-p'],
        invocation: {
            model: 'provider-default',
            reasoningEffort: 'provider-default',
        },
        hostDetectionCommand: ['sh', '-c', 'test -n "$CURSOR_AGENT"'],
        availabilityCommand: [
            'sh',
            '-c',
            'command -v cursor-agent || command -v agent',
        ],
        priority: 70,
    },
};
export function normalizeWorkflowPostImplementSequence(value) {
    if (typeof value === 'string' &&
        VALID_POST_IMPLEMENT_LEGACY_SEQUENCES.includes(value)) {
        const legacy = LEGACY_POST_IMPLEMENT_SEQUENCES[value];
        return {
            preApproval: [...legacy.preApproval],
            postApproval: [...legacy.postApproval],
        };
    }
    if (!isRecord(value)) {
        return undefined;
    }
    const keys = Object.keys(value);
    if (keys.length !== 2 ||
        !keys.includes('preApproval') ||
        !keys.includes('postApproval') ||
        !Array.isArray(value.preApproval) ||
        !Array.isArray(value.postApproval)) {
        return undefined;
    }
    const steps = [...value.preApproval, ...value.postApproval];
    if (!steps.every((step) => typeof step === 'string' &&
        VALID_POST_IMPLEMENT_STEPS.includes(step)) ||
        new Set(steps).size !== steps.length) {
        return undefined;
    }
    return {
        preApproval: [...value.preApproval],
        postApproval: [...value.postApproval],
    };
}
export function isWorkflowPostImplementStructuredSequence(value) {
    return (isRecord(value) &&
        normalizeWorkflowPostImplementSequence(value) !== undefined);
}
function normalizeMaxAttempts(value) {
    if (typeof value !== 'number' || !Number.isFinite(value)) {
        return 2;
    }
    const attempts = Math.trunc(value);
    return attempts >= 1 ? attempts : 2;
}
function normalizeArgv(value) {
    if (!Array.isArray(value) ||
        value.length === 0 ||
        !value.every((item) => typeof item === 'string')) {
        return undefined;
    }
    const [executable] = value;
    if (executable === undefined || !executable.trim()) {
        return undefined;
    }
    return [...value];
}
function normalizeStringList(value) {
    if (!Array.isArray(value) ||
        value.length === 0 ||
        !value.every((item) => typeof item === 'string' && item.trim().length > 0)) {
        return undefined;
    }
    return value.map((item) => item.trim());
}
function normalizeGateConfig(value) {
    if (value === null) {
        return null;
    }
    if (!isRecord(value)) {
        return undefined;
    }
    const command = trimNonEmptyString(value.command);
    if (command === undefined ||
        typeof value.onFailure !== 'string' ||
        !VALID_GATE_ON_FAILURES.includes(value.onFailure)) {
        return undefined;
    }
    const gate = {
        command,
        onFailure: value.onFailure,
        maxAttempts: normalizeMaxAttempts(value.maxAttempts),
    };
    const description = trimNonEmptyString(value.description);
    if (description !== undefined) {
        gate.description = description;
    }
    return gate;
}
function normalizeExecTarget(value) {
    if (value === null) {
        return null;
    }
    if (!isRecord(value)) {
        return undefined;
    }
    const target = {};
    const runtime = trimNonEmptyString(value.runtime);
    if (runtime !== undefined) {
        target.runtime = runtime;
    }
    const baseCommand = normalizeArgv(value.baseCommand);
    if (baseCommand !== undefined) {
        target.baseCommand = baseCommand;
    }
    if (isRecord(value.invocation)) {
        const model = trimNonEmptyString(value.invocation.model);
        const reasoningEffort = trimNonEmptyString(value.invocation.reasoningEffort);
        if (model !== undefined || reasoningEffort !== undefined) {
            target.invocation = {
                ...(model !== undefined ? { model } : {}),
                ...(reasoningEffort !== undefined ? { reasoningEffort } : {}),
            };
        }
    }
    const models = normalizeStringList(value.models);
    if (models !== undefined) {
        target.models = models;
    }
    if ('priority' in value) {
        if (typeof value.priority === 'number' && Number.isFinite(value.priority)) {
            target.priority = value.priority;
        }
    }
    if (isValidGateTimeoutMs(value.timeoutMs)) {
        target.timeoutMs = value.timeoutMs;
    }
    const hostDetectionCommand = normalizeArgv(value.hostDetectionCommand);
    if (hostDetectionCommand !== undefined) {
        target.hostDetectionCommand = hostDetectionCommand;
    }
    const availabilityCommand = normalizeArgv(value.availabilityCommand);
    if (availabilityCommand !== undefined) {
        target.availabilityCommand = availabilityCommand;
    }
    return Object.keys(target).length > 0 ? target : undefined;
}
function normalizeRecordMap(value, normalizeValue) {
    if (!isRecord(value)) {
        return undefined;
    }
    const next = {};
    for (const [key, rawEntry] of Object.entries(value)) {
        if (!key.trim()) {
            continue;
        }
        const normalized = normalizeValue(rawEntry);
        if (normalized !== undefined) {
            next[key] = normalized;
        }
    }
    return Object.keys(next).length > 0 ? next : undefined;
}
function normalizeWorkflowConfig(parsed) {
    if (!isRecord(parsed)) {
        return undefined;
    }
    const next = {};
    if (typeof parsed.hillCheckpointDefault === 'string' &&
        VALID_HILL_CHECKPOINT_DEFAULTS.includes(parsed.hillCheckpointDefault)) {
        next.hillCheckpointDefault =
            parsed.hillCheckpointDefault;
    }
    if (typeof parsed.archiveOnComplete === 'boolean') {
        next.archiveOnComplete = parsed.archiveOnComplete;
    }
    if (typeof parsed.createPrOnComplete === 'boolean') {
        next.createPrOnComplete = parsed.createPrOnComplete;
    }
    const postImplementSequence = normalizeWorkflowPostImplementSequence(parsed.postImplementSequence);
    if (postImplementSequence !== undefined) {
        next.postImplementSequence =
            typeof parsed.postImplementSequence === 'string'
                ? parsed.postImplementSequence
                : postImplementSequence;
    }
    if (typeof parsed.reviewExecutionModel === 'string' &&
        VALID_REVIEW_EXECUTION_MODELS.includes(parsed.reviewExecutionModel)) {
        next.reviewExecutionModel =
            parsed.reviewExecutionModel;
    }
    if (typeof parsed.autoReviewAtHillCheckpoints === 'boolean') {
        next.autoReviewAtHillCheckpoints = parsed.autoReviewAtHillCheckpoints;
    }
    if (typeof parsed.autoNarrowReReviewScope === 'boolean') {
        next.autoNarrowReReviewScope = parsed.autoNarrowReReviewScope;
    }
    if (isRecord(parsed.autoArtifactReview)) {
        const autoArtifactReview = {};
        if (typeof parsed.autoArtifactReview.plan === 'boolean') {
            autoArtifactReview.plan = parsed.autoArtifactReview.plan;
        }
        if (typeof parsed.autoArtifactReview.analysis === 'boolean') {
            autoArtifactReview.analysis = parsed.autoArtifactReview.analysis;
        }
        if (Object.keys(autoArtifactReview).length > 0) {
            next.autoArtifactReview = autoArtifactReview;
        }
    }
    if (typeof parsed.projectLog === 'boolean' || parsed.projectLog === 'auto') {
        next.projectLog = parsed.projectLog;
    }
    const projectLogLedgerPath = trimNonEmptyString(parsed.projectLogLedgerPath);
    if (projectLogLedgerPath !== undefined) {
        next.projectLogLedgerPath = projectLogLedgerPath;
    }
    if (typeof parsed.designMode === 'string' &&
        VALID_DESIGN_MODES.includes(parsed.designMode)) {
        next.designMode = parsed.designMode;
    }
    if (isRecord(parsed.dispatchPolicy)) {
        const mode = typeof parsed.dispatchPolicy.mode === 'string' &&
            VALID_DISPATCH_POLICY_MODES.includes(parsed.dispatchPolicy.mode)
            ? parsed.dispatchPolicy.mode
            : undefined;
        if (mode === 'inherit') {
            next.dispatchPolicy = { mode };
        }
        if (mode === 'managed' &&
            typeof parsed.dispatchPolicy.policy === 'string' &&
            VALID_MANAGED_DISPATCH_POLICIES.includes(parsed.dispatchPolicy.policy)) {
            next.dispatchPolicy = {
                mode,
                policy: parsed.dispatchPolicy.policy,
            };
        }
    }
    if (isRecord(parsed.dispatchCeiling)) {
        const dispatchCeiling = {};
        if (typeof parsed.dispatchCeiling.preset === 'string' &&
            VALID_DISPATCH_CEILING_PRESETS.includes(parsed.dispatchCeiling.preset)) {
            dispatchCeiling.preset = parsed.dispatchCeiling
                .preset;
        }
        const recommendationVersion = trimNonEmptyString(parsed.dispatchCeiling.recommendationVersion);
        if (recommendationVersion !== undefined) {
            dispatchCeiling.recommendationVersion = recommendationVersion;
        }
        if (isRecord(parsed.dispatchCeiling.providers)) {
            const normalized = normalizeDispatchMatrix(parsed.dispatchCeiling.providers, {
                pathPrefix: 'workflow.dispatchCeiling.providers',
                compatibilityMode: 'layered-config',
            });
            if (Object.keys(normalized.providers).length > 0) {
                dispatchCeiling.providers = normalized.providers;
            }
        }
        if (Object.keys(dispatchCeiling).length > 0) {
            next.dispatchCeiling = dispatchCeiling;
        }
    }
    if (isRecord(parsed.gateTimeouts)) {
        const gateTimeouts = {};
        if (isValidGateTimeoutMs(parsed.gateTimeouts.code)) {
            gateTimeouts.code = parsed.gateTimeouts.code;
        }
        if (isValidGateTimeoutMs(parsed.gateTimeouts.artifact)) {
            gateTimeouts.artifact = parsed.gateTimeouts.artifact;
        }
        if (Object.keys(gateTimeouts).length > 0) {
            next.gateTimeouts = gateTimeouts;
        }
    }
    if (isRecord(parsed.gates)) {
        const gates = {};
        const execTargets = normalizeRecordMap(parsed.gates.execTargets, normalizeExecTarget);
        if (execTargets !== undefined) {
            gates.execTargets = execTargets;
        }
        const skills = normalizeRecordMap(parsed.gates.skills, normalizeGateConfig);
        if (skills !== undefined) {
            gates.skills = skills;
        }
        if (Object.keys(gates).length > 0) {
            next.gates = gates;
        }
    }
    return Object.keys(next).length > 0 ? next : undefined;
}
const DEFAULT_OAT_CONFIG = { version: 1 };
const DEFAULT_OAT_LOCAL_CONFIG = { version: 1 };
const DEFAULT_USER_CONFIG = { version: 1 };
function getConfigPath(repoRoot) {
    return join(repoRoot, '.oat', 'config.json');
}
function getLocalConfigPath(repoRoot) {
    return join(repoRoot, '.oat', 'config.local.json');
}
function isRecord(value) {
    return typeof value === 'object' && value !== null;
}
function isMissingFileError(error) {
    return (typeof error === 'object' &&
        error !== null &&
        'code' in error &&
        error.code === 'ENOENT');
}
function trimPathValue(value) {
    return value.replace(/\/+$/, '').replace(/^\.\//, '').trim();
}
function trimNonEmptyString(value) {
    if (typeof value !== 'string') {
        return undefined;
    }
    const trimmed = value.trim();
    return trimmed.length > 0 ? trimmed : undefined;
}
function normalizeKnownStrayPath(pathValue) {
    const trimmed = pathValue.trim();
    if (!trimmed) {
        return undefined;
    }
    const normalized = trimPathValue(normalizeToPosixPath(trimmed));
    return normalized && normalized !== '.' ? normalized : undefined;
}
function normalizeKnownStrays(value) {
    if (!Array.isArray(value)) {
        return undefined;
    }
    const normalized = value
        .filter((pathValue) => typeof pathValue === 'string')
        .map((pathValue) => normalizeKnownStrayPath(pathValue))
        .filter((pathValue) => pathValue !== undefined);
    return normalized.length > 0 ? [...new Set(normalized)].sort() : undefined;
}
export function normalizeProjectPath(repoRoot, pathValue) {
    if (pathValue == null) {
        return null;
    }
    const trimmed = pathValue.trim();
    if (!trimmed) {
        return null;
    }
    const repoRootResolved = resolve(repoRoot);
    const absoluteResolved = resolve(repoRootResolved, trimmed);
    const relativePath = relative(repoRootResolved, absoluteResolved);
    const isInsideRepo = !isAbsolute(relativePath) &&
        relativePath !== '..' &&
        !relativePath.startsWith(`..${sep}`);
    if (!isInsideRepo) {
        return null;
    }
    const normalizedRelative = trimPathValue(normalizeToPosixPath(relativePath));
    return normalizedRelative && normalizedRelative !== '.'
        ? normalizedRelative
        : null;
}
async function normalizeReadableProjectPath(repoRoot, pathValue) {
    const normalizedPath = normalizeProjectPath(repoRoot, pathValue);
    if (!normalizedPath) {
        return null;
    }
    const absolutePath = join(repoRoot, normalizedPath);
    try {
        await lstat(absolutePath);
    }
    catch (error) {
        return isMissingFileError(error) ? normalizedPath : null;
    }
    try {
        const validated = await validateRealPathWithinScope(absolutePath, repoRoot);
        return (await dirExists(validated.realPath))
            ? normalizeProjectPath(validated.realScopeRoot, validated.realPath)
            : null;
    }
    catch {
        return null;
    }
}
function normalizeOatConfig(parsed) {
    const next = { ...DEFAULT_OAT_CONFIG };
    if (!isRecord(parsed)) {
        return next;
    }
    if (isRecord(parsed.worktrees) &&
        typeof parsed.worktrees.root === 'string' &&
        parsed.worktrees.root.trim()) {
        next.worktrees = { root: parsed.worktrees.root.trim() };
    }
    if (isRecord(parsed.projects) &&
        typeof parsed.projects.root === 'string' &&
        parsed.projects.root.trim()) {
        next.projects = { root: parsed.projects.root.trim() };
    }
    if (isRecord(parsed.git)) {
        const git = {};
        if (typeof parsed.git.defaultBranch === 'string' &&
            parsed.git.defaultBranch.trim()) {
            git.defaultBranch = parsed.git.defaultBranch.trim();
        }
        if (Object.keys(git).length > 0) {
            next.git = git;
        }
    }
    if (isRecord(parsed.archive)) {
        const archive = {};
        if (typeof parsed.archive.s3Uri === 'string' &&
            parsed.archive.s3Uri.trim()) {
            archive.s3Uri = parsed.archive.s3Uri.trim().replace(/\/+$/, '');
        }
        if (typeof parsed.archive.s3SyncOnComplete === 'boolean') {
            archive.s3SyncOnComplete = parsed.archive.s3SyncOnComplete;
        }
        if (typeof parsed.archive.summaryExportPath === 'string' &&
            parsed.archive.summaryExportPath.trim()) {
            archive.summaryExportPath = normalizeToPosixPath(parsed.archive.summaryExportPath.trim().replace(/\/+$/, ''));
        }
        if (typeof parsed.archive.wrapUpExportPath === 'string' &&
            parsed.archive.wrapUpExportPath.trim()) {
            archive.wrapUpExportPath = normalizeToPosixPath(parsed.archive.wrapUpExportPath.trim().replace(/\/+$/, ''));
        }
        const awsProfile = trimNonEmptyString(parsed.archive.awsProfile);
        if (awsProfile !== undefined) {
            archive.awsProfile = awsProfile;
        }
        const awsRegion = trimNonEmptyString(parsed.archive.awsRegion);
        if (awsRegion !== undefined) {
            archive.awsRegion = awsRegion;
        }
        if (Object.keys(archive).length > 0) {
            next.archive = archive;
        }
    }
    if (isRecord(parsed.tools)) {
        const validPacks = [
            'core',
            'ideas',
            'docs',
            'workflows',
            'utility',
            'project-management',
            'research',
            'brainstorm',
        ];
        const tools = {};
        for (const pack of validPacks) {
            if (typeof parsed.tools[pack] === 'boolean') {
                tools[pack] = parsed.tools[pack];
            }
        }
        if (Object.keys(tools).length > 0) {
            next.tools = tools;
        }
    }
    if (isRecord(parsed.documentation)) {
        const doc = {};
        if (typeof parsed.documentation.root === 'string' &&
            parsed.documentation.root.trim()) {
            doc.root = parsed.documentation.root.trim();
        }
        if (typeof parsed.documentation.tooling === 'string' &&
            parsed.documentation.tooling.trim()) {
            doc.tooling = parsed.documentation.tooling.trim();
        }
        if (typeof parsed.documentation.config === 'string' &&
            parsed.documentation.config.trim()) {
            doc.config = parsed.documentation.config.trim();
        }
        if (typeof parsed.documentation.index === 'string' &&
            parsed.documentation.index.trim()) {
            doc.index = parsed.documentation.index.trim();
        }
        if (typeof parsed.documentation.requireForProjectCompletion === 'boolean') {
            doc.requireForProjectCompletion =
                parsed.documentation.requireForProjectCompletion;
        }
        if (Object.keys(doc).length > 0) {
            next.documentation = doc;
        }
    }
    if (Array.isArray(parsed.localPaths)) {
        const filtered = parsed.localPaths.filter((v) => typeof v === 'string' && v.trim() !== '');
        if (filtered.length > 0) {
            next.localPaths = [...new Set(filtered)].sort();
        }
    }
    if (typeof parsed.autoReviewAtCheckpoints === 'boolean') {
        next.autoReviewAtCheckpoints = parsed.autoReviewAtCheckpoints;
    }
    const workflow = normalizeWorkflowConfig(parsed.workflow);
    if (workflow) {
        next.workflow = workflow;
    }
    return next;
}
function normalizeOatLocalConfig(repoRoot, parsed) {
    const next = { ...DEFAULT_OAT_LOCAL_CONFIG };
    if (!isRecord(parsed)) {
        return next;
    }
    if ('activeProject' in parsed) {
        const rawValue = typeof parsed.activeProject === 'string' || parsed.activeProject === null
            ? parsed.activeProject
            : null;
        next.activeProject = normalizeProjectPath(repoRoot, rawValue);
    }
    if ('lastPausedProject' in parsed) {
        const rawValue = typeof parsed.lastPausedProject === 'string' ||
            parsed.lastPausedProject === null
            ? parsed.lastPausedProject
            : null;
        next.lastPausedProject = normalizeProjectPath(repoRoot, rawValue);
    }
    if ('activeIdea' in parsed) {
        next.activeIdea =
            typeof parsed.activeIdea === 'string' && parsed.activeIdea.trim()
                ? parsed.activeIdea.trim()
                : null;
    }
    const workflow = normalizeWorkflowConfig(parsed.workflow);
    if (workflow) {
        next.workflow = workflow;
    }
    return next;
}
export function resolveLocalPaths(config) {
    return config.localPaths ?? [];
}
export async function readOatConfig(repoRoot) {
    const configPath = getConfigPath(repoRoot);
    try {
        const raw = await readFile(configPath, 'utf8');
        return normalizeOatConfig(parseJsonConfig(raw, configPath));
    }
    catch (error) {
        if (isMissingFileError(error)) {
            return { ...DEFAULT_OAT_CONFIG };
        }
        throw error;
    }
}
export async function readOatLocalConfig(repoRoot) {
    const configPath = getLocalConfigPath(repoRoot);
    try {
        const raw = await readFile(configPath, 'utf8');
        const normalized = normalizeOatLocalConfig(repoRoot, parseJsonConfig(raw, configPath));
        if (normalized.activeProject !== undefined) {
            normalized.activeProject = await normalizeReadableProjectPath(repoRoot, normalized.activeProject);
        }
        return normalized;
    }
    catch (error) {
        if (isMissingFileError(error)) {
            return { ...DEFAULT_OAT_LOCAL_CONFIG };
        }
        throw error;
    }
}
export async function writeOatConfig(repoRoot, config) {
    const configPath = getConfigPath(repoRoot);
    const normalized = normalizeOatConfig(config);
    await atomicWriteJson(configPath, normalized);
}
export async function writeOatLocalConfig(repoRoot, config) {
    const configPath = getLocalConfigPath(repoRoot);
    const normalized = normalizeOatLocalConfig(repoRoot, config);
    await atomicWriteJson(configPath, normalized);
}
export async function resolveActiveProject(repoRoot) {
    const localConfig = await readOatLocalConfig(repoRoot);
    const projectPath = localConfig.activeProject ?? null;
    if (!projectPath) {
        return { name: null, path: null, status: 'unset' };
    }
    let resolvedProjectPath = projectPath;
    let absoluteProjectPath = join(repoRoot, projectPath);
    try {
        const validated = await validateRealPathWithinScope(absoluteProjectPath, repoRoot);
        const canonicalProjectPath = normalizeProjectPath(validated.realScopeRoot, validated.realPath);
        if (!canonicalProjectPath) {
            throw new Error('Project path resolves to the repository root.');
        }
        resolvedProjectPath = canonicalProjectPath;
        absoluteProjectPath = validated.realPath;
    }
    catch {
        return {
            name: basename(absoluteProjectPath),
            path: projectPath,
            status: 'missing',
        };
    }
    const statePath = join(absoluteProjectPath, 'state.md');
    const isValid = (await dirExists(absoluteProjectPath)) && (await fileExists(statePath));
    return {
        name: basename(absoluteProjectPath),
        path: resolvedProjectPath,
        status: isValid ? 'active' : 'missing',
    };
}
export async function setActiveProject(repoRoot, projectRelativePath) {
    const normalizedPath = normalizeProjectPath(repoRoot, projectRelativePath);
    if (!normalizedPath) {
        throw new Error(`Active project path must be repo-relative or inside repo root: ${projectRelativePath}`);
    }
    let canonicalPath;
    try {
        const validated = await validateRealPathWithinScope(join(repoRoot, normalizedPath), repoRoot);
        const canonicalProjectPath = normalizeProjectPath(validated.realScopeRoot, validated.realPath);
        if (!canonicalProjectPath || !(await dirExists(validated.realPath))) {
            throw new Error('Project directory is missing.');
        }
        canonicalPath = canonicalProjectPath;
    }
    catch {
        throw new Error(`Active project path must be repo-relative or inside repo root: ${projectRelativePath}`);
    }
    const localConfig = await readOatLocalConfig(repoRoot);
    await writeOatLocalConfig(repoRoot, {
        ...localConfig,
        activeProject: canonicalPath,
    });
}
export async function clearActiveProject(repoRoot, options) {
    const localConfig = await readOatLocalConfig(repoRoot);
    const lastPaused = options?.lastPaused === undefined
        ? localConfig.lastPausedProject
        : normalizeProjectPath(repoRoot, options.lastPaused);
    await writeOatLocalConfig(repoRoot, {
        ...localConfig,
        activeProject: null,
        lastPausedProject: lastPaused,
    });
}
function getUserConfigPath(userConfigDir) {
    return join(userConfigDir, 'config.json');
}
function normalizeUserConfig(parsed) {
    const next = { ...DEFAULT_USER_CONFIG };
    if (!isRecord(parsed)) {
        return next;
    }
    if ('activeIdea' in parsed) {
        next.activeIdea =
            typeof parsed.activeIdea === 'string' && parsed.activeIdea.trim()
                ? parsed.activeIdea.trim()
                : null;
    }
    const knownStrays = normalizeKnownStrays(parsed.knownStrays);
    if (knownStrays) {
        next.knownStrays = knownStrays;
    }
    if (typeof parsed.updateNotifications === 'boolean') {
        next.updateNotifications = parsed.updateNotifications;
    }
    const workflow = normalizeWorkflowConfig(parsed.workflow);
    if (workflow) {
        next.workflow = workflow;
    }
    return next;
}
export async function readUserConfig(userConfigDir) {
    const configPath = getUserConfigPath(userConfigDir);
    try {
        const raw = await readFile(configPath, 'utf8');
        return normalizeUserConfig(parseJsonConfig(raw, configPath));
    }
    catch (error) {
        if (isMissingFileError(error)) {
            return { ...DEFAULT_USER_CONFIG };
        }
        throw error;
    }
}
export async function writeUserConfig(userConfigDir, config) {
    const configPath = getUserConfigPath(userConfigDir);
    const normalized = normalizeUserConfig(config);
    await atomicWriteJson(configPath, normalized);
}
export async function resolveActiveIdea(repoRoot, userConfigDir) {
    const localConfig = await readOatLocalConfig(repoRoot);
    if (localConfig.activeIdea) {
        return localConfig.activeIdea;
    }
    const userConfig = await readUserConfig(userConfigDir);
    return userConfig.activeIdea ?? null;
}
export async function setActiveIdea(repoRoot, ideaPath) {
    const localConfig = await readOatLocalConfig(repoRoot);
    await writeOatLocalConfig(repoRoot, {
        ...localConfig,
        activeIdea: ideaPath.trim(),
    });
}
export async function clearActiveIdea(repoRoot) {
    const localConfig = await readOatLocalConfig(repoRoot);
    await writeOatLocalConfig(repoRoot, {
        ...localConfig,
        activeIdea: null,
    });
}
export function detectDefaultBranch(repoRoot) {
    try {
        const branch = execSync('gh repo view --json defaultBranchRef --jq .defaultBranchRef.name', {
            cwd: repoRoot,
            encoding: 'utf8',
            timeout: 10_000,
            stdio: ['pipe', 'pipe', 'pipe'],
        }).trim();
        if (branch)
            return branch;
    }
    catch {
        // gh not available or not authenticated — fall through
    }
    try {
        const ref = execSync('git rev-parse --abbrev-ref origin/HEAD', {
            cwd: repoRoot,
            encoding: 'utf8',
            timeout: 5_000,
            stdio: ['pipe', 'pipe', 'pipe'],
        }).trim();
        if (ref.startsWith('origin/'))
            return ref.replace('origin/', '');
    }
    catch {
        // origin/HEAD not set — fall through
    }
    return 'main';
}
