const INTENTS = new Set([
  "casual",
  "factual",
  "support",
  "explain_internal",
  "admin_task",
  "codex_task",
  "feedback_export",
  "debug",
  "unknown"
]);
const COMPLEXITIES = new Set(["fast", "normal", "expanded", "unlimited"]);
const OKF_DEPTHS = new Set(["none", "light", "deep"]);
const ANSWER_STYLES = new Set(["compact", "normal", "detailed", "json_only"]);
const SOURCE_DEFAULTS = Object.freeze({
  webui: { hard_chars: null, target_chars: 2500, allow_sections: true, allow_long_answer: true, allow_split: false },
  discord: { hard_chars: 1900, target_chars: 1200, allow_sections: true, allow_long_answer: false, allow_split: false },
  twitch: { hard_chars: 450, target_chars: 320, allow_sections: false, allow_long_answer: false, allow_split: false },
  youtube: { hard_chars: 1800, target_chars: 900, allow_sections: false, allow_long_answer: false, allow_split: false },
  kick: { hard_chars: 450, target_chars: 320, allow_sections: false, allow_long_answer: false, allow_split: false },
  other: { hard_chars: 1000, target_chars: 700, allow_sections: false, allow_long_answer: false, allow_split: false }
});
const MODE_BUDGETS = Object.freeze({
  fast: 1024,
  normal: 4096,
  expanded: 8192,
  unlimited: 16384
});

function buildControllerDecision({
  message = "",
  role = "user",
  scope = "assistant",
  originContext = null,
  gateDecision = null,
  requestClass = "simple_answer",
  config = {}
} = {}) {
  const text = String(gateDecision?.message || message || "");
  const source = sourceKey(originContext);
  const signals = detectSignals(text, role, scope);
  const lowConfidenceGate = gateDecision && (
    gateDecision.confidence < 0.5 ||
    /(?:low_confidence|timeout|error|invalid|unsafe|escalated)/i.test(gateDecision.reason_code || "")
  );
  let intent = intentFromSignals(signals, requestClass);
  let complexity = complexityFromSignals(signals, requestClass, role);
  let okfRetrieval = okfDepthFromSignals(signals, complexity);
  let answerStyle = answerStyleFromSignals(signals, complexity, source);

  if (lowConfidenceGate && complexity === "fast") {
    complexity = "normal";
    okfRetrieval = okfRetrieval === "none" && signals.needsOkf ? "light" : okfRetrieval;
  }
  if (gateDecision?.forced && complexity === "fast") {
    complexity = "normal";
  }
  if (signals.adminOnly && role !== "admin" && complexity === "unlimited") {
    complexity = "expanded";
  }

  const decision = normalizeControllerDecision({
    schema: "lumi.ai.controller.v1",
    route: gateDecision?.route || "main_llm",
    intent,
    complexity,
    okf_retrieval: okfRetrieval,
    answer_style: answerStyle,
    source_scoped: true,
    source_profile: buildSourceProfile(source, config),
    permission_sensitive: signals.permissionSensitive,
    admin_only: signals.adminOnly,
    risk_of_private_data: signals.privateDataRisk,
    confidence: gateDecision ? gateDecision.confidence : confidenceFromSignals(signals),
    reason_code: controllerReason(signals, gateDecision, lowConfidenceGate),
    legacy_request_class: requestClass,
    gate_reason_code: gateDecision?.reason_code || null,
    fallback_used: Boolean(lowConfidenceGate)
  });
  return decision;
}

function normalizeControllerDecision(value = {}) {
  const sourceProfile = value.source_profile && typeof value.source_profile === "object"
    ? value.source_profile
    : SOURCE_DEFAULTS.other;
  const requestedSource = String(sourceProfile.source || "other").toLowerCase();
  const source = SOURCE_DEFAULTS[requestedSource] ? requestedSource : "other";
  const fallbackProfile = SOURCE_DEFAULTS[source];
  return {
    schema: "lumi.ai.controller.v1",
    route: ["main_llm", "refusal", "unavailable", "cached_answer", "predefined_answer"].includes(value.route)
      ? value.route
      : "main_llm",
    intent: INTENTS.has(value.intent) ? value.intent : "unknown",
    complexity: COMPLEXITIES.has(value.complexity) ? value.complexity : "normal",
    okf_retrieval: OKF_DEPTHS.has(value.okf_retrieval) ? value.okf_retrieval : "light",
    answer_style: ANSWER_STYLES.has(value.answer_style) ? value.answer_style : "normal",
    source_scoped: value.source_scoped !== false,
    source_profile: {
      source,
      hard_chars: sourceProfile.hard_chars === null || sourceProfile.hard_chars === undefined
        ? fallbackProfile.hard_chars
        : clampChars(sourceProfile.hard_chars, fallbackProfile.hard_chars || 1000),
      target_chars: clampChars(sourceProfile.target_chars, fallbackProfile.target_chars),
      allow_sections: Boolean(sourceProfile.allow_sections),
      allow_long_answer: Boolean(sourceProfile.allow_long_answer),
      allow_split: Boolean(sourceProfile.allow_split)
    },
    permission_sensitive: Boolean(value.permission_sensitive),
    admin_only: Boolean(value.admin_only),
    risk_of_private_data: Boolean(value.risk_of_private_data),
    confidence: Math.max(0, Math.min(1, Number(value.confidence) || 0)),
    reason_code: /^[a-z0-9_]{2,80}$/.test(String(value.reason_code || ""))
      ? value.reason_code
      : "controller_normalized",
    legacy_request_class: String(value.legacy_request_class || "simple_answer"),
    gate_reason_code: value.gate_reason_code ? String(value.gate_reason_code).slice(0, 100) : null,
    fallback_used: Boolean(value.fallback_used)
  };
}

