import { TerminalToolRegistry } from './terminal-agent.mjs';
import { safeFetch } from '../../apis/utils/fetch.mjs';

export function createTerminalToolRegistry({
  getData,
  getHealth,
  getDelta,
  buildBrief,
  intelligenceStore,
  securityProfileStore,
  triggerSweep,
  isSweepInProgress,
  telegramAlerter,
} = {}) {
  const dataFor = runtime => runtime?.data || getData?.() || null;
  const deltaFor = runtime => runtime?.delta || getDelta?.() || null;
  return new TerminalToolRegistry([
    tool('get_system_status', 'Get server, sweep, LLM, Telegram, source, and memory health.', {}, async () => getHealth?.() || {}),
    tool('get_security_profile', 'Get the operator-approved Security Manager profile for personal risk relevance, location, dependencies, mobility, alert preferences, and response language.', {}, async () => ({
      available: Boolean(securityProfileStore?.exists),
      profile: securityProfileStore?.getAgentProfile() || null,
    })),
    tool('get_latest_brief', 'Build the latest operator intelligence brief.', {}, async (_args, runtime) => {
      const data = dataFor(runtime);
      return { brief: data && buildBrief ? buildBrief(data) : 'No completed sweep.' };
    }),
    tool('get_sweep_delta', 'Inspect changes, escalations, de-escalations, and direction from the latest sweep.', {}, async (_args, runtime) => compactDelta(deltaFor(runtime))),
    tool('get_market_snapshot', 'Get key rates, volatility, energy, metals, and current generated ideas.', {}, async (_args, runtime) => compactMarkets(dataFor(runtime))),
    tool('get_source_health', 'Inspect healthy, degraded, or failed sources. Optional arguments: status, name, limit.', { status: 'string', name: 'string', limit: 'number' }, async (args, runtime) => {
      const data = dataFor(runtime);
      const status = clean(args.status, 30).toLowerCase();
      const name = clean(args.name, 80).toLowerCase();
      const limit = bounded(args.limit, 1, 25, 12);
      return (data?.sourceHealth || data?.health || [])
        .filter(item => !status || String(item.status || '').toLowerCase() === status)
        .filter(item => !name || String(item.name || item.n || '').toLowerCase().includes(name))
        .slice(0, limit)
        .map(item => ({ name: item.name || item.n, status: item.status, ms: item.ms, error: clean(item.error || item.message, 240) || null }));
    }),
    tool('get_evidence', 'Search recent news, feed items, and urgent OSINT. Arguments: query, limit.', { query: 'string', limit: 'number' }, async (args, runtime) => {
      const data = dataFor(runtime);
      const query = clean(args.query, 120).toLowerCase();
      const limit = bounded(args.limit, 1, 20, 8);
      const rows = [
        ...(data?.news || []),
        ...(data?.newsFeed || []),
        ...(data?.tg?.urgent || []).map(item => ({ ...item, title: item.text, source: item.source || 'Telegram OSINT' })),
      ];
      return rows.filter(item => !query || `${item.headline || item.title || item.text || ''} ${item.source || ''}`.toLowerCase().includes(query))
        .slice(0, limit)
        .map(item => ({ title: clean(item.headline || item.title || item.text, 400), source: clean(item.source, 100), url: clean(item.url, 500) || null, timestamp: item.timestamp || item.date || null }));
    }),
    tool('web_search', 'Search the public web/news through GDELT for fresh corroborating evidence. Arguments: query, limit, hours.', { query: 'string', limit: 'number', hours: 'number' }, async args => {
      const query = clean(args.query, 160);
      if (!query) return { error: 'query_required', results: [] };
      const limit = bounded(args.limit, 1, 10, 5);
      const hours = bounded(args.hours, 1, 168, 48);
      const start = new Date(Date.now() - hours * 60 * 60 * 1000).toISOString().replace(/[-:TZ.]/g, '').slice(0, 14);
      const params = new URLSearchParams({
        query,
        mode: 'artlist',
        format: 'json',
        maxrecords: String(limit),
        sort: 'datedesc',
        startdatetime: start,
      });
      const data = await safeFetch(`https://api.gdeltproject.org/api/v2/doc/doc?${params}`, {
        timeout: 20000,
        retries: 1,
        source: 'GDELT-WebSearch',
      });
      const articles = Array.isArray(data?.articles) ? data.articles : [];
      return {
        query,
        windowHours: hours,
        results: articles.slice(0, limit).map(item => ({
          title: clean(item.title, 300),
          source: clean(item.sourceCommonName || item.domain || item.sourceCountry, 120),
          url: clean(item.url, 500) || null,
          timestamp: item.seendate || item.datetime || null,
          language: item.language || null,
        })),
        error: data?.error || null,
      };
    }),
    tool('search_memory', 'Search persisted cross-sweep events. Arguments: query, limit.', { query: 'string', limit: 'number' }, async args => intelligenceStore?.queryMemory({ q: clean(args.query, 120), limit: bounded(args.limit, 1, 25, 8) }) || { available: false }),
    tool('list_predictions', 'List persisted predictions and their current outcome states. Arguments: state, limit.', { state: 'string', limit: 'number' }, async args => intelligenceStore?.listPredictions({ state: clean(args.state, 30) || null, limit: bounded(args.limit, 1, 25, 8) }) || { available: false }),
    tool('get_scenarios', 'Inspect current scenario watchlist states and confidence.', {}, async (_args, runtime) => {
      const scenarios = dataFor(runtime)?.scenarios || {};
      return { summary: scenarios.summary || null, items: (scenarios.items || scenarios.scenarios || []).slice(0, 20), changed: (scenarios.changed || []).slice(0, 10) };
    }),
    tool('get_trade_ideas', 'Inspect current LLM-generated ideas. Optional argument: ticker.', { ticker: 'string' }, async (args, runtime) => {
      const ticker = clean(args.ticker, 30).toLowerCase();
      return (dataFor(runtime)?.ideas || []).filter(item => !ticker || String(item.ticker || '').toLowerCase().includes(ticker)).slice(0, 10);
    }),
    tool('trigger_sweep', 'Start a new full intelligence sweep.', {}, async (_args, runtime) => {
      if (!runtime.confirmed) throw new Error('Operator confirmation required');
      if (isSweepInProgress?.()) return { accepted: false, status: 'already_running' };
      triggerSweep?.();
      return { accepted: true, status: 'started' };
    }, true),
    tool('mute_alerts', 'Mute proactive Telegram alerts for a bounded number of hours.', { hours: 'number' }, async (args, runtime) => {
      if (!runtime.confirmed) throw new Error('Operator confirmation required');
      const hours = Math.max(0.25, Math.min(24, Number(args.hours) || 1));
      telegramAlerter?.muteAlerts(hours);
      return { muted: true, hours };
    }, true),
    tool('unmute_alerts', 'Resume proactive Telegram alerts.', {}, async (_args, runtime) => {
      if (!runtime.confirmed) throw new Error('Operator confirmation required');
      telegramAlerter?.unmuteAlerts();
      return { muted: false };
    }, true),
  ]);
}

