import { registerCommand } from "@open-mercato/shared/lib/commands";
import { emitCrudSideEffects, emitCrudUndoSideEffects } from "@open-mercato/shared/lib/commands/helpers";
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { CustomerEntity, CustomerEntityRole } from "../data/entities.js";
import {
  entityRoleCreateSchema,
  entityRoleUpdateSchema,
  entityRoleDeleteSchema
} from "../data/validators.js";
import {
  ensureOrganizationScope,
  ensureSameScope,
  ensureTenantScope,
  extractUndoPayload,
  requireCustomerEntity,
  resolveParentResourceKind
} from "./shared.js";
import { resolveRedoSnapshot } from "@open-mercato/shared/lib/commands/redo";
const entityRoleCrudEvents = {
  module: "customers",
  entity: "entity_role",
  persistent: true,
  buildPayload: (ctx) => ({
    id: ctx.identifiers.id,
    organizationId: ctx.identifiers.organizationId,
    tenantId: ctx.identifiers.tenantId,
    entityId: ctx.entity && typeof ctx.entity === "object" && "entityId" in ctx.entity ? ctx.entity.entityId : null,
    entityType: ctx.entity && typeof ctx.entity === "object" && "entityType" in ctx.entity ? ctx.entity.entityType : null,
    roleType: ctx.entity && typeof ctx.entity === "object" && "roleType" in ctx.entity ? ctx.entity.roleType : null,
    userId: ctx.entity && typeof ctx.entity === "object" && "userId" in ctx.entity ? ctx.entity.userId : null,
    ...ctx.syncOrigin ? { syncOrigin: ctx.syncOrigin } : {}
  })
};
function getRoleIdentifiers(role) {
  return {
    id: role.id,
    organizationId: role.organizationId,
    tenantId: role.tenantId
  };
}
async function loadEntityRoleSnapshot(em, id, scope) {
  const filter = { id, deletedAt: null };
  if (scope?.tenantId) filter.tenantId = scope.tenantId;
  if (scope?.organizationId) filter.organizationId = scope.organizationId;
  const role = await findOneWithDecryption(
    em,
    CustomerEntityRole,
    filter,
    void 0,
    { tenantId: scope?.tenantId ?? null, organizationId: scope?.organizationId ?? null }
  );
  if (!role) return null;
  const entity = await findOneWithDecryption(
    em,
    CustomerEntity,
    { id: role.entityId, tenantId: role.tenantId, deletedAt: null },
    void 0,
    { tenantId: role.tenantId, organizationId: role.organizationId }
  );
  return {
    role: {
      id: role.id,
      entityType: role.entityType,
      entityId: role.entityId,
      userId: role.userId,
      roleType: role.roleType,
      organizationId: role.organizationId,
      tenantId: role.tenantId
    },
    entityKind: entity?.kind === "company" || entity?.kind === "person" ? entity.kind : null
  };
}
const createEntityRoleCommand = {
  id: "customers.entityRoles.create",
  async execute(rawInput, ctx) {
    const parsed = entityRoleCreateSchema.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 }, parsed.entityType, "Customer not found");
    ensureSameScope(entity, parsed.organizationId, parsed.tenantId);
    const activeExisting = await findOneWithDecryption(
      em,
      CustomerEntityRole,
      {
        entityType: parsed.entityType,
        entityId: parsed.entityId,
        roleType: parsed.roleType,
        organizationId: parsed.organizationId,
        tenantId: parsed.tenantId,
        deletedAt: null
      },
      void 0,
      { tenantId: parsed.tenantId, organizationId: parsed.organizationId }
    );
    if (activeExisting) {
      throw new CrudHttpError(409, { error: "Role type already assigned for this entity" });
    }
    const softDeleted = await findOneWithDecryption(
      em,
      CustomerEntityRole,
      {
        entityType: parsed.entityType,
        entityId: parsed.entityId,
        roleType: parsed.roleType,
        organizationId: parsed.organizationId,
        tenantId: parsed.tenantId
      },
      void 0,
      { tenantId: parsed.tenantId, organizationId: parsed.organizationId }
    );
    let role;
    let wasUndelete = false;
    let previousUserId = null;
    let previousDeletedAt = null;
    if (softDeleted) {
      wasUndelete = true;
      previousUserId = softDeleted.userId ?? null;
      previousDeletedAt = softDeleted.deletedAt instanceof Date ? softDeleted.deletedAt.toISOString() : null;
      softDeleted.deletedAt = null;
      softDeleted.userId = parsed.userId;
      role = softDeleted;
    } else {
      role = em.create(CustomerEntityRole, {
        entityType: parsed.entityType,
        entityId: parsed.entityId,
        userId: parsed.userId,
        roleType: parsed.roleType,
        organizationId: parsed.organizationId,
        tenantId: parsed.tenantId
      });
      em.persist(role);
    }
    await em.flush();
    const dataEngine = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine,
      action: "created",
      entity: role,
      identifiers: getRoleIdentifiers(role),
      syncOrigin: ctx.syncOrigin,
      events: entityRoleCrudEvents,
      indexer: { entityType: "customers:entity_role" }
    });
    return { roleId: role.id, wasUndelete, previousUserId, previousDeletedAt };
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em");
    return loadEntityRoleSnapshot(em, result.roleId, {
      tenantId: ctx.auth?.tenantId ?? null,
      organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null
    });
  },
  buildLog: async ({ result, snapshots }) => {
    const { translate } = await resolveTranslations();
    const snapshot = snapshots.after;
    return {
      actionLabel: translate("customers.audit.entityRoles.create", "Create entity role"),
      resourceKind: "customers.entityRole",
      resourceId: result.roleId,
      parentResourceKind: resolveParentResourceKind(snapshot?.entityKind),
      parentResourceId: snapshot?.role.entityId ?? null,
      tenantId: snapshot?.role.tenantId ?? null,
      organizationId: snapshot?.role.organizationId ?? null,
      snapshotAfter: snapshot ?? null,
      payload: {
        undo: {
          after: snapshot ?? null,
          wasUndelete: result.wasUndelete,
          previousUserId: result.previousUserId,
          previousDeletedAt: result.previousDeletedAt
        }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry);
    const after = payload?.after;
    if (!after) return;
    const em = ctx.container.resolve("em").fork();
    const role = await findOneWithDecryption(
      em,
      CustomerEntityRole,
      { id: after.role.id },
      void 0,
      { tenantId: after.role.tenantId, organizationId: after.role.organizationId }
    );
    if (!role) return;
    if (payload?.wasUndelete) {
      role.deletedAt = payload.previousDeletedAt ? new Date(payload.previousDeletedAt) : /* @__PURE__ */ new Date();
      if (payload.previousUserId) role.userId = payload.previousUserId;
    } else {
      role.deletedAt = /* @__PURE__ */ new Date();
    }
    await em.flush();
    const dataEngine = ctx.container.resolve("dataEngine");
    await emitCrudUndoSideEffects({
      dataEngine,
      action: "deleted",
      entity: role,
      identifiers: getRoleIdentifiers(role),
      syncOrigin: ctx.syncOrigin,
      events: entityRoleCrudEvents,
      indexer: { entityType: "customers:entity_role" }
    });
  },
  redo: async ({ logEntry, ctx }) => {
    const after = resolveRedoSnapshot(logEntry);
    if (!after) {
      throw new CrudHttpError(400, { error: "[internal] redo snapshot unavailable for entity role create" });
    }
    const em = ctx.container.resolve("em").fork();
    let role = await findOneWithDecryption(
      em,
      CustomerEntityRole,
      { id: after.role.id },
      void 0,
      { tenantId: after.role.tenantId, organizationId: after.role.organizationId }
    );
    if (!role) {
      role = em.create(CustomerEntityRole, {
        id: after.role.id,
        entityType: after.role.entityType,
        entityId: after.role.entityId,
        userId: after.role.userId,
        roleType: after.role.roleType,
        organizationId: after.role.organizationId,
        tenantId: after.role.tenantId
      });
      em.persist(role);
    } else {
      role.deletedAt = null;
      role.userId = after.role.userId;
    }
    await em.flush();
    const dataEngine = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine,
      action: "created",
      entity: role,
      identifiers: getRoleIdentifiers(role),
      syncOrigin: ctx.syncOrigin,
      events: entityRoleCrudEvents,
      indexer: { entityType: "customers:entity_role" }
    });
    return {
      roleId: role.id,
      wasUndelete: false,
      previousUserId: null,
      previousDeletedAt: null
    };
  }
};
const updateEntityRoleCommand = {
  id: "customers.entityRoles.update",
  async prepare(rawInput, ctx) {
    const parsed = entityRoleUpdateSchema.parse(rawInput);
    const em = ctx.container.resolve("em").fork();
    const snapshot = await loadEntityRoleSnapshot(em, parsed.id, {
      tenantId: ctx.auth?.tenantId ?? null,
      organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null
    });
    return snapshot ? { before: snapshot } : {};
  },
  async execute(rawInput, ctx) {
    const parsed = entityRoleUpdateSchema.parse(rawInput);
    ensureTenantScope(ctx, parsed.tenantId);
    ensureOrganizationScope(ctx, parsed.organizationId);
    const em = ctx.container.resolve("em").fork();
    const role = await findOneWithDecryption(
      em,
      CustomerEntityRole,
      { id: parsed.id, tenantId: parsed.tenantId, organizationId: parsed.organizationId, deletedAt: null },
      void 0,
      { tenantId: parsed.tenantId, organizationId: parsed.organizationId }
    );
    if (!role) {
      throw new CrudHttpError(404, { error: "Role not found" });
    }
    role.userId = parsed.userId;
    await em.flush();
    const dataEngine = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine,
      action: "updated",
      entity: role,
      identifiers: getRoleIdentifiers(role),
      syncOrigin: ctx.syncOrigin,
      events: entityRoleCrudEvents,
      indexer: { entityType: "customers:entity_role" }
    });
    return { roleId: role.id };
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em");
    return loadEntityRoleSnapshot(em, result.roleId, {
      tenantId: ctx.auth?.tenantId ?? null,
      organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null
    });
  },
  buildLog: async ({ result, snapshots }) => {
    const { translate } = await resolveTranslations();
    const before = snapshots.before;
    const after = snapshots.after;
    return {
      actionLabel: translate("customers.audit.entityRoles.update", "Update entity role"),
      resourceKind: "customers.entityRole",
      resourceId: result.roleId,
      parentResourceKind: resolveParentResourceKind(after?.entityKind ?? before?.entityKind),
      parentResourceId: after?.role.entityId ?? before?.role.entityId ?? null,
      tenantId: after?.role.tenantId ?? before?.role.tenantId ?? null,
      organizationId: after?.role.organizationId ?? before?.role.organizationId ?? null,
      snapshotBefore: before ?? null,
      snapshotAfter: after ?? null,
      payload: {
        undo: {
          before: before ?? null,
          after: after ?? null
        }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry);
    const before = payload?.before;
    if (!before) return;
    const em = ctx.container.resolve("em").fork();
    const role = await findOneWithDecryption(
      em,
      CustomerEntityRole,
      { id: before.role.id },
      void 0,
      { tenantId: before.role.tenantId, organizationId: before.role.organizationId }
    );
    if (!role) return;
    role.userId = before.role.userId;
    await em.flush();
    const dataEngine = ctx.container.resolve("dataEngine");
    await emitCrudUndoSideEffects({
      dataEngine,
      action: "updated",
      entity: role,
      identifiers: getRoleIdentifiers(role),
      syncOrigin: ctx.syncOrigin,
      events: entityRoleCrudEvents,
      indexer: { entityType: "customers:entity_role" }
    });
  }
};
const deleteEntityRoleCommand = {
  id: "customers.entityRoles.delete",
  async prepare(rawInput, ctx) {
    const parsed = entityRoleDeleteSchema.parse(rawInput);
    const em = ctx.container.resolve("em").fork();
    const snapshot = await loadEntityRoleSnapshot(em, parsed.id, {
      tenantId: ctx.auth?.tenantId ?? null,
      organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null
    });
    return snapshot ? { before: snapshot } : {};
  },
  async execute(rawInput, ctx) {
    const parsed = entityRoleDeleteSchema.parse(rawInput);
    ensureTenantScope(ctx, parsed.tenantId);
    ensureOrganizationScope(ctx, parsed.organizationId);
    const em = ctx.container.resolve("em").fork();
    const role = await findOneWithDecryption(
      em,
      CustomerEntityRole,
      { id: parsed.id, tenantId: parsed.tenantId, organizationId: parsed.organizationId, deletedAt: null },
      void 0,
      { tenantId: parsed.tenantId, organizationId: parsed.organizationId }
    );
    if (!role) {
      throw new CrudHttpError(404, { error: "Role not found" });
    }
    role.deletedAt = /* @__PURE__ */ new Date();
    await em.flush();
    const dataEngine = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine,
      action: "deleted",
      entity: role,
      identifiers: getRoleIdentifiers(role),
      syncOrigin: ctx.syncOrigin,
      events: entityRoleCrudEvents,
      indexer: { entityType: "customers:entity_role" }
    });
    return { roleId: role.id };
  },
  buildLog: async ({ result, snapshots }) => {
    const { translate } = await resolveTranslations();
    const before = snapshots.before;
    return {
      actionLabel: translate("customers.audit.entityRoles.delete", "Delete entity role"),
      resourceKind: "customers.entityRole",
      resourceId: result.roleId,
      parentResourceKind: resolveParentResourceKind(before?.entityKind),
      parentResourceId: before?.role.entityId ?? null,
      tenantId: before?.role.tenantId ?? null,
      organizationId: before?.role.organizationId ?? null,
      snapshotBefore: before ?? null,
      payload: {
        undo: {
          before: before ?? 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 requireCustomerEntity(
      em,
      before.role.entityId,
      { tenantId: before.role.tenantId, organizationId: before.role.organizationId },
      before.role.entityType,
      "Customer not found"
    );
    ensureSameScope(entity, before.role.organizationId, before.role.tenantId);
    let role = await findOneWithDecryption(
      em,
      CustomerEntityRole,
      { id: before.role.id },
      void 0,
      { tenantId: before.role.tenantId, organizationId: before.role.organizationId }
    );
    if (!role) {
      role = em.create(CustomerEntityRole, {
        id: before.role.id,
        entityType: before.role.entityType,
        entityId: before.role.entityId,
        userId: before.role.userId,
        roleType: before.role.roleType,
        organizationId: before.role.organizationId,
        tenantId: before.role.tenantId
      });
      em.persist(role);
    } else {
      role.userId = before.role.userId;
      role.roleType = before.role.roleType;
      role.deletedAt = null;
    }
    await em.flush();
    const dataEngine = ctx.container.resolve("dataEngine");
    await emitCrudUndoSideEffects({
      dataEngine,
      action: "created",
      entity: role,
      identifiers: getRoleIdentifiers(role),
      syncOrigin: ctx.syncOrigin,
      events: entityRoleCrudEvents,
      indexer: { entityType: "customers:entity_role" }
    });
  }
};
registerCommand(createEntityRoleCommand);
registerCommand(updateEntityRoleCommand);
registerCommand(deleteEntityRoleCommand);
//# sourceMappingURL=entity-roles.js.map
