import { Dictionary, DictionaryEntry } from "@open-mercato/core/modules/dictionaries/data/entities";
import {
  reorderDictionaryEntriesCommandSchema,
  setDefaultDictionaryEntryCommandSchema
} from "@open-mercato/core/modules/dictionaries/data/validators";
import { registerCommand } from "@open-mercato/shared/lib/commands";
import { extractUndoPayload } from "@open-mercato/shared/lib/commands/undo";
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
import { emitCrudSideEffects, emitCrudUndoSideEffects } from "@open-mercato/shared/lib/commands/helpers";
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
const RESOURCE_KIND = "dictionaries.dictionary";
const INDEXER_ENTITY_TYPE = "dictionaries:entry";
const dictionaryCrudEvents = {
  module: "dictionaries",
  entity: "entry",
  persistent: true,
  buildPayload: (ctx) => ({
    id: ctx.identifiers.id,
    organizationId: ctx.identifiers.organizationId,
    tenantId: ctx.identifiers.tenantId,
    ...ctx.syncOrigin ? { syncOrigin: ctx.syncOrigin } : {}
  })
};
function ensureScope(ctx, scope) {
  const tenantId = ctx.auth?.tenantId ?? null;
  if (tenantId && tenantId !== scope.tenantId) {
    throw new CrudHttpError(403, { error: "Forbidden" });
  }
  const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null;
  if (organizationId && organizationId !== scope.organizationId) {
    throw new CrudHttpError(403, { error: "Forbidden" });
  }
}
async function requireDictionary(em, id, scope) {
  const dictionary = await findOneWithDecryption(
    em,
    Dictionary,
    { id, tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null },
    void 0,
    scope
  );
  if (!dictionary) {
    throw new CrudHttpError(404, { error: "Dictionary not found" });
  }
  return dictionary;
}
const reorderDictionaryEntriesCommand = {
  id: "dictionaries.entries.reorder",
  async prepare(rawInput, ctx) {
    const parsed = reorderDictionaryEntriesCommandSchema.parse(rawInput);
    const scope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId };
    ensureScope(ctx, scope);
    const em = ctx.container.resolve("em").fork();
    const ids = parsed.entries.map((e) => e.id);
    const existing = await findWithDecryption(
      em,
      DictionaryEntry,
      {
        id: { $in: ids },
        tenantId: scope.tenantId,
        organizationId: scope.organizationId
      },
      void 0,
      scope
    );
    const before = {
      dictionaryId: parsed.dictionaryId,
      scope,
      positions: existing.map((entry) => ({ id: entry.id, position: entry.position ?? 0 }))
    };
    return { before };
  },
  async execute(rawInput, ctx) {
    const parsed = reorderDictionaryEntriesCommandSchema.parse(rawInput);
    const scope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId };
    ensureScope(ctx, scope);
    const em = ctx.container.resolve("em").fork();
    const dictionary = await requireDictionary(em, parsed.dictionaryId, scope);
    const ids = parsed.entries.map((e) => e.id);
    const entries = await findWithDecryption(
      em,
      DictionaryEntry,
      {
        id: { $in: ids },
        dictionary,
        tenantId: scope.tenantId,
        organizationId: scope.organizationId
      },
      void 0,
      scope
    );
    const entryMap = /* @__PURE__ */ new Map();
    for (const entry of entries) entryMap.set(entry.id, entry);
    const updatedIds = [];
    await withAtomicFlush(em, [
      () => {
        for (const { id, position } of parsed.entries) {
          const entry = entryMap.get(id);
          if (!entry) continue;
          entry.position = position;
          entry.updatedAt = /* @__PURE__ */ new Date();
          updatedIds.push(id);
        }
      }
    ], { transaction: true });
    const dataEngine = ctx.container.resolve("dataEngine");
    for (const entry of entries) {
      await emitCrudSideEffects({
        dataEngine,
        action: "updated",
        entity: entry,
        identifiers: { id: entry.id, tenantId: entry.tenantId, organizationId: entry.organizationId },
        syncOrigin: ctx.syncOrigin,
        events: dictionaryCrudEvents,
        indexer: { entityType: INDEXER_ENTITY_TYPE }
      });
    }
    return { dictionaryId: parsed.dictionaryId, updatedIds };
  },
  captureAfter: async (rawInput, _result, ctx) => {
    const parsed = reorderDictionaryEntriesCommandSchema.parse(rawInput);
    const em = ctx.container.resolve("em");
    const scope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId };
    const ids = parsed.entries.map((e) => e.id);
    const current = await findWithDecryption(
      em,
      DictionaryEntry,
      {
        id: { $in: ids },
        tenantId: scope.tenantId,
        organizationId: scope.organizationId
      },
      void 0,
      scope
    );
    const after = {
      dictionaryId: parsed.dictionaryId,
      scope,
      positions: current.map((entry) => ({ id: entry.id, position: entry.position ?? 0 }))
    };
    return after;
  },
  buildLog: async ({ snapshots, result }) => {
    const { translate } = await resolveTranslations();
    const before = snapshots.before;
    const after = snapshots.after;
    if (!before || !after) return null;
    return {
      actionLabel: translate("dictionaries.entries.audit.reorder", "Reorder dictionary entries"),
      resourceKind: RESOURCE_KIND,
      resourceId: result.dictionaryId,
      tenantId: before.scope.tenantId,
      organizationId: before.scope.organizationId,
      snapshotBefore: before,
      snapshotAfter: after,
      payload: {
        undo: { before, after }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const undo = extractUndoPayload(logEntry);
    const before = undo?.before ?? logEntry?.snapshotBefore ?? null;
    if (!before) return;
    ensureScope(ctx, before.scope);
    const em = ctx.container.resolve("em").fork();
    const ids = before.positions.map((p) => p.id);
    const entries = await findWithDecryption(
      em,
      DictionaryEntry,
      {
        id: { $in: ids },
        tenantId: before.scope.tenantId,
        organizationId: before.scope.organizationId
      },
      void 0,
      before.scope
    );
    const entryMap = /* @__PURE__ */ new Map();
    for (const entry of entries) entryMap.set(entry.id, entry);
    await withAtomicFlush(em, [
      () => {
        for (const { id, position } of before.positions) {
          const entry = entryMap.get(id);
          if (!entry) continue;
          entry.position = position;
          entry.updatedAt = /* @__PURE__ */ new Date();
        }
      }
    ], { transaction: true });
    const dataEngine = ctx.container.resolve("dataEngine");
    for (const entry of entries) {
      await emitCrudUndoSideEffects({
        dataEngine,
        action: "updated",
        entity: entry,
        identifiers: { id: entry.id, tenantId: entry.tenantId, organizationId: entry.organizationId },
        syncOrigin: ctx.syncOrigin,
        events: dictionaryCrudEvents,
        indexer: { entityType: INDEXER_ENTITY_TYPE }
      });
    }
  }
};
const setDefaultDictionaryEntryCommand = {
  id: "dictionaries.entries.set_default",
  async prepare(rawInput, ctx) {
    const parsed = setDefaultDictionaryEntryCommandSchema.parse(rawInput);
    const scope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId };
    ensureScope(ctx, scope);
    const em = ctx.container.resolve("em").fork();
    const dictionary = await requireDictionary(em, parsed.dictionaryId, scope);
    const previousDefaults = await findWithDecryption(
      em,
      DictionaryEntry,
      {
        dictionary,
        tenantId: scope.tenantId,
        organizationId: scope.organizationId,
        isDefault: true
      },
      void 0,
      scope
    );
    const before = {
      dictionaryId: parsed.dictionaryId,
      scope,
      previousDefaultIds: previousDefaults.map((entry) => entry.id),
      newDefaultId: parsed.entryId
    };
    return { before };
  },
  async execute(rawInput, ctx) {
    const parsed = setDefaultDictionaryEntryCommandSchema.parse(rawInput);
    const scope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId };
    ensureScope(ctx, scope);
    const em = ctx.container.resolve("em").fork();
    const dictionary = await requireDictionary(em, parsed.dictionaryId, scope);
    const targetEntry = await findOneWithDecryption(
      em,
      DictionaryEntry,
      {
        id: parsed.entryId,
        dictionary,
        tenantId: scope.tenantId,
        organizationId: scope.organizationId
      },
      void 0,
      scope
    );
    if (!targetEntry) {
      throw new CrudHttpError(404, { error: "Dictionary entry not found" });
    }
    const existingDefaults = await findWithDecryption(
      em,
      DictionaryEntry,
      {
        dictionary,
        tenantId: scope.tenantId,
        organizationId: scope.organizationId,
        isDefault: true
      },
      void 0,
      scope
    );
    const clearedIds = [];
    await withAtomicFlush(em, [
      () => {
        for (const entry of existingDefaults) {
          if (entry.id === targetEntry.id) continue;
          entry.isDefault = false;
          entry.updatedAt = /* @__PURE__ */ new Date();
          clearedIds.push(entry.id);
        }
      }
    ], { transaction: true });
    await withAtomicFlush(em, [
      () => {
        targetEntry.isDefault = true;
        targetEntry.updatedAt = /* @__PURE__ */ new Date();
      }
    ], { transaction: true });
    const dataEngine = ctx.container.resolve("dataEngine");
    const touched = [targetEntry, ...existingDefaults.filter((entry) => entry.id !== targetEntry.id)];
    for (const entry of touched) {
      await emitCrudSideEffects({
        dataEngine,
        action: "updated",
        entity: entry,
        identifiers: { id: entry.id, tenantId: entry.tenantId, organizationId: entry.organizationId },
        syncOrigin: ctx.syncOrigin,
        events: dictionaryCrudEvents,
        indexer: { entityType: INDEXER_ENTITY_TYPE }
      });
    }
    return { dictionaryId: parsed.dictionaryId, entryId: targetEntry.id, clearedIds };
  },
  captureAfter: async (rawInput, result, _ctx) => {
    const parsed = setDefaultDictionaryEntryCommandSchema.parse(rawInput);
    const scope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId };
    const after = {
      dictionaryId: parsed.dictionaryId,
      scope,
      previousDefaultIds: result.clearedIds,
      newDefaultId: result.entryId
    };
    return after;
  },
  buildLog: async ({ snapshots, result }) => {
    const { translate } = await resolveTranslations();
    const before = snapshots.before;
    const after = snapshots.after;
    if (!before || !after) return null;
    return {
      actionLabel: translate("dictionaries.entries.audit.set_default", "Set default dictionary entry"),
      resourceKind: RESOURCE_KIND,
      resourceId: result.dictionaryId,
      tenantId: before.scope.tenantId,
      organizationId: before.scope.organizationId,
      snapshotBefore: before,
      snapshotAfter: after,
      payload: {
        undo: { before, after }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const undo = extractUndoPayload(logEntry);
    const before = undo?.before ?? logEntry?.snapshotBefore ?? null;
    if (!before) return;
    ensureScope(ctx, before.scope);
    const em = ctx.container.resolve("em").fork();
    const dictionary = await requireDictionary(em, before.dictionaryId, before.scope);
    const newDefault = before.newDefaultId ? await findOneWithDecryption(
      em,
      DictionaryEntry,
      {
        id: before.newDefaultId,
        dictionary,
        tenantId: before.scope.tenantId,
        organizationId: before.scope.organizationId
      },
      void 0,
      before.scope
    ) : null;
    const previousDefaults = before.previousDefaultIds.length > 0 ? await findWithDecryption(
      em,
      DictionaryEntry,
      {
        id: { $in: before.previousDefaultIds },
        dictionary,
        tenantId: before.scope.tenantId,
        organizationId: before.scope.organizationId
      },
      void 0,
      before.scope
    ) : [];
    await withAtomicFlush(em, [
      () => {
        if (newDefault) {
          newDefault.isDefault = false;
          newDefault.updatedAt = /* @__PURE__ */ new Date();
        }
      }
    ], { transaction: true });
    await withAtomicFlush(em, [
      () => {
        for (const entry of previousDefaults) {
          entry.isDefault = true;
          entry.updatedAt = /* @__PURE__ */ new Date();
        }
      }
    ], { transaction: true });
    const dataEngine = ctx.container.resolve("dataEngine");
    const touched = [
      ...newDefault ? [newDefault] : [],
      ...previousDefaults
    ];
    for (const entry of touched) {
      await emitCrudUndoSideEffects({
        dataEngine,
        action: "updated",
        entity: entry,
        identifiers: { id: entry.id, tenantId: entry.tenantId, organizationId: entry.organizationId },
        syncOrigin: ctx.syncOrigin,
        events: dictionaryCrudEvents,
        indexer: { entityType: INDEXER_ENTITY_TYPE }
      });
    }
  }
};
registerCommand(reorderDictionaryEntriesCommand);
registerCommand(setDefaultDictionaryEntryCommand);
//# sourceMappingURL=entry-operations.js.map
