import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import {
  ChannelThreadMapping,
  CommunicationChannel,
  ExternalConversation,
  MessageChannelLink
} from "../data/entities.js";
import { ChannelMutationBlockedError, guardOutboundCreate } from "./mutation-guards.js";
import { COMMUNICATION_CHANNELS_QUEUES, getCommunicationChannelsQueue } from "./queue.js";
import { htmlToText } from "./email-mime.js";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("communication_channels");
async function sendAsUser(container, actor, input) {
  if (!input.body.plain && !input.body.html) {
    return { ok: false, status: 422, error: "Either body.plain or body.html is required" };
  }
  if (!Array.isArray(input.to) || input.to.length === 0) {
    return { ok: false, status: 422, error: "At least one recipient is required" };
  }
  const em = container.resolve("em").fork();
  const { tenantId } = actor;
  const organizationId = actor.organizationId;
  const dscope = { tenantId, organizationId };
  const channel = await findOneWithDecryption(
    em,
    CommunicationChannel,
    { id: input.userChannelId, tenantId, organizationId, deletedAt: null },
    void 0,
    dscope
  );
  if (!channel) return { ok: false, status: 404, error: "Channel not found" };
  if (channel.userId !== actor.userId) {
    return { ok: false, status: 403, error: "You can only send through channels you own" };
  }
  try {
    await guardOutboundCreate(em, { channelId: channel.id, scope: dscope });
  } catch (err) {
    if (err instanceof ChannelMutationBlockedError) {
      return { ok: false, status: 422, error: err.message, fieldErrors: err.errors };
    }
    throw err;
  }
  if (!channel.isActive || channel.status !== "connected") {
    return {
      ok: false,
      status: 409,
      error: `Channel is in status '${channel.status}' (not connected)`
    };
  }
  const commandBus = container.resolve("commandBus");
  const messageBody = input.body.plain ?? htmlToText(input.body.html ?? "");
  const composeInput = {
    type: `channel.${channel.providerKey}`,
    visibility: "public",
    sourceEntityType: "communication_channels.send_as_user",
    sourceEntityId: channel.id,
    externalEmail: input.to[0],
    externalName: input.subject,
    recipients: [],
    subject: input.subject,
    body: messageBody,
    bodyFormat: "text",
    priority: "normal",
    sendViaEmail: false,
    parentMessageId: input.parentMessageId,
    isDraft: false,
    tenantId,
    organizationId,
    userId: actor.userId
  };
  let result;
  try {
    result = await commandBus.execute(
      "messages.messages.compose",
      {
        input: composeInput,
        ctx: {
          container,
          auth: actor.auth,
          organizationScope: null,
          selectedOrganizationId: organizationId,
          organizationIds: organizationId ? [organizationId] : null
        }
      }
    );
  } catch (err) {
    logger.error("communication_channels send-as-user compose failed", { err });
    return { ok: false, status: 500, error: "[internal] compose failed" };
  }
  const messageId = result.result.id;
  const messageThreadId = result.result.threadId ?? messageId;
  const externalThreadRef = `outbound:${messageThreadId}`;
  await em.transactional(async () => {
    let conversation = await findOneWithDecryption(
      em,
      ExternalConversation,
      {
        channelId: channel.id,
        externalConversationId: externalThreadRef,
        tenantId,
        organizationId
      },
      void 0,
      dscope
    );
    if (!conversation) {
      conversation = em.create(ExternalConversation, {
        channelId: channel.id,
        externalConversationId: externalThreadRef,
        subject: input.subject,
        assignedUserId: actor.userId,
        tenantId,
        organizationId,
        lastMessageAt: /* @__PURE__ */ new Date()
      });
      em.persist(conversation);
      await em.flush();
    } else {
      conversation.subject = conversation.subject ?? input.subject;
      conversation.assignedUserId = conversation.assignedUserId ?? actor.userId;
      conversation.lastMessageAt = /* @__PURE__ */ new Date();
      await em.flush();
    }
    const existingMapping = await findOneWithDecryption(
      em,
      ChannelThreadMapping,
      {
        externalConversationId: conversation.id,
        tenantId,
        organizationId
      },
      void 0,
      dscope
    );
    if (!existingMapping) {
      const mapping = em.create(ChannelThreadMapping, {
        externalConversationId: conversation.id,
        messageThreadId,
        channelId: channel.id,
        providerKey: channel.providerKey,
        externalThreadRef,
        assignedUserId: actor.userId,
        tenantId,
        organizationId
      });
      em.persist(mapping);
    }
    const channelLink = em.create(MessageChannelLink, {
      messageId,
      externalConversationId: conversation.id,
      providerKey: channel.providerKey,
      channelType: channel.channelType,
      direction: "outbound",
      deliveryStatus: "pending",
      channelPayload: {
        text: input.body.plain ?? messageBody,
        ...input.body.html ? { html: input.body.html } : {}
      },
      channelContentType: input.body.html ? "text/html" : "text/plain",
      channelMetadata: {
        // Caller-supplied pass-through metadata merged FIRST so the validated
        // routing fields below always win — a caller cannot override the
        // recipients/subject/threading headers via `channelMetadata`.
        ...input.channelMetadata ?? {},
        to: input.to,
        cc: input.cc ?? [],
        bcc: input.bcc ?? [],
        subject: input.subject,
        inReplyTo: input.inReplyTo ?? null,
        references: input.references ?? []
      },
      tenantId,
      organizationId
    });
    em.persist(channelLink);
    await em.flush();
  });
  const queue = getCommunicationChannelsQueue(COMMUNICATION_CHANNELS_QUEUES.outbound);
  const deliveryJob = {
    messageId,
    scope: dscope,
    attempt: 1
  };
  await queue.enqueue(deliveryJob);
  return {
    ok: true,
    messageId,
    threadId: messageThreadId,
    channelId: channel.id,
    providerKey: channel.providerKey
  };
}
export {
  sendAsUser
};
//# sourceMappingURL=send-as-user.js.map
