'use strict';
const brain = require('../brain');
const { getDefaultClient, getDefaultModel, resolveCompatibleModel } = require('../llm/client');
const { executeTool, getToolDefinitions } = require('./tool-executor');
const { augmentSystemPrompt } = require('../loops/loop-directives');
let telemetry;
try { telemetry = require('../telemetry'); } catch { telemetry = { track() {} }; }

/**
 * Run a single skill via the harness registry.
 *
 * Item A of skill-dispatch deep-dive: harness selection is now pluggable.
 * Plugins can register specialized harnesses (e.g. ollama-only, sonnet-only)
 * and the registry picks by priority. Default 'claude-tool-use' harness
 * (priority 0) wraps the existing Haiku tool-use loop in _runSkillInternal.
 */
async function runSkill(skill, opts = {}) {
  const { selectSkillHarness, ensureDefaultHarness } = require('./skill-harness-registry');
  ensureDefaultHarness(_runSkillInternal);
  const selection = selectSkillHarness(skill, opts);
  // Log the selection — mirrors OpenClaw's logAgentHarnessSelection
  // (selection.ts:246). Even at debug level, the trail exists in telemetry.
  try {
    telemetry.track('skill_harness_selected', {
      skill: skill.name,
      harness: selection.harness.id,
      reason: selection.selectedReason,
      candidate_count: selection.candidates.length,
    });
  } catch {}
  return await selection.harness.runAttempt(skill, opts);
}

/**
 * Internal implementation — the original Claude tool-use body.
 * Exported for the bundled default harness; not part of the public API.
 */
async function _runSkillInternal(skill, opts = {}) {
  const ownerName = brain.getOwnerName();
  const startTime = Date.now();

  const decisionReason = opts.decision_reason || 'due';

  // Build tool definitions — use skill's custom ones or defaults
  const tools = skill.tool_definitions
    ? JSON.parse(skill.tool_definitions)
    : getToolDefinitions();

  // Build the prompt
  const prompt = skill.prompt_template || buildDefaultPrompt(skill, ownerName);

  // Call the configured provider with a provider-compatible model. This avoids
  // sending stale Claude model ids to Gemini/OpenAI after provider switches.
  const provider = getDefaultClient();
  const resolvedModel = resolveCompatibleModel(opts.model || process.env.WALLE_MODEL || getDefaultModel(), provider.type);
  const messages = [{ role: 'user', content: prompt }];
  const allToolCalls = [];
  const allToolResults = [];
  let memoriesCreated = 0;

  // Agentic loop — Claude may make multiple tool calls
  // Allow SKILL.md to override max_turns and timeout_ms
  const MAX_TURNS = opts.max_turns || 5;
  const timeoutMs = opts.timeout_ms || 120000;
  try {
  for (let turn = 0; turn < MAX_TURNS; turn++) {
    if (Date.now() - startTime > timeoutMs) break;
    const { withRetry } = require('../llm/retry');
    const response = await withRetry(() => provider.chat({
      model: resolvedModel,
      maxTokens: 4096,
      system: augmentSystemPrompt(
        'skill-executor',
        `You are WALL-E, ${ownerName}'s digital twin agent. You are executing a data-fetching skill: "${skill.name}". Use the available tools to gather data. Return the data as a structured list of observations/memories.`,
        { extra: { ownerName, skill: skill.name } }
      ),
      messages,
      tools,
    }), 2);

    // If no tool calls, we're done — extract memories from the text
    if (response.toolCalls.length === 0) {
      const text = response.content || '';
      if (text.trim()) {
        memoriesCreated += storeSkillMemories(text, skill);
      }
      break;
    }

    // Execute each tool call
    const toolResultBlocks = [];
    for (const toolCall of response.toolCalls) {
      allToolCalls.push({ name: toolCall.name, input: toolCall.input });

      const execResult = await executeTool(toolCall.name, toolCall.input);
      allToolResults.push({ name: toolCall.name, ...execResult });

      toolResultBlocks.push({
        type: 'tool_result',
        tool_use_id: toolCall.id,
        content: execResult.success
          ? (typeof execResult.result === 'string' ? execResult.result : JSON.stringify(execResult.result))
          : `Error: ${execResult.error}`,
        is_error: !execResult.success,
      });
    }

    // Reconstruct Anthropic-format content blocks for message history
    const assistantContent = [];
    if (response.content) assistantContent.push({ type: 'text', text: response.content });
    for (const tc of response.toolCalls) {
      assistantContent.push({ type: 'tool_use', id: tc.id, name: tc.name, input: tc.input });
    }
    messages.push({ role: 'assistant', content: assistantContent });
    messages.push({ role: 'user', content: toolResultBlocks });

    // If Claude signals stop, break
    if (response.stopReason === 'end_turn') {
      const finalText = response.content || '';
      if (finalText.trim()) {
        memoriesCreated += storeSkillMemories(finalText, skill);
      }
      break;
    }
  }

  const duration = Date.now() - startTime;
  const success = memoriesCreated > 0 || allToolCalls.length > 0;

  // Log execution
  brain.insertSkillExecution({
    skill_id: skill.id,
    status: success ? 'success' : 'failure',
    tool_calls: JSON.stringify(allToolCalls),
    tool_results: JSON.stringify(allToolResults.map(r => ({ name: r.name, success: r.success, error: r.error }))),
    memories_created: memoriesCreated,
    duration_ms: duration,
    model: resolvedModel,
    decision_reason: decisionReason,
  });

  // Update skill stats
  brain.updateSkillStats(skill.id, success);

  return { success, memoriesCreated, toolCalls: allToolCalls.length, duration };
  } catch (err) {
    const duration = Date.now() - startTime;
    // Store full error stack for debugging
    const errorDetail = err.stack || err.message || String(err);
    try {
      brain.insertSkillExecution({
        skill_id: skill.id,
        status: 'failure',
        tool_calls: JSON.stringify(allToolCalls),
        tool_results: JSON.stringify(allToolResults.map(r => ({ name: r.name, success: r.success, error: r.error }))),
        memories_created: memoriesCreated,
        error: errorDetail,
        duration_ms: duration,
        model: resolvedModel,
        decision_reason: decisionReason,
      });
      brain.updateSkillStats(skill.id, false);
    } catch {}
    throw err;
  }
}

function buildDefaultPrompt(skill, ownerName) {
  return `Execute the skill "${skill.name}": ${skill.description || 'Fetch data and return observations.'}

Return your findings as a list of observations. Each observation should be a factual statement about what you found. The data will be stored as memories in ${ownerName}'s brain.`;
}

function storeSkillMemories(text, skill) {
  // Parse text into individual memories
  const lines = text.split('\n').filter(l => l.trim() && l.trim().length > 10);
  let count = 0;
  for (const line of lines) {
    const cleaned = line.replace(/^[-*\u2022]\s*/, '').trim();
    if (!cleaned) continue;
    brain.insertMemory({
      source: `skill:${skill.name}`,
      source_id: `skill-${skill.id}-${Date.now()}-${count}`,
      memory_type: 'observed',
      direction: 'observed',
      content: cleaned,
      timestamp: new Date().toISOString(),
      metadata: JSON.stringify({ skill_id: skill.id, skill_name: skill.name }),
    });
    count++;
  }
  return count;
}

module.exports = { runSkill, _runSkillInternal };
