import { sql } from "kysely";
import { resolveEntityTableName } from "@open-mercato/shared/lib/query/engine";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("query_index").child({ component: "coverage" });
const COLUMN_CACHE = /* @__PURE__ */ new Map();
const COLUMN_CACHE_PENDING = /* @__PURE__ */ new Map();
const GLOBAL_ORGANIZATION_PLACEHOLDER = "00000000-0000-0000-0000-000000000000";
const COVERAGE_ORG_PLACEHOLDER = GLOBAL_ORGANIZATION_PLACEHOLDER;
function toCount(value) {
  if (typeof value === "number") return Number.isFinite(value) ? value : 0;
  if (typeof value === "string") {
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : 0;
  }
  if (value != null && typeof value.valueOf === "function") {
    const parsed = Number(value.valueOf());
    return Number.isFinite(parsed) ? parsed : 0;
  }
  return 0;
}
function normalizeOrganizationForStore(orgId) {
  return orgId ?? GLOBAL_ORGANIZATION_PLACEHOLDER;
}
function applyOrganizationCondition(qb, column, organizationId) {
  const stored = normalizeOrganizationForStore(organizationId ?? null);
  if (stored === GLOBAL_ORGANIZATION_PLACEHOLDER) {
    return qb.where((eb) => eb.or([
      eb(column, "is", null),
      eb(column, "=", GLOBAL_ORGANIZATION_PLACEHOLDER)
    ]));
  }
  return qb.where(column, "=", stored);
}
async function fetchCoverageRow(db, scope) {
  const { entityType, tenantId, organizationId, withDeleted } = scope;
  let query = db.selectFrom("entity_index_coverage").select([
    "base_count",
    "indexed_count",
    "vector_indexed_count",
    "refreshed_at",
    "organization_id"
  ]).where("entity_type", "=", entityType).where("with_deleted", "=", withDeleted === true).orderBy("refreshed_at", "desc");
  query = tenantId == null ? query.where("tenant_id", "is", null) : query.where("tenant_id", "=", tenantId);
  query = applyOrganizationCondition(query, "organization_id", organizationId ?? null);
  const row = await query.executeTakeFirst();
  return row ?? null;
}
async function pruneDuplicateCoverageRows(db, scope, keepId) {
  let query = db.deleteFrom("entity_index_coverage").where("entity_type", "=", scope.entityType).where("with_deleted", "=", scope.withDeleted === true);
  query = scope.tenantId == null ? query.where("tenant_id", "is", null) : query.where("tenant_id", "=", scope.tenantId);
  query = applyOrganizationCondition(query, "organization_id", scope.organizationId ?? null);
  if (keepId) {
    query = query.where("id", "!=", keepId);
  }
  await query.execute();
}
async function upsertCoverageRow(db, scope, counts) {
  const storedOrgId = normalizeOrganizationForStore(scope.organizationId ?? null);
  if (scope.organizationId == null) {
    let purge = db.deleteFrom("entity_index_coverage").where("entity_type", "=", scope.entityType).where("with_deleted", "=", scope.withDeleted === true).where("organization_id", "is", null);
    purge = scope.tenantId == null ? purge.where("tenant_id", "is", null) : purge.where("tenant_id", "=", scope.tenantId);
    await purge.execute();
  }
  const rows = await db.insertInto("entity_index_coverage").values({
    entity_type: scope.entityType,
    tenant_id: scope.tenantId ?? null,
    organization_id: storedOrgId,
    with_deleted: scope.withDeleted === true,
    base_count: counts.baseCount,
    indexed_count: counts.indexedCount,
    vector_indexed_count: counts.vectorIndexedCount,
    refreshed_at: sql`now()`
  }).onConflict((oc) => oc.columns(["entity_type", "tenant_id", "organization_id", "with_deleted"]).doUpdateSet({
    base_count: counts.baseCount,
    indexed_count: counts.indexedCount,
    vector_indexed_count: counts.vectorIndexedCount,
    refreshed_at: sql`now()`
  })).returning(["id"]).execute();
  const keepId = rows?.[0]?.id ?? null;
  await pruneDuplicateCoverageRows(db, scope, keepId);
}
async function readCoverageSnapshot(db, scope) {
  const entityType = String(scope.entityType || "");
  if (!entityType) return null;
  const row = await fetchCoverageRow(db, {
    entityType,
    tenantId: scope.tenantId ?? null,
    organizationId: scope.organizationId ?? null,
    withDeleted: scope.withDeleted === true
  });
  if (!row) return null;
  const refreshedAt = row.refreshed_at instanceof Date ? row.refreshed_at : row.refreshed_at ? new Date(row.refreshed_at) : null;
  return {
    base_count: row.base_count,
    indexed_count: row.indexed_count,
    vector_indexed_count: row.vector_indexed_count,
    refreshed_at: refreshedAt ?? null,
    baseCount: toCount(row.base_count),
    indexedCount: toCount(row.indexed_count),
    vectorIndexedCount: toCount(row.vector_indexed_count)
  };
}
async function readCoverageSnapshots(db, batch) {
  const entityTypes = Array.from(
    new Set((batch.entityTypes ?? []).map((id) => String(id || "")).filter((id) => id.length > 0))
  );
  const result = /* @__PURE__ */ new Map();
  if (entityTypes.length === 0) return result;
  const withDeleted = batch.withDeleted === true;
  let query = db.selectFrom("entity_index_coverage").select([
    "entity_type",
    "base_count",
    "indexed_count",
    "vector_indexed_count",
    "refreshed_at",
    "organization_id"
  ]).where("entity_type", "in", entityTypes).where("with_deleted", "=", withDeleted).orderBy("refreshed_at", "desc");
  query = batch.tenantId == null ? query.where("tenant_id", "is", null) : query.where("tenant_id", "=", batch.tenantId);
  query = applyOrganizationCondition(query, "organization_id", batch.organizationId ?? null);
  const rows = await query.execute();
  for (const row of rows ?? []) {
    const entityType = String(row.entity_type || "");
    if (!entityType || result.has(entityType)) continue;
    const refreshedAt = row.refreshed_at instanceof Date ? row.refreshed_at : row.refreshed_at ? new Date(row.refreshed_at) : null;
    result.set(entityType, {
      base_count: row.base_count,
      indexed_count: row.indexed_count,
      vector_indexed_count: row.vector_indexed_count,
      refreshed_at: refreshedAt ?? null,
      baseCount: toCount(row.base_count),
      indexedCount: toCount(row.indexed_count),
      vectorIndexedCount: toCount(row.vector_indexed_count)
    });
  }
  return result;
}
async function applyCoverageAdjustments(em, adjustments) {
  if (!adjustments.length) return;
  const db = em.getKysely();
  const aggregated = aggregateAdjustments(adjustments);
  for (const entry of aggregated) {
    const scope = entry.scope;
    const existing = await fetchCoverageRow(db, scope);
    const currentBase = existing ? toCount(existing.base_count) : 0;
    const currentIndex = existing ? toCount(existing.indexed_count) : 0;
    const currentVector = existing ? toCount(existing.vector_indexed_count) : 0;
    const nextBase = Math.max(currentBase + entry.deltaBase, 0);
    const nextIndex = Math.max(currentIndex + entry.deltaIndex, 0);
    const nextVector = Math.max(currentVector + entry.deltaVector, 0);
    await upsertCoverageRow(db, scope, {
      baseCount: nextBase,
      indexedCount: nextIndex,
      vectorIndexedCount: nextVector
    });
  }
}
async function deleteCoverageForEntity(db, entityType) {
  if (!entityType) return;
  await db.deleteFrom("entity_index_coverage").where("entity_type", "=", entityType).execute();
}
async function tableHasColumn(db, table, column) {
  const key = `${table}.${column}`;
  if (COLUMN_CACHE.has(key)) return COLUMN_CACHE.get(key);
  const pending = COLUMN_CACHE_PENDING.get(key);
  if (pending) return pending;
  const promise = (async () => {
    const exists = await db.selectFrom("information_schema.columns").select(sql`1`.as("present")).where(sql`table_schema = current_schema()`).where("table_name", "=", table).where("column_name", "=", column).executeTakeFirst();
    const present = !!exists;
    COLUMN_CACHE.set(key, present);
    return present;
  })();
  COLUMN_CACHE_PENDING.set(key, promise);
  try {
    return await promise;
  } finally {
    COLUMN_CACHE_PENDING.delete(key);
  }
}
async function primeColumnCache(db, checks) {
  const missing = [];
  const seen = /* @__PURE__ */ new Set();
  for (const check of checks) {
    const table = String(check?.table || "");
    const column = String(check?.column || "");
    if (!table || !column) continue;
    const key = `${table}.${column}`;
    if (seen.has(key)) continue;
    seen.add(key);
    if (COLUMN_CACHE.has(key) || COLUMN_CACHE_PENDING.has(key)) continue;
    missing.push({ table, column, key });
  }
  if (!missing.length) return;
  const tables = Array.from(new Set(missing.map((entry) => entry.table)));
  const columns = Array.from(new Set(missing.map((entry) => entry.column)));
  const batchPromise = (async () => {
    const rows = await db.selectFrom("information_schema.columns").select(["table_name", "column_name"]).where(sql`table_schema = current_schema()`).where("table_name", "in", tables).where("column_name", "in", columns).execute();
    return new Set(rows.map((row) => `${row.table_name}.${row.column_name}`));
  })();
  for (const entry of missing) {
    const entryPromise = batchPromise.then((present) => {
      const value = present.has(entry.key);
      COLUMN_CACHE.set(entry.key, value);
      return value;
    });
    entryPromise.catch(() => void 0);
    COLUMN_CACHE_PENDING.set(entry.key, entryPromise);
  }
  try {
    await batchPromise;
  } finally {
    for (const entry of missing) COLUMN_CACHE_PENDING.delete(entry.key);
  }
}
async function refreshCoverageSnapshot(em, scope) {
  const entityType = String(scope.entityType || "");
  if (!entityType) return;
  const tenantId = scope.tenantId ?? null;
  const organizationId = scope.organizationId ?? null;
  const withDeleted = scope.withDeleted === true;
  const db = em.getKysely();
  const baseTable = resolveEntityTableName(em, entityType);
  const hasOrg = await tableHasColumn(db, baseTable, "organization_id");
  const hasTenant = await tableHasColumn(db, baseTable, "tenant_id");
  const hasDeleted = await tableHasColumn(db, baseTable, "deleted_at");
  if (organizationId !== null && !hasOrg) return;
  if (tenantId !== null && !hasTenant) return;
  let baseQuery = db.selectFrom(`${baseTable} as b`).select(sql`count(*)`.as("count"));
  if (organizationId !== null && hasOrg) baseQuery = baseQuery.where("b.organization_id", "=", organizationId);
  if (tenantId !== null && hasTenant) baseQuery = baseQuery.where("b.tenant_id", "=", tenantId);
  if (!withDeleted && hasDeleted) baseQuery = baseQuery.where("b.deleted_at", "is", null);
  let indexQuery = db.selectFrom("entity_indexes as ei").select(sql`count(*)`.as("count")).where("ei.entity_type", "=", entityType);
  if (organizationId !== null) indexQuery = indexQuery.where("ei.organization_id", "=", organizationId);
  if (tenantId !== null) indexQuery = indexQuery.where("ei.tenant_id", "=", tenantId);
  if (!withDeleted) indexQuery = indexQuery.where("ei.deleted_at", "is", null);
  const vectorCountPromise = (async () => {
    const hasVectorTable = await tableHasColumn(db, "vector_search", "entity_id");
    if (!hasVectorTable || typeof tenantId !== "string" || tenantId.length === 0) return void 0;
    try {
      let vectorQuery = db.selectFrom("vector_search").select(sql`count(*)`.as("count")).where("entity_id", "=", entityType).where("tenant_id", "=", tenantId);
      if (organizationId !== null) {
        vectorQuery = vectorQuery.where("organization_id", "=", organizationId);
      }
      const vectorRow = await vectorQuery.executeTakeFirst();
      return toCount(vectorRow?.count);
    } catch (err) {
      logger.warn("Failed to resolve vector count for coverage snapshot", {
        entityType,
        tenantId,
        organizationId,
        error: err instanceof Error ? err.message : err
      });
      return void 0;
    }
  })();
  const [baseRow, indexRow, vectorCount] = await Promise.all([
    baseQuery.executeTakeFirst(),
    indexQuery.executeTakeFirst(),
    vectorCountPromise
  ]);
  const baseCount = toCount(baseRow?.count);
  const indexCount = toCount(indexRow?.count);
  await writeCoverageCounts(em, { entityType, tenantId, organizationId, withDeleted }, {
    baseCount,
    indexedCount: indexCount,
    vectorCount
  });
}
async function writeCoverageCounts(em, scope, counts) {
  const entityType = String(scope.entityType || "");
  if (!entityType) return;
  const db = em.getKysely();
  const tenantId = scope.tenantId ?? null;
  const organizationId = scope.organizationId ?? null;
  const withDeleted = scope.withDeleted === true;
  const existing = await fetchCoverageRow(db, {
    entityType,
    tenantId,
    organizationId,
    withDeleted
  });
  const baseCount = counts.baseCount !== void 0 ? Math.max(0, Math.trunc(toCount(counts.baseCount))) : Math.max(0, Math.trunc(toCount(existing?.base_count)));
  const indexCount = counts.indexedCount !== void 0 ? Math.max(0, Math.trunc(toCount(counts.indexedCount))) : Math.max(0, Math.trunc(toCount(existing?.indexed_count)));
  const vectorCount = counts.vectorCount !== void 0 ? Math.max(0, Math.trunc(toCount(counts.vectorCount))) : Math.max(0, Math.trunc(toCount(existing?.vector_indexed_count)));
  await upsertCoverageRow(db, { entityType, tenantId, organizationId, withDeleted }, {
    baseCount,
    indexedCount: indexCount,
    vectorIndexedCount: vectorCount
  });
}
function aggregateAdjustments(adjustments) {
  const map = /* @__PURE__ */ new Map();
  for (const adj of adjustments) {
    if (!adj?.entityType) continue;
    const deltaBase = Number.isFinite(adj.deltaBase) ? adj.deltaBase : 0;
    const deltaIndex = Number.isFinite(adj.deltaIndex) ? adj.deltaIndex : 0;
    const deltaVector = Number.isFinite(adj.deltaVector) ? adj.deltaVector : 0;
    if (deltaBase === 0 && deltaIndex === 0 && deltaVector === 0) continue;
    const scope = {
      entityType: adj.entityType,
      tenantId: adj.tenantId ?? null,
      organizationId: adj.organizationId ?? null,
      withDeleted: adj.withDeleted === true
    };
    const key = scopeKey(scope);
    const existing = map.get(key);
    if (existing) {
      existing.deltaBase += deltaBase;
      existing.deltaIndex += deltaIndex;
      existing.deltaVector += deltaVector;
    } else {
      map.set(key, { scope, deltaBase, deltaIndex, deltaVector });
    }
  }
  return Array.from(map.values());
}
function scopeKey(scope) {
  const tenant = scope.tenantId ?? "__tenant_null__";
  const org = normalizeOrganizationForStore(scope.organizationId ?? null);
  const deleted = scope.withDeleted === true ? "1" : "0";
  return `${scope.entityType}|${tenant}|${org}|${deleted}`;
}
function createCoverageAdjustments(input) {
  const entityType = String(input.entityType || "");
  if (!entityType) return [];
  const baseDelta = Number.isFinite(input.baseDelta) ? input.baseDelta : 0;
  const indexDelta = Number.isFinite(input.indexDelta) ? input.indexDelta : 0;
  const vectorDelta = Number.isFinite(input.vectorDelta) ? input.vectorDelta : 0;
  if (baseDelta === 0 && indexDelta === 0 && vectorDelta === 0) return [];
  const withDeleted = input.withDeleted === true;
  const tenantId = input.tenantId ?? null;
  const organizationId = input.organizationId ?? null;
  return [
    {
      entityType,
      tenantId,
      organizationId,
      withDeleted,
      deltaBase: baseDelta,
      deltaIndex: indexDelta,
      deltaVector: vectorDelta
    }
  ];
}
export {
  COVERAGE_ORG_PLACEHOLDER,
  applyCoverageAdjustments,
  createCoverageAdjustments,
  deleteCoverageForEntity,
  primeColumnCache,
  readCoverageSnapshot,
  readCoverageSnapshots,
  refreshCoverageSnapshot,
  writeCoverageCounts
};
//# sourceMappingURL=coverage.js.map
