// PRE Web GUI — Multi-Model Router
// Classifies incoming messages and dispatches to the appropriate model tier:
//   fast     — small local model, no tools, quick answers
//   standard — pre-gemma4 (26B MoE), full tool access (default)
//   frontier — remote provider (when configured), complex reasoning
//
// The router is opt-in. When disabled, everything goes through standard (current behavior).

const fs = require('fs');
const path = require('path');
const os = require('os');
const { loadProfile, confidence: calcConfidence, categorize, CATEGORIES, recordToolOutcome } = require('./calibration');

const ROUTER_FILE = path.join(os.homedir(), '.pre', 'router.json');

// ── Defaults ─────────────────────────────────────────────────────────────────

const DEFAULT_CONFIG = {
  enabled: true,             // Router on by default — fast for simple queries, standard for tool use
  fast_model: 'pre-gemma4-fast',  // Gemma 4 E2B (2B) — same family as main model, ~140 tok/s
  frontier_enabled: false,   // Use remote provider for frontier tier
  // Which provider config to dispatch the frontier tier to. 'inherit' = use the
  // active provider (legacy behavior). Setting this to a specific type (e.g.
  // 'gemini') lets the user keep local Ollama as the default for fast/standard
  // while frontier requests go to Gemini Flash without changing the user-facing
  // provider selection.
  frontier_provider: 'inherit',
  auto_escalate: true,       // Auto-escalate from fast→standard when tools are needed
};

// ── Config persistence ───────────────────────────────────────────────────────

let _configCache = null;
let _configMtime = 0;

function loadConfig() {
  try {
    const stat = fs.statSync(ROUTER_FILE);
    if (_configCache && stat.mtimeMs === _configMtime) return _configCache;
    const raw = JSON.parse(fs.readFileSync(ROUTER_FILE, 'utf-8'));
    _configCache = { ...DEFAULT_CONFIG, ...raw };
    _configMtime = stat.mtimeMs;
    return _configCache;
  } catch {
    return { ...DEFAULT_CONFIG };
  }
}

function saveConfig(config) {
  const merged = { ...DEFAULT_CONFIG, ...config };
  fs.writeFileSync(ROUTER_FILE, JSON.stringify(merged, null, 2), { mode: 0o600 });
  _configCache = merged;
  _configMtime = Date.now();
  return merged;
}

function getConfig() {
  return loadConfig();
}

function resetConfig() {
  try { fs.unlinkSync(ROUTER_FILE); } catch {}
  _configCache = null;
  _configMtime = 0;
  return { ...DEFAULT_CONFIG };
}

// ── Tier definitions ─────────────────────────────────────────────────────────

const TIERS = {
  fast: {
    name: 'fast',
    label: 'Fast',
    description: 'Quick answers from a small local model — no tools',
    useTools: false,
    useFullPrompt: false,
  },
  standard: {
    name: 'standard',
    label: 'Standard',
    description: 'Full agent with 26B MoE model and all tools',
    useTools: true,
    useFullPrompt: true,
  },
  frontier: {
    name: 'frontier',
    label: 'Frontier',
    description: 'Remote API model for complex reasoning',
    useTools: true,
    useFullPrompt: true,
  },
};

// ── Classification heuristics ────────────────────────────────────────────────

