'use strict';
/**
 * Skill harness registry — pluggable execution backend for external (agent) skills.
 *
 * Borrowed directly from OpenClaw's selectAgentHarness pattern
 * (src/agents/harness/selection.ts:71-167) and harness/types.ts:34-46.
 *
 * OpenClaw's AgentHarness:
 *   {
 *     id: string;
 *     label: string;
 *     supports(ctx): { supported: true; priority?: number } | { supported: false };
 *     runAttempt(params): Promise<Result>;
 *   }
 *
 * Wall-E's SkillHarness mirrors this with skill-shaped params. Today
 * there's a single bundled harness ('claude-tool-use', priority 0) that
 * wraps the existing Haiku-based runSkill body. Plugins can register
 * specialized harnesses (e.g. ollama-only for offline skills, sonnet-only
 * for long-context skills) and the selector picks by priority.
 *
 * Item A of skill-dispatch deep-dive.
 */

const _harnesses = new Map(); // id -> harness

/**
 * Register a harness. Returns a deregister closure (matches OpenClaw's
 * registerSandboxBackend → () => void pattern).
 *
 * Required harness shape:
 *   {
 *     id: string,
 *     label: string,
 *     supports(skill, ctx) -> { supported: boolean, priority?: number, reason?: string },
 *     runAttempt(skill, opts) -> Promise<{ success, memoriesCreated, toolCalls, duration }>,
 *   }
 */
function registerSkillHarness(harness) {
  if (!harness || typeof harness !== 'object') {
    throw new Error('registerSkillHarness requires an object');
  }
  if (!harness.id || typeof harness.id !== 'string') {
    throw new Error('harness.id must be a non-empty string');
  }
  if (typeof harness.supports !== 'function') {
    throw new Error(`harness "${harness.id}" requires a supports() function`);
  }
  if (typeof harness.runAttempt !== 'function') {
    throw new Error(`harness "${harness.id}" requires a runAttempt() function`);
  }
  const previous = _harnesses.get(harness.id);
  _harnesses.set(harness.id, harness);
  return () => {
    if (previous) {
      _harnesses.set(harness.id, previous);
    } else {
      _harnesses.delete(harness.id);
    }
  };
}

/**
 * Returns all candidate harnesses for a given skill, sorted by priority
 * (highest first). Mirrors OpenClaw's listHarnessCandidates.
 */
function listSkillHarnessCandidates(skill, ctx = {}) {
  const candidates = [];
  for (const harness of _harnesses.values()) {
    let support;
    try { support = harness.supports(skill, ctx); }
    catch (err) { support = { supported: false, reason: `supports() threw: ${err.message}` }; }
    if (!support || typeof support !== 'object') continue;
    candidates.push({
      id: harness.id,
      label: harness.label || harness.id,
      supported: !!support.supported,
      priority: support.supported ? (support.priority ?? 0) : undefined,
      reason: support.reason,
    });
  }
  candidates.sort((a, b) => {
    // supported=true comes before supported=false
    if (a.supported && !b.supported) return -1;
    if (!a.supported && b.supported) return 1;
    // higher priority first among supported
    return (b.priority ?? 0) - (a.priority ?? 0);
  });
  return candidates;
}

/**
 * Pick the best harness for a skill. Returns
 * `{ harness, candidates, selectedReason }` so callers can log the full
 * decision (matches OpenClaw's AgentHarnessSelectionDecision shape).
 *
 * selectedReason values (use ROUTING_REASONS constants from
 * routing-reason-codes.js — Item B of provider-routing deep-dive):
 *   PINNED              — harness explicitly requested via opts.harnessId
 *   AUTO_PROVIDER_DEFAULT — picked the highest-priority supported harness
 *   FALLBACK            — fell through to the bundled claude-tool-use
 *   AUTO_TIER (n/a here, but available in vocabulary)
 */
function selectSkillHarness(skill, opts = {}) {
  const { ROUTING_REASONS } = require('./routing-reason-codes');
  const candidates = listSkillHarnessCandidates(skill, opts);

  // Pinned: caller named a specific harness
  if (opts.harnessId) {
    const pinned = _harnesses.get(opts.harnessId);
    if (pinned) {
      return { harness: pinned, candidates, selectedReason: ROUTING_REASONS.PINNED };
    }
    // Fall through to auto-selection below if pinned not registered
  }

  // Auto: highest-priority supported harness
  const supported = candidates.filter(c => c.supported);
  if (supported.length > 0) {
    const best = supported[0];
    const harness = _harnesses.get(best.id);
    return {
      harness,
      candidates,
      selectedReason: opts.harnessId
        ? ROUTING_REASONS.FALLBACK
        : ROUTING_REASONS.AUTO_PROVIDER_DEFAULT,
    };
  }

  throw new Error(
    `No registered skill harness supports "${skill?.name || 'unknown'}". `
    + `Register one via registerSkillHarness({...}).`
  );
}

function listRegisteredSkillHarnesses() {
  return Array.from(_harnesses.keys()).sort();
}

function clearSkillHarnessRegistry() {
  _harnesses.clear();
  _defaultHarnessRegistered = false;
}

// ── Bundled default harness: claude-tool-use ──
// The existing runSkill body becomes the universal-fallback harness.
// priority=0 ensures any registered specialized harness can preempt it.
// The executor passes its implementation in so this registry stays dependency-light.
let _defaultHarnessRegistered = false;
function ensureDefaultHarness(runAttempt) {
  if (_defaultHarnessRegistered) return;
  if (typeof runAttempt !== 'function') {
    throw new Error('ensureDefaultHarness requires a runAttempt function');
  }
  _defaultHarnessRegistered = true;
  registerSkillHarness({
    id: 'claude-tool-use',
    label: 'Claude tool-use loop (default)',
    supports: () => ({ supported: true, priority: 0 }),
    runAttempt,
  });
}

module.exports = {
  registerSkillHarness,
  selectSkillHarness,
  listSkillHarnessCandidates,
  listRegisteredSkillHarnesses,
  clearSkillHarnessRegistry,
  ensureDefaultHarness,
};
