const crypto = require("crypto");
const { buildPrompt, buildToolResultInstruction } = require("./prompt_builder");
const {
  buildControllerDecision,
  okfLimitForController,
  outputBudgetForController
} = require("./controller");
const { roleOf } = require("./permissions");
const { parseToolCallResult } = require("./tool_router");
const { normalizeScope } = require("./scope_manager");
const { classifyRequestType } = require("./gate_provider");

class AiProvider {
  constructor({ getConfig, runtime, gate, queue, tools, metrics, getContext, lookupRepo, getRepoContext, getCorrections, ensureRuntime }) {
    Object.assign(this, { getConfig, runtime, gate, queue, tools, metrics, getContext, lookupRepo, getRepoContext, getCorrections, ensureRuntime });
  }

  async generate({
    message,
    user,
    sessionId,
    scope = "assistant",
    max_tokens,
    includeRaw = false,
    originContext = null,
    allowDeterministicShortcut = null,
    history = [],
    signal = null,
    onStage = () => {},
    allowTools = true,
    includePrompt = false
  }) {
    const requestId = crypto.randomUUID();
    const role = roleOf(user);
    const started = Date.now();
    const cfg = this.getConfig();
    const supportScope = normalizeScope(cfg.support_scope);
    let effectiveMessage = message;
    let gateDecision = null;
    let requestClass = classifyRequestType(message, { role, scope });
    let controllerDecision = null;
    const conversation = normalizeHistory(history);
    let knowledgeRetrieval = buildKnowledgeRetrievalQuery({ message, history: conversation });
    this.metrics.record({
      kind: "prompt",
      status: "received",
      request_id: requestId,
      user_id: user?.id,
      role,
      scope,
      origin: originContext?.origin || originContext?.platform || "webui",
      platform: originContext?.platform || originContext?.origin || "webui",
      prompt: redactPrompt(message),
      prompt_tokens: estimateTokens(message)
    });
    if (this.gate) {
      gateDecision = await this.gate.route({
        message,
        knowledgeQuery: knowledgeRetrieval.query,
        user,
        role,
        scope,
        originContext,
        onStage: (stage, details) => onStage(
          ["deterministic", "gating"].includes(stage) ? "gate" : stage,
          details
        )
      });
      effectiveMessage = gateDecision.message || message;
      requestClass = gateDecision.request_class || requestClass;
      knowledgeRetrieval = buildKnowledgeRetrievalQuery({ message: effectiveMessage, history: conversation });
      if (gateDecision.knowledge_match_count > 0) {
        knowledgeRetrieval = {
          ...knowledgeRetrieval,
          needs_okf: true,
          reason: knowledgeRetrieval.reason === "current_message" ? "early_okf_match" : knowledgeRetrieval.reason
        };
      }
      this.metrics.record({
        kind: "gate_summary",
        status: "success",
        request_id: requestId,
        user_id: user?.id,
        role,
        scope,
        route_used: gateDecision.route,
        confidence: gateDecision.confidence,
        reason_code: gateDecision.reason_code,
        route_class: requestClass,
        gate_error: gateDecision.gate_error || null,
        deterministic_ms: gateDecision.deterministic_ms,
        gate_ms: gateDecision.gate_ms,
        platform: originContext?.origin || originContext?.platform || "webui"
      });
      controllerDecision = buildControllerDecision({
        message: effectiveMessage,
        role,
        scope,
        originContext,
        gateDecision,
        requestClass,
        config: cfg
      });
      controllerDecision = applyKnowledgeRetrievalDecision(controllerDecision, knowledgeRetrieval);
      this.metrics.record(controllerMetricEntry({
        requestId,
        user,
        role,
        scope,
        originContext,
        decision: controllerDecision
      }));
      onStage("controller", controllerStageDetails(controllerDecision));
      if (gateDecision.answer) {
        const answer = gateDecision.answer;
        this.metrics.record({
          kind: "request",
          status: gateDecision.route === "refusal" ? "refused" : "success",
          request_id: requestId,
          user_id: user.id,
          role,
          scope,
          route_used: gateDecision.route,
          gate_confidence: gateDecision.confidence,
          gate_reason_code: gateDecision.reason_code,
          route_class: requestClass,
          max_output_tokens_used: 0,
          deterministic_ms: gateDecision.deterministic_ms,
          gate_ms: gateDecision.gate_ms,
          main_queue_ms: 0,
          main_generate_ms: 0,
          total_ms: Date.now() - started,
          duration_ms: Date.now() - started
        });
        return {
          success: gateDecision.route !== "unavailable",
          text: answer.text,
          links: answer.links || [],
          source: answer.source || null,
          model_id: cfg.gate?.model_id || "lumi-gate",
          route_used: gateDecision.route,
          route_class: requestClass,
          controller_decision: controllerDecision,
          max_output_tokens_used: 0,
          gate_decision: gateDecision,
          stage_timings: {
            deterministic_ms: gateDecision.deterministic_ms,
            gate_ms: gateDecision.gate_ms,
            main_queue_ms: 0,
            main_generate_ms: 0,
            total_ms: Date.now() - started
          },
          internal_generated_length: answer.text.length,
          duration_ms: Date.now() - started,
          queue_wait_ms: 0,
          request_id: requestId,
          feedback_context_snapshot: buildFeedbackContextSnapshot({
            contextBlocks: gateDecision.knowledge_context || [],
            correctionContext: [],
            repoContext: [],
            contextDiagnostics: gateDecision.knowledge_diagnostics ? [gateDecision.knowledge_diagnostics] : [],
            controllerDecision,
            exposedTools: [],
            selectedTool: null
          })
        };
      }
    }
    const repoAnswer = this.lookupRepo?.(effectiveMessage) || null;
    const shortcutSurfaceAllowed = scope === "assistant" || scope === "platform_command";
    const guardedRepoAnswer = ["clarification", "contact", "unknown"].includes(repoAnswer?.type);
    const verifiedRouteAnswer = isExactHelpShortcut(effectiveMessage, repoAnswer);
    if (!this.gate && shortcutSurfaceAllowed && (guardedRepoAnswer || verifiedRouteAnswer)) {
      this.metrics.record({
        kind: "request", status: "success", request_id: requestId, user_id: user.id,
        role, scope: "repo_lookup", route_used: `repo_${repoAnswer.type}`,
        route_class: requestClass, max_output_tokens_used: 0, duration_ms: Date.now() - started
      });
      return {
        success: true,
        text: repoAnswer.text,
        links: repoAnswer.links || [],
        source: repoAnswer.source || null,
        model_id: "lumi-repo-index",
        route_used: `repo_${repoAnswer.type}`,
        route_class: requestClass,
        controller_decision: buildControllerDecision({
          message: effectiveMessage,
          role,
          scope,
          originContext,
          requestClass,
          config: cfg
        }),
        max_output_tokens_used: 0,
        internal_generated_length: repoAnswer.text.length,
        duration_ms: Date.now() - started,
        queue_wait_ms: 0,
        request_id: requestId
      };
    }

    onStage("queued", {
      route: "main_llm",
      reason_code: gateDecision?.reason_code || "direct_main_llm"
    });
    if (!controllerDecision) {
      controllerDecision = buildControllerDecision({
        message: effectiveMessage,
        role,
        scope,
        originContext,
        requestClass,
        config: cfg
      });
      knowledgeRetrieval = buildKnowledgeRetrievalQuery({ message: effectiveMessage, history: conversation });
      controllerDecision = applyKnowledgeRetrievalDecision(controllerDecision, knowledgeRetrieval);
      this.metrics.record(controllerMetricEntry({
        requestId,
        user,
        role,
        scope,
        originContext,
        decision: controllerDecision
      }));
      onStage("controller", controllerStageDetails(controllerDecision));
    }
    if (this.ensureRuntime) {
      const health = await this.runtime.health();
      if (!health.healthy) {
        onStage("main_model_loading", { route: "main_llm" });
        await this.ensureRuntime();
      }
    }
    onStage("queued", { queue_position: this.queue.length + 1 });
    return this.queue.run(user.id, role, async (queueWait) => {
      const generateStarted = Date.now();
      if (signal?.aborted) throw requestCancelledError();
      const repoContext = supportScope.repo_lookup_enabled
        ? this.getRepoContext?.(effectiveMessage, role, supportScope.allow_moderator_code_help) || []
        : [];
      const correctionContext = this.getCorrections?.({
        message: effectiveMessage,
        role,
        origin: originContext?.origin || originContext?.platform || "webui",
        platform: originContext?.platform || originContext?.origin || "webui"
      }) || [];
      let toolExposure = allowTools
        ? this.tools.inspect({ role, user, context: originContext })
        : { considered: [], exposed: [] };
      toolExposure = applyKnowledgeToolPolicy(toolExposure, gateDecision);
      this.metrics.record({
        kind: "tool_exposure",
        status: allowTools ? "evaluated" : "disabled",
        request_id: requestId,
        user_id: user.id,
        role,
        origin: originContext?.origin || originContext?.platform || "other",
        considered_tools: toolExposure.considered.map((decision) => decision.tool.tool_id),
        exposed_tools: toolExposure.exposed.map((tool) => tool.tool_id),
        rejected_tools: toolExposure.considered
          .filter((decision) => !decision.exposed)
          .map((decision) => ({ tool_id: decision.tool.tool_id, reason: decision.reason }))
      });
      let contextBlocks = [];
      let contextFailure = null;
      let contextDiagnostics = [];
      const okfLimit = okfLimitForController(controllerDecision);
      try {
        const earlyBlocks = Array.isArray(gateDecision?.knowledge_context) ? gateDecision.knowledge_context : [];
        if (earlyBlocks.length && controllerDecision.okf_retrieval === "light") {
          contextBlocks = earlyBlocks;
          contextDiagnostics = gateDecision.knowledge_diagnostics ? [gateDecision.knowledge_diagnostics] : [];
        } else {
          contextBlocks = this.getContext({
            role,
            user,
            message: knowledgeRetrieval.query,
            current_message: effectiveMessage,
            retrieval_query_reason: knowledgeRetrieval.reason,
            originContext,
            scope,
            okf_retrieval: controllerDecision.okf_retrieval,
            limit: okfLimit,
            controller: controllerDecision
          }) || [];
          contextDiagnostics = Array.isArray(this.getContext?.lastDiagnostics)
            ? this.getContext.lastDiagnostics
            : [];
        }
      } catch (error) {
        contextFailure = error;
        contextBlocks = [];
      }
      const okfDiagnostics = summarizeContextDiagnostics(contextDiagnostics);
      this.metrics.record({
        kind: "okf_retrieval",
        status: contextFailure ? "failed" : "success",
        request_id: requestId,
        user_id: user.id,
        role,
        scope,
        okf_retrieval_depth: controllerDecision.okf_retrieval,
        okf_match_count: Array.isArray(contextBlocks) ? contextBlocks.length : 0,
        okf_candidate_count: okfDiagnostics.candidate_count,
        okf_query: okfDiagnostics.query || knowledgeRetrieval.query,
        okf_limit: okfLimit,
        okf_provider_count: okfDiagnostics.provider_count,
        fallback_reason: controllerDecision.fallback_used ? controllerDecision.reason_code : null,
        gate_reason_code: gateDecision?.reason_code || null,
        gate_error: gateDecision?.gate_error || null,
        error_code: contextFailure?.code || null,
        message: contextFailure?.message ? String(contextFailure.message).slice(0, 300) : null
      });
      const prompt = buildPrompt({
        config: cfg,
        role,
        message: effectiveMessage,
        requestClass,
        contextBlocks,
        correctionContext,
        repoContext,
        originContext: originContext ? {
          ...originContext,
          response_profile: controllerDecision.source_profile
        } : null,
        controllerDecision,
        tools: toolExposure.exposed
      });
      this.metrics.record({
        kind: "prompt_build",
        status: "success",
        request_id: requestId,
        user_id: user.id,
        role,
        scope,
        context_block_count: contextBlocks.length,
        correction_count: correctionContext.length,
        repo_context_count: repoContext.length,
        prompt_tokens: estimateTokens(prompt),
        controller_complexity: controllerDecision.complexity,
        okf_retrieval_depth: controllerDecision.okf_retrieval
      });
      const outputTokenLimit = resolveOutputBudget({
        config: cfg,
        requestClass,
        explicitMaxTokens: max_tokens,
        controllerDecision
      });
      const runtimeSettings = {
        backend: this.runtime.activeAcceleration?.backend || this.runtime.runtimeMetadata?.()?.backend || "cpu",
        gpu_layers: this.runtime.activeAcceleration?.gpu_layers || 0,
        context_size: Number(cfg.context_size) || 4096,
        batch_size: Number(cfg.batch_size) || 512,
        ubatch_size: Number(cfg.ubatch_size) || 128,
        threads: Number(cfg.threads) || 0,
        max_output_tokens: outputTokenLimit,
        max_output_tokens_used: outputTokenLimit,
        route_class: requestClass,
        controller_complexity: controllerDecision.complexity,
        okf_retrieval_depth: controllerDecision.okf_retrieval,
        controller_intent: controllerDecision.intent,
        source_profile: controllerDecision.source_profile.source
      };
      onStage("prompt_eval", { route: "main_llm", queue_ms: queueWait, ...runtimeSettings });
      const generatingTimer = setTimeout(() => {
        onStage("generating", { route: "main_llm", queue_ms: queueWait, ...runtimeSettings });
      }, 250);
      generatingTimer.unref?.();
      let result;
      try {
        this.metrics.record({
          kind: "model_request",
          status: "started",
          request_id: requestId,
          user_id: user.id,
          role,
          scope,
          model: cfg.selected_model_id,
          controller_complexity: controllerDecision.complexity,
          okf_retrieval_depth: controllerDecision.okf_retrieval,
          prompt_tokens: estimateTokens(prompt)
        });
        result = await this.runtime.infer(
          [
            { role: "system", content: prompt },
            ...conversation,
            { role: "user", content: effectiveMessage }
          ],
          outputTokenLimit,
          {
            signal,
            timeoutMs: cfg.hard_generation_timeout_ms
          }
        );
      } catch (error) {
        this.metrics.record({
          kind: "error",
          status: "failed",
          request_id: requestId,
          user_id: user.id,
          role,
          scope,
          error_code: error.code || error.name || "model_request_failed",
          message: String(error.message || "Main model request failed.").slice(0, 300)
        });
        throw error;
      } finally {
        clearTimeout(generatingTimer);
      }
      if (signal?.aborted) throw requestCancelledError();
      const initialText = result.choices?.[0]?.message?.content || "";
      const initialInference = normalizeInferenceDiagnostics(result, Date.now() - generateStarted);
      onStage("generating", {
        route: "main_llm",
        queue_ms: queueWait,
        ...runtimeSettings,
        ...initialInference
      });
      const parsedToolCall = allowTools ? parseToolCallResult(initialText) : { status: "none", call: null };
      const toolCall = parsedToolCall.call;
      let confirmation = null;
      let toolResult = null;
      let finalText = initialText;
      let finalResult = null;
      let finalInference = emptyInferenceDiagnostics();
      let selectedTool = null;
      let rejectedReason = null;
      let toolExecutionMs = 0;
      let toolGrounding = { removed_url_count: 0, source_appended: false };
      if (parsedToolCall.status === "malformed") {
        rejectedReason = "malformed_tool_call";
        finalText = "I could not validate the requested tool call. Please retry or clarify the request.";
        this.metrics.record({
          kind: "tool_decision",
          status: "rejected",
          request_id: requestId,
          user_id: user.id,
          origin: originContext?.origin || originContext?.platform || "other",
          rejected_reason: rejectedReason
        });
      } else if (toolCall) {
        selectedTool = toolCall.tool;
        try {
          const prepared = this.tools.prepare({
            tool: toolCall.tool,
            args: toolCall.arguments,
            user,
            role,
            sessionId,
            context: originContext
          });
          confirmation = prepared.confirmation;
          if (prepared.execute) {
            const executionStarted = Date.now();
            onStage("tool_running", { selected_tool: selectedTool });
            try {
              toolResult = await this.tools.execute({
                checked: prepared.checked,
                user,
                requestId,
                context: originContext
              });
            } catch (error) {
              toolResult = {
                status: "failed",
                error: "The selected tool failed to complete."
              };
              rejectedReason = error.code || "execution_failed";
            }
            toolExecutionMs = Date.now() - executionStarted;
            if (prepared.checked.def.read_only) {
              onStage("formatting", { selected_tool: selectedTool, tool_execution_ms: toolExecutionMs });
              const finalStarted = Date.now();
              try {
                finalResult = await this.runtime.infer(
                  [
                    { role: "system", content: prompt },
                    ...conversation,
                    { role: "user", content: effectiveMessage },
                    { role: "assistant", content: initialText },
                    {
                      role: "user",
                      content: buildToolResultInstruction({
                        tool: prepared.checked.def,
                        result: toolResult,
                        originContext
                      })
                    }
                  ],
                  outputTokenLimit,
                  {
                    signal,
                    timeoutMs: cfg.hard_generation_timeout_ms
                  }
                );
                finalInference = normalizeInferenceDiagnostics(finalResult, Date.now() - finalStarted);
                const candidate = finalResult.choices?.[0]?.message?.content || "";
                const repeatedCall = parseToolCallResult(candidate);
                if (repeatedCall.status === "none") {
                  const grounded = groundReadOnlyToolAnswer(candidate, toolResult, prepared.checked.def);
                  finalText = grounded.text;
                  toolGrounding = grounded.diagnostics;
                } else {
                  finalText = fallbackToolMessage(toolResult);
                }
              } catch {
                finalText = fallbackToolMessage(toolResult);
              }
            } else {
              finalText = safeActionResult(toolResult);
            }
          } else {
            finalText = `Please confirm: ${confirmation.display_name}.`;
          }
        } catch (error) {
          rejectedReason = error.code || "tool_rejected";
          finalText = `I could not use ${toolCall.tool}: ${safeToolError(error)}`;
        }
      }
      const inference = combineInferenceDiagnostics(initialInference, finalInference);
      const okfLinkGrounding = groundOkfLinkAnswer(finalText, contextBlocks, effectiveMessage);
      finalText = okfLinkGrounding.text;
      let fallbackReason = null;
      if (!String(finalText || "").trim()) {
        fallbackReason = "empty_model_response";
        rejectedReason = rejectedReason || fallbackReason;
        finalText = "Lumi did not return a usable answer. Please retry, rephrase, or check Work History for the failed stage.";
        this.metrics.record({
          kind: "fallback",
          status: "partial",
          request_id: requestId,
          user_id: user.id,
          role,
          scope,
          fallback_reason: fallbackReason
        });
      }
      const out = {
        success: true,
        text: finalText,
        links: okfLinkGrounding.links,
        raw_response: cfg.logging.log_responses || includeRaw
          ? finalResult ? { initial: result, final: finalResult } : result
          : null,
        raw_prompt: includePrompt ? prompt : undefined,
        tool_call: toolCall,
        tool_result: toolResult,
        confirmation,
        model_id: cfg.selected_model_id,
        duration_ms: Date.now() - started,
        queue_wait_ms: queueWait,
        finish_reason: (finalResult || result).choices?.[0]?.finish_reason || null,
        request_id: requestId,
        route_used: gateDecision ? "main_llm" : "llm",
        route_class: requestClass,
        controller_decision: controllerDecision,
        max_output_tokens_used: outputTokenLimit,
        gate_decision: gateDecision,
        force_through_reason: gateDecision?.forced ? gateDecision.reason_code : null,
        internal_generated_length: initialText.length + String(finalText || "").length,
        fallback_reason: fallbackReason,
        tool_grounding: toolGrounding,
        okf_link_grounding: okfLinkGrounding.diagnostics,
        feedback_context_snapshot: buildFeedbackContextSnapshot({
          contextBlocks,
          correctionContext,
          repoContext,
          contextDiagnostics,
          controllerDecision,
          exposedTools: toolExposure.exposed,
          selectedTool
        }),
        stage_timings: {
          deterministic_ms: gateDecision?.deterministic_ms || 0,
          gate_ms: gateDecision?.gate_ms || 0,
          queue_ms: queueWait,
          prompt_eval_ms: inference.prompt_eval_ms,
          generation_ms: inference.generation_ms,
          main_queue_ms: queueWait,
          main_generate_ms: inference.generation_ms,
          total_ms: Date.now() - started
        },
        diagnostics: {
          ...inference,
          ...runtimeSettings,
          controller_reason_code: controllerDecision.reason_code,
          controller_confidence: controllerDecision.confidence,
          source_profile: controllerDecision.source_profile,
          gate_ms: gateDecision?.gate_ms || 0,
          queue_ms: queueWait,
          total_ms: Date.now() - started
        }
      };
      this.metrics.record({
        kind: "request", status: "success", request_id: requestId, user_id: user.id, role, scope,
        model: cfg.selected_model_id, duration_ms: out.duration_ms, queue_wait_ms: queueWait,
        tool_requested: toolCall?.tool || null,
        considered_tools: toolExposure.considered.map((decision) => decision.tool.tool_id),
        exposed_tools: toolExposure.exposed.map((tool) => tool.tool_id),
        selected_tool: selectedTool,
        rejected_reason: rejectedReason,
        fallback_reason: fallbackReason,
        execution_ms: toolExecutionMs,
        removed_tool_source_urls: toolGrounding.removed_url_count,
        tool_source_appended: toolGrounding.source_appended,
        okf_link_grounded: okfLinkGrounding.diagnostics.grounded,
        tool_executed: Boolean(toolResult),
        route_used: gateDecision ? "main_llm" : "llm",
        route_class: requestClass,
        controller_route: controllerDecision.route,
        controller_intent: controllerDecision.intent,
        controller_complexity: controllerDecision.complexity,
        controller_reason_code: controllerDecision.reason_code,
        controller_confidence: controllerDecision.confidence,
        okf_retrieval_depth: controllerDecision.okf_retrieval,
        source_profile: controllerDecision.source_profile.source,
        target_final_chars: controllerDecision.source_profile.target_chars,
        hard_final_chars: controllerDecision.source_profile.hard_chars,
        controller_fallback_used: controllerDecision.fallback_used,
        max_output_tokens_used: outputTokenLimit,
        gate_confidence: gateDecision?.confidence,
        gate_reason_code: gateDecision?.reason_code,
        gate_error: gateDecision?.gate_error || null,
        force_through_reason: gateDecision?.forced ? gateDecision.reason_code : null,
        deterministic_ms: out.stage_timings.deterministic_ms,
        gate_ms: out.stage_timings.gate_ms,
        main_queue_ms: out.stage_timings.main_queue_ms,
        main_generate_ms: out.stage_timings.main_generate_ms,
        queue_ms: out.stage_timings.queue_ms,
        prompt_eval_ms: out.stage_timings.prompt_eval_ms,
        generation_ms: out.stage_timings.generation_ms,
        total_ms: out.stage_timings.total_ms,
        ...out.diagnostics,
        internal_generated_length: out.internal_generated_length
      });
      return out;
    }, { signal });
  }

