const fs = require("fs");
const path = require("path");
const express = require("express");
const { writeJsonAtomicSync } = require("../../src/services/safe-files");
const { ensureDataDirs, resolveData } = require("./backend/paths");
const { getConfig, saveConfig, getRuntimeState } = require("./backend/config_manager");
const { detectHardware, estimateAllocation, performanceTuningHints } = require("./backend/hardware");
const metrics = require("./backend/metrics");
const { roleOf } = require("./backend/permissions");
const { canUseAssistant } = require("./backend/assistant_permissions");
const { RequestQueue } = require("./backend/queue_manager");
const { ToolRegistry } = require("./backend/tool_router");
const { DownloadManager } = require("./backend/downloader");
const { RuntimeManager, GateRuntimeManager, combinedResourceEstimate } = require("./backend/runtime_manager");
const { AiProvider, groundOkfLinkAnswer } = require("./backend/ai_provider");
const { GateProvider } = require("./backend/gate_provider");
const { SafeAnswerCache } = require("./backend/cache");
const { AssistantRequestJobs } = require("./backend/request_jobs");
const { getLatestDiagnostic, createDiagnosticsBundle } = require("./backend/diagnostics");
const { evaluateAssistantAvailability } = require("./backend/assistant_availability");
const { buildVisibilityDiagnostics } = require("./backend/assistant_visibility");
const repoIndexer = require("./backend/repo_indexer");
const { HARD_RULES, normalizeScope } = require("./backend/scope_manager");
const { AiAccessControl } = require("./backend/access_control");
const { AiRateLimiter, mergeLimits } = require("./backend/rate_limits");
const { buildOriginContext, formatPlatformReply, formatPlatformReplyDetails } = require("./backend/commands");
const { AssistantPanelDiagnostics } = require("./backend/assistant_panel_diagnostics");
const { formatAssistantResponse } = require("./backend/response_formatter");
const { FeedbackStore, FEEDBACK_KINDS, FEEDBACK_TAGS, FINAL_FEEDBACK_STATUSES, improvementAccess, isFinalized } = require("./backend/feedback");
const { sendImprovementResult, sendImprovementError } = require("./backend/improvement_http");
const { CorrectionStore, PROMOTION_TARGETS } = require("./backend/corrections");
const { EvalStore } = require("./backend/evals");
const { TrainingExporter } = require("./backend/training_export");
const { ToolRepoClient } = require("./backend/tool_repo_client");
const { ToolInstaller } = require("./backend/tool_installer");
const { ToolLoader } = require("./backend/tool_loader");
const { ToolManager } = require("./backend/tool_manager");
const { ToolSettings } = require("./backend/tool_settings");
const { buildAllowedToolsSection } = require("./backend/prompt_builder");
const storage = require("./backend/storage");
const { formatBytes, bytesFromMb, sanityCheckSize } = require("./backend/size_utils");
const { SOURCE_DEFAULTS } = require("./backend/controller");

const PLUGIN_ID = "lumi_ai";
const TOKEN_PRESETS = Object.freeze([
  { label: "Tiny (256)", value: 256, description: "Small helper replies and minimal context." },
  { label: "Very small (512)", value: 512, description: "Short replies and low memory usage." },
  { label: "Small (1024)", value: 1024, description: "Compact answers and lightweight request gates." },
  { label: "Short (2048)", value: 2048, description: "Short conversations and normal commands." },
  { label: "Medium (4096)", value: 4096, description: "Balanced default for normal assistant use." },
  { label: "Large (8192)", value: 8192, description: "Longer conversations and documents." },
  { label: "Extended (16384)", value: 16384, description: "Long context when the selected model supports it." },
  { label: "Extra extended (32768)", value: 32768, description: "Highest supported local preset for large-context models." }
]);
const CONTEXT_OPTIONS = TOKEN_PRESETS;
const GATE_CONTEXT_OPTIONS = TOKEN_PRESETS.filter((option) => option.value >= 512 && option.value <= 4096);
const modelManifest = require("./models_manifest.json");
const runtimeManifest = require("./runtime_manifest.json");

