// tools/git/hook-ops.ts — Hook operations: setup-hooks with manager detection
import { writeFile, mkdir, access } from 'node:fs/promises';
import { join } from 'node:path';
import { git, resolveProjectPath, mergeConfig, DEFAULT_PROTECTED_BRANCHES, execFile, } from './git-helpers.js';
async function detectHookManager(projectPath) {
    // Check for husky
    try {
        await access(join(projectPath, '.husky'));
        return 'husky';
    }
    catch {
        /* continue */
    }
    // Check for lefthook
    try {
        await access(join(projectPath, 'lefthook.yml'));
        return 'lefthook';
    }
    catch {
        /* continue */
    }
    try {
        await access(join(projectPath, 'lefthook.yaml'));
        return 'lefthook';
    }
    catch {
        /* continue */
    }
    // Check for simple-git-hooks in package.json
    try {
        const { stdout } = await execFile('node', [
            '-e',
            `const p=require('${join(projectPath, 'package.json')}');console.log(JSON.stringify(Object.keys(p['simple-git-hooks']||{})))`,
        ], { cwd: projectPath, timeout: 5000 });
        if (stdout.trim() && stdout.trim() !== '[]') {
            return 'simple-git-hooks';
        }
    }
    catch {
        /* continue */
    }
    return 'native';
}
async function resolveHooksDir(projectPath) {
    try {
        const { stdout } = await git(projectPath, ['rev-parse', '--git-dir']);
        return join(projectPath, stdout.trim(), 'hooks');
    }
    catch {
        return join(projectPath, '.git', 'hooks');
    }
}
function buildPreCommitScript(requireSpecInCommit, stalenessThreshold, baseBranch) {
    return [
        '#!/bin/sh',
        '# Planu SDD pre-commit hook',
        '',
        'BRANCH=$(git rev-parse --abbrev-ref HEAD)',
        '',
        requireSpecInCommit
            ? [
                'SPEC_PATTERN="^(feat|fix|refactor|infra|docs|project)/SPEC-[0-9]{3}-"',
                'if ! echo "$BRANCH" | grep -qE "$SPEC_PATTERN"; then',
                '  if [ "$BRANCH" != "main" ] && [ "$BRANCH" != "master" ] && [ "$BRANCH" != "develop" ]; then',
                '    echo "WARNING: Branch name \'$BRANCH\' does not follow Planu convention."',
                '    echo "Expected: <type>/SPEC-XXX-<slug>"',
                '  fi',
                'fi',
            ].join('\n')
            : '# Spec-based branch validation disabled',
        '',
        '# Validate branch naming convention (non-blocking)',
        'BRANCH_CONVENTION="^(feat|fix|refactor|infra|docs|project|chore|test|perf|ci|build|style|revert)/"',
        'if [ "$BRANCH" != "main" ] && [ "$BRANCH" != "master" ] && [ "$BRANCH" != "develop" ]; then',
        '  if ! echo "$BRANCH" | grep -qE "$BRANCH_CONVENTION"; then',
        '    echo "WARNING: Branch \'$BRANCH\' does not follow naming convention."',
        '    echo "Expected: feat/*, fix/*, chore/*, docs/*, refactor/*, test/*, perf/*"',
        '  fi',
        'fi',
        '',
        '# Staleness check',
        `STALENESS_THRESHOLD=${stalenessThreshold}`,
        `BASE_BRANCH="${baseBranch}"`,
        'if [ "$STALENESS_THRESHOLD" -gt 0 ] && git rev-parse --verify "$BASE_BRANCH" >/dev/null 2>&1; then',
        '  BEHIND=$(git rev-list --count HEAD.."$BASE_BRANCH" 2>/dev/null || echo 0)',
        '  if [ "$BEHIND" -gt "$STALENESS_THRESHOLD" ]; then',
        '    echo "WARNING: Branch is $BEHIND commits behind $BASE_BRANCH (threshold: $STALENESS_THRESHOLD)."',
        '    echo "Run: manage_git sync-branch to sync."',
        '  fi',
        'fi',
        '',
        '# Check for secrets in staged files',
        "SECRETS_PATTERN='(PRIVATE_KEY|SECRET_KEY|API_KEY|PASSWORD|TOKEN)\\s*=\\s*\\S+'",
        'STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM)',
        'for FILE in $STAGED_FILES; do',
        '  if grep -qE "$SECRETS_PATTERN" "$FILE" 2>/dev/null; then',
        '    echo "ERROR: Potential secret found in $FILE"',
        '    exit 1',
        '  fi',
        'done',
        '',
        '# Run linter',
        'if command -v pnpm &>/dev/null && [ -f "package.json" ]; then',
        '  pnpm lint --max-warnings 0 || exit 1',
        'fi',
        '',
        'exit 0',
    ].join('\n');
}
function buildCommitMsgScript(commitFormat) {
    return [
        '#!/bin/sh',
        '# Planu SDD commit-msg hook',
        '',
        'COMMIT_MSG_FILE="$1"',
        'COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")',
        '',
        commitFormat === 'conventional'
            ? [
                'CONVENTIONAL_PATTERN="^(feat|fix|refactor|docs|test|chore|ci|perf|style|build|revert)(\\(.+\\))?!?:\\s.+"',
                'if ! echo "$COMMIT_MSG" | head -1 | grep -qE "$CONVENTIONAL_PATTERN"; then',
                '  echo "ERROR: Commit message does not follow conventional format."',
                '  echo "Expected: <type>(<scope>): <description>"',
                '  exit 1',
                'fi',
            ].join('\n')
            : commitFormat === 'spec-id'
                ? [
                    'SPEC_PATTERN="\\[SPEC-[0-9]{3}\\]"',
                    'if ! echo "$COMMIT_MSG" | grep -qE "$SPEC_PATTERN"; then',
                    '  echo "WARNING: Commit message does not reference a spec ID."',
                    '  echo "Recommended format: [SPEC-XXX] <description>"',
                    'fi',
                ].join('\n')
                : '# Custom commit format — no validation',
        '',
        'exit 0',
    ].join('\n');
}
function buildPrePushScript(protectedBranches, stalenessThreshold, baseBranch) {
    return [
        '#!/bin/sh',
        '# Planu SDD pre-push hook',
        '',
        'BRANCH=$(git rev-parse --abbrev-ref HEAD)',
        `PROTECTED_BRANCHES="${protectedBranches.join(' ')}"`,
        '',
        'for PROTECTED in $PROTECTED_BRANCHES; do',
        '  if [ "$BRANCH" = "$PROTECTED" ]; then',
        '    echo "ERROR: Direct push to \'$BRANCH\' is not allowed."',
        '    echo "Create a feature branch and submit a PR instead."',
        '    exit 1',
        '  fi',
        'done',
        '',
        '# Staleness check before push',
        `STALENESS_THRESHOLD=${stalenessThreshold}`,
        `BASE_BRANCH="${baseBranch}"`,
        'if [ "$STALENESS_THRESHOLD" -gt 0 ] && git rev-parse --verify "$BASE_BRANCH" >/dev/null 2>&1; then',
        '  BEHIND=$(git rev-list --count HEAD.."$BASE_BRANCH" 2>/dev/null || echo 0)',
        '  if [ "$BEHIND" -gt "$STALENESS_THRESHOLD" ]; then',
        '    echo "WARNING: Branch is $BEHIND commits behind $BASE_BRANCH."',
        '    echo "Consider running: manage_git sync-branch before pushing."',
        '  fi',
        'fi',
        '',
        'exit 0',
    ].join('\n');
}
export async function handleSetupHooks(projectId, config) {
    const projectPath = await resolveProjectPath(projectId);
    const hookManager = await detectHookManager(projectPath);
    const hooksDir = await resolveHooksDir(projectPath);
    await mkdir(hooksDir, { recursive: true });
    const mergedConfig = mergeConfig(config);
    const installedHooks = [];
    /* v8 ignore start */
    const protectedBranches = mergedConfig.protectedBranches ?? DEFAULT_PROTECTED_BRANCHES;
    const stalenessThreshold = mergedConfig.stalenessThreshold ?? 50;
    const baseBranch = mergedConfig.baseBranch ?? 'develop';
    /* v8 ignore end */
    const preCommitScript = buildPreCommitScript(
    /* v8 ignore next */ mergedConfig.requireSpecInCommit ?? true, stalenessThreshold, baseBranch);
    await writeFile(join(hooksDir, 'pre-commit'), preCommitScript, { mode: 0o755 });
    installedHooks.push('pre-commit');
    /* v8 ignore next */
    const commitMsgScript = buildCommitMsgScript(mergedConfig.commitFormat ?? 'conventional');
    await writeFile(join(hooksDir, 'commit-msg'), commitMsgScript, { mode: 0o755 });
    installedHooks.push('commit-msg');
    const prePushScript = buildPrePushScript(protectedBranches, stalenessThreshold, baseBranch);
    await writeFile(join(hooksDir, 'pre-push'), prePushScript, { mode: 0o755 });
    installedHooks.push('pre-push');
    const result = {
        action: 'setup-hooks',
        hooksInstalled: installedHooks,
        hookManager,
        message: `Installed ${installedHooks.length} git hook(s) via ${hookManager}: ${installedHooks.join(', ')}`,
    };
    return {
        content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
    };
}
//# sourceMappingURL=hook-ops.js.map