import { registerCommand } from "@open-mercato/shared/lib/commands";
import { emitCrudSideEffects, emitCrudUndoSideEffects, buildChanges, requireId } from "@open-mercato/shared/lib/commands/helpers";
import { CustomerAddress } from "../data/entities.js";
import { addressCreateSchema, addressUpdateSchema } from "../data/validators.js";
import {
  ensureOrganizationScope,
  ensureTenantScope,
  requireCustomerEntity,
  ensureSameScope,
  extractUndoPayload,
  resolveParentResourceKind
} from "./shared.js";
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
import { E } from "../../../generated/entities.ids.generated.js";
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
import { resolveRedoSnapshot } from "@open-mercato/shared/lib/commands/redo";
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
const addressCrudIndexer = {
  entityType: E.customers.customer_address
};
const addressCrudEvents = {
  module: "customers",
  entity: "address",
  persistent: true,
  buildPayload: (ctx) => ({
    id: ctx.identifiers.id,
    organizationId: ctx.identifiers.organizationId,
    tenantId: ctx.identifiers.tenantId
  })
};
async function loadAddressSnapshot(em, id) {
  const address = await em.findOne(CustomerAddress, { id }, { populate: ["entity"] });
  if (!address) return null;
  const entityRef = address.entity;
  const entityKind = typeof entityRef === "object" && entityRef !== null && "kind" in entityRef ? entityRef.kind : null;
  return {
    id: address.id,
    organizationId: address.organizationId,
    tenantId: address.tenantId,
    entityId: typeof entityRef === "string" ? entityRef : entityRef.id,
    entityKind,
    name: address.name ?? null,
    purpose: address.purpose ?? null,
    companyName: address.companyName ?? null,
    addressLine1: address.addressLine1,
    addressLine2: address.addressLine2 ?? null,
    buildingNumber: address.buildingNumber ?? null,
    flatNumber: address.flatNumber ?? 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
  };
}
async function enforcePrimaryAddress(em, entityId, addressId) {
  await em.nativeUpdate(
    CustomerAddress,
    { entity: entityId, id: { $ne: addressId }, isPrimary: true },
    { isPrimary: false }
  );
}
const createAddressCommand = {
  id: "customers.addresses.create",
  async execute(rawInput, ctx) {
    const parsed = addressCreateSchema.parse(rawInput);
    ensureTenantScope(ctx, parsed.tenantId);
    ensureOrganizationScope(ctx, parsed.organizationId);
    const em = ctx.container.resolve("em").fork();
    const entity = await requireCustomerEntity(em, parsed.entityId, { tenantId: parsed.tenantId, organizationId: parsed.organizationId }, void 0, "Customer not found");
    ensureSameScope(entity, parsed.organizationId, parsed.tenantId);
    const address = em.create(CustomerAddress, {
      organizationId: parsed.organizationId,
      tenantId: parsed.tenantId,
      entity,
      name: parsed.name ?? null,
      purpose: parsed.purpose ?? null,
      companyName: parsed.companyName ?? null,
      addressLine1: parsed.addressLine1,
      addressLine2: parsed.addressLine2 ?? null,
      buildingNumber: parsed.buildingNumber ?? null,
      flatNumber: parsed.flatNumber ?? null,
      city: parsed.city ?? null,
      region: parsed.region ?? null,
      postalCode: parsed.postalCode ?? null,
      country: parsed.country ?? null,
      latitude: parsed.latitude ?? null,
      longitude: parsed.longitude ?? null,
      isPrimary: parsed.isPrimary ?? false,
      createdAt: /* @__PURE__ */ new Date(),
      updatedAt: /* @__PURE__ */ new Date()
    });
    await withAtomicFlush(em, [
      async () => {
        em.persist(address);
        await em.flush();
        if (address.isPrimary) {
          await enforcePrimaryAddress(em, entity.id, address.id);
        }
      }
    ], { transaction: true });
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "created",
      entity: address,
      identifiers: {
        id: address.id,
        organizationId: address.organizationId,
        tenantId: address.tenantId
      },
      indexer: addressCrudIndexer,
      events: addressCrudEvents
    });
    return { addressId: address.id };
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em").fork();
    return await loadAddressSnapshot(em, result.addressId);
  },
  buildLog: async ({ result, snapshots }) => {
    const { translate } = await resolveTranslations();
    const snapshot = snapshots.after;
    return {
      actionLabel: translate("customers.audit.addresses.create", "Create address"),
      resourceKind: "customers.address",
      resourceId: result.addressId,
      parentResourceKind: resolveParentResourceKind(snapshot?.entityKind),
      parentResourceId: snapshot?.entityId ?? null,
      tenantId: snapshot?.tenantId ?? null,
      organizationId: snapshot?.organizationId ?? null,
      snapshotAfter: snapshot ?? null,
      payload: {
        undo: {
          after: snapshot ?? null
        }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const addressId = logEntry?.resourceId ?? null;
    if (!addressId) return;
    const em = ctx.container.resolve("em").fork();
    const address = await em.findOne(CustomerAddress, { id: addressId });
    if (address) {
      em.remove(address);
      await em.flush();
    }
  },
  redo: async ({ logEntry, ctx }) => {
    const after = resolveRedoSnapshot(logEntry);
    if (!after) {
      throw new CrudHttpError(400, { error: "[internal] redo snapshot unavailable for address create" });
    }
    const em = ctx.container.resolve("em").fork();
    const entity = await requireCustomerEntity(em, after.entityId, { tenantId: after.tenantId, organizationId: after.organizationId }, void 0, "Customer not found");
    let address = await findOneWithDecryption(
      em,
      CustomerAddress,
      { id: after.id },
      void 0,
      { tenantId: after.tenantId, organizationId: after.organizationId }
    );
    if (!address) {
      address = em.create(CustomerAddress, {
        id: after.id,
        organizationId: after.organizationId,
        tenantId: after.tenantId,
        entity,
        name: after.name,
        purpose: after.purpose,
        companyName: after.companyName,
        addressLine1: after.addressLine1,
        addressLine2: after.addressLine2,
        buildingNumber: after.buildingNumber,
        flatNumber: after.flatNumber,
        city: after.city,
        region: after.region,
        postalCode: after.postalCode,
        country: after.country,
        latitude: after.latitude,
        longitude: after.longitude,
        isPrimary: after.isPrimary,
        createdAt: /* @__PURE__ */ new Date(),
        updatedAt: /* @__PURE__ */ new Date()
      });
      em.persist(address);
    } else {
      address.entity = entity;
      address.name = after.name;
      address.purpose = after.purpose;
      address.companyName = after.companyName;
      address.addressLine1 = after.addressLine1;
      address.addressLine2 = after.addressLine2;
      address.buildingNumber = after.buildingNumber;
      address.flatNumber = after.flatNumber;
      address.city = after.city;
      address.region = after.region;
      address.postalCode = after.postalCode;
      address.country = after.country;
      address.latitude = after.latitude;
      address.longitude = after.longitude;
      address.isPrimary = after.isPrimary;
    }
    const restoredAddress = address;
    await withAtomicFlush(em, [
      async () => {
        em.persist(restoredAddress);
        await em.flush();
        if (after.isPrimary) {
          await enforcePrimaryAddress(em, after.entityId, after.id);
        }
      }
    ], { transaction: true });
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "created",
      entity: restoredAddress,
      identifiers: {
        id: restoredAddress.id,
        organizationId: restoredAddress.organizationId,
        tenantId: restoredAddress.tenantId
      },
      indexer: addressCrudIndexer,
      events: addressCrudEvents
    });
    return { addressId: restoredAddress.id };
  }
};
const updateAddressCommand = {
  id: "customers.addresses.update",
  async prepare(rawInput, ctx) {
    const parsed = addressUpdateSchema.parse(rawInput);
    const em = ctx.container.resolve("em");
    const snapshot = await loadAddressSnapshot(em, parsed.id);
    return snapshot ? { before: snapshot } : {};
  },
  async execute(rawInput, ctx) {
    const parsed = addressUpdateSchema.parse(rawInput);
    const em = ctx.container.resolve("em").fork();
    const address = await em.findOne(CustomerAddress, { id: parsed.id });
    if (!address) throw new CrudHttpError(404, { error: "Address not found" });
    ensureTenantScope(ctx, address.tenantId);
    ensureOrganizationScope(ctx, address.organizationId);
    if (parsed.entityId !== void 0) {
      const entity = await requireCustomerEntity(em, parsed.entityId, { tenantId: address.tenantId, organizationId: address.organizationId }, void 0, "Customer not found");
      ensureSameScope(entity, address.organizationId, address.tenantId);
      address.entity = entity;
    }
    await withAtomicFlush(em, [
      () => {
        if (parsed.name !== void 0) address.name = parsed.name ?? null;
        if (parsed.purpose !== void 0) address.purpose = parsed.purpose ?? null;
        if (parsed.companyName !== void 0) address.companyName = parsed.companyName ?? null;
        if (parsed.addressLine1 !== void 0) address.addressLine1 = parsed.addressLine1;
        if (parsed.addressLine2 !== void 0) address.addressLine2 = parsed.addressLine2 ?? null;
        if (parsed.buildingNumber !== void 0) address.buildingNumber = parsed.buildingNumber ?? null;
        if (parsed.flatNumber !== void 0) address.flatNumber = parsed.flatNumber ?? null;
        if (parsed.city !== void 0) address.city = parsed.city ?? null;
        if (parsed.region !== void 0) address.region = parsed.region ?? null;
        if (parsed.postalCode !== void 0) address.postalCode = parsed.postalCode ?? null;
        if (parsed.country !== void 0) address.country = parsed.country ?? null;
        if (parsed.latitude !== void 0) address.latitude = parsed.latitude ?? null;
        if (parsed.longitude !== void 0) address.longitude = parsed.longitude ?? null;
        if (parsed.isPrimary !== void 0) address.isPrimary = parsed.isPrimary;
      },
      async () => {
        if (address.isPrimary) {
          await enforcePrimaryAddress(em, typeof address.entity === "string" ? address.entity : address.entity.id, address.id);
        }
      }
    ], { transaction: true });
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "updated",
      entity: address,
      identifiers: {
        id: address.id,
        organizationId: address.organizationId,
        tenantId: address.tenantId
      },
      indexer: addressCrudIndexer,
      events: addressCrudEvents
    });
    return { addressId: address.id };
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em").fork();
    return await loadAddressSnapshot(em, result.addressId);
  },
  buildLog: async ({ snapshots }) => {
    const { translate } = await resolveTranslations();
    const before = snapshots.before;
    if (!before) return null;
    const afterSnapshot = snapshots.after;
    const changes = afterSnapshot && before ? buildChanges(
      before,
      afterSnapshot,
      [
        "entityId",
        "name",
        "purpose",
        "companyName",
        "addressLine1",
        "addressLine2",
        "buildingNumber",
        "flatNumber",
        "city",
        "region",
        "postalCode",
        "country",
        "latitude",
        "longitude",
        "isPrimary"
      ]
    ) : {};
    return {
      actionLabel: translate("customers.audit.addresses.update", "Update address"),
      resourceKind: "customers.address",
      resourceId: before.id,
      parentResourceKind: resolveParentResourceKind(before.entityKind),
      parentResourceId: before.entityId ?? null,
      tenantId: before.tenantId,
      organizationId: before.organizationId,
      snapshotBefore: before,
      snapshotAfter: afterSnapshot ?? null,
      changes,
      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();
    let address = await em.findOne(CustomerAddress, { id: before.id });
    const entity = await requireCustomerEntity(em, before.entityId, { tenantId: before.tenantId, organizationId: before.organizationId }, void 0, "Customer not found");
    if (!address) {
      address = em.create(CustomerAddress, {
        id: before.id,
        organizationId: before.organizationId,
        tenantId: before.tenantId,
        entity,
        name: before.name,
        purpose: before.purpose,
        companyName: before.companyName,
        addressLine1: before.addressLine1,
        addressLine2: before.addressLine2,
        buildingNumber: before.buildingNumber,
        flatNumber: before.flatNumber,
        city: before.city,
        region: before.region,
        postalCode: before.postalCode,
        country: before.country,
        latitude: before.latitude,
        longitude: before.longitude,
        isPrimary: before.isPrimary,
        createdAt: /* @__PURE__ */ new Date(),
        updatedAt: /* @__PURE__ */ new Date()
      });
      em.persist(address);
    } else {
      address.entity = entity;
      address.name = before.name;
      address.purpose = before.purpose;
      address.companyName = before.companyName;
      address.addressLine1 = before.addressLine1;
      address.addressLine2 = before.addressLine2;
      address.buildingNumber = before.buildingNumber;
      address.flatNumber = before.flatNumber;
      address.city = before.city;
      address.region = before.region;
      address.postalCode = before.postalCode;
      address.country = before.country;
      address.latitude = before.latitude;
      address.longitude = before.longitude;
      address.isPrimary = before.isPrimary;
    }
    await withAtomicFlush(em, [
      async () => {
        em.persist(address);
        await em.flush();
        if (before.isPrimary) {
          await enforcePrimaryAddress(em, before.entityId, before.id);
        }
      }
    ], { transaction: true });
    const de = ctx.container.resolve("dataEngine");
    await emitCrudUndoSideEffects({
      dataEngine: de,
      action: "updated",
      entity: address,
      identifiers: {
        id: address.id,
        organizationId: address.organizationId,
        tenantId: address.tenantId
      },
      indexer: addressCrudIndexer,
      events: addressCrudEvents
    });
  }
};
const deleteAddressCommand = {
  id: "customers.addresses.delete",
  async prepare(input, ctx) {
    const id = requireId(input, "Address id required");
    const em = ctx.container.resolve("em");
    const snapshot = await loadAddressSnapshot(em, id);
    return snapshot ? { before: snapshot } : {};
  },
  async execute(input, ctx) {
    const id = requireId(input, "Address id required");
    const em = ctx.container.resolve("em").fork();
    const address = await em.findOne(CustomerAddress, { id });
    if (!address) throw new CrudHttpError(404, { error: "Address not found" });
    ensureTenantScope(ctx, address.tenantId);
    ensureOrganizationScope(ctx, address.organizationId);
    em.remove(address);
    await em.flush();
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "deleted",
      entity: address,
      identifiers: {
        id: address.id,
        organizationId: address.organizationId,
        tenantId: address.tenantId
      },
      indexer: addressCrudIndexer,
      events: addressCrudEvents
    });
    return { addressId: address.id };
  },
  buildLog: async ({ snapshots }) => {
    const before = snapshots.before;
    if (!before) return null;
    const { translate } = await resolveTranslations();
    return {
      actionLabel: translate("customers.audit.addresses.delete", "Delete address"),
      resourceKind: "customers.address",
      resourceId: before.id,
      parentResourceKind: resolveParentResourceKind(before.entityKind),
      parentResourceId: before.entityId ?? null,
      tenantId: before.tenantId,
      organizationId: before.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 entity = await requireCustomerEntity(em, before.entityId, { tenantId: before.tenantId, organizationId: before.organizationId }, void 0, "Customer not found");
    let address = await em.findOne(CustomerAddress, { id: before.id });
    if (!address) {
      address = em.create(CustomerAddress, {
        id: before.id,
        organizationId: before.organizationId,
        tenantId: before.tenantId,
        entity,
        name: before.name,
        purpose: before.purpose,
        companyName: before.companyName,
        addressLine1: before.addressLine1,
        addressLine2: before.addressLine2,
        buildingNumber: before.buildingNumber,
        flatNumber: before.flatNumber,
        city: before.city,
        region: before.region,
        postalCode: before.postalCode,
        country: before.country,
        latitude: before.latitude,
        longitude: before.longitude,
        isPrimary: before.isPrimary,
        createdAt: /* @__PURE__ */ new Date(),
        updatedAt: /* @__PURE__ */ new Date()
      });
      em.persist(address);
    } else {
      address.entity = entity;
      address.name = before.name;
      address.purpose = before.purpose;
      address.addressLine1 = before.addressLine1;
      address.addressLine2 = before.addressLine2;
      address.buildingNumber = before.buildingNumber;
      address.flatNumber = before.flatNumber;
      address.city = before.city;
      address.region = before.region;
      address.postalCode = before.postalCode;
      address.country = before.country;
      address.latitude = before.latitude;
      address.longitude = before.longitude;
      address.isPrimary = before.isPrimary;
    }
    await withAtomicFlush(em, [
      async () => {
        em.persist(address);
        await em.flush();
        if (before.isPrimary) {
          await enforcePrimaryAddress(em, before.entityId, before.id);
        }
      }
    ], { transaction: true });
    const de = ctx.container.resolve("dataEngine");
    await emitCrudUndoSideEffects({
      dataEngine: de,
      action: "created",
      entity: address,
      identifiers: {
        id: address.id,
        organizationId: address.organizationId,
        tenantId: address.tenantId
      },
      indexer: addressCrudIndexer,
      events: addressCrudEvents
    });
  }
};
registerCommand(createAddressCommand);
registerCommand(updateAddressCommand);
registerCommand(deleteAddressCommand);
//# sourceMappingURL=addresses.js.map