module.exports = {
  id: PLUGIN_ID,
  init({ web, settings, commandRouter, db, plugin }) {
    ensureDataDirs();
    if (!repoIndexer.loadIndex()) {
      try { repoIndexer.refreshIndex(); }
      catch (error) { console.warn("Lumi AI repository index initialization failed", error.message); }
    }
    let config = getConfig();
    metrics.configureRetention(config.work_history_retention);
    const feedbackStore = new FeedbackStore();
    const correctionStore = new CorrectionStore({
      getConfig: () => config,
      verifyLink: isVerifiedImprovementLink
    });
    const getModel = (id) => modelManifest.models.find((model) => model.id === id);
    const downloads = new DownloadManager((entry) => metrics.record(entry));
    const accessControl = new AiAccessControl((entry) => metrics.record(entry));
    const rateLimiter = new AiRateLimiter(() => config, (entry) => metrics.record(entry));
    const panelTemplatePath = path.join(__dirname, "views", "assistant-panel.ejs");
    const panelDiagnostics = new AssistantPanelDiagnostics(panelTemplatePath);
    panelDiagnostics.templateCheck(
      ["endpoint", "user"],
      { endpoint: `/plugins/${PLUGIN_ID}`, user: { id: "diagnostic-user" } }
    );
    const queue = new RequestQueue(() => config);
    const requestJobs = new AssistantRequestJobs();
    const tools = new ToolRegistry((entry) => metrics.record(entry));
    const toolRepoClient = new ToolRepoClient({ settings });
    const toolInstaller = new ToolInstaller({ repoClient: toolRepoClient });
    const toolLoader = new ToolLoader({
      registry: tools,
      installer: toolInstaller,
      settings,
      lumiAiVersion: require("./plugin.json").version,
      lumiVersion: require("../../package.json").version
    });
    const toolSettings = new ToolSettings({ installer: toolInstaller });
    const toolManager = new ToolManager({
      repoClient: toolRepoClient,
      installer: toolInstaller,
      loader: toolLoader,
      settings: toolSettings
    });
    const contextProviders = new Map();
    const frontendVisibility = new Map();
    const getSafeContext = (input = {}) => {
      const context = typeof input === "string" ? { role: input } : input;
      const diagnostics = [];
      const blocks = [];
      getSafeContext.lastDiagnostics = diagnostics;
      getSafeContext.lastErrors = [];
      for (const fn of contextProviders.values()) {
        try {
          const result = fn(context);
          if (result && typeof result === "object" && !Array.isArray(result)) {
            if (result.diagnostics) diagnostics.push(safeContextDiagnostic(result.diagnostics));
            blocks.push(...normalizeContext(result.blocks ?? result.context ?? result.items ?? []));
          } else {
            blocks.push(...normalizeContext(result));
          }
        } catch (error) {
          getSafeContext.lastErrors.push(String(error.message || error).slice(0, 300));
        }
      }
      return blocks;
    };
    getSafeContext.lastDiagnostics = [];
    getSafeContext.lastErrors = [];
    const getEarlyOkfContext = (input = {}) => {
      const provider = contextProviders.get("okf") || global.lumiFrameworks?.okf?.context;
      if (typeof provider !== "function") return { blocks: [], diagnostics: null };
      try {
        const result = provider(input);
        if (result && typeof result === "object" && !Array.isArray(result)) {
          return {
            blocks: normalizeContext(result.blocks ?? result.context ?? result.items ?? []),
            diagnostics: result.diagnostics ? safeContextDiagnostic(result.diagnostics) : null
          };
        }
        return { blocks: normalizeContext(result), diagnostics: null };
      } catch (error) {
        return {
          blocks: [],
          diagnostics: { provider: "okf", kind: "okf", reason: "early_lookup_failed", error: String(error.message || error).slice(0, 300) }
        };
      }
    };
    const runtime = new RuntimeManager({
      getConfig: () => config,
      getModel,
      runtimeManifest,
      onCrash: (message) => metrics.record({ kind: "runtime", status: "failed", runtime_crash: true, message }),
      onDiagnostic: (entry) => metrics.record(entry)
    });
    const gateRuntime = new GateRuntimeManager({
      getConfig: () => config,
      getModel,
      onDiagnostic: (entry) => metrics.record(entry)
    });
    const gate = new GateProvider({
      getConfig: () => config,
      runtime: gateRuntime,
      lookupRepo: (message) => repoIndexer.lookupSupport(message),
      lookupCorrection: (context) => correctionStore.findPredefined({
        message: context.message,
        role: context.role,
        origin: context.origin || context.platform,
        platform: context.platform
      }),
      lookupKnowledge: (context) => getEarlyOkfContext({
        message: context.knowledge_query || context.message,
        current_message: context.message,
        user: context.user,
        role: context.role,
        originContext: context.originContext,
        scope: context.scope,
        okf_retrieval: "light",
        limit: 2
      }),
      groundKnowledgeLinks: groundOkfLinkAnswer,
      cache: new SafeAnswerCache(() => config),
      metrics
    });
    let mainStartPromise = null;
    let gateStartPromise = null;
    const ensureMainRuntime = async (options = {}) => {
      const health = await runtime.health();
      if (health.healthy) return health;
      if (!mainStartPromise) {
        mainStartPromise = runtime.start(options).finally(() => { mainStartPromise = null; });
      }
      return mainStartPromise;
    };
    const ensureGateRuntime = async () => {
      if (gateRuntime.status().state === "running") return gateRuntime.health();
      if (!gateStartPromise) {
        gateStartPromise = gateRuntime.start().finally(() => { gateStartPromise = null; });
      }
      return gateStartPromise;
    };
    const provider = new AiProvider({
      getConfig: () => config,
      runtime,
      gate,
      queue,
      tools,
      metrics,
      getContext: getSafeContext,
      lookupRepo: (message) => repoIndexer.lookupSupport(message),
      getRepoContext: (message, role, allowModeratorCodeHelp) =>
        repoIndexer.supportContext(message, repoIndexer.loadIndex(), 8, role, allowModeratorCodeHelp),
      getCorrections: (context) => correctionStore.context(context),
      ensureRuntime: ensureMainRuntime
    });
    const evalStore = new EvalStore({ provider });
    const trainingExporter = new TrainingExporter({
      feedback: feedbackStore,
      corrections: correctionStore
    });
    const startRuntimes = async (options = {}) => {
      if (config.enabled) {
        ensureGateRuntime().catch((error) => {
          metrics.record({ kind: "gate_runtime", status: "failed", reason_code: "gate_start_failed", message: error.message });
          web.emitEvent?.("ai:model_status", {
            status: "gate_start_failed",
            message: `Lumi AI gate runtime failed to start: ${error.message}`
          }, { role: "admin" });
        });
      }
      const main = await ensureMainRuntime(options);
      return { ...main, gate: await gateRuntime.health() };
    };
    const stopRuntimes = async (options = {}) => {
      await gateRuntime.stop();
      return runtime.stop(options);
    };
    const restartRuntimes = async () => {
      await gateRuntime.stop();
      const main = await runtime.restart();
      if (config.enabled) {
        try { await ensureGateRuntime(); }
        catch (error) {
          metrics.record({ kind: "gate_runtime", status: "failed", reason_code: "gate_restart_failed", message: error.message });
          web.emitEvent?.("ai:model_status", {
            status: "gate_restart_failed",
            message: `Lumi AI gate runtime restart failed: ${error.message}`
          }, { role: "admin" });
        }
      }
      return { ...main, gate: await gateRuntime.health() };
    };
    let gateRecoveryPending = false;
    let runtimeInstallInProgress = false;
    const gateMonitor = setInterval(async () => {
      if (
        gateRecoveryPending ||
        runtimeInstallInProgress ||
        !config.enabled ||
        gateRuntime.status().state === "running"
      ) return;
      gateRecoveryPending = true;
      try { await ensureGateRuntime(); }
      catch (error) {
        web.emitEvent?.("ai:model_status", {
          status: "gate_recovery_failed",
          message: `Lumi AI gate recovery failed: ${error.message}`
        }, { role: "admin" });
      }
      finally { gateRecoveryPending = false; }
    }, 30000);
    gateMonitor.unref?.();

    const api = {
      health: () => runtime.health(),
      capabilities: () => ({
        provider: "local_llama_cpp",
        enabled: config.enabled,
        model_id: config.selected_model_id,
        roles: config.assistant_visibility,
        tools: tools.list("admin").map((tool) => tool.tool_id)
      }),
      metrics_summary: () => metrics.report(),
      generate: (input) => provider.generate(input),
      classify: (input) => provider.classify(input),
      summarize: (input) => provider.summarize(input),
      route_tool: async ({ message, allowed_tools = [], ...input }) => {
        const result = await provider.generate({ message, ...input, scope: "route_tool" });
        if (result.tool_call && !allowed_tools.includes(result.tool_call.tool)) {
          return { ...result, success: false, tool_call: null, refusal_reason: "tool_not_allowed" };
        }
        return result;
      },
      registerTool: (definition) => tools.register(definition),
      unregisterToolOwner: (owner) => tools.unregisterOwner(owner),
      registerContext: (id, factory) => {
        if (!id || typeof factory !== "function") throw new Error("Invalid AI context provider.");
        contextProviders.set(id, factory);
      },
      unregisterContext: (id) => contextProviders.delete(id)
    };
    global.lumiFrameworks = global.lumiFrameworks || {};
    global.lumiFrameworks.ai = api;
    global.lumiFrameworks.lumi_ai = api;
    if (typeof global.lumiFrameworks.okf?.context === "function") {
      api.registerContext("okf", global.lumiFrameworks.okf.context);
    }

    const router = web.createRouter();
    router.use("/assets", express.static(path.join(__dirname, "public")));

    router.get("/", async (req, res) => {
      if (!req.session.user?.isAdmin) return denied(res);
      const hardware = detectHardware(modelManifest.models, runtimeManifest);
      const runtimeTarget = getRuntimeTarget(hardware);
      const selectedModel = getModel(config.selected_model_id);
      const runtimeStatus = await runtime.health();
      const gateStatus = await gateRuntime.health();
      const gpuAllocation = estimateAllocation({
        model: selectedModel,
        contextSize: config.context_size,
        gpu: hardware.gpu,
        backend: runtimeStatus.runtime_backend,
        intentPercent: config.gpu_allocation_intent_percent,
        managedUsageMb: runtimeStatus.state === "running" ? runtimeStatus.estimated_gpu_memory_mb : 0
      });
      const assistantAvailability = evaluateAssistantAvailability({
        user: req.session.user,
        config,
        model: selectedModel,
        runtimeHealth: runtimeStatus,
        providerAvailable: Boolean(provider)
      });
      const visibilityDiagnostics = buildVisibilityDiagnostics({
        user: req.session.user,
        config,
        model: selectedModel,
        runtimeHealth: runtimeStatus,
        providerAvailable: Boolean(provider),
        frontend: frontendVisibility.get(req.session.user.id)
      });
      const usage = storage.storageUsage(modelManifest.models, config.selected_model_id);
      const resourceEstimate = combinedResourceEstimate({
        main: runtimeStatus,
        gate: gateStatus,
        hardware
      });
      const recentGeneration = metrics.history(100).find((entry) =>
        entry.kind === "request" && Number(entry.generation_tps) > 0
      );
      const tuningHints = performanceTuningHints({
        model: selectedModel,
        config,
        gpu: hardware.gpu,
        allocation: gpuAllocation,
        generationTps: Number(recentGeneration?.generation_tps) || 0
      });
      const workHistoryPage = metrics.workHistoryPage(req.query, req.query.work_page, 20);
      const metricsPage = metrics.historyPage(req.query.metrics_page, 25);
      const slowRequestsPage = metrics.slowRequestsPage(req.query.slow_page, 15);
      const accessPage = paginateRows(accessControl.list(), req.query.access_page, 25);
      const logPage = storage.listLogsPage(req.query.logs_page, 25);
      const runtimeFolderSize = usage.categories.runtime;
      const selectedModelPath = selectedModel ? storage.modelPath(selectedModel) : null;
      const modelFileSize = selectedModelPath && fs.existsSync(selectedModelPath)
        ? fs.statSync(selectedModelPath).size
        : 0;
      const runtimeDownloadSize = runtimeTarget
        ? Number(runtimeTarget.size || 0) + (runtimeTarget.dependencies || []).reduce((sum, dependency) => sum + Number(dependency.size || 0), 0)
        : 0;
      const sizeDiagnostics = [
        sanityCheckSize("Runtime folder", runtimeFolderSize, 5 * 1024 ** 3),
        sanityCheckSize("Runtime archive", runtimeDownloadSize, 2 * 1024 ** 3),
        sanityCheckSize("Selected model", selectedModel?.size || 0, 100 * 1024 ** 3),
        sanityCheckSize("Installed model", modelFileSize, 100 * 1024 ** 3),
        sanityCheckSize("Estimated GPU memory", bytesFromMb(gpuAllocation.estimated_gpu_memory_mb), 100 * 1024 ** 3)
      ].filter((check) => !check.valid);
      for (const diagnostic of sizeDiagnostics) console.warn(`Lumi AI size diagnostic: ${diagnostic.message}`);
      const models = modelManifest.models.map((model) => ({
        ...model,
        downloaded: fs.existsSync(resolveData("models", model.filename)),
        installed_size: fs.existsSync(resolveData("models", model.filename))
          ? fs.statSync(resolveData("models", model.filename)).size
          : 0,
        compatible: model.ram_gb * 1024 <= hardware.total_ram_mb && model.size / 1048576 <= hardware.free_disk_mb
      }));
      const installedModels = models.filter((model) => model.downloaded);
      res.render(path.join(__dirname, "views", "settings.ejs"), {
        title: "Lumi AI",
        config,
        models,
        installedModels,
        selectedModelInstalled: installedModels.some((model) => model.id === config.selected_model_id),
        contextOptions: CONTEXT_OPTIONS,
        tokenPresets: TOKEN_PRESETS,
        gateContextOptions: GATE_CONTEXT_OPTIONS,
        runtimeTarget,
        runtimeDownloadSize,
        runtimeManifest,
        runtimeStatus,
        gateStatus,
        resourceEstimate,
        tuningHints,
        jobDiagnostics: requestJobs.diagnostics(),
        gpuAllocation,
        assistantAvailability,
        visibilityDiagnostics,
        panelDiagnostics: panelDiagnostics.snapshot(),
        activeAiRestrictions: accessPage.entries,
        accessPage,
        recentRateLimitDenials: rateLimiter.recentDenials(),
        hardRules: HARD_RULES,
        assistantReason: describeAssistantReason(assistantAvailability.reason_code),
        repoIndexStatus: repoIndexer.indexStatus(),
        runtimeState: getRuntimeState(),
        latestDiagnostic: getLatestDiagnostic(),
        runtimeFolderSize,
        modelFileSize,
        storageUsage: usage,
        sizeDiagnostics,
        hardware,
        metrics: metrics.report(),
        workHistoryPage,
        workFilters: req.query,
        history: metricsPage.entries,
        metricsPage,
        slowRequestsPage,
        logFiles: logPage.entries,
        logPage,
        formatBytes,
        formatDate,
        formatDuration
      });
    });

    router.post("/settings", async (req, res) => {
      if (!req.session.user?.isAdmin) return denied(res);
      const model = getModel(req.body.selected_model_id);
      if (!model) return flash(req, res, "error", "Unknown model.");
      if (!fs.existsSync(resolveData("models", model.filename))) {
        return flash(req, res, "error", "Selected model must be installed before it can be saved.");
      }
      const contextValues = CONTEXT_OPTIONS.map((option) => option.value);
      const requestedContext = Number(req.body.context_size);
      if (!contextValues.includes(requestedContext)) {
        return flash(req, res, "error", "Choose a supported AI context size.");
      }
      const contextSize = requestedContext;
      const gateContextValues = GATE_CONTEXT_OPTIONS.map((option) => option.value);
      const requestedGateContext = Number(req.body.gate_context_size);
      if (!gateContextValues.includes(requestedGateContext)) {
        return flash(req, res, "error", "Choose a supported gate context size.");
      }
      const previousConfig = config;
      let nextConfig;
      try {
        nextConfig = {
        ...config,
        enabled: req.body.enabled === "on",
        selected_model_id: model.id,
        context_size: contextSize,
        internal_generation_char_budget: boundedInt(req.body.internal_generation_char_budget, 2000, 64000, 16000),
        threads: boundedInt(req.body.threads, 0, 256, 0),
        gpu_allocation_intent_percent: boundedInt(req.body.gpu_allocation_intent_percent, 0, 100, 0),
        concurrency: boundedInt(req.body.concurrency, 1, 8, 1),
        max_queue_length: boundedInt(req.body.max_queue_length, 1, 100, 8),
        request_timeout_ms: boundedInt(req.body.hard_generation_timeout_ms, 30000, 3600000, 600000),
        ui_soft_timeout_ms: boundedInt(req.body.ui_soft_timeout_ms, 5000, 300000, 45000),
        hard_generation_timeout_ms: boundedInt(req.body.hard_generation_timeout_ms, 30000, 3600000, 600000),
        source_profiles: sourceProfilesFromBody(req.body, config.source_profiles),
        batch_size: boundedInt(req.body.batch_size, 32, 4096, 512),
        ubatch_size: boundedInt(req.body.ubatch_size, 16, 4096, 128),
        per_user_requests_per_minute: boundedInt(req.body.per_user_requests_per_minute, 1, 120, 6),
        admin_bypass_rate_limit: req.body.admin_bypass_rate_limit === "on",
        assistant_enabled: req.body.assistant_enabled === "on",
        assistant_debug_logging: req.body.assistant_debug_logging === "on",
        assistant_visibility: {
          admins: req.body.visibility_admins === "on",
          mods: req.body.visibility_mods === "on",
          users: req.body.visibility_users === "on"
        },
        work_history_retention: {
          mode: req.body.work_history_retention_mode === "age" ? "age" : "count",
          count: boundedInt(req.body.work_history_retention_count, 50, 10000, 500),
          age_value: boundedInt(req.body.work_history_retention_age_value, 1, 1000, 30),
          age_unit: ["hours", "days", "weeks", "months", "years"].includes(req.body.work_history_retention_age_unit)
            ? req.body.work_history_retention_age_unit
            : "days"
        },
        commands: {
          enabled: req.body.command_enabled === "on",
          triggers: parseCommandTriggers(req.body.command_triggers),
          platforms: {
            discord: req.body.command_platform_discord === "on",
            twitch: req.body.command_platform_twitch === "on",
            youtube: req.body.command_platform_youtube === "on",
            kick: req.body.command_platform_kick === "on",
            other: req.body.command_platform_other === "on"
          },
          roles: {
            admins: req.body.command_role_admins === "on",
            mods: req.body.command_role_mods === "on",
            users: req.body.command_role_users === "on"
          },
          unavailable_message: cleanText(req.body.command_unavailable_message, 500),
          denied_message: cleanText(req.body.command_denied_message, 500)
        },
        rate_limits: mergeLimits({
          roles: {
            admin: limitFromBody(req.body, "limit_role_admin", 30, 60),
            mod: limitFromBody(req.body, "limit_role_mod", 12, 60),
            user: limitFromBody(req.body, "limit_role_user", 4, 60)
          },
          platforms: {
            webui: limitFromBody(req.body, "limit_platform_webui", 60, 60),
            discord: limitFromBody(req.body, "limit_platform_discord", 30, 60),
            twitch: limitFromBody(req.body, "limit_platform_twitch", 20, 60),
            youtube: limitFromBody(req.body, "limit_platform_youtube", 20, 60),
            kick: limitFromBody(req.body, "limit_platform_kick", 20, 60),
            other: limitFromBody(req.body, "limit_platform_other", 20, 60)
          },
          per_user: limitFromBody(req.body, "limit_user", 2, 30),
          per_channel: limitFromBody(req.body, "limit_channel", 12, 60),
          queue_when_limited: req.body.queue_when_limited === "on"
        }),
        gate: {
          ...config.gate,
          model_id: getModel(req.body.gate_model_id)?.id || config.gate.model_id,
          context_size: requestedGateContext,
          threads: boundedInt(req.body.gate_threads, 1, 16, 2),
          timeout_ms: boundedInt(req.body.gate_timeout_ms, 1000, 5000, 3000),
          high_confidence_threshold: boundedNumber(req.body.gate_high_confidence_threshold, 0.5, 0.99, 0.88),
          main_llm_threshold: boundedNumber(req.body.gate_main_llm_threshold, 0.1, 0.95, 0.72),
          predefined_enabled: req.body.gate_predefined_enabled === "on",
          cache_ttl_seconds: boundedInt(req.body.gate_cache_ttl_seconds, 30, 604800, 3600),
          repeat_force_window_seconds: boundedInt(req.body.gate_repeat_force_window_seconds, 0, 3600, 90),
          similarity_threshold: boundedNumber(req.body.gate_similarity_threshold, 0.5, 1, 0.86),
          force_prefix: cleanText(req.body.gate_force_prefix, 40)
        },
        support_scope: normalizeScope({
          allowed_topics: cleanText(req.body.allowed_topics, 3000),
          allowed_support_domains: cleanText(req.body.allowed_support_domains, 3000),
          answer_style: cleanText(req.body.answer_style, 2000),
          linking_behavior: cleanText(req.body.linking_behavior, 2000),
          repo_lookup_enabled: req.body.repo_lookup_enabled === "on",
          allow_deterministic_help_shortcuts: req.body.allow_deterministic_help_shortcuts === "on",
          allow_moderator_code_help: req.body.allow_moderator_code_help === "on",
          clarification_behavior: cleanText(req.body.clarification_behavior, 2000),
          max_answer_length: boundedInt(req.body.maximum_answer_length, 100, 4000, 4000),
          role_overrides: {
            admin: cleanText(req.body.scope_admin, 3000),
            mod: cleanText(req.body.scope_mod, 3000),
            user: cleanText(req.body.scope_user, 3000)
          }
        }),
        instructions: {
          out_of_scope_response: cleanText(req.body.out_of_scope_response, 1000),
          roleplay_intensity: boundedInt(req.body.roleplay_intensity, 0, 10, 0),
          community_tone: cleanText(req.body.community_tone, 2000),
          admin_custom: cleanText(req.body.admin_custom, 6000)
        },
        logging: {
          log_prompts: req.body.log_prompts === "on",
          log_responses: req.body.log_responses === "on",
          log_tool_calls: req.body.log_tool_calls === "on",
          log_metrics: req.body.log_metrics === "on",
          log_internal_audit: req.body.log_internal_audit === "on"
        },
        improvement: {
          ...config.improvement,
          allow_moderators_to_review_responses: req.body.allow_moderators_to_review_responses === "on",
          trusted_moderator_reviewers: parseIdList(req.body.trusted_moderator_reviewers),
          corrections_enabled: req.body.corrections_enabled === "on"
        }
      };
      } catch (error) {
        return flash(req, res, "error", error.message);
      }
      config = saveConfig(nextConfig);
      metrics.configureRetention(config.work_history_retention);
      registerAssistantCommands({
        commandRouter,
        provider,
        runtime,
        getConfig: () => config,
        accessControl,
        rateLimiter,
        metrics,
        getBaseUrl: () => configuredBaseUrl(settings)
      });
      writeCommandsManifest(plugin?.dir || __dirname, config);
      const runtimeSettingsChanged = [
        "selected_model_id",
        "context_size",
        "threads",
        "batch_size",
        "ubatch_size",
        "gpu_allocation_intent_percent"
      ].some((key) => previousConfig[key] !== config[key]);
      const gateSettingsChanged = JSON.stringify(previousConfig.gate) !== JSON.stringify(config.gate);
      const enabledChanged = previousConfig.enabled !== config.enabled;
      if ((runtimeSettingsChanged || gateSettingsChanged || enabledChanged) && runtime.status().state === "running") {
        try {
          await restartRuntimes();
        } catch (error) {
          return flash(req, res, "error", `Settings saved, but runtime restart failed: ${error.message}`);
        }
      } else if (!config.enabled && gateRuntime.status().state === "running") {
        await gateRuntime.stop();
      }
      return flash(req, res, "success", "Lumi AI settings saved.");
    });

    router.post("/download/runtime", (req, res) => {
      if (!req.session.user?.isAdmin) return denied(res);
      const wantsJson = req.accepts(["json", "html"]) === "json";
      const hardware = detectHardware(modelManifest.models, runtimeManifest);
      const target = getRuntimeTarget(hardware);
      if (!target) {
        if (wantsJson) return res.status(400).json({ error: "No managed llama.cpp runtime is available for this platform." });
        return flash(req, res, "error", "No managed llama.cpp runtime is available for this platform.");
      }
      try {
        const job = downloads.start({
          id: "runtime",
          ...target,
          kind: "runtime",
          archive: true,
          runtimeMetadata: {
            backend: target.backend || "cpu",
            backend_variant: target.backend_variant || null,
            version: runtimeManifest.version,
            target: target.filename,
            dependencies: Array.isArray(target.dependencies)
              ? target.dependencies.map((dependency) => ({
                  filename: dependency.filename,
                  sha256: dependency.sha256,
                  size: dependency.size || 0
                }))
              : []
          },
          beforeInstall: async () => {
            runtimeInstallInProgress = true;
            await stopRuntimes({ manual: true, reason: "runtime_reinstall" });
          },
          afterInstall: async () => {
            runtimeInstallInProgress = false;
          }
        });
        if (wantsJson) return res.json({ success: true, job });
        return flash(req, res, "success", `${String(target.backend || "CPU").toUpperCase()} runtime download started. Lumi AI runtimes will stop when the new runtime is ready to install; start them again after the download completes.`);
      } catch (error) {
        if (wantsJson) return res.status(400).json({ error: error.message });
        return flash(req, res, "error", error.message);
      }
    });

    router.post("/download/model/:id", (req, res) => {
      if (!req.session.user?.isAdmin) return denied(res);
      const wantsJson = req.accepts(["json", "html"]) === "json";
      const model = getModel(req.params.id);
      if (!model) {
        if (wantsJson) return res.status(404).json({ error: "Unknown model." });
        return flash(req, res, "error", "Unknown model.");
      }
      if (
        (model.id === config.selected_model_id && runtime.status().state === "running") ||
        (model.id === config.gate.model_id && gateRuntime.status().state === "running")
      ) {
        if (wantsJson) return res.status(400).json({ error: "Stop the AI runtimes before replacing an active model." });
        return flash(req, res, "error", "Stop the AI runtimes before replacing an active model.");
      }
      const hardware = detectHardware(modelManifest.models);
      const incompatible = model.ram_gb * 1024 > hardware.total_ram_mb || model.size / 1048576 > hardware.free_disk_mb;
      if (incompatible && req.body.override_compatibility !== "on") {
        if (wantsJson) return res.status(400).json({ error: "This model exceeds detected RAM or free disk. Check override to download anyway." });
        return flash(req, res, "error", "This model exceeds detected RAM or free disk. Check override to download anyway.");
      }
      try {
        const job = downloads.start({
          id: `model:${model.id}`,
          url: `https://huggingface.co/${model.repo}/resolve/${model.revision}/${model.filename}`,
          filename: model.filename,
          sha256: model.sha256,
          size: model.size,
          kind: "model"
        });
        if (wantsJson) return res.json({ success: true, job });
        return flash(req, res, "success", `${model.label} download started.`);
      } catch (error) {
        if (wantsJson) return res.status(400).json({ error: error.message });
        return flash(req, res, "error", error.message);
      }
    });

    router.get("/api/status", async (req, res) => {
      const permission = canUseAssistant({
        user: req.session.user,
        config,
        origin: "webui",
        platform: "webui",
        requestedSurface: "webui_chat"
      });
      if (!permission.allowed) return res.status(403).json({ error: "Access denied.", reason: permission.reason });
      res.json({
        runtime: await runtime.health(),
        gate: await gateRuntime.health(),
        queue_length: queue.length,
        enabled: config.enabled,
        model_id: config.selected_model_id
      });
    });
    router.get("/api/downloads", (req, res) => {
      if (!req.session.user?.isAdmin) return res.status(403).json({ error: "Access denied." });
      res.json(Object.fromEntries(downloads.jobs));
    });
    router.get("/api/gpu-capacity", (req, res) => {
      if (!req.session.user?.isAdmin) return res.status(403).json({ error: "Access denied." });
      const model = getModel(req.query.model_id || config.selected_model_id);
      if (!model) return res.status(400).json({ error: "Unknown model." });
      const contextSize = boundedInt(req.query.context_size, 512, 131072, config.context_size);
      const hardware = detectHardware(modelManifest.models, runtimeManifest);
      const installedBackend = runtime.runtimeMetadata().backend;
      res.json(estimateAllocation({
        model,
        contextSize,
        gpu: hardware.gpu,
        backend: installedBackend,
        intentPercent: req.query.intent_percent ?? config.gpu_allocation_intent_percent,
        managedUsageMb: runtime.activeAcceleration?.estimated_gpu_memory_mb || 0
      }));
    });
    router.get("/api/assistant-diagnostics", async (req, res) => {
      if (!req.session.user?.isAdmin) return res.status(403).json({ error: "Access denied." });
      const availability = evaluateAssistantAvailability({
        user: req.session.user,
        config,
        model: getModel(config.selected_model_id),
        runtimeHealth: await runtime.health(),
        providerAvailable: Boolean(provider)
      });
      res.json({ ...availability, reason: describeAssistantReason(availability.reason_code) });
    });
    const visibilityDebugHandler = async (req, res) => {
      if (!req.session.user?.isAdmin) return res.status(403).json({ error: "Access denied." });
      const runtimeHealth = await runtime.health();
      const diagnostics = buildVisibilityDiagnostics({
        user: req.session.user,
        config,
        model: getModel(config.selected_model_id),
        runtimeHealth,
        providerAvailable: Boolean(provider),
        frontend: frontendVisibility.get(req.session.user.id)
      });
      res.set("Cache-Control", "no-store");
      return res.json({
        ...diagnostics,
        reason: describeAssistantReason(diagnostics.reason_code),
        frontend_reported: frontendVisibility.has(req.session.user.id),
        ...panelDiagnostics.snapshot()
      });
    };
    const visibilityReportHandler = (req, res) => {
      if (!req.session.user?.isAdmin) return res.status(403).json({ error: "Access denied." });
      frontendVisibility.set(req.session.user.id, {
        assistant_slot_found: Boolean(req.body.assistant_slot_found),
        frontend_loader_loaded: Boolean(req.body.frontend_loader_loaded),
        panel_html_returned: Boolean(req.body.panel_html_returned),
        mount_successful: Boolean(req.body.mount_successful),
        reported_at: new Date().toISOString()
      });
      panelDiagnostics.frontend(req.body);
      return res.json({ success: true });
    };
    router.get("/api/assistant/visibility-debug", visibilityDebugHandler);
    router.post("/api/assistant/visibility-debug", visibilityReportHandler);
    if (typeof web.addRoute === "function") {
      web.addRoute("get", "/api/lumi-ai/assistant/visibility-debug", visibilityDebugHandler);
      web.addRoute("post", "/api/lumi-ai/assistant/visibility-debug", visibilityReportHandler);
    }
    router.post("/repo-index/refresh", (req, res) => {
      if (!req.session.user?.isAdmin) return denied(res);
      try {
        const index = req.body.source === "public"
          ? repoIndexer.refreshPublicIndex()
          : repoIndexer.refreshIndex();
        return flash(req, res, "success", `Repository support index refreshed with ${index.routes.length} routes.`);
      } catch (error) {
        return flash(req, res, "error", `Repository index refresh failed: ${error.message}`);
      }
    });
    router.post("/access-control", (req, res) => {
      if (!req.session.user?.isAdmin) return denied(res);
      try {
        accessControl.set(cleanText(req.body.user_id, 200), {
          action: req.body.action,
          timeoutUntil: req.body.timeout_until,
          reason: cleanText(req.body.reason, 500),
          silent: req.body.silent === "on",
          actorId: req.session.user.id
        });
        return flash(req, res, "success", "AI user access updated.");
      } catch (error) {
        return flash(req, res, "error", error.message);
      }
    });
    router.post("/models/:id/delete", (req, res) => {
      if (!req.session.user?.isAdmin) return denied(res);
      try {
        const result = storage.deleteModel(getModel(req.params.id), {
          selectedModelId: config.selected_model_id,
          gateModelId: config.gate.model_id,
          runtimeRunning: runtime.status().state === "running",
          gateRuntimeRunning: gateRuntime.status().state === "running",
          confirmed: req.body.confirm === "yes"
        });
        return flash(req, res, "success", result.deleted ? `Model deleted. Recovered ${formatBytes(result.bytes_recovered)}.` : "Model was not installed.");
      } catch (error) {
        return flash(req, res, "error", error.message);
      }
    });
    router.post("/models/:id/verify", async (req, res) => {
      if (!req.session.user?.isAdmin) return denied(res);
      const result = await runtime.verifyModel(req.params.id);
      return flash(req, res, result.success ? "success" : "error", result.success ? "Model verification passed." : result.message);
    });
    router.post("/storage/cleanup", (req, res) => {
      if (!req.session.user?.isAdmin) return denied(res);
      try {
        const categories = Array.isArray(req.body.categories) ? req.body.categories : req.body.categories ? [req.body.categories] : [];
        const activeDownload = [...downloads.jobs.values()].some((job) => !["complete", "error"].includes(job.state));
        if (activeDownload && categories.some((category) => ["tmp", "runtime_archives"].includes(category))) {
          throw new Error("Wait for active downloads before cleaning temporary files or runtime archives.");
        }
        const result = storage.cleanupStorage(categories, {
          models: modelManifest.models,
          selectedModelId: config.selected_model_id,
          gateModelId: config.gate.model_id,
          runtimeRunning: runtime.status().state === "running" || gateRuntime.status().state === "running",
          activeLogPath: runtime.activeLogPath
        });
        return flash(req, res, "success", `Storage cleanup recovered ${formatBytes(result.recovered_bytes)}.`);
      } catch (error) {
        return flash(req, res, "error", error.message);
      }
    });
    router.get("/logs/:name", (req, res) => {
      if (!req.session.user?.isAdmin) return denied(res);
      try {
        const log = storage.readLogTail(req.params.name);
        return res.render(path.join(__dirname, "views", "log-viewer.ejs"), {
          title: `Lumi AI log - ${log.name}`,
          log,
          formatBytes,
          formatDate
        });
      } catch (error) {
        return res.status(404).render("error", { title: "Log unavailable", message: error.message });
      }
    });
    router.get("/logs/:name/download", (req, res) => {
      if (!req.session.user?.isAdmin) return res.status(403).json({ error: "Access denied." });
      try { return res.download(storage.resolveLog(req.params.name)); }
      catch (error) { return res.status(404).json({ error: error.message }); }
    });
    router.post("/logs/:name/delete", (req, res) => {
      if (!req.session.user?.isAdmin) return denied(res);
      try {
        const result = storage.deleteLog(req.params.name, runtime.activeLogPath);
        return flash(req, res, "success", `Log deleted. Recovered ${formatBytes(result.bytes_recovered)}.`);
      } catch (error) {
        return flash(req, res, "error", error.message);
      }
    });
    router.post("/runtime/:action", async (req, res) => {
      if (!req.session.user?.isAdmin) return res.status(403).json({ error: "Access denied." });
      try {
        const action = req.params.action;
        if (!["start", "stop", "restart", "self-test", "verify-runtime", "verify-model", "verify-gate-model"].includes(action)) throw new Error("Unknown runtime action.");
        const result = action === "self-test" ? await runtime.selfTest()
          : action === "verify-runtime" ? runtime.verifyRuntimeInstallation()
          : action === "verify-model" ? await runtime.verifyModel()
          : action === "verify-gate-model" ? await gateRuntime.verifyModel()
          : action === "stop"
          ? await stopRuntimes({ manual: true, reason: "admin_stop" })
          : action === "restart" ? await restartRuntimes() : await startRuntimes();
        if (result?.success === false) return res.status(400).json({ error: result.message, diagnostic: result });
        res.json(result);
      } catch (error) {
        res.status(400).json({ error: error.message });
      }
    });
    router.get("/diagnostics/download", (req, res) => {
      if (!req.session.user?.isAdmin) return res.status(403).json({ error: "Access denied." });
      const file = createDiagnosticsBundle({
        config,
        runtimeState: getRuntimeState(),
        manifest: {
          runtime: runtimeManifest,
          model: getModel(config.selected_model_id),
          gate_model: getModel(config.gate.model_id)
        },
        metrics: metrics.report()
      });
      return res.download(file);
    });

    router.get("/work-history/:id/export", (req, res) => {
      if (!req.session.user?.isAdmin) return denied(res);
      const payload = metrics.workHistoryExport(req.params.id);
      if (!payload) return res.status(404).send("Work history item not found.");
      const safeId = String(req.params.id || "work").replace(/[^a-z0-9_-]+/gi, "-").slice(0, 80) || "work";
      res.set("Cache-Control", "private, no-store");
      res.attachment(`lumi-ai-work-${safeId}.json`);
      return res.send(`${JSON.stringify(payload, null, 2)}\n`);
    });
    router.post("/assistant/message", async (req, res) => {
      const permission = canUseAssistant({
        user: req.session.user,
        config,
        origin: "webui",
        platform: "webui",
        requestedSurface: "webui_chat"
      });
      if (!permission.allowed) {
        return res.status(403).json({
          error: "Lumi AI is unavailable for this account.",
          reason: permission.reason,
          permission: permission.debug_details
        });
      }
      const message = cleanText(req.body.message, 6000);
      if (!message) return res.status(400).json({ error: "Message is required." });
      const originContext = webOriginContext(req);
      const access = authorizeAiRequest({ userId: req.session.user.id, context: originContext, accessControl, rateLimiter });
      if (!access.allowed) return res.status(429).json({ error: access.message, reason: access.reason, retry_after_seconds: access.retry_after_seconds });
      const requestUser = { ...req.session.user };
      const requestRole = roleOf(requestUser);
      const requestConfig = config;
      const requestSessionId = req.sessionID;
      const history = normalizeConversationHistory(req.body.history);
      const requestStarted = Date.now();
      const job = requestJobs.create({
        userId: requestUser.id,
        metadata: {
          context_size: requestConfig.context_size,
          batch_size: requestConfig.batch_size,
          ubatch_size: requestConfig.ubatch_size,
          threads: requestConfig.threads
        },
        execute: async (updateStage, signal) => {
          try {
            const result = await provider.generate({
              message,
              user: requestUser,
              sessionId: requestSessionId,
              originContext,
              allowDeterministicShortcut: requestConfig.support_scope.allow_deterministic_help_shortcuts,
              history,
              signal,
              onStage: updateStage
            });
            updateStage("formatting", { route: result.route_used, ...(result.diagnostics || {}) });
            const delivered = finalizeAssistantResult(result, {
              role: requestRole,
              config: requestConfig,
              baseUrl: originContext.base_url,
              maxLength: originContext.max_message_length,
              requestMessage: message
            });
            metrics.record({
              kind: "delivery",
              status: "success",
              scope: "webui_chat",
              route_used: result.route_used || "llm",
              route_class: result.route_class,
              max_output_tokens_used: result.max_output_tokens_used,
              role: requestRole,
              user_id: requestUser.id,
              internal_generated_length: result.internal_generated_length || String(result.text || "").length,
              final_reply_length: delivered.original_final_length,
              original_final_length: delivered.original_final_length,
              delivered_length: delivered.delivered_length,
              final_tokens: estimateTokenCountFromLength(delivered.original_final_length),
              delivered_tokens: estimateTokenCountFromLength(delivered.delivered_length),
              truncated: delivered.truncated,
              delivery_action: delivered.delivery_action,
              ...controllerDeliveryFields(result),
              ...(result.stage_timings || {})
            });
            return {
              ...delivered,
              diagnostics: result.diagnostics || null,
              feedback_context: {
                user_message: message,
                assistant_answer: delivered.text,
                route_used: result.route_used || "main_llm",
                role: requestRole,
                origin: originContext.origin,
                platform: originContext.platform,
                model: result.model_id || requestConfig.selected_model_id,
                timestamp: new Date().toISOString(),
                context_snapshot: result.feedback_context_snapshot || null
              }
            };
          } catch (error) {
            metrics.record({
              kind: "request",
              status: "failed",
              user_id: requestUser.id,
              role: requestRole,
              message: error.message,
              timeout: error.name === "TimeoutError" || error.code === "HARD_GENERATION_TIMEOUT",
              cancelled: error.code === "REQUEST_CANCELLED",
              total_ms: Date.now() - requestStarted,
              duration_ms: Date.now() - requestStarted
            });
            if (error.code === "QUEUE_FULL" && !error.retry_after_seconds) error.retry_after_seconds = 5;
            throw error;
          }
        }
      });
      return res.status(202).json({
        job_id: job.id,
        state: job.state,
        stage: job.stage,
        status_url: `/plugins/${PLUGIN_ID}/assistant/jobs/${job.id}`,
        cancel_url: `/plugins/${PLUGIN_ID}/assistant/jobs/${job.id}/cancel`,
        soft_timeout_url: `/plugins/${PLUGIN_ID}/assistant/jobs/${job.id}/soft-timeout`,
        ui_soft_timeout_ms: requestConfig.ui_soft_timeout_ms
      });
    });
    router.post("/assistant/feedback", (req, res) => {
      const permission = canUseAssistant({
        user: req.session.user,
        config,
        origin: "webui",
        platform: "webui",
        requestedSurface: "webui_chat"
      });
      if (!permission.allowed) return res.status(403).json({ error: "Access denied." });
      try {
        const entry = feedbackStore.capture({
          user_message: req.body.user_message,
          assistant_answer: req.body.assistant_answer,
          route_used: req.body.route_used,
          role: roleOf(req.session.user),
          origin: "webui",
          platform: "webui",
          model: req.body.model,
          timestamp: req.body.timestamp,
          rating: req.body.rating,
          feedback_tag: req.body.feedback_tag,
          feedback_kind: req.body.feedback_kind,
          optional_correction: req.body.optional_correction,
          context_snapshot: req.body.context_snapshot
        }, req.session.user);
        web.emitEvent?.("ai:improvement_changed", {
          kind: "review",
          review_id: entry.id,
          status: entry.status,
          message: "New AI feedback is waiting for review."
        }, { role: "mod" });
        return res.status(201).json({ success: true, id: entry.id });
      } catch (error) {
        return res.status(400).json({ error: error.message });
      }
    });
    router.get("/assistant/jobs/:id", (req, res) => {
      const permission = canUseAssistant({
        user: req.session.user,
        config,
        origin: "webui",
        platform: "webui",
        requestedSurface: "webui_chat"
      });
      if (!permission.allowed) return res.status(403).json({ error: "Access denied." });
      const job = requestJobs.get(req.params.id, req.session.user.id);
      if (!job) return res.status(404).json({ error: "Assistant request was not found or expired." });
      return res.json(job);
    });
    router.post("/assistant/jobs/:id/cancel", (req, res) => {
      const permission = canUseAssistant({
        user: req.session.user,
        config,
        origin: "webui",
        platform: "webui",
        requestedSurface: "webui_chat"
      });
      if (!permission.allowed) return res.status(403).json({ error: "Access denied." });
      const job = requestJobs.cancel(req.params.id, req.session.user.id, {
        admin: Boolean(req.session.user?.isAdmin)
      });
      if (!job) return res.status(404).json({ error: "Assistant request was not found or expired." });
      metrics.record({
        kind: "request_job",
        status: "cancelled",
        job_id: job.id,
        user_id: req.session.user.id,
        stage: job.stage,
        elapsed_ms: job.elapsed_ms
      });
      return res.json(job);
    });
    router.post("/assistant/jobs/:id/soft-timeout", (req, res) => {
      const permission = canUseAssistant({
        user: req.session.user,
        config,
        origin: "webui",
        platform: "webui",
        requestedSurface: "webui_chat"
      });
      if (!permission.allowed) return res.status(403).json({ error: "Access denied." });
      const job = requestJobs.markSoftTimeout(req.params.id, req.session.user.id);
      if (!job) return res.status(404).json({ error: "Assistant request was not found or expired." });
      metrics.record({
        kind: "request_job",
        status: "soft_timeout",
        job_id: job.id,
        user_id: req.session.user.id,
        stage: job.stage,
        elapsed_ms: job.elapsed_ms,
        still_running: job.still_running
      });
      return res.json(job);
    });
    router.post("/assistant/test", async (req, res) => {
      if (!req.session.user?.isAdmin) return res.status(403).json({ error: "Access denied." });
      const message = cleanText(req.body.message, 6000);
      if (!message) return res.status(400).json({ error: "Message is required." });
      const simulatedRole = ["admin", "mod", "user"].includes(req.body.role) ? req.body.role : "admin";
      const simulatedUser = {
        id: req.session.user.id,
        username: req.session.user.username,
        isAdmin: simulatedRole === "admin",
        isMod: simulatedRole === "mod"
      };
      const allowTools = req.body.allow_tools === true;
      const testOrigin = ["webui", "discord", "twitch", "youtube", "kick", "other"].includes(req.body.origin)
        ? req.body.origin
        : "webui";
      try {
        const result = await provider.test({
          message,
          user: simulatedUser,
          includeRaw: Boolean(req.body.show_raw_output),
          allowTools,
          originContext: diagnosticOriginContext(simulatedUser, testOrigin)
        });
        if (!req.body.show_raw_prompt) delete result.raw_prompt;
        res.json(result);
      } catch (error) {
        res.status(503).json({ error: error.message });
      }
    });
    router.post("/assistant/confirm", async (req, res) => {
      const permission = canUseAssistant({
        user: req.session.user,
        config,
        origin: "webui",
        platform: "webui",
        requestedSurface: "webui_chat"
      });
      if (!permission.allowed) return res.status(403).json({ error: "Access denied.", reason: permission.reason });
      try { res.json({ success: true, result: await tools.confirm({ id: req.body.id, user: req.session.user, sessionId: req.sessionID }) }); }
      catch (error) { res.status(400).json({ error: error.message }); }
    });
    router.post("/assistant/cancel", (req, res) => {
      const permission = canUseAssistant({
        user: req.session.user,
        config,
        origin: "webui",
        platform: "webui",
        requestedSurface: "webui_chat"
      });
      if (!permission.allowed) return res.status(403).json({ error: "Access denied.", reason: permission.reason });
      const cancelled = tools.cancel(req.body.id, req.session.user.id);
      metrics.record({ kind: "tool", status: cancelled ? "cancelled" : "failed", user_id: req.session.user.id });
      res.json({ success: cancelled });
    });

    router.get("/api/tools", async (req, res) => {
      if (!req.session.user?.isAdmin) return res.status(403).json({ error: "Access denied." });
      try {
        return res.json(await toolManager.list({ force: req.query.refresh === "1" }));
      } catch (error) {
        return res.status(503).json({ error: error.message });
      }
    });

    router.get("/api/tools-diagnostics", async (req, res) => {
      if (!req.session.user?.isAdmin) return res.status(403).json({ error: "Access denied." });
      const role = ["admin", "mod", "user"].includes(req.query.role) ? req.query.role : "admin";
      const origin = ["webui", "discord", "twitch", "youtube", "kick", "other"].includes(req.query.origin)
        ? req.query.origin
        : "webui";
      const simulatedUser = {
        id: req.session.user.id,
        username: req.session.user.username,
        isAdmin: role === "admin",
        isMod: role === "mod"
      };
      try {
        const diagnostics = await toolManager.diagnostics({
          role,
          user: simulatedUser,
          context: diagnosticOriginContext(simulatedUser, origin)
        });
        return res.json({
          ...diagnostics,
          prompt_preview: buildAllowedToolsSection(diagnostics.prompt_tools)
        });
      } catch (error) {
        return res.status(503).json({ error: error.message });
      }
    });

    router.get("/api/tools/:id/readme", async (req, res) => {
      if (!req.session.user?.isAdmin) return res.status(403).json({ error: "Access denied." });
      try {
        return res.json(await toolManager.readme(req.params.id));
      } catch (error) {
        return res.status(404).json({ error: error.message });
      }
    });

    router.get("/api/tools/:id/settings", (req, res) => {
      if (!req.session.user?.isAdmin) return res.status(403).json({ error: "Access denied." });
      try {
        return res.json(toolManager.settingsFor(req.params.id));
      } catch (error) {
        return res.status(404).json({ error: error.message });
      }
    });

    router.post("/api/tools/:id/settings", express.json({ limit: "64kb" }), async (req, res) => {
      if (!req.session.user?.isAdmin) return res.status(403).json({ error: "Access denied." });
      try {
        return res.json(await toolManager.saveSettings(req.params.id, req.body?.values || {}));
      } catch (error) {
        return res.status(400).json({ error: error.message });
      }
    });

    router.post("/api/tools/:id/settings/reset", async (req, res) => {
      if (!req.session.user?.isAdmin) return res.status(403).json({ error: "Access denied." });
      try {
        return res.json(await toolManager.resetSettings(req.params.id));
      } catch (error) {
        return res.status(400).json({ error: error.message });
      }
    });

    router.get("/tools/:id/assets/*", (req, res) => {
      if (!req.session.user?.isAdmin) {
        const permission = canUseAssistant({
          user: req.session.user,
          config,
          origin: "webui",
          platform: "webui",
          requestedSurface: "webui_chat"
        });
        if (!permission.allowed) return res.status(403).end();
      }
      const file = toolManager.resolveAsset(req.params.id, req.params[0], {
        allowInstalled: req.session.user?.isAdmin === true
      });
      return file ? res.sendFile(file) : res.status(404).end();
    });

    router.post("/tools/:id/enable", async (req, res) => {
      if (!req.session.user?.isAdmin) return toolDenied(req, res);
      try {
        const result = await toolManager.enable(req.params.id);
        return toolActionResponse(req, res, "AI tool enabled.", result);
      } catch (error) {
        return toolActionError(req, res, error);
      }
    });

    router.post("/tools/:id/disable", async (req, res) => {
      if (!req.session.user?.isAdmin) return toolDenied(req, res);
      try {
        const result = await toolManager.disable(req.params.id);
        return toolActionResponse(req, res, "AI tool disabled.", result);
      } catch (error) {
        return toolActionError(req, res, error);
      }
    });

    router.post("/tools/:id/update", async (req, res) => {
      if (!req.session.user?.isAdmin) return toolDenied(req, res);
      try {
        const result = await toolManager.update(req.params.id);
        return toolActionResponse(req, res, "AI tool updated.", result);
      } catch (error) {
        return toolActionError(req, res, error);
      }
    });

    router.post("/tools/:id/delete", async (req, res) => {
      if (!req.session.user?.isAdmin) return toolDenied(req, res);
      try {
        const result = await toolManager.delete(req.params.id);
        return toolActionResponse(req, res, "AI tool deleted.", result);
      } catch (error) {
        return toolActionError(req, res, error);
      }
    });

    const announceImprovementChange = (review, message, kind = "review") => {
      web.emitEvent?.("ai:improvement_changed", {
        kind,
        review_id: review?.id || null,
        status: review?.status || null,
        message: message || "The AI feedback queue changed."
      }, { role: "mod" });
    };

    router.get("/improvement_center", (req, res) => {
      const access = improvementAccess(req.session.user, config);
      if (!access.allowed) return deniedImprovement(req, res);
      const historyStatus = FINAL_FEEDBACK_STATUSES.includes(cleanText(req.query.history_status, 30))
        ? cleanText(req.query.history_status, 30)
        : "";
      return res.render(path.join(__dirname, "views", "improvement-center.ejs"), {
        title: "Lumi AI Improvement Center",
        config,
        access,
        feedbackTags: FEEDBACK_TAGS,
        feedbackKinds: FEEDBACK_KINDS,
        promotionTargets: PROMOTION_TARGETS,
        reviews: feedbackStore.list({
          page: req.query.review_page,
          pageSize: 15,
          scope: "active",
          viewerRole: access.role
        }),
        historyReviews: feedbackStore.list({
          page: req.query.history_page,
          pageSize: 15,
          scope: "history",
          query: req.query.history_q,
          status: historyStatus,
          viewerRole: access.role
        }),
        reviewCounts: feedbackStore.counts(access.role),
        historyQuery: cleanText(req.query.history_q, 300),
        historyStatus,
        corrections: correctionStore.list({ page: req.query.correction_page, pageSize: 15 }),
        evalCases: evalStore.list({ page: req.query.eval_page, pageSize: 15 }),
        evalResults: evalStore.results(25),
        formatDate
      });
    });

    router.post("/improvement_center/settings", (req, res) => {
      const access = improvementAccess(req.session.user, config);
      if (!access.can_approve) return deniedImprovement(req, res);
      config = saveConfig({
        ...config,
        improvement: {
          ...config.improvement,
          allow_moderators_to_review_responses: req.body.allow_moderators_to_review_responses === "on",
          trusted_moderator_reviewers: parseIdList(req.body.trusted_moderator_reviewers),
          corrections_enabled: req.body.corrections_enabled === "on"
        }
      });
      ensureSidebarNavItem(settings);
      announceImprovementChange(null, "Improvement Center settings changed.", "settings");
      return improvementFlash(req, res, "success", "Improvement Center settings saved.");
    });

    router.post("/improvement_center/reviews/:id", (req, res) => {
      const access = improvementAccess(req.session.user, config);
      if (!access.allowed) return deniedImprovement(req, res);
      try {
        const review = feedbackStore.get(req.params.id);
        if (!review) {
          return sendImprovementError(req, res, 404, "This feedback record no longer exists. Refresh the queue and try again.");
        }
        if (!canReviewAiFeedback(review, access)) return deniedImprovement(req, res);
        const body = req.body || {};
        const action = cleanText(body.action, 30);
        let updated = null;
        if (action === "flag" && access.can_flag) {
          updated = feedbackStore.setStatus(req.params.id, "flagged", req.session.user, body.review_notes);
        } else if (action === "verify" && access.can_verify) {
          updated = feedbackStore.verify(req.params.id, req.session.user, body.review_notes);
        } else if (action === "approve" && access.can_approve) {
          updated = feedbackStore.setStatus(req.params.id, "approved", req.session.user, body.review_notes);
        } else if (action === "reject" && access.can_approve) {
          updated = feedbackStore.setStatus(req.params.id, "rejected", req.session.user, body.review_notes);
        } else if (action === "reviewed" && access.can_approve) {
          updated = feedbackStore.setStatus(req.params.id, "reviewed", req.session.user, body.review_notes);
        } else if (action === "restore" && access.can_approve) {
          updated = feedbackStore.restoreToQueue(req.params.id, req.session.user);
        } else if (action === "edit" && access.can_edit) {
          updated = feedbackStore.edit(req.params.id, body, req.session.user);
        } else if (action === "export" && access.can_export) {
          updated = feedbackStore.markExportApproved(req.params.id, req.session.user);
        } else if (!action) {
          return sendImprovementError(req, res, 400, "The feedback action was missing. Refresh the page and try again.");
        } else {
          return sendImprovementError(req, res, 403, "This account is not allowed to perform that feedback action.");
        }
        const messages = {
          flag: "Feedback marked for closer review.",
          verify: "Feedback reviewed and sent to an administrator.",
          approve: "Feedback approved and ready to apply.",
          reject: "Feedback dismissed and moved to history.",
          reviewed: "Feedback marked as requiring no change and moved to history.",
          restore: "Feedback restored to the active queue.",
          edit: "Feedback details saved.",
          export: "Feedback approved for training export."
        };
        announceImprovementChange(updated, messages[action]);
        return improvementFlash(req, res, "success", messages[action] || "Feedback updated.", { review: updated });
      } catch (error) {
        logImprovementActionError(req, error);
        return improvementFlash(req, res, "error", error.message);
      }
    });

    router.post("/improvement_center/reviews/:id/delete", (req, res) => {
      const access = improvementAccess(req.session.user, config);
      if (!access.can_delete) return deniedImprovement(req, res);
      try {
        const deleted = feedbackStore.get(req.params.id);
        feedbackStore.delete(req.params.id);
        announceImprovementChange(deleted, "Feedback record permanently deleted.");
        return improvementFlash(req, res, "success", "Feedback record permanently deleted.");
      } catch (error) {
        return improvementFlash(req, res, "error", error.message);
      }
    });

    router.post("/improvement_center/reviews/:id/archive", (req, res) => {
      const access = improvementAccess(req.session.user, config);
      if (!access.can_delete) return deniedImprovement(req, res);
      try {
        const updated = feedbackStore.setStatus(req.params.id, "archived", req.session.user, req.body.review_notes);
        announceImprovementChange(updated, "Feedback archived and moved to history.");
        return improvementFlash(req, res, "success", "Feedback archived and moved to history.", { review: updated });
      } catch (error) {
        return improvementFlash(req, res, "error", error.message);
      }
    });

    router.post("/improvement_center/reviews/:id/implement", (req, res) => {
      const access = improvementAccess(req.session.user, config);
      if (!access.can_implement) return deniedImprovement(req, res);
      try {
        let review = feedbackStore.get(req.params.id);
        if (!review || isFinalized(review)) throw new Error("This feedback is already finalized. Restore it from history before applying it again.");
        if (review.status !== "approved") {
          review = feedbackStore.setStatus(review.id, "approved", req.session.user, req.body.review_notes);
        }
        const target = PROMOTION_TARGETS.includes(req.body.target) ? req.body.target : "correction";
        let implementationId = "";
        let implementationSummary = "";
        if (target === "eval_case") {
          const evalCase = evalStore.add({
            prompt: review.user_message,
            role: req.body.min_role || review.role,
            origin: req.body.permission_origin || review.origin,
            expected_behavior: req.body.corrected_answer || review.optional_correction,
            forbidden_behavior: req.body.forbidden_behavior,
            expected_link: req.body.expected_link,
            notes: req.body.notes
          }, req.session.user);
          implementationId = evalCase.id;
          implementationSummary = "Added as an evaluation case.";
        } else if (target === "training_export") {
          feedbackStore.markExportApproved(review.id, req.session.user);
          implementationSummary = "Approved for the next manual training export.";
        } else {
          const correction = correctionStore.createFromFeedback(review, {
            ...req.body,
            target,
            explicitly_safe: req.body.explicitly_safe === "on",
            enabled: req.body.enabled === "on"
          }, req.session.user);
          if (target === "correction") {
            try {
              const okfEntry = saveAiFeedbackOkfCorrection(review, correction, req.body, req.session.user);
              correctionStore.linkOkf(correction.id, okfEntry.path);
              feedbackStore.linkOkfCorrection(review.id, okfEntry.path, req.session.user);
            } catch (error) {
              correctionStore.delete(correction.id);
              throw error;
            }
          }
          correctionStore.saveCorrections(req.session.user);
          implementationId = correction.id;
          implementationSummary = target === "correction"
            ? "Created a searchable OKF correction and activated it for Lumi AI."
            : `Created and activated a ${target.replaceAll("_", " ")}.`;
        }
        const updated = feedbackStore.markImplemented(review.id, req.session.user, {
          target,
          implementation_id: implementationId,
          summary: implementationSummary
        });
        announceImprovementChange(updated, "Improvement applied and moved to history.");
        return improvementFlash(req, res, "success", "Improvement applied and moved to history.", { review: updated });
      } catch (error) {
        return improvementFlash(req, res, "error", error.message);
      }
    });

    router.post("/improvement_center/corrections/save", (req, res) => {
      const access = improvementAccess(req.session.user, config);
      if (!access.can_implement) return deniedImprovement(req, res);
      const result = correctionStore.saveCorrections(req.session.user);
      announceImprovementChange(null, "AI corrections were activated.", "correction");
      return improvementFlash(req, res, "success", `Corrections saved. ${result.active} of ${result.total} are active.`);
    });

    router.post("/improvement_center/corrections/:id", (req, res) => {
      const access = improvementAccess(req.session.user, config);
      if (!access.allowed) return deniedImprovement(req, res);
      try {
        const action = cleanText(req.body.action, 30);
        if (action === "verify" && access.can_verify) {
          correctionStore.verify(req.params.id, req.session.user);
        } else if (action === "edit" && access.can_edit) {
          if (req.body.expected_link && !isVerifiedImprovementLink(req.body.expected_link)) {
            throw new Error("Internal correction links must match a verified Lumi route.");
          }
          correctionStore.update(req.params.id, {
            ...req.body,
            explicitly_safe: req.body.explicitly_safe === "on",
            enabled: req.body.enabled === "on"
          });
        } else if (action === "toggle" && access.can_edit) {
          correctionStore.setEnabled(req.params.id, req.body.enabled === "on");
        } else {
          return deniedImprovement(req, res);
        }
        announceImprovementChange(null, `Correction ${action} completed.`, "correction");
        return improvementFlash(req, res, "success", `Correction ${action} completed. Save Corrections to activate changes.`);
      } catch (error) {
        return improvementFlash(req, res, "error", error.message);
      }
    });

    router.post("/improvement_center/corrections/:id/delete", (req, res) => {
      const access = improvementAccess(req.session.user, config);
      if (!access.can_delete) return deniedImprovement(req, res);
      try {
        correctionStore.delete(req.params.id);
        announceImprovementChange(null, "Correction deleted.", "correction");
        return improvementFlash(req, res, "success", "Correction delete completed. Save Corrections to activate changes.");
      } catch (error) {
        return improvementFlash(req, res, "error", error.message);
      }
    });

    router.post("/improvement_center/evals", (req, res) => {
      const access = improvementAccess(req.session.user, config);
      if (!access.can_run_evals) return deniedImprovement(req, res);
      try {
        if (req.body.expected_link && !isVerifiedImprovementLink(req.body.expected_link)) {
          throw new Error("Expected links must match a verified Lumi route.");
        }
        evalStore.add(req.body, req.session.user);
        announceImprovementChange(null, "Evaluation case added.", "eval");
        return improvementFlash(req, res, "success", "Eval case added.");
      } catch (error) {
        return improvementFlash(req, res, "error", error.message);
      }
    });

    router.post("/improvement_center/evals/:id/delete", (req, res) => {
      const access = improvementAccess(req.session.user, config);
      if (!access.can_run_evals) return deniedImprovement(req, res);
      evalStore.delete(req.params.id);
      announceImprovementChange(null, "Evaluation case deleted.", "eval");
      return improvementFlash(req, res, "success", "Eval case deleted.");
    });

    router.post("/improvement_center/evals/run", async (req, res) => {
      const access = improvementAccess(req.session.user, config);
      if (!access.can_run_evals) return deniedImprovement(req, res);
      try {
        const results = await evalStore.runAll({ provider, actor: req.session.user });
        announceImprovementChange(null, "Evaluation run completed.", "eval");
        return improvementFlash(req, res, "success", `Eval run completed with ${results.length} result(s).`);
      } catch (error) {
        return improvementFlash(req, res, "error", error.message);
      }
    });

    router.post("/improvement_center/exports/:format", (req, res) => {
      const access = improvementAccess(req.session.user, config);
      if (!access.can_export) return deniedImprovement(req, res);
      try {
        const output = trainingExporter.export(req.params.format);
        return res.download(output.file, output.filename);
      } catch (error) {
        return improvementFlash(req, res, "error", error.message);
      }
    });

    web.mount(`/plugins/${PLUGIN_ID}`, router, {
      label: "Lumi AI",
      role: "admin",
      section: "plugins"
    });
    web.addNavItem({
      label: "AI Improvement Center",
      path: `/plugins/${PLUGIN_ID}/improvement_center`,
      role: "mod",
      section: "moderation",
      canAccess: (user) => improvementAccess(user, config).allowed
    });
    let removeAssistantPanel = () => {};
    if (typeof web.addAssistantPanel === "function") {
      removeAssistantPanel = web.addAssistantPanel({
        id: PLUGIN_ID,
        version: require("./plugin.json").version,
        canAccess: (user) => canUseAssistant({
          user,
          config,
          origin: "webui",
          platform: "webui",
          requestedSurface: "webui_panel"
        }),
        getAvailability: async (user) => evaluateAssistantAvailability({
          user,
          config,
          model: getModel(config.selected_model_id),
          runtimeHealth: await runtime.health(),
          providerAvailable: Boolean(provider),
          origin: "webui",
          platform: "webui",
          requestedSurface: "webui_panel"
        }),
        view: path.join(__dirname, "views", "assistant-panel.ejs"),
        requiredLocals: ["endpoint", "user"],
        onRenderDiagnostic: (report) => {
          panelDiagnostics.update(report);
          if (report.panel_html_error) panelDiagnostics.log("render_failure", report);
        },
        stylesheet: `/plugins/${PLUGIN_ID}/assets/assistant.css`,
        script: `/plugins/${PLUGIN_ID}/assets/assistant.js`,
        getDebug: () => ({
          enabled: Boolean(config.assistant_debug_logging),
          ...panelDiagnostics.snapshot()
        }),
        locals: { endpoint: `/plugins/${PLUGIN_ID}` }
      });
    } else {
      console.warn("Lumi AI assistant panel hook is unavailable; settings remain accessible.");
    }
    ensureSidebarNavItem(settings);
    registerAssistantCommands({
      commandRouter,
      provider,
      runtime,
      getConfig: () => config,
      accessControl,
      rateLimiter,
      metrics,
      getBaseUrl: () => configuredBaseUrl(settings)
    });
    writeCommandsManifest(plugin?.dir || __dirname, config);
    setImmediate(() => toolManager.loadEnabled().catch((error) =>
      console.error("Lumi AI tool loader failed", error)
    ));

    if (config.enabled) {
      setImmediate(() => ensureGateRuntime().catch((error) =>
        console.error("Lumi AI gate runtime start failed", error)
      ));
    }
    const state = getRuntimeState();
    if (shouldAutoResume(config, state)) {
      setImmediate(() => startRuntimes({ resume: true }).catch((error) => console.error("Lumi AI runtime resume failed", error)));
    }

    return async () => {
      clearInterval(gateMonitor);
      removeAssistantPanel();
      commandRouter?.clearCommands?.(PLUGIN_ID);
      await toolManager.stopAll();
      await stopRuntimes({ manual: false, reason: "bot_shutdown" });
      if (global.lumiFrameworks?.ai === api) delete global.lumiFrameworks.ai;
      if (global.lumiFrameworks?.lumi_ai === api) delete global.lumiFrameworks.lumi_ai;
    };
  }
};

