import { z } from "zod";
import { registerCommand } from "@open-mercato/shared/lib/commands";
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { emitCommunicationChannelsEvent } from "../events.js";
import {
  CommunicationChannel,
  ExternalMessage,
  MessageChannelLink,
  MessageReaction
} from "../data/entities.js";
import { inboundReactionEventSchema } from "../data/validators.js";
import {
  allowsMultipleReactionsPerUser,
  resolveInboundAddMutation
} from "../lib/reaction-semantics.js";
import { isUniqueViolation } from "../lib/pg-errors.js";
const processInboundReactionInputSchema = z.object({
  channelId: z.string().uuid(),
  providerKey: z.string().min(1),
  channelType: z.string().min(1),
  scope: z.object({
    tenantId: z.string().uuid(),
    organizationId: z.string().uuid().nullable()
  }),
  event: inboundReactionEventSchema
});
const COMMUNICATION_CHANNELS_PROCESS_INBOUND_REACTION_COMMAND_ID = "communication_channels.reaction.process_inbound";
const processInboundReactionCommand = {
  id: COMMUNICATION_CHANNELS_PROCESS_INBOUND_REACTION_COMMAND_ID,
  async execute(rawInput, ctx) {
    const input = processInboundReactionInputSchema.parse(rawInput);
    const em = ctx.container.resolve("em").fork();
    const dscope = {
      tenantId: input.scope.tenantId,
      organizationId: input.scope.organizationId ?? null
    };
    const externalMessage = await findOneWithDecryption(
      em,
      ExternalMessage,
      {
        channelId: input.channelId,
        externalMessageId: input.event.externalMessageId,
        tenantId: input.scope.tenantId,
        organizationId: input.scope.organizationId ?? null
      },
      void 0,
      dscope
    );
    if (!externalMessage) {
      return { status: "no_message_link" };
    }
    const channelLink = await findOneWithDecryption(
      em,
      MessageChannelLink,
      {
        externalMessageId: externalMessage.id,
        tenantId: input.scope.tenantId,
        organizationId: input.scope.organizationId ?? null
      },
      void 0,
      dscope
    );
    if (!channelLink) {
      return { status: "no_message_link" };
    }
    const messageId = channelLink.messageId;
    const channel = await findOneWithDecryption(
      em,
      CommunicationChannel,
      {
        id: input.channelId,
        tenantId: input.scope.tenantId,
        organizationId: input.scope.organizationId ?? null,
        deletedAt: null
      },
      void 0,
      dscope
    );
    const capabilities = channel?.capabilities ?? null;
    if (input.event.action === "added") {
      const mutation = resolveInboundAddMutation(capabilities);
      let replaced = 0;
      if (mutation === "replace") {
        const existing = await findWithDecryption(
          em,
          MessageReaction,
          {
            messageId,
            reactedByExternalId: input.event.userIdentifier,
            tenantId: input.scope.tenantId,
            organizationId: input.scope.organizationId ?? null
          },
          void 0,
          dscope
        );
        replaced = existing.length;
        for (const row of existing) em.remove(row);
        await em.flush();
      }
      let reaction;
      try {
        reaction = em.create(MessageReaction, {
          messageId,
          emoji: input.event.emoji,
          reactedByUserId: null,
          reactedByExternalId: input.event.userIdentifier,
          reactedByDisplayName: input.event.userDisplayName ?? null,
          providerKey: input.providerKey,
          externalReactionId: input.event.externalReactionId ?? null,
          tenantId: input.scope.tenantId,
          organizationId: input.scope.organizationId ?? null
        });
        em.persist(reaction);
        await em.flush();
      } catch (err) {
        if (isUniqueViolation(err)) {
          return { status: "noop" };
        }
        throw err;
      }
      await emitCommunicationChannelsEvent(
        "communication_channels.reaction.added",
        {
          reactionId: reaction.id,
          messageId,
          channelLinkId: channelLink.id,
          channelId: input.channelId,
          providerKey: input.providerKey,
          channelType: input.channelType,
          emoji: input.event.emoji,
          reactedByExternalId: input.event.userIdentifier,
          reactedByDisplayName: input.event.userDisplayName ?? null,
          allowsMultiplePerUser: allowsMultipleReactionsPerUser(capabilities),
          tenantId: input.scope.tenantId,
          organizationId: input.scope.organizationId ?? null
        },
        { persistent: true }
      );
      return {
        status: "added",
        reactionId: reaction.id,
        messageId,
        emoji: input.event.emoji,
        replaced
      };
    }
    const filter = {
      messageId,
      reactedByExternalId: input.event.userIdentifier,
      emoji: input.event.emoji,
      tenantId: input.scope.tenantId,
      organizationId: input.scope.organizationId ?? null
    };
    if (input.event.externalReactionId) {
      filter.externalReactionId = input.event.externalReactionId;
    }
    const toDelete = await findWithDecryption(
      em,
      MessageReaction,
      filter,
      void 0,
      dscope
    );
    for (const row of toDelete) em.remove(row);
    await em.flush();
    if (toDelete.length > 0) {
      await emitCommunicationChannelsEvent(
        "communication_channels.reaction.removed",
        {
          messageId,
          channelLinkId: channelLink.id,
          channelId: input.channelId,
          providerKey: input.providerKey,
          channelType: input.channelType,
          emoji: input.event.emoji,
          reactedByExternalId: input.event.userIdentifier,
          deletedCount: toDelete.length,
          tenantId: input.scope.tenantId,
          organizationId: input.scope.organizationId ?? null
        },
        { persistent: true }
      );
    }
    return {
      status: "removed",
      messageId,
      emoji: input.event.emoji,
      deleted: toDelete.length
    };
  }
};
registerCommand(processInboundReactionCommand);
var process_inbound_reaction_default = processInboundReactionCommand;
export {
  COMMUNICATION_CHANNELS_PROCESS_INBOUND_REACTION_COMMAND_ID,
  process_inbound_reaction_default as default
};
//# sourceMappingURL=process-inbound-reaction.js.map
