import { registerCommand } from "@open-mercato/shared/lib/commands";
import { buildChanges, requireId, emitCrudSideEffects } from "@open-mercato/shared/lib/commands/helpers";
import { extractUndoPayload } from "@open-mercato/shared/lib/commands/undo";
import { makeCreateRedo } from "@open-mercato/shared/lib/commands/redo";
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
import { CrudHttpError, conflict, isUniqueViolation } from "@open-mercato/shared/lib/crud/errors";
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
import { Currency, ExchangeRate } from "../data/entities.js";
import {
  currencyCreateSchema,
  currencyUpdateSchema,
  currencyDeleteSchema
} from "../data/validators.js";
const currencyCrudEvents = {
  module: "currencies",
  entity: "currency",
  persistent: true,
  buildPayload: (ctx) => ({
    id: ctx.identifiers.id,
    organizationId: ctx.identifiers.organizationId,
    tenantId: ctx.identifiers.tenantId
  })
};
async function loadCurrencySnapshot(em, id) {
  const record = await em.findOne(Currency, { id });
  if (!record) return null;
  return {
    id: record.id,
    organizationId: record.organizationId,
    tenantId: record.tenantId,
    code: record.code,
    name: record.name,
    symbol: record.symbol ?? null,
    decimalPlaces: record.decimalPlaces,
    thousandsSeparator: record.thousandsSeparator ?? null,
    decimalSeparator: record.decimalSeparator ?? null,
    isBase: !!record.isBase,
    isActive: !!record.isActive,
    createdAt: record.createdAt.toISOString(),
    updatedAt: record.updatedAt.toISOString()
  };
}
async function enforceBaseCurrency(em, currencyId, organizationId, tenantId) {
  await em.nativeUpdate(
    Currency,
    {
      organizationId,
      tenantId,
      id: { $ne: currencyId },
      isBase: true,
      deletedAt: null
    },
    { isBase: false, updatedAt: /* @__PURE__ */ new Date() }
  );
}
const createCurrencyCommand = {
  id: "currencies.currencies.create",
  async execute(input, ctx) {
    const parsed = currencyCreateSchema.parse(input);
    const em = ctx.container.resolve("em").fork();
    const existing = await em.findOne(Currency, {
      code: parsed.code,
      organizationId: parsed.organizationId,
      tenantId: parsed.tenantId,
      deletedAt: null
    });
    if (existing) {
      throw conflict("Currency code already exists for this organization.");
    }
    const now = /* @__PURE__ */ new Date();
    const record = em.create(Currency, {
      organizationId: parsed.organizationId,
      tenantId: parsed.tenantId,
      code: parsed.code,
      name: parsed.name,
      symbol: parsed.symbol ?? null,
      decimalPlaces: parsed.decimalPlaces ?? 2,
      thousandsSeparator: parsed.thousandsSeparator ?? null,
      decimalSeparator: parsed.decimalSeparator ?? null,
      isBase: parsed.isBase ?? false,
      isActive: parsed.isActive !== false,
      createdAt: now,
      updatedAt: now
    });
    em.persist(record);
    try {
      await withAtomicFlush(
        em,
        [
          () => record.isBase ? enforceBaseCurrency(em, record.id, record.organizationId, record.tenantId) : void 0
        ],
        { transaction: true }
      );
    } catch (err) {
      if (isUniqueViolation(err, "currencies_code_scope_unique")) {
        throw conflict("Currency code already exists for this organization.");
      }
      throw err;
    }
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "created",
      entity: record,
      identifiers: {
        id: record.id,
        organizationId: record.organizationId,
        tenantId: record.tenantId
      },
      events: currencyCrudEvents
    });
    return { currencyId: record.id };
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em").fork();
    return loadCurrencySnapshot(em, result.currencyId);
  },
  buildLog: async ({ snapshots }) => {
    const after = snapshots.after;
    if (!after) return null;
    const { translate } = await resolveTranslations();
    return {
      actionLabel: translate("currencies.audit.create", "Create currency"),
      resourceKind: "currencies.currency",
      resourceId: after.id,
      tenantId: after.tenantId,
      organizationId: after.organizationId,
      snapshotAfter: after,
      payload: { undo: { after } }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry);
    const after = payload?.after ?? null;
    if (!after) return;
    const em = ctx.container.resolve("em").fork();
    const record = await em.findOne(Currency, { id: after.id });
    if (!record) return;
    record.deletedAt = /* @__PURE__ */ new Date();
    record.isActive = false;
    await em.flush();
  },
  redo: makeCreateRedo({
    entityClass: Currency,
    buildResult: (entity) => ({ currencyId: entity.id }),
    events: currencyCrudEvents,
    afterRestore: async ({ em, entity }) => {
      if (entity.isBase) {
        await enforceBaseCurrency(em, entity.id, entity.organizationId, entity.tenantId);
        await em.flush();
      }
    }
  })
};
const updateCurrencyCommand = {
  id: "currencies.currencies.update",
  async prepare(input, ctx) {
    requireId(input.id, "Currency ID is required");
    const em = ctx.container.resolve("em");
    const before = await loadCurrencySnapshot(em, input.id);
    return { before };
  },
  async execute(input, ctx) {
    const parsed = currencyUpdateSchema.parse(input);
    requireId(parsed.id, "Currency ID is required");
    const em = ctx.container.resolve("em").fork();
    const record = await em.findOne(Currency, { id: parsed.id, deletedAt: null });
    if (!record) {
      throw new CrudHttpError(404, { error: "Currency not found" });
    }
    if (parsed.code && parsed.code !== record.code) {
      const existing = await em.findOne(Currency, {
        code: parsed.code,
        organizationId: record.organizationId,
        tenantId: record.tenantId,
        id: { $ne: record.id },
        deletedAt: null
      });
      if (existing) {
        throw conflict("Currency code already exists for this organization.");
      }
    }
    const allChanges = buildChanges(record, parsed, [
      "code",
      "name",
      "symbol",
      "decimalPlaces",
      "thousandsSeparator",
      "decimalSeparator",
      "isBase",
      "isActive"
    ]);
    const changes = Object.fromEntries(
      Object.entries(allChanges).filter(([, c]) => c.to !== void 0)
    );
    if (Object.keys(changes).length === 0) {
      return { currencyId: record.id };
    }
    await withAtomicFlush(
      em,
      [
        () => {
          for (const [key, change] of Object.entries(changes)) {
            ;
            record[key] = change.to;
          }
          record.updatedAt = /* @__PURE__ */ new Date();
        },
        () => parsed.isBase === true && record.isBase ? enforceBaseCurrency(em, record.id, record.organizationId, record.tenantId) : void 0
      ],
      { transaction: true }
    );
    const de = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine: de,
      action: "updated",
      entity: record,
      identifiers: {
        id: record.id,
        organizationId: record.organizationId,
        tenantId: record.tenantId
      },
      events: currencyCrudEvents
    });
    return { currencyId: record.id };
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em").fork();
    return loadCurrencySnapshot(em, result.currencyId);
  },
  buildLog: async ({ snapshots, result }) => {
    const before = snapshots.before;
    const after = snapshots.after;
    if (!after) return null;
    const { translate } = await resolveTranslations();
    return {
      actionLabel: translate("currencies.audit.update", "Update currency"),
      resourceKind: "currencies.currency",
      resourceId: after.id,
      tenantId: after.tenantId,
      organizationId: after.organizationId,
      snapshotBefore: before ?? void 0,
      snapshotAfter: after,
      payload: { undo: { before, after } }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry);
    const before = payload?.before ?? null;
    if (!before) return;
    const em = ctx.container.resolve("em").fork();
    const record = await em.findOne(Currency, { id: before.id });
    if (!record) return;
    Object.assign(record, {
      code: before.code,
      name: before.name,
      symbol: before.symbol,
      decimalPlaces: before.decimalPlaces,
      thousandsSeparator: before.thousandsSeparator,
      decimalSeparator: before.decimalSeparator,
      isBase: before.isBase,
      isActive: before.isActive,
      updatedAt: /* @__PURE__ */ new Date()
    });
    await em.flush();
  }
};
const deleteCurrencyCommand = {
  id: "currencies.currencies.delete",
  async prepare(input, ctx) {
    requireId(input.id, "Currency ID is required");
    const em = ctx.container.resolve("em");
    const before = await loadCurrencySnapshot(em, input.id);
    return { before };
  },
  async execute(input, ctx) {
    const parsed = currencyDeleteSchema.parse(input);
    requireId(parsed.id, "Currency ID is required");
    const em = ctx.container.resolve("em").fork();
    const record = await em.findOne(Currency, { id: parsed.id, deletedAt: null });
    if (!record) {
      throw new CrudHttpError(404, { error: "Currency not found" });
    }
    if (record.isBase) {
      throw new CrudHttpError(400, { error: "Cannot delete the base currency" });
    }
    const activeRatesCount = await em.count(ExchangeRate, {
      $or: [
        { fromCurrencyCode: record.code },
        { toCurrencyCode: record.code }
      ],
      organizationId: record.organizationId,
      tenantId: record.tenantId,
      deletedAt: null,
      isActive: true
    });
    if (activeRatesCount > 0) {
      throw new CrudHttpError(400, {
        error: `Cannot delete currency ${record.code} because it has ${activeRatesCount} active exchange rate(s). Please delete or deactivate the exchange rates first.`
      });
    }
    record.deletedAt = /* @__PURE__ */ new Date();
    record.isActive = false;
    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
      },
      events: currencyCrudEvents
    });
    return { currencyId: record.id };
  },
  buildLog: async ({ snapshots, result }) => {
    const before = snapshots.before;
    if (!before) return null;
    const { translate } = await resolveTranslations();
    return {
      actionLabel: translate("currencies.audit.delete", "Delete currency"),
      resourceKind: "currencies.currency",
      resourceId: before.id,
      tenantId: before.tenantId,
      organizationId: before.organizationId,
      snapshotBefore: before,
      payload: { undo: { before } }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const payload = extractUndoPayload(logEntry);
    const before = payload?.before ?? null;
    if (!before) return;
    const em = ctx.container.resolve("em").fork();
    const record = await em.findOne(Currency, { id: before.id });
    if (!record) return;
    record.deletedAt = null;
    record.isActive = before.isActive;
    record.updatedAt = /* @__PURE__ */ new Date();
    await em.flush();
  }
};
registerCommand(createCurrencyCommand);
registerCommand(updateCurrencyCommand);
registerCommand(deleteCurrencyCommand);
export {
  createCurrencyCommand,
  deleteCurrencyCommand,
  updateCurrencyCommand
};
//# sourceMappingURL=currencies.js.map
