import { registerCommand } from "@open-mercato/shared/lib/commands";
import {
  parseWithCustomFields,
  setCustomFieldsIfAny,
  emitCrudSideEffects,
  emitCrudUndoSideEffects,
  requireId,
  snapshotsEqual
} from "@open-mercato/shared/lib/commands/helpers";
import {
  CustomerAddress,
  CustomerComment,
  CustomerActivity,
  CustomerInteraction,
  CustomerDeal,
  CustomerDealPersonLink,
  CustomerPersonCompanyLink,
  CustomerTodoLink,
  CustomerEntity,
  CustomerPersonProfile,
  CustomerTagAssignment
} from "../data/entities.js";
import { resolvePersonCustomFieldRouting, CUSTOMER_ENTITY_ID, PERSON_ENTITY_ID } from "../lib/customFieldRouting.js";
import { CustomFieldValue } from "@open-mercato/core/modules/entities/data/entities";
import {
  personCreateSchema,
  personUpdateSchema
} from "../data/validators.js";
import {
  ensureOrganizationScope,
  ensureTenantScope,
  extractUndoPayload,
  assertFound,
  syncEntityTags,
  loadEntityTagIds,
  ensureDictionaryEntry,
  emitQueryIndexDeleteEvents,
  emitQueryIndexUpsertEvents
} from "./shared.js";
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
import { resolveRedoSnapshot } from "@open-mercato/shared/lib/commands/redo";
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
import {
  loadCustomFieldSnapshot,
  buildCustomFieldResetMap
} from "@open-mercato/shared/lib/commands/customFieldSnapshots";
import { E } from "../../../generated/entities.ids.generated.js";
import { findWithDecryption, findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { deriveDisplayName, isDerivedDisplayName } from "../lib/displayName.js";
import {
  loadPersonCompanyLinks,
  summarizePersonCompanies,
  syncLegacyPrimaryCompanyLink
} from "../lib/personCompanies.js";
const INTERACTION_ENTITY_ID = "customers:customer_interaction";
const personCrudIndexer = {
  entityType: E.customers.customer_person_profile
};
const personCrudEvents = {
  module: "customers",
  entity: "person",
  persistent: true,
  buildPayload: (ctx) => ({
    id: ctx.identifiers.id,
    entityId: ctx.entity?.id ?? ctx.identifiers.id,
    organizationId: ctx.identifiers.organizationId,
    tenantId: ctx.identifiers.tenantId
  })
};
function personEntityIndexEntry(entity) {
  return {
    entityType: E.customers.customer_entity,
    recordId: entity.id,
    tenantId: entity.tenantId,
    organizationId: entity.organizationId
  };
}
function normalizeOptionalString(value) {
  if (typeof value !== "string") return null;
  const trimmed = value.trim();
  return trimmed.length ? trimmed : null;
}
function normalizeHexColor(value) {
  if (typeof value !== "string") return null;
  const trimmed = value.trim().toLowerCase();
  return /^#([0-9a-f]{6})$/.test(trimmed) ? trimmed : null;
}
function normalizeEmail(value) {
  const normalized = normalizeOptionalString(value);
  return normalized ? normalized.toLowerCase() : null;
}
function buildPersonHasDependentsError(translate, counts) {
  const blockers = [];
  if (counts.dealLinks > 0) {
    blockers.push(
      translate("customers.people.delete.blockers.deals", "linked deals ({{count}})", { count: counts.dealLinks })
    );
  }
  const summary = blockers.join(", ");
  const message = translate(
    "customers.people.delete.blocked",
    "Cannot delete person: {{blockers}}. Please unlink or reassign first.",
    { blockers: summary }
  );
  return new CrudHttpError(422, { error: message, code: "PERSON_HAS_DEPENDENTS" });
}
function serializePersonSnapshot(entity, profile, tagIds, addresses, comments, deals, activities, todoLinks, interactions, custom) {
  const companies = summarizePersonCompanies(profile, []);
  return {
    entity: {
      id: entity.id,
      organizationId: entity.organizationId,
      tenantId: entity.tenantId,
      displayName: entity.displayName,
      description: entity.description ?? null,
      ownerUserId: entity.ownerUserId ?? null,
      primaryEmail: entity.primaryEmail ?? null,
      primaryPhone: entity.primaryPhone ?? null,
      status: entity.status ?? null,
      lifecycleStage: entity.lifecycleStage ?? null,
      source: entity.source ?? null,
      nextInteractionAt: entity.nextInteractionAt ?? null,
      nextInteractionName: entity.nextInteractionName ?? null,
      nextInteractionRefId: entity.nextInteractionRefId ?? null,
      nextInteractionIcon: entity.nextInteractionIcon ?? null,
      nextInteractionColor: entity.nextInteractionColor ?? null,
      isActive: entity.isActive
    },
    profile: {
      id: profile.id,
      firstName: profile.firstName ?? null,
      lastName: profile.lastName ?? null,
      preferredName: profile.preferredName ?? null,
      jobTitle: profile.jobTitle ?? null,
      department: profile.department ?? null,
      seniority: profile.seniority ?? null,
      timezone: profile.timezone ?? null,
      linkedInUrl: profile.linkedInUrl ?? null,
      twitterUrl: profile.twitterUrl ?? null,
      companyEntityId: profile.company ? typeof profile.company === "string" ? profile.company : profile.company.id : null
    },
    companies,
    tagIds,
    addresses: addresses.map((address) => ({
      id: address.id,
      name: address.name ?? null,
      purpose: address.purpose ?? null,
      addressLine1: address.addressLine1,
      addressLine2: address.addressLine2 ?? null,
      city: address.city ?? null,
      region: address.region ?? null,
      postalCode: address.postalCode ?? null,
      country: address.country ?? null,
      latitude: address.latitude ?? null,
      longitude: address.longitude ?? null,
      isPrimary: address.isPrimary
    })),
    comments: comments.map((comment) => ({
      id: comment.id,
      body: comment.body,
      authorUserId: comment.authorUserId ?? null,
      dealId: comment.deal ? typeof comment.deal === "string" ? comment.deal : comment.deal.id : null,
      createdAt: comment.createdAt,
      updatedAt: comment.updatedAt,
      deletedAt: comment.deletedAt ?? null,
      appearanceIcon: comment.appearanceIcon ?? null,
      appearanceColor: comment.appearanceColor ?? null
    })),
    deals: deals.filter((link) => link.deal).map((link) => ({
      id: link.id,
      dealId: link.deal.id,
      participantRole: link.participantRole ?? null,
      createdAt: link.createdAt
    })),
    activities: activities.map((activity) => ({
      id: activity.id,
      activityType: activity.activityType,
      subject: activity.subject ?? null,
      body: activity.body ?? null,
      occurredAt: activity.occurredAt ?? null,
      authorUserId: activity.authorUserId ?? null,
      appearanceIcon: activity.appearanceIcon ?? null,
      appearanceColor: activity.appearanceColor ?? null,
      dealId: activity.deal ? typeof activity.deal === "string" ? activity.deal : activity.deal.id : null,
      createdAt: activity.createdAt,
      updatedAt: activity.updatedAt
    })),
    todos: todoLinks.map((todo) => ({
      id: todo.id,
      todoId: todo.todoId,
      todoSource: todo.todoSource,
      createdAt: todo.createdAt,
      createdByUserId: todo.createdByUserId ?? null
    })),
    interactions,
    custom
  };
}
async function loadPersonSnapshot(em, entityId) {
  const entity = await em.findOne(CustomerEntity, { id: entityId, deletedAt: null });
  if (!entity || entity.kind !== "person") return null;
  const profile = await findOneWithDecryption(
    em,
    CustomerPersonProfile,
    { entity },
    { populate: ["company"] },
    { tenantId: entity.tenantId, organizationId: entity.organizationId }
  );
  if (!profile) return null;
  const companyLinks = await loadPersonCompanyLinks(em, entity);
  const tagIds = await loadEntityTagIds(em, entity);
  const addresses = await em.find(CustomerAddress, { entity }, { orderBy: { createdAt: "asc" } });
  const comments = await findWithDecryption(
    em,
    CustomerComment,
    { entity },
    { orderBy: { createdAt: "asc" }, populate: ["deal"] },
    { tenantId: entity.tenantId, organizationId: entity.organizationId }
  );
  const deals = await findWithDecryption(
    em,
    CustomerDealPersonLink,
    { person: entity },
    { orderBy: { createdAt: "asc" }, populate: ["deal"] },
    { tenantId: entity.tenantId, organizationId: entity.organizationId }
  );
  const activities = await findWithDecryption(
    em,
    CustomerActivity,
    { entity },
    { orderBy: { createdAt: "asc" }, populate: ["deal"] },
    { tenantId: entity.tenantId, organizationId: entity.organizationId }
  );
  const todoLinks = await em.find(CustomerTodoLink, { entity }, { orderBy: { createdAt: "asc" } });
  const interactions = await em.find(CustomerInteraction, { entity }, { orderBy: { createdAt: "asc" } });
  const entityCustom = await loadCustomFieldSnapshot(em, {
    entityId: CUSTOMER_ENTITY_ID,
    recordId: entity.id,
    tenantId: entity.tenantId,
    organizationId: entity.organizationId
  });
  const profileCustom = await loadCustomFieldSnapshot(em, {
    entityId: PERSON_ENTITY_ID,
    recordId: profile.id,
    tenantId: entity.tenantId,
    organizationId: entity.organizationId
  });
  const routing = await resolvePersonCustomFieldRouting(em, entity.tenantId, entity.organizationId);
  const custom = { ...entityCustom };
  for (const [key, value] of Object.entries(profileCustom)) {
    const target = routing.get(key);
    if (target === CUSTOMER_ENTITY_ID && Object.prototype.hasOwnProperty.call(custom, key)) continue;
    custom[key] = value;
  }
  const interactionSnapshots = await Promise.all(
    interactions.map(async (interaction) => ({
      id: interaction.id,
      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,
      dealId: interaction.dealId ?? null,
      createdAt: interaction.createdAt,
      updatedAt: interaction.updatedAt,
      deletedAt: interaction.deletedAt ?? null,
      custom: await loadCustomFieldSnapshot(em, {
        entityId: INTERACTION_ENTITY_ID,
        recordId: interaction.id,
        tenantId: entity.tenantId,
        organizationId: entity.organizationId
      })
    }))
  );
  const snapshot = serializePersonSnapshot(entity, profile, tagIds, addresses, comments, deals, activities, todoLinks, interactionSnapshots, custom);
  snapshot.companies = summarizePersonCompanies(profile, companyLinks);
  return snapshot;
}
async function resolveCompanyReference(em, companyId, organizationId, tenantId) {
  if (!companyId) return null;
  const company = await em.findOne(CustomerEntity, { id: companyId, kind: "company", deletedAt: null });
  if (!company) {
    throw new CrudHttpError(400, { error: "Company not found" });
  }
  if (company.organizationId !== organizationId || company.tenantId !== tenantId) {
    throw new CrudHttpError(403, { error: "Cannot link person to company outside current scope" });
  }
  return company;
}
async function setCustomFieldsForPerson(ctx, entityId, profileId, organizationId, tenantId, values) {
  if (!values || !Object.keys(values).length) return;
  const em = ctx.container.resolve("em");
  const routing = await resolvePersonCustomFieldRouting(em, tenantId, organizationId);
  const entityScoped = {};
  const profileScoped = {};
  for (const [key, value] of Object.entries(values)) {
    const target = routing.get(key) ?? PERSON_ENTITY_ID;
    if (target === CUSTOMER_ENTITY_ID) entityScoped[key] = value;
    else profileScoped[key] = value;
  }
  const de = ctx.container.resolve("dataEngine");
  if (Object.keys(entityScoped).length) {
    await setCustomFieldsIfAny({
      dataEngine: de,
      entityId: CUSTOMER_ENTITY_ID,
      recordId: entityId,
      organizationId,
      tenantId,
      values: entityScoped,
      notify: true
    });
  }
  if (Object.keys(profileScoped).length) {
    await setCustomFieldsIfAny({
      dataEngine: de,
      entityId: PERSON_ENTITY_ID,
      recordId: profileId,
      organizationId,
      tenantId,
      values: profileScoped,
      notify: true
    });
  }
}
function buildPersonGraph(em, values) {
  const entity = em.create(CustomerEntity, {
    ...values.entityId ? { id: values.entityId } : {},
    organizationId: values.organizationId,
    tenantId: values.tenantId,
    kind: "person",
    displayName: values.displayName,
    description: values.description,
    ownerUserId: values.ownerUserId,
    primaryEmail: values.primaryEmail,
    primaryPhone: values.primaryPhone,
    status: values.status,
    lifecycleStage: values.lifecycleStage,
    source: values.source,
    nextInteractionAt: values.nextInteractionAt,
    nextInteractionName: values.nextInteractionName,
    nextInteractionRefId: values.nextInteractionRefId,
    nextInteractionIcon: values.nextInteractionIcon,
    nextInteractionColor: values.nextInteractionColor,
    isActive: values.isActive
  });
  const profile = em.create(CustomerPersonProfile, {
    ...values.profileId ? { id: values.profileId } : {},
    organizationId: values.organizationId,
    tenantId: values.tenantId,
    entity,
    firstName: values.firstName,
    lastName: values.lastName,
    preferredName: values.preferredName,
    jobTitle: values.jobTitle,
    department: values.department,
    seniority: values.seniority,
    timezone: values.timezone,
    linkedInUrl: values.linkedInUrl,
    twitterUrl: values.twitterUrl,
    company: null
  });
  em.persist(entity);
  em.persist(profile);
  return { entity, profile };
}
const createPersonCommand = {
  id: "customers.people.create",
  async execute(rawInput, ctx) {
    const { parsed, custom } = parseWithCustomFields(personCreateSchema, rawInput);
    ensureTenantScope(ctx, parsed.tenantId);
    ensureOrganizationScope(ctx, parsed.organizationId);
    const em = ctx.container.resolve("em").fork();
    const firstName = parsed.firstName?.trim() ?? "";
    const lastName = parsed.lastName?.trim() ?? "";
    const description = normalizeOptionalString(parsed.description);
    const primaryEmail = normalizeEmail(parsed.primaryEmail);
    const primaryPhone = normalizeOptionalString(parsed.primaryPhone);
    const status = normalizeOptionalString(parsed.status);
    const lifecycleStage = normalizeOptionalString(parsed.lifecycleStage);
    const source = normalizeOptionalString(parsed.source);
    const preferredName = normalizeOptionalString(parsed.preferredName);
    const jobTitle = normalizeOptionalString(parsed.jobTitle);
    const department = normalizeOptionalString(parsed.department);
    const seniority = normalizeOptionalString(parsed.seniority);
    const timezone = normalizeOptionalString(parsed.timezone);
    const linkedInUrl = normalizeOptionalString(parsed.linkedInUrl);
    const twitterUrl = normalizeOptionalString(parsed.twitterUrl);
    const displayNameInput = parsed.displayName?.trim() ?? "";
    const displayName = displayNameInput.length > 0 ? displayNameInput : deriveDisplayName(firstName, lastName);
    const nextInteractionName = parsed.nextInteraction?.name ? parsed.nextInteraction.name.trim() : null;
    const nextInteractionRefId = normalizeOptionalString(parsed.nextInteraction?.refId);
    const nextInteractionIcon = normalizeOptionalString(parsed.nextInteraction?.icon);
    const nextInteractionColor = normalizeHexColor(parsed.nextInteraction?.color);
    if (!firstName || !lastName) {
      throw new CrudHttpError(400, { error: "First and last name are required" });
    }
    if (!displayName) {
      throw new CrudHttpError(400, { error: "Display name is required" });
    }
    let entity;
    let profile;
    await withAtomicFlush(em, [
      () => {
        const graph = buildPersonGraph(em, {
          organizationId: parsed.organizationId,
          tenantId: parsed.tenantId,
          displayName,
          description,
          ownerUserId: parsed.ownerUserId ?? null,
          primaryEmail,
          primaryPhone,
          status,
          lifecycleStage,
          source,
          nextInteractionAt: parsed.nextInteraction?.at ?? null,
          nextInteractionName,
          nextInteractionRefId,
          nextInteractionIcon,
          nextInteractionColor,
          isActive: parsed.isActive ?? true,
          firstName,
          lastName,
          preferredName,
          jobTitle,
          department,
          seniority,
          timezone,
          linkedInUrl,
          twitterUrl
        });
        entity = graph.entity;
        profile = graph.profile;
      },
      async () => {
        if (status) {
          await ensureDictionaryEntry(em, {
            tenantId: parsed.tenantId,
            organizationId: parsed.organizationId,
            kind: "status",
            value: status
          });
        }
        if (jobTitle) {
          await ensureDictionaryEntry(em, {
            tenantId: parsed.tenantId,
            organizationId: parsed.organizationId,
            kind: "job_title",
            value: jobTitle
          });
        }
        if (source) {
          await ensureDictionaryEntry(em, {
            tenantId: parsed.tenantId,
            organizationId: parsed.organizationId,
            kind: "source",
            value: source
          });
        }
      },
      () => syncLegacyPrimaryCompanyLink(em, entity, profile, parsed.companyEntityId ?? null),
      () => syncEntityTags(em, entity, parsed.tags)
    ], { transaction: true });
    const tenantId = entity.tenantId;
    const organizationId = entity.organizationId;
    await setCustomFieldsForPerson(ctx, entity.id, profile.id, organizationId, tenantId, custom);
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "created",
      entity,
      identifiers: {
        id: profile.id ?? entity.id,
        tenantId,
        organizationId
      },
      indexer: personCrudIndexer,
      events: personCrudEvents
    });
    await emitQueryIndexUpsertEvents(ctx, [personEntityIndexEntry(entity)]);
    return { entityId: entity.id, personId: profile.id };
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em").fork();
    return await loadPersonSnapshot(em, result.entityId);
  },
  buildLog: async ({ result, snapshots }) => {
    const { translate } = await resolveTranslations();
    const snapshot = snapshots.after;
    return {
      actionLabel: translate("customers.audit.people.create", "Create person"),
      resourceKind: "customers.person",
      resourceId: result.entityId,
      tenantId: snapshot?.entity.tenantId ?? null,
      organizationId: snapshot?.entity.organizationId ?? null,
      snapshotAfter: snapshot ?? null,
      payload: {
        undo: {
          after: snapshot
        }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry) ?? null;
    const entityId = logEntry?.resourceId ?? payload?.after?.entity.id ?? null;
    if (!entityId) return;
    const em = ctx.container.resolve("em").fork();
    const entity = await em.findOne(CustomerEntity, { id: entityId });
    if (!entity) return;
    const profile = await findOneWithDecryption(
      em,
      CustomerPersonProfile,
      { entity },
      void 0,
      { tenantId: entity.tenantId, organizationId: entity.organizationId }
    );
    const identifiers = {
      id: payload?.after?.profile.id ?? profile?.id ?? entity.id,
      organizationId: entity.organizationId,
      tenantId: entity.tenantId
    };
    await withAtomicFlush(em, [
      () => em.nativeDelete(CustomerTagAssignment, { entity, organizationId: entity.organizationId, tenantId: entity.tenantId }),
      () => {
        if (profile) em.remove(profile);
      },
      () => em.remove(entity)
    ], { transaction: true });
    const de = ctx.container.resolve("dataEngine");
    await emitCrudUndoSideEffects({
      dataEngine: de,
      action: "deleted",
      entity,
      identifiers,
      indexer: personCrudIndexer,
      events: personCrudEvents
    });
    await emitQueryIndexDeleteEvents(ctx, [personEntityIndexEntry(entity)]);
  },
  redo: async ({ logEntry, ctx }) => {
    const after = resolveRedoSnapshot(logEntry);
    if (!after) {
      throw new CrudHttpError(400, { error: "[internal] redo snapshot unavailable for person create" });
    }
    const em = ctx.container.resolve("em").fork();
    let entity = await findOneWithDecryption(
      em,
      CustomerEntity,
      { id: after.entity.id },
      void 0,
      { tenantId: after.entity.tenantId, organizationId: after.entity.organizationId }
    );
    let profile;
    if (!entity) {
      let newEntity;
      let newProfile;
      await withAtomicFlush(em, [
        () => {
          const graph = buildPersonGraph(em, {
            entityId: after.entity.id,
            profileId: after.profile.id,
            organizationId: after.entity.organizationId,
            tenantId: after.entity.tenantId,
            displayName: after.entity.displayName,
            description: after.entity.description,
            ownerUserId: after.entity.ownerUserId,
            primaryEmail: after.entity.primaryEmail,
            primaryPhone: after.entity.primaryPhone,
            status: after.entity.status,
            lifecycleStage: after.entity.lifecycleStage,
            source: after.entity.source,
            nextInteractionAt: after.entity.nextInteractionAt,
            nextInteractionName: after.entity.nextInteractionName,
            nextInteractionRefId: after.entity.nextInteractionRefId,
            nextInteractionIcon: after.entity.nextInteractionIcon,
            nextInteractionColor: after.entity.nextInteractionColor,
            isActive: after.entity.isActive,
            firstName: after.profile.firstName,
            lastName: after.profile.lastName,
            preferredName: after.profile.preferredName,
            jobTitle: after.profile.jobTitle,
            department: after.profile.department,
            seniority: after.profile.seniority,
            timezone: after.profile.timezone,
            linkedInUrl: after.profile.linkedInUrl,
            twitterUrl: after.profile.twitterUrl
          });
          newEntity = graph.entity;
          newProfile = graph.profile;
        },
        () => syncLegacyPrimaryCompanyLink(em, newEntity, newProfile, after.profile.companyEntityId),
        () => syncEntityTags(em, newEntity, after.tagIds)
      ], { transaction: true });
      entity = newEntity;
      profile = newProfile;
    } else {
      const existingProfile = await findOneWithDecryption(
        em,
        CustomerPersonProfile,
        { entity },
        void 0,
        { tenantId: after.entity.tenantId, organizationId: after.entity.organizationId }
      );
      if (!existingProfile) throw new CrudHttpError(404, { error: "Person profile not found" });
      profile = existingProfile;
      const survivingEntity = entity;
      await withAtomicFlush(em, [
        () => {
          survivingEntity.deletedAt = null;
          survivingEntity.displayName = after.entity.displayName;
          survivingEntity.description = after.entity.description;
          survivingEntity.ownerUserId = after.entity.ownerUserId;
          survivingEntity.primaryEmail = after.entity.primaryEmail;
          survivingEntity.primaryPhone = after.entity.primaryPhone;
          survivingEntity.status = after.entity.status;
          survivingEntity.lifecycleStage = after.entity.lifecycleStage;
          survivingEntity.source = after.entity.source;
          survivingEntity.nextInteractionAt = after.entity.nextInteractionAt;
          survivingEntity.nextInteractionName = after.entity.nextInteractionName;
          survivingEntity.nextInteractionRefId = after.entity.nextInteractionRefId;
          survivingEntity.nextInteractionIcon = after.entity.nextInteractionIcon;
          survivingEntity.nextInteractionColor = after.entity.nextInteractionColor;
          survivingEntity.isActive = after.entity.isActive;
          profile.firstName = after.profile.firstName;
          profile.lastName = after.profile.lastName;
          profile.preferredName = after.profile.preferredName;
          profile.jobTitle = after.profile.jobTitle;
          profile.department = after.profile.department;
          profile.seniority = after.profile.seniority;
          profile.timezone = after.profile.timezone;
          profile.linkedInUrl = after.profile.linkedInUrl;
          profile.twitterUrl = after.profile.twitterUrl;
        },
        () => syncLegacyPrimaryCompanyLink(em, survivingEntity, profile, after.profile.companyEntityId),
        () => syncEntityTags(em, survivingEntity, after.tagIds)
      ], { transaction: true });
    }
    const restoredEntity = entity;
    const restoreValues = buildCustomFieldResetMap(after.custom, void 0);
    if (Object.keys(restoreValues).length) {
      await setCustomFieldsForPerson(ctx, restoredEntity.id, profile.id, restoredEntity.organizationId, restoredEntity.tenantId, restoreValues);
    }
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "created",
      entity: restoredEntity,
      identifiers: {
        id: profile.id ?? restoredEntity.id,
        tenantId: restoredEntity.tenantId,
        organizationId: restoredEntity.organizationId
      },
      indexer: personCrudIndexer,
      events: personCrudEvents
    });
    await emitQueryIndexUpsertEvents(ctx, [personEntityIndexEntry(restoredEntity)]);
    return { entityId: restoredEntity.id, personId: profile.id };
  }
};
const updatePersonCommand = {
  id: "customers.people.update",
  async prepare(rawInput, ctx) {
    const { parsed } = parseWithCustomFields(personUpdateSchema, rawInput);
    const em = ctx.container.resolve("em").fork();
    const snapshot = await loadPersonSnapshot(em, parsed.id);
    return snapshot ? { before: snapshot } : {};
  },
  async execute(rawInput, ctx) {
    const { parsed, custom } = parseWithCustomFields(personUpdateSchema, rawInput);
    const em = ctx.container.resolve("em").fork();
    const entity = await em.findOne(CustomerEntity, { id: parsed.id, deletedAt: null });
    const record = assertFound(entity, "Person not found");
    ensureTenantScope(ctx, record.tenantId);
    ensureOrganizationScope(ctx, record.organizationId);
    const profile = await em.findOne(CustomerPersonProfile, { entity: record });
    if (!profile) throw new CrudHttpError(404, { error: "Person profile not found" });
    if (parsed.displayName !== void 0) {
      const nextDisplayName = parsed.displayName.trim();
      if (!nextDisplayName) {
        throw new CrudHttpError(400, { error: "Display name is required" });
      }
    }
    if (parsed.displayName === void 0 && (parsed.firstName !== void 0 || parsed.lastName !== void 0) && isDerivedDisplayName(record.displayName, profile.firstName, profile.lastName)) {
      const nextFirst = parsed.firstName !== void 0 ? parsed.firstName : profile.firstName;
      const nextLast = parsed.lastName !== void 0 ? parsed.lastName : profile.lastName;
      const derived = deriveDisplayName(nextFirst, nextLast);
      if (derived.length > 0) parsed.displayName = derived;
    }
    await withAtomicFlush(em, [
      () => {
        if (parsed.description !== void 0) record.description = normalizeOptionalString(parsed.description);
        if (parsed.ownerUserId !== void 0) record.ownerUserId = parsed.ownerUserId ?? null;
        if (parsed.primaryEmail !== void 0) record.primaryEmail = normalizeEmail(parsed.primaryEmail);
        if (parsed.primaryPhone !== void 0) record.primaryPhone = normalizeOptionalString(parsed.primaryPhone);
        if (parsed.status !== void 0) {
          record.status = normalizeOptionalString(parsed.status);
        }
        if (parsed.lifecycleStage !== void 0) record.lifecycleStage = normalizeOptionalString(parsed.lifecycleStage);
        if (parsed.source !== void 0) {
          record.source = normalizeOptionalString(parsed.source);
        }
        if (parsed.isActive !== void 0) record.isActive = parsed.isActive;
        if (parsed.nextInteraction) {
          record.nextInteractionAt = parsed.nextInteraction.at;
          record.nextInteractionName = parsed.nextInteraction.name.trim();
          record.nextInteractionRefId = normalizeOptionalString(parsed.nextInteraction.refId) ?? null;
          record.nextInteractionIcon = normalizeOptionalString(parsed.nextInteraction.icon);
          record.nextInteractionColor = normalizeHexColor(parsed.nextInteraction.color);
        } else if (parsed.nextInteraction === null) {
          record.nextInteractionAt = null;
          record.nextInteractionName = null;
          record.nextInteractionRefId = null;
          record.nextInteractionIcon = null;
          record.nextInteractionColor = null;
        }
        if (parsed.firstName !== void 0) profile.firstName = normalizeOptionalString(parsed.firstName);
        if (parsed.lastName !== void 0) profile.lastName = normalizeOptionalString(parsed.lastName);
        if (parsed.preferredName !== void 0) profile.preferredName = normalizeOptionalString(parsed.preferredName);
        if (parsed.jobTitle !== void 0) {
          profile.jobTitle = normalizeOptionalString(parsed.jobTitle);
        }
        if (parsed.department !== void 0) profile.department = normalizeOptionalString(parsed.department);
        if (parsed.seniority !== void 0) profile.seniority = normalizeOptionalString(parsed.seniority);
        if (parsed.timezone !== void 0) profile.timezone = normalizeOptionalString(parsed.timezone);
        if (parsed.linkedInUrl !== void 0) profile.linkedInUrl = normalizeOptionalString(parsed.linkedInUrl);
        if (parsed.twitterUrl !== void 0) profile.twitterUrl = normalizeOptionalString(parsed.twitterUrl);
        const profileFieldsUpdated = [
          parsed.firstName,
          parsed.lastName,
          parsed.preferredName,
          parsed.jobTitle,
          parsed.department,
          parsed.seniority,
          parsed.timezone,
          parsed.linkedInUrl,
          parsed.twitterUrl,
          parsed.companyEntityId
        ].some((v) => v !== void 0);
        if (profileFieldsUpdated) {
          record.updatedAt = /* @__PURE__ */ new Date();
        }
        if (parsed.displayName !== void 0) {
          record.displayName = parsed.displayName.trim();
        }
      },
      async () => {
        if (parsed.status !== void 0) {
          const normalizedStatus = normalizeOptionalString(parsed.status);
          if (normalizedStatus) {
            await ensureDictionaryEntry(em, {
              tenantId: record.tenantId,
              organizationId: record.organizationId,
              kind: "status",
              value: normalizedStatus
            });
          }
        }
        if (parsed.source !== void 0) {
          const normalizedSource = normalizeOptionalString(parsed.source);
          if (normalizedSource) {
            await ensureDictionaryEntry(em, {
              tenantId: record.tenantId,
              organizationId: record.organizationId,
              kind: "source",
              value: normalizedSource
            });
          }
        }
        if (parsed.jobTitle !== void 0) {
          const normalizedJobTitle = normalizeOptionalString(parsed.jobTitle);
          if (normalizedJobTitle) {
            await ensureDictionaryEntry(em, {
              tenantId: record.tenantId,
              organizationId: record.organizationId,
              kind: "job_title",
              value: normalizedJobTitle
            });
          }
        }
      },
      async () => {
        if (parsed.companyEntityId !== void 0) {
          await syncLegacyPrimaryCompanyLink(em, record, profile, parsed.companyEntityId);
        }
      },
      () => syncEntityTags(em, record, parsed.tags)
    ], { transaction: true });
    await setCustomFieldsForPerson(ctx, record.id, profile.id, record.organizationId, record.tenantId, custom);
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "updated",
      entity: record,
      identifiers: {
        id: profile.id ?? record.id,
        tenantId: record.tenantId,
        organizationId: record.organizationId
      },
      indexer: personCrudIndexer,
      events: personCrudEvents
    });
    await emitQueryIndexUpsertEvents(ctx, [personEntityIndexEntry(record)]);
    return { entityId: record.id, updatedAt: record.updatedAt };
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em").fork();
    return await loadPersonSnapshot(em, result.entityId);
  },
  buildLog: async ({ snapshots }) => {
    const { translate } = await resolveTranslations();
    const before = snapshots.before;
    if (!before) return null;
    const afterSnapshot = snapshots.after;
    if (afterSnapshot && snapshotsEqual(before, afterSnapshot)) {
      return { skipLog: true };
    }
    return {
      actionLabel: translate("customers.audit.people.update", "Update person"),
      resourceKind: "customers.person",
      resourceId: before.entity.id,
      tenantId: before.entity.tenantId,
      organizationId: before.entity.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 entity = await em.findOne(CustomerEntity, { id: before.entity.id });
    if (!entity) {
      let newEntity;
      let newProfile;
      await withAtomicFlush(em, [
        () => {
          newEntity = em.create(CustomerEntity, {
            id: before.entity.id,
            organizationId: before.entity.organizationId,
            tenantId: before.entity.tenantId,
            kind: "person",
            displayName: before.entity.displayName,
            description: before.entity.description,
            ownerUserId: before.entity.ownerUserId,
            primaryEmail: before.entity.primaryEmail,
            primaryPhone: before.entity.primaryPhone,
            status: before.entity.status,
            lifecycleStage: before.entity.lifecycleStage,
            source: before.entity.source,
            nextInteractionAt: before.entity.nextInteractionAt,
            nextInteractionName: before.entity.nextInteractionName,
            nextInteractionRefId: before.entity.nextInteractionRefId,
            nextInteractionIcon: before.entity.nextInteractionIcon,
            nextInteractionColor: before.entity.nextInteractionColor,
            isActive: before.entity.isActive
          });
          em.persist(newEntity);
          newProfile = em.create(CustomerPersonProfile, {
            id: before.profile.id,
            organizationId: before.entity.organizationId,
            tenantId: before.entity.tenantId,
            entity: newEntity,
            firstName: before.profile.firstName,
            lastName: before.profile.lastName,
            preferredName: before.profile.preferredName,
            jobTitle: before.profile.jobTitle,
            department: before.profile.department,
            seniority: before.profile.seniority,
            timezone: before.profile.timezone,
            linkedInUrl: before.profile.linkedInUrl,
            twitterUrl: before.profile.twitterUrl
          });
          em.persist(newProfile);
        },
        () => syncLegacyPrimaryCompanyLink(em, newEntity, newProfile, before.profile.companyEntityId),
        () => syncEntityTags(em, newEntity, before.tagIds)
      ], { transaction: true });
    } else {
      await withAtomicFlush(em, [
        () => {
          entity.displayName = before.entity.displayName;
          entity.description = before.entity.description;
          entity.ownerUserId = before.entity.ownerUserId;
          entity.primaryEmail = before.entity.primaryEmail;
          entity.primaryPhone = before.entity.primaryPhone;
          entity.status = before.entity.status;
          entity.lifecycleStage = before.entity.lifecycleStage;
          entity.source = before.entity.source;
          entity.nextInteractionAt = before.entity.nextInteractionAt;
          entity.nextInteractionName = before.entity.nextInteractionName;
          entity.nextInteractionRefId = before.entity.nextInteractionRefId;
          entity.nextInteractionIcon = before.entity.nextInteractionIcon;
          entity.nextInteractionColor = before.entity.nextInteractionColor;
          entity.isActive = before.entity.isActive;
        },
        async () => {
          const profile = await findOneWithDecryption(
            em,
            CustomerPersonProfile,
            { entity },
            void 0,
            { tenantId: before.entity.tenantId, organizationId: before.entity.organizationId }
          );
          if (profile) {
            profile.firstName = before.profile.firstName;
            profile.lastName = before.profile.lastName;
            profile.preferredName = before.profile.preferredName;
            profile.jobTitle = before.profile.jobTitle;
            profile.department = before.profile.department;
            profile.seniority = before.profile.seniority;
            profile.timezone = before.profile.timezone;
            profile.linkedInUrl = before.profile.linkedInUrl;
            profile.twitterUrl = before.profile.twitterUrl;
            await syncLegacyPrimaryCompanyLink(em, entity, profile, before.profile.companyEntityId);
          }
        },
        () => syncEntityTags(em, entity, before.tagIds)
      ], { transaction: true });
    }
    const indexedEntity = await em.findOne(CustomerEntity, { id: before.entity.id });
    const de = ctx.container.resolve("dataEngine");
    await emitCrudUndoSideEffects({
      dataEngine: de,
      action: "updated",
      entity: indexedEntity,
      identifiers: {
        id: before.profile.id ?? before.entity.id,
        organizationId: before.entity.organizationId,
        tenantId: before.entity.tenantId
      },
      indexer: personCrudIndexer,
      events: personCrudEvents
    });
    if (indexedEntity) {
      await emitQueryIndexUpsertEvents(ctx, [personEntityIndexEntry(indexedEntity)]);
    }
    const resetValues = buildCustomFieldResetMap(before.custom, payload?.after?.custom);
    if (Object.keys(resetValues).length) {
      await setCustomFieldsForPerson(ctx, before.entity.id, before.profile.id, before.entity.organizationId, before.entity.tenantId, resetValues);
    }
  }
};
const deletePersonCommand = {
  id: "customers.people.delete",
  async prepare(input, ctx) {
    const id = requireId(input, "Person id required");
    const em = ctx.container.resolve("em").fork();
    const snapshot = await loadPersonSnapshot(em, id);
    return snapshot ? { before: snapshot } : {};
  },
  async execute(input, ctx) {
    const id = requireId(input, "Person id required");
    const em = ctx.container.resolve("em").fork();
    const snapshot = await loadPersonSnapshot(em, id);
    const entity = await em.findOne(CustomerEntity, { id, deletedAt: null });
    const record = assertFound(entity, "Person not found");
    ensureTenantScope(ctx, record.tenantId);
    ensureOrganizationScope(ctx, record.organizationId);
    const dealLinks = await em.count(CustomerDealPersonLink, { person: record });
    if (dealLinks > 0) {
      const { translate } = await resolveTranslations();
      throw buildPersonHasDependentsError(translate, { dealLinks });
    }
    const profile = await em.findOne(CustomerPersonProfile, { entity: record });
    await withAtomicFlush(em, [
      async () => {
        if (profile) em.remove(profile);
        await em.nativeDelete(CustomerAddress, { entity: record, organizationId: record.organizationId, tenantId: record.tenantId });
        await em.nativeDelete(CustomerComment, { entity: record, organizationId: record.organizationId, tenantId: record.tenantId });
        await em.nativeDelete(CustomerActivity, { entity: record, organizationId: record.organizationId, tenantId: record.tenantId });
        await em.nativeDelete(CustomerInteraction, { entity: record, organizationId: record.organizationId, tenantId: record.tenantId });
        await em.nativeDelete(CustomerTodoLink, { entity: record, organizationId: record.organizationId, tenantId: record.tenantId });
        await em.nativeDelete(CustomerTagAssignment, { entity: record, organizationId: record.organizationId, tenantId: record.tenantId });
        await em.nativeDelete(CustomerDealPersonLink, { person: record });
        await em.nativeDelete(CustomerPersonCompanyLink, { person: record });
        if (profile) {
          await em.nativeDelete(CustomFieldValue, { entityId: PERSON_ENTITY_ID, recordId: profile.id });
        }
        await em.nativeDelete(CustomFieldValue, { entityId: CUSTOMER_ENTITY_ID, recordId: record.id });
        em.remove(record);
      }
    ], { transaction: true });
    const indexDeletes = [];
    const dealUpserts = [];
    if (snapshot) {
      for (const activity of snapshot.activities ?? []) {
        indexDeletes.push({
          entityType: E.customers.customer_activity,
          recordId: activity.id,
          tenantId: record.tenantId,
          organizationId: record.organizationId
        });
      }
      for (const comment of snapshot.comments ?? []) {
        indexDeletes.push({
          entityType: E.customers.customer_comment,
          recordId: comment.id,
          tenantId: record.tenantId,
          organizationId: record.organizationId
        });
      }
      for (const address of snapshot.addresses ?? []) {
        indexDeletes.push({
          entityType: E.customers.customer_address,
          recordId: address.id,
          tenantId: record.tenantId,
          organizationId: record.organizationId
        });
      }
      for (const todo of snapshot.todos ?? []) {
        indexDeletes.push({
          entityType: E.customers.customer_todo_link,
          recordId: todo.id,
          tenantId: record.tenantId,
          organizationId: record.organizationId
        });
      }
      for (const interaction of snapshot.interactions ?? []) {
        indexDeletes.push({
          entityType: E.customers.customer_interaction,
          recordId: interaction.id,
          tenantId: record.tenantId,
          organizationId: record.organizationId
        });
      }
      for (const deal of snapshot.deals ?? []) {
        if (deal.dealId) {
          dealUpserts.push({
            entityType: E.customers.customer_deal,
            recordId: deal.dealId,
            tenantId: record.tenantId,
            organizationId: record.organizationId
          });
        }
      }
    }
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "deleted",
      entity: record,
      identifiers: {
        id: profile?.id ?? record.id,
        organizationId: record.organizationId,
        tenantId: record.tenantId
      },
      indexer: personCrudIndexer,
      events: personCrudEvents
    });
    await emitQueryIndexDeleteEvents(ctx, [personEntityIndexEntry(record)]);
    await emitQueryIndexDeleteEvents(ctx, indexDeletes);
    await emitQueryIndexUpsertEvents(ctx, dealUpserts);
    return { entityId: record.id };
  },
  buildLog: async ({ snapshots }) => {
    const before = snapshots.before;
    if (!before) return null;
    const { translate } = await resolveTranslations();
    return {
      actionLabel: translate("customers.audit.people.delete", "Delete person"),
      resourceKind: "customers.person",
      resourceId: before.entity.id,
      tenantId: before.entity.tenantId,
      organizationId: before.entity.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 de = ctx.container.resolve("dataEngine");
    const decryptionScope = {
      tenantId: before.entity.tenantId ?? null,
      organizationId: before.entity.organizationId ?? null
    };
    await em.begin();
    let txResult;
    try {
      txResult = await (async () => {
        let entity2 = await findOneWithDecryption(
          em,
          CustomerEntity,
          {
            id: before.entity.id,
            tenantId: before.entity.tenantId,
            organizationId: before.entity.organizationId
          },
          {},
          decryptionScope
        );
        if (!entity2) {
          entity2 = em.create(CustomerEntity, {
            id: before.entity.id,
            organizationId: before.entity.organizationId,
            tenantId: before.entity.tenantId,
            kind: "person",
            displayName: before.entity.displayName,
            description: before.entity.description,
            ownerUserId: before.entity.ownerUserId,
            primaryEmail: before.entity.primaryEmail,
            primaryPhone: before.entity.primaryPhone,
            status: before.entity.status,
            lifecycleStage: before.entity.lifecycleStage,
            source: before.entity.source,
            nextInteractionAt: before.entity.nextInteractionAt,
            nextInteractionName: before.entity.nextInteractionName,
            nextInteractionRefId: before.entity.nextInteractionRefId,
            nextInteractionIcon: before.entity.nextInteractionIcon,
            nextInteractionColor: before.entity.nextInteractionColor,
            isActive: before.entity.isActive
          });
          em.persist(entity2);
        }
        entity2.displayName = before.entity.displayName;
        entity2.description = before.entity.description;
        entity2.ownerUserId = before.entity.ownerUserId;
        entity2.primaryEmail = before.entity.primaryEmail;
        entity2.primaryPhone = before.entity.primaryPhone;
        entity2.status = before.entity.status;
        entity2.lifecycleStage = before.entity.lifecycleStage;
        entity2.source = before.entity.source;
        entity2.nextInteractionAt = before.entity.nextInteractionAt;
        entity2.nextInteractionName = before.entity.nextInteractionName;
        entity2.nextInteractionRefId = before.entity.nextInteractionRefId;
        entity2.nextInteractionIcon = before.entity.nextInteractionIcon;
        entity2.nextInteractionColor = before.entity.nextInteractionColor;
        entity2.isActive = before.entity.isActive;
        entity2.deletedAt = null;
        let profile2 = await findOneWithDecryption(
          em,
          CustomerPersonProfile,
          {
            entity: entity2,
            tenantId: before.entity.tenantId,
            organizationId: before.entity.organizationId
          },
          {},
          decryptionScope
        );
        if (!profile2) {
          profile2 = em.create(CustomerPersonProfile, {
            id: before.profile.id,
            organizationId: before.entity.organizationId,
            tenantId: before.entity.tenantId,
            entity: entity2,
            firstName: before.profile.firstName,
            lastName: before.profile.lastName,
            preferredName: before.profile.preferredName,
            jobTitle: before.profile.jobTitle,
            department: before.profile.department,
            seniority: before.profile.seniority,
            timezone: before.profile.timezone,
            linkedInUrl: before.profile.linkedInUrl,
            twitterUrl: before.profile.twitterUrl
          });
        } else {
          profile2.firstName = before.profile.firstName;
          profile2.lastName = before.profile.lastName;
          profile2.preferredName = before.profile.preferredName;
          profile2.jobTitle = before.profile.jobTitle;
          profile2.department = before.profile.department;
          profile2.seniority = before.profile.seniority;
          profile2.timezone = before.profile.timezone;
          profile2.linkedInUrl = before.profile.linkedInUrl;
          profile2.twitterUrl = before.profile.twitterUrl;
        }
        await syncLegacyPrimaryCompanyLink(em, entity2, profile2, before.profile.companyEntityId);
        await em.flush();
        await em.nativeDelete(CustomerPersonCompanyLink, { person: entity2 });
        for (const companyLink of before.companies ?? []) {
          const company = await resolveCompanyReference(
            em,
            companyLink.companyId,
            before.entity.organizationId,
            before.entity.tenantId
          );
          if (!company) continue;
          const restoredLink = em.create(CustomerPersonCompanyLink, {
            id: companyLink.linkId ?? void 0,
            organizationId: before.entity.organizationId,
            tenantId: before.entity.tenantId,
            person: entity2,
            company,
            isPrimary: companyLink.isPrimary
          });
          em.persist(restoredLink);
          if (companyLink.isPrimary) {
            profile2.company = company;
          }
        }
        await withAtomicFlush(em, [
          () => syncEntityTags(em, entity2, before.tagIds)
        ]);
        const beforeActivities = before.activities ?? [];
        const beforeTodos = before.todos ?? [];
        const beforeInteractions = before.interactions ?? [];
        const relatedDealIds = /* @__PURE__ */ new Set();
        for (const link of before.deals) relatedDealIds.add(link.dealId);
        for (const activity of beforeActivities) {
          if (activity.dealId) relatedDealIds.add(activity.dealId);
        }
        for (const comment of before.comments) {
          if (comment.dealId) relatedDealIds.add(comment.dealId);
        }
        let dealMap = /* @__PURE__ */ new Map();
        if (relatedDealIds.size) {
          const deals = await em.find(CustomerDeal, {
            id: { $in: Array.from(relatedDealIds) },
            organizationId: entity2.organizationId,
            tenantId: entity2.tenantId
          });
          dealMap = new Map(deals.map((deal) => [deal.id, deal]));
        }
        await em.nativeDelete(CustomerDealPersonLink, { person: entity2 });
        for (const link of before.deals) {
          const deal = dealMap.get(link.dealId);
          if (!deal) continue;
          const restoredLink = em.create(CustomerDealPersonLink, {
            id: link.id,
            deal,
            person: entity2,
            participantRole: link.participantRole,
            createdAt: link.createdAt
          });
          em.persist(restoredLink);
        }
        await em.flush();
        await em.nativeDelete(CustomerActivity, { entity: entity2, organizationId: entity2.organizationId, tenantId: entity2.tenantId });
        for (const activity of beforeActivities) {
          const restoredActivity = em.create(CustomerActivity, {
            id: activity.id,
            organizationId: entity2.organizationId,
            tenantId: entity2.tenantId,
            entity: entity2,
            activityType: activity.activityType,
            subject: activity.subject,
            body: activity.body,
            occurredAt: activity.occurredAt,
            authorUserId: activity.authorUserId,
            appearanceIcon: activity.appearanceIcon,
            appearanceColor: activity.appearanceColor,
            deal: activity.dealId ? dealMap.get(activity.dealId) ?? null : null,
            createdAt: activity.createdAt,
            updatedAt: activity.updatedAt
          });
          em.persist(restoredActivity);
        }
        await em.flush();
        await em.nativeDelete(CustomerComment, { entity: entity2, organizationId: entity2.organizationId, tenantId: entity2.tenantId });
        for (const comment of before.comments) {
          const restoredComment = em.create(CustomerComment, {
            id: comment.id,
            organizationId: entity2.organizationId,
            tenantId: entity2.tenantId,
            entity: entity2,
            body: comment.body,
            authorUserId: comment.authorUserId,
            appearanceIcon: comment.appearanceIcon,
            appearanceColor: comment.appearanceColor,
            deal: comment.dealId ? dealMap.get(comment.dealId) ?? null : null,
            createdAt: comment.createdAt,
            updatedAt: comment.updatedAt,
            deletedAt: comment.deletedAt
          });
          em.persist(restoredComment);
        }
        await em.flush();
        await em.nativeDelete(CustomerAddress, { entity: entity2, organizationId: entity2.organizationId, tenantId: entity2.tenantId });
        for (const address of before.addresses) {
          const restoredAddress = em.create(CustomerAddress, {
            id: address.id,
            organizationId: entity2.organizationId,
            tenantId: entity2.tenantId,
            entity: entity2,
            name: address.name,
            purpose: address.purpose,
            addressLine1: address.addressLine1,
            addressLine2: address.addressLine2,
            city: address.city,
            region: address.region,
            postalCode: address.postalCode,
            country: address.country,
            latitude: address.latitude,
            longitude: address.longitude,
            isPrimary: address.isPrimary
          });
          em.persist(restoredAddress);
        }
        await em.flush();
        await em.nativeDelete(CustomerTodoLink, { entity: entity2, organizationId: entity2.organizationId, tenantId: entity2.tenantId });
        for (const todo of beforeTodos) {
          const restoredTodo = em.create(CustomerTodoLink, {
            id: todo.id,
            organizationId: entity2.organizationId,
            tenantId: entity2.tenantId,
            entity: entity2,
            todoId: todo.todoId,
            todoSource: todo.todoSource,
            createdAt: todo.createdAt,
            createdByUserId: todo.createdByUserId
          });
          em.persist(restoredTodo);
        }
        await em.flush();
        await em.nativeDelete(CustomerInteraction, { entity: entity2, organizationId: entity2.organizationId, tenantId: entity2.tenantId });
        for (const interaction of beforeInteractions) {
          const restoredInteraction = em.create(CustomerInteraction, {
            id: interaction.id,
            organizationId: entity2.organizationId,
            tenantId: entity2.tenantId,
            entity: entity2,
            interactionType: interaction.interactionType,
            title: interaction.title,
            body: interaction.body,
            status: interaction.status,
            scheduledAt: interaction.scheduledAt,
            occurredAt: interaction.occurredAt,
            priority: interaction.priority,
            authorUserId: interaction.authorUserId,
            ownerUserId: interaction.ownerUserId,
            dealId: interaction.dealId,
            source: interaction.source,
            appearanceIcon: interaction.appearanceIcon,
            appearanceColor: interaction.appearanceColor,
            createdAt: interaction.createdAt,
            updatedAt: interaction.updatedAt,
            deletedAt: interaction.deletedAt
          });
          em.persist(restoredInteraction);
        }
        await em.flush();
        return { entity: entity2, profile: profile2, beforeInteractions, beforeTodos };
      })();
      await em.commit();
    } catch (err) {
      try {
        await em.rollback();
      } catch {
      }
      throw err;
    }
    for (const interaction of txResult.beforeInteractions) {
      if (!interaction.custom || !Object.keys(interaction.custom).length) continue;
      await setCustomFieldsIfAny({
        dataEngine: de,
        entityId: INTERACTION_ENTITY_ID,
        recordId: interaction.id,
        organizationId: txResult.entity.organizationId,
        tenantId: txResult.entity.tenantId,
        values: interaction.custom,
        notify: false
      });
    }
    const entity = txResult.entity;
    const profile = txResult.profile;
    await emitCrudUndoSideEffects({
      dataEngine: de,
      action: "created",
      entity,
      identifiers: {
        id: profile.id ?? entity.id,
        organizationId: entity.organizationId,
        tenantId: entity.tenantId
      },
      indexer: personCrudIndexer,
      events: personCrudEvents
    });
    const upsertEntries = [];
    for (const activity of before.activities ?? []) {
      upsertEntries.push({
        entityType: E.customers.customer_activity,
        recordId: activity.id,
        tenantId: entity.tenantId,
        organizationId: entity.organizationId
      });
    }
    for (const comment of before.comments ?? []) {
      upsertEntries.push({
        entityType: E.customers.customer_comment,
        recordId: comment.id,
        tenantId: entity.tenantId,
        organizationId: entity.organizationId
      });
    }
    for (const address of before.addresses ?? []) {
      upsertEntries.push({
        entityType: E.customers.customer_address,
        recordId: address.id,
        tenantId: entity.tenantId,
        organizationId: entity.organizationId
      });
    }
    for (const todo of txResult.beforeTodos ?? []) {
      upsertEntries.push({
        entityType: E.customers.customer_todo_link,
        recordId: todo.id,
        tenantId: entity.tenantId,
        organizationId: entity.organizationId
      });
    }
    for (const interaction of txResult.beforeInteractions ?? []) {
      upsertEntries.push({
        entityType: E.customers.customer_interaction,
        recordId: interaction.id,
        tenantId: entity.tenantId,
        organizationId: entity.organizationId
      });
    }
    const dealUpserts = [];
    for (const deal of before.deals ?? []) {
      if (deal.dealId) {
        dealUpserts.push({
          entityType: E.customers.customer_deal,
          recordId: deal.dealId,
          tenantId: entity.tenantId,
          organizationId: entity.organizationId
        });
      }
    }
    const resetValues = buildCustomFieldResetMap(before.custom, void 0);
    if (Object.keys(resetValues).length) {
      await setCustomFieldsForPerson(ctx, entity.id, profile.id, entity.organizationId, entity.tenantId, resetValues);
    }
    await emitQueryIndexUpsertEvents(ctx, [personEntityIndexEntry(entity)]);
    await emitQueryIndexUpsertEvents(ctx, upsertEntries);
    await emitQueryIndexUpsertEvents(ctx, dealUpserts);
  }
};
registerCommand(createPersonCommand);
registerCommand(updatePersonCommand);
registerCommand(deletePersonCommand);
//# sourceMappingURL=people.js.map