  async classify({ message, labels, user }) {
    const result = await this.generate({
      message: `Classify this Lumi-related request into exactly one label: ${labels.join(", ")}. Request: ${message}`,
      user, scope: "classify", max_tokens: 40
    });
    return { ...result, label: labels.find((label) => result.text.toLowerCase().includes(label.toLowerCase())) || null };
  }

  async summarize({ text, max_length = 500, user }) {
    return this.generate({
      message: `Summarize this Lumi-related content in at most ${max_length} characters:\n${text}`,
      user, scope: "summarize", max_tokens: Math.ceil(max_length / 3)
    });
  }

  async test({ message, user, max_tokens = 300, includeRaw = false, allowTools = false, originContext = null }) {
    if (allowTools) {
      const result = await this.generate({
        message,
        user,
        sessionId: `admin-test:${user.id}:${Date.now()}`,
        scope: "model_test",
        max_tokens,
        includeRaw,
        includePrompt: true,
        originContext,
        allowTools: true
      });
      return { ...result, tools_notice: "Tools were enabled for this test." };
    }
    const requestId = crypto.randomUUID();
    const role = roleOf(user);
    const started = Date.now();
    return this.queue.run(user.id, role, async (queueWait) => {
      const cfg = this.getConfig();
      const prompt = [
        "You are Lumi Assistant, the built-in assistant for Lumi Bot, running an administrator-requested local model diagnostic.",
        "Answer the exact user message directly and concisely.",
        "Do not identify yourself as the underlying model.",
        "Do not call tools, perform actions, claim access to Lumi data, or follow requests to execute code, files, SQL, shell commands, or URLs."
      ].join("\n");
      const result = await this.runtime.infer([{ role: "system", content: prompt }, { role: "user", content: message }], max_tokens);
      const text = result.choices?.[0]?.message?.content || "";
      const output = {
        success: true, text, raw_response: includeRaw ? result : null, raw_prompt: prompt,
        tool_call: null, tool_result: null, confirmation: null, model_id: cfg.selected_model_id,
        duration_ms: Date.now() - started, queue_wait_ms: queueWait,
        finish_reason: result.choices?.[0]?.finish_reason || null, request_id: requestId,
        tools_notice: "Tools were disabled for this test; this result does not exercise tool discovery or execution."
      };
      this.metrics.record({
        kind: "request", status: "success", request_id: requestId, user_id: user.id, role,
        scope: "model_test", model: cfg.selected_model_id, duration_ms: output.duration_ms, queue_wait_ms: queueWait
      });
      return output;
    });
  }
}