function outputBudgetForController({ config = {}, requestClass = "simple_answer", explicitMaxTokens = null, controllerDecision = null } = {}) {
  const requested = Number(explicitMaxTokens);
  if (Number.isFinite(requested) && requested > 0) return clampBudget(requested);
  const mode = controllerDecision?.complexity || modeFromRequestClass(requestClass);
  const modeBudget = Number(MODE_BUDGETS[mode]);
  if (Number.isFinite(modeBudget) && modeBudget > 0) return clampBudget(modeBudget);
  const classBudget = Number(config.output_budgets?.[requestClass]);
  if (Number.isFinite(classBudget) && classBudget > 0) return clampBudget(classBudget);
  const fallback = Number(config.max_output_tokens);
  return clampBudget(Number.isFinite(fallback) && fallback > 0 ? fallback : 512);
}

function okfLimitForController(decision) {
  if (!decision || decision.okf_retrieval === "none") return 0;
  if (decision.okf_retrieval === "deep") return decision.complexity === "expanded" || decision.complexity === "unlimited" ? 5 : 4;
  return 2;
}

function buildSourceProfile(source, config = {}) {
  const key = SOURCE_DEFAULTS[source] ? source : "other";
  const configured = config.source_profiles?.[key] || {};
  const merged = { ...SOURCE_DEFAULTS[key], ...configured, source: key };
  return {
    source: key,
    hard_chars: merged.hard_chars === null || merged.hard_chars === undefined
      ? null
      : Math.max(100, Math.min(12000, Number(merged.hard_chars) || SOURCE_DEFAULTS[key].hard_chars || 1000)),
    target_chars: Math.max(100, Math.min(12000, Number(merged.target_chars) || SOURCE_DEFAULTS[key].target_chars)),
    allow_sections: merged.allow_sections !== false,
    allow_long_answer: merged.allow_long_answer === true,
    allow_split: merged.allow_split === true
  };
}

