import { createHmac, timingSafeEqual } from "node:crypto";
/** Route kinds aligned with ADR-github-native-invocation.md slash commands. */
export const GITHUB_INVOCATION_ROUTE_KINDS = [
    "plan",
    "implement",
    "review",
    "fix-review",
    "none"
];
const ROUTE_SET = new Set(GITHUB_INVOCATION_ROUTE_KINDS);
/** Verify GitHub `X-Hub-Signature-256` (HMAC-SHA256 hex digest of raw body). */
export function verifyGithubWebhookSignatureSha256(rawBody, signatureHeader, secret) {
    if (!secret || !signatureHeader) {
        return false;
    }
    const prefix = "sha256=";
    if (!signatureHeader.startsWith(prefix)) {
        return false;
    }
    const theirHex = signatureHeader.slice(prefix.length).trim();
    if (!/^[0-9a-f]+$/i.test(theirHex) || theirHex.length % 2 !== 0) {
        return false;
    }
    const theirs = Buffer.from(theirHex, "hex");
    const hmac = createHmac("sha256", secret);
    hmac.update(rawBody);
    const mine = hmac.digest();
    if (theirs.length !== mine.length) {
        return false;
    }
    return timingSafeEqual(theirs, mine);
}
export function extractTaskIdsFromText(text) {
    const out = new Set();
    const re = /\b(T\d{3,})\b/g;
    let m;
    while ((m = re.exec(text)) !== null) {
        out.add(m[1]);
    }
    return [...out].sort();
}
const SLASH_RE = /^\/(cannon-plan|cannon-implement|cannon-review|cannon-fix-review)\b(.*)$/im;
export function parseCannonSlashCommand(body) {
    const lines = body.split(/\r?\n/);
    let m = null;
    for (const raw of lines) {
        const line = raw.trim();
        if (!line)
            continue;
        m = line.match(SLASH_RE);
        if (m)
            break;
    }
    if (!m) {
        return null;
    }
    const cmd = m[1].toLowerCase();
    const remainder = (m[2] ?? "").trim();
    const map = {
        "cannon-plan": "plan",
        "cannon-implement": "implement",
        "cannon-review": "review",
        "cannon-fix-review": "fix-review"
    };
    const routeKind = map[cmd];
    if (!routeKind) {
        return null;
    }
    return { routeKind, remainder };
}
export function getRepositoryFullName(payload) {
    const repo = payload.repository;
    if (!repo || typeof repo !== "object" || repo === null) {
        return null;
    }
    const name = repo.full_name;
    return typeof name === "string" && name.includes("/") ? name : null;
}
export function getGithubDeliveryMeta(headers, payload) {
    const deliveryId = headers["x-github-delivery"] ??
        headers["X-GitHub-Delivery"] ??
        (typeof payload.delivery_id === "string" ? payload.delivery_id : "");
    const eventName = headers["x-github-event"] ??
        headers["X-GitHub-Event"] ??
        (typeof payload.action === "string" ? `synthetic.${payload.action}` : "unknown");
    return {
        deliveryId: deliveryId || "unknown-delivery",
        eventName: eventName || "unknown"
    };
}
export function resolveRouteKind(args) {
    const slash = parseCannonSlashCommand(args.commentBody);
    if (slash) {
        return slash.routeKind;
    }
    const mapped = args.eventPlaybookMap[args.eventName];
    if (mapped === undefined) {
        return null;
    }
    if (!ROUTE_SET.has(mapped)) {
        return null;
    }
    return mapped;
}
export function getIssueCommentBody(payload) {
    const c = payload.comment;
    if (c && typeof c === "object" && c !== null) {
        const body = c.body;
        if (typeof body === "string") {
            return body;
        }
    }
    return "";
}
/** Text used for slash parsing: issue comments, PR review bodies, or review comment bodies. */
export function getInvocationCommentBody(payload, eventName) {
    const direct = getIssueCommentBody(payload);
    if (direct.trim()) {
        return direct;
    }
    if (eventName === "pull_request_review") {
        const r = payload.review;
        if (r && typeof r === "object" && r !== null) {
            const body = r.body;
            if (typeof body === "string") {
                return body;
            }
        }
    }
    return "";
}
export function isRepositoryAllowed(fullName, allowedRepositories) {
    if (!fullName) {
        return false;
    }
    const set = new Set(allowedRepositories.map((s) => s.trim().toLowerCase()).filter(Boolean));
    return set.has(fullName.toLowerCase());
}
export function buildAuditRecord(partial) {
    return { schemaVersion: 1, ...partial };
}
