import { z } from "zod";
import { findWithDecryption, findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { defineAiTool } from "../lib/ai-tool-definition.js";
async function loadAttachmentEntity() {
  const mod = await import("@open-mercato/core/modules/attachments/data/entities");
  return mod.Attachment;
}
async function loadAttachmentMetadata() {
  const mod = await import("@open-mercato/core/modules/attachments/lib/metadata");
  return mod;
}
function assertTenantScope(ctx) {
  if (!ctx.tenantId) {
    throw new Error("Tenant context is required for attachments tools");
  }
  return ctx.tenantId;
}
function resolveEm(ctx) {
  return ctx.container.resolve("em");
}
const listInput = z.object({
  entityType: z.string().min(1).describe('Entity identifier (e.g. "customers:customer_person_profile").'),
  recordId: z.string().min(1).describe("Record identifier within that entity.")
});
const listRecordAttachmentsTool = defineAiTool({
  name: "attachments.list_record_attachments",
  displayName: "List record attachments",
  description: "List attachments bound to a record, scoped to the caller tenant and organization. Returns metadata only (no bytes, no signed URL).",
  inputSchema: listInput,
  requiredFeatures: ["attachments.view"],
  tags: ["read", "attachments"],
  handler: async (rawInput, ctx) => {
    const tenantId = assertTenantScope(ctx);
    const input = listInput.parse(rawInput);
    const em = resolveEm(ctx);
    const Attachment = await loadAttachmentEntity();
    const where = {
      entityId: input.entityType,
      recordId: input.recordId,
      tenantId
    };
    if (ctx.organizationId) where.organizationId = ctx.organizationId;
    const rows = await findWithDecryption(
      em,
      Attachment,
      where,
      { orderBy: { createdAt: "desc" } },
      { tenantId, organizationId: ctx.organizationId }
    );
    return {
      entityType: input.entityType,
      recordId: input.recordId,
      total: rows.length,
      items: rows.map((row) => ({
        id: row.id,
        entityType: row.entityId,
        recordId: row.recordId,
        fileName: row.fileName,
        mediaType: row.mimeType,
        size: row.fileSize,
        partitionCode: row.partitionCode,
        createdAt: row.createdAt ? new Date(row.createdAt).toISOString() : null
      }))
    };
  }
});
const readInput = z.object({
  attachmentId: z.string().uuid().describe("Attachment identifier."),
  includeExtractedText: z.boolean().optional().describe("When true, include the stored extracted / OCR text if present (default false).")
});
const readAttachmentTool = defineAiTool({
  name: "attachments.read_attachment",
  displayName: "Read attachment metadata",
  description: "Return attachment metadata, tags, assignments, and optionally the stored extracted text. Never returns raw bytes or signed URLs.",
  inputSchema: readInput,
  requiredFeatures: ["attachments.view"],
  tags: ["read", "attachments"],
  handler: async (rawInput, ctx) => {
    const tenantId = assertTenantScope(ctx);
    const input = readInput.parse(rawInput);
    const em = resolveEm(ctx);
    const Attachment = await loadAttachmentEntity();
    const { readAttachmentMetadata } = await loadAttachmentMetadata();
    const where = { id: input.attachmentId, tenantId };
    if (ctx.organizationId) where.organizationId = ctx.organizationId;
    const row = await findOneWithDecryption(
      em,
      Attachment,
      where,
      void 0,
      { tenantId, organizationId: ctx.organizationId }
    );
    if (!row) {
      return { found: false, attachmentId: input.attachmentId };
    }
    const metadata = readAttachmentMetadata(row.storageMetadata);
    return {
      found: true,
      id: row.id,
      entityType: row.entityId,
      recordId: row.recordId,
      fileName: row.fileName,
      mediaType: row.mimeType,
      size: row.fileSize,
      partitionCode: row.partitionCode,
      createdAt: row.createdAt ? new Date(row.createdAt).toISOString() : null,
      tags: metadata.tags ?? [],
      assignments: metadata.assignments ?? [],
      extractedText: input.includeExtractedText === true && typeof row.content === "string" ? row.content : null,
      hasExtractedText: typeof row.content === "string" && row.content.length > 0
    };
  }
});
const transferInput = z.object({
  fromEntityType: z.string().min(1).describe("Current entity type of the source attachments."),
  fromRecordId: z.string().min(1).describe("Current record id the attachments are bound to."),
  toEntityType: z.string().min(1).describe("Target entity type (must match the source)."),
  toRecordId: z.string().min(1).describe("Target record id to re-bind the attachments to."),
  attachmentIds: z.array(z.string().uuid()).min(1).max(100).optional().describe("Optional subset; defaults to every attachment on the source record.")
});
const transferRecordAttachmentsTool = defineAiTool({
  name: "attachments.transfer_record_attachments",
  displayName: "Transfer record attachments",
  description: "Move uploaded files from a temporary/draft record to a saved record. Mutation tool \u2014 agents with readOnly=true are blocked by the policy gate.",
  inputSchema: transferInput,
  isMutation: true,
  requiredFeatures: ["attachments.manage"],
  tags: ["write", "attachments"],
  handler: async (rawInput, ctx) => {
    const tenantId = assertTenantScope(ctx);
    const input = transferInput.parse(rawInput);
    if (input.fromEntityType !== input.toEntityType) {
      throw new Error(
        "attachments.transfer_record_attachments requires fromEntityType and toEntityType to match"
      );
    }
    const em = resolveEm(ctx);
    const Attachment = await loadAttachmentEntity();
    const { readAttachmentMetadata, mergeAttachmentMetadata } = await loadAttachmentMetadata();
    const where = {
      entityId: input.fromEntityType,
      recordId: input.fromRecordId,
      tenantId
    };
    if (ctx.organizationId) where.organizationId = ctx.organizationId;
    if (input.attachmentIds && input.attachmentIds.length > 0) {
      where.id = { $in: input.attachmentIds };
    }
    const rows = await findWithDecryption(
      em,
      Attachment,
      where,
      void 0,
      { tenantId, organizationId: ctx.organizationId }
    );
    if (!rows.length) {
      return {
        transferred: 0,
        fromEntityType: input.fromEntityType,
        fromRecordId: input.fromRecordId,
        toEntityType: input.toEntityType,
        toRecordId: input.toRecordId
      };
    }
    for (const row of rows) {
      const previousRecordId = row.recordId;
      row.recordId = input.toRecordId;
      const metadata = readAttachmentMetadata(row.storageMetadata);
      const nextAssignments = metadata.assignments?.map((assignment) => {
        const matchesType = assignment.type === input.fromEntityType;
        const matchesRecord = assignment.id === previousRecordId;
        if (matchesType && matchesRecord) {
          return { ...assignment, id: input.toRecordId };
        }
        return assignment;
      }) ?? [];
      row.storageMetadata = mergeAttachmentMetadata(row.storageMetadata, {
        assignments: nextAssignments
      });
    }
    await em.persist(rows).flush();
    return {
      transferred: rows.length,
      fromEntityType: input.fromEntityType,
      fromRecordId: input.fromRecordId,
      toEntityType: input.toEntityType,
      toRecordId: input.toRecordId,
      attachmentIds: rows.map((row) => row.id)
    };
  }
});
const attachmentsAiTools = [
  listRecordAttachmentsTool,
  readAttachmentTool,
  transferRecordAttachmentsTool
];
var attachments_pack_default = attachmentsAiTools;
export {
  attachmentsAiTools,
  attachments_pack_default as default
};
//# sourceMappingURL=attachments-pack.js.map
