"use strict";
/**
 * POST /api/hooks — receives Claude Code lifecycle hook events.
 * Localhost only (isLocalRequest). No JWT auth needed.
 *
 * Body: { event: string, tool?: string, dir: string, session?: string }
 *
 * Event handling (order matters for Stop):
 *   PreToolUse   → update semanticStatus immediately (NO triggerRead — JSONL not written yet)
 *   PostToolUse  → triggerRead (JSONL now has tool result)
 *   Stop         → clearSemanticStatus first, then triggerRead (read final text without re-setting phase)
 */
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;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.getContextData = getContextData;
exports.setBroadcastContextUpdate = setBroadcastContextUpdate;
const express_1 = require("express");
const path = __importStar(require("path"));
const auth_1 = require("../auth");
const config_1 = require("../config");
const session_manager_1 = require("../session-manager");
const notify_service_1 = require("../notify-service");
const logger_1 = require("../logger");
/**
 * Hook routes are mounted without authMiddleware (Claude Code CLI hooks
 * call us from localhost; the identity proof is `isLocalRequest` not JWT).
 * That means `request-log.ts` can't tag the user from `req.user`. We can
 * still tag the ALS scope from the project owner so the HTTP summary and
 * any downstream log events carry the right user — otherwise "filter logs
 * by user" quietly misses hook-driven activity (reviewer I4 #2).
 */
function tagProjectOwnerInAls(projectId) {
    const ctx = logger_1.als.getStore();
    if (!ctx || ctx.user)
        return;
    const p = (0, config_1.getProject)(projectId);
    if (p?.owner)
        ctx.user = p.owner;
}
const contextDataMap = new Map();
function getContextData(projectId) {
    return contextDataMap.get(projectId) ?? null;
}
// Callback to broadcast context update to project WS clients
let broadcastContextUpdate = null;
function setBroadcastContextUpdate(fn) {
    broadcastContextUpdate = fn;
}
const router = (0, express_1.Router)();
/**
 * Resolve which registered project a hook invocation belongs to.
 * Matches semantics with `bin/ccweb-approval-hook.js#resolveProjectId`:
 *   1. exact folderPath match (after path.resolve)
 *   2. otherwise longest-prefix match — user may run Claude from a sub-directory
 *      (e.g. `~/Projects/X/src`), in which case the PreToolUse hook's $CLAUDE_PROJECT_DIR
 *      is the sub-directory, not the project root. Without prefix fallback, hooks
 *      from sub-directories silently drop on the floor and chat history never updates.
 */
function findProjectByDir(dir) {
    const projects = (0, config_1.getProjects)();
    const resolved = path.resolve(dir);
    let bestId = null;
    let bestLen = -1;
    for (const p of projects) {
        const proj = path.resolve(p.folderPath);
        if (proj === resolved)
            return p.id;
        if (resolved.startsWith(proj + path.sep) && proj.length > bestLen) {
            bestId = p.id;
            bestLen = proj.length;
        }
    }
    return bestId;
}
router.post('/', (req, res) => {
    if (!(0, auth_1.isLocalRequest)(req)) {
        res.status(403).json({ error: 'Forbidden' });
        return;
    }
    const { event, tool, dir } = req.body;
    if (!event || !dir) {
        res.status(400).json({ error: 'Missing event or dir' });
        return;
    }
    const projectId = findProjectByDir(dir);
    if (!projectId) {
        // Hook fired from a claude session not managed by ccweb — silently ignore
        res.json({ ok: true });
        return;
    }
    tagProjectOwnerInAls(projectId);
    switch (event) {
        case 'PreToolUse':
            // Update semantic status immediately from CLAUDE_TOOL_NAME env var.
            // Do NOT call triggerRead here — JSONL hasn't been written yet.
            session_manager_1.sessionManager.handleHookPreTool(projectId, tool ?? '');
            break;
        case 'PostToolUse':
            // JSONL now contains the tool result block — trigger a read.
            session_manager_1.sessionManager.triggerRead(projectId);
            break;
        case 'Stop':
            // Clear semantic status FIRST (so any subsequent JSONL read won't re-emit a stale phase).
            // Then read the final text block from JSONL.
            session_manager_1.sessionManager.clearSemanticStatus(projectId);
            session_manager_1.sessionManager.triggerRead(projectId);
            // Retry to catch late-flushed final text (Claude may not have flushed JSONL
            // at the exact moment Stop hook fires — without these retries, the last
            // assistant message appears only when the next turn starts).
            setTimeout(() => session_manager_1.sessionManager.triggerRead(projectId), 300);
            setTimeout(() => session_manager_1.sessionManager.triggerRead(projectId), 1500);
            // Fire notification 300ms after Stop so JSONL has been read
            setTimeout(() => {
                const p = (0, config_1.getProject)(projectId);
                if (p)
                    void notify_service_1.notifyService.onProjectStopped(projectId, p.name);
            }, 300).unref();
            break;
        default:
            break;
    }
    res.json({ ok: true });
});
// ── POST /api/hooks/context — receive status line context data ──
router.post('/context', (req, res) => {
    if (!(0, auth_1.isLocalRequest)(req)) {
        res.status(403).json({ error: 'Forbidden' });
        return;
    }
    const { dir, context_window } = req.body;
    if (!dir || !context_window) {
        res.json({ ok: true });
        return;
    }
    const projectId = findProjectByDir(dir);
    if (!projectId) {
        res.json({ ok: true });
        return;
    }
    tagProjectOwnerInAls(projectId);
    const data = {
        usedPercentage: context_window.used_percentage ?? 0,
        remainingPercentage: context_window.remaining_percentage ?? 100,
        contextWindowSize: context_window.context_window_size ?? 0,
        inputTokens: context_window.current_usage?.input_tokens ?? 0,
        outputTokens: context_window.current_usage?.output_tokens ?? 0,
        cacheCreationTokens: context_window.current_usage?.cache_creation_input_tokens ?? 0,
        cacheReadTokens: context_window.current_usage?.cache_read_input_tokens ?? 0,
        updatedAt: Date.now(),
    };
    contextDataMap.set(projectId, data);
    broadcastContextUpdate?.(projectId, data);
    res.json({ ok: true });
});
exports.default = router;
//# sourceMappingURL=hooks.js.map