import { z } from "zod";
import { registerCommand } from "@open-mercato/shared/lib/commands";
import {
  parseWithCustomFields,
  setCustomFieldsIfAny,
  emitCrudSideEffects,
  emitCrudUndoSideEffects,
  requireId,
  normalizeAuthorUserId
} from "@open-mercato/shared/lib/commands/helpers";
import { DefaultDataEngine } from "@open-mercato/shared/lib/data/engine";
import { CustomerInteraction } from "../data/entities.js";
import {
  interactionCreateSchema,
  interactionUpdateSchema,
  interactionCompleteSchema,
  interactionCancelSchema
} from "../data/validators.js";
import {
  ensureOrganizationScope,
  ensureTenantScope,
  requireTimelineParentEntity,
  extractUndoPayload,
  emitQueryIndexUpsertEvents,
  requireDealInScope,
  resolveParentResourceKind
} from "./shared.js";
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
import { resolveRedoSnapshot } from "@open-mercato/shared/lib/commands/redo";
import {
  loadCustomFieldSnapshot,
  buildCustomFieldResetMap
} from "@open-mercato/shared/lib/commands/customFieldSnapshots";
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
import { enforceRecordGoneIsConflict, enforceCommandOptimisticLockWithGuards } from "@open-mercato/shared/lib/crud/optimistic-lock-command";
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { recomputeNextInteraction } from "../lib/interactionProjection.js";
import {
  INTERACTION_STATUS_CANCELED,
  INTERACTION_STATUS_COMPLETED,
  INTERACTION_STATUS_PLANNED
} from "../lib/interactionStatus.js";
import { canChangeEmailVisibility } from "../lib/visibilityFilter.js";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("customers");
const INTERACTION_ENTITY_ID = "customers:customer_interaction";
const interactionCrudIndexer = {
  entityType: "customers:customer_interaction"
};
const interactionCrudEvents = {
  module: "customers",
  entity: "interaction",
  persistent: true,
  buildPayload: (ctx) => ({
    id: ctx.identifiers.id,
    organizationId: ctx.identifiers.organizationId,
    tenantId: ctx.identifiers.tenantId,
    entityId: ctx.entity && typeof ctx.entity === "object" && "entity" in ctx.entity ? (() => {
      const entityRef = ctx.entity.entity;
      return typeof entityRef === "string" ? entityRef : entityRef?.id ?? null;
    })() : null,
    interactionType: ctx.entity && typeof ctx.entity === "object" && "interactionType" in ctx.entity ? ctx.entity.interactionType : null,
    status: ctx.entity && typeof ctx.entity === "object" && "status" in ctx.entity ? ctx.entity.status : null,
    source: ctx.entity && typeof ctx.entity === "object" && "source" in ctx.entity ? ctx.entity.source ?? null : null,
    ...ctx.syncOrigin ? { syncOrigin: ctx.syncOrigin } : {}
  })
};
async function loadInteractionSnapshot(em, id) {
  const interaction = await findOneWithDecryption(em, CustomerInteraction, { id }, { populate: ["entity"] });
  if (!interaction) return null;
  const custom = await loadCustomFieldSnapshot(em, {
    entityId: INTERACTION_ENTITY_ID,
    recordId: interaction.id,
    tenantId: interaction.tenantId,
    organizationId: interaction.organizationId
  });
  const entityRef = interaction.entity;
  const entityKind = typeof entityRef === "object" && entityRef !== null && "kind" in entityRef ? entityRef.kind : null;
  return {
    interaction: {
      id: interaction.id,
      organizationId: interaction.organizationId,
      tenantId: interaction.tenantId,
      entityId: typeof entityRef === "string" ? entityRef : entityRef.id,
      entityKind,
      dealId: interaction.dealId ?? null,
      interactionType: interaction.interactionType,
      title: interaction.title ?? null,
      body: interaction.body ?? null,
      status: interaction.status,
      scheduledAt: interaction.scheduledAt ?? null,
      occurredAt: interaction.occurredAt ?? null,
      priority: interaction.priority ?? null,
      authorUserId: interaction.authorUserId ?? null,
      ownerUserId: interaction.ownerUserId ?? null,
      appearanceIcon: interaction.appearanceIcon ?? null,
      appearanceColor: interaction.appearanceColor ?? null,
      source: interaction.source ?? null,
      durationMinutes: interaction.durationMinutes ?? null,
      location: interaction.location ?? null,
      allDay: interaction.allDay ?? null,
      recurrenceRule: interaction.recurrenceRule ?? null,
      recurrenceEnd: interaction.recurrenceEnd ?? null,
      participants: interaction.participants ?? null,
      reminderMinutes: interaction.reminderMinutes ?? null,
      visibility: interaction.visibility ?? null,
      linkedEntities: interaction.linkedEntities ?? null,
      guestPermissions: interaction.guestPermissions ?? null
    },
    custom
  };
}
async function setInteractionCustomFields(dataEngine, interactionId, organizationId, tenantId, values) {
  if (!values || !Object.keys(values).length) return;
  await setCustomFieldsIfAny({
    dataEngine,
    entityId: INTERACTION_ENTITY_ID,
    recordId: interactionId,
    organizationId,
    tenantId,
    values,
    notify: false
  });
}
async function emitLifecycleEvent(ctx, eventId, payload) {
  let bus = null;
  try {
    bus = ctx.container.resolve("eventBus");
  } catch (err) {
    logger.warn("eventBus resolve failed; skipping emit", { component: "commands.interactions", eventId, err });
    bus = null;
  }
  if (!bus) return;
  await bus.emitEvent(eventId, payload, { persistent: true }).catch((err) => {
    logger.warn("Event emit failed", { component: "commands.interactions", eventId, err });
    return void 0;
  });
}
async function emitInteractionRevertedEvent(ctx, interaction) {
  await emitLifecycleEvent(ctx, "customers.interaction.reverted", {
    id: interaction.id,
    organizationId: interaction.organizationId,
    tenantId: interaction.tenantId,
    entityId: interaction.entityId,
    interactionType: interaction.interactionType,
    source: interaction.source ?? null,
    status: interaction.status,
    occurredAt: interaction.occurredAt?.toISOString() ?? null,
    ...ctx.syncOrigin ? { syncOrigin: ctx.syncOrigin } : {}
  });
}
function createTransactionalDataEngine(ctx, em) {
  const emWithConnection = em;
  if (typeof emWithConnection.getConnection !== "function") {
    return ctx.container.resolve("dataEngine");
  }
  return new DefaultDataEngine(em, ctx.container);
}
async function runInTransaction(em, operation) {
  const supportsBegin = typeof em.begin === "function" && typeof em.commit === "function" && typeof em.rollback === "function";
  if (!supportsBegin) {
    return operation(em);
  }
  await em.begin();
  try {
    const result = await operation(em);
    await em.commit();
    return result;
  } catch (err) {
    try {
      await em.rollback();
    } catch {
    }
    throw err;
  }
}
async function emitNextInteractionUpdatedEvent(ctx, projection, identifiers) {
  await emitQueryIndexUpsertEvents(ctx, [{
    entityType: "customers:customer_entity",
    recordId: projection.entityId,
    organizationId: identifiers.organizationId,
    tenantId: identifiers.tenantId
  }]);
  await emitLifecycleEvent(ctx, "customers.next_interaction.updated", {
    id: projection.entityId,
    entityId: projection.entityId,
    nextInteractionId: projection.nextInteractionId,
    organizationId: identifiers.organizationId,
    tenantId: identifiers.tenantId
  });
}
function buildInteractionGraph(em, values) {
  return em.create(CustomerInteraction, {
    ...values.id ? { id: values.id } : {},
    organizationId: values.organizationId,
    tenantId: values.tenantId,
    entity: values.entity,
    interactionType: values.interactionType,
    title: values.title,
    body: values.body,
    status: values.status,
    scheduledAt: values.scheduledAt,
    occurredAt: values.occurredAt,
    priority: values.priority,
    authorUserId: values.authorUserId,
    ownerUserId: values.ownerUserId,
    dealId: values.dealId,
    source: values.source,
    appearanceIcon: values.appearanceIcon,
    appearanceColor: values.appearanceColor,
    durationMinutes: values.durationMinutes,
    location: values.location,
    allDay: values.allDay,
    recurrenceRule: values.recurrenceRule,
    recurrenceEnd: values.recurrenceEnd,
    participants: values.participants,
    reminderMinutes: values.reminderMinutes,
    visibility: values.visibility,
    linkedEntities: values.linkedEntities,
    guestPermissions: values.guestPermissions,
    createdAt: /* @__PURE__ */ new Date(),
    updatedAt: /* @__PURE__ */ new Date()
  });
}
const createInteractionCommand = {
  id: "customers.interactions.create",
  async execute(rawInput, ctx) {
    const { parsed, custom } = parseWithCustomFields(interactionCreateSchema, rawInput);
    const em = ctx.container.resolve("em").fork();
    const normalizedAuthor = normalizeAuthorUserId(parsed.authorUserId ?? null, ctx.auth);
    const { interaction, entityId, nextInteractionId } = await runInTransaction(em, async (trx) => {
      const entity = await requireTimelineParentEntity(trx, parsed.entityId, { tenantId: parsed.tenantId, organizationId: parsed.organizationId });
      ensureTenantScope(ctx, entity.tenantId);
      ensureOrganizationScope(ctx, entity.organizationId);
      if (parsed.dealId) {
        await requireDealInScope(trx, parsed.dealId, entity.tenantId, entity.organizationId);
      }
      const interaction2 = buildInteractionGraph(trx, {
        ...parsed.id ? { id: parsed.id } : {},
        organizationId: entity.organizationId,
        tenantId: entity.tenantId,
        entity,
        interactionType: parsed.interactionType,
        title: parsed.title ?? null,
        body: parsed.body ?? null,
        status: parsed.status ?? INTERACTION_STATUS_PLANNED,
        scheduledAt: parsed.scheduledAt ?? null,
        occurredAt: parsed.occurredAt ?? null,
        priority: parsed.priority ?? null,
        authorUserId: normalizedAuthor,
        ownerUserId: parsed.ownerUserId ?? null,
        dealId: parsed.dealId ?? null,
        source: parsed.source ?? null,
        appearanceIcon: parsed.appearanceIcon ?? null,
        appearanceColor: parsed.appearanceColor ?? null,
        durationMinutes: parsed.durationMinutes ?? null,
        location: parsed.location ?? null,
        allDay: parsed.allDay ?? null,
        recurrenceRule: parsed.recurrenceRule ?? null,
        recurrenceEnd: parsed.recurrenceEnd ?? null,
        participants: parsed.participants ?? null,
        reminderMinutes: parsed.reminderMinutes ?? null,
        visibility: parsed.visibility ?? null,
        linkedEntities: parsed.linkedEntities ?? null,
        guestPermissions: parsed.guestPermissions ?? null
      });
      trx.persist(interaction2);
      await trx.flush();
      await setInteractionCustomFields(
        createTransactionalDataEngine(ctx, trx),
        interaction2.id,
        entity.organizationId,
        entity.tenantId,
        custom
      );
      const projection = await recomputeNextInteraction(trx, entity.id);
      return {
        interaction: interaction2,
        entityId: entity.id,
        nextInteractionId: projection.nextInteractionId
      };
    });
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "created",
      entity: interaction,
      identifiers: {
        id: interaction.id,
        organizationId: interaction.organizationId,
        tenantId: interaction.tenantId
      },
      syncOrigin: ctx.syncOrigin,
      indexer: interactionCrudIndexer,
      events: interactionCrudEvents
    });
    await emitNextInteractionUpdatedEvent(ctx, { entityId, nextInteractionId }, {
      id: interaction.id,
      organizationId: interaction.organizationId,
      tenantId: interaction.tenantId
    });
    return { interactionId: interaction.id, entityId };
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em").fork();
    return await loadInteractionSnapshot(em, result.interactionId);
  },
  buildLog: async ({ result, snapshots }) => {
    const { translate } = await resolveTranslations();
    const snapshot = snapshots.after;
    return {
      actionLabel: translate("customers.audit.interactions.create", "Create interaction"),
      resourceKind: "customers.interaction",
      resourceId: result.interactionId,
      parentResourceKind: resolveParentResourceKind(snapshot?.interaction?.entityKind),
      parentResourceId: snapshot?.interaction?.entityId ?? null,
      tenantId: snapshot?.interaction.tenantId ?? null,
      organizationId: snapshot?.interaction.organizationId ?? null,
      snapshotAfter: snapshot ?? null,
      payload: {
        undo: {
          after: snapshot
        }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const interactionId = logEntry?.resourceId;
    if (!interactionId) return;
    const em = ctx.container.resolve("em").fork();
    const result = await runInTransaction(em, async (trx) => {
      const record = await findOneWithDecryption(trx, CustomerInteraction, { id: interactionId });
      if (!record) return null;
      const entityId = typeof record.entity === "string" ? record.entity : record.entity.id;
      trx.remove(record);
      await trx.flush();
      const projection = await recomputeNextInteraction(trx, entityId);
      return {
        entityId,
        nextInteractionId: projection.nextInteractionId,
        identifiers: {
          id: record.id,
          organizationId: record.organizationId,
          tenantId: record.tenantId
        }
      };
    });
    if (!result) return;
    await emitNextInteractionUpdatedEvent(ctx, {
      entityId: result.entityId,
      nextInteractionId: result.nextInteractionId
    }, result.identifiers);
  },
  redo: async ({ logEntry, ctx }) => {
    const after = resolveRedoSnapshot(logEntry);
    if (!after) {
      throw new CrudHttpError(400, { error: "[internal] redo snapshot unavailable for interaction create" });
    }
    const em = ctx.container.resolve("em").fork();
    const { interaction, nextInteractionId } = await runInTransaction(em, async (trx) => {
      const entity = await requireTimelineParentEntity(trx, after.interaction.entityId, { tenantId: after.interaction.tenantId, organizationId: after.interaction.organizationId });
      let interaction2 = await findOneWithDecryption(trx, CustomerInteraction, { id: after.interaction.id });
      if (!interaction2) {
        interaction2 = buildInteractionGraph(trx, {
          id: after.interaction.id,
          organizationId: after.interaction.organizationId,
          tenantId: after.interaction.tenantId,
          entity,
          interactionType: after.interaction.interactionType,
          title: after.interaction.title,
          body: after.interaction.body,
          status: after.interaction.status,
          scheduledAt: after.interaction.scheduledAt,
          occurredAt: after.interaction.occurredAt,
          priority: after.interaction.priority,
          authorUserId: after.interaction.authorUserId,
          ownerUserId: after.interaction.ownerUserId,
          dealId: after.interaction.dealId,
          source: after.interaction.source,
          appearanceIcon: after.interaction.appearanceIcon,
          appearanceColor: after.interaction.appearanceColor,
          durationMinutes: after.interaction.durationMinutes,
          location: after.interaction.location,
          allDay: after.interaction.allDay,
          recurrenceRule: after.interaction.recurrenceRule,
          recurrenceEnd: after.interaction.recurrenceEnd,
          participants: after.interaction.participants,
          reminderMinutes: after.interaction.reminderMinutes,
          visibility: after.interaction.visibility,
          linkedEntities: after.interaction.linkedEntities,
          guestPermissions: after.interaction.guestPermissions
        });
        trx.persist(interaction2);
      } else {
        interaction2.deletedAt = null;
        interaction2.entity = entity;
        interaction2.interactionType = after.interaction.interactionType;
        interaction2.title = after.interaction.title;
        interaction2.body = after.interaction.body;
        interaction2.status = after.interaction.status;
        interaction2.scheduledAt = after.interaction.scheduledAt;
        interaction2.occurredAt = after.interaction.occurredAt;
        interaction2.priority = after.interaction.priority;
        interaction2.authorUserId = after.interaction.authorUserId;
        interaction2.ownerUserId = after.interaction.ownerUserId;
        interaction2.dealId = after.interaction.dealId;
        interaction2.source = after.interaction.source;
        interaction2.appearanceIcon = after.interaction.appearanceIcon;
        interaction2.appearanceColor = after.interaction.appearanceColor;
        interaction2.durationMinutes = after.interaction.durationMinutes;
        interaction2.location = after.interaction.location;
        interaction2.allDay = after.interaction.allDay;
        interaction2.recurrenceRule = after.interaction.recurrenceRule;
        interaction2.recurrenceEnd = after.interaction.recurrenceEnd;
        interaction2.participants = after.interaction.participants;
        interaction2.reminderMinutes = after.interaction.reminderMinutes;
        interaction2.visibility = after.interaction.visibility;
        interaction2.linkedEntities = after.interaction.linkedEntities;
        interaction2.guestPermissions = after.interaction.guestPermissions;
      }
      await trx.flush();
      const projection = await recomputeNextInteraction(trx, after.interaction.entityId);
      const restoreValues = buildCustomFieldResetMap(after.custom, void 0);
      if (Object.keys(restoreValues).length) {
        await setCustomFieldsIfAny({
          dataEngine: createTransactionalDataEngine(ctx, trx),
          entityId: INTERACTION_ENTITY_ID,
          recordId: interaction2.id,
          organizationId: interaction2.organizationId,
          tenantId: interaction2.tenantId,
          values: restoreValues,
          notify: false
        });
      }
      return { interaction: interaction2, nextInteractionId: projection.nextInteractionId };
    });
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "created",
      entity: interaction,
      identifiers: {
        id: interaction.id,
        organizationId: interaction.organizationId,
        tenantId: interaction.tenantId
      },
      syncOrigin: ctx.syncOrigin,
      indexer: interactionCrudIndexer,
      events: interactionCrudEvents
    });
    await emitNextInteractionUpdatedEvent(ctx, {
      entityId: after.interaction.entityId,
      nextInteractionId
    }, {
      id: interaction.id,
      organizationId: interaction.organizationId,
      tenantId: interaction.tenantId
    });
    return { interactionId: interaction.id, entityId: after.interaction.entityId };
  }
};
const updateInteractionCommand = {
  id: "customers.interactions.update",
  async prepare(rawInput, ctx) {
    const { parsed } = parseWithCustomFields(interactionUpdateSchema, rawInput);
    const em = ctx.container.resolve("em").fork();
    const snapshot = await loadInteractionSnapshot(em, parsed.id);
    return snapshot ? { before: snapshot } : {};
  },
  async execute(rawInput, ctx) {
    const { parsed, custom } = parseWithCustomFields(interactionUpdateSchema, rawInput);
    const em = ctx.container.resolve("em").fork();
    const { interaction, entityId, nextInteractionId } = await runInTransaction(em, async (trx) => {
      const interaction2 = await findOneWithDecryption(trx, CustomerInteraction, { id: parsed.id, deletedAt: null });
      if (!interaction2) {
        enforceRecordGoneIsConflict({ resourceKind: "customers.interaction", resourceId: parsed.id, request: ctx.request ?? null });
        throw new CrudHttpError(404, { error: "Interaction not found" });
      }
      ensureTenantScope(ctx, interaction2.tenantId);
      ensureOrganizationScope(ctx, interaction2.organizationId);
      await enforceCommandOptimisticLockWithGuards(ctx.container, {
        resourceKind: "customers.interaction",
        resourceId: interaction2.id,
        current: interaction2.updatedAt,
        request: ctx.request ?? null
      });
      if (parsed.visibility !== void 0 && interaction2.interactionType === "email" && (parsed.visibility ?? null) !== (interaction2.visibility ?? null)) {
        const actorUserId = ctx.auth?.sub ?? null;
        if (!canChangeEmailVisibility({
          interactionType: interaction2.interactionType,
          currentVisibility: interaction2.visibility,
          nextVisibility: parsed.visibility,
          authorUserId: interaction2.authorUserId,
          actorUserId,
          // v1 strict owner-only: only the author may flip visibility; no admin
          // bypass (canChangeEmailVisibility ignores caller features in v1).
          userFeatures: void 0
        })) {
          throw new CrudHttpError(404, { error: "Email not found" });
        }
      }
      if (parsed.dealId !== void 0) {
        if (parsed.dealId) {
          await requireDealInScope(trx, parsed.dealId, interaction2.tenantId, interaction2.organizationId);
        }
        interaction2.dealId = parsed.dealId ?? null;
      }
      if (parsed.interactionType !== void 0) interaction2.interactionType = parsed.interactionType;
      if (parsed.title !== void 0) interaction2.title = parsed.title ?? null;
      if (parsed.body !== void 0) interaction2.body = parsed.body ?? null;
      if (parsed.status !== void 0) interaction2.status = parsed.status;
      if (parsed.scheduledAt !== void 0) interaction2.scheduledAt = parsed.scheduledAt ?? null;
      if (parsed.occurredAt !== void 0) interaction2.occurredAt = parsed.occurredAt ?? null;
      if (parsed.priority !== void 0) interaction2.priority = parsed.priority ?? null;
      if (parsed.authorUserId !== void 0) interaction2.authorUserId = parsed.authorUserId ?? null;
      if (parsed.ownerUserId !== void 0) interaction2.ownerUserId = parsed.ownerUserId ?? null;
      if (parsed.appearanceIcon !== void 0) interaction2.appearanceIcon = parsed.appearanceIcon ?? null;
      if (parsed.appearanceColor !== void 0) interaction2.appearanceColor = parsed.appearanceColor ?? null;
      if (parsed.pinned !== void 0) interaction2.pinned = parsed.pinned;
      if (parsed.durationMinutes !== void 0) interaction2.durationMinutes = parsed.durationMinutes ?? null;
      if (parsed.location !== void 0) interaction2.location = parsed.location ?? null;
      if (parsed.allDay !== void 0) interaction2.allDay = parsed.allDay ?? null;
      if (parsed.recurrenceRule !== void 0) interaction2.recurrenceRule = parsed.recurrenceRule ?? null;
      if (parsed.recurrenceEnd !== void 0) interaction2.recurrenceEnd = parsed.recurrenceEnd ?? null;
      if (parsed.participants !== void 0) interaction2.participants = parsed.participants ?? null;
      if (parsed.reminderMinutes !== void 0) interaction2.reminderMinutes = parsed.reminderMinutes ?? null;
      if (parsed.visibility !== void 0) interaction2.visibility = parsed.visibility ?? null;
      if (parsed.linkedEntities !== void 0) interaction2.linkedEntities = parsed.linkedEntities ?? null;
      if (parsed.guestPermissions !== void 0) interaction2.guestPermissions = parsed.guestPermissions ?? null;
      await trx.flush();
      const entityId2 = typeof interaction2.entity === "string" ? interaction2.entity : interaction2.entity.id;
      await setInteractionCustomFields(
        createTransactionalDataEngine(ctx, trx),
        interaction2.id,
        interaction2.organizationId,
        interaction2.tenantId,
        custom
      );
      const projection = await recomputeNextInteraction(trx, entityId2);
      return { interaction: interaction2, entityId: entityId2, nextInteractionId: projection.nextInteractionId };
    });
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "updated",
      entity: interaction,
      identifiers: {
        id: interaction.id,
        organizationId: interaction.organizationId,
        tenantId: interaction.tenantId
      },
      syncOrigin: ctx.syncOrigin,
      indexer: interactionCrudIndexer,
      events: interactionCrudEvents
    });
    await emitNextInteractionUpdatedEvent(ctx, { entityId, nextInteractionId }, {
      id: interaction.id,
      organizationId: interaction.organizationId,
      tenantId: interaction.tenantId
    });
    return { interactionId: interaction.id };
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em").fork();
    return await loadInteractionSnapshot(em, result.interactionId);
  },
  buildLog: async ({ snapshots }) => {
    const { translate } = await resolveTranslations();
    const before = snapshots.before;
    if (!before) return null;
    const afterSnapshot = snapshots.after;
    return {
      actionLabel: translate("customers.audit.interactions.update", "Update interaction"),
      resourceKind: "customers.interaction",
      resourceId: before.interaction.id,
      parentResourceKind: resolveParentResourceKind(before.interaction.entityKind),
      parentResourceId: before.interaction.entityId ?? null,
      tenantId: before.interaction.tenantId,
      organizationId: before.interaction.organizationId,
      snapshotBefore: before,
      snapshotAfter: afterSnapshot ?? null,
      payload: {
        undo: {
          before,
          after: afterSnapshot ?? null
        }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry);
    const before = payload?.before;
    if (!before) return;
    const em = ctx.container.resolve("em").fork();
    const { interaction, nextInteractionId } = await runInTransaction(em, async (trx) => {
      let interaction2 = await findOneWithDecryption(trx, CustomerInteraction, { id: before.interaction.id });
      const entity = await requireTimelineParentEntity(trx, before.interaction.entityId, { tenantId: before.interaction.tenantId, organizationId: before.interaction.organizationId });
      if (!interaction2) {
        interaction2 = trx.create(CustomerInteraction, {
          id: before.interaction.id,
          organizationId: before.interaction.organizationId,
          tenantId: before.interaction.tenantId,
          entity,
          interactionType: before.interaction.interactionType,
          title: before.interaction.title,
          body: before.interaction.body,
          status: before.interaction.status,
          scheduledAt: before.interaction.scheduledAt,
          occurredAt: before.interaction.occurredAt,
          priority: before.interaction.priority,
          authorUserId: before.interaction.authorUserId,
          ownerUserId: before.interaction.ownerUserId,
          dealId: before.interaction.dealId,
          source: before.interaction.source,
          appearanceIcon: before.interaction.appearanceIcon,
          appearanceColor: before.interaction.appearanceColor,
          durationMinutes: before.interaction.durationMinutes,
          location: before.interaction.location,
          allDay: before.interaction.allDay,
          recurrenceRule: before.interaction.recurrenceRule,
          recurrenceEnd: before.interaction.recurrenceEnd,
          participants: before.interaction.participants,
          reminderMinutes: before.interaction.reminderMinutes,
          visibility: before.interaction.visibility,
          linkedEntities: before.interaction.linkedEntities,
          guestPermissions: before.interaction.guestPermissions,
          createdAt: /* @__PURE__ */ new Date(),
          updatedAt: /* @__PURE__ */ new Date()
        });
        trx.persist(interaction2);
      } else {
        interaction2.entity = entity;
        interaction2.interactionType = before.interaction.interactionType;
        interaction2.title = before.interaction.title;
        interaction2.body = before.interaction.body;
        interaction2.status = before.interaction.status;
        interaction2.scheduledAt = before.interaction.scheduledAt;
        interaction2.occurredAt = before.interaction.occurredAt;
        interaction2.priority = before.interaction.priority;
        interaction2.authorUserId = before.interaction.authorUserId;
        interaction2.ownerUserId = before.interaction.ownerUserId;
        interaction2.dealId = before.interaction.dealId;
        interaction2.source = before.interaction.source;
        interaction2.appearanceIcon = before.interaction.appearanceIcon;
        interaction2.appearanceColor = before.interaction.appearanceColor;
        interaction2.durationMinutes = before.interaction.durationMinutes;
        interaction2.location = before.interaction.location;
        interaction2.allDay = before.interaction.allDay;
        interaction2.recurrenceRule = before.interaction.recurrenceRule;
        interaction2.recurrenceEnd = before.interaction.recurrenceEnd;
        interaction2.participants = before.interaction.participants;
        interaction2.reminderMinutes = before.interaction.reminderMinutes;
        interaction2.visibility = before.interaction.visibility;
        interaction2.linkedEntities = before.interaction.linkedEntities;
        interaction2.guestPermissions = before.interaction.guestPermissions;
      }
      await trx.flush();
      const projection = await recomputeNextInteraction(trx, before.interaction.entityId);
      const resetValues = buildCustomFieldResetMap(before.custom, payload?.after?.custom);
      if (Object.keys(resetValues).length) {
        await setCustomFieldsIfAny({
          dataEngine: createTransactionalDataEngine(ctx, trx),
          entityId: INTERACTION_ENTITY_ID,
          recordId: interaction2.id,
          organizationId: interaction2.organizationId,
          tenantId: interaction2.tenantId,
          values: resetValues,
          notify: false
        });
      }
      return {
        interaction: interaction2,
        nextInteractionId: projection.nextInteractionId
      };
    });
    const de = ctx.container.resolve("dataEngine");
    await emitCrudUndoSideEffects({
      dataEngine: de,
      action: "updated",
      entity: interaction,
      identifiers: {
        id: interaction.id,
        organizationId: interaction.organizationId,
        tenantId: interaction.tenantId
      },
      syncOrigin: ctx.syncOrigin,
      indexer: interactionCrudIndexer,
      events: interactionCrudEvents
    });
    await emitNextInteractionUpdatedEvent(ctx, {
      entityId: before.interaction.entityId,
      nextInteractionId
    }, {
      id: interaction.id,
      organizationId: interaction.organizationId,
      tenantId: interaction.tenantId
    });
  }
};
const completeInteractionCommand = {
  id: "customers.interactions.complete",
  async prepare(rawInput, ctx) {
    const parsed = interactionCompleteSchema.parse(rawInput);
    const em = ctx.container.resolve("em").fork();
    const snapshot = await loadInteractionSnapshot(em, parsed.id);
    return snapshot ? { before: snapshot } : {};
  },
  async execute(rawInput, ctx) {
    const parsed = interactionCompleteSchema.parse(rawInput);
    const em = ctx.container.resolve("em").fork();
    const { interaction, entityId, nextInteractionId } = await runInTransaction(em, async (trx) => {
      const interaction2 = await findOneWithDecryption(trx, CustomerInteraction, { id: parsed.id, deletedAt: null });
      if (!interaction2) {
        enforceRecordGoneIsConflict({ resourceKind: "customers.interaction", resourceId: parsed.id, request: ctx.request ?? null });
        throw new CrudHttpError(404, { error: "Interaction not found" });
      }
      ensureTenantScope(ctx, interaction2.tenantId);
      ensureOrganizationScope(ctx, interaction2.organizationId);
      await enforceCommandOptimisticLockWithGuards(ctx.container, {
        resourceKind: "customers.interaction",
        resourceId: interaction2.id,
        current: interaction2.updatedAt,
        request: ctx.request ?? null
      });
      interaction2.status = INTERACTION_STATUS_COMPLETED;
      interaction2.occurredAt = parsed.occurredAt ?? /* @__PURE__ */ new Date();
      await trx.flush();
      const entityId2 = typeof interaction2.entity === "string" ? interaction2.entity : interaction2.entity.id;
      const projection = await recomputeNextInteraction(trx, entityId2);
      return { interaction: interaction2, entityId: entityId2, nextInteractionId: projection.nextInteractionId };
    });
    const identifiers = {
      id: interaction.id,
      organizationId: interaction.organizationId,
      tenantId: interaction.tenantId
    };
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "updated",
      entity: interaction,
      identifiers,
      syncOrigin: ctx.syncOrigin,
      indexer: interactionCrudIndexer,
      events: interactionCrudEvents
    });
    await emitLifecycleEvent(ctx, "customers.interaction.completed", {
      ...identifiers,
      entityId,
      interactionType: interaction.interactionType,
      status: interaction.status,
      source: interaction.source ?? null,
      occurredAt: interaction.occurredAt?.toISOString() ?? null,
      ...ctx.syncOrigin ? { syncOrigin: ctx.syncOrigin } : {}
    });
    await emitNextInteractionUpdatedEvent(ctx, { entityId, nextInteractionId }, identifiers);
    return { interactionId: interaction.id };
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em").fork();
    return await loadInteractionSnapshot(em, result.interactionId);
  },
  buildLog: async ({ snapshots }) => {
    const { translate } = await resolveTranslations();
    const before = snapshots.before;
    if (!before) return null;
    const afterSnapshot = snapshots.after;
    return {
      actionLabel: translate("customers.audit.interactions.complete", "Complete interaction"),
      resourceKind: "customers.interaction",
      resourceId: before.interaction.id,
      parentResourceKind: resolveParentResourceKind(before.interaction.entityKind),
      parentResourceId: before.interaction.entityId ?? null,
      tenantId: before.interaction.tenantId,
      organizationId: before.interaction.organizationId,
      snapshotBefore: before,
      snapshotAfter: afterSnapshot ?? null,
      payload: {
        undo: {
          before,
          after: afterSnapshot ?? null
        }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry);
    const before = payload?.before;
    if (!before) return;
    const em = ctx.container.resolve("em").fork();
    const result = await runInTransaction(em, async (trx) => {
      const interaction = await findOneWithDecryption(trx, CustomerInteraction, { id: before.interaction.id });
      if (!interaction) return null;
      interaction.status = before.interaction.status;
      interaction.occurredAt = before.interaction.occurredAt;
      await trx.flush();
      const projection = await recomputeNextInteraction(trx, before.interaction.entityId);
      return {
        interaction,
        nextInteractionId: projection.nextInteractionId
      };
    });
    if (!result) return;
    const de = ctx.container.resolve("dataEngine");
    await emitCrudUndoSideEffects({
      dataEngine: de,
      action: "updated",
      entity: result.interaction,
      identifiers: {
        id: result.interaction.id,
        organizationId: result.interaction.organizationId,
        tenantId: result.interaction.tenantId
      },
      syncOrigin: ctx.syncOrigin,
      indexer: interactionCrudIndexer,
      events: interactionCrudEvents
    });
    await emitInteractionRevertedEvent(ctx, before.interaction);
    await emitNextInteractionUpdatedEvent(ctx, {
      entityId: before.interaction.entityId,
      nextInteractionId: result.nextInteractionId
    }, {
      id: result.interaction.id,
      organizationId: result.interaction.organizationId,
      tenantId: result.interaction.tenantId
    });
  }
};
const cancelInteractionCommand = {
  id: "customers.interactions.cancel",
  async prepare(rawInput, ctx) {
    const parsed = interactionCancelSchema.parse(rawInput);
    const em = ctx.container.resolve("em").fork();
    const snapshot = await loadInteractionSnapshot(em, parsed.id);
    return snapshot ? { before: snapshot } : {};
  },
  async execute(rawInput, ctx) {
    const parsed = interactionCancelSchema.parse(rawInput);
    const em = ctx.container.resolve("em").fork();
    const { interaction, entityId, nextInteractionId } = await runInTransaction(em, async (trx) => {
      const interaction2 = await findOneWithDecryption(trx, CustomerInteraction, { id: parsed.id, deletedAt: null });
      if (!interaction2) {
        enforceRecordGoneIsConflict({ resourceKind: "customers.interaction", resourceId: parsed.id, request: ctx.request ?? null });
        throw new CrudHttpError(404, { error: "Interaction not found" });
      }
      ensureTenantScope(ctx, interaction2.tenantId);
      ensureOrganizationScope(ctx, interaction2.organizationId);
      await enforceCommandOptimisticLockWithGuards(ctx.container, {
        resourceKind: "customers.interaction",
        resourceId: interaction2.id,
        current: interaction2.updatedAt,
        request: ctx.request ?? null
      });
      interaction2.status = INTERACTION_STATUS_CANCELED;
      await trx.flush();
      const entityId2 = typeof interaction2.entity === "string" ? interaction2.entity : interaction2.entity.id;
      const projection = await recomputeNextInteraction(trx, entityId2);
      return { interaction: interaction2, entityId: entityId2, nextInteractionId: projection.nextInteractionId };
    });
    const identifiers = {
      id: interaction.id,
      organizationId: interaction.organizationId,
      tenantId: interaction.tenantId
    };
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "updated",
      entity: interaction,
      identifiers,
      syncOrigin: ctx.syncOrigin,
      indexer: interactionCrudIndexer,
      events: interactionCrudEvents
    });
    await emitLifecycleEvent(ctx, "customers.interaction.canceled", {
      ...identifiers,
      entityId,
      interactionType: interaction.interactionType,
      status: interaction.status,
      source: interaction.source ?? null,
      ...ctx.syncOrigin ? { syncOrigin: ctx.syncOrigin } : {}
    });
    await emitNextInteractionUpdatedEvent(ctx, { entityId, nextInteractionId }, identifiers);
    return { interactionId: interaction.id };
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em").fork();
    return await loadInteractionSnapshot(em, result.interactionId);
  },
  buildLog: async ({ snapshots }) => {
    const { translate } = await resolveTranslations();
    const before = snapshots.before;
    if (!before) return null;
    const afterSnapshot = snapshots.after;
    return {
      actionLabel: translate("customers.audit.interactions.cancel", "Cancel interaction"),
      resourceKind: "customers.interaction",
      resourceId: before.interaction.id,
      parentResourceKind: resolveParentResourceKind(before.interaction.entityKind),
      parentResourceId: before.interaction.entityId ?? null,
      tenantId: before.interaction.tenantId,
      organizationId: before.interaction.organizationId,
      snapshotBefore: before,
      snapshotAfter: afterSnapshot ?? null,
      payload: {
        undo: {
          before,
          after: afterSnapshot ?? null
        }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry);
    const before = payload?.before;
    if (!before) return;
    const em = ctx.container.resolve("em").fork();
    const result = await runInTransaction(em, async (trx) => {
      const interaction = await findOneWithDecryption(trx, CustomerInteraction, { id: before.interaction.id });
      if (!interaction) return null;
      interaction.status = before.interaction.status;
      await trx.flush();
      const projection = await recomputeNextInteraction(trx, before.interaction.entityId);
      return {
        interaction,
        nextInteractionId: projection.nextInteractionId
      };
    });
    if (!result) return;
    const de = ctx.container.resolve("dataEngine");
    await emitCrudUndoSideEffects({
      dataEngine: de,
      action: "updated",
      entity: result.interaction,
      identifiers: {
        id: result.interaction.id,
        organizationId: result.interaction.organizationId,
        tenantId: result.interaction.tenantId
      },
      syncOrigin: ctx.syncOrigin,
      indexer: interactionCrudIndexer,
      events: interactionCrudEvents
    });
    await emitInteractionRevertedEvent(ctx, before.interaction);
    await emitNextInteractionUpdatedEvent(ctx, {
      entityId: before.interaction.entityId,
      nextInteractionId: result.nextInteractionId
    }, {
      id: result.interaction.id,
      organizationId: result.interaction.organizationId,
      tenantId: result.interaction.tenantId
    });
  }
};
const deleteInteractionCommand = {
  id: "customers.interactions.delete",
  async prepare(input, ctx) {
    const id = requireId(input, "Interaction id required");
    const em = ctx.container.resolve("em").fork();
    const snapshot = await loadInteractionSnapshot(em, id);
    return snapshot ? { before: snapshot } : {};
  },
  async execute(input, ctx) {
    const id = requireId(input, "Interaction id required");
    const em = ctx.container.resolve("em").fork();
    const { interaction, entityId, nextInteractionId } = await runInTransaction(em, async (trx) => {
      const interaction2 = await findOneWithDecryption(trx, CustomerInteraction, { id, deletedAt: null });
      if (!interaction2) {
        enforceRecordGoneIsConflict({ resourceKind: "customers.interaction", resourceId: id, request: ctx.request ?? null });
        throw new CrudHttpError(404, { error: "Interaction not found" });
      }
      ensureTenantScope(ctx, interaction2.tenantId);
      ensureOrganizationScope(ctx, interaction2.organizationId);
      await enforceCommandOptimisticLockWithGuards(ctx.container, {
        resourceKind: "customers.interaction",
        resourceId: interaction2.id,
        current: interaction2.updatedAt,
        request: ctx.request ?? null
      });
      const entityId2 = typeof interaction2.entity === "string" ? interaction2.entity : interaction2.entity.id;
      interaction2.deletedAt = /* @__PURE__ */ new Date();
      await trx.flush();
      const projection = await recomputeNextInteraction(trx, entityId2);
      return { interaction: interaction2, entityId: entityId2, nextInteractionId: projection.nextInteractionId };
    });
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "deleted",
      entity: interaction,
      identifiers: {
        id: interaction.id,
        organizationId: interaction.organizationId,
        tenantId: interaction.tenantId
      },
      syncOrigin: ctx.syncOrigin,
      indexer: interactionCrudIndexer,
      events: interactionCrudEvents
    });
    await emitNextInteractionUpdatedEvent(ctx, { entityId, nextInteractionId }, {
      id: interaction.id,
      organizationId: interaction.organizationId,
      tenantId: interaction.tenantId
    });
    return { interactionId: interaction.id };
  },
  buildLog: async ({ snapshots }) => {
    const before = snapshots.before;
    if (!before) return null;
    const { translate } = await resolveTranslations();
    return {
      actionLabel: translate("customers.audit.interactions.delete", "Delete interaction"),
      resourceKind: "customers.interaction",
      resourceId: before.interaction.id,
      parentResourceKind: resolveParentResourceKind(before.interaction.entityKind),
      parentResourceId: before.interaction.entityId ?? null,
      tenantId: before.interaction.tenantId,
      organizationId: before.interaction.organizationId,
      snapshotBefore: before,
      payload: {
        undo: {
          before
        }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry);
    const before = payload?.before;
    if (!before) return;
    const em = ctx.container.resolve("em").fork();
    const { interaction, nextInteractionId } = await runInTransaction(em, async (trx) => {
      const entity = await requireTimelineParentEntity(trx, before.interaction.entityId, { tenantId: before.interaction.tenantId, organizationId: before.interaction.organizationId });
      let interaction2 = await findOneWithDecryption(trx, CustomerInteraction, { id: before.interaction.id });
      if (!interaction2) {
        interaction2 = trx.create(CustomerInteraction, {
          id: before.interaction.id,
          organizationId: before.interaction.organizationId,
          tenantId: before.interaction.tenantId,
          entity,
          interactionType: before.interaction.interactionType,
          title: before.interaction.title,
          body: before.interaction.body,
          status: before.interaction.status,
          scheduledAt: before.interaction.scheduledAt,
          occurredAt: before.interaction.occurredAt,
          priority: before.interaction.priority,
          authorUserId: before.interaction.authorUserId,
          ownerUserId: before.interaction.ownerUserId,
          dealId: before.interaction.dealId,
          source: before.interaction.source,
          appearanceIcon: before.interaction.appearanceIcon,
          appearanceColor: before.interaction.appearanceColor,
          durationMinutes: before.interaction.durationMinutes,
          location: before.interaction.location,
          allDay: before.interaction.allDay,
          recurrenceRule: before.interaction.recurrenceRule,
          recurrenceEnd: before.interaction.recurrenceEnd,
          participants: before.interaction.participants,
          reminderMinutes: before.interaction.reminderMinutes,
          visibility: before.interaction.visibility,
          linkedEntities: before.interaction.linkedEntities,
          guestPermissions: before.interaction.guestPermissions,
          createdAt: /* @__PURE__ */ new Date(),
          updatedAt: /* @__PURE__ */ new Date()
        });
        trx.persist(interaction2);
      } else {
        interaction2.deletedAt = null;
        interaction2.entity = entity;
        interaction2.interactionType = before.interaction.interactionType;
        interaction2.title = before.interaction.title;
        interaction2.body = before.interaction.body;
        interaction2.status = before.interaction.status;
        interaction2.scheduledAt = before.interaction.scheduledAt;
        interaction2.occurredAt = before.interaction.occurredAt;
        interaction2.priority = before.interaction.priority;
        interaction2.authorUserId = before.interaction.authorUserId;
        interaction2.ownerUserId = before.interaction.ownerUserId;
        interaction2.dealId = before.interaction.dealId;
        interaction2.source = before.interaction.source;
        interaction2.appearanceIcon = before.interaction.appearanceIcon;
        interaction2.appearanceColor = before.interaction.appearanceColor;
        interaction2.durationMinutes = before.interaction.durationMinutes;
        interaction2.location = before.interaction.location;
        interaction2.allDay = before.interaction.allDay;
        interaction2.recurrenceRule = before.interaction.recurrenceRule;
        interaction2.recurrenceEnd = before.interaction.recurrenceEnd;
        interaction2.participants = before.interaction.participants;
        interaction2.reminderMinutes = before.interaction.reminderMinutes;
        interaction2.visibility = before.interaction.visibility;
        interaction2.linkedEntities = before.interaction.linkedEntities;
        interaction2.guestPermissions = before.interaction.guestPermissions;
      }
      await trx.flush();
      const projection = await recomputeNextInteraction(trx, before.interaction.entityId);
      const resetValues = buildCustomFieldResetMap(before.custom, void 0);
      if (Object.keys(resetValues).length) {
        await setCustomFieldsIfAny({
          dataEngine: createTransactionalDataEngine(ctx, trx),
          entityId: INTERACTION_ENTITY_ID,
          recordId: interaction2.id,
          organizationId: interaction2.organizationId,
          tenantId: interaction2.tenantId,
          values: resetValues,
          notify: false
        });
      }
      return {
        interaction: interaction2,
        nextInteractionId: projection.nextInteractionId
      };
    });
    const de = ctx.container.resolve("dataEngine");
    await emitCrudUndoSideEffects({
      dataEngine: de,
      action: "created",
      entity: interaction,
      identifiers: {
        id: interaction.id,
        organizationId: interaction.organizationId,
        tenantId: interaction.tenantId
      },
      syncOrigin: ctx.syncOrigin,
      indexer: interactionCrudIndexer,
      events: interactionCrudEvents
    });
    await emitNextInteractionUpdatedEvent(ctx, {
      entityId: before.interaction.entityId,
      nextInteractionId
    }, {
      id: interaction.id,
      organizationId: interaction.organizationId,
      tenantId: interaction.tenantId
    });
  }
};
const recomputeNextSchema = z.object({ entityId: z.string().min(1) });
const recomputeNextCommand = {
  id: "customers.interaction.recompute_next",
  async execute(rawInput, ctx) {
    const parsed = recomputeNextSchema.parse(rawInput);
    const em = ctx.container.resolve("em").fork();
    const projection = await recomputeNextInteraction(em, parsed.entityId);
    const entity = await requireTimelineParentEntity(em, parsed.entityId, {
      tenantId: ctx.auth?.tenantId ?? "",
      organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? ""
    });
    await emitNextInteractionUpdatedEvent(ctx, {
      entityId: parsed.entityId,
      nextInteractionId: projection.nextInteractionId
    }, {
      id: parsed.entityId,
      organizationId: entity.organizationId,
      tenantId: entity.tenantId
    });
    return { entityId: parsed.entityId };
  }
};
registerCommand(createInteractionCommand);
registerCommand(updateInteractionCommand);
registerCommand(completeInteractionCommand);
registerCommand(cancelInteractionCommand);
registerCommand(deleteInteractionCommand);
registerCommand(recomputeNextCommand);
//# sourceMappingURL=interactions.js.map
