/** Observability tools — "what is Forge doing, and what is it costing?". A live
 *  status snapshot (tasks/pipelines/schedules/tools) and token/cost analytics.
 *  All in-process lib reads; no mutations. */

import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { listTasksLite } from '@/lib/task-manager';
import { listPipelines } from '@/lib/pipeline';
import { listSchedules } from '@/lib/schedules/store';
import { checkTools } from '@/lib/health';
import { queryUsage } from '@/lib/usage-scanner';
import { text, guard, fmtTokens, summarizePipelines } from './_shared';

const TASK_STATES = ['running', 'queued', 'done', 'failed', 'cancelled'] as const;

// checkTools() spawns blocking `which`/`--version` subprocesses per CLI tool, so
// memoize it behind a short TTL — forge_status shouldn't reprobe the PATH on every
// call. The set of installed tools changes rarely. Stored on a globalThis Symbol so
// the cache survives HMR / multiple module evaluations (same pattern as
// app/api/mcp/route.ts), instead of resetting on every reload.
type ToolsCache = { at: number; tools: ReturnType<typeof checkTools> };
const TOOLS_CACHE_KEY = Symbol.for('forge-mcp-tools-cache');
const _g = globalThis as unknown as Record<symbol, ToolsCache | undefined>;
function checkToolsCached(): ReturnType<typeof checkTools> {
  const now = Date.now();
  const cached = _g[TOOLS_CACHE_KEY];
  if (cached && now - cached.at < 60_000) return cached.tools;
  const fresh: ToolsCache = { at: now, tools: checkTools() };
  _g[TOOLS_CACHE_KEY] = fresh;
  return fresh.tools;
}

export function registerObservabilityTools(server: McpServer): void {
  server.tool(
    'forge_status',
    'A snapshot of what Forge is doing right now: background-task counts by status, in-flight and recent pipelines, schedules, and which CLI tools are installed. Use this to answer "is Forge healthy / what is running?".',
    {},
    () => guard(() => {
      const tasks = listTasksLite();
      const counts: Record<string, number> = {};
      for (const t of tasks) counts[t.status] = (counts[t.status] || 0) + 1;
      const taskLine = TASK_STATES.filter((s) => counts[s]).map((s) => `${counts[s]} ${s}`).join(', ') || 'none';

      const { runningCount, recent } = summarizePipelines(listPipelines());

      const schedules = listSchedules();
      const enabled = schedules.filter((s) => s.enabled).length;

      const tools = checkToolsCached();
      const installed = tools.filter((t) => t.installed).map((t) => t.name);
      const missing = tools.filter((t) => !t.installed).map((t) => t.name);

      const lines = [
        `Tasks: ${taskLine} (${tasks.length} total)`,
        `Pipelines: ${runningCount} running${recent.length ? ` · recent: ${recent.join(' · ')}` : ''}`,
        `Schedules: ${schedules.length} (${enabled} enabled)`,
        `Tools installed: ${installed.join(', ') || 'none'}${missing.length ? ` · missing: ${missing.join(', ')}` : ''}`,
      ];
      return text(`Forge status:\n${lines.join('\n')}`);
    }),
  );

  server.tool(
    'forge_get_usage',
    'Token usage and cost analytics over a recent window, optionally filtered by project or model. Reads the usage store, which Forge rescans automatically about hourly, so figures may be up to an hour old.',
    {
      days: z.number().int().positive().max(365).optional().describe('Window in days (default 30)'),
      project: z.string().optional().describe('Filter to one project name'),
      model: z.string().optional().describe('Filter to one model family as shown in the "By model" output (e.g. claude-opus-4, claude-sonnet-4, claude-haiku-4). Usage is aggregated by family — full model ids do not match.'),
    },
    (params) => guard(() => {
      const days = params.days ?? 30;
      const d = queryUsage({ days, projectName: params.project, model: params.model });
      const t = d.total;
      const scope = `last ${days} day(s)${params.project ? `, project=${params.project}` : ''}${params.model ? `, model=${params.model}` : ''}`;
      if (!t.messages && !t.cost) {
        return text(`No usage recorded (${scope}). Usage is rescanned automatically about hourly.`);
      }
      const lines = [
        `Usage (${scope}):`,
        `Total: $${t.cost.toFixed(2)} · ${fmtTokens(t.input)} in / ${fmtTokens(t.output)} out · ${t.sessions} sessions · ${t.messages} messages`,
      ];
      const topProjects = [...d.byProject].sort((a, b) => b.cost - a.cost).slice(0, 5);
      if (topProjects.length) {
        lines.push('By project:');
        for (const p of topProjects) lines.push(`  • ${p.name}  $${p.cost.toFixed(2)}  (${p.sessions} sessions)`);
      }
      const topModels = [...d.byModel].sort((a, b) => b.cost - a.cost).slice(0, 5);
      if (topModels.length) {
        lines.push('By model:');
        for (const m of topModels) lines.push(`  • ${m.model}  $${m.cost.toFixed(2)}`);
      }
      return text(lines.join('\n'));
    }),
  );
}