function emptyInferenceDiagnostics() {
  return {
    prompt_tokens: 0,
    generated_tokens: 0,
    prompt_eval_ms: 0,
    generation_ms: 0,
    prompt_tps: 0,
    generation_tps: 0
  };
}

function combineInferenceDiagnostics(initial, final) {
  const promptTokens = Number(initial.prompt_tokens || 0) + Number(final.prompt_tokens || 0);
  const generatedTokens = Number(initial.generated_tokens || 0) + Number(final.generated_tokens || 0);
  const promptEvalMs = Number(initial.prompt_eval_ms || 0) + Number(final.prompt_eval_ms || 0);
  const generationMs = Number(initial.generation_ms || 0) + Number(final.generation_ms || 0);
  return {
    prompt_tokens: promptTokens,
    generated_tokens: generatedTokens,
    prompt_eval_ms: promptEvalMs,
    generation_ms: generationMs,
    prompt_tps: ratePerSecond(promptTokens, promptEvalMs),
    generation_tps: ratePerSecond(generatedTokens, generationMs)
  };
}

function fallbackToolMessage(result) {
  if (result?.user_message) return String(result.user_message);
  if (result?.status === "blocked") return "The requested lookup was blocked by the configured tool policy.";
  if (["failed", "unavailable"].includes(result?.status)) return "The requested tool is currently unavailable.";
  if (result?.status === "no_results") return "The tool completed but found no usable results.";
  return "The tool completed, but I could not format a final response.";
}

