import type {
  AssistantFallbackType,
  AssistantReplyType,
  AnswerGroundingCheck,
  AssistantRequirement,
  RequirementCoverageReport,
  UnifiedRetrievalResult
} from "../types/assistant";
import type { RouteHintSummary } from "../types/normalizer";
import type { AnswerStructureV11, EvidenceConfidence, EvidenceItem, EvidenceLimitationReasonCode } from "../types/stage1Contracts";

interface ComposeAnswerInput {
  userMessage: string;
  routeSummary: RouteHintSummary | null;
  retrievalResults: UnifiedRetrievalResult[];
  requirements: AssistantRequirement[];
  coverageReport: RequirementCoverageReport;
  groundingCheck: AnswerGroundingCheck;
  enableAnswerPolicyV11?: boolean;
}

interface ComposeAnswerOutput {
  assistant_reply: string;
  fallback_type: AssistantFallbackType;
  reply_type: AssistantReplyType;
  answer_structure_v11?: AnswerStructureV11;
}

function fallbackFromSummary(routeSummary: RouteHintSummary | null): AssistantFallbackType {
  if (!routeSummary || routeSummary.mode !== "deterministic_v2") {
    return "none";
  }
  return routeSummary.fallback.type as AssistantFallbackType;
}

function uniqueStrings(values: string[], limit = 6): string[] {
  return Array.from(new Set(values.map((item) => item.trim()).filter(Boolean))).slice(0, limit);
}

function formatList(items: string[]): string {
  if (items.length === 0) {
    return "";
  }
  return items.map((item) => `- ${item}`).join("\n");
}

function extractTopFacts(results: UnifiedRetrievalResult[]): string[] {
  const lines: string[] = [];
  for (const result of results.filter((item) => item.status === "ok").slice(0, 3)) {
    if (result.result_type === "chain") {
      const top = result.items.slice(0, 3).map((item) => {
        const counterparty = String(item.counterparty_id ?? "не указан");
        const operations = String(item.operations_count ?? "0");
        const docs = String(item.document_refs_count ?? "0");
        return `Контрагент ${counterparty}: операций ${operations}, документов в связке ${docs}.`;
      });
      lines.push(...top);
      continue;
    }
    if (result.result_type === "ranking") {
      const top = result.items
        .slice(0, 5)
        .map((item) => `${item.rank ?? "•"}. ${String(item.entity ?? "Сущность")} — ${String(item.records_count ?? 0)}.`);
      lines.push(...top);
      continue;
    }
    if (result.result_type === "list") {
      const top = result.items.slice(0, 5).map((item) => {
        if (item.risk_score !== undefined) {
          return `${String(item.source_entity ?? "Запись")} (${String(item.source_id ?? "")}) — риск ${String(item.risk_score)}.`;
        }
        return `${String(item.source_entity ?? "Запись")} (${String(item.source_id ?? "")}).`;
      });
      lines.push(...top);
      continue;
    }
    const top = result.items
      .slice(0, 3)
      .map((item) => `${String(item.source_entity ?? "Запись")} (${String(item.source_id ?? "")}).`);
    lines.push(...top);
  }
  return lines;
}

function extractWhyIncluded(results: UnifiedRetrievalResult[]): string[] {
  return uniqueStrings(results.flatMap((item) => item.why_included));
}

function extractSelectionReasons(results: UnifiedRetrievalResult[]): string[] {
  return uniqueStrings(results.flatMap((item) => item.selection_reason));
}

function extractRiskFactors(results: UnifiedRetrievalResult[]): string[] {
  return uniqueStrings(results.flatMap((item) => item.risk_factors));
}

function extractBusinessInterpretation(results: UnifiedRetrievalResult[]): string[] {
  return uniqueStrings(results.flatMap((item) => item.business_interpretation));
}

function extractLimitations(results: UnifiedRetrievalResult[]): string[] {
  return uniqueStrings(results.flatMap((item) => item.limitations));
}

function summaryValue(result: UnifiedRetrievalResult, key: string): unknown {
  const summary = result.summary ?? {};
  return Object.prototype.hasOwnProperty.call(summary, key) ? summary[key] : undefined;
}

function summaryBoolean(result: UnifiedRetrievalResult, key: string): boolean {
  return summaryValue(result, key) === true;
}

function summaryString(result: UnifiedRetrievalResult, key: string): string | null {
  const value = summaryValue(result, key);
  return typeof value === "string" ? value : null;
}

