// PRE Web GUI — Self-Improving Skills Engine
// Analyzes completed tool chains and auto-creates reusable custom tools.
// Runs as a background task after tool loops (alongside memory + experience).
//
// Enhanced with effectiveness tracking: records success/failure/correction outcomes
// per skill, calculates effectiveness scores, and recommends relevant skills
// for new tasks based on tool-chain similarity.

const fs = require('fs');
const path = require('path');
const { streamChat } = require('./ollama');
const { loadCustomTools, saveCustomTool } = require('./custom-tools');
const { PRE_DIR } = require('./constants');

// ── Constants ─────────────────────────────────────────────────────────────────

const SKILL_COOLDOWN_MS = 180000; // 3 minutes between analyses
const MIN_TOOL_CALLS = 3;         // Minimum tool calls to trigger analysis
const MAX_CHAIN_LENGTH = 15;      // Max tool calls to analyze (trim oldest)
const OVERLAP_THRESHOLD = 0.70;   // Reject proposed skills with >70% step overlap
const EFFECTIVENESS_FILE = path.join(PRE_DIR, 'skill-effectiveness.json');

// ── State ─────────────────────────────────────────────────────────────────────

const state = {
  running: false,
  lastAnalyzeTime: 0,
};

// ── Chain Extraction ──────────────────────────────────────────────────────────

/**
 * Extract tool call chains from conversation messages.
 * Returns an array of { tool, args, result, success } objects.
 */
function extractToolChains(messages) {
  const chains = [];

  for (let i = 0; i < messages.length; i++) {
    const msg = messages[i];

    // Assistant messages with tool_calls
    if (msg.role === 'assistant' && msg.tool_calls) {
      for (const tc of msg.tool_calls) {
        const name = tc.function?.name || '';
        const args = tc.function?.arguments || {};
        chains.push({ tool: name, args, result: null, success: true });
      }
    }

    // Tool result messages — match to the most recent chain entry
    if (msg.role === 'tool' && msg.content) {
      const content = msg.content || '';
      // Parse tool_response tags
      const responses = content.match(/<tool_response name="([^"]+)">([\s\S]*?)<\/tool_response>/g) || [];
      for (const resp of responses) {
        const nameMatch = resp.match(/name="([^"]+)"/);
        const bodyMatch = resp.match(/<tool_response[^>]*>([\s\S]*?)<\/tool_response>/);
        if (nameMatch && bodyMatch) {
          const toolName = nameMatch[1];
          const body = bodyMatch[1].trim();
          const isError = body.startsWith('Error:') || body.includes('Error:');
          // Find matching chain entry (last one with this tool name and no result)
          for (let j = chains.length - 1; j >= 0; j--) {
            if (chains[j].tool === toolName && chains[j].result === null) {
              chains[j].result = body.slice(0, 500); // Cap for prompt
              chains[j].success = !isError;
              break;
            }
          }
        }
      }
    }
  }

  return chains.slice(-MAX_CHAIN_LENGTH);
}

// ── Deduplication ─────────────────────────────────────────────────────────────

/**
 * Check if a proposed skill overlaps with existing custom tools.
 * Compares tool step sequences — if >OVERLAP_THRESHOLD of steps match, it's a dupe.
 */
function isDuplicate(proposed, existingTools) {
  if (!proposed.implementation?.steps) return false;
  const proposedSteps = proposed.implementation.steps.map(s => s.tool);

  for (const existing of existingTools) {
    // Check name similarity
    if (existing.name === proposed.name) return true;

    if (!existing.implementation?.steps) continue;
    const existingSteps = existing.implementation.steps.map(s => s.tool);

    // Calculate step overlap
    const common = proposedSteps.filter(s => existingSteps.includes(s));
    const maxLen = Math.max(proposedSteps.length, existingSteps.length);
    if (maxLen > 0 && common.length / maxLen >= OVERLAP_THRESHOLD) return true;
  }
  return false;
}

// ── Main Entry Point ──────────────────────────────────────────────────────────

/**
 * Analyze a completed conversation for reusable tool patterns.
 * If found, auto-creates a custom tool (chain type).
 *
 * @param {Array} messages - Full session messages
 * @param {Object} opts - { sessionId, cwd }
 * @returns {Promise<Array>} Created skill objects (name + description)
 */
