import { registerCommand } from "@open-mercato/shared/lib/commands";
import {
  parseWithCustomFields,
  setCustomFieldsIfAny,
  emitCrudSideEffects,
  emitCrudUndoSideEffects,
  requireId,
  normalizeAuthorUserId
} from "@open-mercato/shared/lib/commands/helpers";
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
import { resolveRedoSnapshot } from "@open-mercato/shared/lib/commands/redo";
import { runCrudCommandWrite } from "@open-mercato/shared/lib/commands/runCrudCommandWrite";
import {
  CustomerDeal,
  CustomerDealPersonLink,
  CustomerDealCompanyLink,
  CustomerDealStageTransition,
  CustomerPipelineStage
} from "../data/entities.js";
import {
  dealCreateSchema,
  dealUpdateSchema
} from "../data/validators.js";
import {
  ensureOrganizationScope,
  ensureTenantScope,
  requireCustomerEntity,
  ensureSameScope,
  extractUndoPayload,
  ensureDictionaryEntry
} from "./shared.js";
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
import {
  loadCustomFieldSnapshot,
  buildCustomFieldResetMap
} from "@open-mercato/shared/lib/commands/customFieldSnapshots";
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
import { E } from "../../../generated/entities.ids.generated.js";
import { findWithDecryption, findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { isMissingDealStageTransitionTable, warnMissingDealStageTransitionTable } from "../lib/dealStageTransitionTable.js";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("customers");
const DEAL_ENTITY_ID = "customers:customer_deal";
const dealCrudIndexer = {
  entityType: E.customers.customer_deal
};
const dealCrudEvents = {
  module: "customers",
  entity: "deal",
  persistent: true,
  buildPayload: (ctx) => ({
    id: ctx.identifiers.id,
    organizationId: ctx.identifiers.organizationId,
    tenantId: ctx.identifiers.tenantId
  })
};
function coerceSnapshotDate(value, fieldName) {
  if (value === void 0 || value === null) return null;
  if (value instanceof Date) return value;
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) {
    throw new Error(`[internal] Invalid ${fieldName} undo snapshot date: ${value}`);
  }
  return date;
}
function coerceRequiredSnapshotDate(value, fieldName) {
  const date = coerceSnapshotDate(value, fieldName);
  if (!date) {
    throw new Error(`[internal] Missing ${fieldName} undo snapshot date`);
  }
  return date;
}
async function loadPipelineStageSnapshot(em, pipelineStageId, tenantId, organizationId) {
  const stage = await findOneWithDecryption(em, CustomerPipelineStage, { id: pipelineStageId }, {}, { tenantId, organizationId });
  if (!stage) return null;
  return {
    id: stage.id,
    pipelineId: stage.pipelineId,
    label: stage.label,
    order: stage.order
  };
}
async function resolvePipelineStageValue(em, pipelineStageId, tenantId, organizationId) {
  const stage = await loadPipelineStageSnapshot(em, pipelineStageId, tenantId, organizationId);
  if (!stage) return null;
  const entry = await ensureDictionaryEntry(em, {
    tenantId,
    organizationId,
    kind: "pipeline_stage",
    value: stage.label
  });
  return entry?.value ?? stage.label;
}
function resolvePipelineAssignment(input) {
  const requestedPipelineId = input.pipelineId ?? null;
  const requestedPipelineStageId = input.pipelineStageId ?? null;
  if (requestedPipelineStageId && !input.stageSnapshot) {
    throw new CrudHttpError(400, { error: "Pipeline stage not found" });
  }
  if (requestedPipelineId && input.stageSnapshot && input.stageSnapshot.pipelineId !== requestedPipelineId) {
    throw new CrudHttpError(400, {
      error: "Pipeline stage does not belong to the selected pipeline"
    });
  }
  return {
    pipelineId: requestedPipelineId ?? input.stageSnapshot?.pipelineId ?? null,
    pipelineStageId: requestedPipelineStageId
  };
}
async function upsertDealStageTransition(em, input) {
  let existing = null;
  try {
    existing = await findOneWithDecryption(
      em,
      CustomerDealStageTransition,
      { deal: input.deal.id, stageId: input.stageId, deletedAt: null },
      {},
      { tenantId: input.deal.tenantId, organizationId: input.deal.organizationId }
    );
  } catch (error) {
    if (!isMissingDealStageTransitionTable(error)) {
      throw error;
    }
    warnMissingDealStageTransitionTable("customers.commands.deals.upsertTransition");
    return;
  }
  const transitionedAt = input.transitionedAt ?? /* @__PURE__ */ new Date();
  if (existing) {
    existing.pipelineId = input.pipelineId;
    existing.stageLabel = input.stageLabel;
    existing.stageOrder = input.stageOrder;
    existing.transitionedAt = transitionedAt;
    existing.transitionedByUserId = input.transitionedByUserId;
    existing.deletedAt = null;
    existing.isActive = true;
    return;
  }
  const transition = em.create(CustomerDealStageTransition, {
    organizationId: input.deal.organizationId,
    tenantId: input.deal.tenantId,
    deal: input.deal,
    pipelineId: input.pipelineId,
    stageId: input.stageId,
    stageLabel: input.stageLabel,
    stageOrder: input.stageOrder,
    transitionedAt,
    transitionedByUserId: input.transitionedByUserId,
    isActive: true
  });
  em.persist(transition);
}
async function deleteDealStageTransitions(em, deal) {
  try {
    await em.nativeDelete(CustomerDealStageTransition, { deal: deal.id });
  } catch (error) {
    if (!isMissingDealStageTransitionTable(error)) {
      throw error;
    }
    warnMissingDealStageTransitionTable("customers.commands.deals.deleteTransitions");
  }
}
async function restoreDealStageTransitions(em, deal, transitions) {
  if (!transitions.length) return;
  for (const transitionSnapshot of transitions) {
    const transition = em.create(CustomerDealStageTransition, {
      id: transitionSnapshot.id,
      organizationId: deal.organizationId,
      tenantId: deal.tenantId,
      deal,
      pipelineId: transitionSnapshot.pipelineId,
      stageId: transitionSnapshot.stageId,
      stageLabel: transitionSnapshot.stageLabel,
      stageOrder: transitionSnapshot.stageOrder,
      transitionedAt: coerceRequiredSnapshotDate(transitionSnapshot.transitionedAt, "transitionedAt"),
      transitionedByUserId: transitionSnapshot.transitionedByUserId,
      isActive: true
    });
    em.persist(transition);
  }
}
async function loadDealSnapshot(em, id) {
  const deal = await findOneWithDecryption(em, CustomerDeal, { id, deletedAt: null });
  if (!deal) return null;
  const decryptionScope = { tenantId: deal.tenantId ?? null, organizationId: deal.organizationId ?? null };
  const peopleLinks = await findWithDecryption(
    em,
    CustomerDealPersonLink,
    { deal },
    { populate: ["person"] },
    decryptionScope
  );
  const companyLinks = await findWithDecryption(
    em,
    CustomerDealCompanyLink,
    { deal },
    { populate: ["company"] },
    decryptionScope
  );
  const transitions = await findWithDecryption(
    em,
    CustomerDealStageTransition,
    { deal: deal.id, deletedAt: null },
    { orderBy: { stageOrder: "ASC", transitionedAt: "ASC" } },
    decryptionScope
  ).catch((error) => {
    if (!isMissingDealStageTransitionTable(error)) {
      throw error;
    }
    warnMissingDealStageTransitionTable("customers.commands.deals.loadSnapshot");
    return [];
  });
  const custom = await loadCustomFieldSnapshot(em, {
    entityId: DEAL_ENTITY_ID,
    recordId: deal.id,
    tenantId: deal.tenantId,
    organizationId: deal.organizationId
  });
  return {
    deal: {
      id: deal.id,
      organizationId: deal.organizationId,
      tenantId: deal.tenantId,
      title: deal.title,
      description: deal.description ?? null,
      status: deal.status,
      pipelineStage: deal.pipelineStage ?? null,
      pipelineId: deal.pipelineId ?? null,
      pipelineStageId: deal.pipelineStageId ?? null,
      valueAmount: deal.valueAmount ?? null,
      valueCurrency: deal.valueCurrency ?? null,
      probability: deal.probability ?? null,
      expectedCloseAt: deal.expectedCloseAt ?? null,
      ownerUserId: deal.ownerUserId ?? null,
      source: deal.source ?? null,
      closureOutcome: deal.closureOutcome ?? null,
      lossReasonId: deal.lossReasonId ?? null,
      lossNotes: deal.lossNotes ?? null
    },
    people: peopleLinks.map(
      (link) => typeof link.person === "string" ? link.person : link.person.id
    ),
    companies: companyLinks.map(
      (link) => typeof link.company === "string" ? link.company : link.company.id
    ),
    transitions: transitions.map((transition) => ({
      id: transition.id,
      pipelineId: transition.pipelineId,
      stageId: transition.stageId,
      stageLabel: transition.stageLabel,
      stageOrder: transition.stageOrder,
      transitionedAt: transition.transitionedAt,
      transitionedByUserId: transition.transitionedByUserId ?? null
    })),
    custom
  };
}
function toNumericString(value) {
  if (value === void 0 || value === null) return null;
  return value.toString();
}
async function syncDealPeople(em, deal, personIds) {
  if (personIds === void 0) return;
  await em.nativeDelete(CustomerDealPersonLink, { deal });
  if (!personIds || !personIds.length) return;
  const unique = Array.from(new Set(personIds));
  for (const personId of unique) {
    const person = await requireCustomerEntity(em, personId, { tenantId: deal.tenantId, organizationId: deal.organizationId }, "person", "Person not found");
    ensureSameScope(person, deal.organizationId, deal.tenantId);
    const link = em.create(CustomerDealPersonLink, {
      deal,
      person
    });
    em.persist(link);
  }
}
async function syncDealCompanies(em, deal, companyIds) {
  if (companyIds === void 0) return;
  await em.nativeDelete(CustomerDealCompanyLink, { deal });
  if (!companyIds || !companyIds.length) return;
  const unique = Array.from(new Set(companyIds));
  for (const companyId of unique) {
    const company = await requireCustomerEntity(em, companyId, { tenantId: deal.tenantId, organizationId: deal.organizationId }, "company", "Company not found");
    ensureSameScope(company, deal.organizationId, deal.tenantId);
    const link = em.create(CustomerDealCompanyLink, {
      deal,
      company
    });
    em.persist(link);
  }
}
const createDealCommand = {
  id: "customers.deals.create",
  async execute(rawInput, ctx) {
    const { parsed, custom } = parseWithCustomFields(dealCreateSchema, rawInput);
    ensureTenantScope(ctx, parsed.tenantId);
    ensureOrganizationScope(ctx, parsed.organizationId);
    const em = ctx.container.resolve("em").fork();
    const normalizedTransitionAuthorUserId = normalizeAuthorUserId(null, ctx.auth);
    let deal;
    let stageSnapshot = null;
    let pipelineAssignment = {
      pipelineId: null,
      pipelineStageId: null
    };
    let resolvedPipelineStageLabel = null;
    await withAtomicFlush(em, [
      async () => {
        stageSnapshot = parsed.pipelineStageId ? await loadPipelineStageSnapshot(em, parsed.pipelineStageId, parsed.tenantId, parsed.organizationId) : null;
        pipelineAssignment = resolvePipelineAssignment({
          pipelineId: parsed.pipelineId,
          pipelineStageId: parsed.pipelineStageId,
          stageSnapshot
        });
        resolvedPipelineStageLabel = stageSnapshot ? (await ensureDictionaryEntry(em, {
          tenantId: parsed.tenantId,
          organizationId: parsed.organizationId,
          kind: "pipeline_stage",
          value: stageSnapshot.label
        }))?.value ?? stageSnapshot.label : parsed.pipelineStage ?? null;
      },
      async () => {
        deal = em.create(CustomerDeal, {
          organizationId: parsed.organizationId,
          tenantId: parsed.tenantId,
          title: parsed.title,
          description: parsed.description ?? null,
          status: parsed.status ?? "open",
          pipelineStage: resolvedPipelineStageLabel,
          pipelineId: pipelineAssignment.pipelineId,
          pipelineStageId: pipelineAssignment.pipelineStageId,
          valueAmount: toNumericString(parsed.valueAmount),
          valueCurrency: parsed.valueCurrency ?? null,
          probability: parsed.probability ?? null,
          expectedCloseAt: parsed.expectedCloseAt ?? null,
          ownerUserId: parsed.ownerUserId ?? null,
          source: parsed.source ?? null,
          closureOutcome: parsed.closureOutcome ?? null,
          lossReasonId: parsed.lossReasonId ?? null,
          lossNotes: parsed.lossNotes ?? null
        });
        em.persist(deal);
        await em.flush();
      },
      async () => {
        const snapshot = stageSnapshot;
        if (!snapshot) return;
        await upsertDealStageTransition(em, {
          deal,
          pipelineId: snapshot.pipelineId,
          stageId: snapshot.id,
          stageLabel: snapshot.label,
          stageOrder: snapshot.order,
          transitionedByUserId: normalizedTransitionAuthorUserId
        });
      },
      () => syncDealPeople(em, deal, parsed.personIds ?? []),
      () => syncDealCompanies(em, deal, parsed.companyIds ?? [])
    ], { transaction: true });
    const de = ctx.container.resolve("dataEngine");
    await setCustomFieldsIfAny({
      dataEngine: de,
      entityId: DEAL_ENTITY_ID,
      recordId: deal.id,
      organizationId: deal.organizationId,
      tenantId: deal.tenantId,
      values: custom,
      notify: false
    });
    await emitCrudSideEffects({
      dataEngine: de,
      action: "created",
      entity: deal,
      identifiers: {
        id: deal.id,
        organizationId: deal.organizationId,
        tenantId: deal.tenantId
      },
      indexer: dealCrudIndexer,
      events: dealCrudEvents
    });
    return { dealId: deal.id };
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em").fork();
    return await loadDealSnapshot(em, result.dealId);
  },
  buildLog: async ({ result, ctx }) => {
    const { translate } = await resolveTranslations();
    const em = ctx.container.resolve("em").fork();
    const snapshot = await loadDealSnapshot(em, result.dealId);
    return {
      actionLabel: translate("customers.audit.deals.create", "Create deal"),
      resourceKind: "customers.deal",
      resourceId: result.dealId,
      tenantId: snapshot?.deal.tenantId ?? null,
      organizationId: snapshot?.deal.organizationId ?? null,
      snapshotAfter: snapshot ?? null,
      payload: {
        undo: {
          after: snapshot
        }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const dealId = logEntry?.resourceId;
    if (!dealId) return;
    const em = ctx.container.resolve("em").fork();
    const deal = await findOneWithDecryption(em, CustomerDeal, { id: dealId });
    if (!deal) return;
    await deleteDealStageTransitions(em, deal);
    await em.nativeDelete(CustomerDealPersonLink, { deal });
    await em.nativeDelete(CustomerDealCompanyLink, { deal });
    em.remove(deal);
    await em.flush();
  },
  redo: async ({ logEntry, ctx }) => {
    const after = resolveRedoSnapshot(logEntry);
    if (!after) {
      throw new CrudHttpError(400, { error: "[internal] redo snapshot unavailable for deal create" });
    }
    const em = ctx.container.resolve("em").fork();
    let deal = await findOneWithDecryption(em, CustomerDeal, { id: after.deal.id });
    if (!deal) {
      deal = em.create(CustomerDeal, {
        id: after.deal.id,
        organizationId: after.deal.organizationId,
        tenantId: after.deal.tenantId,
        title: after.deal.title,
        description: after.deal.description,
        status: after.deal.status,
        pipelineStage: after.deal.pipelineStage,
        pipelineId: after.deal.pipelineId,
        pipelineStageId: after.deal.pipelineStageId,
        valueAmount: after.deal.valueAmount,
        valueCurrency: after.deal.valueCurrency,
        probability: after.deal.probability,
        expectedCloseAt: after.deal.expectedCloseAt,
        ownerUserId: after.deal.ownerUserId,
        source: after.deal.source,
        closureOutcome: after.deal.closureOutcome,
        lossReasonId: after.deal.lossReasonId,
        lossNotes: after.deal.lossNotes
      });
      em.persist(deal);
    }
    const restoredDeal = deal;
    await withAtomicFlush(em, [
      () => syncDealPeople(em, restoredDeal, after.people),
      () => syncDealCompanies(em, restoredDeal, after.companies),
      () => deleteDealStageTransitions(em, restoredDeal),
      () => restoreDealStageTransitions(em, restoredDeal, after.transitions)
    ], { transaction: true });
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "created",
      entity: restoredDeal,
      identifiers: {
        id: restoredDeal.id,
        organizationId: restoredDeal.organizationId,
        tenantId: restoredDeal.tenantId
      },
      indexer: dealCrudIndexer,
      events: dealCrudEvents
    });
    const restoreValues = buildCustomFieldResetMap(after.custom, void 0);
    if (Object.keys(restoreValues).length) {
      await setCustomFieldsIfAny({
        dataEngine: de,
        entityId: DEAL_ENTITY_ID,
        recordId: restoredDeal.id,
        organizationId: restoredDeal.organizationId,
        tenantId: restoredDeal.tenantId,
        values: restoreValues,
        notify: false
      });
    }
    return { dealId: restoredDeal.id };
  }
};
const updateDealCommand = {
  id: "customers.deals.update",
  async prepare(rawInput, ctx) {
    const { parsed } = parseWithCustomFields(dealUpdateSchema, rawInput);
    const em = ctx.container.resolve("em").fork();
    const snapshot = await loadDealSnapshot(em, parsed.id);
    return snapshot ? { before: snapshot } : {};
  },
  async execute(rawInput, ctx) {
    const { parsed, custom } = parseWithCustomFields(dealUpdateSchema, rawInput);
    const em = ctx.container.resolve("em").fork();
    const deal = await findOneWithDecryption(em, CustomerDeal, { id: parsed.id, deletedAt: null });
    const record = deal ?? null;
    if (!record) throw new CrudHttpError(404, { error: "Deal not found" });
    ensureTenantScope(ctx, record.tenantId);
    ensureOrganizationScope(ctx, record.organizationId);
    const previousStatus = record.status;
    const previousPipelineStageId = record.pipelineStageId ?? null;
    const normalizedTransitionAuthorUserId = normalizeAuthorUserId(null, ctx.auth);
    let nextStageSnapshot = null;
    let nextPipelineAssignment = {
      pipelineId: record.pipelineId ?? null,
      pipelineStageId: record.pipelineStageId ?? null
    };
    let nextPipelineStageLabel = null;
    let resolvedCurrentPipelineStageLabel = null;
    await runCrudCommandWrite({
      ctx,
      em,
      entityId: DEAL_ENTITY_ID,
      action: "updated",
      scope: { tenantId: record.tenantId, organizationId: record.organizationId },
      customFields: custom,
      events: dealCrudEvents,
      indexer: dealCrudIndexer,
      sideEffect: () => ({
        entity: record,
        identifiers: {
          id: record.id,
          organizationId: record.organizationId,
          tenantId: record.tenantId
        }
      }),
      phases: [
        async () => {
          const pipelineAssignmentChanged = parsed.pipelineId !== void 0 || parsed.pipelineStageId !== void 0;
          const requestedPipelineStageId = parsed.pipelineStageId !== void 0 ? parsed.pipelineStageId ?? null : record.pipelineStageId ?? null;
          const requestedPipelineId = parsed.pipelineId !== void 0 ? parsed.pipelineId ?? null : record.pipelineId ?? null;
          nextStageSnapshot = requestedPipelineStageId && (pipelineAssignmentChanged || !record.pipelineStage) ? await loadPipelineStageSnapshot(em, requestedPipelineStageId, record.tenantId, record.organizationId) : null;
          if (pipelineAssignmentChanged) {
            nextPipelineAssignment = resolvePipelineAssignment({
              pipelineId: requestedPipelineId,
              pipelineStageId: requestedPipelineStageId,
              stageSnapshot: nextStageSnapshot
            });
          }
          nextPipelineStageLabel = nextStageSnapshot ? (await ensureDictionaryEntry(em, {
            tenantId: record.tenantId,
            organizationId: record.organizationId,
            kind: "pipeline_stage",
            value: nextStageSnapshot.label
          }))?.value ?? nextStageSnapshot.label : null;
          resolvedCurrentPipelineStageLabel = !nextStageSnapshot && record.pipelineStageId && (parsed.pipelineStageId !== void 0 || !record.pipelineStage) ? await resolvePipelineStageValue(em, record.pipelineStageId, record.tenantId, record.organizationId) : null;
        },
        () => {
          if (parsed.title !== void 0) record.title = parsed.title;
          if (parsed.description !== void 0) record.description = parsed.description ?? null;
          if (parsed.status !== void 0) record.status = parsed.status ?? record.status;
          if (parsed.pipelineStage !== void 0) record.pipelineStage = parsed.pipelineStage ?? null;
          if (parsed.pipelineId !== void 0 || parsed.pipelineStageId !== void 0 && nextStageSnapshot) {
            record.pipelineId = nextPipelineAssignment.pipelineId;
          }
          if (parsed.pipelineStageId !== void 0) record.pipelineStageId = nextPipelineAssignment.pipelineStageId;
          if (nextPipelineStageLabel && (parsed.pipelineStageId !== void 0 || !record.pipelineStage)) {
            record.pipelineStage = nextPipelineStageLabel;
          } else if (resolvedCurrentPipelineStageLabel && (parsed.pipelineStageId !== void 0 || !record.pipelineStage)) {
            record.pipelineStage = resolvedCurrentPipelineStageLabel;
          }
          if (parsed.valueAmount !== void 0) record.valueAmount = toNumericString(parsed.valueAmount);
          if (parsed.valueCurrency !== void 0) record.valueCurrency = parsed.valueCurrency ?? null;
          if (parsed.probability !== void 0) record.probability = parsed.probability ?? null;
          if (parsed.expectedCloseAt !== void 0) record.expectedCloseAt = parsed.expectedCloseAt ?? null;
          if (parsed.ownerUserId !== void 0) record.ownerUserId = parsed.ownerUserId ?? null;
          if (parsed.source !== void 0) record.source = parsed.source ?? null;
          if (parsed.closureOutcome !== void 0) record.closureOutcome = parsed.closureOutcome ?? null;
          if (parsed.lossReasonId !== void 0) record.lossReasonId = parsed.lossReasonId ?? null;
          if (parsed.lossNotes !== void 0) record.lossNotes = parsed.lossNotes ?? null;
        },
        async () => {
          await em.flush();
        },
        async () => {
          const snapshot = nextStageSnapshot;
          if (!snapshot) return;
          const shouldRecord = parsed.pipelineStageId !== void 0 && parsed.pipelineStageId !== null && parsed.pipelineStageId !== previousPipelineStageId;
          if (!shouldRecord) return;
          await upsertDealStageTransition(em, {
            deal: record,
            pipelineId: snapshot.pipelineId,
            stageId: snapshot.id,
            stageLabel: nextPipelineStageLabel ?? snapshot.label,
            stageOrder: snapshot.order,
            transitionedByUserId: normalizedTransitionAuthorUserId
          });
        },
        () => syncDealPeople(em, record, parsed.personIds),
        () => syncDealCompanies(em, record, parsed.companyIds)
      ]
    });
    const newStatus = record.status;
    const normalizedStatus = newStatus === "win" ? "won" : newStatus === "loose" ? "lost" : newStatus;
    if (previousStatus !== newStatus && (normalizedStatus === "won" || normalizedStatus === "lost")) {
      const closureEvent = normalizedStatus === "won" ? "customers.deal.won" : "customers.deal.lost";
      try {
        const eventBus = ctx.container.resolve("eventBus");
        if (eventBus) {
          await eventBus.emitEvent(
            closureEvent,
            {
              id: record.id,
              tenantId: record.tenantId,
              organizationId: record.organizationId,
              ownerUserId: record.ownerUserId ?? null,
              title: record.title,
              valueAmount: record.valueAmount ?? null,
              valueCurrency: record.valueCurrency ?? null
            },
            { persistent: true }
          );
        }
      } catch (err) {
        logger.warn("Deal closure event emit failed", { component: "deals.update", closureEvent, err });
      }
    }
    return { dealId: record.id };
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em").fork();
    return await loadDealSnapshot(em, result.dealId);
  },
  buildLog: async ({ result, snapshots, ctx }) => {
    const { translate } = await resolveTranslations();
    const before = snapshots.before;
    if (!before) return null;
    const em = ctx.container.resolve("em").fork();
    const afterSnapshot = await loadDealSnapshot(em, result.dealId);
    return {
      actionLabel: translate("customers.audit.deals.update", "Update deal"),
      resourceKind: "customers.deal",
      resourceId: before.deal.id,
      tenantId: before.deal.tenantId,
      organizationId: before.deal.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 normalizedTransitionAuthorUserId = normalizeAuthorUserId(null, ctx.auth);
    let deal = await findOneWithDecryption(em, CustomerDeal, { id: before.deal.id });
    if (!deal) {
      deal = em.create(CustomerDeal, {
        id: before.deal.id,
        organizationId: before.deal.organizationId,
        tenantId: before.deal.tenantId,
        title: before.deal.title,
        description: before.deal.description,
        status: before.deal.status,
        pipelineStage: before.deal.pipelineStage,
        pipelineId: before.deal.pipelineId,
        pipelineStageId: before.deal.pipelineStageId,
        valueAmount: before.deal.valueAmount,
        valueCurrency: before.deal.valueCurrency,
        probability: before.deal.probability,
        expectedCloseAt: coerceSnapshotDate(before.deal.expectedCloseAt, "expectedCloseAt"),
        ownerUserId: before.deal.ownerUserId,
        source: before.deal.source,
        closureOutcome: before.deal.closureOutcome,
        lossReasonId: before.deal.lossReasonId,
        lossNotes: before.deal.lossNotes
      });
      em.persist(deal);
    }
    const revertedStageSnapshot = before.deal.pipelineStageId ? await loadPipelineStageSnapshot(em, before.deal.pipelineStageId, before.deal.tenantId, before.deal.organizationId) : null;
    const existingTransition = before.deal.pipelineStageId ? before.transitions.find((transition) => transition.stageId === before.deal.pipelineStageId) ?? null : null;
    const shouldRecordRevertTransition = before.deal.pipelineStageId !== (payload?.after?.deal.pipelineStageId ?? null) && !!before.deal.pipelineStageId && !!(revertedStageSnapshot?.pipelineId ?? before.deal.pipelineId ?? existingTransition?.pipelineId) && !!(revertedStageSnapshot?.label ?? before.deal.pipelineStage ?? existingTransition?.stageLabel);
    await withAtomicFlush(em, [
      () => {
        deal.title = before.deal.title;
        deal.description = before.deal.description;
        deal.status = before.deal.status;
        deal.pipelineStage = before.deal.pipelineStage;
        deal.pipelineId = before.deal.pipelineId;
        deal.pipelineStageId = before.deal.pipelineStageId;
        deal.valueAmount = before.deal.valueAmount;
        deal.valueCurrency = before.deal.valueCurrency;
        deal.probability = before.deal.probability;
        deal.expectedCloseAt = coerceSnapshotDate(before.deal.expectedCloseAt, "expectedCloseAt");
        deal.ownerUserId = before.deal.ownerUserId;
        deal.source = before.deal.source;
        deal.closureOutcome = before.deal.closureOutcome;
        deal.lossReasonId = before.deal.lossReasonId;
        deal.lossNotes = before.deal.lossNotes;
      },
      async () => {
        await em.flush();
      },
      async () => {
        if (!shouldRecordRevertTransition || !before.deal.pipelineStageId) return;
        const pipelineId = revertedStageSnapshot?.pipelineId ?? before.deal.pipelineId ?? existingTransition?.pipelineId;
        const stageLabel = revertedStageSnapshot?.label ?? before.deal.pipelineStage ?? existingTransition?.stageLabel;
        if (!pipelineId || !stageLabel) return;
        await upsertDealStageTransition(em, {
          deal,
          pipelineId,
          stageId: before.deal.pipelineStageId,
          stageLabel,
          stageOrder: revertedStageSnapshot?.order ?? existingTransition?.stageOrder ?? 0,
          transitionedByUserId: normalizedTransitionAuthorUserId
        });
      },
      () => syncDealPeople(em, deal, before.people),
      () => syncDealCompanies(em, deal, before.companies)
    ], { transaction: true });
    const de = ctx.container.resolve("dataEngine");
    await emitCrudUndoSideEffects({
      dataEngine: de,
      action: "updated",
      entity: deal,
      identifiers: {
        id: deal.id,
        organizationId: deal.organizationId,
        tenantId: deal.tenantId
      },
      indexer: dealCrudIndexer,
      events: dealCrudEvents
    });
    const resetValues = buildCustomFieldResetMap(before.custom, payload?.after?.custom);
    if (Object.keys(resetValues).length) {
      await setCustomFieldsIfAny({
        dataEngine: de,
        entityId: DEAL_ENTITY_ID,
        recordId: deal.id,
        organizationId: deal.organizationId,
        tenantId: deal.tenantId,
        values: resetValues,
        notify: false
      });
    }
  }
};
const deleteDealCommand = {
  id: "customers.deals.delete",
  async prepare(input, ctx) {
    const id = requireId(input, "Deal id required");
    const em = ctx.container.resolve("em").fork();
    const snapshot = await loadDealSnapshot(em, id);
    return snapshot ? { before: snapshot } : {};
  },
  async execute(input, ctx) {
    const id = requireId(input, "Deal id required");
    const em = ctx.container.resolve("em").fork();
    const deal = await findOneWithDecryption(em, CustomerDeal, { id, deletedAt: null });
    const record = deal ?? null;
    if (!record) throw new CrudHttpError(404, { error: "Deal not found" });
    ensureTenantScope(ctx, record.tenantId);
    ensureOrganizationScope(ctx, record.organizationId);
    await deleteDealStageTransitions(em, record);
    await em.nativeDelete(CustomerDealPersonLink, { deal: record });
    await em.nativeDelete(CustomerDealCompanyLink, { deal: record });
    em.remove(record);
    await em.flush();
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "deleted",
      entity: record,
      identifiers: {
        id: record.id,
        organizationId: record.organizationId,
        tenantId: record.tenantId
      },
      indexer: dealCrudIndexer,
      events: dealCrudEvents
    });
    return { dealId: record.id };
  },
  buildLog: async ({ snapshots }) => {
    const before = snapshots.before;
    if (!before) return null;
    const { translate } = await resolveTranslations();
    return {
      actionLabel: translate("customers.audit.deals.delete", "Delete deal"),
      resourceKind: "customers.deal",
      resourceId: before.deal.id,
      tenantId: before.deal.tenantId,
      organizationId: before.deal.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();
    let deal = await findOneWithDecryption(em, CustomerDeal, { id: before.deal.id });
    if (!deal) {
      deal = em.create(CustomerDeal, {
        id: before.deal.id,
        organizationId: before.deal.organizationId,
        tenantId: before.deal.tenantId,
        title: before.deal.title,
        description: before.deal.description,
        status: before.deal.status,
        pipelineStage: before.deal.pipelineStage,
        pipelineId: before.deal.pipelineId,
        pipelineStageId: before.deal.pipelineStageId,
        valueAmount: before.deal.valueAmount,
        valueCurrency: before.deal.valueCurrency,
        probability: before.deal.probability,
        expectedCloseAt: coerceSnapshotDate(before.deal.expectedCloseAt, "expectedCloseAt"),
        ownerUserId: before.deal.ownerUserId,
        source: before.deal.source,
        closureOutcome: before.deal.closureOutcome,
        lossReasonId: before.deal.lossReasonId,
        lossNotes: before.deal.lossNotes
      });
      em.persist(deal);
    }
    await withAtomicFlush(em, [
      () => syncDealPeople(em, deal, before.people),
      () => syncDealCompanies(em, deal, before.companies),
      () => deleteDealStageTransitions(em, deal),
      () => restoreDealStageTransitions(em, deal, before.transitions)
    ], { transaction: true });
    const de = ctx.container.resolve("dataEngine");
    await emitCrudUndoSideEffects({
      dataEngine: de,
      action: "created",
      entity: deal,
      identifiers: {
        id: deal.id,
        organizationId: deal.organizationId,
        tenantId: deal.tenantId
      },
      indexer: dealCrudIndexer,
      events: dealCrudEvents
    });
    const resetValues = buildCustomFieldResetMap(before.custom, void 0);
    if (Object.keys(resetValues).length) {
      await setCustomFieldsIfAny({
        dataEngine: de,
        entityId: DEAL_ENTITY_ID,
        recordId: deal.id,
        organizationId: deal.organizationId,
        tenantId: deal.tenantId,
        values: resetValues,
        notify: false
      });
    }
  }
};
registerCommand(createDealCommand);
registerCommand(updateDealCommand);
registerCommand(deleteDealCommand);
//# sourceMappingURL=deals.js.map
