import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import {
  COMMUNICATION_CHANNELS_PROCESS_INBOUND_REACTION_COMMAND_ID
} from "../commands/process-inbound-reaction.js";
import { COMMUNICATION_CHANNELS_QUEUES, getCommunicationChannelsQueue } from "../lib/queue.js";
import { classifyOutboundError, computeBackoffMs } from "../lib/error-classification.js";
import { CommunicationChannel } from "../data/entities.js";
import {
  REACTION_PROCESSOR_MAX_ATTEMPTS
} from "../lib/reaction-processor-types.js";
import { refreshCredentialsIfNeeded } from "../lib/credential-refresh.js";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("communication_channels").child({ component: "reaction-processor" });
const metadata = {
  queue: COMMUNICATION_CHANNELS_QUEUES.reactions,
  id: "communication_channels:reaction-processor",
  concurrency: 10
};
async function handle(job, ctx) {
  switch (job.payload.kind) {
    case "inbound":
      await handleInbound(job.payload, ctx);
      return;
    case "outbound_send":
      await handleOutboundSend(job.payload, ctx);
      return;
    case "outbound_remove":
      await handleOutboundRemove(job.payload, ctx);
      return;
    default: {
      const exhaustive = job.payload;
      throw new Error(`Unknown reaction job kind: ${JSON.stringify(exhaustive)}`);
    }
  }
}
async function handleInbound(payload, ctx) {
  const commandBus = ctx.resolve("commandBus");
  const containerProxy = { resolve: ctx.resolve.bind(ctx) };
  const commandCtx = {
    container: containerProxy,
    auth: null,
    organizationScope: null,
    selectedOrganizationId: payload.scope.organizationId ?? null,
    organizationIds: payload.scope.organizationId ? [payload.scope.organizationId] : null
  };
  const input = {
    channelId: payload.channelId,
    providerKey: payload.providerKey,
    channelType: payload.channelType,
    scope: payload.scope,
    event: payload.event
  };
  await commandBus.execute(
    COMMUNICATION_CHANNELS_PROCESS_INBOUND_REACTION_COMMAND_ID,
    { input, ctx: commandCtx }
  );
}
async function handleOutboundSend(payload, ctx) {
  const result = await callAdapterOutbound(payload, ctx, "send");
  await maybeRetry(result, payload, ctx);
}
async function handleOutboundRemove(payload, ctx) {
  const result = await callAdapterOutbound(payload, ctx, "remove");
  await maybeRetry(result, payload, ctx);
}
async function callAdapterOutbound(payload, ctx, action) {
  const adapterRegistry = ctx.resolve("channelAdapterRegistry");
  const adapter = adapterRegistry?.get(payload.providerKey);
  if (!adapter) {
    return { status: "no_adapter", message: `No adapter for provider '${payload.providerKey}'` };
  }
  const em = ctx.resolve("em").fork();
  const channel = await findOneWithDecryption(
    em,
    CommunicationChannel,
    {
      id: payload.channelId,
      tenantId: payload.scope.tenantId,
      organizationId: payload.scope.organizationId ?? null,
      deletedAt: null
    },
    void 0,
    payload.scope
  );
  if (!channel) {
    return { status: "no_adapter", message: `Channel ${payload.channelId} not found` };
  }
  if (!channel.isActive) {
    return { status: "channel_inactive", message: `Channel ${payload.channelId} is inactive` };
  }
  let credentials = {};
  let credentialsService = null;
  try {
    credentialsService = ctx.resolve("integrationCredentialsService");
  } catch {
    credentialsService = null;
  }
  const credentialsScope = {
    tenantId: payload.scope.tenantId,
    organizationId: payload.scope.organizationId ?? payload.scope.tenantId,
    userId: channel.userId ?? null
  };
  if (channel.credentialsRef && credentialsService) {
    try {
      credentials = await credentialsService.resolve(`channel_${channel.providerKey}`, credentialsScope) ?? {};
    } catch {
      credentials = {};
    }
  }
  const refreshed = await refreshCredentialsIfNeeded(
    {
      adapter,
      channelId: channel.id,
      credentials,
      scope: credentialsScope
    },
    { credentialsService }
  );
  credentials = refreshed.credentials;
  try {
    if (action === "send") {
      if (typeof adapter.sendReaction !== "function") {
        return { status: "no_adapter", message: `Adapter '${adapter.providerKey}' has no sendReaction` };
      }
      const sendPayload = payload;
      await adapter.sendReaction({
        externalMessageId: sendPayload.messageId,
        // platform message id; the adapter maps to provider id via its own channel-link lookup if needed
        conversationId: sendPayload.conversationId ?? "",
        emoji: sendPayload.emoji,
        credentials,
        scope: {
          tenantId: payload.scope.tenantId,
          organizationId: payload.scope.organizationId ?? payload.scope.tenantId
        }
      });
    } else {
      if (typeof adapter.removeReaction !== "function") {
        return { status: "no_adapter", message: `Adapter '${adapter.providerKey}' has no removeReaction` };
      }
      const removePayload = payload;
      await adapter.removeReaction({
        externalMessageId: removePayload.externalReactionId ?? removePayload.messageId,
        conversationId: removePayload.conversationId ?? "",
        emoji: removePayload.emoji,
        credentials,
        scope: {
          tenantId: payload.scope.tenantId,
          organizationId: payload.scope.organizationId ?? payload.scope.tenantId
        }
      });
    }
    return { status: "ok" };
  } catch (err) {
    const classification = classifyOutboundError(err);
    return {
      status: "failed",
      transient: classification.transient,
      message: classification.message
    };
  }
}
async function maybeRetry(result, payload, ctx) {
  if (result.status === "ok") return;
  if (result.status === "no_adapter" || result.status === "channel_inactive") {
    logger.error("reaction processing failed", { status: result.status, reason: result.message });
    return;
  }
  if (!result.transient) {
    logger.error("permanent reaction processing failure", { reason: result.message });
    return;
  }
  const attempt = payload.attempt ?? 1;
  if (attempt >= REACTION_PROCESSOR_MAX_ATTEMPTS) {
    logger.error("giving up on reaction processing", { attempt, reason: result.message });
    return;
  }
  const next = { ...payload, attempt: attempt + 1 };
  const queue = getCommunicationChannelsQueue(COMMUNICATION_CHANNELS_QUEUES.reactions);
  await queue.enqueue(next, {
    delayMs: computeBackoffMs(attempt)
  });
}
export {
  handle as default,
  metadata
};
//# sourceMappingURL=reaction-processor.js.map
