import { registerCommand } from "@open-mercato/shared/lib/commands";
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
import { Organization, Tenant } from "@open-mercato/core/modules/directory/data/entities";
import { organizationCreateSchema, organizationUpdateSchema } from "@open-mercato/core/modules/directory/data/validators";
import { rebuildHierarchyForTenant } from "@open-mercato/core/modules/directory/lib/hierarchy";
import { enforceTenantSelection } from "@open-mercato/core/modules/auth/lib/tenantAccess";
import { E } from "../../../generated/entities.ids.generated.js";
import {
  loadCustomFieldSnapshot,
  buildCustomFieldResetMap,
  diffCustomFieldChanges
} from "@open-mercato/shared/lib/commands/customFieldSnapshots";
import { extractUndoPayload } from "@open-mercato/shared/lib/commands/undo";
import { resolveRedoSnapshot } from "@open-mercato/shared/lib/commands/redo";
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
import {
  parseWithCustomFields,
  setCustomFieldsIfAny,
  emitCrudSideEffects,
  emitCrudUndoSideEffects,
  requireId,
  buildChanges
} from "@open-mercato/shared/lib/commands/helpers";
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
import { toOptionalString } from "@open-mercato/shared/lib/string/coerce";
import { slugify } from "@open-mercato/shared/lib/slugify";
const organizationCrudEvents = {
  module: "directory",
  entity: "organization",
  persistent: true,
  buildPayload: (ctx) => {
    const orgCtx = ctx;
    return {
      id: orgCtx.identifiers.id,
      tenantId: tenantIdFromContext(orgCtx),
      organizationId: orgCtx.identifiers.id
    };
  }
};
const organizationCrudIndexer = {
  entityType: E.directory.organization,
  buildUpsertPayload: (ctx) => {
    const orgCtx = ctx;
    return {
      entityType: E.directory.organization,
      recordId: orgCtx.identifiers.id,
      organizationId: orgCtx.identifiers.id,
      tenantId: tenantIdFromContext(orgCtx)
    };
  },
  buildDeletePayload: (ctx) => {
    const orgCtx = ctx;
    return {
      entityType: E.directory.organization,
      recordId: orgCtx.identifiers.id,
      organizationId: orgCtx.identifiers.id,
      tenantId: tenantIdFromContext(orgCtx)
    };
  }
};
function resolveTenantIdFromEntity(entity) {
  const shape = entity;
  const cached = toOptionalString(shape.__tenantId);
  if (cached) return cached;
  const tenantRef = shape.tenant;
  if (typeof tenantRef === "string") return tenantRef;
  if (tenantRef && typeof tenantRef === "object") {
    const direct = toOptionalString(tenantRef.id);
    if (direct) return direct;
    if (typeof tenantRef.getEntity === "function") {
      const nested = tenantRef.getEntity();
      const nestedId = nested ? toOptionalString(nested.id) : null;
      if (nestedId) return nestedId;
    }
  }
  const fallback = toOptionalString(shape.tenantId) || toOptionalString(shape.tenant_id);
  return fallback;
}
function serializeOrganization(entity, custom) {
  return {
    id: String(entity.id),
    tenantId: resolveTenantIdFromEntity(entity),
    name: entity.name,
    slug: entity.slug ?? null,
    logoUrl: entity.logoUrl ?? null,
    isActive: !!entity.isActive,
    parentId: entity.parentId ?? null,
    ancestorIds: Array.isArray(entity.ancestorIds) ? [...entity.ancestorIds] : [],
    childIds: Array.isArray(entity.childIds) ? [...entity.childIds] : [],
    descendantIds: Array.isArray(entity.descendantIds) ? [...entity.descendantIds] : [],
    createdAt: entity.createdAt ? entity.createdAt.toISOString() : null,
    updatedAt: entity.updatedAt ? entity.updatedAt.toISOString() : null,
    ...custom && Object.keys(custom).length ? { custom } : {}
  };
}
function captureOrganizationSnapshots(entity, childParents, custom) {
  const tenantId = resolveTenantIdFromEntity(entity);
  return {
    view: serializeOrganization(entity, custom),
    undo: {
      id: String(entity.id),
      tenantId,
      name: entity.name,
      slug: entity.slug ?? null,
      logoUrl: entity.logoUrl ?? null,
      isActive: !!entity.isActive,
      parentId: entity.parentId ?? null,
      childParents: (childParents ?? []).map((entry) => ({
        childId: String(entry.childId),
        parentId: entry.parentId
      })),
      ...custom && Object.keys(custom).length ? { custom } : {}
    }
  };
}
async function loadChildParentSnapshots(em, tenantId, childIds) {
  if (!tenantId) return [];
  const ids = Array.from(new Set(Array.from(childIds ?? []).map((id) => String(id)).filter(Boolean)));
  if (!ids.length) return [];
  const filter = {
    tenant: tenantId,
    deletedAt: null,
    id: { $in: ids }
  };
  const children = await em.find(Organization, filter);
  if (!children.length) return [];
  const map = new Map(children.map((child) => [String(child.id), child.parentId ? String(child.parentId) : null]));
  return ids.filter((id) => map.has(id)).map((id) => ({
    childId: id,
    parentId: map.get(id) ?? null
  }));
}
async function restoreChildParents(em, tenantId, snapshots) {
  if (!snapshots?.length) return;
  const ids = Array.from(new Set(snapshots.map((entry) => entry.childId).filter(Boolean)));
  if (!ids.length) return;
  const filter = {
    tenant: tenantId,
    deletedAt: null,
    id: { $in: ids }
  };
  const children = await em.find(Organization, filter);
  if (!children.length) return;
  const desired = new Map(snapshots.map((entry) => [entry.childId, entry.parentId ?? null]));
  const toPersist = [];
  for (const child of children) {
    const id = String(child.id);
    if (!desired.has(id)) continue;
    const nextParent = desired.get(id) ?? null;
    if (child.parentId !== nextParent) {
      child.parentId = nextParent;
      toPersist.push(child);
    }
  }
  if (toPersist.length) await em.persist(toPersist).flush();
}
function normalizeChildIds(ids, exclude) {
  const excludeSet = new Set(exclude);
  return Array.from(new Set(ids)).filter((id) => !excludeSet.has(id));
}
async function ensureParentExists(em, tenantId, parentId) {
  if (!parentId) return;
  const parentFilter = { id: parentId, tenant: tenantId, deletedAt: null };
  const parent = await em.findOne(Organization, parentFilter);
  if (!parent) throw new CrudHttpError(400, { error: "Parent not found" });
}
async function ensureChildrenValid(em, tenantId, childIds) {
  if (!childIds.length) return;
  const childFilter = { id: { $in: childIds }, tenant: tenantId, deletedAt: null };
  const children = await em.find(Organization, childFilter);
  if (children.length !== childIds.length) throw new CrudHttpError(400, { error: "Invalid child assignment" });
}
async function assignChildren(em, tenantId, recordId, desiredChildIds) {
  const targetIds = Array.from(new Set(desiredChildIds)).filter((id) => id !== recordId);
  if (!targetIds.length) return;
  const filter = { tenant: tenantId, deletedAt: null, id: { $in: targetIds } };
  const children = await em.find(Organization, filter);
  const toPersist = [];
  for (const child of children) {
    if (String(child.id) === recordId) continue;
    if (child.parentId !== recordId) {
      child.parentId = recordId;
      toPersist.push(child);
    }
  }
  if (toPersist.length) await em.persist(toPersist).flush();
}
async function clearRemovedChildren(em, tenantId, recordId, desiredChildIds) {
  const currentFilter = { tenant: tenantId, parentId: recordId, deletedAt: null };
  const current = await em.find(Organization, currentFilter);
  const toPersist = current.filter((child) => !desiredChildIds.has(String(child.id)));
  if (!toPersist.length) return;
  for (const child of toPersist) child.parentId = null;
  await em.persist(toPersist).flush();
}
async function resolveUniqueSlug(em, tenantId, baseSlug, excludeId) {
  let candidate = baseSlug;
  let suffix = 0;
  const maxAttempts = 50;
  while (suffix < maxAttempts) {
    const filter = {
      tenant: tenantId,
      slug: candidate,
      deletedAt: null
    };
    const existing = await em.findOne(Organization, filter);
    if (!existing || excludeId && String(existing.id) === excludeId) return candidate;
    suffix += 1;
    candidate = `${baseSlug}-${suffix}`;
  }
  return `${baseSlug}-${Date.now()}`;
}
const createOrganizationCommand = {
  id: "directory.organizations.create",
  async execute(rawInput, ctx) {
    const { parsed, custom } = parseWithCustomFields(organizationCreateSchema, rawInput);
    const em = ctx.container.resolve("em");
    const tenantId = await enforceTenantSelection(ctx, parsed.tenantId ?? null);
    if (!tenantId) throw new CrudHttpError(400, { error: "Tenant scope required" });
    const parentId = parsed.parentId ?? null;
    if (parentId) {
      await ensureParentExists(em, tenantId, parentId);
    }
    const childIds = normalizeChildIds(parsed.childIds ?? [], parentId ? [parentId] : []);
    if (parentId && childIds.includes(parentId)) throw new CrudHttpError(400, { error: "Child cannot equal parent" });
    await ensureChildrenValid(em, tenantId, childIds);
    const childParentsBefore = await loadChildParentSnapshots(em, tenantId, childIds);
    const tenantRef = em.getReference(Tenant, tenantId);
    const baseSlug = parsed.slug ? parsed.slug : slugify(parsed.name);
    const slug = baseSlug ? await resolveUniqueSlug(em, tenantId, baseSlug) : null;
    const de = ctx.container.resolve("dataEngine");
    let organization;
    await withAtomicFlush(em, [
      async () => {
        organization = await de.createOrmEntity({
          entity: Organization,
          data: {
            tenant: tenantRef,
            name: parsed.name,
            slug,
            logoUrl: parsed.logoUrl ?? null,
            isActive: parsed.isActive ?? true,
            parentId
          }
        });
        setInternalTenantId(organization, tenantId);
        const recordId2 = String(organization.id);
        if (childIds.length) {
          await assignChildren(em, tenantId, recordId2, childIds);
        }
        const childParentsAfter = await loadChildParentSnapshots(em, tenantId, childIds);
        setUndoMeta(organization, { childParentsBefore, childParentsAfter });
        await setCustomFieldsIfAny({
          dataEngine: de,
          entityId: E.directory.organization,
          recordId: recordId2,
          tenantId,
          organizationId: recordId2,
          values: custom
        });
        await rebuildHierarchyForTenant(em, tenantId);
      }
    ], { transaction: true });
    const recordId = String(organization.id);
    const identifiers = { id: recordId, organizationId: recordId, tenantId };
    await emitCrudSideEffects({
      dataEngine: de,
      action: "created",
      entity: organization,
      identifiers,
      events: organizationCrudEvents,
      indexer: organizationCrudIndexer
    });
    return organization;
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em").fork();
    const tenantId = resolveTenantIdFromEntity(result);
    const custom = await loadCustomFieldSnapshot(em, {
      entityId: E.directory.organization,
      recordId: String(result.id),
      tenantId,
      organizationId: String(result.id)
    });
    return serializeOrganization(result, custom);
  },
  buildLog: async ({ result, ctx }) => {
    const { translate } = await resolveTranslations();
    const meta = getUndoMeta(result);
    const em = ctx.container.resolve("em").fork();
    const tenantId = resolveTenantIdFromEntity(result);
    const custom = await loadCustomFieldSnapshot(em, {
      entityId: E.directory.organization,
      recordId: String(result.id),
      tenantId,
      organizationId: String(result.id)
    });
    const afterSnapshots = captureOrganizationSnapshots(result, meta.childParentsAfter ?? [], custom);
    return {
      actionLabel: translate("directory.audit.organizations.create", "Create organization"),
      resourceKind: "directory.organization",
      resourceId: String(result.id),
      tenantId: ctx.auth?.tenantId ?? resolveTenantIdFromEntity(result),
      snapshotAfter: afterSnapshots.view,
      payload: {
        undo: {
          after: afterSnapshots.undo,
          childrenBefore: meta.childParentsBefore ?? []
        }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry);
    const after = payload?.after;
    const childrenBefore = payload?.childrenBefore ?? [];
    if (!after) return;
    const tenantId = after.tenantId;
    if (!tenantId) return;
    const em = ctx.container.resolve("em");
    const de = ctx.container.resolve("dataEngine");
    await withAtomicFlush(em, [
      async () => {
        await restoreChildParents(em, tenantId, childrenBefore);
        if (after.custom && Object.keys(after.custom).length) {
          const reset = buildCustomFieldResetMap(void 0, after.custom);
          if (Object.keys(reset).length) {
            const resetValues = reset;
            await de.setCustomFields({
              entityId: E.directory.organization,
              recordId: after.id,
              tenantId,
              organizationId: after.id,
              values: resetValues,
              notify: false
            });
          }
        }
        await de.deleteOrmEntity({
          entity: Organization,
          where: { id: after.id, deletedAt: null },
          soft: false
        });
        await rebuildHierarchyForTenant(em, tenantId);
      }
    ], { transaction: true });
  },
  redo: async ({ logEntry, ctx }) => {
    const after = resolveRedoSnapshot(logEntry);
    if (!after) throw new CrudHttpError(400, { error: "[internal] redo snapshot unavailable for organization create" });
    const tenantId = after.tenantId;
    if (!tenantId) throw new CrudHttpError(400, { error: "[internal] redo snapshot missing tenant for organization create" });
    const em = ctx.container.resolve("em");
    const de = ctx.container.resolve("dataEngine");
    let organization;
    await withAtomicFlush(em, [
      async () => {
        const existing = await em.findOne(Organization, { id: after.id });
        if (existing) {
          existing.deletedAt = null;
          existing.name = after.name;
          if (after.slug !== void 0) existing.slug = after.slug ?? null;
          if (after.logoUrl !== void 0) existing.logoUrl = after.logoUrl ?? null;
          existing.isActive = after.isActive;
          existing.parentId = after.parentId;
          await em.flush();
          organization = existing;
        } else {
          organization = await de.createOrmEntity({
            entity: Organization,
            data: {
              id: after.id,
              name: after.name,
              slug: after.slug ?? null,
              logoUrl: after.logoUrl ?? null,
              tenant: em.getReference(Tenant, tenantId),
              isActive: after.isActive,
              parentId: after.parentId
            }
          });
        }
        setInternalTenantId(organization, tenantId);
        if (after.custom && Object.keys(after.custom).length) {
          const reset = buildCustomFieldResetMap(after.custom, void 0);
          if (Object.keys(reset).length) {
            const resetValues = reset;
            await de.setCustomFields({
              entityId: E.directory.organization,
              recordId: after.id,
              tenantId,
              organizationId: after.id,
              values: resetValues,
              notify: false
            });
          }
        }
        await assignChildren(em, tenantId, after.id, after.childParents.map((entry) => entry.childId));
        await rebuildHierarchyForTenant(em, tenantId);
      }
    ], { transaction: true });
    const identifiers = { id: after.id, organizationId: after.id, tenantId };
    await emitCrudSideEffects({
      dataEngine: de,
      action: "created",
      entity: organization,
      identifiers,
      events: organizationCrudEvents,
      indexer: organizationCrudIndexer
    });
    return organization;
  }
};
const updateOrganizationCommand = {
  id: "directory.organizations.update",
  async prepare(rawInput, ctx) {
    const { parsed } = parseWithCustomFields(organizationUpdateSchema, rawInput);
    const em = ctx.container.resolve("em");
    const current = await em.findOne(Organization, { id: parsed.id, deletedAt: null });
    if (!current) throw new CrudHttpError(404, { error: "Not found" });
    const tenantId = resolveTenantIdFromEntity(current);
    const currentChildIds = Array.isArray(current.childIds) ? current.childIds : [];
    const requestedChildIds = Array.isArray(parsed.childIds) ? parsed.childIds : [];
    const combinedChildIds = /* @__PURE__ */ new Set([...currentChildIds.map(String), ...requestedChildIds.map(String)]);
    const childParentsBefore = tenantId ? await loadChildParentSnapshots(em, tenantId, combinedChildIds) : [];
    const custom = await loadCustomFieldSnapshot(em, {
      entityId: E.directory.organization,
      recordId: String(current.id),
      tenantId,
      organizationId: String(current.id)
    });
    return { before: captureOrganizationSnapshots(current, childParentsBefore, custom) };
  },
  async execute(rawInput, ctx) {
    const { parsed, custom } = parseWithCustomFields(organizationUpdateSchema, rawInput);
    const em = ctx.container.resolve("em");
    const existing = await em.findOne(Organization, { id: parsed.id, deletedAt: null });
    if (!existing) throw new CrudHttpError(404, { error: "Not found" });
    const tenantId = await enforceTenantSelection(ctx, parsed.tenantId ?? resolveTenantIdFromEntity(existing));
    if (!tenantId) throw new CrudHttpError(400, { error: "Tenant scope required" });
    const parentId = parsed.parentId ?? null;
    if (parentId) {
      if (parentId === parsed.id) throw new CrudHttpError(400, { error: "Organization cannot be its own parent" });
      if (Array.isArray(existing.descendantIds) && existing.descendantIds.includes(parentId)) {
        throw new CrudHttpError(400, { error: "Cannot assign descendant as parent" });
      }
      await ensureParentExists(em, tenantId, parentId);
    }
    const normalizedChildIds = normalizeChildIds(parsed.childIds ?? [], [parsed.id, parentId ?? ""]);
    if (normalizedChildIds.some((id) => id === parentId)) throw new CrudHttpError(400, { error: "Child cannot equal parent" });
    if (Array.isArray(existing.ancestorIds) && normalizedChildIds.some((id) => existing.ancestorIds.includes(id))) {
      throw new CrudHttpError(400, { error: "Cannot assign ancestor as child" });
    }
    if (normalizedChildIds.length) {
      await ensureChildrenValid(em, tenantId, normalizedChildIds);
      const childFilter = {
        tenant: tenantId,
        deletedAt: null,
        id: { $in: normalizedChildIds }
      };
      const children = await em.find(Organization, childFilter);
      for (const child of children) {
        if (Array.isArray(child.descendantIds) && child.descendantIds.includes(parsed.id)) {
          throw new CrudHttpError(400, { error: "Cannot assign descendant cycle" });
        }
      }
    }
    const combinedChildIds = /* @__PURE__ */ new Set([
      ...normalizedChildIds.map(String),
      ...Array.isArray(existing.childIds) ? existing.childIds.map(String) : []
    ]);
    const childParentsBefore = await loadChildParentSnapshots(em, tenantId, combinedChildIds);
    let resolvedSlug;
    if (parsed.slug !== void 0) {
      resolvedSlug = parsed.slug ? await resolveUniqueSlug(em, tenantId, parsed.slug, parsed.id) : parsed.slug;
    }
    const de = ctx.container.resolve("dataEngine");
    let resolvedOrganization;
    await withAtomicFlush(em, [
      async () => {
        const organization = await de.updateOrmEntity({
          entity: Organization,
          where: { id: parsed.id, deletedAt: null },
          apply: (entity) => {
            if (parsed.name !== void 0) entity.name = parsed.name;
            if (resolvedSlug !== void 0) entity.slug = resolvedSlug;
            if (parsed.logoUrl !== void 0) entity.logoUrl = parsed.logoUrl ?? null;
            if (parsed.isActive !== void 0) entity.isActive = parsed.isActive;
            entity.parentId = parentId;
          }
        });
        if (!organization) throw new CrudHttpError(404, { error: "Not found" });
        setInternalTenantId(organization, tenantId);
        const recordId2 = String(organization.id);
        const desiredChildIds = new Set(normalizedChildIds.filter((id) => id !== recordId2));
        await clearRemovedChildren(em, tenantId, recordId2, desiredChildIds);
        await assignChildren(em, tenantId, recordId2, desiredChildIds);
        const childParentsAfter = await loadChildParentSnapshots(em, tenantId, combinedChildIds);
        setUndoMeta(organization, { childParentsBefore, childParentsAfter });
        await setCustomFieldsIfAny({
          dataEngine: de,
          entityId: E.directory.organization,
          recordId: recordId2,
          tenantId,
          organizationId: recordId2,
          values: custom
        });
        await rebuildHierarchyForTenant(em, tenantId);
        resolvedOrganization = organization;
      }
    ], { transaction: true });
    const recordId = String(resolvedOrganization.id);
    const identifiers = { id: recordId, organizationId: recordId, tenantId };
    await emitCrudSideEffects({
      dataEngine: de,
      action: "updated",
      entity: resolvedOrganization,
      identifiers,
      events: organizationCrudEvents,
      indexer: organizationCrudIndexer
    });
    return resolvedOrganization;
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em").fork();
    const tenantId = resolveTenantIdFromEntity(result);
    const custom = await loadCustomFieldSnapshot(em, {
      entityId: E.directory.organization,
      recordId: String(result.id),
      tenantId,
      organizationId: String(result.id)
    });
    return serializeOrganization(result, custom);
  },
  buildLog: async ({ snapshots, result, ctx }) => {
    const { translate } = await resolveTranslations();
    const meta = getUndoMeta(result);
    const beforeSnapshots = snapshots.before;
    const beforeRecord = beforeSnapshots?.view ?? null;
    const em = ctx.container.resolve("em").fork();
    const tenantId = resolveTenantIdFromEntity(result);
    const custom = await loadCustomFieldSnapshot(em, {
      entityId: E.directory.organization,
      recordId: String(result.id),
      tenantId,
      organizationId: String(result.id)
    });
    const after = serializeOrganization(result, custom);
    const changes = buildChanges(beforeRecord, after, ["name", "slug", "logoUrl", "isActive", "parentId"]);
    const customDiff = diffCustomFieldChanges(beforeRecord?.custom, custom);
    for (const [key, diff] of Object.entries(customDiff)) {
      changes[`cf_${key}`] = diff;
    }
    return {
      actionLabel: translate("directory.audit.organizations.update", "Update organization"),
      resourceKind: "directory.organization",
      resourceId: String(result.id),
      changes,
      tenantId: ctx.auth?.tenantId ?? after.tenantId,
      payload: {
        undo: {
          before: beforeSnapshots?.undo ?? null,
          after: captureOrganizationSnapshots(result, meta.childParentsAfter ?? [], custom).undo
        }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry);
    const before = payload?.before;
    const after = payload?.after;
    if (!before) return;
    const tenantId = before.tenantId;
    if (!tenantId) return;
    const em = ctx.container.resolve("em");
    const de = ctx.container.resolve("dataEngine");
    let updated = null;
    await withAtomicFlush(em, [
      async () => {
        updated = await de.updateOrmEntity({
          entity: Organization,
          where: { id: before.id },
          apply: (entity) => {
            entity.name = before.name;
            if (before.slug !== void 0) entity.slug = before.slug;
            if (before.logoUrl !== void 0) entity.logoUrl = before.logoUrl ?? null;
            entity.isActive = before.isActive;
            entity.parentId = before.parentId;
          }
        });
        if (updated && tenantId) {
          setInternalTenantId(updated, tenantId);
        }
        const reset = buildCustomFieldResetMap(before.custom, after?.custom);
        if (Object.keys(reset).length) {
          const resetValues = reset;
          await de.setCustomFields({
            entityId: E.directory.organization,
            recordId: before.id,
            tenantId,
            organizationId: before.id,
            values: resetValues,
            notify: false
          });
        }
        const childSnapshots = before.childParents;
        await restoreChildParents(em, tenantId, childSnapshots);
        await rebuildHierarchyForTenant(em, tenantId);
      }
    ], { transaction: true });
    await emitCrudUndoSideEffects({
      dataEngine: de,
      action: "updated",
      entity: updated,
      identifiers: {
        id: before.id,
        tenantId,
        organizationId: before.id
      },
      events: organizationCrudEvents,
      indexer: organizationCrudIndexer
    });
  }
};
const deleteOrganizationCommand = {
  id: "directory.organizations.delete",
  async prepare(input, ctx) {
    const id = requireId(input, "Organization id required");
    const em = ctx.container.resolve("em");
    const existing = await em.findOne(Organization, { id, deletedAt: null });
    if (!existing) return {};
    const tenantId = resolveTenantIdFromEntity(existing);
    const childParentsBefore = tenantId ? await loadChildParentSnapshots(em, tenantId, Array.isArray(existing.childIds) ? existing.childIds : []) : [];
    const custom = await loadCustomFieldSnapshot(em, {
      entityId: E.directory.organization,
      recordId: String(existing.id),
      tenantId,
      organizationId: String(existing.id)
    });
    return { before: captureOrganizationSnapshots(existing, childParentsBefore, custom) };
  },
  async execute(input, ctx) {
    const id = requireId(input, "Organization id required");
    const em = ctx.container.resolve("em");
    const existing = await em.findOne(Organization, { id, deletedAt: null });
    if (!existing) throw new CrudHttpError(404, { error: "Not found" });
    const tenantId = await enforceTenantSelection(ctx, resolveTenantIdFromEntity(existing));
    if (!tenantId) throw new CrudHttpError(400, { error: "Tenant scope required" });
    const parentId = existing.parentId ?? null;
    const childSnapshotsBefore = await loadChildParentSnapshots(
      em,
      tenantId,
      Array.isArray(existing.childIds) ? existing.childIds : []
    );
    const de = ctx.container.resolve("dataEngine");
    let resolvedDeleted;
    await withAtomicFlush(em, [
      async () => {
        const deleted = await de.deleteOrmEntity({
          entity: Organization,
          where: { id, deletedAt: null },
          soft: true,
          softDeleteField: "deletedAt"
        });
        if (!deleted) throw new CrudHttpError(404, { error: "Not found" });
        setInternalTenantId(deleted, tenantId);
        deleted.isActive = false;
        deleted.parentId = null;
        resolvedDeleted = deleted;
      },
      async () => {
        const deleted = resolvedDeleted;
        const childrenFilter = { tenant: tenantId, parentId: id, deletedAt: null };
        const children = await em.find(Organization, childrenFilter);
        const toPersist = [];
        for (const child of children) {
          child.parentId = parentId;
          toPersist.push(child);
        }
        toPersist.push(deleted);
        if (toPersist.length) em.persist(toPersist);
        setUndoMeta(deleted, { childParentsBefore: childSnapshotsBefore });
      },
      async () => {
        await rebuildHierarchyForTenant(em, tenantId);
      }
    ], { transaction: true });
    const identifiers = { id, organizationId: id, tenantId };
    await emitCrudSideEffects({
      dataEngine: de,
      action: "deleted",
      entity: resolvedDeleted,
      identifiers,
      events: organizationCrudEvents,
      indexer: organizationCrudIndexer
    });
    return resolvedDeleted;
  },
  buildLog: async ({ snapshots, input, ctx }) => {
    const { translate } = await resolveTranslations();
    const beforeSnapshots = snapshots.before;
    const beforeSnapshot = beforeSnapshots?.view ?? null;
    const beforeUndo = beforeSnapshots?.undo ?? null;
    const id = String(input?.body?.id ?? input?.query?.id ?? "");
    const fallbackId = beforeSnapshot?.id ?? null;
    const fallbackTenant = beforeSnapshot?.tenantId ?? null;
    return {
      actionLabel: translate("directory.audit.organizations.delete", "Delete organization"),
      resourceKind: "directory.organization",
      resourceId: id || fallbackId || null,
      snapshotBefore: beforeSnapshot ?? null,
      tenantId: ctx.auth?.tenantId ?? fallbackTenant,
      payload: {
        undo: {
          before: beforeUndo
        }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry);
    const before = payload?.before;
    if (!before) return;
    const tenantId = before.tenantId;
    if (!tenantId) return;
    const em = ctx.container.resolve("em");
    const de = ctx.container.resolve("dataEngine");
    let organization = null;
    await withAtomicFlush(em, [
      async () => {
        organization = await em.findOne(Organization, { id: before.id });
        if (organization) {
          organization.deletedAt = null;
          organization.isActive = before.isActive;
          organization.name = before.name;
          if (before.slug !== void 0) organization.slug = before.slug;
          organization.parentId = before.parentId;
          await em.flush();
          if (tenantId) setInternalTenantId(organization, tenantId);
        } else {
          organization = await de.createOrmEntity({
            entity: Organization,
            data: {
              id: before.id,
              name: before.name,
              slug: before.slug ?? null,
              tenant: tenantId ? em.getReference(Tenant, tenantId) : void 0,
              isActive: before.isActive,
              parentId: before.parentId
            }
          });
          if (tenantId) setInternalTenantId(organization, tenantId);
        }
        if (tenantId) {
          const customValues = buildCustomFieldResetMap(before.custom, void 0);
          if (Object.keys(customValues).length) {
            const resetValues = customValues;
            await de.setCustomFields({
              entityId: E.directory.organization,
              recordId: before.id,
              tenantId,
              organizationId: before.id,
              values: resetValues,
              notify: false
            });
          }
        }
        await restoreChildParents(em, tenantId, before.childParents);
        await rebuildHierarchyForTenant(em, tenantId);
      }
    ], { transaction: true });
    await emitCrudUndoSideEffects({
      dataEngine: de,
      action: "updated",
      entity: organization,
      identifiers: {
        id: before.id,
        tenantId,
        organizationId: before.id
      },
      events: organizationCrudEvents,
      indexer: organizationCrudIndexer
    });
  }
};
const UNDO_META_KEY = /* @__PURE__ */ Symbol("directory.organization.undoMeta");
function getUndoMeta(entity) {
  return Reflect.get(entity, UNDO_META_KEY) ?? {};
}
function setUndoMeta(entity, meta) {
  const current = getUndoMeta(entity);
  Reflect.set(entity, UNDO_META_KEY, { ...current, ...meta });
}
registerCommand(createOrganizationCommand);
registerCommand(updateOrganizationCommand);
registerCommand(deleteOrganizationCommand);
function setInternalTenantId(entity, tenantId) {
  Reflect.set(entity, "__tenantId", tenantId);
}
function tenantIdFromContext(ctx) {
  return resolveTenantIdFromEntity(ctx.entity) ?? ctx.identifiers.tenantId ?? null;
}
export {
  organizationCrudEvents,
  organizationCrudIndexer,
  resolveTenantIdFromEntity
};
//# sourceMappingURL=organizations.js.map