function getRuntimeTarget(hardware = detectHardware(modelManifest.models, runtimeManifest)) {
  const selection = hardware.runtime_selection;
  return selection?.target ? { ...selection.target, backend: selection.backend } : null;
}
function normalizeContext(value) {
  if (Array.isArray(value)) return value.filter((item) => typeof item === "string");
  return typeof value === "string" ? [value] : [];
}
function safeContextDiagnostic(value) {
  const row = value && typeof value === "object" ? value : {};
  return {
    provider: cleanText(row.provider || row.kind, 40),
    kind: cleanText(row.kind, 40),
    query: cleanText(row.query, 500),
    candidate_count: boundedInt(row.candidate_count, 0, 1000, 0),
    returned_count: boundedInt(row.returned_count, 0, 1000, 0),
    limit: boundedInt(row.limit, 0, 1000, 0),
    okf_retrieval: cleanText(row.okf_retrieval, 40),
    reason: cleanText(row.reason, 160)
  };
}
function boundedInt(value, min, max, fallback) {
  const number = Number.parseInt(value, 10);
  return Number.isFinite(number) ? Math.min(max, Math.max(min, number)) : fallback;
}
function boundedNumber(value, min, max, fallback) {
  const number = Number(value);
  return Number.isFinite(number) ? Math.min(max, Math.max(min, number)) : fallback;
}
function cleanText(value, max) {
  return String(value || "").trim().slice(0, max);
}

