import { registerCommand } from "@open-mercato/shared/lib/commands";
import {
  emitCrudSideEffects,
  emitCrudUndoSideEffects,
  buildChanges,
  requireId
} 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 { E } from "../../../generated/entities.ids.generated.js";
import {
  SalesNote,
  SalesOrder,
  SalesQuote,
  SalesInvoice,
  SalesCreditMemo
} from "../data/entities.js";
import {
  noteCreateSchema,
  noteUpdateSchema
} from "../data/validators.js";
import { makeCreateRedo } from "@open-mercato/shared/lib/commands/redo";
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
import { ensureOrganizationScope, ensureSameScope, ensureTenantScope, extractUndoPayload } from "./shared.js";
const noteCrudIndexer = {
  entityType: E.sales.sales_note
};
const noteCrudEvents = {
  module: "sales",
  entity: "note",
  persistent: true,
  buildPayload: (ctx) => ({
    id: ctx.identifiers.id,
    organizationId: ctx.identifiers.organizationId,
    tenantId: ctx.identifiers.tenantId
  })
};
async function loadNoteSnapshot(em, id) {
  const note = await findOneWithDecryption(em, SalesNote, { id }, {});
  if (!note) return null;
  return {
    id: note.id,
    organizationId: note.organizationId,
    tenantId: note.tenantId,
    contextType: note.contextType,
    contextId: note.contextId,
    orderId: note.order ? typeof note.order === "string" ? note.order : note.order.id : null,
    quoteId: note.quote ? typeof note.quote === "string" ? note.quote : note.quote.id : null,
    body: note.body,
    authorUserId: note.authorUserId ?? null,
    appearanceIcon: note.appearanceIcon ?? null,
    appearanceColor: note.appearanceColor ?? null
  };
}
async function requireContext(em, contextType, contextId, organizationId, tenantId) {
  if (contextType === "order") {
    const order = await findOneWithDecryption(em, SalesOrder, { id: contextId }, {}, { tenantId, organizationId });
    if (!order) {
      throw new CrudHttpError(404, { error: "sales.notes.context_not_found" });
    }
    if (organizationId && tenantId) {
      ensureSameScope(order, organizationId, tenantId);
    }
    return {
      organizationId: order.organizationId,
      tenantId: order.tenantId,
      order,
      quote: null
    };
  }
  if (contextType === "quote") {
    const quote = await findOneWithDecryption(em, SalesQuote, { id: contextId }, {}, { tenantId, organizationId });
    if (!quote) {
      throw new CrudHttpError(404, { error: "sales.notes.context_not_found" });
    }
    if (organizationId && tenantId) {
      ensureSameScope(quote, organizationId, tenantId);
    }
    return {
      organizationId: quote.organizationId,
      tenantId: quote.tenantId,
      order: null,
      quote
    };
  }
  const repo = contextType === "invoice" ? SalesInvoice : SalesCreditMemo;
  const entity = await findOneWithDecryption(
    em,
    repo,
    { id: contextId },
    {},
    { tenantId, organizationId }
  );
  if (!entity) {
    throw new CrudHttpError(404, { error: "sales.notes.context_not_found" });
  }
  if (organizationId && tenantId) {
    ensureSameScope(entity, organizationId, tenantId);
  }
  return {
    organizationId: entity.organizationId,
    tenantId: entity.tenantId,
    order: null,
    quote: null
  };
}
function resolveAuthor(authSub) {
  const sub = authSub?.trim();
  if (!sub) return null;
  const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/;
  return uuidRegex.test(sub) ? sub : null;
}
const createNoteCommand = {
  id: "sales.notes.create",
  async execute(rawInput, ctx) {
    const parsed = noteCreateSchema.parse(rawInput ?? {});
    ensureTenantScope(ctx, parsed.tenantId);
    ensureOrganizationScope(ctx, parsed.organizationId);
    const em = ctx.container.resolve("em").fork();
    const context = await requireContext(em, parsed.contextType, parsed.contextId, parsed.organizationId, parsed.tenantId);
    const authorUserId = resolveAuthor(ctx.auth?.isApiKey ? null : ctx.auth?.sub ?? null);
    const note = em.create(SalesNote, {
      organizationId: parsed.organizationId,
      tenantId: parsed.tenantId,
      contextType: parsed.contextType,
      contextId: parsed.contextId,
      order: context.order ?? null,
      quote: context.quote ?? null,
      authorUserId,
      appearanceIcon: parsed.appearanceIcon ?? null,
      appearanceColor: parsed.appearanceColor ?? null,
      body: parsed.body,
      createdAt: /* @__PURE__ */ new Date(),
      updatedAt: /* @__PURE__ */ new Date()
    });
    em.persist(note);
    await em.flush();
    const dataEngine = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine,
      action: "created",
      entity: note,
      identifiers: {
        id: note.id,
        organizationId: note.organizationId,
        tenantId: note.tenantId
      },
      indexer: noteCrudIndexer,
      events: noteCrudEvents
    });
    return { noteId: note.id, authorUserId: note.authorUserId ?? null };
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em").fork();
    return loadNoteSnapshot(em, result.noteId);
  },
  buildLog: async ({ result, snapshots }) => {
    const { translate } = await resolveTranslations();
    const snapshot = snapshots.after;
    return {
      actionLabel: translate("sales.audit.notes.create", "Create note"),
      resourceKind: "sales.note",
      resourceId: result.noteId,
      parentResourceKind: snapshot?.contextType ? `sales.${snapshot.contextType}` : null,
      parentResourceId: snapshot?.contextId ?? null,
      tenantId: snapshot?.tenantId ?? null,
      organizationId: snapshot?.organizationId ?? null,
      snapshotAfter: snapshot ?? null,
      payload: {
        undo: {
          after: snapshot ?? null
        }
      }
    };
  },
  undo: async ({ logEntry, ctx }) => {
    const noteId = logEntry?.resourceId ?? null;
    if (!noteId) return;
    const em = ctx.container.resolve("em").fork();
    const existing = await findOneWithDecryption(em, SalesNote, { id: noteId }, {});
    if (existing) {
      em.remove(existing);
      await em.flush();
    }
  },
  redo: makeCreateRedo({
    entityClass: SalesNote,
    indexer: noteCrudIndexer,
    events: noteCrudEvents,
    findRow: ({ em, id, snapshot }) => findOneWithDecryption(
      em,
      SalesNote,
      { id },
      void 0,
      { tenantId: snapshot.tenantId, organizationId: snapshot.organizationId }
    ),
    seedFromSnapshot: (snapshot) => ({
      id: snapshot.id,
      organizationId: snapshot.organizationId,
      tenantId: snapshot.tenantId,
      contextType: snapshot.contextType,
      contextId: snapshot.contextId,
      body: snapshot.body,
      authorUserId: snapshot.authorUserId,
      appearanceIcon: snapshot.appearanceIcon,
      appearanceColor: snapshot.appearanceColor,
      createdAt: /* @__PURE__ */ new Date(),
      updatedAt: /* @__PURE__ */ new Date()
    }),
    beforeRestore: async ({ em, snapshot }) => {
      const context = await requireContext(em, snapshot.contextType, snapshot.contextId).catch(() => null);
      if (!context) {
        throw new CrudHttpError(404, { error: "sales.notes.context_not_found" });
      }
      return {
        order: snapshot.orderId ? context.order ?? null : null,
        quote: snapshot.quoteId ? context.quote ?? null : null
      };
    },
    buildResult: (entity) => ({ noteId: entity.id, authorUserId: entity.authorUserId ?? null })
  })
};
const updateNoteCommand = {
  id: "sales.notes.update",
  async prepare(rawInput, ctx) {
    const parsed = noteUpdateSchema.parse(rawInput ?? {});
    const em = ctx.container.resolve("em");
    const snapshot = await loadNoteSnapshot(em, parsed.id);
    return snapshot ? { before: snapshot } : {};
  },
  async execute(rawInput, ctx) {
    const parsed = noteUpdateSchema.parse(rawInput ?? {});
    const em = ctx.container.resolve("em").fork();
    const note = await findOneWithDecryption(em, SalesNote, { id: parsed.id }, {});
    if (!note) throw new CrudHttpError(404, { error: "sales.notes.not_found" });
    ensureTenantScope(ctx, note.tenantId);
    ensureOrganizationScope(ctx, note.organizationId);
    if (parsed.body !== void 0) note.body = parsed.body;
    if (parsed.authorUserId !== void 0) {
      note.authorUserId = resolveAuthor(ctx.auth?.isApiKey ? null : ctx.auth?.sub ?? null);
    }
    if (parsed.appearanceIcon !== void 0) note.appearanceIcon = parsed.appearanceIcon ?? null;
    if (parsed.appearanceColor !== void 0) note.appearanceColor = parsed.appearanceColor ?? null;
    note.updatedAt = /* @__PURE__ */ new Date();
    await em.flush();
    const dataEngine = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine,
      action: "updated",
      entity: note,
      identifiers: {
        id: note.id,
        organizationId: note.organizationId,
        tenantId: note.tenantId
      },
      indexer: noteCrudIndexer,
      events: noteCrudEvents
    });
    return { noteId: note.id };
  },
  captureAfter: async (_input, result, ctx) => {
    const em = ctx.container.resolve("em").fork();
    return await loadNoteSnapshot(em, result.noteId);
  },
  buildLog: async ({ snapshots }) => {
    const { translate } = await resolveTranslations();
    const before = snapshots.before;
    if (!before) return null;
    const after = snapshots.after;
    const changes = after && before ? buildChanges(
      before,
      after,
      ["body", "authorUserId", "appearanceIcon", "appearanceColor"]
    ) : {};
    return {
      actionLabel: translate("sales.audit.notes.update", "Update note"),
      resourceKind: "sales.note",
      resourceId: before.id,
      parentResourceKind: before.contextType ? `sales.${before.contextType}` : null,
      parentResourceId: before.contextId ?? null,
      tenantId: before.tenantId,
      organizationId: before.organizationId,
      snapshotBefore: before,
      snapshotAfter: after ?? null,
      changes,
      payload: {
        undo: {
          before,
          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 context = await requireContext(em, before.contextType, before.contextId).catch(() => null);
    if (!context) return;
    let note = await findOneWithDecryption(em, SalesNote, { id: before.id }, {}, { tenantId: before.tenantId, organizationId: before.organizationId });
    if (!note) {
      note = em.create(SalesNote, {
        id: before.id,
        organizationId: before.organizationId,
        tenantId: before.tenantId,
        contextType: before.contextType,
        contextId: before.contextId,
        order: before.orderId ? context.order ?? null : null,
        quote: before.quoteId ? context.quote ?? null : null,
        body: before.body,
        authorUserId: before.authorUserId,
        appearanceIcon: before.appearanceIcon,
        appearanceColor: before.appearanceColor,
        createdAt: /* @__PURE__ */ new Date(),
        updatedAt: /* @__PURE__ */ new Date()
      });
      em.persist(note);
    }
    note.organizationId = before.organizationId;
    note.tenantId = before.tenantId;
    note.contextType = before.contextType;
    note.contextId = before.contextId;
    note.order = before.orderId ? context.order ?? null : null;
    note.quote = before.quoteId ? context.quote ?? null : null;
    note.body = before.body;
    note.authorUserId = before.authorUserId;
    note.appearanceIcon = before.appearanceIcon;
    note.appearanceColor = before.appearanceColor;
    note.updatedAt = /* @__PURE__ */ new Date();
    await em.flush();
    const dataEngine = ctx.container.resolve("dataEngine");
    await emitCrudUndoSideEffects({
      dataEngine,
      action: "updated",
      entity: note,
      identifiers: {
        id: note.id,
        organizationId: note.organizationId,
        tenantId: note.tenantId
      },
      indexer: noteCrudIndexer,
      events: noteCrudEvents
    });
  }
};
const deleteNoteCommand = {
  id: "sales.notes.delete",
  async prepare(input, ctx) {
    const id = requireId(input, "Note id required");
    const em = ctx.container.resolve("em");
    const snapshot = await loadNoteSnapshot(em, id);
    return snapshot ? { before: snapshot } : {};
  },
  async execute(input, ctx) {
    const id = requireId(input, "Note id required");
    const em = ctx.container.resolve("em").fork();
    const note = await findOneWithDecryption(em, SalesNote, { id }, {});
    if (!note) throw new CrudHttpError(404, { error: "sales.notes.not_found" });
    ensureTenantScope(ctx, note.tenantId);
    ensureOrganizationScope(ctx, note.organizationId);
    em.remove(note);
    await em.flush();
    const dataEngine = ctx.container.resolve("dataEngine");
    await emitCrudSideEffects({
      dataEngine,
      action: "deleted",
      entity: note,
      identifiers: {
        id: note.id,
        organizationId: note.organizationId,
        tenantId: note.tenantId
      },
      indexer: noteCrudIndexer,
      events: noteCrudEvents
    });
    return { noteId: note.id };
  },
  buildLog: async ({ snapshots }) => {
    const before = snapshots.before;
    if (!before) return null;
    const { translate } = await resolveTranslations();
    return {
      actionLabel: translate("sales.audit.notes.delete", "Delete note"),
      resourceKind: "sales.note",
      resourceId: before.id,
      parentResourceKind: before.contextType ? `sales.${before.contextType}` : null,
      parentResourceId: before.contextId ?? 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 context = await requireContext(em, before.contextType, before.contextId).catch(() => null);
    if (!context) return;
    let note = await findOneWithDecryption(em, SalesNote, { id: before.id }, {}, { tenantId: before.tenantId, organizationId: before.organizationId });
    if (!note) {
      note = em.create(SalesNote, {
        id: before.id,
        organizationId: before.organizationId,
        tenantId: before.tenantId,
        contextType: before.contextType,
        contextId: before.contextId,
        order: before.orderId ? context.order ?? null : null,
        quote: before.quoteId ? context.quote ?? null : null,
        body: before.body,
        authorUserId: before.authorUserId,
        appearanceIcon: before.appearanceIcon,
        appearanceColor: before.appearanceColor,
        createdAt: /* @__PURE__ */ new Date(),
        updatedAt: /* @__PURE__ */ new Date()
      });
      em.persist(note);
    }
    note.organizationId = before.organizationId;
    note.tenantId = before.tenantId;
    note.contextType = before.contextType;
    note.contextId = before.contextId;
    note.order = before.orderId ? context.order ?? null : null;
    note.quote = before.quoteId ? context.quote ?? null : null;
    note.body = before.body;
    note.authorUserId = before.authorUserId;
    note.appearanceIcon = before.appearanceIcon;
    note.appearanceColor = before.appearanceColor;
    note.updatedAt = /* @__PURE__ */ new Date();
    await em.flush();
    const dataEngine = ctx.container.resolve("dataEngine");
    await emitCrudUndoSideEffects({
      dataEngine,
      action: "created",
      entity: note,
      identifiers: {
        id: note.id,
        organizationId: note.organizationId,
        tenantId: note.tenantId
      },
      indexer: noteCrudIndexer,
      events: noteCrudEvents
    });
  }
};
registerCommand(createNoteCommand);
registerCommand(updateNoteCommand);
registerCommand(deleteNoteCommand);
//# sourceMappingURL=notes.js.map
