import { normalizeUrl } from "../cache/store.js";
import { getConfig } from "../config.js";
import { createLogger } from "../logger.js";
const log = createLogger("search");
const RRF_K = 60;
function normalizeQueries(queries) {
  try {
    const config = getConfig();
    const seen = /* @__PURE__ */ new Set();
    const normalized = [];
    for (const raw of queries) {
      const q = raw.toLowerCase().trim().replace(/\s+/g, " ");
      if (q.length === 0) continue;
      if (seen.has(q)) continue;
      seen.add(q);
      normalized.push(q);
    }
    if (normalized.length > config.multiQueryMax) {
      log.warn("multi-query array exceeds max, truncating", {
        provided: normalized.length,
        max: config.multiQueryMax
      });
      return normalized.slice(0, config.multiQueryMax);
    }
    return normalized;
  } catch (err) {
    log.error("normalizeQueries failed", { error: String(err) });
    return [];
  }
}
async function fanOutSearch(queries, engines, options) {
  const allResults = [];
  const enginesUsed = /* @__PURE__ */ new Set();
  const errors = [];
  if (queries.length === 0 || engines.length === 0) {
    return { results: [], enginesUsed: [], errors: [] };
  }
  try {
    const config = getConfig();
    const concurrency = config.multiQueryConcurrency;
    const hasFilterAttrition = !!(options.includeDomains?.length || options.excludeDomains?.length);
    const overfetchFactor = hasFilterAttrition ? 3 : 2;
    const engineOptions = {
      maxResults: options.maxResults * overfetchFactor,
      timeRange: options.timeRange,
      language: options.language,
      includeDomains: options.includeDomains,
      excludeDomains: options.excludeDomains,
      fromDate: options.fromDate,
      toDate: options.toDate,
      category: options.category
    };
    const effEngines = queries.length >= 3 && engines.length > 2 ? engines.slice(0, 2) : engines;
    const tasks = [];
    for (const engine of effEngines) {
      for (const query of queries) {
        tasks.push({ engine, query });
      }
    }
    for (let i = 0; i < tasks.length; i += concurrency) {
      const batch = tasks.slice(i, i + concurrency);
      const promises = batch.map(async ({ engine, query }) => {
        try {
          const results = await engine.search(query, engineOptions);
          for (const r of results) {
            allResults.push(r);
            enginesUsed.add(engine.name);
          }
        } catch (err) {
          const msg = err instanceof Error ? err.message : String(err);
          log.warn("multi-query engine search failed", {
            engine: engine.name,
            query,
            error: msg
          });
          errors.push(`${engine.name}(${query}): ${msg}`);
        }
      });
      await Promise.allSettled(promises);
    }
    return {
      results: allResults,
      enginesUsed: [...enginesUsed],
      errors
    };
  } catch (err) {
    log.error("fanOutSearch failed", { error: String(err) });
    return {
      results: allResults,
      enginesUsed: [...enginesUsed],
      errors: [...errors, `fanOutSearch: ${String(err)}`]
    };
  }
}
function synthesizeIntent(queries) {
  try {
    return queries.map((q) => q.trim()).filter(Boolean).join("; ");
  } catch (err) {
    log.error("synthesizeIntent failed", { error: String(err) });
    return "";
  }
}
function mergeWithRRF(rankedLists) {
  try {
    if (rankedLists.length === 0) return [];
    const nonEmpty = rankedLists.filter((l) => l.length > 0);
    if (nonEmpty.length === 0) return [];
    const rrfScores = /* @__PURE__ */ new Map();
    const bestAppearance = /* @__PURE__ */ new Map();
    for (const list of nonEmpty) {
      for (let rank = 0; rank < list.length; rank++) {
        const item = list[rank];
        let normalizedUrlStr;
        try {
          normalizedUrlStr = normalizeUrl(item.url);
        } catch {
          normalizedUrlStr = item.url;
        }
        const rrfContribution = 1 / (RRF_K + rank + 1);
        const current = rrfScores.get(normalizedUrlStr) ?? 0;
        rrfScores.set(normalizedUrlStr, current + rrfContribution);
        const existing = bestAppearance.get(normalizedUrlStr);
        if (!existing || rank < existing.bestRank) {
          bestAppearance.set(normalizedUrlStr, { result: item, bestRank: rank });
        }
      }
    }
    let maxScore = 0;
    for (const score of rrfScores.values()) {
      if (score > maxScore) maxScore = score;
    }
    const merged = [];
    for (const [normalizedUrlStr, score] of rrfScores.entries()) {
      const appearance = bestAppearance.get(normalizedUrlStr);
      merged.push({
        ...appearance.result,
        relevance_score: maxScore > 0 ? score / maxScore : 0
      });
    }
    merged.sort((a, b) => b.relevance_score - a.relevance_score);
    return merged;
  } catch (err) {
    log.error("mergeWithRRF failed", { error: String(err) });
    return [];
  }
}
const DEEP_SUFFIXES = ["guide", "tutorial", "examples", "best practices"];
const COMPARE_INTRO = /(compare|comparison of|difference[s]? between|trade[- ]?off[s]? (?:of|between)|vs\.?|versus)/i;
const STOP_PHRASES = /* @__PURE__ */ new Set([
  "and",
  "or",
  "vs",
  "versus",
  "between",
  "of",
  "the",
  "a",
  "an",
  "for",
  "in",
  "on",
  "to",
  "with",
  "compare",
  "comparison",
  "differences",
  "tradeoff",
  "tradeoffs",
  "trade-off",
  "trade-offs"
]);
function splitListEntities(segment) {
  const cleaned = segment.replace(/^\s*(?:between|of|the|a|an)\s+/i, "").trim();
  const rawParts = cleaned.split(/,\s*(?:and\s+)?|\s+and\s+|\s+vs\.?\s+|\s+versus\s+/i).map((s) => s.trim()).filter((s) => s.length >= 2 && s.length <= 120 && !STOP_PHRASES.has(s.toLowerCase()));
  if (rawParts.length < 2) return [...new Set(rawParts)];
  const tokenCounts = rawParts.map((p) => p.split(/\s+/).length);
  const maxTokens = Math.max(...tokenCounts);
  if (maxTokens <= 1) return [...new Set(rawParts)];
  const longestIdx = tokenCounts.indexOf(maxTokens);
  const longestTokens = rawParts[longestIdx].split(/\s+/);
  const tailLen = Math.min(3, maxTokens - 1);
  const tail = longestTokens.slice(maxTokens - tailLen).join(" ");
  if (!tail) return [...new Set(rawParts)];
  const merged = rawParts.map((part, i) => {
    if (i === longestIdx) return part;
    const tokens = part.split(/\s+/);
    if (tokens.length > 1) return part;
    if (part.toLowerCase().endsWith(tail.toLowerCase())) return part;
    return `${part} ${tail}`;
  });
  return [...new Set(merged)];
}
function decomposeMultiHop(query) {
  const text = query.trim();
  const compareMatch = text.match(/(?:compare|trade[- ]?off[s]? (?:of|between)|difference[s]? between)\s+(.+?)(?:[?.]|$)/i);
  if (compareMatch) {
    const items = splitListEntities(compareMatch[1]);
    if (items.length >= 2 && items.length <= 6) return items;
  }
  const vsMatch = text.match(/^([\w\s.&+#/-]+?)\s+(?:vs\.?|versus)\s+([\w\s.&+#/-]+?)(?:\s+(?:for|in|when|to)\s+|[?.]|$)/i);
  if (vsMatch) {
    const items = [vsMatch[1].trim(), vsMatch[2].trim()].filter(Boolean);
    if (items.length === 2) return items;
  }
  const commaList = text.match(/of\s+([\w\s.,&+#/-]+?(?:,\s*and\s+|\s+and\s+)[\w\s.&+#/-]+?)(?:\s+(?:for|in|when|to)\s+|[?.]|$)/i);
  if (commaList) {
    const items = splitListEntities(commaList[1]);
    if (items.length >= 2 && items.length <= 6) return items;
  }
  return null;
}
function expandQueryHeuristic(query) {
  const max = Math.max(1, parseInt(process.env.WIGOLO_QUERY_EXPAND_VARIANTS || "5", 10));
  const trimmed = query.trim();
  if (COMPARE_INTRO.test(trimmed) || /,.+,.+\band\b/.test(trimmed)) {
    const parts = decomposeMultiHop(trimmed);
    if (parts && parts.length >= 2) {
      const cap = Math.max(max, parts.length);
      const out = [trimmed, ...parts];
      return [...new Set(out)].slice(0, cap);
    }
  }
  const variants = [trimmed];
  for (const suffix of DEEP_SUFFIXES) {
    if (variants.length >= max) break;
    const candidate = `${trimmed} ${suffix}`;
    if (!variants.includes(candidate)) variants.push(candidate);
  }
  return variants;
}
function expandIfSingle(query) {
  if (Array.isArray(query)) return [...query];
  return expandQueryHeuristic(query);
}
export {
  decomposeMultiHop,
  expandIfSingle,
  expandQueryHeuristic,
  fanOutSearch,
  mergeWithRRF,
  normalizeQueries,
  synthesizeIntent
};
//# sourceMappingURL=multi-query.js.map