import { createLogger } from "../logger.js";
import { decomposeQuestion, extractComparisonEntities } from "./decompose.js";
import { synthesizeReport } from "./synthesize.js";
import { synthesizeLocal } from "./synthesis-local.js";
import { buildResearchBrief } from "./brief.js";
import { deduplicateResults } from "../search/dedup.js";
import { rerankResults } from "../search/rerank.js";
import { applyAllFilters } from "../search/filters.js";
import { exploreInParallel } from "./branch-exploration.js";
import { getExtractProvider } from "../providers/extract-provider.js";
import { truncateSmartly } from "../search/truncate.js";
import { cacheContent } from "../cache/store.js";
import { getEmbeddingService } from "../embedding/embed.js";
import { checkSamplingSupport } from "../search/sampling.js";
import { isLlmConfigured as isLocalLlmEnabled } from "../integrations/cloud/llm/run.js";
const log = createLogger("research");
const DEPTH_CONFIG = {
  quick: { subQueries: 2, minSources: 5, maxSources: 8 },
  standard: { subQueries: 4, minSources: 10, maxSources: 15 },
  comprehensive: { subQueries: 7, minSources: 20, maxSources: 25 }
};
const SEARCH_TOTAL_BUDGET_MS = {
  quick: 15e3,
  standard: 3e4,
  comprehensive: 6e4
};
const SEARCH_PER_QUERY_BUDGET_MS = {
  quick: 8e3,
  standard: 1e4,
  comprehensive: 15e3
};
const PER_SOURCE_CHAR_CAP = 3e3;
const TOTAL_SOURCES_CHAR_CAP = 4e4;
async function runResearchPipeline(input, engines, router, server) {
  const start = Date.now();
  const depth = input.depth ?? "standard";
  const config = DEPTH_CONFIG[depth] ?? DEPTH_CONFIG.standard;
  const maxSources = input.max_sources ?? config.maxSources;
  try {
    log.info("research pipeline started", { question: input.question, depth });
    const decomposeResult = await decomposeQuestion(
      input.question,
      depth,
      server
    );
    const subQueries = (() => {
      const original = input.question.trim();
      if (!original) return decomposeResult.subQueries;
      const seen = new Set(decomposeResult.subQueries.map((q) => q.toLowerCase()));
      if (seen.has(original.toLowerCase())) return decomposeResult.subQueries;
      return [original, ...decomposeResult.subQueries];
    })();
    const queryType = decomposeResult.queryType;
    log.info("decomposition complete", { subQueryCount: subQueries.length, samplingUsed: decomposeResult.samplingUsed, queryType });
    const effEngines = subQueries.length >= 3 && engines.length > 2 ? engines.slice(0, 2) : engines;
    const perEngineMaxResults = Math.ceil(maxSources / subQueries.length) * 2;
    const branchResults = await exploreInParallel(
      subQueries,
      async (subQuery, signal) => {
        const results = [];
        const usedHere = /* @__PURE__ */ new Set();
        const engineOpts = {
          maxResults: perEngineMaxResults,
          includeDomains: input.include_domains,
          excludeDomains: input.exclude_domains
        };
        await Promise.allSettled(
          effEngines.map(async (engine) => {
            if (signal.aborted) return;
            try {
              const rs = await engine.search(subQuery, engineOpts);
              for (const r of rs) results.push(r);
              usedHere.add(engine.name);
            } catch (err) {
              log.warn("research engine search failed", {
                engine: engine.name,
                query: subQuery,
                error: err instanceof Error ? err.message : String(err)
              });
            }
          })
        );
        return { results, enginesUsed: [...usedHere] };
      },
      {
        maxConcurrent: 3,
        totalBudgetMs: SEARCH_TOTAL_BUDGET_MS[depth] ?? SEARCH_TOTAL_BUDGET_MS.standard,
        perQueryBudgetMs: SEARCH_PER_QUERY_BUDGET_MS[depth] ?? SEARCH_PER_QUERY_BUDGET_MS.standard
      }
    );
    const allRaw = [];
    const enginesUsed = /* @__PURE__ */ new Set();
    const searchErrors = [];
    for (const br of branchResults) {
      if (br.ok && br.result) {
        allRaw.push(...br.result.results);
        for (const e of br.result.enginesUsed) enginesUsed.add(e);
      } else if (br.error) {
        searchErrors.push(`${br.query}: ${br.error}`);
      }
    }
    if (searchErrors.length > 0) {
      log.warn("some search sub-queries failed", { errors: searchErrors });
    }
    log.info("search phase complete", { totalRaw: allRaw.length, engines: [...enginesUsed] });
    let merged = deduplicateResults(allRaw);
    merged = applyAllFilters(merged, {
      includeDomains: input.include_domains,
      excludeDomains: input.exclude_domains
    });
    merged = await rerankResults(input.question, merged);
    merged = merged.slice(0, maxSources);
    if (merged.length === 0) {
      return {
        report: `## Research: ${input.question}

No sources could be found for this query.`,
        citations: [],
        sources: [],
        sub_queries: subQueries,
        depth,
        total_time_ms: Date.now() - start,
        sampling_supported: !!server && checkSamplingSupport(server)
      };
    }
    const sources = await fetchSources(merged, router, maxSources);
    applySourceBudget(sources, PER_SOURCE_CHAR_CAP, TOTAL_SOURCES_CHAR_CAP);
    log.info("fetch phase complete", {
      fetched: sources.filter((s) => s.fetched).length,
      failed: sources.filter((s) => !s.fetched).length
    });
    const synthesisResult = await synthesizeReport(
      input.question,
      sources,
      depth,
      server
    );
    log.info("synthesis complete", { samplingUsed: synthesisResult.samplingUsed, reportLength: synthesisResult.report.length });
    let finalReport = synthesisResult.report;
    let finalCitations = synthesisResult.citations;
    let localSynthesisText;
    if (!synthesisResult.samplingUsed && isLocalLlmEnabled()) {
      try {
        const localSources = sources.filter((s) => s.fetched && s.markdown_content.length > 0).map((s) => ({ url: s.url, title: s.title, markdown: s.markdown_content }));
        if (localSources.length > 0) {
          const local = await synthesizeLocal(input.question, localSources);
          finalReport = local.text;
          localSynthesisText = local.text;
          finalCitations = local.citations.filter((idx) => idx >= 0 && idx < localSources.length).map((idx) => {
            const s = localSources[idx];
            return {
              index: idx + 1,
              url: s.url,
              title: s.title,
              snippet: s.markdown.slice(0, 200)
            };
          });
          log.info("local synthesis succeeded", { reportLength: finalReport.length });
        }
      } catch (err) {
        log.warn("local LLM synthesis failed; using heuristic fallback", {
          error: err instanceof Error ? err.message : String(err)
        });
      }
    }
    const comparisonEntities = queryType === "comparison" ? extractComparisonEntities(input.question).entities : [];
    const brief = !synthesisResult.samplingUsed ? await buildResearchBrief(
      input.question,
      sources,
      subQueries,
      PER_SOURCE_CHAR_CAP,
      TOTAL_SOURCES_CHAR_CAP,
      queryType,
      comparisonEntities,
      localSynthesisText
    ) : void 0;
    return {
      report: finalReport,
      citations: finalCitations,
      sources,
      sub_queries: subQueries,
      depth,
      total_time_ms: Date.now() - start,
      sampling_supported: !!server && checkSamplingSupport(server),
      ...brief ? { brief } : {}
    };
  } catch (err) {
    log.error("research pipeline failed", {
      question: input.question,
      error: err instanceof Error ? err.message : String(err)
    });
    return {
      report: "",
      citations: [],
      sources: [],
      sub_queries: [],
      depth,
      total_time_ms: Date.now() - start,
      sampling_supported: !!server && checkSamplingSupport(server),
      error: err instanceof Error ? err.message : String(err)
    };
  }
}
async function fetchSources(merged, router, maxSources) {
  const fetchPromises = merged.slice(0, maxSources).map(async (result) => {
    try {
      const raw = await Promise.race([
        router.fetch(result.url, { renderJs: "auto" }),
        new Promise(
          (_, reject) => setTimeout(() => reject(new Error("fetch timeout")), 15e3)
        )
      ]);
      const extractor = await getExtractProvider();
      const extraction = await extractor.extract(raw.html, raw.finalUrl, {
        maxChars: 3e4,
        contentType: raw.contentType
      });
      const truncated = truncateSmartly(extraction.markdown, PER_SOURCE_CHAR_CAP);
      try {
        cacheContent(raw, extraction);
      } catch (err) {
        log.warn("failed to cache research source", { url: result.url, error: String(err) });
      }
      try {
        const embeddingService = getEmbeddingService();
        if (embeddingService.isAvailable()) {
          embeddingService.embedAsync(raw.finalUrl, extraction.markdown);
        }
      } catch (err) {
        log.debug("embedding hook skipped for research source", { error: String(err) });
      }
      return {
        url: result.url,
        title: extraction.title || result.title,
        markdown_content: truncated,
        relevance_score: result.relevance_score,
        fetched: true
      };
    } catch (err) {
      log.debug("failed to fetch research source", {
        url: result.url,
        error: err instanceof Error ? err.message : String(err)
      });
      return {
        url: result.url,
        title: result.title,
        markdown_content: result.snippet,
        relevance_score: result.relevance_score,
        fetched: false,
        fetch_error: err instanceof Error ? err.message : String(err)
      };
    }
  });
  return Promise.all(fetchPromises);
}
function applySourceBudget(sources, perSourceCap, totalCap) {
  let used = 0;
  for (const s of sources) {
    if (!s.markdown_content) continue;
    if (used >= totalCap) {
      s.markdown_content = "";
      continue;
    }
    const remaining = totalCap - used;
    const cap = Math.min(perSourceCap, remaining);
    if (s.markdown_content.length > cap) {
      s.markdown_content = truncateSmartly(s.markdown_content, cap);
    }
    used += s.markdown_content.length;
  }
}
export {
  runResearchPipeline
};
//# sourceMappingURL=pipeline.js.map