async function analyzeForSkills(messages, opts = {}) {
  // Gate: cooldown
  if (state.running) return [];
  const elapsed = Date.now() - state.lastAnalyzeTime;
  if (elapsed < SKILL_COOLDOWN_MS) return [];

  // Gate: minimum tool usage
  const toolMessages = messages.filter(m => m.role === 'tool');
  if (toolMessages.length < MIN_TOOL_CALLS) return [];

  state.running = true;
  state.lastAnalyzeTime = Date.now();

  try {
    const chains = extractToolChains(messages);
    if (chains.length < 2) return [];

    // Only analyze successful chains
    const successChains = chains.filter(c => c.success);
    if (successChains.length < 2) return [];

    const existingTools = loadCustomTools();

    // Build compact chain summary for the model
    const chainSummary = successChains.map((c, i) =>
      `${i + 1}. ${c.tool}(${JSON.stringify(c.args).slice(0, 200)}) → ${(c.result || '').slice(0, 150)}`
    ).join('\n');

    const result = await streamChat({
      messages: [
        {
          role: 'system',
          content: `You analyze tool execution chains to find reusable patterns. Given a sequence of tool calls that completed successfully, determine if they form a reusable workflow worth saving as a custom tool.

CRITERIA for a good skill:
- 2+ tools used in sequence that form a logical unit
- The pattern would be useful in future, similar tasks
- The steps have clear input parameters that can be generalized
- NOT a one-off investigation or debugging session

OUTPUT: Return ONLY valid JSON (no markdown, no explanation):
{ "worth_saving": false }
OR
{
  "worth_saving": true,
  "tool": {
    "name": "skill_name_here",
    "description": "What this skill does",
    "parameters": [{"name": "param1", "type": "string", "description": "...", "required": true}],
    "implementation": {
      "type": "chain",
      "steps": [{"tool": "tool_name", "args": {"key": "\${param1}"}}]
    },
    "guidance": "Behavioral instructions in Markdown. Include:\n- When to use this skill vs alternatives\n- Decision points: what to check before each step, how to handle failures\n- Quality criteria: how to know the result is correct\n- Common pitfalls to avoid\nKeep it concise (3-8 bullet points). Focus on decision logic, not mechanical steps."
  }
}

Use snake_case for names. Use \${param} syntax for parameter substitution in step args.
The "guidance" field teaches the model HOW TO THINK about using this skill — not just what tools to call.`,
        },
        {
          role: 'user',
          content: `Analyze this tool chain for reusable patterns:\n\n${chainSummary}`,
        },
      ],
      maxTokens: 1536,
      think: false,
      onToken: () => {},
    });

    // Parse the response
    let parsed;
    try {
      // Extract JSON from response (model might wrap in backticks)
      const jsonStr = (result.response || '').replace(/```json?\n?/g, '').replace(/```/g, '').trim();
      parsed = JSON.parse(jsonStr);
    } catch {
      return [];
    }

    if (!parsed.worth_saving || !parsed.tool) return [];

    const proposed = parsed.tool;

    // Validate required fields
    if (!proposed.name || !proposed.description || !proposed.implementation) return [];

    // Check for duplicates
    if (isDuplicate(proposed, existingTools)) {
      console.log(`[skills] Proposed skill "${proposed.name}" overlaps with existing tool — skipped`);
      return [];
    }

    // Add metadata
    proposed.source = 'auto';
    proposed.created = new Date().toISOString();
    proposed.usage_count = 0;
    proposed.last_used = null;
    // Preserve guidance if the model generated it
    if (proposed.guidance && typeof proposed.guidance === 'string') {
      proposed.guidance = proposed.guidance.trim();
    }

    saveCustomTool(proposed);
    console.log(`[skills] Created skill: ${proposed.name} — ${proposed.description}`);
    return [{ name: proposed.name, description: proposed.description }];
  } finally {
    state.running = false;
  }
}

// ── Effectiveness Tracking ────────────────────────────────────────────────────

/**
 * Load skill effectiveness data.
 * Format: { [skillName]: { successes, failures, corrections, uses, lastUsed, lastOutcome } }
 */
function loadEffectiveness() {
  try {
    if (fs.existsSync(EFFECTIVENESS_FILE)) {
      return JSON.parse(fs.readFileSync(EFFECTIVENESS_FILE, 'utf-8'));
    }
  } catch (_e) {}
  return {};
}

function saveEffectiveness(data) {
  try {
    fs.writeFileSync(EFFECTIVENESS_FILE, JSON.stringify(data, null, 2));
  } catch (err) {
    console.log(`[skills] Failed to save effectiveness data: ${err.message}`);
  }
}

/**
 * Record a skill execution outcome.
 * @param {string} skillName - Name of the custom tool / skill
 * @param {'success'|'failure'|'correction'} outcome
 */
function recordOutcome(skillName, outcome) {
  const data = loadEffectiveness();
  if (!data[skillName]) {
    data[skillName] = { successes: 0, failures: 0, corrections: 0, uses: 0, lastUsed: null, lastOutcome: null };
  }
  const entry = data[skillName];
  entry.uses++;
  entry.lastUsed = new Date().toISOString();
  entry.lastOutcome = outcome;

  if (outcome === 'success') entry.successes++;
  else if (outcome === 'failure') entry.failures++;
  else if (outcome === 'correction') entry.corrections++;

  saveEffectiveness(data);
}

/**
 * Calculate effectiveness score for a skill (0.0 to 1.0).
 * Corrections penalized 2x failures.
 * Returns null if insufficient data (<3 uses).
 */
function effectiveness(skillName) {
  const data = loadEffectiveness();
  const entry = data[skillName];
  if (!entry || entry.uses < 3) return null;

  const effectiveTotal = entry.uses + (entry.corrections * 2);
  return Math.max(0, Math.min(1, (entry.successes) / (effectiveTotal || 1)));
}

