import { NextResponse } from "next/server";
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
import { resolveFeatureCheckContext, resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
import { serializeOperationMetadata } from "@open-mercato/shared/lib/commands/operationMetadata";
import { z } from "zod";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("audit_logs").child({ component: "redo" });
const metadata = {
  POST: { requireAuth: true, requireFeatures: ["audit_logs.redo_self"] }
};
const redoRequestSchema = z.object({
  logId: z.string().min(1).describe("Identifier of the previously undone action log")
});
const redoResponseSchema = z.object({
  ok: z.literal(true),
  logId: z.string().nullable().describe("Identifier of the new redo log entry, if available"),
  undoToken: z.string().nullable().describe("New undo token associated with the redone action")
});
const errorSchema = z.object({
  error: z.string()
});
async function POST(req) {
  const auth = await getAuthFromRequest(req);
  if (!auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  const body = await req.json().catch(() => null);
  const logId = typeof body?.logId === "string" ? body.logId.trim() : "";
  if (!logId) return NextResponse.json({ error: "Invalid log id" }, { status: 400 });
  const container = await createRequestContainer();
  const commandBus = container.resolve("commandBus");
  const logs = container.resolve("actionLogService");
  let rbac = null;
  try {
    rbac = container.resolve("rbacService");
  } catch {
    rbac = null;
  }
  const { organizationId } = await resolveFeatureCheckContext({ container, auth, request: req });
  const canRedoTenant = rbac ? await rbac.userHasAllFeatures(auth.sub, ["audit_logs.redo_tenant"], {
    tenantId: auth.tenantId ?? null,
    organizationId
  }) : false;
  const scopedOrgId = canRedoTenant ? organizationId ?? null : organizationId ?? auth.orgId ?? null;
  const log = await logs.findById(logId);
  if (!log || log.executionState !== "undone") {
    return NextResponse.json({ error: "Redo target not available" }, { status: 400 });
  }
  if (log.actorUserId && log.actorUserId !== auth.sub && !canRedoTenant) {
    return NextResponse.json({ error: "Redo target not available" }, { status: 400 });
  }
  if (log.tenantId && log.tenantId !== (auth.tenantId ?? null)) {
    return NextResponse.json({ error: "Redo target not available" }, { status: 400 });
  }
  const orgScopeMismatch = canRedoTenant ? Boolean(log.organizationId && scopedOrgId && log.organizationId !== scopedOrgId) : Boolean(log.organizationId && log.organizationId !== scopedOrgId);
  if (orgScopeMismatch) {
    return NextResponse.json({ error: "Redo target not available" }, { status: 400 });
  }
  const lookupActorId = canRedoTenant ? log.actorUserId ?? auth.sub : auth.sub;
  const latestUndoneOrganizationId = log.organizationId ?? null;
  const latestUndone = await logs.latestUndoneForActor(lookupActorId, {
    tenantId: auth.tenantId ?? null,
    organizationId: latestUndoneOrganizationId
  });
  if (!latestUndone || latestUndone.id !== log.id) {
    return NextResponse.json({ error: "Redo target not available" }, { status: 400 });
  }
  try {
    const ctx = await createRuntimeContext(container, auth, req);
    const contextRecord = log.contextJson && typeof log.contextJson === "object" ? log.contextJson : null;
    const cacheAliasesRaw = Array.isArray(contextRecord?.cacheAliases) ? contextRecord.cacheAliases : [];
    const cacheAliases = cacheAliasesRaw.filter((value) => typeof value === "string" && value.trim().length > 0).map((value) => value.trim());
    const metadataContext = {
      historyAction: "redo",
      sourceLogId: log.id,
      sourceCommandId: log.commandId
    };
    if (cacheAliases.length) metadataContext.cacheAliases = cacheAliases;
    const metadata2 = {
      tenantId: log.tenantId,
      organizationId: log.organizationId,
      actorUserId: auth.sub,
      actionLabel: log.actionLabel,
      resourceKind: log.resourceKind,
      resourceId: log.resourceId,
      context: metadataContext
    };
    const resolvedInput = resolveRedoInput(log.commandPayload, log);
    if (!resolvedInput) {
      return NextResponse.json({ error: "Redo data unavailable for this action" }, { status: 400 });
    }
    const commandInput = resolvedInput;
    const { logEntry } = await commandBus.execute(log.commandId, {
      input: commandInput,
      ctx,
      metadata: metadata2,
      redoLogEntry: log
    });
    await logs.markRedone(log.id);
    const actionLog = asActionLog(logEntry);
    const response = NextResponse.json({
      ok: true,
      logId: actionLog?.id ?? null,
      undoToken: actionLog?.undoToken ?? null
    });
    if (actionLog?.undoToken && actionLog.id) {
      const createdAt = actionLog.createdAt instanceof Date ? actionLog.createdAt.toISOString() : typeof actionLog.createdAt === "string" ? actionLog.createdAt : (/* @__PURE__ */ new Date()).toISOString();
      response.headers.set("x-om-operation", serializeOperationMetadata({
        id: actionLog.id,
        undoToken: actionLog.undoToken,
        commandId: actionLog.commandId ?? log.commandId,
        actionLabel: actionLog.actionLabel ?? log.actionLabel ?? null,
        resourceKind: typeof actionLog.resourceKind === "string" ? actionLog.resourceKind : log.resourceKind ?? null,
        resourceId: typeof actionLog.resourceId === "string" ? actionLog.resourceId : log.resourceId ?? null,
        executedAt: createdAt
      }));
    }
    return response;
  } catch (err) {
    logger.error("Redo failed", { err });
    return NextResponse.json({ error: "Redo failed" }, { status: 400 });
  }
}
async function createRuntimeContext(container, auth, request) {
  const scope = await resolveOrganizationScopeForRequest({ container, auth, request });
  return {
    container,
    auth,
    organizationScope: scope,
    selectedOrganizationId: scope.selectedId,
    organizationIds: scope.filterIds,
    request
  };
}
function asActionLog(entry) {
  if (!entry || typeof entry !== "object") return null;
  if (typeof entry.id !== "string") return null;
  return entry;
}
function resolveRedoInput(payload, log) {
  if (payload && typeof payload === "object" && !Array.isArray(payload) && "__redoInput" in payload) {
    const envelope = payload;
    return envelope.__redoInput ?? {};
  }
  const updateFallback = deriveUpdateInput(log);
  if (updateFallback) return updateFallback;
  return null;
}
function deriveUpdateInput(log) {
  if (!log.commandId.endsWith(".update")) return null;
  if (!log.resourceId) return null;
  const changes = log.changesJson;
  if (!changes || typeof changes !== "object" || Array.isArray(changes)) return { id: log.resourceId };
  const payload = { id: log.resourceId };
  for (const [key, value] of Object.entries(changes)) {
    if (value && typeof value === "object" && !Array.isArray(value) && "to" in value) {
      payload[key] = value.to;
    } else {
      payload[key] = value;
    }
  }
  return payload;
}
const openApi = {
  summary: "Redo a previously undone action",
  description: "Replays the command associated with a recently undone action, reapplying the change and issuing a fresh undo token.",
  methods: {
    POST: {
      summary: "Redo by action log id",
      description: "Redoes the latest undone command owned by the caller. Requires the action to still be eligible for redo within tenant and organization scope.",
      requestBody: {
        contentType: "application/json",
        schema: redoRequestSchema
      },
      responses: [
        { status: 200, description: "Redo executed successfully", schema: redoResponseSchema }
      ],
      errors: [
        { status: 400, description: "Log not eligible for redo", schema: errorSchema },
        { status: 401, description: "Authentication required", schema: errorSchema },
        { status: 403, description: "Redo blocked by scope checks", schema: errorSchema }
      ]
    }
  }
};
export {
  POST,
  metadata,
  openApi
};
//# sourceMappingURL=route.js.map
