import path from 'node:path';
import { projectRoot, readJson } from '../fsx.js';
import { recordHookPolicyMismatchWrongness } from '../triwiki-wrongness/wrongness-ledger.js';
import { CODEX_HOOK_EVENTS, codexHookEventName } from './codex-schema-snapshot.js';
import { validateCodexFixtureOutputs, validateCodexHookOutput } from './codex-hook-schema.js';
import { validateCodexHookSemanticOutput } from './codex-hook-semantic-validator.js';
import { codexHookIssuesByCategory, codexHookIssueWarningString, dedupeCodexHookIssues, makeCodexHookIssue } from './codex-hook-issues.js';
import { validateCodexHookConfigFiles } from './codex-config-policy.js';
const RESERVED_PERMISSION_REQUEST_FIELDS = ['updatedInput', 'updatedPermissions', 'interrupt'];
const LEGACY_TOP_LEVEL_KEYS = ['permissionDecision', 'permissionDecisionReason', 'updatedInput', 'additionalContext', 'hookEventName'];
export async function detectCodexHookOutputWarnings(eventLike, output) {
    const event = codexHookEventName(eventLike) || 'UserPromptSubmit';
    const validation = await validateCodexHookOutput(event, output);
    const semantic = validateCodexHookSemanticOutput(event, output);
    const issues = [
        ...validation.structured_issues,
        ...semantic.issues,
        ...snakeCaseKeyIssues(output)
    ];
    for (const key of LEGACY_TOP_LEVEL_KEYS) {
        if (output && typeof output === 'object' && Object.prototype.hasOwnProperty.call(output, key)) {
            issues.push(makeCodexHookIssue('legacy_shape', `legacy_top_level_${key}`, `Legacy top-level hook field is not accepted by SKS: ${key}.`, { path: `$.${key}`, upstream_supported: false, sks_disallowed: true }));
        }
    }
    if (event === 'PermissionRequest') {
        const decision = output?.hookSpecificOutput?.decision || {};
        for (const key of RESERVED_PERMISSION_REQUEST_FIELDS) {
            if (Object.prototype.hasOwnProperty.call(decision, key)) {
                issues.push(makeCodexHookIssue('upstream_semantic_unsupported', `permission_request_reserved_${key}`, `PermissionRequest hook returned unsupported ${key}`, { path: `$.hookSpecificOutput.decision.${key}`, upstream_supported: false, sks_disallowed: true }));
            }
        }
    }
    if (event === 'Stop' && output?.decision === 'block' && !String(output?.reason || '').trim()) {
        issues.push(makeCodexHookIssue('upstream_semantic_unsupported', 'stop_block_without_reason', 'Stop hook returned decision:block without a non-empty reason', { path: '$.reason', upstream_supported: false, sks_disallowed: true }));
    }
    if (event === 'SubagentStop' && output?.decision === 'block' && !String(output?.reason || '').trim()) {
        issues.push(makeCodexHookIssue('upstream_semantic_unsupported', 'subagentstop_block_without_reason', 'SubagentStop hook returned decision:block without a non-empty reason', { path: '$.reason', upstream_supported: false, sks_disallowed: true }));
    }
    const actualEvent = output?.hookSpecificOutput?.hookEventName;
    if (actualEvent && actualEvent !== event) {
        issues.push(makeCodexHookIssue('upstream_semantic_unsupported', 'hook_event_mismatch', `Hook output event mismatch: expected ${event} but saw ${actualEvent}.`, { path: '$.hookSpecificOutput.hookEventName', upstream_supported: false, sks_disallowed: true }));
    }
    const uniqueIssues = dedupeCodexHookIssues(issues);
    const warnings = uniqueIssues.map(codexHookIssueWarningString);
    return {
        schema: 'sks.codex-hook-warning-detection.v2',
        ok: uniqueIssues.length === 0,
        event,
        semantic,
        issues: uniqueIssues,
        issues_by_category: codexHookIssuesByCategory(uniqueIssues),
        warnings: [...new Set(warnings)]
    };
}
export async function codexHookWarningCheck(root, opts = {}) {
    root ||= await projectRoot();
    const fixtureValidation = await validateCodexFixtureOutputs(root);
    const rows = [];
    for (const row of fixtureValidation.outputs || []) {
        const output = await readJson(row.file, {});
        const warning = await detectCodexHookOutputWarnings(row.event, output);
        rows.push({ event: row.event, file: row.file, warnings: warning.warnings, issues: warning.issues, ok: warning.ok });
    }
    const config = await validateCodexHookConfigFiles(root);
    const missingFixtureEvents = CODEX_HOOK_EVENTS.filter((event) => rows.filter((row) => row.event === event).length === 0);
    const configIssues = config.issues.map((issue) => makeCodexHookIssue('policy_disallowed', 'hook_config_policy', `Codex hook config policy issue: ${issue}`, { upstream_supported: true, sks_disallowed: true }));
    const fixtureIssues = missingFixtureEvents.map((event) => makeCodexHookIssue('policy_disallowed', 'missing_latest_event_fixture', `Codex hook fixture missing for latest event: ${event}`, { upstream_supported: true, sks_disallowed: true }));
    const warnings = [
        ...rows.flatMap((row) => row.warnings.map((warning) => `${path.relative(root, row.file)}:${warning}`)),
        ...config.issues,
        ...missingFixtureEvents.map((event) => `missing_latest_event_fixture:${event}`)
    ];
    const allIssues = [
        ...rows.flatMap((row) => row.issues.map((issue) => ({ ...issue, path: issue.path || path.relative(root, row.file) }))),
        ...configIssues,
        ...fixtureIssues
    ];
    const issuesByCategory = codexHookIssuesByCategory(allIssues);
    let wrongness = null;
    if (warnings.length && opts.recordWrongness !== false) {
        const semanticMismatch = allIssues.some((issue) => issue.category === 'upstream_semantic_unsupported');
        const strictSubsetMismatch = allIssues.some((issue) => issue.category === 'sks_zero_warning_disallowed');
        wrongness = await recordHookPolicyMismatchWrongness(root, {
            artifact: 'test/fixtures/codex-hooks/latest',
            expected: 'Codex latest schema-compatible hook output with warning count 0 and all 10 events covered',
            actual: warnings.join(', '),
            detail: `Codex hook warning check failed; issues_by_category=${JSON.stringify(issuesByCategory)}`,
            route: '$Hooks',
            wrongness_kind: strictSubsetMismatch ? 'hook_strict_subset_misclassified' : semanticMismatch ? 'hook_semantic_mismatch' : 'hook_policy_mismatch'
        }).catch(() => null);
    }
    return {
        schema: 'sks.codex-hook-warning-check.v2',
        ok: warnings.length === 0,
        baseline: 'latest',
        warnings_count: warnings.length,
        issues_by_category: issuesByCategory,
        issues: allIssues,
        warnings,
        events: CODEX_HOOK_EVENTS.map((event) => ({
            event,
            checked: rows.filter((row) => row.event === event).length,
            ok: rows.filter((row) => row.event === event).every((row) => row.ok),
            warnings: rows.filter((row) => row.event === event).flatMap((row) => row.warnings),
            issues_by_category: codexHookIssuesByCategory(rows.filter((row) => row.event === event).flatMap((row) => row.issues))
        })),
        config,
        wrongness
    };
}
function snakeCaseKeyIssues(value, pointer = '$') {
    if (!value || typeof value !== 'object')
        return [];
    if (Array.isArray(value))
        return value.flatMap((item, index) => snakeCaseKeyIssues(item, `${pointer}[${index}]`));
    const issues = [];
    for (const [key, child] of Object.entries(value)) {
        if (/_/.test(key))
            issues.push(makeCodexHookIssue('legacy_shape', 'snake_case', `Snake_case hook key is not accepted by SKS: ${pointer}.${key}.`, { path: `${pointer}.${key}`, upstream_supported: false, sks_disallowed: true }));
        issues.push(...snakeCaseKeyIssues(child, `${pointer}.${key}`));
    }
    return issues;
}
//# sourceMappingURL=codex-hook-warning-detector.js.map