import { COMMON_NOUNS } from "./common-nouns.js";
const TOKEN_RE = /[a-z0-9]+/g;
const OVERLAP_THRESHOLD = 0.3;
function tokenize(s) {
  return (s.toLowerCase().match(TOKEN_RE) ?? []).filter((t) => t.length >= 2);
}
function jaccard(a, b) {
  if (a.length === 0 || b.length === 0) return 0;
  const sa = new Set(a);
  const sb = new Set(b);
  let inter = 0;
  for (const t of sa) if (sb.has(t)) inter++;
  const union = sa.size + sb.size - inter;
  return union === 0 ? 0 : inter / union;
}
function primaryHostnameLabel(url) {
  try {
    const host = new URL(url).hostname.toLowerCase().replace(/^www\./, "");
    return host.split(".")[0] ?? "";
  } catch {
    return "";
  }
}
function primaryQuery(input) {
  if (Array.isArray(input.query)) return (input.query[0] ?? "").trim();
  return (input.query ?? "").trim();
}
function isBrandCollisionSuspect(input, output) {
  const q = primaryQuery(input);
  const qTokens = tokenize(q);
  if (qTokens.length === 0 || qTokens.length > 2) {
    return { fires: false, reason: "" };
  }
  const matchedNoun = qTokens.find((t) => COMMON_NOUNS.has(t));
  if (!matchedNoun) return { fires: false, reason: "" };
  const top = output.results[0];
  if (!top) return { fires: false, reason: "" };
  const label = primaryHostnameLabel(top.url);
  if (!qTokens.includes(label)) return { fires: false, reason: "" };
  const overlap = jaccard(qTokens, tokenize(top.title));
  if (overlap >= OVERLAP_THRESHOLD) return { fires: false, reason: "" };
  return {
    fires: true,
    reason: `query "${q}" is a common noun, top-1 hostname "${label}" matches the query, title overlap ${(overlap * 100).toFixed(0)}%`
  };
}
function isIncludeDomainsOverFilter(input, output) {
  if (!input.include_domains || input.include_domains.length === 0) {
    return { fires: false, reason: "" };
  }
  if (output.results.length >= 2) return { fires: false, reason: "" };
  return {
    fires: true,
    reason: `include_domains set but only ${output.results.length} core result(s)`
  };
}
function isAllEnginesFailed(_input, output) {
  if (output.results.length > 0) return { fires: false, reason: "" };
  const outcomes = output.engine_outcomes;
  if (outcomes && outcomes.length > 0 && outcomes.some((o) => o.ok)) {
    return { fires: false, reason: "" };
  }
  return { fires: true, reason: "core returned no results; engines failed or empty" };
}
function isTop1HighScoreLowOverlap(input, output) {
  const top = output.results[0];
  if (!top) return { fires: false, reason: "" };
  if (top.relevance_score < 0.99) return { fires: false, reason: "" };
  const q = primaryQuery(input);
  const overlap = jaccard(tokenize(q), tokenize(top.title));
  if (overlap >= OVERLAP_THRESHOLD) return { fires: false, reason: "" };
  return {
    fires: true,
    reason: `top-1 score ${top.relevance_score.toFixed(2)} but title overlap ${(overlap * 100).toFixed(0)}%`
  };
}
const SIGNALS = [
  { name: "brand_collision_suspect", predicate: isBrandCollisionSuspect },
  { name: "include_domains_over_filter", predicate: isIncludeDomainsOverFilter },
  { name: "all_engines_failed", predicate: isAllEnginesFailed },
  { name: "top1_high_score_low_overlap", predicate: isTop1HighScoreLowOverlap }
];
const SIGNAL_NAMES = SIGNALS.map((s) => s.name);
function evaluateSignals(input, output) {
  const fired = [];
  for (const s of SIGNALS) {
    if (s.predicate(input, output).fires) fired.push(s.name);
  }
  return fired;
}
function describeSignals(input, output) {
  return SIGNALS.map((s) => ({ name: s.name, result: s.predicate(input, output) }));
}
export {
  SIGNAL_NAMES,
  describeSignals,
  evaluateSignals,
  isAllEnginesFailed,
  isBrandCollisionSuspect,
  isIncludeDomainsOverFilter,
  isTop1HighScoreLowOverlap
};
//# sourceMappingURL=signals.js.map