function suggestNextStep(requirements: AssistantRequirement[], coverage: RequirementCoverageReport): string[] {
  const next: string[] = [];
  if (coverage.clarification_needed_for.length > 0) {
    next.push("Уточните период, счет, документ или контрагента для требований: " + coverage.clarification_needed_for.join(", ") + ".");
  }
  if (coverage.requirements_uncovered.length > 0) {
    next.push("Проверьте непокрытые требования: " + coverage.requirements_uncovered.join(", ") + ".");
  }
  if (coverage.out_of_scope_requirements.length > 0) {
    next.push("Часть запроса вне текущего учетного контура: " + coverage.out_of_scope_requirements.join(", ") + ".");
  }
  if (next.length === 0 && requirements.length > 0) {
    next.push("Следующим шагом можно открыть технический разбор и углубить проверку по выбранным объектам.");
  }
  return next;
}

interface PolicySignals {
  broad_query_detected: boolean;
  broad_result_flag: boolean;
  minimum_evidence_failed: boolean;
  degraded_to: "partial" | "clarification" | null;
  narrowing_strength: "weak" | "medium" | "strong" | null;
}

type PolicyMode =
  | "focused_grounded"
  | "broad_partial"
  | "clarification_required"
  | "out_of_scope"
  | "route_mismatch"
  | "empty"
  | "no_grounded"
  | "backend_error";

interface PolicyDecision {
  mode: PolicyMode;
  fallback_type: AssistantFallbackType;
  reply_type: AssistantReplyType;
}

interface MissingAnchors {
  period: boolean;
  account: boolean;
  documentOrObject: boolean;
  counterparty: boolean;
  anomalyType: boolean;
}

function flattenEvidence(results: UnifiedRetrievalResult[]): EvidenceItem[] {
  return results.flatMap((item) => item.evidence);
}

function buildClaimEvidenceLinks(results: UnifiedRetrievalResult[]): NonNullable<AnswerStructureV11["evidence_block"]["claim_evidence_links"]> {
  const byClaim = new Map<string, string[]>();
  for (const evidence of flattenEvidence(results)) {
    const claimRef = String(evidence.claim_ref ?? "").trim();
    const evidenceId = String(evidence.evidence_id ?? "").trim();
    if (!claimRef || !evidenceId) {
      continue;
    }
    const current = byClaim.get(claimRef) ?? [];
    current.push(evidenceId);
    byClaim.set(claimRef, current);
  }
  return Array.from(byClaim.entries())
    .slice(0, 10)
    .map(([claim_ref, evidenceIds]) => ({
      claim_ref,
      evidence_ids: uniqueStrings(evidenceIds, 10)
    }));
}

function aggregatePolicySignals(results: UnifiedRetrievalResult[]): PolicySignals {
  const broad_query_detected = results.some((item) => summaryBoolean(item, "broad_query_detected"));
  const broad_result_flag = results.some((item) => summaryBoolean(item, "broad_result_flag"));
  const minimum_evidence_failed = results.some((item) => summaryBoolean(item, "minimum_evidence_failed"));

  let degraded_to: PolicySignals["degraded_to"] = null;
  for (const result of results) {
    const degraded = summaryString(result, "degraded_to");
    if (degraded === "clarification") {
      degraded_to = "clarification";
      break;
    }
    if (degraded === "partial") {
      degraded_to = "partial";
    }
  }

  const narrowingOrder: Record<"weak" | "medium" | "strong", number> = {
    weak: 0,
    medium: 1,
    strong: 2
  };
  let narrowing_strength: PolicySignals["narrowing_strength"] = null;
  for (const result of results) {
    const value = summaryString(result, "narrowing_strength");
    if (value !== "weak" && value !== "medium" && value !== "strong") {
      continue;
    }
    if (!narrowing_strength || narrowingOrder[value] < narrowingOrder[narrowing_strength]) {
      narrowing_strength = value;
    }
  }

  return {
    broad_query_detected,
    broad_result_flag,
    minimum_evidence_failed,
    degraded_to,
    narrowing_strength
  };
}

function confidenceToScore(value: EvidenceConfidence): number {
  if (value === "high") return 3;
  if (value === "medium") return 2;
  return 1;
}

function aggregateConfidence(results: UnifiedRetrievalResult[], evidenceItems: EvidenceItem[]): EvidenceConfidence {
  const scores: number[] = [];
  for (const evidence of evidenceItems) {
    scores.push(confidenceToScore(evidence.confidence));
  }
  for (const result of results) {
    if (result.status === "error") {
      continue;
    }
    scores.push(confidenceToScore(result.confidence));
  }
  if (scores.length === 0) {
    return "low";
  }
  const average = scores.reduce((acc, item) => acc + item, 0) / scores.length;
  if (average >= 2.6) return "high";
  if (average >= 1.8) return "medium";
  return "low";
}

