import { getEnrichersForEntity } from "./enricher-registry.js";
import { logEnricherTiming } from "../umes/enricher-timing.js";
import { createLogger } from "../logger/index.js";
import { authorizeFeatures } from "../../security/featurePolicy.js";
const logger = createLogger("shared").child({ component: "umes" });
const DEFAULT_TIMEOUT = 2e3;
const SLOW_WARN_MS = 100;
const SLOW_ERROR_MS = 500;
const DEFAULT_CACHE_TTL_MS = 6e4;
function timeoutPromise(ms) {
  return new Promise(
    (_, reject) => setTimeout(() => reject(new Error(`Enricher timed out after ${ms}ms`)), ms)
  );
}
function hasRequiredFeatures(enricher, userFeatures) {
  if (!enricher.features || enricher.features.length === 0) return true;
  if (!userFeatures) return false;
  return authorizeFeatures(enricher.features, { grantedFeatures: userFeatures });
}
function filterByACLAndTenant(entries, context) {
  return entries.filter((entry) => {
    const enricher = entry.enricher;
    if (!hasRequiredFeatures(enricher, context.userFeatures)) return false;
    if (enricher.disabledTenantIds?.includes(context.tenantId)) return false;
    return true;
  });
}
function getActiveEnrichers(targetEntity, context) {
  const entries = getEnrichersForEntity(targetEntity);
  return filterByACLAndTenant(entries, context);
}
function resolveListCacheEnricherPlan(targetEntity, context) {
  const active = getActiveEnrichers(targetEntity, context);
  if (active.length === 0) return { signature: "", skipEnrichersOnCacheHit: false };
  const allCacheable = active.every((entry) => entry.enricher.cacheableOnListHit === true);
  if (!allCacheable) return { signature: "", skipEnrichersOnCacheHit: false };
  return {
    signature: active.map((entry) => entry.enricher.id).join(","),
    skipEnrichersOnCacheHit: true
  };
}
function resolveCache(context) {
  const container = context.container;
  if (!container?.resolve) return null;
  try {
    const cache = container.resolve("cache");
    if (cache && typeof cache.get === "function" && typeof cache.set === "function") {
      return cache;
    }
  } catch {
  }
  try {
    const cacheService = container.resolve("cacheService");
    if (cacheService && typeof cacheService.get === "function" && typeof cacheService.set === "function") {
      return cacheService;
    }
  } catch {
  }
  return null;
}
function buildCacheKey(enricher, context, mode, recordIds) {
  const sortedIds = [...recordIds].sort((a, b) => a.localeCompare(b));
  return `umes:enricher:${enricher.id}:tenant:${context.tenantId}:org:${context.organizationId}:mode:${mode}:ids:${JSON.stringify(sortedIds)}`;
}
function extractRecordId(record) {
  const idValue = record.id;
  if (typeof idValue === "string" && idValue.trim().length > 0) return idValue.trim();
  if (typeof idValue === "number") return String(idValue);
  return "unknown";
}
function getEnricherCacheTtl(enricher) {
  const ttl = enricher.cache?.ttl;
  if (typeof ttl === "number" && Number.isFinite(ttl) && ttl > 0) {
    return ttl;
  }
  return DEFAULT_CACHE_TTL_MS;
}
function getEnricherCacheTags(enricher, context) {
  const tags = /* @__PURE__ */ new Set([
    `tenant:${context.tenantId}`,
    `organization:${context.organizationId}`,
    `enricher:${enricher.id}`
  ]);
  for (const tag of enricher.cache?.tags ?? []) {
    if (!tag || tag.trim().length === 0) continue;
    tags.add(tag);
  }
  return Array.from(tags);
}
async function readEnricherCache(cache, key) {
  if (!cache) return null;
  try {
    const value = await cache.get(key);
    return value == null ? null : value;
  } catch {
    return null;
  }
}
async function writeEnricherCache(cache, key, value, ttl, tags) {
  if (!cache) return;
  try {
    await cache.set(key, value, { ttl, tags });
  } catch {
  }
}
async function applyResponseEnrichers(items, targetEntity, context, preFilteredEntries) {
  const activeEntries = preFilteredEntries ? filterByACLAndTenant(preFilteredEntries, context) : getActiveEnrichers(targetEntity, context);
  if (activeEntries.length === 0) {
    return { items, _meta: { enrichedBy: [] } };
  }
  const enrichedBy = [];
  const enricherErrors = [];
  let currentItems = items;
  const cache = resolveCache(context);
  for (const entry of activeEntries) {
    const enricher = entry.enricher;
    const timeout = enricher.timeout ?? DEFAULT_TIMEOUT;
    const startTime = Date.now();
    try {
      let result;
      const recordIds = currentItems.map((item) => extractRecordId(item));
      const shouldUseCache = enricher.cache?.strategy === "read-through";
      const cacheKey = shouldUseCache ? buildCacheKey(enricher, context, "many", recordIds) : null;
      if (shouldUseCache && cacheKey) {
        const cached = await readEnricherCache(cache, cacheKey);
        if (cached) {
          currentItems = cached;
          enrichedBy.push(enricher.id);
          continue;
        }
      }
      if (enricher.enrichMany) {
        result = await Promise.race([
          enricher.enrichMany(currentItems, context),
          timeoutPromise(timeout)
        ]);
      } else {
        throw new Error(
          `Enricher ${enricher.id} must implement enrichMany() for list endpoints`
        );
      }
      const elapsedMs = Date.now() - startTime;
      if (elapsedMs > SLOW_ERROR_MS) {
        logger.error("Enricher exceeded slow threshold", { enricherId: enricher.id, elapsedMs, thresholdMs: SLOW_ERROR_MS });
      } else if (elapsedMs > SLOW_WARN_MS) {
        logger.warn("Enricher exceeded slow threshold", { enricherId: enricher.id, elapsedMs, thresholdMs: SLOW_WARN_MS });
      }
      logEnricherTiming(enricher.id, entry.moduleId, targetEntity, elapsedMs);
      currentItems = result;
      if (shouldUseCache && cacheKey) {
        await writeEnricherCache(
          cache,
          cacheKey,
          result,
          getEnricherCacheTtl(enricher),
          getEnricherCacheTags(enricher, context)
        );
      }
      enrichedBy.push(enricher.id);
    } catch (err) {
      if (enricher.critical) {
        throw err;
      }
      logger.warn("Enricher failed", { enricherId: enricher.id, err });
      enricherErrors.push(enricher.id);
      if (enricher.fallback) {
        currentItems = currentItems.map((item) => ({
          ...item,
          ...enricher.fallback
        }));
      }
    }
  }
  return {
    items: currentItems,
    _meta: {
      enrichedBy,
      ...enricherErrors.length > 0 ? { enricherErrors } : {}
    }
  };
}
async function applyResponseEnricherToRecord(record, targetEntity, context, preFilteredEntries) {
  const activeEntries = preFilteredEntries ? filterByACLAndTenant(preFilteredEntries, context) : getActiveEnrichers(targetEntity, context);
  if (activeEntries.length === 0) {
    return { record, _meta: { enrichedBy: [] } };
  }
  const enrichedBy = [];
  const enricherErrors = [];
  let currentRecord = record;
  const cache = resolveCache(context);
  for (const entry of activeEntries) {
    const enricher = entry.enricher;
    const timeout = enricher.timeout ?? DEFAULT_TIMEOUT;
    const startTime = Date.now();
    try {
      const recordId = extractRecordId(currentRecord);
      const shouldUseCache = enricher.cache?.strategy === "read-through";
      const cacheKey = shouldUseCache ? buildCacheKey(enricher, context, "one", [recordId]) : null;
      if (shouldUseCache && cacheKey) {
        const cached = await readEnricherCache(cache, cacheKey);
        if (cached) {
          currentRecord = cached;
          enrichedBy.push(enricher.id);
          continue;
        }
      }
      const result = await Promise.race([
        enricher.enrichOne(currentRecord, context),
        timeoutPromise(timeout)
      ]);
      const elapsedMs = Date.now() - startTime;
      logEnricherTiming(enricher.id, entry.moduleId, targetEntity, elapsedMs);
      currentRecord = result;
      if (shouldUseCache && cacheKey) {
        await writeEnricherCache(
          cache,
          cacheKey,
          result,
          getEnricherCacheTtl(enricher),
          getEnricherCacheTags(enricher, context)
        );
      }
      enrichedBy.push(enricher.id);
    } catch (err) {
      if (enricher.critical) {
        throw err;
      }
      logger.warn("Enricher failed", { enricherId: enricher.id, err });
      enricherErrors.push(enricher.id);
      if (enricher.fallback) {
        currentRecord = { ...currentRecord, ...enricher.fallback };
      }
    }
  }
  return {
    record: currentRecord,
    _meta: {
      enrichedBy,
      ...enricherErrors.length > 0 ? { enricherErrors } : {}
    }
  };
}
export {
  applyResponseEnricherToRecord,
  applyResponseEnrichers,
  resolveListCacheEnricherPlan
};
//# sourceMappingURL=enricher-runner.js.map
