import { getAllSyncSubscribers } from "../crud/sync-subscriber-store.js";
import { matchesEventPattern } from "../crud/sync-event-runner.js";
import { getEnrichersForEntity } from "../crud/enricher-registry.js";
import { applyResponseEnrichers, applyResponseEnricherToRecord } from "../crud/enricher-runner.js";
import { createLogger } from "../logger/index.js";
const logger = createLogger("shared").child({ component: "query-extensions" });
function toQueryingEventId(entity) {
  return `${entity}.querying`;
}
function toQueriedEventId(entity) {
  return `${entity}.queried`;
}
function entityIdToEventEntity(entityId) {
  return entityId.replace(/:/g, ".");
}
function collectQuerySubscribers(eventId) {
  const allSync = getAllSyncSubscribers();
  return allSync.filter((s) => matchesEventPattern(s.metadata.event, eventId)).sort((a, b) => (a.metadata.priority ?? 50) - (b.metadata.priority ?? 50));
}
async function runBeforeQueryEvent(payload, ctx) {
  const eventId = toQueryingEventId(payload.entity);
  const subscribers = collectQuerySubscribers(eventId);
  if (subscribers.length === 0) {
    return { ok: true };
  }
  let currentQuery = payload.query;
  for (const subscriber of subscribers) {
    try {
      const result = await subscriber.handler(
        { ...payload, eventId, query: currentQuery },
        ctx
      );
      const queryResult = result;
      if (queryResult?.ok === false) {
        return {
          ok: false,
          errorMessage: queryResult.message ?? "Query blocked by subscriber",
          errorStatus: queryResult.status ?? 422
        };
      }
      if (queryResult?.modifiedQuery) {
        currentQuery = { ...currentQuery, ...queryResult.modifiedQuery };
      }
    } catch (error) {
      logger.error("Before-query subscriber failed", { subscriberId: subscriber.metadata.id, err: error });
      return {
        ok: false,
        errorMessage: `Subscriber ${subscriber.metadata.id} threw unexpectedly`,
        errorStatus: 500
      };
    }
  }
  const queryChanged = currentQuery !== payload.query;
  return {
    ok: true,
    modifiedQuery: queryChanged ? currentQuery : void 0
  };
}
async function runAfterQueryEvent(payload, ctx) {
  const eventId = toQueriedEventId(payload.entity);
  const subscribers = collectQuerySubscribers(eventId);
  if (subscribers.length === 0) {
    return {};
  }
  let currentResult = payload.result;
  for (const subscriber of subscribers) {
    try {
      const result = await subscriber.handler(
        { ...payload, eventId, result: currentResult },
        ctx
      );
      const queryResult = result;
      if (queryResult?.modifiedResult) {
        if (isValidQueryResult(queryResult.modifiedResult)) {
          currentResult = queryResult.modifiedResult;
        } else {
          logger.warn("After-query subscriber returned invalid modifiedResult shape \u2014 ignored", { subscriberId: subscriber.metadata.id });
        }
      }
    } catch (error) {
      logger.error("After-query subscriber failed", { subscriberId: subscriber.metadata.id, err: error });
    }
  }
  const resultChanged = currentResult !== payload.result;
  return {
    modifiedResult: resultChanged ? currentResult : void 0
  };
}
function isValidQueryResult(value) {
  if (!value || typeof value !== "object") return false;
  const candidate = value;
  return Array.isArray(candidate.items) && typeof candidate.page === "number" && typeof candidate.pageSize === "number" && typeof candidate.total === "number";
}
function reapplyScopeGuards(query, tenantId, organizationId) {
  return {
    ...query,
    tenantId,
    ...organizationId != null ? { organizationId } : {}
  };
}
async function applyQueryLevelEnrichers(items, entity, engine, context, mode = "list") {
  const selector = {
    surface: "query-engine",
    engine
  };
  const entries = getEnrichersForEntity(entity, selector);
  const filteredEntries = entries.filter((entry) => {
    const applyOn = entry.enricher.queryEngine?.applyOn ?? ["list", "detail"];
    return applyOn.includes(mode);
  });
  if (filteredEntries.length === 0) {
    return { items, enrichedBy: [], enricherErrors: [] };
  }
  if (mode === "detail" && items.length === 1) {
    const singleResult = await applyResponseEnricherToRecord(items[0], entity, context, filteredEntries);
    return {
      items: [singleResult.record],
      enrichedBy: singleResult._meta.enrichedBy,
      enricherErrors: singleResult._meta.enricherErrors ?? []
    };
  }
  const result = await applyResponseEnrichers(items, entity, context, filteredEntries);
  return {
    items: result.items,
    enrichedBy: result._meta.enrichedBy,
    enricherErrors: result._meta.enricherErrors ?? []
  };
}
async function runBeforeQueryPipeline(query, extensionCtx, diCtx) {
  const eventEntity = entityIdToEventEntity(extensionCtx.entity);
  const beforePayload = {
    eventId: toQueryingEventId(eventEntity),
    entity: eventEntity,
    timing: "before",
    engine: extensionCtx.engine,
    query,
    userId: extensionCtx.userId,
    organizationId: extensionCtx.organizationId,
    tenantId: extensionCtx.tenantId,
    em: extensionCtx.em
  };
  const beforeResult = await runBeforeQueryEvent(beforePayload, diCtx);
  if (!beforeResult.ok) {
    return {
      blocked: true,
      errorMessage: beforeResult.errorMessage,
      errorStatus: beforeResult.errorStatus,
      query
    };
  }
  let effectiveQuery = beforeResult.modifiedQuery ? { ...query, ...beforeResult.modifiedQuery } : query;
  effectiveQuery = reapplyScopeGuards(
    effectiveQuery,
    extensionCtx.tenantId,
    extensionCtx.organizationId
  );
  return { blocked: false, query: effectiveQuery };
}
async function runAfterQueryPipeline(result, query, extensionCtx, diCtx) {
  const eventEntity = entityIdToEventEntity(extensionCtx.entity);
  let currentResult = result;
  const enricherContext = {
    organizationId: extensionCtx.organizationId ?? "",
    tenantId: extensionCtx.tenantId,
    userId: extensionCtx.userId ?? "",
    em: extensionCtx.em,
    container: extensionCtx.container,
    userFeatures: extensionCtx.userFeatures
  };
  const mode = currentResult.items.length === 1 ? "detail" : "list";
  const enrichResult = await applyQueryLevelEnrichers(
    currentResult.items,
    eventEntity,
    extensionCtx.engine,
    enricherContext,
    mode
  );
  currentResult = { ...currentResult, items: enrichResult.items };
  const afterPayload = {
    eventId: toQueriedEventId(eventEntity),
    entity: eventEntity,
    timing: "after",
    engine: extensionCtx.engine,
    query,
    result: currentResult,
    userId: extensionCtx.userId,
    organizationId: extensionCtx.organizationId,
    tenantId: extensionCtx.tenantId,
    em: extensionCtx.em
  };
  const afterResult = await runAfterQueryEvent(afterPayload, diCtx);
  if (afterResult.modifiedResult) {
    currentResult = afterResult.modifiedResult;
  }
  return currentResult;
}
export {
  applyQueryLevelEnrichers,
  collectQuerySubscribers,
  entityIdToEventEntity,
  reapplyScopeGuards,
  runAfterQueryEvent,
  runAfterQueryPipeline,
  runBeforeQueryEvent,
  runBeforeQueryPipeline
};
//# sourceMappingURL=query-extension-runner.js.map
