import { Router } from "express";
import { addApprovalCommentSchema, createApprovalSchema, requestApprovalRevisionSchema, resolveApprovalSchema, resubmitApprovalSchema, } from "@paperclipai/shared";
import { validate } from "../middleware/validate.js";
import { logger } from "../middleware/logger.js";
import { approvalService, accessService, heartbeatService, issueApprovalService, logActivity, secretService, } from "../services/index.js";
import { assertBoard, assertCompanyAccess, getActorInfo } from "./authz.js";
import { redactEventPayload } from "../redaction.js";
function redactApprovalPayload(approval) {
    return {
        ...approval,
        payload: redactEventPayload(approval.payload) ?? {},
    };
}
export function approvalRoutes(db, options = {}) {
    const router = Router();
    const svc = approvalService(db);
    const access = accessService(db);
    const heartbeat = heartbeatService(db, {
        pluginWorkerManager: options.pluginWorkerManager,
    });
    const issueApprovalsSvc = issueApprovalService(db);
    const secretsSvc = secretService(db);
    const strictSecretsMode = process.env.PAPERCLIP_SECRETS_STRICT_MODE === "true";
    async function requireApprovalAccess(req, id) {
        const approval = await svc.getById(id);
        if (!approval) {
            return null;
        }
        assertCompanyAccess(req, approval.companyId);
        return approval;
    }
    async function assertApprovalAccessAllowed(req, res, companyId) {
        const decision = await access.decide({
            actor: req.actor,
            action: "company_scope:read",
            resource: { type: "company", companyId },
        });
        if (decision.allowed)
            return true;
        res.status(403).json({ error: "Approvals are outside this actor's authorization boundary" });
        return false;
    }
    router.get("/companies/:companyId/approvals", async (req, res) => {
        const companyId = req.params.companyId;
        assertCompanyAccess(req, companyId);
        if (!(await assertApprovalAccessAllowed(req, res, companyId)))
            return;
        const status = req.query.status;
        const result = await svc.list(companyId, status);
        res.json(result.map((approval) => redactApprovalPayload(approval)));
    });
    router.get("/approvals/:id", async (req, res) => {
        const id = req.params.id;
        const approval = await svc.getById(id);
        if (!approval) {
            res.status(404).json({ error: "Approval not found" });
            return;
        }
        assertCompanyAccess(req, approval.companyId);
        if (!(await assertApprovalAccessAllowed(req, res, approval.companyId)))
            return;
        res.json(redactApprovalPayload(approval));
    });
    router.post("/companies/:companyId/approvals", validate(createApprovalSchema), async (req, res) => {
        const companyId = req.params.companyId;
        assertCompanyAccess(req, companyId);
        if (!(await assertApprovalAccessAllowed(req, res, companyId)))
            return;
        const rawIssueIds = req.body.issueIds;
        const issueIds = Array.isArray(rawIssueIds)
            ? rawIssueIds.filter((value) => typeof value === "string")
            : [];
        const uniqueIssueIds = Array.from(new Set(issueIds));
        const { issueIds: _issueIds, ...approvalInput } = req.body;
        const normalizedPayload = approvalInput.type === "hire_agent"
            ? await secretsSvc.normalizeHireApprovalPayloadForPersistence(companyId, approvalInput.payload, { strictMode: strictSecretsMode })
            : approvalInput.payload;
        const actor = getActorInfo(req);
        const approval = await svc.create(companyId, {
            ...approvalInput,
            payload: normalizedPayload,
            requestedByUserId: actor.actorType === "user" ? actor.actorId : null,
            requestedByAgentId: approvalInput.requestedByAgentId ?? (actor.actorType === "agent" ? actor.actorId : null),
            status: "pending",
            decisionNote: null,
            decidedByUserId: null,
            decidedAt: null,
            updatedAt: new Date(),
        });
        if (uniqueIssueIds.length > 0) {
            await issueApprovalsSvc.linkManyForApproval(approval.id, uniqueIssueIds, {
                agentId: actor.agentId,
                userId: actor.actorType === "user" ? actor.actorId : null,
            });
        }
        await logActivity(db, {
            companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            action: "approval.created",
            entityType: "approval",
            entityId: approval.id,
            details: { type: approval.type, issueIds: uniqueIssueIds },
        });
        res.status(201).json(redactApprovalPayload(approval));
    });
    router.get("/approvals/:id/issues", async (req, res) => {
        const id = req.params.id;
        const approval = await svc.getById(id);
        if (!approval) {
            res.status(404).json({ error: "Approval not found" });
            return;
        }
        assertCompanyAccess(req, approval.companyId);
        if (!(await assertApprovalAccessAllowed(req, res, approval.companyId)))
            return;
        const issues = await issueApprovalsSvc.listIssuesForApproval(id);
        res.json(issues);
    });
    router.post("/approvals/:id/approve", validate(resolveApprovalSchema), async (req, res) => {
        assertBoard(req);
        const id = req.params.id;
        if (!(await requireApprovalAccess(req, id))) {
            res.status(404).json({ error: "Approval not found" });
            return;
        }
        const decidedByUserId = req.actor.userId ?? "board";
        const { approval, applied } = await svc.approve(id, decidedByUserId, req.body.decisionNote);
        if (applied) {
            const linkedIssues = await issueApprovalsSvc.listIssuesForApproval(approval.id);
            const linkedIssueIds = linkedIssues.map((issue) => issue.id);
            const primaryIssueId = linkedIssueIds[0] ?? null;
            await logActivity(db, {
                companyId: approval.companyId,
                actorType: "user",
                actorId: req.actor.userId ?? "board",
                action: "approval.approved",
                entityType: "approval",
                entityId: approval.id,
                details: {
                    type: approval.type,
                    requestedByAgentId: approval.requestedByAgentId,
                    linkedIssueIds,
                },
            });
            if (approval.requestedByAgentId) {
                try {
                    const wakeRun = await heartbeat.wakeup(approval.requestedByAgentId, {
                        source: "automation",
                        triggerDetail: "system",
                        reason: "approval_approved",
                        payload: {
                            approvalId: approval.id,
                            approvalStatus: approval.status,
                            issueId: primaryIssueId,
                            issueIds: linkedIssueIds,
                        },
                        requestedByActorType: "user",
                        requestedByActorId: req.actor.userId ?? "board",
                        contextSnapshot: {
                            source: "approval.approved",
                            approvalId: approval.id,
                            approvalStatus: approval.status,
                            issueId: primaryIssueId,
                            issueIds: linkedIssueIds,
                            taskId: primaryIssueId,
                            wakeReason: "approval_approved",
                        },
                    });
                    await logActivity(db, {
                        companyId: approval.companyId,
                        actorType: "user",
                        actorId: req.actor.userId ?? "board",
                        action: "approval.requester_wakeup_queued",
                        entityType: "approval",
                        entityId: approval.id,
                        details: {
                            requesterAgentId: approval.requestedByAgentId,
                            wakeRunId: wakeRun?.id ?? null,
                            linkedIssueIds,
                        },
                    });
                }
                catch (err) {
                    logger.warn({
                        err,
                        approvalId: approval.id,
                        requestedByAgentId: approval.requestedByAgentId,
                    }, "failed to queue requester wakeup after approval");
                    await logActivity(db, {
                        companyId: approval.companyId,
                        actorType: "user",
                        actorId: req.actor.userId ?? "board",
                        action: "approval.requester_wakeup_failed",
                        entityType: "approval",
                        entityId: approval.id,
                        details: {
                            requesterAgentId: approval.requestedByAgentId,
                            linkedIssueIds,
                            error: err instanceof Error ? err.message : String(err),
                        },
                    });
                }
            }
        }
        res.json(redactApprovalPayload(approval));
    });
    router.post("/approvals/:id/reject", validate(resolveApprovalSchema), async (req, res) => {
        assertBoard(req);
        const id = req.params.id;
        if (!(await requireApprovalAccess(req, id))) {
            res.status(404).json({ error: "Approval not found" });
            return;
        }
        const decidedByUserId = req.actor.userId ?? "board";
        const { approval, applied } = await svc.reject(id, decidedByUserId, req.body.decisionNote);
        if (applied) {
            await logActivity(db, {
                companyId: approval.companyId,
                actorType: "user",
                actorId: req.actor.userId ?? "board",
                action: "approval.rejected",
                entityType: "approval",
                entityId: approval.id,
                details: { type: approval.type },
            });
        }
        res.json(redactApprovalPayload(approval));
    });
    router.post("/approvals/:id/request-revision", validate(requestApprovalRevisionSchema), async (req, res) => {
        assertBoard(req);
        const id = req.params.id;
        if (!(await requireApprovalAccess(req, id))) {
            res.status(404).json({ error: "Approval not found" });
            return;
        }
        const decidedByUserId = req.actor.userId ?? "board";
        const approval = await svc.requestRevision(id, decidedByUserId, req.body.decisionNote);
        await logActivity(db, {
            companyId: approval.companyId,
            actorType: "user",
            actorId: req.actor.userId ?? "board",
            action: "approval.revision_requested",
            entityType: "approval",
            entityId: approval.id,
            details: { type: approval.type },
        });
        res.json(redactApprovalPayload(approval));
    });
    router.post("/approvals/:id/resubmit", validate(resubmitApprovalSchema), async (req, res) => {
        const id = req.params.id;
        const existing = await svc.getById(id);
        if (!existing) {
            res.status(404).json({ error: "Approval not found" });
            return;
        }
        assertCompanyAccess(req, existing.companyId);
        if (req.actor.type === "agent" && req.actor.agentId !== existing.requestedByAgentId) {
            res.status(403).json({ error: "Only requesting agent can resubmit this approval" });
            return;
        }
        const normalizedPayload = req.body.payload
            ? existing.type === "hire_agent"
                ? await secretsSvc.normalizeHireApprovalPayloadForPersistence(existing.companyId, req.body.payload, { strictMode: strictSecretsMode })
                : req.body.payload
            : undefined;
        const approval = await svc.resubmit(id, normalizedPayload);
        const actor = getActorInfo(req);
        await logActivity(db, {
            companyId: approval.companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            action: "approval.resubmitted",
            entityType: "approval",
            entityId: approval.id,
            details: { type: approval.type },
        });
        res.json(redactApprovalPayload(approval));
    });
    router.get("/approvals/:id/comments", async (req, res) => {
        const id = req.params.id;
        const approval = await svc.getById(id);
        if (!approval) {
            res.status(404).json({ error: "Approval not found" });
            return;
        }
        assertCompanyAccess(req, approval.companyId);
        const comments = await svc.listComments(id);
        res.json(comments);
    });
    router.post("/approvals/:id/comments", validate(addApprovalCommentSchema), async (req, res) => {
        const id = req.params.id;
        const approval = await svc.getById(id);
        if (!approval) {
            res.status(404).json({ error: "Approval not found" });
            return;
        }
        assertCompanyAccess(req, approval.companyId);
        const actor = getActorInfo(req);
        const comment = await svc.addComment(id, req.body.body, {
            agentId: actor.agentId ?? undefined,
            userId: actor.actorType === "user" ? actor.actorId : undefined,
        });
        await logActivity(db, {
            companyId: approval.companyId,
            actorType: actor.actorType,
            actorId: actor.actorId,
            agentId: actor.agentId,
            action: "approval.comment_added",
            entityType: "approval",
            entityId: approval.id,
            details: { commentId: comment.id },
        });
        res.status(201).json(comment);
    });
    return router;
}
//# sourceMappingURL=approvals.js.map