/**
 * Get effectiveness report for all tracked skills.
 */
function getEffectivenessReport() {
  const data = loadEffectiveness();
  const report = [];

  for (const [name, entry] of Object.entries(data)) {
    const score = effectiveness(name);
    report.push({
      name,
      score: score !== null ? Math.round(score * 100) : null,
      ...entry,
    });
  }

  return report.sort((a, b) => (b.score || 0) - (a.score || 0));
}

// ── Skill Recommendation ─────────────────────────────────────────────────────

/**
 * Recommend relevant skills based on the tools about to be used.
 * Matches the tool chain against existing skills by step overlap.
 *
 * @param {string[]} toolNames - Tools the user seems to need
 * @returns {Array<{ name, description, score, effectiveness, guidance }>} Ranked recommendations
 */
function recommendSkills(toolNames) {
  if (!toolNames || toolNames.length === 0) return [];

  const existingTools = loadCustomTools();
  if (existingTools.length === 0) return [];

  const toolSet = new Set(toolNames.map(t => t.toLowerCase()));
  const recommendations = [];

  for (const tool of existingTools) {
    if (!tool.implementation?.steps) continue;
    const steps = tool.implementation.steps.map(s => (s.tool || '').toLowerCase());
    const overlap = steps.filter(s => toolSet.has(s)).length;
    if (overlap === 0) continue;

    const score = overlap / Math.max(steps.length, toolSet.size);
    const eff = effectiveness(tool.name);

    // Skip skills with very low effectiveness
    if (eff !== null && eff < 0.3) continue;

    recommendations.push({
      name: tool.name,
      description: tool.description || '',
      matchScore: Math.round(score * 100),
      effectiveness: eff !== null ? Math.round(eff * 100) : null,
      usageCount: tool.usage_count || 0,
      guidance: tool.guidance || null,
    });
  }

  return recommendations
    .sort((a, b) => {
      // Sort by match score, then effectiveness, then usage
      const aScore = a.matchScore + (a.effectiveness || 50);
      const bScore = b.matchScore + (b.effectiveness || 50);
      return bScore - aScore;
    })
    .slice(0, 5);
}

/**
 * Build a skill recommendation context block for the system prompt.
 * Shows top skills that might be relevant to the user's query.
 *
 * @param {string} userQuery - The user's message
 * @returns {string} Context block or empty string
 */
function buildSkillRecommendationContext(userQuery) {
  if (!userQuery) return '';

  // Extract likely tool names from the query using keyword heuristics
  const toolKeywords = {
    'file': ['read_file', 'write_file', 'edit_file', 'list_files'],
    'search': ['search_files', 'web_search', 'rag'],
    'bash': ['bash'],
    'shell': ['bash'],
    'run': ['bash'],
    'email': ['apple_mail', 'google_mail'],
    'calendar': ['apple_calendar'],
    'reminder': ['apple_reminders'],
    'github': ['github'],
    'jira': ['jira'],
    'slack': ['slack'],
    'image': ['image_generate'],
    'screenshot': ['computer'],
    'browser': ['browser'],
    'web': ['web_search', 'web_fetch'],
    'memory': ['memory_save', 'memory_search'],
    'document': ['create_document', 'create_artifact'],
    'deploy': ['bash', 'github'],
    'test': ['bash'],
    'install': ['bash'],
  };

  const queryLower = userQuery.toLowerCase();
  const matchedTools = new Set();
  for (const [keyword, tools] of Object.entries(toolKeywords)) {
    if (queryLower.includes(keyword)) {
      for (const t of tools) matchedTools.add(t);
    }
  }

  if (matchedTools.size === 0) return '';

  const recs = recommendSkills([...matchedTools]);
  if (recs.length === 0) return '';

  // Progressive disclosure:
  // - High match (>80%): full guidance block (Level 2 — behavioral instructions)
  // - Lower match: one-liner name + description (Level 1 — awareness only)
  const GUIDANCE_THRESHOLD = 80;
  const lines = ['AVAILABLE SKILLS (custom tools you\'ve built that may help):'];
  for (const r of recs.slice(0, 3)) {
    const effStr = r.effectiveness !== null ? ` (${r.effectiveness}% effective)` : '';
    lines.push(`- ${r.name}: ${r.description}${effStr}`);

    // Level 2: inject full behavioral guidance for high-confidence matches
    if (r.matchScore >= GUIDANCE_THRESHOLD && r.guidance) {
      lines.push(`  GUIDANCE for ${r.name}:`);
      // Indent each line of guidance for readability
      for (const gLine of r.guidance.split('\n')) {
        lines.push(`    ${gLine}`);
      }
    }
  }
  return lines.join('\n');
}


module.exports = {
  analyzeForSkills,
  extractToolChains,
  isDuplicate,
  recordOutcome,
  effectiveness,
  getEffectivenessReport,
  recommendSkills,
  buildSkillRecommendationContext,
  loadEffectiveness,
  SKILL_COOLDOWN_MS,
  MIN_TOOL_CALLS,
  OVERLAP_THRESHOLD,
};