function canReviewAiFeedback(review, access) {
  if (!review || !access?.allowed) return false;
  const rank = { user: 1, mod: 2, admin: 3 };
  return (rank[access.role] || 0) >= (rank[review.role] || 1);
}

function saveAiFeedbackOkfCorrection(review, correction, values, actor) {
  const saveCorrection = global.lumiFrameworks?.okf?.saveCorrection;
  if (typeof saveCorrection !== "function") {
    throw new Error("Enable the OKF plugin before creating a searchable correction file.");
  }
  const title = cleanText(values.okf_title, 180) || `Assistant correction: ${cleanText(review.user_message, 120)}`;
  const visibility = ["user", "mod", "admin"].includes(correction.min_role) ? correction.min_role : "user";
  const safePrompt = cleanText(review.user_message, 3000);
  const safeAnswer = cleanText(correction.corrected_answer, 10000);
  return saveCorrection({
    slug: `lumi-ai-${review.id}`,
    id: `correction.lumi_ai.${review.id}`,
    title,
    status: "active",
    priority: 150,
    visibility,
    category: "Lumi AI correction",
    tags: `lumi ai, assistant feedback, correction, ${review.feedback_tag || "reviewed"}`,
    source_feedback_id: `lumi-ai:${review.id}`,
    source_feedback_url: `/plugins/lumi_ai/improvement_center#review-${review.id}`,
    body: [
      "## When this applies",
      safePrompt,
      "",
      "## Approved response guidance",
      safeAnswer
    ].join("\n")
  }, actor);
}
function flash(req, res, type, message) {
  if (req.get("X-Lumi-Enhanced") === "1" || req.get("X-Requested-With") === "XMLHttpRequest") {
    const ok = type !== "error";
    return res.status(ok ? 200 : 422).json({
      ok,
      message,
      fieldErrors: {},
      data: {},
      patch: {},
      redirect: null,
      reloadRequired: false
    });
  }
  req.session.flash = { type, message };
  return res.redirect(`/plugins/${PLUGIN_ID}`);
}
function toolActionResponse(req, res, message, result = {}) {
  if (req.accepts(["json", "html"]) === "json") return res.json({ success: true, message, result });
  req.session.flash = { type: "success", message };
  return res.redirect(`/plugins/${PLUGIN_ID}`);
}
function toolActionError(req, res, error) {
  if (req.accepts(["json", "html"]) === "json") return res.status(400).json({ error: error.message });
  req.session.flash = { type: "error", message: error.message };
  return res.redirect(`/plugins/${PLUGIN_ID}`);
}
function toolDenied(req, res) {
  if (req.accepts(["json", "html"]) === "json") return res.status(403).json({ error: "Access denied." });
  return denied(res);
}
function improvementFlash(req, res, type, message, result = {}) {
  return sendImprovementResult(req, res, type, message, result);
}
function denied(res) {
  return res.status(403).render("error", { title: "Access denied", message: "Administrator access is required." });
}
function deniedImprovement(req, res) {
  return sendImprovementError(req, res, 403, "Improvement Center access is not enabled for this account.");
}
function logImprovementActionError(req, error) {
  console.error("Lumi AI feedback action failed", {
    review_id: cleanText(req.params?.id, 100),
    action: cleanText(req.body?.action, 30),
    user_id: cleanText(req.session?.user?.id, 100),
    code: cleanText(error?.code, 40),
    message: cleanText(error?.message || error, 500)
  });
}
function parseIdList(value) {
  return [...new Set(String(value || "")
    .split(/[\s,;]+/)
    .map((entry) => entry.trim())
    .filter(Boolean))]
    .slice(0, 250);
}
function isVerifiedImprovementLink(value) {
  const cleaned = cleanText(value, 2000).replace(/^(?:GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+/i, "");
  if (!cleaned) return true;
  let pathname;
  try {
    const url = new URL(cleaned, "https://lumi.invalid");
    if (!["http:", "https:"].includes(url.protocol)) return false;
    pathname = url.pathname;
  } catch {
    return false;
  }
  return repoIndexer.verifiedRoutePaths().includes(pathname);
}
function formatDuration(ms) {
  if (!ms) return "0 ms";
  return ms < 1000 ? `${ms} ms` : `${(ms / 1000).toFixed(1)} s`;
}
function formatDate(value) {
  const date = new Date(value);
  return Number.isNaN(date.getTime()) ? "-" : date.toLocaleString();
}
function describeAssistantReason(code) {
  return ({
    anonymous: "User is not logged in.",
    feature_disabled: "AI Assistant is disabled.",
    role_forbidden: "The current role is not enabled for the assistant.",
    model_not_selected: "No model is selected.",
    model_missing: "The selected model is not installed.",
    runtime_missing: "The managed runtime is not installed.",
    runtime_unusable: "The runtime self-test has not passed.",
    runtime_stopped: "The runtime is stopped.",
    runtime_starting: "The runtime is starting but not ready.",
    runtime_error: "The runtime is in an error state.",
    runtime_unhealthy: "The runtime health check failed.",
    assistant_disabled: "The sidebar assistant is disabled.",
    provider_unavailable: "The AI provider is unavailable."
  })[code] || (code ? `Assistant unavailable: ${code}.` : "Assistant is available.");
}
function ensureSidebarNavItem(settings) {
  if (!settings?.getSetting || !settings?.setSetting) return;
  const raw = settings.getSetting("nav_structure", null);
  if (!raw) return;
  let structure = raw;
  if (typeof structure === "string") {
    try { structure = JSON.parse(structure); } catch { return; }
  }
  if (!structure?.enabled || !Array.isArray(structure.sections)) return;
  const navId = "plugins_lumi_ai";
  const improvementNavId = "plugins_lumi_ai_improvement_center";
  for (const section of structure.sections) {
    if (Array.isArray(section.items)) {
      section.items = section.items.filter((item) => ![navId, improvementNavId].includes(item));
    }
  }
  let plugins = structure.sections.find((section) => section.id === "plugins");
  if (!plugins) {
    plugins = { id: "plugins", label: "Plugins", icon: "blocks", items: [] };
    structure.sections.push(plugins);
  }
  plugins.items = Array.isArray(plugins.items) ? plugins.items : [];
  plugins.items.push(navId);
  let moderation = structure.sections.find((section) => section.id === "moderation");
  if (!moderation) {
    moderation = { id: "moderation", label: "Mod", icon: "shield", items: [] };
    structure.sections.push(moderation);
  }
  moderation.items = Array.isArray(moderation.items) ? moderation.items : [];
  moderation.items.push(improvementNavId);
  settings.setSetting("nav_structure", structure);
}

function shouldAutoResume(config, state) {
  return Boolean(config.enabled && state.desired_state === "running" && !state.last_manual_stop && !state.last_crashed);
}

function registerAssistantCommands({
  commandRouter,
  provider,
  runtime,
  getConfig,
  accessControl,
  rateLimiter,
  metrics: commandMetrics = metrics,
  getBaseUrl = () => ""
}) {
  if (!commandRouter) return;
  const config = getConfig();
  if (!config.commands.enabled) {
    commandRouter.registerCommands(PLUGIN_ID, []);
    return;
  }
  const platforms = Object.entries(config.commands.platforms)
    .filter(([, enabled]) => enabled)
    .map(([platform]) => platform);
  commandRouter.registerCommands(PLUGIN_ID, [{
    id: "lumi_ai:assistant",
    triggers: config.commands.triggers,
    platforms,
    handler: async (ctx) => {
      const current = getConfig();
      const originContext = buildOriginContext(ctx, ctx.trigger);
      originContext.base_url = getBaseUrl();
      const started = Date.now();
      const user = {
        id: originContext.user_id,
        username: originContext.username,
        isAdmin: originContext.is_admin,
        isMod: originContext.is_mod
      };
      const permission = canUseAssistant({
        user,
        config: current,
        origin: originContext.origin,
        platform: originContext.platform,
        requestedSurface: "command",
        roleHint: originContext.role,
        roleSource: "command_origin"
      });
      if (!permission.allowed) {
        commandMetrics.record({
          kind: "request",
          status: "refused",
          scope: "platform_command",
          route_used: "denial",
          role: permission.normalized_role,
          user_id: originContext.user_id,
          platform: originContext.platform,
          reason: permission.reason,
          duration_ms: Date.now() - started
        });
        if (permission.reason === "command_disabled" || permission.reason === "platform_forbidden") return false;
        await ctx.reply(formatPlatformReply(current.commands.denied_message, [], originContext));
        return true;
      }
      if (!ctx.argsText.trim()) {
        await ctx.reply(`Usage: !${ctx.trigger} <question>`);
        return true;
      }
      const access = authorizeAiRequest({
        userId: originContext.user_id,
        context: originContext,
        accessControl,
        rateLimiter
      });
      if (!access.allowed) {
        commandMetrics.record({
          kind: "request",
          status: "refused",
          scope: "platform_command",
          route_used: "denial",
          role: permission.normalized_role,
          user_id: originContext.user_id,
          platform: originContext.platform,
          reason: access.reason,
          duration_ms: Date.now() - started
        });
        if (!access.silent) {
          const message = access.reason === "rate_limited"
            ? `Lumi Assistant is rate limited. Try again in ${access.retry_after_seconds}s.`
            : current.commands.denied_message;
          await ctx.reply(formatPlatformReply(message, [], originContext));
        }
        return true;
      }
      const health = await runtime.health();
      if (!current.enabled || health.state !== "running" || !health.healthy) {
        commandMetrics.record({
          kind: "request",
          status: "failed",
          scope: "platform_command",
          route_used: "unavailable_fallback",
          role: permission.normalized_role,
          user_id: originContext.user_id,
          platform: originContext.platform,
          duration_ms: Date.now() - started
        });
        await ctx.reply(formatPlatformReply(current.commands.unavailable_message, [], originContext));
        return true;
      }
      try {
        const result = await provider.generate({
          message: ctx.argsText.trim(),
          user,
          sessionId: `command:${originContext.platform}:${originContext.message_id || Date.now()}`,
          scope: "platform_command",
          originContext,
          allowDeterministicShortcut: current.support_scope?.allow_deterministic_help_shortcuts === true
        });
        const delivered = finalizeAssistantResult(result, {
          role: permission.normalized_role,
          config: current,
          baseUrl: originContext.base_url,
          maxLength: originContext.max_message_length,
          requestMessage: ctx.argsText.trim()
        });
        const reply = formatPlatformReplyDetails(delivered.text, delivered.links, originContext);
        await ctx.reply(reply.text);
        commandMetrics.record({
          kind: "command_audit",
          status: "success",
          scope: "platform_command",
          route_used: result.route_used || "llm",
          role: permission.normalized_role,
          user_id: originContext.user_id,
          platform: originContext.platform,
          duration_ms: Date.now() - started,
          original_final_length: delivered.original_final_length,
          final_reply_length: reply.original_final_length,
          delivered_length: reply.delivered_length,
          final_tokens: estimateTokenCountFromLength(delivered.original_final_length),
          delivered_tokens: estimateTokenCountFromLength(reply.delivered_length),
          truncated: delivered.truncated || reply.delivered_length < delivered.original_final_length,
          delivery_action: reply.delivery_action !== "none" ? reply.delivery_action : delivered.delivery_action,
          ...controllerDeliveryFields(result),
          internal_generated_length: result.internal_generated_length || String(result.text || "").length
        });
      } catch (error) {
        commandMetrics.record({
          kind: "request",
          status: "failed",
          scope: "platform_command",
          route_used: "unavailable_fallback",
          role: permission.normalized_role,
          user_id: originContext.user_id,
          platform: originContext.platform,
          message: error.message,
          duration_ms: Date.now() - started
        });
        await ctx.reply(formatPlatformReply(current.commands.unavailable_message, [], originContext));
      }
      return true;
    }
  }]);
}

function authorizeAiRequest({ userId, context, accessControl, rateLimiter }) {
  const access = accessControl.check(userId, context);
  if (!access.allowed) return access;
  const rate = rateLimiter.check(context);
  if (!rate.allowed) {
    return {
      ...rate,
      message: `Lumi Assistant is rate limited. Try again in ${rate.retry_after_seconds}s.`
    };
  }
  return { allowed: true };
}

function webOriginContext(req) {
  const user = req.session.user;
  const role = roleOf(user);
  return {
    origin: "webui",
    platform: "webui",
    channel_id: null,
    channel_name: null,
    server_id: null,
    user_id: user.id,
    username: user.username,
    display_name: user.username,
    role,
    is_admin: role === "admin",
    is_mod: role === "mod",
    message_id: null,
    reply_mode: "panel",
    format_capabilities: { markdown: false, html: true },
    max_message_length: null,
    base_url: `${req.protocol}://${req.get("host")}`,
    source_plugin: "lumi_ai",
    source_command: "webui_assistant",
    permission_context: { identified_user: true, webui_actions_allowed: true }
  };
}

function diagnosticOriginContext(user, origin = "webui") {
  const role = roleOf(user);
  const limits = { webui: null, discord: 1900, twitch: 450, youtube: 1800, kick: 450, other: 1000 };
  return {
    origin,
    platform: origin,
    channel_id: "diagnostic-channel",
    channel_name: "Diagnostic channel",
    server_id: "diagnostic-server",
    user_id: user.id,
    username: user.username,
    display_name: user.username,
    role,
    is_admin: role === "admin",
    is_mod: role === "mod",
    message_id: "diagnostic-message",
    reply_mode: origin === "webui" ? "panel" : "same_channel",
    format_capabilities: {
      markdown: origin === "discord",
      html: origin === "webui"
    },
    max_message_length: origin === "webui" ? null : (limits[origin] || limits.other),
    source_plugin: "lumi_ai",
    source_command: "admin_diagnostic",
    permission_context: {
      identified_user: true,
      webui_actions_allowed: origin === "webui"
    }
  };
}

function finalizeAssistantResult(result, { role, config, baseUrl = "", maxLength = null, requestMessage = "" }) {
  const formatted = formatAssistantResponse({
    text: normalizeCustomCommandReply(result.text, requestMessage),
    links: result.links,
    baseUrl,
    verifiedRoutes: repoIndexer.verifiedRoutePaths(),
    role,
    allowModeratorCodeHelp: config.support_scope?.allow_moderator_code_help === true,
    maxLength
  });
  const output = { ...result, ...formatted };
  if (role !== "admin") {
    delete output.source;
    output.raw_response = null;
  }
  return output;
}

function controllerDeliveryFields(result = {}) {
  const decision = result.controller_decision || {};
  const profile = decision.source_profile || {};
  return {
    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,
    source_profile: profile.source,
    target_final_chars: profile.target_chars,
    hard_final_chars: profile.hard_chars,
    controller_fallback_used: decision.fallback_used
  };
}

function estimateTokenCountFromLength(value) {
  const number = Number(value);
  return Number.isFinite(number) && number > 0 ? Math.max(1, Math.ceil(number / 4)) : 0;
}

function normalizeCustomCommandReply(text, requestMessage) {
  const request = String(requestMessage || "");
  const output = String(text || "");
  if (!/\b(custom|advanced)\s+(?:javascript|js|python|command)|\bcustom command\b/i.test(request)) return output;
  if (/```(?:javascript|python)\s*\n[\s\S]*?```/i.test(output)) return output;

  const language = /\bpython\b/i.test(request) || /^\s*def\s+run\s*\(/m.test(output) ? "python" : "javascript";
  const codePattern = language === "python"
    ? /(^|\n)(def\s+run\s*\([\s\S]+)$/m
    : /(^|\n)((?:async\s+)?function\s+run\s*\([\s\S]+)$/m;
  const match = output.match(codePattern);
  if (!match) return output;
  const start = match.index + match[1].length;
  const explanation = output.slice(0, start).trim();
  const code = output.slice(start).trim();
  return `${explanation ? `${explanation}\n\n` : ""}\`\`\`${language}\n${code}\n\`\`\``;
}

function configuredBaseUrl(settings) {
  for (const key of ["discord_redirect_uri", "twitch_redirect_uri", "youtube_redirect_uri"]) {
    const value = settings?.getSetting?.(key, "");
    if (!value) continue;
    try { return new URL(value).origin; } catch {}
  }
  return "";
}

function paginateRows(rows, pageValue, pageSize = 25) {
  const size = Math.max(1, Number.parseInt(pageSize, 10) || 25);
  const pages = Math.max(1, Math.ceil(rows.length / size));
  const page = Math.min(pages, Math.max(1, Number.parseInt(pageValue, 10) || 1));
  const start = (page - 1) * size;
  return { entries: rows.slice(start, start + size), page, pages, page_size: size, total: rows.length };
}

function normalizeConversationHistory(history) {
  if (!Array.isArray(history)) return [];
  return history.slice(-12).map((entry) => ({
    role: ["user", "assistant"].includes(entry?.role) ? entry.role : null,
    content: cleanText(entry?.content, 4000)
  })).filter((entry) => entry.role && entry.content);
}

function searchKnownUsers(database, query) {
  const value = String(query || "").trim().replace(/[%_]/g, "");
  if (value.length < 2) return [];
  const pattern = `%${value}%`;
  const rows = database.prepare(
    "SELECT DISTINCT p.id, p.internal_username, i.provider, i.provider_user_id, i.display_name " +
    "FROM user_profiles p LEFT JOIN user_identities i ON i.user_id = p.id " +
    "WHERE p.id LIKE ? OR p.internal_username LIKE ? OR i.provider_user_id LIKE ? OR i.display_name LIKE ? " +
    "ORDER BY p.internal_username LIMIT 30"
  ).all(pattern, pattern, pattern, pattern);
  return [...rows.reduce((map, row) => {
    const user = map.get(row.id) || { id: row.id, username: row.internal_username, identities: [] };
    if (row.provider) {
      user.identities.push({
        provider: row.provider,
        provider_user_id: row.provider_user_id,
        display_name: row.display_name
      });
    }
    map.set(row.id, user);
    return map;
  }, new Map()).values()];
}

function parseCommandTriggers(value) {
  const triggers = String(value || "assistant,lumi").split(/[,\s]+/)
    .map((entry) => entry.trim().replace(/^!+/, "").toLowerCase())
    .filter((entry) => /^[a-z0-9_-]+$/.test(entry));
  return [...new Set(triggers.length ? triggers : ["assistant", "lumi"])];
}

function limitFromBody(body, prefix, fallbackRequests, fallbackWindow) {
  return {
    requests: boundedInt(body[`${prefix}_requests`], 0, 10000, fallbackRequests),
    window_seconds: boundedInt(body[`${prefix}_window`], 1, 86400, fallbackWindow)
  };
}

function sourceProfilesFromBody(body, current = {}) {
  return Object.fromEntries(Object.entries(SOURCE_DEFAULTS).map(([source, fallback]) => {
    const existing = current?.[source] || {};
    const hardRaw = body[`source_profile_${source}_hard_chars`];
    const targetFallback = existing.target_chars ?? fallback.target_chars;
    const hardFallback = existing.hard_chars ?? fallback.hard_chars ?? 1000;
    return [source, {
      source,
      target_chars: boundedInt(body[`source_profile_${source}_target_chars`], 100, 12000, targetFallback),
      hard_chars: String(hardRaw ?? "").trim() === ""
        ? null
        : boundedInt(hardRaw, 100, 12000, hardFallback),
      allow_sections: body[`source_profile_${source}_allow_sections`] === "on",
      allow_long_answer: body[`source_profile_${source}_allow_long_answer`] === "on",
      allow_split: body[`source_profile_${source}_allow_split`] === "on"
    }];
  }));
}

function writeCommandsManifest(pluginDir, config) {
  const triggers = config.commands.triggers || ["assistant", "lumi"];
  const primary = triggers[0] || "assistant";
  const manifest = {
    pluginId: PLUGIN_ID,
    pluginName: "Lumi AI",
    platformKeys: {
      discord: "command_platform_discord",
      twitch: "command_platform_twitch",
      youtube: "command_platform_youtube",
      kick: "command_platform_kick"
    },
    commands: [{
      id: "assistant",
      trigger: primary,
      usage: `${primary} <question>`,
      name: "Lumi Assistant",
      description: "Ask Lumi Assistant a scoped Lumi, bot, community, or WebUI support question.",
      level: "public",
      platforms: Object.entries(config.commands.platforms).filter(([, enabled]) => enabled).map(([key]) => key),
      aliases: triggers.slice(1)
    }]
  };
  writeJsonAtomicSync(path.join(pluginDir, "cmds.json"), manifest);
}

module.exports.shouldAutoResume = shouldAutoResume;
module.exports.registerAssistantCommands = registerAssistantCommands;
module.exports.authorizeAiRequest = authorizeAiRequest;
module.exports.searchKnownUsers = searchKnownUsers;
module.exports.finalizeAssistantResult = finalizeAssistantResult;
