import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { sanitizeChannelHtml } from "../lib/sanitize-channel-html.js";
import {
  CommunicationChannel,
  ExternalConversation,
  MessageChannelLink,
  MessageReaction
} from "./entities.js";
function ctxEm(ctx) {
  return ctx.em;
}
function sanitizeEmailPayloadHtml(contentType, payload) {
  if (!contentType || !contentType.startsWith("email/")) return null;
  const html = payload?.html;
  if (typeof html !== "string" || html.length === 0) return null;
  return sanitizeChannelHtml(html);
}
const messageChannelEnricher = {
  id: "communication_channels.message-channel",
  targetEntity: "messages.message",
  features: ["communication_channels.view"],
  priority: 30,
  timeout: 2e3,
  fallback: { _channel: null, _channelPayload: null, _channelContact: null },
  critical: false,
  async enrichOne(record, ctx) {
    const [out] = await this.enrichMany([record], ctx);
    return out;
  },
  async enrichMany(records, ctx) {
    if (records.length === 0) return records;
    const messageIds = records.map((r) => r.id);
    const em = ctxEm(ctx);
    const tenantId = ctx.tenantId;
    const organizationId = ctx.organizationId ?? null;
    const dscope = { tenantId, organizationId };
    const userId = typeof ctx.userId === "string" ? ctx.userId : null;
    if (!userId) {
      return records.map((record) => ({
        ...record,
        _channel: null,
        _channelPayload: null,
        _channelContact: null
      }));
    }
    const db = em.getKysely();
    let participantQuery = db.selectFrom("messages as m").leftJoin("message_recipients as r", (join) => join.onRef("m.id", "=", "r.message_id").on("r.recipient_user_id", "=", userId).on("r.deleted_at", "is", null)).select("m.id").distinct().where("m.id", "in", messageIds).where("m.tenant_id", "=", tenantId).where("m.deleted_at", "is", null).where((eb) => eb.or([
      eb("m.sender_user_id", "=", userId),
      eb("r.message_id", "is not", null)
    ]));
    participantQuery = organizationId !== null ? participantQuery.where("m.organization_id", "=", organizationId) : participantQuery.where("m.organization_id", "is", null);
    const participantRows = await participantQuery.execute();
    const participantMessageIds = participantRows.map((row) => row.id);
    const participantMessageIdSet = new Set(participantMessageIds);
    const links = await findWithDecryption(
      em,
      MessageChannelLink,
      {
        messageId: { $in: participantMessageIds },
        tenantId,
        organizationId
      },
      void 0,
      dscope
    );
    const linksByMessage = /* @__PURE__ */ new Map();
    for (const link of links) linksByMessage.set(link.messageId, link);
    const conversationIds = Array.from(
      new Set(
        links.map((l) => l.externalConversationId).filter((id) => typeof id === "string" && id.length > 0)
      )
    );
    const conversationsById = /* @__PURE__ */ new Map();
    if (conversationIds.length > 0) {
      const conversations = await findWithDecryption(
        em,
        ExternalConversation,
        { id: { $in: conversationIds }, tenantId, organizationId },
        void 0,
        dscope
      );
      for (const conversation of conversations) {
        conversationsById.set(conversation.id, conversation);
      }
    }
    const channelIds = Array.from(
      new Set(
        Array.from(conversationsById.values()).map((conversation) => conversation.channelId).filter((id) => typeof id === "string" && id.length > 0)
      )
    );
    const channelsById = /* @__PURE__ */ new Map();
    if (channelIds.length > 0) {
      const channels = await findWithDecryption(
        em,
        CommunicationChannel,
        { id: { $in: channelIds }, tenantId, organizationId, deletedAt: null },
        void 0,
        dscope
      );
      for (const channel of channels) channelsById.set(channel.id, channel);
    }
    const channelByMessageId = /* @__PURE__ */ new Map();
    for (const link of links) {
      if (!link.externalConversationId) continue;
      const conversation = conversationsById.get(link.externalConversationId);
      if (!conversation) continue;
      const channel = conversation.channelId ? channelsById.get(conversation.channelId) : void 0;
      if (channel) channelByMessageId.set(link.messageId, channel);
    }
    return records.map((r) => {
      if (!participantMessageIdSet.has(r.id)) {
        return { ...r, _channel: null, _channelPayload: null, _channelContact: null };
      }
      const link = linksByMessage.get(r.id);
      if (!link) {
        return { ...r, _channel: null, _channelPayload: null, _channelContact: null };
      }
      const channel = channelByMessageId.get(r.id);
      const channelEnrichment = {
        providerKey: link.providerKey,
        channelType: link.channelType,
        direction: link.direction,
        deliveryStatus: link.deliveryStatus ?? null,
        capabilities: channel?.capabilities ?? null
      };
      const payloadEnrichment = {
        channelContentType: link.channelContentType ?? null,
        channelPayload: link.channelPayload ?? null,
        interactiveState: link.interactiveState ?? null,
        channelMetadata: link.channelMetadata ?? null,
        sanitizedHtml: sanitizeEmailPayloadHtml(link.channelContentType, link.channelPayload)
      };
      const conversation = link.externalConversationId ? conversationsById.get(link.externalConversationId) : void 0;
      const contactEnrichment = conversation ? {
        contactPersonId: conversation.contactPersonId ?? null,
        assignedUserId: conversation.assignedUserId ?? null,
        subject: conversation.subject ?? null
      } : null;
      return {
        ...r,
        _channel: channelEnrichment,
        _channelPayload: payloadEnrichment,
        _channelContact: contactEnrichment
      };
    });
  }
};
const messageReactionsEnricher = {
  id: "communication_channels.message-reactions",
  targetEntity: "messages.message",
  features: ["communication_channels.view"],
  priority: 25,
  timeout: 1500,
  fallback: { _reactions: [] },
  critical: false,
  async enrichOne(record, ctx) {
    const [out] = await this.enrichMany([record], ctx);
    return out;
  },
  async enrichMany(records, ctx) {
    if (records.length === 0) return records;
    const messageIds = records.map((r) => r.id);
    const em = ctxEm(ctx);
    const dscope = { tenantId: ctx.tenantId, organizationId: ctx.organizationId ?? null };
    const reactions = await findWithDecryption(
      em,
      MessageReaction,
      {
        messageId: { $in: messageIds },
        tenantId: ctx.tenantId,
        organizationId: ctx.organizationId ?? null
      },
      void 0,
      dscope
    );
    const byMessage = /* @__PURE__ */ new Map();
    for (const reaction of reactions) {
      const existing = byMessage.get(reaction.messageId) ?? [];
      existing.push(reaction);
      byMessage.set(reaction.messageId, existing);
    }
    return records.map((r) => {
      const rows = byMessage.get(r.id) ?? [];
      const grouped = groupReactions(rows, ctx.userId);
      return { ...r, _reactions: grouped };
    });
  }
};
function groupReactions(rows, currentUserId) {
  const map = /* @__PURE__ */ new Map();
  for (const row of rows) {
    const key = row.emoji;
    if (!map.has(key)) {
      map.set(key, { emoji: key, count: 0, users: [], reactedByMe: false, myReactionId: null });
    }
    const group = map.get(key);
    group.count += 1;
    group.users.push({
      userId: row.reactedByUserId ?? null,
      externalId: row.reactedByExternalId ?? null,
      displayName: row.reactedByDisplayName ?? null,
      providerKey: row.providerKey ?? null
    });
    if (row.reactedByUserId && row.reactedByUserId === currentUserId) {
      group.reactedByMe = true;
      if (!group.myReactionId) group.myReactionId = row.id ?? null;
    }
  }
  return Array.from(map.values()).sort((a, b) => b.count - a.count);
}
const enrichers = [
  messageChannelEnricher,
  messageReactionsEnricher
];
var enrichers_default = enrichers;
export {
  enrichers_default as default,
  enrichers
};
//# sourceMappingURL=enrichers.js.map
