import { specStore } from '../../storage/index.js';
import { ti } from '../../i18n/index.js';
import { git, resolveProjectPath, slugify, mergeConfig, detectGitFlowType, resolveBaseBranch, DEFAULT_BRANCH_PREFIXES, DEFAULT_PROTECTED_BRANCHES, } from './git-helpers.js';
import { compactResult, compactError, formatKeyValue, formatTable, formatList, } from '../output-formatter.js';
// ---------------------------------------------------------------------------
// Worktree helpers
// ---------------------------------------------------------------------------
/** Derives a worktree name from a specId (e.g. SPEC-252 -> feat-spec-252). */
function worktreeNameFromSpec(specId) {
    const match = /SPEC-(\d+)/i.exec(specId);
    if (match?.[1]) {
        return `feat-spec-${match[1]}`;
    }
    return slugify(specId);
}
/** Generates a random 8-char hex suffix. */
function randomSuffix() {
    return Math.floor(Math.random() * 0xffffffff)
        .toString(16)
        .padStart(8, '0');
}
export async function handleCreateBranch(projectId, specId, config) {
    if (!specId) {
        return compactError('❌ Error: specId is required for create-branch action');
    }
    const projectPath = await resolveProjectPath(projectId);
    const spec = await specStore.getSpec(projectId, specId);
    if (!spec) {
        return compactError(`❌ Error: Spec '${specId}' not found`);
    }
    const mergedConfig = mergeConfig(config);
    /* v8 ignore start */
    const prefixes = mergedConfig.branchPrefix ?? DEFAULT_BRANCH_PREFIXES;
    const prefix = prefixes[spec.type] ?? 'feat';
    /* v8 ignore end */
    const slug = slugify(spec.title);
    const branchName = `${prefix}/${specId}-${slug}`;
    // Check if branch already exists
    try {
        const { stdout } = await git(projectPath, ['branch', '--list', branchName]);
        if (stdout.trim().length > 0) {
            await git(projectPath, ['checkout', branchName]);
            const result = {
                action: 'create-branch',
                branchName,
                message: `Switched to existing branch '${branchName}'`,
            };
            return compactResult(formatKeyValue(result));
        }
    }
    catch {
        // branch list failed — proceed with creation
    }
    await git(projectPath, ['checkout', '-b', branchName]);
    await specStore.updateSpec(projectId, specId, { gitBranch: branchName });
    const result = {
        action: 'create-branch',
        branchName,
        message: ti('tools.manage_git.success', { action: 'create-branch' }),
    };
    return compactResult(formatKeyValue(result));
}
export async function handleCheckBranch(projectId, _specId, config) {
    const projectPath = await resolveProjectPath(projectId);
    const { stdout: currentBranch } = await git(projectPath, ['rev-parse', '--abbrev-ref', 'HEAD']);
    const branch = currentBranch.trim();
    const mergedConfig = mergeConfig(config);
    /* v8 ignore start */
    const protectedBranches = mergedConfig.protectedBranches ?? DEFAULT_PROTECTED_BRANCHES;
    /* v8 ignore end */
    const isProtected = protectedBranches.includes(branch);
    const conventionPattern = /^(feat|fix|refactor|infra|docs|project)\/SPEC-\d{3}-/;
    const followsConvention = conventionPattern.test(branch);
    const specIdMatch = /SPEC-\d{3}/.exec(branch);
    const branchSpecId = specIdMatch ? specIdMatch[0] : null;
    let specExists = false;
    if (branchSpecId) {
        const spec = await specStore.getSpec(projectId, branchSpecId);
        specExists = spec !== null;
    }
    const issues = [];
    if (isProtected) {
        issues.push(`Currently on protected branch '${branch}'. Create a feature branch for changes.`);
    }
    if (!followsConvention && !isProtected) {
        issues.push(`Branch '${branch}' does not follow Planu naming convention: <type>/SPEC-XXX-<slug>`);
    }
    if (branchSpecId && !specExists) {
        issues.push(`Spec '${branchSpecId}' referenced in branch name not found in project.`);
    }
    const summary = issues.length === 0
        ? `Branch '${branch}' is valid`
        : `Branch '${branch}' has ${issues.length} issue(s)`;
    const lines = [
        formatKeyValue({
            branch,
            protected: isProtected,
            followsConvention,
            linkedSpec: branchSpecId ?? '-',
            specExists,
        }),
        '',
        summary,
    ];
    if (issues.length > 0) {
        lines.push('', formatList(issues, 'Issues'));
    }
    return compactResult(lines.join('\n'));
}
/** Check if the working tree has uncommitted changes. */
async function hasDirtyState(projectPath) {
    const { stdout } = await git(projectPath, ['status', '--porcelain']);
    return stdout.trim().length > 0;
}
/**
 * Auto-setup: detect git workflow, sync base branch, create feature branch.
 * Combines detectGitFlowType + resolveBaseBranch + fetch/pull + create-branch
 * into a single atomic operation for spec creation.
 */
