import { createScopedApiHelpers } from "@open-mercato/shared/lib/api/scoped";
import { sql } from "kysely";
import { resolveSearchConfig } from "@open-mercato/shared/lib/search/config";
import { tokenizeText } from "@open-mercato/shared/lib/search/tokenize";
import { SortDir } from "@open-mercato/shared/lib/query/types";
const { withScopedPayload, parseScopedCommandInput } = createScopedApiHelpers({
  messages: {
    tenantRequired: { key: "customers.errors.tenant_required", fallback: "Tenant context is required" },
    organizationRequired: { key: "customers.errors.organization_required", fallback: "Organization context is required" }
  }
});
const NO_MATCH_ID = "00000000-0000-0000-0000-000000000000";
async function enrichSearchSourcesWithCustomFieldTokens(ctx, sources) {
  const entityTypes = Array.from(
    new Set(
      sources.map((source) => source.entityType).filter((value) => typeof value === "string" && value.length > 0)
    )
  );
  if (!entityTypes.length) return sources;
  const em = ctx.container.resolve("em");
  const db = em.getKysely();
  let defsQuery = db.selectFrom("custom_field_defs").select(["entity_id", "key", "kind"]).where("entity_id", "in", entityTypes).where("is_active", "=", true);
  const tenantScope = ctx.auth?.tenantId ?? null;
  defsQuery = defsQuery.where((eb) => eb.or([
    eb("tenant_id", "=", tenantScope),
    eb("tenant_id", "is", null)
  ]));
  if (ctx.selectedOrganizationId) {
    defsQuery = defsQuery.where((eb) => eb.or([
      eb("organization_id", "=", ctx.selectedOrganizationId),
      eb("organization_id", "is", null)
    ]));
  } else if (Array.isArray(ctx.organizationIds) && ctx.organizationIds.length > 0) {
    defsQuery = defsQuery.where((eb) => eb.or([
      eb("organization_id", "in", ctx.organizationIds),
      eb("organization_id", "is", null)
    ]));
  }
  const customFieldKeysByEntity = /* @__PURE__ */ new Map();
  const rows = await defsQuery.execute();
  for (const row of rows) {
    if (row.kind === "attachment") continue;
    const entityType = typeof row.entity_id === "string" ? row.entity_id : null;
    const key = typeof row.key === "string" ? row.key.trim() : "";
    if (!entityType || !key) continue;
    const bucket = customFieldKeysByEntity.get(entityType) ?? /* @__PURE__ */ new Set();
    bucket.add(`cf:${key}`);
    customFieldKeysByEntity.set(entityType, bucket);
  }
  return sources.map((source) => {
    const customFieldKeys = customFieldKeysByEntity.get(source.entityType);
    return {
      ...source,
      fields: Array.from(/* @__PURE__ */ new Set([
        "search_text",
        ...source.fields,
        ...customFieldKeys ? Array.from(customFieldKeys) : []
      ]))
    };
  });
}
async function findSearchTokenEntityIds({
  ctx,
  entityType,
  fields,
  query
}) {
  const trimmed = query.trim();
  if (!trimmed) return null;
  const tokens = tokenizeText(trimmed, resolveSearchConfig());
  if (!tokens.hashes.length) return [];
  const em = ctx.container.resolve("em");
  const db = em.getKysely();
  let searchQuery = db.selectFrom("search_tokens").select("entity_id").where("entity_type", "=", entityType).where("field", "in", fields).where("token_hash", "in", tokens.hashes).groupBy("entity_id").having(sql`count(distinct token_hash) >= ${tokens.hashes.length}`);
  if (ctx.auth?.tenantId !== void 0) {
    searchQuery = searchQuery.where(sql`tenant_id is not distinct from ${ctx.auth?.tenantId ?? null}`);
  }
  if (ctx.selectedOrganizationId) {
    searchQuery = searchQuery.where("organization_id", "=", ctx.selectedOrganizationId);
  } else if (Array.isArray(ctx.organizationIds) && ctx.organizationIds.length > 0) {
    searchQuery = searchQuery.where("organization_id", "in", ctx.organizationIds);
  }
  const rows = await searchQuery.execute();
  return rows.map((row) => typeof row.entity_id === "string" ? row.entity_id : null).filter((id) => typeof id === "string" && id.length > 0);
}
async function mapScopedEntityIds({
  ctx,
  ids,
  config
}) {
  if (!ids.length) return [];
  const em = ctx.container.resolve("em");
  const db = em.getKysely();
  const sourceColumn = config.sourceColumn ?? "id";
  const tenantColumn = config.tenantColumn ?? "tenant_id";
  const organizationColumn = config.organizationColumn ?? "organization_id";
  let mapQuery = db.selectFrom(config.table).select(config.targetColumn).where(sourceColumn, "in", ids);
  if (ctx.auth?.tenantId !== void 0) {
    mapQuery = mapQuery.where(sql`${sql.ref(tenantColumn)} is not distinct from ${ctx.auth?.tenantId ?? null}`);
  }
  if (ctx.selectedOrganizationId) {
    mapQuery = mapQuery.where(organizationColumn, "=", ctx.selectedOrganizationId);
  } else if (Array.isArray(ctx.organizationIds) && ctx.organizationIds.length > 0) {
    mapQuery = mapQuery.where(organizationColumn, "in", ctx.organizationIds);
  }
  const rows = await mapQuery.execute();
  return rows.map((row) => {
    const value = row[config.targetColumn];
    return typeof value === "string" ? value : null;
  }).filter((id) => typeof id === "string" && id.length > 0);
}
async function findMatchingEntityIdsBySearchTokensAcrossSources({
  ctx,
  sources,
  query
}) {
  const trimmed = query.trim();
  if (!trimmed) return null;
  const enrichedSources = await enrichSearchSourcesWithCustomFieldTokens(ctx, sources);
  const perSource = await Promise.all(
    enrichedSources.map(async (source) => {
      const rawIds = await findSearchTokenEntityIds({
        ctx,
        entityType: source.entityType,
        fields: source.fields,
        query: trimmed
      });
      if (rawIds === null) return null;
      return source.mapToEntityIds ? await mapScopedEntityIds({ ctx, ids: rawIds, config: source.mapToEntityIds }) : rawIds;
    })
  );
  const matchedIds = /* @__PURE__ */ new Set();
  for (const entityIds of perSource) {
    if (entityIds === null) return null;
    entityIds.forEach((id) => matchedIds.add(id));
  }
  return Array.from(matchedIds);
}
async function findMatchingEntityIdsBySearchTokens({
  ctx,
  entityType,
  fields,
  query
}) {
  return findMatchingEntityIdsBySearchTokensAcrossSources({
    ctx,
    query,
    sources: [{ entityType, fields }]
  });
}
function applyEntityIdRestriction(filters, ids) {
  if (ids === null) return;
  const currentIdFilter = filters.id && typeof filters.id === "object" && !Array.isArray(filters.id) ? filters.id : null;
  const currentEq = typeof currentIdFilter?.$eq === "string" ? currentIdFilter.$eq : null;
  if (currentEq) {
    filters.id = ids.includes(currentEq) ? { $eq: currentEq } : { $eq: NO_MATCH_ID };
    return;
  }
  filters.id = ids.length > 0 ? { $in: ids } : { $eq: NO_MATCH_ID };
}
function applyEntityIdExclusion(filters, ids) {
  const uniqueIds = Array.from(new Set(ids.filter((id) => typeof id === "string" && id.length > 0)));
  if (!uniqueIds.length) return;
  const currentIdFilter = filters.id && typeof filters.id === "object" && !Array.isArray(filters.id) ? filters.id : null;
  const currentEq = typeof currentIdFilter?.$eq === "string" ? currentIdFilter.$eq : null;
  const currentIn = Array.isArray(currentIdFilter?.$in) ? currentIdFilter.$in.filter((value) => typeof value === "string" && value.length > 0) : null;
  const currentNotIn = Array.isArray(currentIdFilter?.$nin) ? currentIdFilter.$nin.filter((value) => typeof value === "string" && value.length > 0) : [];
  if (currentEq) {
    filters.id = uniqueIds.includes(currentEq) ? { $eq: NO_MATCH_ID } : { $eq: currentEq };
    return;
  }
  if (currentIn) {
    const nextIds = currentIn.filter((id) => !uniqueIds.includes(id));
    filters.id = nextIds.length > 0 ? { $in: nextIds } : { $eq: NO_MATCH_ID };
    return;
  }
  filters.id = {
    ...currentIdFilter ?? {},
    $nin: Array.from(/* @__PURE__ */ new Set([...currentNotIn, ...uniqueIds]))
  };
}
async function findMatchingEntityIdsWithQueryEngine({
  ctx,
  entityId,
  filters,
  customFieldSources,
  joins
}) {
  const qe = ctx.container.resolve("queryEngine");
  const ids = /* @__PURE__ */ new Set();
  const pageSize = 100;
  let page = 1;
  let total = 0;
  do {
    const result = await qe.query(entityId, {
      fields: ["id"],
      filters,
      page: { page, pageSize },
      sort: [{ field: "id", dir: SortDir.Asc }],
      tenantId: ctx.auth?.tenantId ?? void 0,
      organizationId: ctx.selectedOrganizationId ?? void 0,
      organizationIds: ctx.organizationIds ?? void 0,
      customFieldSources,
      joins
    });
    total = result.total ?? 0;
    for (const item of result.items ?? []) {
      const id = item && typeof item === "object" ? item.id : null;
      if (typeof id === "string" && id.length > 0) {
        ids.add(id);
      }
    }
    if (!result.items?.length) break;
    page += 1;
  } while (ids.size < total);
  return Array.from(ids);
}
export {
  applyEntityIdExclusion,
  applyEntityIdRestriction,
  findMatchingEntityIdsBySearchTokens,
  findMatchingEntityIdsBySearchTokensAcrossSources,
  findMatchingEntityIdsWithQueryEngine,
  parseScopedCommandInput,
  withScopedPayload
};
//# sourceMappingURL=utils.js.map
