import {
  findOneWithDecryption,
  findWithDecryption
} from "@open-mercato/shared/lib/encryption/find";
import {
  loadCustomFieldDefinitionIndex,
  loadCustomFieldValues
} from "@open-mercato/shared/lib/crud/custom-fields";
import { E } from "../../../generated/entities.ids.generated.js";
import { Attachment } from "@open-mercato/core/modules/attachments/data/entities";
import {
  CatalogPriceKind,
  CatalogProduct,
  CatalogProductCategoryAssignment,
  CatalogProductPrice,
  CatalogProductTagAssignment,
  CatalogProductUnitConversion,
  CatalogProductVariant
} from "../data/entities.js";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("catalog");
function resolveEm(ctx) {
  return ctx.container.resolve("em");
}
function buildScope(ctx, tenantId) {
  return { tenantId, organizationId: ctx.organizationId };
}
async function listPriceKindsCore(ctx, input, tenantId) {
  const em = resolveEm(ctx);
  const limit = input.limit ?? 50;
  const offset = input.offset ?? 0;
  const where = { tenantId, deletedAt: null };
  if (ctx.organizationId) {
    where.$or = [{ organizationId: ctx.organizationId }, { organizationId: null }];
  }
  const [rows, total] = await Promise.all([
    findWithDecryption(
      em,
      CatalogPriceKind,
      where,
      { limit, offset, orderBy: { code: "asc" } },
      buildScope(ctx, tenantId)
    ),
    em.count(CatalogPriceKind, where)
  ]);
  const filtered = rows.filter((row) => row.tenantId === tenantId);
  return {
    items: filtered.map((row) => ({
      id: row.id,
      code: row.code,
      title: row.title,
      displayMode: row.displayMode,
      currencyCode: row.currencyCode ?? null,
      isPromotion: !!row.isPromotion,
      isActive: !!row.isActive,
      organizationId: row.organizationId ?? null,
      tenantId: row.tenantId ?? null,
      createdAt: row.createdAt ? new Date(row.createdAt).toISOString() : null,
      updatedAt: row.updatedAt ? new Date(row.updatedAt).toISOString() : null
    })),
    total,
    limit,
    offset
  };
}
function toProductSummary(row) {
  return {
    id: row.id,
    title: row.title,
    subtitle: row.subtitle ?? null,
    sku: row.sku ?? null,
    handle: row.handle ?? null,
    productType: row.productType,
    statusEntryId: row.statusEntryId ?? null,
    primaryCurrencyCode: row.primaryCurrencyCode ?? null,
    defaultMediaId: row.defaultMediaId ?? null,
    defaultMediaUrl: row.defaultMediaUrl ?? null,
    imageUrl: row.defaultMediaUrl ?? null,
    isActive: !!row.isActive,
    isConfigurable: !!row.isConfigurable,
    organizationId: row.organizationId ?? null,
    tenantId: row.tenantId ?? null,
    createdAt: row.createdAt ? new Date(row.createdAt).toISOString() : null,
    updatedAt: row.updatedAt ? new Date(row.updatedAt).toISOString() : null,
    // `description` is a product field used by D18 authoring tools to seed
    // extract-attributes-from-description; falls back to null when absent.
    description: row.description ?? null
  };
}
function toPriceNumeric(value) {
  if (value === null || value === void 0) return null;
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : null;
}
function summarizeDefinitionAsField(summary, scope) {
  return {
    key: summary.key,
    label: summary.label ?? null,
    type: summary.kind ?? null,
    required: false,
    options: null,
    scope
  };
}
async function resolveAttributeSchema(ctx, tenantId, productId, categoryId) {
  const em = resolveEm(ctx);
  const organizationIds = ctx.organizationId ? [ctx.organizationId] : [];
  const moduleDefs = await loadCustomFieldDefinitionIndex({
    em,
    entityIds: [E.catalog.catalog_product, E.catalog.catalog_product_category],
    tenantId,
    organizationIds
  });
  const fields = [];
  moduleDefs.forEach((entries) => {
    const pick = entries[0];
    if (!pick) return;
    const scope = pick.organizationId ? "product" : "module";
    fields.push(summarizeDefinitionAsField(pick, scope));
  });
  return {
    fields,
    resolvedFor: {
      ...productId ? { productId } : {},
      ...categoryId ? { categoryId } : {}
    }
  };
}
function resolvePricingService(ctx) {
  try {
    return ctx.container.resolve("catalogPricingService");
  } catch {
    return null;
  }
}
async function buildProductBundle(em, ctx, tenantId, productId) {
  const where = {
    id: productId,
    tenantId,
    deletedAt: null
  };
  if (ctx.organizationId) where.organizationId = ctx.organizationId;
  const product = await findOneWithDecryption(
    em,
    CatalogProduct,
    where,
    void 0,
    buildScope(ctx, tenantId)
  );
  if (!product || product.tenantId !== tenantId) {
    return { found: false, productId };
  }
  const scope = buildScope(ctx, tenantId);
  const [
    categoryAssignments,
    tagAssignments,
    variants,
    prices,
    mediaAttachments,
    unitConversions,
    customFieldValues,
    attributeSchema
  ] = await Promise.all([
    findWithDecryption(
      em,
      CatalogProductCategoryAssignment,
      { tenantId, product: product.id },
      { limit: 100, populate: ["category"] },
      scope
    ),
    findWithDecryption(
      em,
      CatalogProductTagAssignment,
      { tenantId, product: product.id },
      { limit: 100, populate: ["tag"] },
      scope
    ),
    findWithDecryption(
      em,
      CatalogProductVariant,
      { tenantId, product: product.id, deletedAt: null },
      { limit: 100, orderBy: { createdAt: "asc" } },
      scope
    ),
    findWithDecryption(
      em,
      CatalogProductPrice,
      { tenantId, product: product.id },
      { limit: 100, orderBy: { createdAt: "asc" } },
      scope
    ),
    findWithDecryption(
      em,
      Attachment,
      { tenantId, entityId: E.catalog.catalog_product, recordId: product.id },
      { limit: 100, orderBy: { createdAt: "asc" } },
      scope
    ),
    findWithDecryption(
      em,
      CatalogProductUnitConversion,
      { tenantId, product: product.id, deletedAt: null },
      { limit: 100, orderBy: { sortOrder: "asc", createdAt: "asc" } },
      scope
    ),
    loadCustomFieldValues({
      em,
      entityId: E.catalog.catalog_product,
      recordIds: [product.id],
      tenantIdByRecord: { [product.id]: product.tenantId ?? null },
      organizationIdByRecord: { [product.id]: product.organizationId ?? null },
      tenantFallbacks: [product.tenantId ?? tenantId].filter((value) => !!value)
    }),
    resolveAttributeSchema(ctx, tenantId, product.id, void 0)
  ]);
  const categories = categoryAssignments.map((assignment) => {
    const category = assignment.category;
    if (!category || typeof category === "string") {
      const fallbackId = typeof category === "string" ? category : null;
      return fallbackId ? { id: fallbackId, name: null, slug: null, path: null } : null;
    }
    return {
      id: category.id,
      name: category.name ?? null,
      slug: category.slug ?? null,
      path: category.treePath ?? null
    };
  }).filter((value) => value !== null);
  const tags = tagAssignments.map((assignment) => {
    const tag = assignment.tag;
    if (!tag || typeof tag === "string") return null;
    return { id: tag.id, label: tag.label, slug: tag.slug };
  }).filter((value) => value !== null);
  const priceRows = prices.map((row) => ({
    id: row.id,
    priceKindId: row.priceKind && typeof row.priceKind === "object" ? row.priceKind.id : row.priceKind ?? null,
    currencyCode: row.currencyCode,
    kind: row.kind,
    minQuantity: row.minQuantity,
    maxQuantity: row.maxQuantity ?? null,
    unitPriceNet: row.unitPriceNet ?? null,
    unitPriceGross: row.unitPriceGross ?? null,
    taxRate: row.taxRate ?? null,
    taxAmount: row.taxAmount ?? null,
    channelId: row.channelId ?? null,
    offerId: row.offer && typeof row.offer === "object" ? row.offer.id : row.offer ?? null,
    variantId: row.variant && typeof row.variant === "object" ? row.variant.id : row.variant ?? null,
    startsAt: row.startsAt ? new Date(row.startsAt).toISOString() : null,
    endsAt: row.endsAt ? new Date(row.endsAt).toISOString() : null
  }));
  let bestPrice = null;
  const pricingService = resolvePricingService(ctx);
  if (pricingService && prices.length > 0) {
    const pricingContext = {
      quantity: 1,
      date: /* @__PURE__ */ new Date()
    };
    try {
      const resolved = await pricingService.resolvePrice(prices, pricingContext);
      if (resolved) {
        bestPrice = {
          id: resolved.id,
          currencyCode: resolved.currencyCode,
          kind: resolved.kind,
          unitPriceNet: resolved.unitPriceNet ?? null,
          unitPriceGross: resolved.unitPriceGross ?? null
        };
      }
    } catch (error) {
      logger.warn("catalog.get_product_bundle resolvePrice failed, omitting best price", { err: error });
    }
  }
  return {
    found: true,
    id: product.id,
    product: toProductSummary(product),
    categories,
    tags,
    variants: variants.map((variant) => ({
      id: variant.id,
      name: variant.name ?? null,
      sku: variant.sku ?? null,
      barcode: variant.barcode ?? null,
      optionValues: variant.optionValues ?? null,
      defaultMediaId: variant.defaultMediaId ?? null,
      defaultMediaUrl: variant.defaultMediaUrl ?? null,
      isDefault: !!variant.isDefault,
      isActive: !!variant.isActive
    })),
    prices: {
      all: priceRows,
      best: bestPrice
    },
    media: mediaAttachments.map((attachment) => ({
      mediaId: attachment.id,
      attachmentId: attachment.id,
      fileName: attachment.fileName,
      mediaType: attachment.mimeType,
      size: attachment.fileSize,
      altText: null,
      sortOrder: 0
    })),
    customFields: customFieldValues[product.id] ?? {},
    attributeSchema,
    // No translation resolver exists for catalog (no `translations.ts` at
    // module root yet); returning null is an explicit null-surface contract
    // and a hint for Step 5+ to add the translations resolver.
    translations: null
  };
}
export {
  buildProductBundle,
  buildScope,
  listPriceKindsCore,
  resolveAttributeSchema,
  resolveEm,
  toPriceNumeric,
  toProductSummary
};
//# sourceMappingURL=_shared.js.map