function groundReadOnlyToolAnswer(value, result, tool = {}) {
  let text = String(value || "").trim();
  const diagnostics = { removed_url_count: 0, source_appended: false };
  const isWebSearch = String(tool.tool_id || "").startsWith("web_search.");
  if (!isWebSearch) return { text, diagnostics };
  const sources = toolResultSources(result);
  const allowed = new Set(sources.map((source) => comparableUrl(source.url)).filter(Boolean));
  text = text.replace(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/gi, (match, label, url) => {
    if (allowed.has(comparableUrl(url))) return match;
    diagnostics.removed_url_count += 1;
    return label;
  });
  text = text.replace(/https?:\/\/[^\s<>()\]]+/gi, (match) => {
    const trimmed = match.replace(/[.,;:!?]+$/, "");
    if (allowed.has(comparableUrl(trimmed))) return match;
    diagnostics.removed_url_count += 1;
    return "[unsupported source removed]";
  });
  const usableStatus = ["ok", "partial"].includes(String(result?.status || ""));
  const alreadyAttributed = sources.some((source) => textIncludesUrl(text, source.url));
  if (usableStatus && sources.length && !alreadyAttributed) {
    const links = sources.slice(0, 2).map((source) =>
      `[${safeSourceLabel(source)}](${source.url})`
    );
    text = `${text}\n\nSources: ${links.join(", ")}`.trim();
    diagnostics.source_appended = true;
  }
  return { text, diagnostics };
}

