/**
 * Per-project health tracking — Svelte 5 runes.
 *
 * Tracks activity state, burn rate (5-min EMA from cost snapshots),
 * context pressure (tokens / model limit), and attention scoring.
 * 5-second tick timer drives derived state updates.
 */

// ── Types ────────────────────────────────────────────────────────────────────

export type ActivityState = 'inactive' | 'running' | 'idle' | 'stalled';

export interface ProjectHealth {
  projectId: string;
  activityState: ActivityState;
  activeTool: string | null;
  idleDurationMs: number;
  burnRatePerHour: number;
  contextPressure: number | null;
  fileConflictCount: number;
  attentionScore: number;
  attentionReason: string | null;
}

// ── Configuration ────────────────────────────────────────────────────────────

const DEFAULT_STALL_THRESHOLD_MS = 15 * 60 * 1000; // 15 minutes
const TICK_INTERVAL_MS = 5_000;
const BURN_RATE_WINDOW_MS = 5 * 60 * 1000; // 5-minute window

const DEFAULT_CONTEXT_LIMIT = 200_000;

// ── Internal state ───────────────────────────────────────────────────────────

// Feature 10: Per-tool tracking entry
interface ToolEntry {
  startTime: number;
  count: number;
}

// Feature 10: Tool duration histogram entry
interface ToolHistogramEntry {
  toolName: string;
  totalMs: number;
  count: number;
}

interface ProjectTracker {
  projectId: string;
  lastActivityTs: number;
  lastToolName: string | null;
  // Fix #8 (Codex audit): Counter instead of boolean for concurrent tools
  toolsInFlight: number;
  // Feature 10: Per-tool tracking map
  activeToolMap: Map<string, ToolEntry>;
  costSnapshots: Array<[number, number]>; // [timestamp, costUsd]
  totalTokens: number;
  totalCost: number;
  status: 'inactive' | 'running' | 'idle' | 'done' | 'error';
}

// Feature 10: Global tool duration histogram (across all projects)
const toolDurationHistogram = new Map<string, ToolHistogramEntry>();

let trackers = $state<Map<string, ProjectTracker>>(new Map());
let tickTs = $state<number>(Date.now());
let tickInterval: ReturnType<typeof setInterval> | null = null;

// ── Attention scoring (pure) ─────────────────────────────────────────────────

function scoreAttention(
  activityState: ActivityState,
  contextPressure: number | null,
  fileConflictCount: number,
  status: string,
): { score: number; reason: string | null } {
  if (status === 'error') return { score: 90, reason: 'Agent error' };
  if (activityState === 'stalled') return { score: 100, reason: 'Agent stalled (>15 min)' };
  if (contextPressure !== null && contextPressure > 0.9) return { score: 80, reason: 'Context >90%' };
  if (fileConflictCount > 0) return { score: 70, reason: `${fileConflictCount} file conflict(s)` };
  if (contextPressure !== null && contextPressure > 0.75) return { score: 40, reason: 'Context >75%' };
  return { score: 0, reason: null };
}

// ── Burn rate calculation ────────────────────────────────────────────────────

function computeBurnRate(snapshots: Array<[number, number]>): number {
  if (snapshots.length < 2) return 0;
  const windowStart = Date.now() - BURN_RATE_WINDOW_MS;
  const recent = snapshots.filter(([ts]) => ts >= windowStart);
  if (recent.length < 2) return 0;
  const first = recent[0];
  const last = recent[recent.length - 1];
  const elapsedHours = (last[0] - first[0]) / 3_600_000;
  if (elapsedHours < 0.001) return 0;
  const costDelta = last[1] - first[1];
  return Math.max(0, costDelta / elapsedHours);
}

// ── Derived health per project ───────────────────────────────────────────────

function computeHealth(tracker: ProjectTracker, now: number): ProjectHealth {
  let activityState: ActivityState;
  let idleDurationMs = 0;
  let activeTool: string | null = null;

  if (tracker.status === 'inactive' || tracker.status === 'done' || tracker.status === 'error') {
    activityState = 'inactive';
  } else if (tracker.toolsInFlight > 0) {
    activityState = 'running';
    activeTool = tracker.lastToolName;
  } else {
    idleDurationMs = now - tracker.lastActivityTs;
    activityState = idleDurationMs >= DEFAULT_STALL_THRESHOLD_MS ? 'stalled' : 'idle';
  }

  let contextPressure: number | null = null;
  if (tracker.totalTokens > 0) {
    contextPressure = Math.min(1, tracker.totalTokens / DEFAULT_CONTEXT_LIMIT);
  }

  const burnRatePerHour = computeBurnRate(tracker.costSnapshots);
  const attention = scoreAttention(activityState, contextPressure, 0, tracker.status);

  return {
    projectId: tracker.projectId,
    activityState,
    activeTool,
    idleDurationMs,
    burnRatePerHour,
    contextPressure,
    fileConflictCount: 0,
    attentionScore: attention.score,
    attentionReason: attention.reason,
  };
}

// ── Public API ───────────────────────────────────────────────────────────────

/** Register a project for health tracking. */
export function trackProject(projectId: string): void {
  if (trackers.has(projectId)) return;
  trackers.set(projectId, {
    projectId,
    lastActivityTs: Date.now(),
    lastToolName: null,
    toolsInFlight: 0,
    activeToolMap: new Map(),
    costSnapshots: [],
    totalTokens: 0,
    totalCost: 0,
    status: 'inactive',
  });
  if (!tickInterval) startHealthTick();
}

