import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
import { sql } from "kysely";
import { createProgressBar } from "@open-mercato/shared/lib/cli/progress";
import { resolveTenantEncryptionService } from "@open-mercato/shared/lib/encryption/customFieldValues";
import { decryptIndexDocForSearch, encryptIndexDocForStorage } from "@open-mercato/shared/lib/encryption/indexDoc";
import { parseBooleanToken } from "@open-mercato/shared/lib/boolean";
import { resolveEntityTableName } from "@open-mercato/shared/lib/query/engine";
import { recordIndexerError } from "@open-mercato/shared/lib/indexers/error-log";
import { recordIndexerLog } from "@open-mercato/shared/lib/indexers/status-log";
import { upsertIndexBatch } from "./lib/batch.js";
import { reindexEntity, DEFAULT_REINDEX_PARTITIONS } from "./lib/reindexer.js";
import { purgeIndexScope } from "./lib/purge.js";
import { flattenSystemEntityIds } from "@open-mercato/shared/lib/entities/system-entities";
function isIndexerVerbose() {
  const parsed = parseBooleanToken(process.env.OM_INDEXER_VERBOSE ?? "");
  return parsed === true;
}
function createGroupedProgress(label, partitionTargets) {
  const totals = /* @__PURE__ */ new Map();
  const processed = /* @__PURE__ */ new Map();
  let bar = null;
  const getTotals = () => {
    let total = 0;
    let done = 0;
    for (const value of totals.values()) total += value;
    for (const value of processed.values()) done += value;
    return { total, done };
  };
  const tryInitBar = () => {
    if (bar) return;
    if (totals.size < partitionTargets.length) return;
    const { total } = getTotals();
    if (total <= 0) return;
    bar = createProgressBar(label, total);
  };
  return {
    onProgress(partition, info) {
      processed.set(partition, info.processed);
      if (!totals.has(partition)) totals.set(partition, info.total);
      tryInitBar();
      if (!bar) return;
      const { done } = getTotals();
      bar.update(done);
    },
    complete() {
      if (bar) bar.complete();
    },
    getTotals
  };
}
function parseArgs(rest) {
  const args = {};
  for (let i = 0; i < rest.length; i += 1) {
    const part = rest[i];
    if (!part?.startsWith("--")) continue;
    const [rawKey, rawValue] = part.slice(2).split("=");
    if (!rawKey) continue;
    if (rawValue !== void 0) {
      args[rawKey] = rawValue;
    } else if (i + 1 < rest.length && !rest[i + 1].startsWith("--")) {
      args[rawKey] = rest[i + 1];
      i += 1;
    } else {
      args[rawKey] = true;
    }
  }
  return args;
}
function stringOption(args, ...keys) {
  for (const key of keys) {
    const raw = args[key];
    if (typeof raw !== "string") continue;
    const trimmed = raw.trim();
    if (trimmed.length > 0) return trimmed;
  }
  return void 0;
}
function numberOption(args, ...keys) {
  for (const key of keys) {
    const raw = args[key];
    if (typeof raw === "number") return raw;
    if (typeof raw === "string") {
      const parsed = Number(raw);
      if (Number.isFinite(parsed)) return parsed;
    }
  }
  return void 0;
}
function flagEnabled(args, ...keys) {
  for (const key of keys) {
    const raw = args[key];
    if (raw === void 0) continue;
    if (raw === true) return true;
    if (raw === false) continue;
    if (typeof raw === "string") {
      const trimmed = raw.trim();
      if (!trimmed) return true;
      const parsed = parseBooleanToken(trimmed);
      return parsed === null ? true : parsed;
    }
  }
  return false;
}
function toPositiveInt(value) {
  if (value === void 0) return void 0;
  const n = Math.floor(value);
  if (!Number.isFinite(n) || n <= 0) return void 0;
  return n;
}
function toNonNegativeInt(value, fallback = 0) {
  if (value === void 0) return fallback;
  const n = Math.floor(value);
  if (!Number.isFinite(n) || n < 0) return fallback;
  return n;
}
const DEFAULT_BATCH_SIZE = 200;
async function rebuildEntityIndexes(options) {
  const {
    em,
    db,
    entityType,
    tableName,
    orgOverride,
    tenantOverride,
    global,
    includeDeleted,
    limit,
    offset,
    recordId,
    batchSize,
    progressLabel,
    supportsOrgFilter,
    supportsTenantFilter,
    supportsDeletedFilter
  } = options;
  const encryption = resolveTenantEncryptionService(em);
  const dekKeyCache = /* @__PURE__ */ new Map();
  const encryptDoc = async (targetEntity, doc, scope) => {
    try {
      return await encryptIndexDocForStorage(
        targetEntity,
        doc,
        { tenantId: scope.tenantId ?? null, organizationId: scope.organizationId ?? null },
        encryption
      );
    } catch {
      return doc;
    }
  };
  const decryptDoc = async (targetEntity, doc, scope) => {
    try {
      return await decryptIndexDocForSearch(
        targetEntity,
        doc,
        { tenantId: scope.tenantId ?? null, organizationId: scope.organizationId ?? null },
        encryption,
        dekKeyCache
      );
    } catch {
      return doc;
    }
  };
  const applyFilters = (q) => {
    let chain = q;
    if (!global) {
      if (orgOverride !== void 0 && supportsOrgFilter) {
        chain = chain.where("organization_id", "=", orgOverride);
      }
      if (tenantOverride !== void 0 && supportsTenantFilter) {
        chain = chain.where("tenant_id", "=", tenantOverride);
      }
    }
    if (!includeDeleted && supportsDeletedFilter) {
      chain = chain.where("deleted_at", "is", null);
    }
    return chain;
  };
  if (recordId) {
    const row = await applyFilters(
      db.selectFrom(tableName).selectAll()
    ).where("id", "=", recordId).executeTakeFirst();
    if (!row) return { processed: 0, matched: 0 };
    const bar2 = createProgressBar(progressLabel ?? `Rebuilding ${entityType}`, 1);
    await upsertIndexBatch(db, entityType, [row], { orgId: orgOverride, tenantId: tenantOverride }, { encryptDoc, decryptDoc });
    bar2.update(1);
    bar2.complete();
    return { processed: 1, matched: 1 };
  }
  const countRow = await applyFilters(
    db.selectFrom(tableName).select(sql`count(*)`.as("count"))
  ).executeTakeFirst();
  const totalRaw = countRow?.count;
  const total = totalRaw != null ? Number(totalRaw) : 0;
  const effectiveOffset = Math.max(0, offset);
  const matchedWithoutLimit = Math.max(0, total - effectiveOffset);
  const limitValue = toPositiveInt(limit);
  const intended = limitValue !== void 0 ? Math.min(matchedWithoutLimit, limitValue) : matchedWithoutLimit;
  if (!Number.isFinite(intended) || intended <= 0) {
    return { processed: 0, matched: 0 };
  }
  const bar = createProgressBar(progressLabel ?? `Rebuilding ${entityType}`, intended);
  let processed = 0;
  let cursorOffset = effectiveOffset;
  let remaining = limitValue;
  while (processed < intended) {
    const chunkLimit = remaining !== void 0 ? Math.min(batchSize, remaining) : batchSize;
    const chunk = await applyFilters(
      db.selectFrom(tableName).selectAll().orderBy("id").limit(chunkLimit).offset(cursorOffset)
    ).execute();
    if (!chunk.length) break;
    await upsertIndexBatch(db, entityType, chunk, {
      orgId: orgOverride,
      tenantId: tenantOverride
    }, { encryptDoc, decryptDoc });
    processed += chunk.length;
    cursorOffset += chunk.length;
    if (remaining !== void 0) remaining -= chunk.length;
    bar.update(processed);
    if (remaining !== void 0 && remaining <= 0) break;
  }
  if (processed < intended) {
    bar.update(processed);
  }
  bar.complete();
  return { processed, matched: intended };
}
async function getColumnSet(db, tableName) {
  try {
    const rows = await db.selectFrom("information_schema.columns").select(["column_name"]).where(sql`table_schema = current_schema()`).where("table_name", "=", tableName).execute();
    return new Set(rows.map((row) => String(row.column_name).toLowerCase()));
  } catch {
    return /* @__PURE__ */ new Set();
  }
}
function describeScope(scope) {
  const parts = [];
  if (scope.global) parts.push("global");
  if (!scope.global && scope.orgId && scope.supportsOrg) parts.push(`org=${scope.orgId}`);
  if (!scope.global && scope.tenantId && scope.supportsTenant) parts.push(`tenant=${scope.tenantId}`);
  if (!scope.includeDeleted && scope.supportsDeleted) parts.push("active-only");
  return parts.length ? ` (${parts.join(" ")})` : "";
}
const rebuild = {
  command: "rebuild",
  async run(rest) {
    const args = parseArgs(rest);
    const entity = stringOption(args, "entity", "e");
    if (!entity) {
      console.error(
        "Usage: mercato query_index rebuild --entity <module:entity> [--record <id>] [--org <id>] [--tenant <id>] [--global] [--withDeleted] [--limit <n>] [--offset <n>]"
      );
      return;
    }
    const globalFlag = flagEnabled(args, "global");
    const includeDeleted = flagEnabled(args, "withDeleted");
    const orgId = stringOption(args, "org", "organizationId");
    const tenantId = stringOption(args, "tenant", "tenantId");
    const recordId = stringOption(args, "record", "recordId", "id");
    const limit = toPositiveInt(numberOption(args, "limit"));
    const offset = toNonNegativeInt(numberOption(args, "offset"));
    const batchSize = toPositiveInt(numberOption(args, "batch", "chunk", "size")) ?? DEFAULT_BATCH_SIZE;
    const container = await createRequestContainer();
    const em = container.resolve("em");
    try {
      const db = em.getKysely();
      const tableName = resolveEntityTableName(em, entity);
      const columns = await getColumnSet(db, tableName);
      const supportsOrg = columns.has("organization_id");
      const supportsTenant = columns.has("tenant_id");
      const supportsDeleted = columns.has("deleted_at");
      if (!globalFlag && orgId && !supportsOrg) {
        console.warn(`[query_index] ${entity} does not expose organization_id, ignoring --org filter`);
      }
      if (!globalFlag && tenantId && !supportsTenant) {
        console.warn(`[query_index] ${entity} does not expose tenant_id, ignoring --tenant filter`);
      }
      if (!includeDeleted && !supportsDeleted) {
        console.warn(`[query_index] ${entity} does not expose deleted_at, cannot skip deleted rows`);
      }
      const result = await rebuildEntityIndexes({
        em,
        db,
        entityType: entity,
        tableName,
        orgOverride: orgId,
        tenantOverride: tenantId,
        global: globalFlag,
        includeDeleted,
        limit,
        offset,
        recordId,
        batchSize,
        progressLabel: recordId ? `Rebuilding ${entity} record ${recordId}` : `Rebuilding ${entity}`,
        supportsOrgFilter: supportsOrg,
        supportsTenantFilter: supportsTenant,
        supportsDeletedFilter: supportsDeleted
      });
      if (recordId) {
        if (result.processed === 0) {
          console.log(`No matching row found for ${entity} with id ${recordId}`);
        } else {
          console.log(`Rebuilt index for ${entity} record ${recordId}`);
        }
        return;
      }
      const scopeLabel = describeScope({
        global: globalFlag,
        orgId,
        tenantId,
        includeDeleted,
        supportsOrg,
        supportsTenant,
        supportsDeleted
      });
      if (result.matched === 0) {
        console.log(`No rows matched filters for ${entity}${scopeLabel}`);
        return;
      }
      console.log(`Rebuilt ${result.processed} row(s) for ${entity}${scopeLabel}`);
    } catch (error) {
      await recordIndexerError(
        { em },
        {
          source: "query_index",
          handler: "cli:query_index.rebuild",
          error,
          entityType: entity,
          recordId,
          tenantId,
          organizationId: orgId,
          payload: { args }
        }
      );
      throw error;
    } finally {
      if (typeof container?.dispose === "function") {
        await container.dispose();
      }
    }
  }
};
const rebuildAll = {
  command: "rebuild-all",
  async run(rest) {
    const args = parseArgs(rest);
    const globalFlag = flagEnabled(args, "global");
    const includeDeleted = flagEnabled(args, "withDeleted");
    const orgId = stringOption(args, "org", "organizationId");
    const tenantId = stringOption(args, "tenant", "tenantId");
    const limit = toPositiveInt(numberOption(args, "limit"));
    const offset = toNonNegativeInt(numberOption(args, "offset"));
    const batchSize = toPositiveInt(numberOption(args, "batch", "chunk", "size")) ?? DEFAULT_BATCH_SIZE;
    const recordId = stringOption(args, "record", "recordId", "id");
    if (recordId) {
      console.error("`rebuild-all` does not support --record. Use `mercato query_index rebuild --record <id>` instead.");
      return;
    }
    const container = await createRequestContainer();
    const em = container.resolve("em");
    try {
      const db = em.getKysely();
      const { getEntityIds } = await import("@open-mercato/shared/lib/encryption/entityIds");
      const entityIds = flattenSystemEntityIds(getEntityIds());
      if (!entityIds.length) {
        console.log("No entity definitions registered for query indexing.");
        return;
      }
      let totalProcessed = 0;
      for (let idx = 0; idx < entityIds.length; idx += 1) {
        const entity = entityIds[idx];
        const tableName = resolveEntityTableName(em, entity);
        const columns = await getColumnSet(db, tableName);
        const supportsOrg = columns.has("organization_id");
        const supportsTenant = columns.has("tenant_id");
        const supportsDeleted = columns.has("deleted_at");
        if (!globalFlag && orgId && !supportsOrg) {
          console.warn(`[query_index] ${entity} does not expose organization_id, ignoring --org filter`);
        }
        if (!globalFlag && tenantId && !supportsTenant) {
          console.warn(`[query_index] ${entity} does not expose tenant_id, ignoring --tenant filter`);
        }
        if (!includeDeleted && !supportsDeleted) {
          console.warn(`[query_index] ${entity} does not expose deleted_at, cannot skip deleted rows`);
        }
        const scopeLabel = describeScope({
          global: globalFlag,
          orgId,
          tenantId,
          includeDeleted,
          supportsOrg,
          supportsTenant,
          supportsDeleted
        });
        console.log(`[${idx + 1}/${entityIds.length}] Rebuilding ${entity}${scopeLabel}`);
        const result = await rebuildEntityIndexes({
          em,
          db,
          entityType: entity,
          tableName,
          orgOverride: orgId,
          tenantOverride: tenantId,
          global: globalFlag,
          includeDeleted,
          limit,
          offset,
          batchSize,
          supportsOrgFilter: supportsOrg,
          supportsTenantFilter: supportsTenant,
          supportsDeletedFilter: supportsDeleted
        });
        totalProcessed += result.processed;
        if (result.matched === 0) {
          console.log("  -> no rows matched filters");
        } else {
          console.log(`  -> processed ${result.processed} row(s)`);
        }
      }
      console.log(`Finished rebuilding all query indexes (processed ${totalProcessed} row(s))`);
    } catch (error) {
      await recordIndexerError(
        { em },
        {
          source: "query_index",
          handler: "cli:query_index.rebuild-all",
          error,
          tenantId,
          organizationId: orgId,
          payload: { args }
        }
      );
      throw error;
    } finally {
      if (typeof container?.dispose === "function") {
        await container.dispose();
      }
    }
  }
};
const reindex = {
  command: "reindex",
  async run(rest) {
    const args = parseArgs(rest);
    const entity = stringOption(args, "entity", "e");
    const orgId = stringOption(args, "org", "organizationId");
    const tenantId = stringOption(args, "tenant", "tenantId");
    const force = flagEnabled(args, "force", "full");
    const batchSize = toPositiveInt(numberOption(args, "batch", "chunk", "size"));
    const partitionsOption = toPositiveInt(numberOption(args, "partitions", "partitionCount", "parallel"));
    const partitionIndexOptionRaw = numberOption(args, "partition", "partitionIndex");
    const resetCoverageFlag = flagEnabled(args, "resetCoverage");
    const skipResetCoverageFlag = flagEnabled(args, "skipResetCoverage", "noResetCoverage");
    const skipPurge = flagEnabled(args, "skipPurge", "noPurge");
    const container = await createRequestContainer();
    const baseEm = container.resolve("em");
    const partitionIndexOption = partitionIndexOptionRaw === void 0 ? void 0 : toNonNegativeInt(partitionIndexOptionRaw, 0);
    const partitionCount = Math.max(
      1,
      partitionsOption ?? DEFAULT_REINDEX_PARTITIONS
    );
    if (partitionIndexOption !== void 0 && partitionIndexOption >= partitionCount) {
      console.error(`partitionIndex (${partitionIndexOption}) must be < partitionCount (${partitionCount})`);
      if (typeof container?.dispose === "function") {
        await container.dispose();
      }
      return;
    }
    const partitionTargets = partitionIndexOption !== void 0 ? [partitionIndexOption] : Array.from({ length: partitionCount }, (_, idx) => idx);
    const shouldResetCoverage = (partition) => {
      if (resetCoverageFlag) return true;
      if (skipResetCoverageFlag) return false;
      if (partitionIndexOption !== void 0) return partitionIndexOption === 0;
      return partition === partitionTargets[0];
    };
    try {
      if (entity) {
        await recordIndexerLog(
          { em: baseEm },
          {
            source: "query_index",
            handler: "cli:query_index.reindex",
            message: `Reindex started for ${entity}`,
            entityType: entity,
            tenantId: tenantId ?? null,
            organizationId: orgId ?? null,
            details: {
              force,
              partitions: partitionTargets.length,
              partitionCount,
              partitionIndex: partitionIndexOption ?? null,
              skipPurge
            }
          }
        ).catch(() => void 0);
        if (!skipPurge) {
          console.log(`Purging existing index rows for ${entity}...`);
          await purgeIndexScope(baseEm, { entityType: entity, organizationId: orgId, tenantId });
        }
        console.log(`Reindexing ${entity}${force ? " (forced)" : ""} in ${partitionTargets.length} partition(s)...`);
        const verbose = isIndexerVerbose();
        const progressState = verbose ? /* @__PURE__ */ new Map() : null;
        const groupedProgress = !verbose && partitionTargets.length > 1 ? createGroupedProgress(`Reindexing ${entity}`, partitionTargets) : null;
        const renderProgress = (part, entityId, info) => {
          if (!progressState) return;
          const state = progressState.get(part) ?? { last: 0 };
          const now = Date.now();
          if (now - state.last < 1e3 && info.processed < info.total) return;
          state.last = now;
          progressState.set(part, state);
          const percent = info.total > 0 ? (info.processed / info.total * 100).toFixed(2) : "0.00";
          console.log(
            `     [${entityId}] partition ${part + 1}/${partitionCount}: ${info.processed.toLocaleString()} / ${info.total.toLocaleString()} (${percent}%)`
          );
        };
        const stats = await Promise.all(
          partitionTargets.map(async (part, idx) => {
            const label = partitionTargets.length > 1 ? ` [partition ${part + 1}/${partitionCount}]` : "";
            if (partitionTargets.length === 1) {
              console.log(`  -> processing${label}`);
            } else if (verbose && idx === 0) {
              console.log(`  -> processing partitions in parallel (count=${partitionTargets.length})`);
            }
            const partitionContainer = await createRequestContainer();
            const partitionEm = partitionContainer.resolve("em");
            let partitionVectorService = null;
            try {
              partitionVectorService = partitionContainer.resolve("vectorIndexService");
            } catch {
              partitionVectorService = null;
            }
            try {
              let progressBar = null;
              const useBar = partitionTargets.length === 1;
              const partitionStats = await reindexEntity(partitionEm, {
                entityType: entity,
                tenantId,
                organizationId: orgId,
                force,
                batchSize,
                emitVectorizeEvents: false,
                partitionCount,
                partitionIndex: part,
                resetCoverage: shouldResetCoverage(part),
                vectorService: partitionVectorService,
                onProgress(info) {
                  if (useBar) {
                    if (info.total > 0 && !progressBar) {
                      progressBar = createProgressBar(
                        `Reindexing ${entity}${label}`,
                        info.total
                      );
                    }
                    progressBar?.update(info.processed);
                  } else if (groupedProgress) {
                    groupedProgress.onProgress(part, info);
                  } else {
                    renderProgress(part, entity, info);
                  }
                }
              });
              if (progressBar) {
                progressBar.complete();
              }
              if (!useBar && groupedProgress) {
                groupedProgress.onProgress(part, { processed: partitionStats.processed, total: partitionStats.total });
              } else if (!useBar) {
                renderProgress(part, entity, { processed: partitionStats.processed, total: partitionStats.total });
              } else {
                console.log(
                  `     processed ${partitionStats.processed} row(s)${partitionStats.total ? ` (base ${partitionStats.total})` : ""}`
                );
              }
              return partitionStats.processed;
            } finally {
              if (typeof partitionContainer?.dispose === "function") {
                await partitionContainer.dispose();
              }
            }
          })
        );
        groupedProgress?.complete();
        const totalProcessed = stats.reduce((acc, value) => acc + value, 0);
        console.log(`Finished ${entity}: processed ${totalProcessed} row(s) across ${partitionTargets.length} partition(s)`);
        await recordIndexerLog(
          { em: baseEm },
          {
            source: "query_index",
            handler: "cli:query_index.reindex",
            message: `Reindex completed for ${entity}`,
            entityType: entity,
            tenantId: tenantId ?? null,
            organizationId: orgId ?? null,
            details: {
              processed: totalProcessed,
              partitions: partitionTargets.length,
              partitionCount,
              partitionIndex: partitionIndexOption ?? null
            }
          }
        ).catch(() => void 0);
        return;
      }
      const { getEntityIds } = await import("@open-mercato/shared/lib/encryption/entityIds");
      const entityIds = flattenSystemEntityIds(getEntityIds());
      if (!entityIds.length) {
        console.log("No entity definitions registered for query indexing.");
        return;
      }
      for (let idx = 0; idx < entityIds.length; idx += 1) {
        const id = entityIds[idx];
        await recordIndexerLog(
          { em: baseEm },
          {
            source: "query_index",
            handler: "cli:query_index.reindex",
            message: `Reindex started for ${id}`,
            entityType: id,
            tenantId: tenantId ?? null,
            organizationId: orgId ?? null,
            details: {
              force,
              partitions: partitionTargets.length,
              partitionCount,
              partitionIndex: partitionIndexOption ?? null,
              skipPurge
            }
          }
        ).catch(() => void 0);
        if (!skipPurge) {
          console.log(`[${idx + 1}/${entityIds.length}] Purging existing index rows for ${id}...`);
          await purgeIndexScope(baseEm, { entityType: id, organizationId: orgId, tenantId });
        }
        console.log(
          `[${idx + 1}/${entityIds.length}] Reindexing ${id}${force ? " (forced)" : ""} in ${partitionTargets.length} partition(s)...`
        );
        const verbose = isIndexerVerbose();
        const progressState = verbose ? /* @__PURE__ */ new Map() : null;
        const groupedProgress = !verbose && partitionTargets.length > 1 ? createGroupedProgress(`Reindexing ${id}`, partitionTargets) : null;
        const renderProgress = (part, entityId, info) => {
          if (!progressState) return;
          const state = progressState.get(part) ?? { last: 0 };
          const now = Date.now();
          if (now - state.last < 1e3 && info.processed < info.total) return;
          state.last = now;
          progressState.set(part, state);
          const percent = info.total > 0 ? (info.processed / info.total * 100).toFixed(2) : "0.00";
          console.log(
            `     [${entityId}] partition ${part + 1}/${partitionCount}: ${info.processed.toLocaleString()} / ${info.total.toLocaleString()} (${percent}%)`
          );
        };
        const partitionResults = await Promise.all(
          partitionTargets.map(async (part, partitionIdx) => {
            const label = partitionTargets.length > 1 ? ` [partition ${part + 1}/${partitionCount}]` : "";
            if (partitionTargets.length === 1) {
              console.log(`  -> processing${label}`);
            } else if (verbose && partitionIdx === 0) {
              console.log(`  -> processing partitions in parallel (count=${partitionTargets.length})`);
            }
            const partitionContainer = await createRequestContainer();
            const partitionEm = partitionContainer.resolve("em");
            let partitionVectorService = null;
            try {
              partitionVectorService = partitionContainer.resolve("vectorIndexService");
            } catch {
              partitionVectorService = null;
            }
            try {
              let progressBar = null;
              const useBar = partitionTargets.length === 1;
              const result = await reindexEntity(partitionEm, {
                entityType: id,
                tenantId,
                organizationId: orgId,
                force,
                batchSize,
                emitVectorizeEvents: false,
                partitionCount,
                partitionIndex: part,
                resetCoverage: shouldResetCoverage(part),
                vectorService: partitionVectorService,
                onProgress(info) {
                  if (useBar) {
                    if (info.total > 0 && !progressBar) {
                      progressBar = createProgressBar(`Reindexing ${id}${label}`, info.total);
                    }
                    progressBar?.update(info.processed);
                  } else if (groupedProgress) {
                    groupedProgress.onProgress(part, info);
                  } else {
                    renderProgress(part, id, info);
                  }
                }
              });
              if (progressBar) {
                progressBar.complete();
              }
              if (!useBar && groupedProgress) {
                groupedProgress.onProgress(part, { processed: result.processed, total: result.total });
              } else if (!useBar) {
                renderProgress(part, id, { processed: result.processed, total: result.total });
              } else {
                console.log(
                  `     processed ${result.processed} row(s)${result.total ? ` (base ${result.total})` : ""}`
                );
              }
              return result.processed;
            } finally {
              if (typeof partitionContainer?.dispose === "function") {
                await partitionContainer.dispose();
              }
            }
          })
        );
        groupedProgress?.complete();
        const totalProcessed = partitionResults.reduce((acc, value) => acc + value, 0);
        console.log(`  -> ${id} complete: processed ${totalProcessed} row(s) across ${partitionTargets.length} partition(s)`);
        await recordIndexerLog(
          { em: baseEm },
          {
            source: "query_index",
            handler: "cli:query_index.reindex",
            message: `Reindex completed for ${id}`,
            entityType: id,
            tenantId: tenantId ?? null,
            organizationId: orgId ?? null,
            details: {
              processed: totalProcessed,
              partitions: partitionTargets.length,
              partitionCount,
              partitionIndex: partitionIndexOption ?? null
            }
          }
        ).catch(() => void 0);
      }
      console.log(`Finished reindexing ${entityIds.length} entities`);
    } catch (error) {
      const targetLabel = entity ?? "multiple entities";
      await recordIndexerLog(
        { em: baseEm },
        {
          source: "query_index",
          handler: "cli:query_index.reindex",
          level: "warn",
          message: `Reindex failed for ${targetLabel}`,
          entityType: entity ?? null,
          tenantId: tenantId ?? null,
          organizationId: orgId ?? null,
          details: {
            error: error instanceof Error ? error.message : String(error)
          }
        }
      ).catch(() => void 0);
      await recordIndexerError(
        { em: baseEm },
        {
          source: "query_index",
          handler: "cli:query_index.reindex",
          error,
          entityType: entity ?? null,
          tenantId,
          organizationId: orgId ?? null,
          payload: {
            args,
            partitionTargets,
            partitionCount,
            partitionIndex: partitionIndexOption,
            force,
            skipPurge
          }
        }
      );
      throw error;
    } finally {
      if (typeof container?.dispose === "function") {
        await container.dispose();
      }
    }
  }
};
const purge = {
  command: "purge",
  async run(rest) {
    const args = parseArgs(rest);
    const entity = stringOption(args, "entity", "e");
    const orgId = stringOption(args, "org", "organizationId");
    const tenantId = stringOption(args, "tenant", "tenantId");
    const container = await createRequestContainer();
    let em = null;
    try {
      em = container.resolve("em");
    } catch {
      em = null;
    }
    try {
      const bus = container.resolve("eventBus");
      if (entity) {
        await bus.emitEvent(
          "query_index.purge",
          { entityType: entity, organizationId: orgId, tenantId },
          { persistent: true }
        );
        await recordIndexerLog(
          { em: em ?? void 0 },
          {
            source: "query_index",
            handler: "cli:query_index.purge",
            message: `Purge requested for ${entity}`,
            entityType: entity,
            tenantId: tenantId ?? null,
            organizationId: orgId ?? null
          }
        ).catch(() => void 0);
        console.log(`Scheduled purge for ${entity}`);
        return;
      }
      const { getEntityIds } = await import("@open-mercato/shared/lib/encryption/entityIds");
      const entityIds = flattenSystemEntityIds(getEntityIds());
      for (const id of entityIds) {
        await bus.emitEvent(
          "query_index.purge",
          { entityType: id, organizationId: orgId, tenantId },
          { persistent: true }
        );
        await recordIndexerLog(
          { em: em ?? void 0 },
          {
            source: "query_index",
            handler: "cli:query_index.purge",
            message: `Purge requested for ${id}`,
            entityType: id,
            tenantId: tenantId ?? null,
            organizationId: orgId ?? null,
            details: { mode: "bulk" }
          }
        ).catch(() => void 0);
      }
      console.log(`Scheduled purge for ${entityIds.length} entities`);
    } catch (error) {
      await recordIndexerLog(
        { em: em ?? void 0 },
        {
          source: "query_index",
          handler: "cli:query_index.purge",
          level: "warn",
          message: `Purge scheduling failed${entity ? ` for ${entity}` : ""}`,
          entityType: entity ?? null,
          tenantId: tenantId ?? null,
          organizationId: orgId ?? null,
          details: { error: error instanceof Error ? error.message : String(error) }
        }
      ).catch(() => void 0);
      await recordIndexerError(
        { em: em ?? void 0 },
        {
          source: "query_index",
          handler: "cli:query_index.purge",
          error,
          entityType: entity ?? null,
          tenantId,
          organizationId: orgId,
          payload: { args }
        }
      );
      throw error;
    } finally {
      if (typeof container?.dispose === "function") {
        await container.dispose();
      }
    }
  }
};
var cli_default = [rebuild, rebuildAll, reindex, purge];
export {
  cli_default as default
};
//# sourceMappingURL=cli.js.map