function toolResultSources(result) {
  const sources = [];
  const seen = new Set();
  const add = (url, title) => {
    const normalized = comparableUrl(url);
    if (!normalized || seen.has(normalized)) return;
    seen.add(normalized);
    sources.push({ url: String(url), title: String(title || "") });
  };
  for (const row of Array.isArray(result?.results) ? result.results : []) add(row?.url, row?.title || row?.domain);
  for (const page of Array.isArray(result?.fetched_pages) ? result.fetched_pages : []) {
    add(page?.final_url || page?.url, page?.title);
  }
  return sources;
}

function comparableUrl(value) {
  try {
    const url = new URL(String(value || ""));
    if (!["http:", "https:"].includes(url.protocol)) return "";
    url.hash = "";
    if (url.pathname.length > 1) url.pathname = url.pathname.replace(/\/+$/, "");
    return url.href;
  } catch {
    return "";
  }
}

function textIncludesUrl(text, value) {
  const wanted = comparableUrl(value);
  if (!wanted) return false;
  return (String(text || "").match(/https?:\/\/[^\s<>()\]]+/gi) || [])
    .some((candidate) => comparableUrl(candidate.replace(/[.,;:!?]+$/, "")) === wanted);
}

function safeSourceLabel(source) {
  const candidate = String(source.title || "").replace(/[\[\]()`*_]/g, " ").replace(/\s+/g, " ").trim();
  if (candidate) return candidate.slice(0, 80);
  try { return new URL(source.url).hostname; }
  catch { return "Source"; }
}

function groundOkfLinkAnswer(value, contextBlocks = [], message = "") {
  const text = String(value || "").trim();
  const diagnostics = { requested: false, candidate_count: 0, grounded: false };
  if (!isExplicitLinkRequest(message)) return { text, links: [], diagnostics };
  diagnostics.requested = true;
  const candidates = extractOkfLinks(contextBlocks, message);
  diagnostics.candidate_count = candidates.length;
  if (!candidates.length) return { text, links: [], diagnostics };
  const plural = /\b(?:links|urls)\b/i.test(String(message || ""));
  const selected = candidates.slice(0, plural ? 3 : 1);
  const links = selected.map((candidate) => ({
    href: candidate.url,
    label: candidate.label,
    external: true
  }));
  const groundedText = selected.length === 1
    ? `Here you go: [${selected[0].label}](${selected[0].url}).`
    : `Here are the relevant links:\n${selected.map((candidate) => `- [${candidate.label}](${candidate.url})`).join("\n")}`;
  diagnostics.grounded = true;
  return { text: groundedText, links, diagnostics };
}

function isExplicitLinkRequest(message) {
  const value = String(message || "");
  return /\b(?:link|links|url|urls|invite)\b/i.test(value) &&
    /\b(?:what|where|which|give|send|share|show|find|need|want|have|has|is|are)\b/i.test(value);
}

function extractOkfLinks(contextBlocks, message) {
  const query = String(message || "").toLowerCase();
  const serviceTerms = ["discord", "twitch", "youtube", "github", "website", "homepage", "documentation", "docs"]
    .filter((term) => query.includes(term));
  const candidates = [];
  const seen = new Set();
  for (const [blockIndex, rawBlock] of (Array.isArray(contextBlocks) ? contextBlocks : []).entries()) {
    const block = String(rawBlock || "");
    const heading = block.match(/(?:^|;\s*)heading=([^;\n]+)/im)?.[1] || "";
    const blockTokens = simpleLookupTokens(`${heading} ${block.slice(0, 1800)}`);
    for (const match of block.matchAll(/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/gi)) {
      const url = comparableUrl(match[2]);
      if (!url || seen.has(url)) continue;
      seen.add(url);
      const label = String(match[1] || "Link").replace(/[\[\]()`*_]/g, " ").replace(/\s+/g, " ").trim().slice(0, 100) || "Link";
      const candidateText = `${label} ${url}`.toLowerCase();
      const matchesService = !serviceTerms.length || serviceTerms.some((term) => candidateText.includes(term));
      let score = matchesService ? 200 : 0;
      for (const token of simpleLookupTokens(query)) {
        if (candidateText.includes(token)) score += 30;
        else if (blockTokens.has(token)) score += 8;
      }
      if (heading && query.includes(heading.toLowerCase())) score += 120;
      score -= blockIndex * 5;
      candidates.push({ url, label, score, matchesService });
    }
  }
  const serviceMatches = candidates.filter((candidate) => candidate.matchesService);
  return (serviceMatches.length ? serviceMatches : candidates)
    .sort((left, right) => right.score - left.score || left.label.localeCompare(right.label));
}