function tool(name, description, parameters, handler, mutating = false) {
  return { name, description, parameters, handler, mutating };
}

function compactDelta(delta) {
  return {
    summary: delta?.summary || null,
    new: (delta?.signals?.new || []).slice(0, 15),
    escalated: (delta?.signals?.escalated || []).slice(0, 15),
    deescalated: (delta?.signals?.deescalated || []).slice(0, 15),
  };
}

function compactMarkets(data) {
  if (!data) return { available: false };
  const fred = Object.fromEntries((data.fred || []).filter(item => ['VIXCLS', 'DFF', 'DGS10', 'DGS2', 'T10Y2Y', 'BAMLH0A0HYM2'].includes(item.id)).map(item => [item.id, item.value]));
  return { available: true, generatedAt: data.meta?.generatedAt || data.meta?.timestamp, fred, energy: data.energy || null, metals: data.metals || null, ideasSource: data.ideasSource, ideas: (data.ideas || []).slice(0, 8) };
}

function clean(value, maxLength) {
  return String(value || '').replace(/[\u0000-\u001f]/g, ' ').trim().slice(0, maxLength);
}

function bounded(value, min, max, fallback) {
  const parsed = Number.parseInt(value, 10);
  return Number.isFinite(parsed) ? Math.max(min, Math.min(max, parsed)) : fallback;
}