// Patterns that signal the user wants tool use (→ standard or frontier)
const TOOL_INTENT_PATTERNS = [
  // File operations
  /\b(read|write|edit|create|delete|open|save|list)\s+(the\s+)?(file|dir|folder|document)/i,
  // App launching — "open/launch <something> app" or imperative "open <name>"
  /\b(open|launch|start|run)\s+(the\s+)?([\w\s]+?\s+)?(app|application|program)\b/i,
  /^(please\s+)?(can\s+you\s+|could\s+you\s+)?(open|launch)\s+(the\s+)?(?!question|issue|problem|source|ended|minded|to\b)[\w]/i,
  /\b(show|display|cat|grep|find|search|glob)\b.*\b(file|director|folder|code|log)/i,
  // Shell / system
  /\b(run|execute|install|build|compile|deploy|restart|kill|stop)\b/i,
  /\b(bash|terminal|shell|command|script|npm|pip|make|docker|git)\b/i,
  // Web
  /\b(fetch|scrape|browse|download|web.?search|google|look.?up)\b/i,
  // Memory / artifacts
  /\b(remember|recall|forget|save.?to.?memory|artifact|generate.?(image|report|document))\b/i,
  // Cloud integrations
  /\b(email|gmail|slack|jira|github|calendar|confluence|sharepoint|microsoft|zoom|telegram|linear|figma|asana|dynamics|smartsheet|google\s+drive|google\s+docs)\b/i,
  // Integration / connector / configuration questions (need system context)
  /\b(connector|integration|connect|configured|connection|provider|api.?key|oauth|credential)\b/i,
  // Self-referential — questions about PRE / its capabilities need full system prompt
  /\b(pre|your|you)\b.{0,20}\b(tool|setting|config|feature|capabilit|support|available|enabled|disabled|installed)\b/i,
  /\b(what|which)\s+(tool|feature|integration|service|connector)s?\s+(are|is|do)\b/i,
  /\b(can\s+you|do\s+you|are\s+you)\s+(able|capable|support|access|connect|use)\b/i,
  // Agent / cron / RAG
  /\b(agent|delegate|cron|schedule|recurring|rag|index|workflow|trigger)\b/i,
  // Computer use
  /\b(click|screenshot|type|mouse|keyboard|desktop|browser|navigate)\b/i,
  // Real-time information — requires web search or tool use
  /\b(what\s+time|current\s+time|time\s+in|timezone|time\s+zone)\b/i,
  /\b(weather|forecast|temperature)\s+(in|at|for|today|tomorrow|this\s+week)\b/i,
  /\b(price|cost|rate|stock|crypto|bitcoin|worth)\s+(of|for|right\s+now|today|current)\b/i,
  /\b(current|latest|recent|live|real.?time|right\s+now|today'?s?)\s+(news|score|status|price|rate|weather|time|version|release|update)/i,
  // Recency signals — questions about events, results, or state that changes over time
  /\b(who\s+won|who\s+is\s+winning|what\s+happened|what's\s+happening|did\s+.{1,30}\s+win)\b/i,
  /\b(new\s+version|been\s+released|come\s+out|just\s+(launched|released|announced|dropped))\b/i,
  /\b(trending|viral|breaking\s+news|headlines)\b/i,
  /\b(how\s+much\s+(does|is|do|are|did))\b.*\b(cost|worth|price|charge|pay)\b/i,
  /\b(near\s+me|close\s+by|in\s+my\s+area|around\s+here|nearby)\b/i,
  /\b(this\s+(year|month|week|season|quarter)|last\s+(year|month|week|night|season))\b/i,
  // Explicit file paths
  /[~\/\\][\w.\-\/\\]+\.\w{1,6}\b/,
  // Code blocks or technical content that implies tool use
  /```/,
];

// Patterns that signal complex reasoning (→ frontier)
const FRONTIER_PATTERNS = [
  /\b(analyze|architect|design|plan|strategy|compare\s+and\s+contrast)\b.*\b(system|approach|option|trade.?off)/i,
  /\b(write|draft|compose)\s+(a\s+)?(detailed|comprehensive|thorough|in.?depth)\b/i,
  /\b(refactor|redesign|rewrite|rearchitect)\b.*\b(entire|whole|complete|full)\b/i,
  /\b(debug|diagnose|investigate)\s+(this\s+)?(complex|intermittent|race.?condition|memory.?leak)/i,
  /\b(review|audit)\s+(the\s+)?(security|performance|architecture|codebase)/i,
  /\bexplain\b.*\b(in\s+detail|thoroughly|step\s+by\s+step|how\s+.{10,}\s+works)\b/i,
];

// Patterns that signal simple queries (→ fast)
const FAST_PATTERNS = [
  /^(hi|hey|hello|sup|yo|thanks|thank you|ok|okay|got it|cool|nice|great)\s*[.!?]?$/i,
  /^(what|who|when|where|how)\s+(is|are|was|were|do|does|did)\s+/i,
  /^(can you|could you|would you)\s+(tell|explain|describe|define|summarize)\b/i,
  /^(what'?s?|how'?s?|who'?s?)\s+\w/i,
  /\b(define|meaning of|what does .{1,30} mean)\b/i,
  /\b(translate|convert|calculate|compute)\b/i,
  /^(yes|no|yep|nope|sure|nah|absolutely|definitely)\s*[.!?]?$/i,
];

// User override prefixes — bypass classification
const TIER_OVERRIDES = {
  '@fast': 'fast',
  '@quick': 'fast',
  '@standard': 'standard',
  '@full': 'standard',
  '@frontier': 'frontier',
  '@cloud': 'frontier',
  '@remote': 'frontier',
};

/**
 * Classify a user message into a routing tier.
 *
 * @param {string} message - The user's message text
 * @param {Object} [context] - Optional context about the session
 * @param {boolean} [context.hasHistory] - Whether the session has prior messages
 * @param {string} [context.lastTier] - The tier used for the previous message
 * @returns {{ tier: string, reason: string, override: string|null, strippedMessage: string }}
 */
function classify(message, context = {}) {
  const config = loadConfig();

  // If routing is disabled, everything is standard
  if (!config.enabled) {
    return { tier: 'standard', reason: 'routing disabled', override: null, strippedMessage: message };
  }

  // Per-session tier pin (set via /tier command)
  if (config._sessionPin) {
    const pin = config._sessionPin;
    if (pin === 'fast' && !config.fast_model) {
      return { tier: 'standard', reason: 'pinned to fast but no fast model configured', override: '/tier', strippedMessage: message };
    }
    if (pin === 'frontier' && !config.frontier_enabled) {
      return { tier: 'standard', reason: 'pinned to frontier but frontier not enabled', override: '/tier', strippedMessage: message };
    }
    return { tier: pin, reason: `pinned via /tier ${pin}`, override: '/tier', strippedMessage: message };
  }

  // Check for explicit tier override prefix
  const trimmed = message.trim();
  for (const [prefix, tier] of Object.entries(TIER_OVERRIDES)) {
    if (trimmed.toLowerCase().startsWith(prefix)) {
      const stripped = trimmed.slice(prefix.length).trim();
      // Don't route to fast if no fast model configured
      if (tier === 'fast' && !config.fast_model) {
        return { tier: 'standard', reason: `${prefix} override but no fast model configured`, override: prefix, strippedMessage: stripped || message };
      }
      // Don't route to frontier if no remote provider
      if (tier === 'frontier' && !config.frontier_enabled) {
        return { tier: 'standard', reason: `${prefix} override but frontier not enabled`, override: prefix, strippedMessage: stripped || message };
      }
      return { tier, reason: `user override: ${prefix}`, override: prefix, strippedMessage: stripped || message };
    }
  }

  // If this is a follow-up in a tool conversation, stay on standard
  // (don't downgrade mid-conversation to fast)
  if (context.hasHistory && context.lastTier === 'standard') {
    // Only consider escalating to frontier, not downgrading to fast
    if (config.frontier_enabled && matchesAny(trimmed, FRONTIER_PATTERNS)) {
      return { tier: 'frontier', reason: 'complex reasoning detected in active session', override: null, strippedMessage: message };
    }
    return { tier: 'standard', reason: 'continuing active session', override: null, strippedMessage: message };
  }

  // Check for tool intent — needs standard or frontier
  const needsTools = matchesAny(trimmed, TOOL_INTENT_PATTERNS);

  if (needsTools) {
    // Check if it's also frontier-worthy
    if (config.frontier_enabled && matchesAny(trimmed, FRONTIER_PATTERNS)) {
      return { tier: 'frontier', reason: 'complex reasoning with tool intent', override: null, strippedMessage: message };
    }
    return { tier: 'standard', reason: 'tool intent detected', override: null, strippedMessage: message };
  }

  // Check for frontier-level complexity (even without tools)
  if (config.frontier_enabled && matchesAny(trimmed, FRONTIER_PATTERNS)) {
    return { tier: 'frontier', reason: 'complex reasoning detected', override: null, strippedMessage: message };
  }

  // Check for simple/fast patterns
  let result;
  if (config.fast_model && matchesAny(trimmed, FAST_PATTERNS)) {
    result = { tier: 'fast', reason: 'simple query pattern matched', override: null, strippedMessage: message };
  }
  // Default: check message length as a heuristic
  // Short messages (< 80 chars) without tool signals → fast (if available)
  else if (config.fast_model && trimmed.length < 80 && !needsTools) {
    result = { tier: 'fast', reason: 'short message, no tool signals', override: null, strippedMessage: message };
  }
  // Default to standard
  else {
    result = { tier: 'standard', reason: 'default routing', override: null, strippedMessage: message };
  }

  // ── Calibration-aware adjustment ──────────────────────────────────────────
  // Consult calibration confidence and escalate/descend if warranted.
  const { tier: adjustedTier, calibrationNote } = adjustTierByConfidence(result.tier, trimmed, config);
  if (adjustedTier !== result.tier) {
    result.tier = adjustedTier;
    result.reason += ` → ${calibrationNote}`;
    result.calibrationNote = calibrationNote;
  }

  return result;
}

/**
 * Test if text matches any pattern in a list.
 */
function matchesAny(text, patterns) {
  return patterns.some(p => p.test(text));
}

/**
 * Get the tier definition object for a tier name.
 */
function getTier(name) {
  return TIERS[name] || TIERS.standard;
}

/**
 * Get the model name to use for a given tier.
 * Returns null if the default (pre-gemma4) should be used.
 */
function getModelForTier(tierName) {
  const config = loadConfig();
  switch (tierName) {
    case 'fast':
      return config.fast_model || null;
    case 'frontier':
      // Frontier uses the remote provider — model is set in provider config
      return null;
    case 'standard':
    default:
      return null; // Uses MODEL constant (pre-gemma4)
  }
}

/**
 * Check if the frontier tier should use the remote provider.
 */
function shouldUseRemoteProvider(tierName) {
  if (tierName !== 'frontier') return false;
  const config = loadConfig();
  return config.frontier_enabled;
}

/**
 * Build a streamChat-compatible provider object for the frontier tier when the
 * user has configured a frontier-specific provider that differs from the
 * default. Returns null when frontier_provider is 'inherit' (legacy: frontier
 * uses whatever provider is active), or when the requested type has no saved
 * config in connections.json.
 *
 * Saved configs live under _provider.{openai,azure,anthropic,gemini}. We read
 * them directly here rather than going through connections.getProvider() so
 * the frontier override doesn't have to flip _provider.active to function.
 */
function getFrontierProvider() {
  const config = loadConfig();
  if (!config.frontier_enabled) return null;
  const type = config.frontier_provider || 'inherit';
  if (type === 'inherit') return null;

  // Lazy require to avoid circular import (connections → ollama → router).
  const fs = require('fs');
  const path = require('path');
  const os = require('os');
  const connectionsFile = path.join(os.homedir(), '.pre', 'connections.json');
  let data;
  try { data = JSON.parse(fs.readFileSync(connectionsFile, 'utf-8')); } catch { return null; }
  const saved = data?._provider?.[type];
  if (!saved) return null;

  // Build a provider object matching the shape connections.getProvider()
  // produces for each type. Mirrors that function — kept here intentionally so
  // the frontier override can target a non-active provider without flipping
  // _provider.active.
  let baseUrl = (saved.base_url || '').replace(/\/+$/, '');
  if (type === 'gemini' && !baseUrl) baseUrl = 'https://generativelanguage.googleapis.com/v1beta';
  if (!baseUrl) return null;

  const out = {
    type,
    base_url: baseUrl,
    api_key: saved.api_key || '',
    model: saved.model || '',
    max_tokens: parseInt(saved.max_tokens, 10) || 4096,
  };
  if (type === 'anthropic') out.api_version = saved.api_version || '2023-06-01';
  if (type === 'azure') out.api_version = saved.api_version || '2024-10-21';
  return out;
}

// ── Calibration-aware tier adjustment ────────────────────────────────────────

// Keywords that map user messages to calibration categories
const CATEGORY_KEYWORDS = {
  file_ops:   /\b(file|folder|directory|read|write|edit|create|delete|move|copy|rename)\b/i,
  bash:       /\b(bash|shell|terminal|command|run|execute|script|npm|pip|make|docker|git|install|build|compile|deploy)\b/i,
  web:        /\b(web|search|fetch|browse|scrape|download|http|url|google|look.?up)\b/i,
  memory:     /\b(remember|recall|forget|memory|save.?to.?memory)\b/i,
  code:       /\b(code|function|class|variable|bug|debug|refactor|test|lint|type|import|module)\b/i,
  calendar:   /\b(calendar|event|meeting|schedule|appointment|reminder|due|deadline)\b/i,
  email:      /\b(email|mail|inbox|send|reply|draft|message|slack)\b/i,
  cloud:      /\b(github|jira|confluence|sharepoint|smartsheet|linear|asana|figma|zoom|dynamics)\b/i,
  image:      /\b(image|photo|picture|generate|draw|create.*(image|photo|picture))\b/i,
  documents:  /\b(document|report|artifact|docx|xlsx|pdf|csv|html)\b/i,
  rag:        /\b(rag|index|semantic|embed|vector|search.*(file|document|content))\b/i,
  computer:   /\b(click|screenshot|desktop|mouse|keyboard|type|scroll|open.?app|window|screen)\b/i,
};

/**
 * Infer calibration categories from a user message.
 * Returns an array of category keys that the message likely involves.
 */
function inferCategories(message) {
  const cats = [];
  for (const [cat, pattern] of Object.entries(CATEGORY_KEYWORDS)) {
    if (pattern.test(message)) cats.push(cat);
  }
  return cats;
}

/**
 * Get the lowest calibration confidence across inferred categories.
 * Returns { minConfidence: number|null, category: string|null }
 */
function getCalibrationConfidence(message) {
  const cats = inferCategories(message);
  if (cats.length === 0) return { minConfidence: null, category: null };

  const profile = loadProfile();
  let minConf = null;
  let minCat = null;

  for (const cat of cats) {
    const catData = profile.categories[cat];
    if (!catData) continue;
    const conf = calcConfidence(catData);
    if (conf !== null && (minConf === null || conf < minConf)) {
      minConf = conf;
      minCat = cat;
    }
  }

  return { minConfidence: minConf, category: minCat };
}

/**
 * Adjust tier based on calibration confidence.
 * - Low confidence (<0.5) on fast → escalate to standard
 * - Very low confidence (<0.3) with frontier enabled → escalate to frontier
 * - High confidence (>0.85) → allow descent to fast more aggressively
 *
 * @param {string} tier - The initially classified tier
 * @param {string} message - The user's message
 * @param {Object} config - Router config
 * @returns {{ tier: string, calibrationNote: string|null }}
 */
function adjustTierByConfidence(tier, message, config) {
  const { minConfidence, category } = getCalibrationConfidence(message);
  if (minConfidence === null) return { tier, calibrationNote: null };

  const label = CATEGORIES[category]?.label || category;

  // Very low confidence → prefer frontier if available
  if (minConfidence < 0.3 && config.frontier_enabled && tier !== 'frontier') {
    return {
      tier: 'frontier',
      calibrationNote: `Routed to frontier — low confidence on ${label} (${Math.round(minConfidence * 100)}%)`,
    };
  }

  // Low confidence on fast → escalate to standard
  if (minConfidence < 0.5 && tier === 'fast') {
    return {
      tier: 'standard',
      calibrationNote: `Escalated from fast — low confidence on ${label} (${Math.round(minConfidence * 100)}%)`,
    };
  }

  return { tier, calibrationNote: null };
}

/**
 * Record a routing outcome for per-tier calibration learning.
 * Called after each tool turn so calibration can learn per-tier accuracy.
 *
 * @param {string} toolName - The tool that was used
 * @param {string} tier - The tier that handled this turn
 * @param {boolean} success - Whether the tool call succeeded
 */
function recordRoutingOutcome(toolName, tier, success) {
  // Record to calibration's existing per-category tracking
  recordToolOutcome(toolName, success);

  // Also append tier info to a lightweight per-tier log
  // (stored alongside calibration data for future per-tier analysis)
  try {
    const tierLog = path.join(os.homedir(), '.pre', 'tier-outcomes.jsonl');
    const entry = JSON.stringify({
      ts: Date.now(),
      tool: toolName,
      tier,
      success,
      categories: categorize(toolName),
    });
    fs.appendFileSync(tierLog, entry + '\n');
  } catch (_e) {
    // Non-blocking — telemetry should never break the tool loop
  }
}

module.exports = {
  classify,
  getTier,
  getModelForTier,
  shouldUseRemoteProvider,
  getFrontierProvider,
  getConfig,
  saveConfig,
  resetConfig,
  recordRoutingOutcome,
  inferCategories,
  getCalibrationConfidence,
  adjustTierByConfidence,
  TIERS,
  DEFAULT_CONFIG,
};