function simpleLookupTokens(value) {
  return new Set(String(value || "").toLowerCase().split(/[^a-z0-9]+/)
    .filter((token) => token.length >= 3 && !["the", "what", "where", "which", "link", "links", "url", "urls", "server"].includes(token)));
}

function applyKnowledgeToolPolicy(exposure = {}, gateDecision = null) {
  const normalized = {
    considered: Array.isArray(exposure.considered) ? exposure.considered : [],
    exposed: Array.isArray(exposure.exposed) ? exposure.exposed : []
  };
  if (!gateDecision?.knowledge_sufficient) return normalized;
  return {
    ...normalized,
    considered: normalized.considered.map((decision) =>
      String(decision?.tool?.tool_id || "").startsWith("web_search.")
        ? { ...decision, exposed: false, reason: "okf_sufficient" }
        : decision
    ),
    exposed: normalized.exposed.filter((tool) => !String(tool.tool_id || "").startsWith("web_search."))
  };
}

function safeActionResult(result) {
  if (result?.user_message) return String(result.user_message);
  if (result?.status === "failed") return "The action failed.";
  return "The action completed successfully.";
}

function safeToolError(error) {
  return ({
    not_registered: "the tool is not registered.",
    permission_blocked: "permission was denied.",
    origin_blocked: "the tool is unavailable for this origin.",
    scope_blocked: "the tool is outside this request context.",
    schema_invalid: "the tool arguments were invalid."
  })[error?.code] || "the tool is unavailable.";
}