/** Record activity (call on every agent message). */
export function recordActivity(projectId: string, toolName?: string): void {
  const t = trackers.get(projectId);
  if (!t) return;
  t.lastActivityTs = Date.now();
  t.status = 'running';
  if (toolName !== undefined) {
    t.lastToolName = toolName;
    // Fix #8 (Codex audit): Increment counter for concurrent tool tracking
    t.toolsInFlight++;
    // Feature 10: Track per-tool starts
    const existing = t.activeToolMap.get(toolName);
    if (existing) {
      existing.count++;
    } else {
      t.activeToolMap.set(toolName, { startTime: Date.now(), count: 1 });
    }
  }
  if (!tickInterval) startHealthTick();
}

/** Record tool completion. */
export function recordToolDone(projectId: string): void {
  const t = trackers.get(projectId);
  if (!t) return;
  const now = Date.now();
  t.lastActivityTs = now;
  // Fix #8 (Codex audit): Decrement counter, floor at 0
  t.toolsInFlight = Math.max(0, t.toolsInFlight - 1);

  // Feature 10: Record duration in histogram, remove from activeToolMap
  if (t.lastToolName) {
    const entry = t.activeToolMap.get(t.lastToolName);
    if (entry) {
      const durationMs = now - entry.startTime;
      // Update global histogram
      const hist = toolDurationHistogram.get(t.lastToolName);
      if (hist) {
        hist.totalMs += durationMs;
        hist.count++;
      } else {
        toolDurationHistogram.set(t.lastToolName, {
          toolName: t.lastToolName,
          totalMs: durationMs,
          count: 1,
        });
      }
      entry.count--;
      if (entry.count <= 0) t.activeToolMap.delete(t.lastToolName);
    }
  }
}

/** Record a token/cost snapshot for burn rate calculation. */
export function recordTokenSnapshot(projectId: string, totalTokens: number, costUsd: number): void {
  const t = trackers.get(projectId);
  if (!t) return;
  const now = Date.now();
  t.totalTokens = totalTokens;
  t.totalCost = costUsd;
  t.costSnapshots.push([now, costUsd]);
  const cutoff = now - BURN_RATE_WINDOW_MS * 2;
  t.costSnapshots = t.costSnapshots.filter(([ts]) => ts > cutoff);
}

/** Update project status. */
export function setProjectStatus(projectId: string, status: 'running' | 'idle' | 'done' | 'error'): void {
  const t = trackers.get(projectId);
  if (t) t.status = status;
}

/** Get health for a single project (reactive via tickTs). */
export function getProjectHealth(projectId: string): ProjectHealth | null {
  const now = tickTs;
  const t = trackers.get(projectId);
  if (!t) return null;
  return computeHealth(t, now);
}

/** Get top N items needing attention. */
export function getAttentionQueue(limit = 5): ProjectHealth[] {
  const now = tickTs;
  const results: ProjectHealth[] = [];
  for (const t of trackers.values()) {
    const h = computeHealth(t, now);
    if (h.attentionScore > 0) results.push(h);
  }
  results.sort((a, b) => b.attentionScore - a.attentionScore);
  return results.slice(0, limit);
}

/** Get aggregate stats across all tracked projects. */
export function getHealthAggregates(): {
  running: number;
  idle: number;
  stalled: number;
  totalBurnRatePerHour: number;
} {
  const now = tickTs;
  let running = 0, idle = 0, stalled = 0, totalBurnRatePerHour = 0;
  for (const t of trackers.values()) {
    const h = computeHealth(t, now);
    if (h.activityState === 'running') running++;
    else if (h.activityState === 'idle') idle++;
    else if (h.activityState === 'stalled') stalled++;
    totalBurnRatePerHour += h.burnRatePerHour;
  }
  return { running, idle, stalled, totalBurnRatePerHour };
}

/** Feature 10: Get all currently active tools across all projects. */
export function getActiveTools(): Array<{ projectId: string; toolName: string; startTime: number; count: number }> {
  const results: Array<{ projectId: string; toolName: string; startTime: number; count: number }> = [];
  for (const t of trackers.values()) {
    for (const [name, entry] of t.activeToolMap) {
      results.push({ projectId: t.projectId, toolName: name, startTime: entry.startTime, count: entry.count });
    }
  }
  return results;
}

/** Feature 10: Get tool duration histogram for diagnostics. */
export function getToolHistogram(): Array<{ toolName: string; avgMs: number; count: number }> {
  const results: Array<{ toolName: string; avgMs: number; count: number }> = [];
  for (const entry of toolDurationHistogram.values()) {
    results.push({
      toolName: entry.toolName,
      avgMs: entry.count > 0 ? entry.totalMs / entry.count : 0,
      count: entry.count,
    });
  }
  return results.sort((a, b) => b.count - a.count).slice(0, 15);
}

/** Start the health tick timer. */
function startHealthTick(): void {
  if (tickInterval) return;
  tickInterval = setInterval(() => {
    tickTs = Date.now();
  }, TICK_INTERVAL_MS);
}

/** Untrack a project (e.g. when project is removed). Fix #14 (Codex audit). */
export function untrackProject(projectId: string): void {
  trackers.delete(projectId);
  // Stop tick if no more tracked projects
  if (trackers.size === 0 && tickInterval) {
    clearInterval(tickInterval);
    tickInterval = null;
  }
}

/** Stop the health tick timer. */
export function stopHealthTick(): void {
  if (tickInterval) {
    clearInterval(tickInterval);
    tickInterval = null;
  }
}
