import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { Attachment } from "@open-mercato/core/modules/attachments/data/entities";
import { resolveEffectiveMutationPolicy } from "./agent-policy.js";
import { hasRequiredFeatures } from "./auth.js";
function checkStatusAndExpiry(action, options = {}) {
  const now = options.now ?? /* @__PURE__ */ new Date();
  if (action.status !== "pending") {
    return {
      ok: false,
      status: 409,
      code: "invalid_status",
      message: `Pending action is in status "${action.status}"; expected "pending".`
    };
  }
  const expiresAt = action.expiresAt instanceof Date ? action.expiresAt : new Date(action.expiresAt);
  if (expiresAt.getTime() <= now.getTime()) {
    return {
      ok: false,
      status: 409,
      code: "expired",
      message: "Pending action has expired. The model must re-propose the mutation."
    };
  }
  return { ok: true };
}
function checkAgentAndFeatures(agent, ctx) {
  if (!agent) {
    return {
      ok: false,
      status: 404,
      code: "agent_unknown",
      message: "Agent is no longer registered."
    };
  }
  const required = agent.requiredFeatures ?? [];
  if (!hasRequiredFeatures(required, ctx.userFeatures, ctx.isSuperAdmin)) {
    return {
      ok: false,
      status: 403,
      code: "agent_features_denied",
      message: `Caller lacks one of the agent's required features: ${required.join(", ")}`
    };
  }
  return { ok: true };
}
function checkToolWhitelist(agent, tool, action, options = {}) {
  if (!tool) {
    return {
      ok: false,
      status: 403,
      code: "tool_not_whitelisted",
      message: `Tool "${action.toolName}" is not registered.`
    };
  }
  if (!agent.allowedTools.includes(tool.name) || tool.isMutation !== true) {
    return {
      ok: false,
      status: 403,
      code: "tool_not_whitelisted",
      message: `Tool "${tool.name}" is no longer whitelisted as a mutation tool for agent "${agent.id}".`
    };
  }
  const effective = resolveEffectiveMutationPolicy(
    agent.mutationPolicy,
    options.mutationPolicyOverride ?? null,
    agent.id
  );
  if (effective === "read-only") {
    return {
      ok: false,
      status: 403,
      code: "read_only_agent",
      message: `Agent "${agent.id}" effective mutationPolicy=read-only; mutation tool "${tool.name}" cannot be executed.`
    };
  }
  return { ok: true };
}
async function checkAttachmentScope(action, ctx) {
  const ids = Array.isArray(action.attachmentIds) ? action.attachmentIds : [];
  if (ids.length === 0) return { ok: true };
  const em = ctx.em;
  if (!em) {
    return {
      ok: false,
      status: 500,
      code: "attachment_cross_tenant",
      message: "Attachment scope check requires an EntityManager."
    };
  }
  const rows = await findWithDecryption(
    em,
    Attachment,
    { id: { $in: ids } },
    {},
    { tenantId: ctx.tenantId, organizationId: ctx.organizationId }
  );
  if (rows.length !== ids.length) {
    return {
      ok: false,
      status: 403,
      code: "attachment_cross_tenant",
      message: "One or more attachments are not accessible to the caller."
    };
  }
  for (const row of rows) {
    if ((row.tenantId ?? null) !== ctx.tenantId) {
      return {
        ok: false,
        status: 403,
        code: "attachment_cross_tenant",
        message: "One or more attachments belong to a different tenant."
      };
    }
    if (ctx.organizationId !== null && row.organizationId && row.organizationId !== ctx.organizationId) {
      return {
        ok: false,
        status: 403,
        code: "attachment_cross_tenant",
        message: "One or more attachments belong to a different organization."
      };
    }
  }
  return { ok: true };
}
async function checkRecordVersion(action, tool, ctx) {
  const parseResult = tool.inputSchema.safeParse(action.normalizedInput ?? {});
  if (!parseResult.success) {
    return {
      ok: false,
      status: 412,
      code: "schema_drift",
      message: "Pending input no longer satisfies the tool schema.",
      extra: { issues: parseResult.error.issues }
    };
  }
  const handlerContext = toHandlerContext(ctx);
  const records = Array.isArray(action.records) ? action.records : null;
  if (records && records.length > 0) {
    if (!tool.loadBeforeRecords) {
      return { ok: true };
    }
    const currentRows = await tool.loadBeforeRecords(parseResult.data, handlerContext);
    const currentVersionById = /* @__PURE__ */ new Map();
    for (const row of currentRows ?? []) {
      currentVersionById.set(row.recordId, row.recordVersion ?? null);
    }
    const stale = [];
    for (const record of records) {
      const current2 = currentVersionById.get(record.recordId);
      const captured2 = record.recordVersion ?? null;
      if (current2 === void 0) {
        stale.push({
          recordId: record.recordId,
          error: { code: "stale_version", message: "Record no longer exists." }
        });
        continue;
      }
      if (captured2 !== null && current2 !== captured2) {
        stale.push({
          recordId: record.recordId,
          error: { code: "stale_version", message: "Record version changed since preview." }
        });
      }
    }
    if (stale.length === records.length) {
      return {
        ok: false,
        status: 412,
        code: "stale_version",
        message: "All pending records are stale; the model must re-propose the batch.",
        extra: { staleRecords: stale.map((entry) => entry.recordId) }
      };
    }
    if (stale.length > 0) {
      return { ok: true, failedRecords: stale };
    }
    return { ok: true };
  }
  if (!tool.loadBeforeRecord) {
    return { ok: true };
  }
  const before = await tool.loadBeforeRecord(parseResult.data, handlerContext);
  if (!before) return { ok: true };
  const captured = action.recordVersion ?? null;
  const current = before.recordVersion ?? null;
  if (captured !== null && current !== captured) {
    return {
      ok: false,
      status: 412,
      code: "stale_version",
      message: "Record version changed since preview; re-propose the mutation.",
      extra: { recordId: before.recordId }
    };
  }
  return { ok: true };
}
function toHandlerContext(ctx) {
  return {
    tenantId: ctx.tenantId,
    organizationId: ctx.organizationId,
    userId: ctx.userId,
    container: ctx.container,
    userFeatures: ctx.userFeatures,
    isSuperAdmin: ctx.isSuperAdmin
  };
}
async function runPendingActionRechecks(input) {
  const { action, agent, tool, ctx, now, mutationPolicyOverride } = input;
  const statusCheck = checkStatusAndExpiry(action, { now });
  if (!statusCheck.ok) return statusCheck;
  const agentCheck = checkAgentAndFeatures(agent, ctx);
  if (!agentCheck.ok) return agentCheck;
  const whitelistCheck = checkToolWhitelist(agent, tool, action, {
    mutationPolicyOverride: mutationPolicyOverride ?? null
  });
  if (!whitelistCheck.ok) return whitelistCheck;
  const attachmentCheck = await checkAttachmentScope(action, ctx);
  if (!attachmentCheck.ok) return attachmentCheck;
  const versionCheck = await checkRecordVersion(action, tool, ctx);
  return versionCheck;
}
const PENDING_ACTION_RECHECK_CODES = [
  "invalid_status",
  "expired",
  "agent_unknown",
  "agent_features_denied",
  "tool_not_whitelisted",
  "read_only_agent",
  "attachment_cross_tenant",
  "stale_version",
  "schema_drift"
];
function isPendingActionRecheckCode(value) {
  return typeof value === "string" && PENDING_ACTION_RECHECK_CODES.includes(value);
}
export {
  PENDING_ACTION_RECHECK_CODES,
  checkAgentAndFeatures,
  checkAttachmentScope,
  checkRecordVersion,
  checkStatusAndExpiry,
  checkToolWhitelist,
  isPendingActionRecheckCode,
  runPendingActionRechecks
};
//# sourceMappingURL=pending-action-recheck.js.map
