import { NextResponse } from "next/server";
import { z } from "zod";
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
import { AiPendingActionRepository } from "../../../../../data/repositories/AiPendingActionRepository.js";
import { hasRequiredFeatures } from "../../../../../lib/auth.js";
import { serializePendingActionForClient } from "../../../../../lib/pending-action-client.js";
import { checkStatusAndExpiry } from "../../../../../lib/pending-action-recheck.js";
import { executePendingActionCancel } from "../../../../../lib/pending-action-cancel.js";
const REQUIRED_FEATURE = "ai_assistant.view";
const idParamSchema = z.object({
  id: z.string().trim().min(1, "id must be a non-empty string").max(128, "id exceeds the maximum length of 128 characters")
});
const bodySchema = z.object({
  reason: z.string().max(500, "reason must be at most 500 characters").optional()
}).strict().optional();
const openApi = {
  tag: "AI Assistant",
  summary: "Pending action (mutation approval gate) cancel",
  methods: {
    POST: {
      operationId: "aiAssistantCancelPendingAction",
      summary: "Cancel an AI pending action without executing the wrapped tool.",
      description: "Flips a pending AI action from `pending` to `cancelled` and emits the `ai.action.cancelled` event. The tool handler is never invoked. Idempotent: a second call on a row already in `cancelled` status returns 200 with the current row without re-emitting the event. Rows whose `expiresAt` is in the past are flipped to `expired` and returned as 409 `expired` so the client can surface the TTL loss instead of silently masking it as a cancellation.",
      responses: [
        {
          status: 200,
          description: "Cancellation complete (or idempotent replay); body includes the serialized pending action with status `cancelled`.",
          mediaType: "application/json"
        }
      ],
      errors: [
        { status: 400, description: "Invalid cancel request body (unknown field, reason exceeds 500 chars, wrong type)." },
        { status: 401, description: "Unauthenticated caller." },
        { status: 403, description: "Caller lacks `ai_assistant.view`." },
        { status: 404, description: "Pending action not found in the caller scope." },
        { status: 409, description: "Pending action is not in `pending` status (already confirmed/failed/executing) or has expired." },
        { status: 500, description: "Unexpected server failure during cancel." }
      ]
    }
  }
};
const metadata = {
  POST: { requireAuth: true, requireFeatures: [REQUIRED_FEATURE] }
};
function jsonError(status, message, code, extra) {
  return NextResponse.json({ error: message, code, ...extra ?? {} }, { status });
}
async function readRequestBody(req) {
  try {
    const text = await req.text();
    if (!text || text.trim().length === 0) return void 0;
    return JSON.parse(text);
  } catch {
    return /* @__PURE__ */ Symbol.for("ai_assistant.cancel.bad_json");
  }
}
async function POST(req, context) {
  const auth = await getAuthFromRequest(req);
  if (!auth) {
    return jsonError(401, "Unauthorized", "unauthenticated");
  }
  const rawParams = await context.params;
  const paramResult = idParamSchema.safeParse(rawParams);
  if (!paramResult.success) {
    return jsonError(400, "Invalid pending action id.", "validation_error", {
      issues: paramResult.error.issues
    });
  }
  const pendingActionId = paramResult.data.id;
  const rawBody = await readRequestBody(req);
  if (rawBody === /* @__PURE__ */ Symbol.for("ai_assistant.cancel.bad_json")) {
    return jsonError(400, "Invalid JSON body.", "validation_error");
  }
  const bodyResult = bodySchema.safeParse(rawBody);
  if (!bodyResult.success) {
    return jsonError(400, "Invalid cancel body.", "validation_error", {
      issues: bodyResult.error.issues
    });
  }
  const parsedBody = bodyResult.data ?? {};
  try {
    const container = await createRequestContainer();
    const rbacService = container.resolve("rbacService");
    const acl = await rbacService.loadAcl(auth.sub, {
      tenantId: auth.tenantId,
      organizationId: auth.orgId
    });
    if (!hasRequiredFeatures([REQUIRED_FEATURE], acl.features, acl.isSuperAdmin, rbacService)) {
      return jsonError(403, `Caller lacks required feature "${REQUIRED_FEATURE}".`, "forbidden");
    }
    if (!auth.tenantId) {
      return jsonError(
        404,
        `No pending action "${pendingActionId}" accessible to the caller.`,
        "pending_action_not_found"
      );
    }
    const em = container.resolve("em");
    const repo = new AiPendingActionRepository(em);
    const row = await repo.getById(pendingActionId, {
      tenantId: auth.tenantId,
      organizationId: auth.orgId ?? null,
      userId: auth.sub
    });
    if (!row) {
      return jsonError(
        404,
        `No pending action "${pendingActionId}" accessible to the caller.`,
        "pending_action_not_found"
      );
    }
    if (row.status === "cancelled") {
      return NextResponse.json({
        ok: true,
        pendingAction: serializePendingActionForClient(row)
      });
    }
    const statusCheck = checkStatusAndExpiry(row);
    if (!statusCheck.ok) {
      if (statusCheck.code === "expired") {
        const cancelResult2 = await executePendingActionCancel({
          action: row,
          ctx: {
            tenantId: auth.tenantId,
            organizationId: auth.orgId ?? null,
            userId: auth.sub,
            container
          },
          repo
        });
        return jsonError(409, statusCheck.message, "expired", {
          pendingAction: serializePendingActionForClient(cancelResult2.row)
        });
      }
      return jsonError(statusCheck.status, statusCheck.message, statusCheck.code, statusCheck.extra);
    }
    const cancelResult = await executePendingActionCancel({
      action: row,
      ctx: {
        tenantId: auth.tenantId,
        organizationId: auth.orgId ?? null,
        userId: auth.sub,
        container
      },
      reason: parsedBody.reason,
      repo
    });
    if (cancelResult.status === "expired") {
      return jsonError(
        409,
        "Pending action has expired. The model must re-propose the mutation.",
        "expired",
        { pendingAction: serializePendingActionForClient(cancelResult.row) }
      );
    }
    return NextResponse.json({
      ok: true,
      pendingAction: serializePendingActionForClient(cancelResult.row)
    });
  } catch (error) {
    console.error("[AI Pending Action CANCEL] Failure:", error);
    return jsonError(
      500,
      error instanceof Error ? error.message : "Failed to cancel pending action.",
      "cancel_internal_error"
    );
  }
}
export {
  POST,
  metadata,
  openApi
};
//# sourceMappingURL=route.js.map