function detectSignals(message, role, scope) {
  const text = String(message || "");
  const routeOrApi = /\b(?:GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\s+\/[^\s]+/i.test(text) ||
    /\b(?:route|webroute|web route|endpoint|request|api)\b[\s\S]{0,80}\/[a-z0-9_/-]+/i.test(text) ||
    /\/(?:admin|api|setup|auth|plugins|commands|feedback|stats|pages)(?:\/[a-z0-9_/-]*)?\b/i.test(text);
  const lumiInternal = /\b(lumi|webui|route|endpoint|api|plugin|okf|settings?|setup|wizard|admin page|moderator|permission|config|configuration|update|dashboard|navigation)\b/i.test(text) || routeOrApi;
  const codex = /\b(codex|taskfile|task file|implementation plan|acceptance criteria|validation steps|json output)\b/i.test(text);
  const feedbackExport = /\b(feedback.*export|export.*feedback|feedback-to-codex|internal-feedback-to-codex-taskfile)\b/i.test(text);
  const debug = /\b(debug|diagnos|troubleshoot|stack trace|runtime|backend|database|logs?|metrics?|failed|failure|error|exception)\b/i.test(text);
  const security = /\b(security|permission|private|secret|token|password|credential|audit|rbac|role|admin-only|sensitive)\b/i.test(text);
  const unlimited = role === "admin" && /\b(large-scale|architecture analysis|security audit|incident investigation|major migration|major refactor|comprehensive report|extensive synthesis|full system review)\b/i.test(text);
  const support = /\b(how do i|how to|what does|tell me about|explain|configure|set up|setup|support|help with|why does)\b/i.test(text);
  const knowledgeLookup = isSimpleKnowledgeLookup(text);
  const casual = text.split(/\s+/).length <= 18 && !lumiInternal && !debug && !codex && !security && !knowledgeLookup;
  return {
    routeOrApi,
    lumiInternal,
    knowledgeLookup,
    needsOkf: lumiInternal || support || debug || codex || feedbackExport || knowledgeLookup,
    codex,
    feedbackExport,
    debug,
    security,
    unlimited,
    support,
    casual,
    permissionSensitive: security || /\b(admin|moderator|mod|permission|role|private)\b/i.test(text),
    adminOnly: role === "admin" && (debug || security || unlimited || scope === "model_test"),
    privateDataRisk: security || /\b(my|mine|our|token|secret|password|credential|user id|database)\b/i.test(text)
  };
}

function intentFromSignals(signals, requestClass) {
  if (signals.feedbackExport) return "feedback_export";
  if (signals.codex) return "codex_task";
  if (signals.debug) return "debug";
  if (signals.security || signals.adminOnly) return "admin_task";
  if (signals.lumiInternal) return "explain_internal";
  if (signals.knowledgeLookup) return "factual";
  if (signals.support) return "support";
  if (requestClass === "navigation_help") return "support";
  if (signals.casual) return "casual";
  return "factual";
}

function complexityFromSignals(signals, requestClass, role) {
  if (signals.unlimited) return "unlimited";
  if (signals.codex || signals.feedbackExport || signals.debug || signals.security || requestClass === "admin_debug" || requestClass === "explicit_long") {
    return "expanded";
  }
  if (signals.knowledgeLookup) return "fast";
  if (signals.lumiInternal || signals.support || requestClass === "code_custom_command") return "normal";
  if (signals.casual && role !== "admin") return "fast";
  return "normal";
}

function okfDepthFromSignals(signals, complexity) {
  if (!signals.needsOkf) return "none";
  if (complexity === "expanded" || complexity === "unlimited" || signals.routeOrApi || signals.debug) return "deep";
  return "light";
}

function answerStyleFromSignals(signals, complexity, source) {
  if (signals.codex || signals.feedbackExport) return "json_only";
  if (complexity === "expanded" || complexity === "unlimited") return "detailed";
  if (source === "twitch" || source === "kick") return "compact";
  return "normal";
}

function confidenceFromSignals(signals) {
  if (signals.routeOrApi || signals.codex || signals.feedbackExport || signals.debug) return 0.9;
  if (signals.knowledgeLookup) return 0.82;
  if (signals.lumiInternal || signals.support) return 0.8;
  if (signals.casual) return 0.75;
  return 0.6;
}

function controllerReason(signals, gateDecision, lowConfidenceGate) {
  if (lowConfidenceGate) return "gate_fallback_normal";
  if (gateDecision?.forced) return "forced_main_llm";
  if (signals.unlimited) return "admin_unlimited_request";
  if (signals.codex) return "codex_expanded";
  if (signals.feedbackExport) return "feedback_export_expanded";
  if (signals.debug) return "debug_expanded";
  if (signals.routeOrApi) return "route_docs_okf";
  if (signals.lumiInternal) return "lumi_internal_okf";
  if (signals.knowledgeLookup) return "simple_knowledge_okf";
  if (signals.support) return "support_okf";
  if (signals.casual) return "casual_fast";
  return "default_normal";
}

function modeFromRequestClass(requestClass) {
  if (requestClass === "navigation_help" || requestClass === "simple_answer") return "fast";
  if (requestClass === "admin_debug" || requestClass === "explicit_long") return "expanded";
  return "normal";
}

function sourceKey(originContext) {
  const source = String(originContext?.origin || originContext?.platform || "webui").toLowerCase();
  return SOURCE_DEFAULTS[source] ? source : "other";
}

function clampBudget(value) {
  return Math.max(64, Math.min(32768, Math.round(Number(value) || 512)));
}

function clampChars(value, fallback) {
  const number = Number(value);
  return Math.max(100, Math.min(12000, Math.round(Number.isFinite(number) ? number : fallback)));
}

function isSimpleKnowledgeLookup(message) {
  const text = String(message || "").trim();
  if (!text || text.length > 180 || text.split(/\s+/).length > 18) return false;
  if (/\b(who|what)\s+(?:are|r)\s+you\b|\byour\s+(?:name|identity)\b/i.test(text)) return false;
  return (
    /^(?:who|what)\s+(?:is|are|was|were)\s+["'`]?[\p{L}\p{N}_ .'-]{2,80}["'`]?\??$/iu.test(text) ||
    /^(?:tell me about|describe|identify)\s+["'`]?[\p{L}\p{N}_ .'-]{2,80}["'`]?\??$/iu.test(text)
  );
}

module.exports = {
  MODE_BUDGETS,
  SOURCE_DEFAULTS,
  buildControllerDecision,
  buildSourceProfile,
  isSimpleKnowledgeLookup,
  normalizeControllerDecision,
  okfLimitForController,
  outputBudgetForController
};