export async function handleAutoSetup(projectId, specId, config) {
    if (!specId) {
        return compactError('❌ Error: specId is required for auto-setup action');
    }
    const projectPath = await resolveProjectPath(projectId);
    // Check for dirty state — abort if uncommitted changes exist
    if (await hasDirtyState(projectPath)) {
        return compactError('❌ Error: Working tree has uncommitted changes. Commit or stash them before auto-setup.\nSuggestion: git stash or git commit your changes first');
    }
    const spec = await specStore.getSpec(projectId, specId);
    if (!spec) {
        return compactError(`❌ Error: Spec '${specId}' not found`);
    }
    // 1. Detect git workflow type
    const gitFlowType = await detectGitFlowType(projectPath);
    // 2. Resolve base branch
    const mergedConfig = mergeConfig(config);
    const baseBranch = await resolveBaseBranch(projectPath, mergedConfig.baseBranch);
    if (!baseBranch) {
        return compactError('❌ Error: No base branch found (develop/main/master). Is this a git repository?');
    }
    // 3. Switch to base branch and sync with remote
    await git(projectPath, ['checkout', baseBranch]);
    let syncedBase = false;
    try {
        await git(projectPath, ['fetch', 'origin', baseBranch]);
        await git(projectPath, ['pull', '--ff-only', 'origin', baseBranch]);
        syncedBase = true;
    }
    catch {
        // No remote or fetch failed — continue with local base (offline-friendly)
    }
    // 4. Create feature branch from the updated base
    const prefixes = mergedConfig.branchPrefix ?? DEFAULT_BRANCH_PREFIXES;
    /* v8 ignore next */
    const prefix = prefixes[spec.type] ?? 'feat';
    const slug = slugify(spec.title);
    const branchName = `${prefix}/${specId}-${slug}`;
    // Check if branch already exists
    try {
        const { stdout } = await git(projectPath, ['branch', '--list', branchName]);
        if (stdout.trim().length > 0) {
            await git(projectPath, ['checkout', branchName]);
            const result = {
                action: 'auto-setup',
                gitFlowType,
                baseBranch,
                newBranch: branchName,
                syncedBase,
                message: `Switched to existing branch '${branchName}' (${gitFlowType} workflow detected)`,
            };
            return compactResult(formatKeyValue(result));
        }
    }
    catch {
        // branch list failed — proceed with creation
    }
    await git(projectPath, ['checkout', '-b', branchName]);
    await specStore.updateSpec(projectId, specId, { gitBranch: branchName });
    const result = {
        action: 'auto-setup',
        gitFlowType,
        baseBranch,
        newBranch: branchName,
        syncedBase,
        message: `Created branch '${branchName}' from '${baseBranch}' (${gitFlowType} workflow detected)`,
    };
    return compactResult(formatKeyValue(result));
}
// ---------------------------------------------------------------------------
// Worktree operations (SPEC-260)
// ---------------------------------------------------------------------------
/**
 * Returns a structured worktree-start payload with commands/paths so the caller
 * knows exactly how to spin up an isolated Claude Code session.
 */
