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 { Message } from "../../messages/data/entities.js";
import {
  CommunicationChannel,
  ChannelThreadMapping,
  MessageChannelLink,
  MessageReaction
} from "../data/entities.js";
import { COMMUNICATION_CHANNELS_QUEUES, getCommunicationChannelsQueue } from "../lib/queue.js";
import {
  allowsMultipleReactionsPerUser
} from "../lib/reaction-semantics.js";
import { isUniqueViolation } from "../lib/pg-errors.js";
const toggleOutboundReactionSchema = z.object({
  messageId: z.string().uuid(),
  emoji: z.string().min(1).max(64),
  action: z.enum(["add", "remove"]),
  /** Reaction id (required for remove only). */
  reactionId: z.string().uuid().optional(),
  reactedByUserId: z.string().uuid(),
  scope: z.object({
    tenantId: z.string().uuid(),
    organizationId: z.string().uuid().nullable()
  })
});
const COMMUNICATION_CHANNELS_TOGGLE_OUTBOUND_REACTION_COMMAND_ID = "communication_channels.reaction.toggle_outbound";
const toggleOutboundReactionCommand = {
  id: COMMUNICATION_CHANNELS_TOGGLE_OUTBOUND_REACTION_COMMAND_ID,
  async execute(rawInput, ctx) {
    const input = toggleOutboundReactionSchema.parse(rawInput);
    const em = ctx.container.resolve("em").fork();
    const dscope = {
      tenantId: input.scope.tenantId,
      organizationId: input.scope.organizationId ?? null
    };
    const message = await findOneWithDecryption(
      em,
      Message,
      {
        id: input.messageId,
        tenantId: input.scope.tenantId,
        organizationId: input.scope.organizationId ?? null,
        deletedAt: null
      },
      void 0,
      dscope
    );
    if (!message) {
      return { status: "no_channel_link", reason: "message not found" };
    }
    const channelLink = await findOneWithDecryption(
      em,
      MessageChannelLink,
      {
        messageId: message.id,
        tenantId: input.scope.tenantId,
        organizationId: input.scope.organizationId ?? null
      },
      void 0,
      dscope
    );
    if (!channelLink) {
      return { status: "no_channel_link", reason: "message is not channel-linked" };
    }
    const mapping = await findOneWithDecryption(
      em,
      ChannelThreadMapping,
      {
        messageThreadId: message.threadId ?? message.id,
        tenantId: input.scope.tenantId,
        organizationId: input.scope.organizationId ?? null
      },
      void 0,
      dscope
    );
    if (!mapping) {
      return { status: "no_channel_link", reason: "no thread mapping for channel resolution" };
    }
    const resolvedChannel = await findOneWithDecryption(
      em,
      CommunicationChannel,
      {
        id: mapping.channelId,
        tenantId: input.scope.tenantId,
        organizationId: input.scope.organizationId ?? null,
        deletedAt: null
      },
      void 0,
      dscope
    );
    if (!resolvedChannel) {
      return { status: "no_channel_link", reason: "channel not resolved" };
    }
    if (resolvedChannel.userId != null && resolvedChannel.userId !== input.reactedByUserId) {
      return { status: "not_owner", reason: "channel is owned by another user" };
    }
    const capabilities = resolvedChannel.capabilities ?? null;
    if (input.action === "add") {
      let replaced = 0;
      if (!allowsMultipleReactionsPerUser(capabilities)) {
        const prior = await findWithDecryption(
          em,
          MessageReaction,
          {
            messageId: message.id,
            reactedByUserId: input.reactedByUserId,
            tenantId: input.scope.tenantId,
            organizationId: input.scope.organizationId ?? null
          },
          void 0,
          dscope
        );
        replaced = prior.length;
        for (const row of prior) em.remove(row);
        if (replaced > 0) await em.flush();
      }
      let reaction2;
      try {
        reaction2 = em.create(MessageReaction, {
          messageId: message.id,
          emoji: input.emoji,
          reactedByUserId: input.reactedByUserId,
          reactedByExternalId: null,
          providerKey: channelLink.providerKey,
          tenantId: input.scope.tenantId,
          organizationId: input.scope.organizationId ?? null
        });
        em.persist(reaction2);
        await em.flush();
      } catch (err) {
        if (isUniqueViolation(err)) {
          return { status: "noop", reason: "duplicate reaction from same user with same emoji" };
        }
        throw err;
      }
      let enqueued2 = false;
      if (typeof resolvedChannel.id === "string") {
        const job = {
          kind: "outbound_send",
          providerKey: channelLink.providerKey,
          channelId: resolvedChannel.id,
          messageId: message.id,
          reactionId: reaction2.id,
          emoji: input.emoji,
          conversationId: channelLink.channelMetadata?.["thread_id"],
          scope: {
            tenantId: input.scope.tenantId,
            organizationId: input.scope.organizationId
          },
          attempt: 1
        };
        const queue = getCommunicationChannelsQueue(COMMUNICATION_CHANNELS_QUEUES.reactions);
        await queue.enqueue(job);
        enqueued2 = true;
      }
      await emitCommunicationChannelsEvent(
        "communication_channels.reaction.added",
        {
          reactionId: reaction2.id,
          messageId: message.id,
          channelLinkId: channelLink.id,
          channelId: resolvedChannel.id,
          providerKey: channelLink.providerKey,
          channelType: channelLink.channelType,
          emoji: input.emoji,
          reactedByUserId: input.reactedByUserId,
          allowsMultiplePerUser: allowsMultipleReactionsPerUser(capabilities),
          tenantId: input.scope.tenantId,
          organizationId: input.scope.organizationId ?? null
        },
        { persistent: true }
      );
      return {
        status: "added",
        reactionId: reaction2.id,
        messageId: message.id,
        emoji: input.emoji,
        enqueued: enqueued2,
        replaced
      };
    }
    if (!input.reactionId) {
      return { status: "noop", reason: "reactionId required for remove" };
    }
    const reaction = await findOneWithDecryption(
      em,
      MessageReaction,
      {
        id: input.reactionId,
        messageId: message.id,
        tenantId: input.scope.tenantId,
        organizationId: input.scope.organizationId ?? null
      },
      void 0,
      dscope
    );
    if (!reaction) {
      return { status: "noop", reason: "reaction not found" };
    }
    if (reaction.reactedByUserId !== input.reactedByUserId) {
      return { status: "noop", reason: "reaction not owned by current user" };
    }
    const externalReactionId = reaction.externalReactionId ?? null;
    em.remove(reaction);
    await em.flush();
    let enqueued = false;
    if (typeof resolvedChannel.id === "string") {
      const job = {
        kind: "outbound_remove",
        providerKey: channelLink.providerKey,
        channelId: resolvedChannel.id,
        messageId: message.id,
        emoji: input.emoji,
        externalReactionId,
        conversationId: channelLink.channelMetadata?.["thread_id"],
        scope: {
          tenantId: input.scope.tenantId,
          organizationId: input.scope.organizationId
        },
        attempt: 1
      };
      const queue = getCommunicationChannelsQueue(COMMUNICATION_CHANNELS_QUEUES.reactions);
      await queue.enqueue(job);
      enqueued = true;
    }
    await emitCommunicationChannelsEvent(
      "communication_channels.reaction.removed",
      {
        messageId: message.id,
        channelLinkId: channelLink.id,
        channelId: resolvedChannel.id,
        providerKey: channelLink.providerKey,
        channelType: channelLink.channelType,
        emoji: input.emoji,
        reactedByUserId: input.reactedByUserId,
        deletedCount: 1,
        tenantId: input.scope.tenantId,
        organizationId: input.scope.organizationId ?? null
      },
      { persistent: true }
    );
    return {
      status: "removed",
      messageId: message.id,
      emoji: input.emoji,
      enqueued,
      deleted: 1
    };
  }
};
registerCommand(toggleOutboundReactionCommand);
var toggle_outbound_reaction_default = toggleOutboundReactionCommand;
export {
  COMMUNICATION_CHANNELS_TOGGLE_OUTBOUND_REACTION_COMMAND_ID,
  toggle_outbound_reaction_default as default
};
//# sourceMappingURL=toggle-outbound-reaction.js.map