function collectLimitationReasonCodes(evidenceItems: EvidenceItem[]): EvidenceLimitationReasonCode[] {
  const codes = evidenceItems
    .map((item) => item.limitation?.reason_code ?? null)
    .filter((item): item is EvidenceLimitationReasonCode => Boolean(item));
  return uniqueStrings(codes, 8) as EvidenceLimitationReasonCode[];
}

function limitationReasonToText(code: EvidenceLimitationReasonCode): string {
  if (code === "snapshot_only") return "Evidence is snapshot-only and may lag source-of-record.";
  if (code === "heuristic_inference") return "Part of the conclusion relies on heuristic inference.";
  if (code === "missing_mechanism") return "Mechanism is unresolved for part of the evidence.";
  if (code === "weak_source_mapping") return "Source mapping is weak for part of the evidence.";
  if (code === "insufficient_detail") return "Evidence lacks detail for a strong factual claim.";
  return "Some evidence limitations remain unresolved.";
}

function detectMissingAnchors(userMessage: string): MissingAnchors {
  const lower = String(userMessage ?? "").toLowerCase();
  const hasPeriod = /\b20\d{2}(?:[-./](?:0[1-9]|1[0-2]))?\b/.test(lower);
  const hasAccount = /(?:\bсчет\b|\baccount\b|\bschet\b|\b\d{2}(?:\.\d{2})?\b)/i.test(lower);
  const hasDocumentOrObject = /(?:документ|invoice|guid|object|obj|#\d+|\bid\b|\bref\b|dokument|doc)/i.test(lower);
  const hasCounterparty = /(?:контрагент|supplier|buyer|customer|kontragent|postavsh|pokupatel)/i.test(lower);
  const hasAnomalyType = /(?:аномал|risk|отклон|разрыв|mismatch|duplicate|tail|цепочк|anomali|hvost)/i.test(lower);

  return {
    period: !hasPeriod,
    account: !hasAccount,
    documentOrObject: !hasDocumentOrObject,
    counterparty: !hasCounterparty,
    anomalyType: !hasAnomalyType
  };
}

function buildClarificationQuestions(input: {
  mode: PolicyMode;
  missingAnchors: MissingAnchors;
  coverageReport: RequirementCoverageReport;
  policySignals: PolicySignals;
}): string[] {
  const questions: string[] = [];
  const shouldAsk = input.mode === "clarification_required" || input.coverageReport.clarification_needed_for.length > 0;
  if (!shouldAsk) {
    return questions;
  }

  if (input.missingAnchors.period) {
    questions.push("Уточните период проверки (например, 2020-06).");
  }
  if (input.missingAnchors.account) {
    questions.push("Уточните счет или группу счетов (например, 19, 60, 62).");
  }
  if (input.missingAnchors.documentOrObject) {
    questions.push("Укажите документ/GUID/конкретный объект для трассировки.");
  }
  if (input.missingAnchors.counterparty) {
    questions.push("Укажите контрагента или группу контрагентов.");
  }
  if (input.policySignals.broad_query_detected && input.missingAnchors.anomalyType) {
    questions.push("Уточните тип отклонения: разрыв цепочки, неверный документ или аномальный риск.");
  }
  if (input.coverageReport.clarification_needed_for.length > 0) {
    questions.push(`Закройте уточнения для требований: ${input.coverageReport.clarification_needed_for.join(", ")}.`);
  }

  return uniqueStrings(questions, 6);
}

function buildRecommendedActions(input: {
  mode: PolicyMode;
  coverageReport: RequirementCoverageReport;
  policySignals: PolicySignals;
  limitationReasonCodes: EvidenceLimitationReasonCode[];
  sourceRefs: string[];
}): string[] {
  const actions: string[] = [];
  if (input.mode === "focused_grounded") {
    actions.push("Проверьте 1-2 ключевые записи по source_ref и зафиксируйте итог в рабочем файле проверки.");
  }
  if (input.mode === "broad_partial") {
    actions.push("Сузьте запрос до периода + счета или периода + документа и повторите проверку.");
  }
  if (input.mode === "clarification_required") {
    actions.push("Дайте недостающие якоря (период/счет/объект), иначе сильный factual вывод невозможен.");
  }
  if (input.coverageReport.requirements_uncovered.length > 0) {
    actions.push(`Закройте непокрытые требования: ${input.coverageReport.requirements_uncovered.join(", ")}.`);
  }
  if (input.coverageReport.requirements_partially_covered.length > 0) {
    actions.push(`Доуточните частично покрытые требования: ${input.coverageReport.requirements_partially_covered.join(", ")}.`);
  }
  if (input.policySignals.broad_query_detected && input.policySignals.narrowing_strength !== "strong") {
    actions.push("Добавьте более узкий контекст: тип отклонения, группу документов и бизнес-участок.");
  }
  if (input.limitationReasonCodes.includes("snapshot_only")) {
    actions.push("Сверьте критичные выводы с live source-of-record в 1C.");
  }
  if (input.limitationReasonCodes.includes("weak_source_mapping")) {
    actions.push("Проверьте source mapping для связей document/register по указанным ref.");
  }
  if (input.sourceRefs.length > 0) {
    actions.push(`Начните проверку с source_ref: ${input.sourceRefs.slice(0, 2).join(", ")}.`);
  }

  return uniqueStrings(actions, 6);
}

function firstMeaningfulFact(results: UnifiedRetrievalResult[]): string | null {
  const facts = extractTopFacts(results);
  return facts.length > 0 ? facts[0] : null;
}

function buildPolicyDecision(input: {
  fallbackType: AssistantFallbackType;
  coverageReport: RequirementCoverageReport;
  groundingCheck: AnswerGroundingCheck;
  okResults: UnifiedRetrievalResult[];
  partialResults: UnifiedRetrievalResult[];
  emptyResults: UnifiedRetrievalResult[];
  errorResults: UnifiedRetrievalResult[];
  hasSupport: boolean;
  focusedStrong: boolean;
  policySignals: PolicySignals;
}): PolicyDecision {
  const hasCoverageGaps =
    input.coverageReport.requirements_uncovered.length > 0 ||
    input.coverageReport.requirements_partially_covered.length > 0 ||
    input.coverageReport.clarification_needed_for.length > 0 ||
    input.coverageReport.out_of_scope_requirements.length > 0;

  if (input.fallbackType === "out_of_scope" && input.coverageReport.requirements_covered === 0) {
    return {
      mode: "out_of_scope",
      fallback_type: "out_of_scope",
      reply_type: "out_of_scope"
    };
  }

  if (input.groundingCheck.status === "route_mismatch_blocked") {
    return {
      mode: "route_mismatch",
      fallback_type: "partial",
      reply_type: "route_mismatch_blocked"
    };
  }

  if (
    (input.policySignals.degraded_to === "clarification" && input.policySignals.minimum_evidence_failed) ||
    (input.fallbackType === "clarification" && !input.hasSupport) ||
    (input.groundingCheck.status === "no_grounded_answer" && !input.hasSupport)
  ) {
    return {
      mode: "clarification_required",
      fallback_type: "clarification",
      reply_type: "clarification_required"
    };
  }

  if (input.errorResults.length > 0 && input.okResults.length === 0 && input.partialResults.length === 0) {
    return {
      mode: "backend_error",
      fallback_type: input.fallbackType,
      reply_type: "backend_error"
    };
  }

  if (input.okResults.length === 0 && input.partialResults.length === 0 && input.emptyResults.length > 0) {
    return {
      mode: "empty",
      fallback_type: input.fallbackType,
      reply_type: "empty_but_valid"
    };
  }

  if (input.groundingCheck.status === "no_grounded_answer" && input.okResults.length === 0 && input.partialResults.length === 0) {
    return {
      mode: "no_grounded",
      fallback_type: input.fallbackType,
      reply_type: "no_grounded_answer"
    };
  }

  if (
    input.focusedStrong &&
    !input.policySignals.broad_query_detected &&
    !input.policySignals.minimum_evidence_failed &&
    !hasCoverageGaps
  ) {
    return {
      mode: "focused_grounded",
      fallback_type: "none",
      reply_type: "factual_with_explanation"
    };
  }

  if (
    input.okResults.length > 0 ||
    input.partialResults.length > 0 ||
    hasCoverageGaps ||
    input.policySignals.minimum_evidence_failed ||
    input.policySignals.broad_result_flag ||
    input.groundingCheck.status === "partial"
  ) {
    return {
      mode: "broad_partial",
      fallback_type: "partial",
      reply_type: "partial_coverage"
    };
  }

  return {
    mode: "backend_error",
    fallback_type: "unknown",
    reply_type: "backend_error"
  };
}

function buildAnswerSummary(mode: PolicyMode): string {
  if (mode === "focused_grounded") return "Сформирован прямой ответ на основе подтвержденной опоры.";
  if (mode === "broad_partial") return "Вывод ограничен: есть частичная опора, но не полный coverage.";
  if (mode === "clarification_required") return "Нужны уточнения: без сужения strong factual вывод ненадежен.";
  if (mode === "out_of_scope") return "Запрос вне доступного учетного контура.";
  if (mode === "route_mismatch") return "Результат маршрута не совпал с предметом вопроса.";
  if (mode === "empty") return "В текущем срезе данных релевантные записи не обнаружены.";
  if (mode === "no_grounded") return "Недостаточно опоры для обоснованного ответа.";
  return "Не удалось собрать обоснованный ответ по текущему запросу.";
}

function buildDirectAnswer(input: {
  mode: PolicyMode;
  retrievalResults: UnifiedRetrievalResult[];
  policySignals: PolicySignals;
}): string {
  const topFact = firstMeaningfulFact(input.retrievalResults);
  if (input.mode === "focused_grounded") {
    return topFact ?? "Подтвержденный результат получен; можно продолжать предметную проверку без деградации.";
  }
  if (input.mode === "broad_partial") {
    if (topFact) {
      return `Доступен ограниченный подтвержденный фрагмент: ${topFact}`;
    }
    return "Есть только ограниченная опора; вывод дан в частичном режиме без ложной точности.";
  }
  if (input.mode === "clarification_required") {
    return "Текущий запрос слишком широкий или недоопределен; надежный factual вывод пока невозможен.";
  }
  if (input.mode === "out_of_scope") {
    return "Могу отвечать только в пределах данных доступного учетного контура.";
  }
  if (input.mode === "route_mismatch") {
    return "Предмет результата не совпал с предметом вопроса; требуется уточнение фокуса.";
  }
  if (input.mode === "empty") {
    return "В текущем срезе данных проблемные записи по заданному условию не найдены.";
  }
  if (input.mode === "no_grounded") {
    return "Недостаточно подтвержденной опоры для ответа в требуемой точности.";
  }
  if (input.policySignals.minimum_evidence_failed) {
    return "Маршрут отработал, но минимальная evidence-опора не пройдена.";
  }
  return "Не удалось сформировать обоснованный ответ; нужно уточнение запроса.";
}

function renderPolicyReply(structure: AnswerStructureV11): string {
  const mechanismLines: string[] = [`status=${structure.mechanism_block.status}`];
  if (structure.mechanism_block.mechanism_notes.length > 0) {
    mechanismLines.push(...structure.mechanism_block.mechanism_notes.map((item) => `note: ${item}`));
  }
  if (structure.mechanism_block.limitation_reason_codes.length > 0) {
    mechanismLines.push(`limitation_codes: ${structure.mechanism_block.limitation_reason_codes.join(", ")}`);
  }
  if (structure.mechanism_block.status === "unresolved" && structure.mechanism_block.mechanism_notes.length === 0) {
    mechanismLines.push("mechanism_note is intentionally omitted due to weak or missing mechanism evidence");
  }

  const evidenceLines: string[] = [
    `coverage=${structure.evidence_block.coverage_note}`,
    `evidence_ids=${structure.evidence_block.evidence_ids.length > 0 ? structure.evidence_block.evidence_ids.join(", ") : "none"}`
  ];
  if (Array.isArray(structure.evidence_block.source_refs) && structure.evidence_block.source_refs.length > 0) {
    evidenceLines.push(`source_refs=${structure.evidence_block.source_refs.join(", ")}`);
  }
  if (Array.isArray(structure.evidence_block.claim_evidence_links) && structure.evidence_block.claim_evidence_links.length > 0) {
    const compactLinks = structure.evidence_block.claim_evidence_links
      .slice(0, 4)
      .map((item) => `${item.claim_ref}:${item.evidence_ids.join("|")}`);
    evidenceLines.push(`claim_evidence_links=${compactLinks.join("; ")}`);
  }

  const uncertaintyLines = [
    ...structure.uncertainty_block.open_uncertainties.map((item) => `open: ${item}`),
    ...structure.uncertainty_block.limitations.map((item) => `limit: ${item}`)
  ];
  if (uncertaintyLines.length === 0) {
    uncertaintyLines.push("No material uncertainty detected in current scoped answer.");
  }

  const nextStepLines = [
    ...structure.next_step_block.recommended_actions.map((item) => `action: ${item}`),
    ...structure.next_step_block.clarification_questions.map((item) => `clarify: ${item}`)
  ];
  if (nextStepLines.length === 0) {
    nextStepLines.push("No additional action is required for this scoped answer.");
  }

  return [
    `Answer summary: ${structure.answer_summary}`,
    `Direct answer:\n${structure.direct_answer}`,
    `Mechanism block:\n${formatList(mechanismLines)}`,
    `Evidence block:\n${formatList(evidenceLines)}`,
    `Uncertainty block:\n${formatList(uncertaintyLines)}`,
    `Next step block:\n${formatList(nextStepLines)}`
  ]
    .filter(Boolean)
    .join("\n\n");
}

function composeAssistantAnswerV11(input: ComposeAnswerInput): ComposeAnswerOutput {
  const fallbackType = fallbackFromSummary(input.routeSummary);
  const okResults = input.retrievalResults.filter((item) => item.status === "ok");
  const partialResults = input.retrievalResults.filter((item) => item.status === "partial");
  const emptyResults = input.retrievalResults.filter((item) => item.status === "empty");
  const errorResults = input.retrievalResults.filter((item) => item.status === "error");
  const evidenceItems = flattenEvidence(input.retrievalResults);
  const policySignals = aggregatePolicySignals(input.retrievalResults);
  const limitationReasonCodes = collectLimitationReasonCodes(evidenceItems);
  const sourceRefs = uniqueStrings(
    evidenceItems
      .map((item) => item.source_ref?.canonical_ref)
      .filter((item): item is string => typeof item === "string" && item.trim().length > 0),
    8
  );
  const mechanismNotes = uniqueStrings(
    evidenceItems
      .map((item) => item.mechanism_note)
      .filter((item): item is string => typeof item === "string" && item.trim().length > 0),
    6
  );
  const claimEvidenceLinks = buildClaimEvidenceLinks(input.retrievalResults);
  const aggregateEvidenceConfidence = aggregateConfidence(input.retrievalResults, evidenceItems);
  const hasSupport =
    okResults.length > 0 ||
    partialResults.length > 0 ||
    evidenceItems.length > 0 ||
    input.retrievalResults.some((item) => item.items.length > 0);
  const hasCoverageGaps =
    input.coverageReport.requirements_uncovered.length > 0 ||
    input.coverageReport.requirements_partially_covered.length > 0 ||
    input.coverageReport.clarification_needed_for.length > 0 ||
    input.coverageReport.out_of_scope_requirements.length > 0;
  const hasCriticalEvidenceLimitation =
    limitationReasonCodes.includes("weak_source_mapping") ||
    limitationReasonCodes.includes("insufficient_detail");
  const hasNonLowRouteConfidence = input.retrievalResults.some(
    (item) => item.status === "ok" && item.confidence !== "low"
  );
  const focusedStrong =
    okResults.length > 0 &&
    input.groundingCheck.status === "grounded" &&
    !hasCoverageGaps &&
    !policySignals.broad_query_detected &&
    !policySignals.broad_result_flag &&
    !policySignals.minimum_evidence_failed &&
    !hasCriticalEvidenceLimitation &&
    (aggregateEvidenceConfidence !== "low" || hasNonLowRouteConfidence);

  const decision = buildPolicyDecision({
    fallbackType,
    coverageReport: input.coverageReport,
    groundingCheck: input.groundingCheck,
    okResults,
    partialResults,
    emptyResults,
    errorResults,
    hasSupport,
    focusedStrong,
    policySignals
  });

  const missingAnchors = detectMissingAnchors(input.userMessage);
  const clarificationQuestions = buildClarificationQuestions({
    mode: decision.mode,
    missingAnchors,
    coverageReport: input.coverageReport,
    policySignals
  });
  const recommendedActions = buildRecommendedActions({
    mode: decision.mode,
    coverageReport: input.coverageReport,
    policySignals,
    limitationReasonCodes,
    sourceRefs
  });

  const limitations = uniqueStrings(
    [
      ...limitationReasonCodes.map((code) => limitationReasonToText(code)),
      ...extractLimitations(input.retrievalResults),
      ...input.groundingCheck.reasons,
      ...(policySignals.minimum_evidence_failed ? ["Minimum evidence gate failed for current scope."] : []),
      ...(policySignals.broad_query_detected && policySignals.narrowing_strength === "weak"
        ? ["Broad query remains weakly narrowed; precision is intentionally limited."]
        : [])
    ],
    10
  );
  const openUncertainties = uniqueStrings(
    [
      ...input.groundingCheck.missing_requirements,
      ...(decision.mode === "clarification_required" && missingAnchors.period ? ["missing_anchor:period"] : []),
      ...(decision.mode === "clarification_required" && missingAnchors.account ? ["missing_anchor:account"] : []),
      ...(decision.mode === "clarification_required" && missingAnchors.documentOrObject ? ["missing_anchor:document_or_object"] : []),
      ...(decision.mode === "clarification_required" && missingAnchors.counterparty ? ["missing_anchor:counterparty"] : [])
    ],
    8
  );

  const mechanismStatus: AnswerStructureV11["mechanism_block"]["status"] =
    mechanismNotes.length === 0
      ? "unresolved"
      : limitationReasonCodes.includes("missing_mechanism") || limitationReasonCodes.includes("heuristic_inference")
        ? "limited"
        : "grounded";

  const answerStructure: AnswerStructureV11 = {
    schema_version: "answer_structure_v1_1",
    answer_summary: buildAnswerSummary(decision.mode),
    direct_answer: buildDirectAnswer({
      mode: decision.mode,
      retrievalResults: input.retrievalResults,
      policySignals
    }),
    mechanism_block: {
      status: mechanismStatus,
      mechanism_notes: mechanismNotes,
      limitation_reason_codes: limitationReasonCodes
    },
    evidence_block: {
      evidence_ids: uniqueStrings(evidenceItems.map((item) => item.evidence_id), 10),
      source_refs: sourceRefs,
      mechanism_notes: mechanismNotes,
      coverage_note:
        input.coverageReport.requirements_total > 0 &&
        input.coverageReport.requirements_total === input.coverageReport.requirements_covered &&
        input.coverageReport.requirements_uncovered.length === 0 &&
        input.coverageReport.requirements_partially_covered.length === 0
          ? "coverage_full_or_near_full"
          : "coverage_partial_or_limited",
      ...(claimEvidenceLinks.length > 0
        ? {
            claim_evidence_links: claimEvidenceLinks
          }
        : {})
    },
    uncertainty_block: {
      open_uncertainties: openUncertainties,
      limitations
    },
    next_step_block: {
      recommended_actions: recommendedActions,
      clarification_questions: clarificationQuestions
    }
  };

  return {
    assistant_reply: renderPolicyReply(answerStructure),
    fallback_type: decision.fallback_type,
    reply_type: decision.reply_type,
    answer_structure_v11: answerStructure
  };
}

function composeExplainableAnswer(input: ComposeAnswerInput, scopeLabel: "full" | "partial"): string {
  const facts = extractTopFacts(input.retrievalResults);
  const whyIncluded = extractWhyIncluded(input.retrievalResults);
  const selectionReasons = extractSelectionReasons(input.retrievalResults);
  const riskFactors = extractRiskFactors(input.retrievalResults);
  const interpretation = extractBusinessInterpretation(input.retrievalResults);
  const limitations = uniqueStrings([...extractLimitations(input.retrievalResults), ...input.groundingCheck.reasons]);
  const nextSteps = suggestNextStep(input.requirements, input.coverageReport);

  const lead =
    scopeLabel === "full"
      ? "Итог: запрос обработан по предмету, найденные объекты подтверждены данными контура."
      : "Итог: запрос обработан частично, ниже подтвержденная часть и ограничения.";

  return [
    lead,
    facts.length > 0 ? "Подтвержденные результаты:\n" + formatList(facts) : "",
    whyIncluded.length > 0 ? "Почему это попало в ответ:\n" + formatList(whyIncluded) : "",
    selectionReasons.length > 0 ? "Основание отбора:\n" + formatList(selectionReasons) : "",
    riskFactors.length > 0 ? "Подтверждающие признаки:\n" + formatList(riskFactors) : "",
    interpretation.length > 0 ? "Практический смысл:\n" + formatList(interpretation) : "",
    limitations.length > 0 ? "Ограничения:\n" + formatList(limitations) : "",
    nextSteps.length > 0 ? "Что проверить дальше:\n" + formatList(nextSteps) : ""
  ]
    .filter(Boolean)
    .join("\n\n");
}

export function composeAssistantAnswer(input: ComposeAnswerInput): ComposeAnswerOutput {
  if (input.enableAnswerPolicyV11) {
    return composeAssistantAnswerV11(input);
  }

  const fallbackType = fallbackFromSummary(input.routeSummary);
  const okResults = input.retrievalResults.filter((item) => item.status === "ok");
  const partialResults = input.retrievalResults.filter((item) => item.status === "partial");
  const emptyResults = input.retrievalResults.filter((item) => item.status === "empty");
  const errorResults = input.retrievalResults.filter((item) => item.status === "error");
  const hasBroadMinimumEvidenceSignal = input.retrievalResults.some(
    (item) => summaryBoolean(item, "broad_guard_applied") && summaryBoolean(item, "minimum_evidence_failed")
  );
  const hasBroadClarificationSignal = input.retrievalResults.some(
    (item) =>
      summaryBoolean(item, "broad_guard_applied") &&
      summaryBoolean(item, "minimum_evidence_failed") &&
      summaryString(item, "degraded_to") === "clarification"
  );

  if (fallbackType === "out_of_scope" && input.coverageReport.requirements_covered === 0) {
    return {
      assistant_reply:
        "Я могу отвечать только по данным вашей учетной базы. Этот запрос выходит за рамки доступного контура.",
      fallback_type: "out_of_scope",
      reply_type: "out_of_scope"
    };
  }

  if (input.groundingCheck.status === "route_mismatch_blocked") {
    return {
      assistant_reply: [
        "Не отправляю финальный ответ, потому что предмет результата не совпал с предметом вопроса.",
        "Уточните формулировку (например, нужный счет/участок учета), и я выполню повторный проход."
      ].join("\n\n"),
      fallback_type: "partial",
      reply_type: "route_mismatch_blocked"
    };
  }

  if (input.groundingCheck.status === "no_grounded_answer" && okResults.length === 0 && !hasBroadMinimumEvidenceSignal) {
    return {
      assistant_reply:
        "Пока не удалось собрать предметно подтвержденный ответ по вашему вопросу. Нужны дополнительные уточнения по периоду или объекту проверки.",
      fallback_type: fallbackType,
      reply_type: "no_grounded_answer"
    };
  }

  if (hasBroadClarificationSignal && okResults.length === 0 && partialResults.length === 0) {
    return {
      assistant_reply:
        "Запрос слишком широкий для надежного вывода по текущей опоре. Уточните период, участок учета или объект проверки, после чего я дам предметный результат.",
      fallback_type: "clarification",
      reply_type: "clarification_required"
    };
  }

  if (fallbackType === "clarification" && okResults.length === 0 && partialResults.length === 0) {
    return {
      assistant_reply: "Уточните, пожалуйста, период, счет, документ или контрагента, чтобы закрыть все части вопроса корректно.",
      fallback_type: "clarification",
      reply_type: "clarification_required"
    };
  }

  if (errorResults.length > 0 && okResults.length === 0 && partialResults.length === 0) {
    return {
      assistant_reply: "Не удалось получить данные из контура. Попробуйте повторить запрос или уточнить формулировку.",
      fallback_type: fallbackType,
      reply_type: "backend_error"
    };
  }

  if (partialResults.length > 0 && okResults.length === 0) {
    return {
      assistant_reply: composeExplainableAnswer(input, "partial"),
      fallback_type: "partial",
      reply_type: "partial_coverage"
    };
  }

  if (okResults.length === 0 && partialResults.length === 0 && emptyResults.length > 0) {
    return {
      assistant_reply: "По заданному условию в текущем срезе данных явных проблемных записей не найдено.",
      fallback_type: fallbackType,
      reply_type: "empty_but_valid"
    };
  }

  const hasPartialCoverage =
    input.coverageReport.requirements_uncovered.length > 0 ||
    input.coverageReport.requirements_partially_covered.length > 0 ||
    input.coverageReport.clarification_needed_for.length > 0 ||
    input.coverageReport.out_of_scope_requirements.length > 0 ||
    input.groundingCheck.status === "partial" ||
    errorResults.length > 0;

  if (okResults.length > 0 && hasPartialCoverage) {
    return {
      assistant_reply: composeExplainableAnswer(input, "partial"),
      fallback_type: "partial",
      reply_type: "partial_coverage"
    };
  }

  if (okResults.length > 0) {
    return {
      assistant_reply: composeExplainableAnswer(input, "full"),
      fallback_type: "none",
      reply_type: "factual_with_explanation"
    };
  }

  return {
    assistant_reply: "По текущему запросу не удалось построить обоснованный ответ. Уточните формулировку и попробуйте снова.",
    fallback_type: "unknown",
    reply_type: "backend_error"
  };
}