export function handleWorktreeStart(_projectId, specId, config) {
    let name;
    if (specId) {
        name = worktreeNameFromSpec(specId);
    }
    else if (config?.customName) {
        name = slugify(config.customName);
    }
    else {
        name = `worktree-${randomSuffix()}`;
    }
    const branchName = `worktree-${name}`;
    const worktreePath = `.claude/worktrees/${name}`;
    const command = `claude --worktree ${name}`;
    const cleanupCommand = `git worktree remove ${worktreePath}`;
    const lines = [
        formatKeyValue({
            command,
            branchName,
            worktreePath,
            cleanupCommand,
        }),
        '',
        `**gitignoreNote**: Add '.claude/worktrees/' to .gitignore to prevent contents appearing as untracked files`,
        `**subagentNote**: For subagents: add 'isolation: worktree' to agent frontmatter for automatic isolation`,
        `**autoCleanup**: Worktree is auto-removed by Claude Code if no changes are made`,
    ];
    return compactResult(lines.join('\n'));
}
/**
 * Lists all git worktrees by parsing `git worktree list --porcelain`.
 */
export async function handleWorktreeList(projectId) {
    const projectPath = await resolveProjectPath(projectId);
    const { stdout } = await git(projectPath, ['worktree', 'list', '--porcelain']);
    const worktrees = [];
    let current = {};
    let isFirst = true;
    for (const raw of stdout.split('\n')) {
        const line = raw.trim();
        if (line === '') {
            if (current.path !== undefined) {
                worktrees.push({
                    path: current.path,
                    branch: current.branch ?? '(detached)',
                    head: current.head ?? '',
                    isMain: current.isMain ?? false,
                });
            }
            current = {};
            isFirst = false;
            continue;
        }
        if (line.startsWith('worktree ')) {
            current.path = line.slice('worktree '.length);
            current.isMain = isFirst;
        }
        else if (line.startsWith('HEAD ')) {
            current.head = line.slice('HEAD '.length);
        }
        else if (line.startsWith('branch ')) {
            current.branch = line.slice('branch '.length).replace('refs/heads/', '');
        }
        else if (line === 'bare') {
            current.branch = '(bare)';
        }
    }
    // flush last entry if not terminated by blank line
    if (current.path !== undefined) {
        worktrees.push({
            path: current.path,
            branch: current.branch ?? '(detached)',
            head: current.head ?? '',
            isMain: current.isMain ?? false,
        });
    }
    if (worktrees.length === 0) {
        return compactResult('No worktrees found. Total: 0');
    }
    const text = formatTable(worktrees.map((w) => ({
        path: w.path,
        branch: w.branch,
        head: w.head,
        main: w.isMain,
    })), ['path', 'branch', 'head', 'main'], `Worktrees (${worktrees.length})`);
    return compactResult(text);
}
/**
 * Returns cleanup commands for worktrees (does NOT execute them — caller runs them).
 */
export function handleWorktreeCleanup(_projectId, config) {
    let commands;
    if (config?.removeAll) {
        commands = [
            "git worktree list --porcelain | grep '^worktree ' | awk '{print \"git worktree remove \"$2}'",
        ];
    }
    else if (config?.name) {
        commands = [`git worktree remove .claude/worktrees/${config.name}`];
    }
    else {
        commands = ['git worktree prune'];
    }
    const lines = [
        'Done: worktree-cleanup',
        'Note: Run these commands manually — Planu does not execute destructive git operations automatically.',
        '',
        formatList(commands, 'Commands'),
    ];
    return compactResult(lines.join('\n'));
}
//# sourceMappingURL=branch-ops.js.map