function isClearlyOutOfScope() { return false; }
function isInScope() { return true; }
function isIdentityQuery(message) {
  return /\b(who|what)\s+(are|r)\s+you\b|\byour\s+(name|identity)\b/i.test(String(message || ""));
}
function isExactHelpShortcut(message, repoAnswer) {
  if (isIdentityQuery(message) || repoAnswer?.type !== "route") return false;
  if (repoAnswer?.source?.confidence !== "high") return false;
  return /\b(where|open|find|navigate|page|screen|menu|settings?|configuration|wizard)\b/i.test(String(message || ""));
}

function buildKnowledgeRetrievalQuery({ message = "", history = [] } = {}) {
  const current = String(message || "").trim().slice(0, 6000);
  const explicitKnowledgeCue = /\b(?:okf|knowledge\s+(?:base|file)|community\s+people|community\s+knowledge|community\s+file)\b/i.test(current);
  const identityLookupCue = /^\s*who(?:'s|\s+is|\s+are)\b/i.test(current);
  const localKnowledgeCue = /\b(?:who|what|where|how|which|link|url|invite|contact)\b/i.test(current) &&
    /\b(?:lumi|okf|community|discord|twitch|youtube|streamer|admin|moderator|staff|plugin|command|overlay|bot)\b/i.test(current);
  const conversationalFollowup = /^(?:no|yes|yeah|yep|did you|have you|can you|could you|what about|how about|tell me more|more (?:info|information|details)|what(?:'s| is) (?:the )?(?:link|url))\b/i.test(current) ||
    /\b(?:this|that|it|they|them|their|he|him|his|she|her|hers|the person|the file|the source)\b/i.test(current);
  const useHistory = explicitKnowledgeCue || conversationalFollowup;
  let subject = "";
  if (useHistory) {
    const userMessages = (Array.isArray(history) ? history : [])
      .filter((entry) => entry?.role === "user")
      .map((entry) => String(entry.content || "").trim())
      .filter(Boolean)
      .reverse();
    subject = userMessages.find((entry) =>
      entry !== current && !isKnowledgeMetaFollowup(entry)
    ) || "";
  }
  const query = subject
    ? `${current}\nEarlier user subject: ${subject}`.slice(0, 1200)
    : current.slice(0, 1200);
  return {
    query,
    used_history: Boolean(subject),
    needs_okf: explicitKnowledgeCue || identityLookupCue || localKnowledgeCue || Boolean(subject),
    reason: subject
      ? "conversation_subject"
      : explicitKnowledgeCue
        ? "explicit_knowledge_reference"
        : identityLookupCue || localKnowledgeCue
          ? "local_knowledge_lookup"
          : "current_message"
  };
}

function isKnowledgeMetaFollowup(value) {
  const text = String(value || "").trim();
  return /^(?:no|yes|yeah|yep|did you|have you|can you|could you|what about|how about|tell me more|more (?:info|information|details))\b/i.test(text) ||
    /\b(?:check|look|search|find|consult)\b[\s\S]{0,40}\b(?:okf|knowledge|file|source)\b/i.test(text) ||
    /\b(?:he|him|his|she|her|hers|they|them|their|theirs|it|its|this person|that person)\b/i.test(text);
}

function applyKnowledgeRetrievalDecision(decision, retrieval) {
  if (!retrieval?.needs_okf || decision?.okf_retrieval !== "none") return decision;
  return {
    ...decision,
    okf_retrieval: "light",
    reason_code: retrieval.used_history
      ? "conversation_knowledge_followup"
      : "explicit_knowledge_reference"
  };
}

function normalizeHistory(history, maxMessages = 12, maxCharacters = 12000) {
  const rows = Array.isArray(history) ? history.slice(-maxMessages) : [];
  const output = [];
  let used = 0;
  for (let index = rows.length - 1; index >= 0; index -= 1) {
    const role = rows[index]?.role;
    const content = String(rows[index]?.content || "").trim();
    if (!["user", "assistant"].includes(role) || !content) continue;
    if (used + content.length > maxCharacters) break;
    output.unshift({ role, content });
    used += content.length;
  }
  return output;
}

function normalizeInferenceDiagnostics(result, elapsedMs = 0) {
  const timings = result?.timings || {};
  const usage = result?.usage || {};
  const promptTokens = numberValue(
    usage.prompt_tokens,
    timings.prompt_n,
    timings.prompt_tokens
  );
  const generatedTokens = numberValue(
    usage.completion_tokens,
    timings.predicted_n,
    timings.predicted_tokens
  );
  const promptEvalMs = numberValue(timings.prompt_ms, timings.prompt_eval_ms) || 0;
  const generationMs = numberValue(timings.predicted_ms, timings.generation_ms) ||
    Math.max(0, Number(elapsedMs) - promptEvalMs);
  return {
    prompt_tokens: promptTokens,
    generated_tokens: generatedTokens,
    prompt_eval_ms: promptEvalMs,
    generation_ms: generationMs,
    prompt_tps: numberValue(timings.prompt_per_second, timings.prompt_tps) ||
      ratePerSecond(promptTokens, promptEvalMs),
    generation_tps: numberValue(timings.predicted_per_second, timings.generation_tps) ||
      ratePerSecond(generatedTokens, generationMs)
  };
}

function resolveOutputBudget({ config, requestClass, explicitMaxTokens, controllerDecision } = {}) {
  return outputBudgetForController({ config, requestClass, explicitMaxTokens, controllerDecision });
}

function controllerMetricEntry({ requestId, user, role, scope, originContext, decision }) {
  return {
    kind: "controller_decision",
    status: "success",
    request_id: requestId,
    user_id: user?.id,
    role,
    scope,
    route_used: decision.route,
    controller_route: decision.route,
    controller_intent: decision.intent,
    controller_complexity: decision.complexity,
    controller_reason_code: decision.reason_code,
    controller_confidence: decision.confidence,
    okf_retrieval_depth: decision.okf_retrieval,
    answer_style: decision.answer_style,
    source_profile: decision.source_profile.source,
    target_final_chars: decision.source_profile.target_chars,
    hard_final_chars: decision.source_profile.hard_chars,
    permission_sensitive: decision.permission_sensitive,
    admin_only: decision.admin_only,
    risk_of_private_data: decision.risk_of_private_data,
    controller_fallback_used: decision.fallback_used,
    gate_reason_code: decision.gate_reason_code,
    platform: originContext?.origin || originContext?.platform || "other"
  };
}

function controllerStageDetails(decision) {
  return {
    route: decision.route,
    controller_intent: decision.intent,
    controller_complexity: decision.complexity,
    controller_reason_code: decision.reason_code,
    controller_confidence: decision.confidence,
    okf_retrieval_depth: decision.okf_retrieval,
    answer_style: decision.answer_style,
    source_profile: decision.source_profile.source,
    target_final_chars: decision.source_profile.target_chars,
    hard_final_chars: decision.source_profile.hard_chars,
    controller_fallback_used: decision.fallback_used
  };
}

function summarizeContextDiagnostics(rows = []) {
  const diagnostics = Array.isArray(rows) ? rows.filter((row) => row && typeof row === "object") : [];
  const okfRows = diagnostics.filter((row) => row.provider === "okf" || row.kind === "okf");
  const preferred = okfRows[0] || diagnostics[0] || {};
  return {
    query: preferred.query ? String(preferred.query).slice(0, 500) : null,
    candidate_count: okfRows.reduce((sum, row) => sum + (Number(row.candidate_count) || 0), 0),
    provider_count: diagnostics.length
  };
}

function numberValue(...values) {
  for (const value of values) {
    const number = Number(value);
    if (Number.isFinite(number) && number >= 0) return number;
  }
  return 0;
}

function ratePerSecond(tokens, milliseconds) {
  return tokens > 0 && milliseconds > 0
    ? Math.round((tokens / milliseconds) * 100000) / 100
    : 0;
}

function requestCancelledError() {
  return Object.assign(
    new Error("Assistant request was cancelled."),
    { name: "AbortError", code: "REQUEST_CANCELLED" }
  );
}

function estimateTokens(value) {
  const text = String(value || "");
  if (!text.trim()) return 0;
  return Math.max(1, Math.ceil(text.length / 4));
}

function redactPrompt(value) {
  return String(value || "")
    .replace(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g, "[email]")
    .replace(/\b((?:token|secret|password|api[_ -]?key|client[_ -]?secret)\s*[:=]\s*)\S+/gi, "$1[redacted]")
    .slice(0, 6000);
}

function buildFeedbackContextSnapshot({
  contextBlocks = [],
  correctionContext = [],
  repoContext = [],
  contextDiagnostics = [],
  controllerDecision = {},
  exposedTools = [],
  selectedTool = null
} = {}) {
  const retrieval = summarizeContextDiagnostics(contextDiagnostics);
  return {
    retrieval: {
      query: safeFeedbackText(retrieval.query, 500),
      candidate_count: retrieval.candidate_count,
      selected_count: Array.isArray(contextBlocks) ? contextBlocks.length : 0,
      depth: String(controllerDecision.okf_retrieval || "none").slice(0, 40)
    },
    okf: safeFeedbackList(contextBlocks, 4),
    corrections: safeFeedbackList(correctionContext, 3),
    repository: safeFeedbackList(repoContext, 3),
    tools: [...new Set([
      ...exposedTools.map((tool) => tool?.tool_id || tool?.id).filter(Boolean),
      selectedTool
    ].filter(Boolean).map((value) => String(value).slice(0, 120)))].slice(0, 20)
  };
}

function safeFeedbackList(values, limit) {
  return (Array.isArray(values) ? values : [])
    .slice(0, limit)
    .map((value) => safeFeedbackText(typeof value === "string" ? value : JSON.stringify(value), 1200))
    .filter(Boolean);
}

function safeFeedbackText(value, limit) {
  const cwd = process.cwd();
  return redactPrompt(value)
    .replaceAll(cwd, "[repo]")
    .replaceAll(cwd.replaceAll("\\", "/"), "[repo]")
    .replace(/\b[A-Za-z]:[\\/][^\r\n"']+/g, "[local path]")
    .slice(0, limit);
}

module.exports = {
  AiProvider,
  isInScope,
  isClearlyOutOfScope,
  isIdentityQuery,
  isExactHelpShortcut,
  normalizeHistory,
  normalizeInferenceDiagnostics,
  resolveOutputBudget,
  buildFeedbackContextSnapshot,
  groundReadOnlyToolAnswer,
  groundOkfLinkAnswer,
  applyKnowledgeToolPolicy,
  buildKnowledgeRetrievalQuery,
  applyKnowledgeRetrievalDecision
};
