"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_test_1 = require("node:test");
const strict_1 = __importDefault(require("node:assert/strict"));
const fs = __importStar(require("node:fs"));
const path = __importStar(require("node:path"));
const os = __importStar(require("node:os"));
const hooks_1 = require("../hooks");
function stubConfig() {
    return { provider: 'local' };
}
// ─── loadHooks ───────────────────────────────────────────────────────────────
(0, node_test_1.describe)('loadHooks', () => {
    let tmpDir;
    (0, node_test_1.beforeEach)(() => {
        tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'aidev-hooks-test-'));
    });
    (0, node_test_1.afterEach)(() => {
        fs.rmSync(tmpDir, { recursive: true, force: true });
    });
    (0, node_test_1.it)('returns empty object when path is empty', () => {
        const hooks = (0, hooks_1.loadHooks)('');
        strict_1.default.deepEqual(hooks, {});
    });
    (0, node_test_1.it)('returns empty object when file does not exist', () => {
        const hooks = (0, hooks_1.loadHooks)(path.join(tmpDir, 'nonexistent.js'));
        strict_1.default.deepEqual(hooks, {});
    });
    (0, node_test_1.it)('loads a JS hooks file with valid functions', () => {
        const hooksFile = path.join(tmpDir, 'hooks.js');
        fs.writeFileSync(hooksFile, `
      module.exports = {
        beforeRun: async (ctx) => ctx,
        afterRun: async () => {},
      };
    `, 'utf8');
        const hooks = (0, hooks_1.loadHooks)(hooksFile);
        strict_1.default.equal(typeof hooks.beforeRun, 'function');
        strict_1.default.equal(typeof hooks.afterRun, 'function');
    });
    (0, node_test_1.it)('ignores non-function exports', () => {
        const hooksFile = path.join(tmpDir, 'hooks.js');
        fs.writeFileSync(hooksFile, `
      module.exports = {
        beforeRun: 'not a function',
        afterRun: async () => {},
      };
    `, 'utf8');
        const hooks = (0, hooks_1.loadHooks)(hooksFile);
        strict_1.default.equal(hooks.beforeRun, undefined);
        strict_1.default.equal(typeof hooks.afterRun, 'function');
    });
    (0, node_test_1.it)('resolves relative paths from cwd', () => {
        const hooksFile = path.join(tmpDir, 'hooks.js');
        fs.writeFileSync(hooksFile, `
      module.exports = { beforeRun: async () => {} };
    `, 'utf8');
        const originalCwd = process.cwd();
        try {
            process.chdir(tmpDir);
            const hooks = (0, hooks_1.loadHooks)('./hooks.js');
            strict_1.default.equal(typeof hooks.beforeRun, 'function');
        }
        finally {
            process.chdir(originalCwd);
        }
    });
    (0, node_test_1.it)('handles default export', () => {
        const hooksFile = path.join(tmpDir, 'hooks.js');
        fs.writeFileSync(hooksFile, `
      module.exports.default = {
        beforeEachTask: async (ctx) => ctx,
      };
    `, 'utf8');
        const hooks = (0, hooks_1.loadHooks)(hooksFile);
        strict_1.default.equal(typeof hooks.beforeEachTask, 'function');
    });
    (0, node_test_1.it)('returns empty object when require fails', () => {
        const hooksFile = path.join(tmpDir, 'hooks.js');
        fs.writeFileSync(hooksFile, 'this is not valid javascript{{{', 'utf8');
        const hooks = (0, hooks_1.loadHooks)(hooksFile);
        strict_1.default.deepEqual(hooks, {});
    });
    (0, node_test_1.it)('ignores unknown export keys', () => {
        const hooksFile = path.join(tmpDir, 'hooks.js');
        fs.writeFileSync(hooksFile, `
      module.exports = {
        beforeRun: async () => {},
        notAHook: async () => {},
        extraField: 42,
      };
    `, 'utf8');
        const hooks = (0, hooks_1.loadHooks)(hooksFile);
        strict_1.default.equal(typeof hooks.beforeRun, 'function');
        strict_1.default.equal(hooks.notAHook, undefined);
    });
    (0, node_test_1.it)('loads beforeReviewTask and afterReviewTask hooks', () => {
        const hooksFile = path.join(tmpDir, 'hooks.js');
        fs.writeFileSync(hooksFile, `
      module.exports = {
        beforeReviewTask: async (ctx) => ctx,
        afterReviewTask: async () => {},
      };
    `, 'utf8');
        const hooks = (0, hooks_1.loadHooks)(hooksFile);
        strict_1.default.equal(typeof hooks.beforeReviewTask, 'function');
        strict_1.default.equal(typeof hooks.afterReviewTask, 'function');
    });
    (0, node_test_1.it)('loads a TypeScript hooks file via jiti', () => {
        const hooksFile = path.join(tmpDir, 'hooks.ts');
        fs.writeFileSync(hooksFile, `
      interface Ctx { config: Record<string, unknown>; filter: string; taskCount: number }
      const hooks = {
        beforeRun: async (ctx: Ctx) => ctx,
        afterRun: async () => {},
      };
      module.exports = hooks;
    `, 'utf8');
        const hooks = (0, hooks_1.loadHooks)(hooksFile);
        strict_1.default.equal(typeof hooks.beforeRun, 'function');
        strict_1.default.equal(typeof hooks.afterRun, 'function');
    });
    (0, node_test_1.it)('loads a TypeScript hooks file with export default via jiti', () => {
        const hooksFile = path.join(tmpDir, 'hooks-default.ts');
        fs.writeFileSync(hooksFile, `
      export default {
        beforeEachTask: async (ctx: any) => ctx,
      };
    `, 'utf8');
        const hooks = (0, hooks_1.loadHooks)(hooksFile);
        strict_1.default.equal(typeof hooks.beforeEachTask, 'function');
    });
    (0, node_test_1.it)('returns empty object when TypeScript file has syntax errors', () => {
        const hooksFile = path.join(tmpDir, 'bad.ts');
        fs.writeFileSync(hooksFile, 'this is not valid typescript{{{', 'utf8');
        const hooks = (0, hooks_1.loadHooks)(hooksFile);
        strict_1.default.deepEqual(hooks, {});
    });
});
// ─── executeHook ─────────────────────────────────────────────────────────────
(0, node_test_1.describe)('executeHook', () => {
    const mockVM = {
        runAI: async () => ({ success: true, output: 'ok', error: '' }),
        postComment: async () => { },
        updateStatus: async () => { },
        getComments: async () => [],
        log: { info: () => { }, warn: () => { }, error: () => { } },
    };
    (0, node_test_1.it)('returns original context when hook does not exist', async () => {
        const hooks = {};
        const ctx = { config: stubConfig(), filter: 'all', taskCount: 5 };
        const result = await (0, hooks_1.executeHook)(hooks, 'beforeRun', ctx, mockVM);
        strict_1.default.deepEqual(result, ctx);
    });
    (0, node_test_1.it)('returns modified context when hook returns one', async () => {
        const hooks = {
            beforeRun: async (ctx) => ({ ...ctx, taskCount: 99 }),
        };
        const ctx = { config: stubConfig(), filter: 'all', taskCount: 5 };
        const result = await (0, hooks_1.executeHook)(hooks, 'beforeRun', ctx, mockVM);
        strict_1.default.equal(result.taskCount, 99);
    });
    (0, node_test_1.it)('returns original context when hook returns void', async () => {
        const hooks = {
            beforeRun: async () => { },
        };
        const ctx = { config: stubConfig(), filter: 'all', taskCount: 5 };
        const result = await (0, hooks_1.executeHook)(hooks, 'beforeRun', ctx, mockVM);
        strict_1.default.equal(result.taskCount, 5);
    });
    (0, node_test_1.it)('propagates errors thrown by hooks', async () => {
        const hooks = {
            beforeRun: async () => { throw new Error('hook abort'); },
        };
        const ctx = { config: stubConfig(), filter: 'all', taskCount: 5 };
        await strict_1.default.rejects(() => (0, hooks_1.executeHook)(hooks, 'beforeRun', ctx, mockVM), { message: 'hook abort' });
    });
    (0, node_test_1.it)('passes vm to hook function', async () => {
        let receivedVM;
        const hooks = {
            afterRun: async (_ctx, vm) => { receivedVM = vm; },
        };
        const ctx = { config: stubConfig(), filter: 'all', taskCount: 0, processed: 0, skipped: 0 };
        await (0, hooks_1.executeHook)(hooks, 'afterRun', ctx, mockVM);
        strict_1.default.equal(receivedVM, mockVM);
    });
    (0, node_test_1.it)('allows beforeEachTask to modify prompt', async () => {
        const hooks = {
            beforeEachTask: async (ctx) => {
                return { ...ctx, prompt: ctx.prompt + '\nExtra instructions' };
            },
        };
        const ctx = {
            task: { id: '1', name: 'test', description: '', status: 'open', url: '', tags: [] },
            config: stubConfig(),
            branchName: 'test-branch',
            prompt: 'Original prompt',
        };
        const result = await (0, hooks_1.executeHook)(hooks, 'beforeEachTask', ctx, mockVM);
        strict_1.default.ok(result.prompt.includes('Extra instructions'));
        strict_1.default.ok(result.prompt.includes('Original prompt'));
    });
    (0, node_test_1.it)('allows beforeReviewTask to modify prompt', async () => {
        const hooks = {
            beforeReviewTask: async (ctx) => {
                return { ...ctx, prompt: ctx.prompt + '\nReview extra instructions' };
            },
        };
        const ctx = {
            task: { id: '1', name: 'review-task', description: '', status: 'in review', url: '', tags: [] },
            config: stubConfig(),
            branchName: 'review-branch',
            threads: [{ id: 'thread-1', path: 'src/foo.ts', line: 10, comments: [{ body: 'fix this', author: 'reviewer' }] }],
            prompt: 'Original review prompt',
        };
        const result = await (0, hooks_1.executeHook)(hooks, 'beforeReviewTask', ctx, mockVM);
        strict_1.default.ok(result.prompt.includes('Review extra instructions'));
        strict_1.default.ok(result.prompt.includes('Original review prompt'));
        strict_1.default.equal(result.threads.length, 1);
    });
    (0, node_test_1.it)('passes success and resolvedCount to afterReviewTask', async () => {
        let captured;
        const hooks = {
            afterReviewTask: async (ctx) => { captured = { success: ctx.success, resolvedCount: ctx.resolvedCount }; },
        };
        const ctx = {
            task: { id: '1', name: 'review-task', description: '', status: 'in review', url: '', tags: [] },
            config: stubConfig(),
            branchName: 'review-branch',
            threads: [],
            prompt: 'prompt',
            success: true,
            resolvedCount: 3,
        };
        await (0, hooks_1.executeHook)(hooks, 'afterReviewTask', ctx, mockVM);
        strict_1.default.equal(captured?.success, true);
        strict_1.default.equal(captured?.resolvedCount, 3);
    });
    (0, node_test_1.it)('allows beforeThinkingTask to adjust subtasks', async () => {
        const hooks = {
            beforeThinkingTask: async (ctx) => ({
                ...ctx,
                subtasks: ctx.subtasks.map((s) => s.id === 1 ? { ...s, title: s.title + ' (edited)' } : s),
            }),
        };
        const ctx = {
            task: { id: 't1', name: 'think', description: '', status: 'open', url: '', tags: [] },
            config: stubConfig(),
            branchName: 'b',
            subtasks: [
                { id: 1, title: 'Step one', description: 'd1', status: 'pending' },
                { id: 2, title: 'Step two', description: 'd2', status: 'pending' },
            ],
        };
        const result = await (0, hooks_1.executeHook)(hooks, 'beforeThinkingTask', ctx, mockVM);
        strict_1.default.equal(result.subtasks[0].title, 'Step one (edited)');
        strict_1.default.equal(result.subtasks[1].title, 'Step two');
    });
});
// ─── createHookVM ────────────────────────────────────────────────────────────
(0, node_test_1.describe)('createHookVM', () => {
    (0, node_test_1.it)('creates a VM with all required methods', () => {
        const mockProvider = {
            fetchTasks: async () => [],
            fetchTasksByStatus: async () => [],
            postComment: async () => { },
            getComments: async () => [],
            updateStatus: async () => { },
            createTask: async () => ({ id: '1', url: '' }),
        };
        const mockRunner = {
            name: 'test-runner',
            isAvailable: () => true,
            run: async () => ({ success: true, output: 'ok', error: '' }),
        };
        const vm = (0, hooks_1.createHookVM)(mockProvider, [mockRunner]);
        strict_1.default.equal(typeof vm.runAI, 'function');
        strict_1.default.equal(typeof vm.postComment, 'function');
        strict_1.default.equal(typeof vm.updateStatus, 'function');
        strict_1.default.equal(typeof vm.getComments, 'function');
        strict_1.default.equal(typeof vm.log.info, 'function');
        strict_1.default.equal(typeof vm.log.warn, 'function');
        strict_1.default.equal(typeof vm.log.error, 'function');
    });
    (0, node_test_1.it)('runAI delegates to first available runner', async () => {
        const mockProvider = {
            fetchTasks: async () => [],
            fetchTasksByStatus: async () => [],
            postComment: async () => { },
            getComments: async () => [],
            updateStatus: async () => { },
            createTask: async () => ({ id: '1', url: '' }),
        };
        let capturedPrompt = '';
        const mockRunner = {
            name: 'test-runner',
            isAvailable: () => true,
            run: async (prompt) => {
                capturedPrompt = prompt;
                return { success: true, output: 'result', error: '' };
            },
        };
        const vm = (0, hooks_1.createHookVM)(mockProvider, [mockRunner]);
        const result = await vm.runAI('test prompt');
        strict_1.default.equal(capturedPrompt, 'test prompt');
        strict_1.default.equal(result.success, true);
        strict_1.default.equal(result.output, 'result');
    });
    (0, node_test_1.it)('runAI returns failure when no runner is available', async () => {
        const mockProvider = {
            fetchTasks: async () => [],
            fetchTasksByStatus: async () => [],
            postComment: async () => { },
            getComments: async () => [],
            updateStatus: async () => { },
            createTask: async () => ({ id: '1', url: '' }),
        };
        const unavailableRunner = {
            name: 'unavailable',
            isAvailable: () => false,
            run: async () => ({ success: false, output: '', error: '' }),
        };
        const vm = (0, hooks_1.createHookVM)(mockProvider, [unavailableRunner]);
        const result = await vm.runAI('test prompt');
        strict_1.default.equal(result.success, false);
        strict_1.default.ok(result.error.includes('No AI runner available'));
    });
    (0, node_test_1.it)('postComment delegates to provider', async () => {
        let capturedTaskId = '';
        let capturedText = '';
        const mockProvider = {
            fetchTasks: async () => [],
            fetchTasksByStatus: async () => [],
            postComment: async (taskId, text) => { capturedTaskId = taskId; capturedText = text; },
            getComments: async () => [],
            updateStatus: async () => { },
            createTask: async () => ({ id: '1', url: '' }),
        };
        const vm = (0, hooks_1.createHookVM)(mockProvider, []);
        await vm.postComment('task-123', 'hello');
        strict_1.default.equal(capturedTaskId, 'task-123');
        strict_1.default.equal(capturedText, 'hello');
    });
});
//# sourceMappingURL=hooks.test.js.map