/**
 * extensions/lib/can-use.ts — NX canUse() Plugin Gating Registry
 *
 * Implements the NX pattern of gating tool/feature registration behind capability
 * checks. In NX this is called `canUse()` and is checked per-generator/executor
 * before it is exposed to the LLM or tool system.
 *
 * Usage:
 *   import { canUse, registerCapability, markUnavailable } from './can-use.ts';
 *
 *   // In your extension's session_start or activation:
 *   if (!await canUse('memgraph')) {
 *     process.stderr.write('[my-ext] Memgraph unavailable — skipping tool registration\n');
 *     return;
 *   }
 *   pi.registerTool(myTool);
 *
 *   // Mark a capability as available (called by infrastructure extensions):
 *   registerCapability('memgraph', true);
 *
 *   // Mark as unavailable (e.g., on connection failure):
 *   markUnavailable('memgraph', 'connection refused');
 *
 * Built-in capabilities auto-checked on first use:
 *   - 'memgraph'   — Bolt connection to Memgraph available
 *   - 'gh'         — GitHub CLI installed
 *   - 'browser'    — Playwright/browser backend available
 *   - 'imessage'   — macOS + iMessage access (darwin only)
 *   - 'erd'        — ERDEntity nodes exist in Memgraph
 *   - 'daemon'     — Helios daemon HTTP endpoint reachable
 */

import { createRequire } from 'node:module';
const _require = createRequire(import.meta.url);

// ── Capability registry (process-global, shared across all extensions) ────────

interface CapabilityEntry {
  available: boolean;
  reason?: string;     // why it's unavailable (if false)
  checkedAt: number;   // timestamp of last check
  ttlMs: number;       // how long the result is valid
}

const REGISTRY_KEY = '__helios_can_use_registry';
const DEFAULT_TTL_MS = 5 * 60 * 1000; // 5 minutes

function getRegistry(): Map<string, CapabilityEntry> {
  if (!(globalThis as any)[REGISTRY_KEY]) {
    (globalThis as any)[REGISTRY_KEY] = new Map<string, CapabilityEntry>();
  }
  return (globalThis as any)[REGISTRY_KEY] as Map<string, CapabilityEntry>;
}

// ── Auto-checkers for built-in capabilities ────────────────────────────────────

const AUTO_CHECKERS: Record<string, () => Promise<boolean>> = {
  async memgraph(): Promise<boolean> {
    try {
      const mg = _require('../../lib/safe-memgraph.js');
      const rows = await Promise.race([
        mg.safeRead('RETURN 1 AS ok', {}),
        new Promise<never>((_, r) => setTimeout(() => r(new Error('timeout')), 2000)),
      ]);
      return Array.isArray(rows) && rows.length > 0;
    } catch { return false; }
  },

  async gh(): Promise<boolean> {
    // BUG-7A fix: on Windows, execFile requires the .exe extension.
    // Use 'gh.exe' on Windows, 'gh' on all other platforms.
    const ghCmd = process.platform === 'win32' ? 'gh.exe' : 'gh';
    return new Promise((resolve) => {
      const { execFile } = _require('node:child_process');
      execFile(ghCmd, ['--version'], { timeout: 3000 }, (err: Error | null) => resolve(!err));
    });
  },

  async browser(): Promise<boolean> {
    try {
      // Check if playwright or electron browser backend is installed
      _require('playwright'); return true;
    } catch {
      try { _require('@playwright/browser-chromium'); return true; } catch { return false; }
    }
  },

  async imessage(): Promise<boolean> {
    if (process.platform !== 'darwin') return false;
    const { existsSync } = _require('node:fs');
    const { homedir } = _require('node:os');
    const chatDb = `${homedir()}/Library/Messages/chat.db`;
    return existsSync(chatDb);
  },

  async erd(): Promise<boolean> {
    try {
      const mg = _require('../../lib/safe-memgraph.js');
      const rows = await Promise.race([
        mg.safeRead('MATCH (e:ERDEntity) RETURN count(e) AS cnt LIMIT 1', {}),
        new Promise<never>((_, r) => setTimeout(() => r(new Error('timeout')), 2000)),
      ]) as any[];
      const cnt = Number(rows?.[0]?.cnt ?? rows?.[0]?.[0] ?? 0);
      return cnt > 0;
    } catch { return false; }
  },

  async daemon(): Promise<boolean> {
    const daemonUrl = process.env.HELIOS_DAEMON_URL ?? 'http://localhost:9091';
    return new Promise((resolve) => {
      const { request } = _require('node:http');
      const req = request(`${daemonUrl}/health`, { timeout: 2000 }, (res: any) => {
        resolve(res.statusCode >= 200 && res.statusCode < 300);
      });
      req.on('error', () => resolve(false));
      req.on('timeout', () => { req.destroy(); resolve(false); });
      req.end();
    });
  },
};

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

/**
 * Check if a named capability is available.
 * Results are cached for `ttlMs` (default 5 min) to avoid repeated checks.
 * If the capability has a built-in auto-checker, it runs on first call.
 *
 * @param capability  Name of the capability to check.
 * @param ttlMs       Override TTL for this check (optional).
 */
export async function canUse(capability: string, ttlMs = DEFAULT_TTL_MS): Promise<boolean> {
  const registry = getRegistry();
  const now = Date.now();
  const entry = registry.get(capability);

  // Cache hit
  if (entry && (now - entry.checkedAt) < entry.ttlMs) {
    return entry.available;
  }

  // Auto-check if we have a checker for this capability
  const checker = AUTO_CHECKERS[capability];
  if (checker) {
    try {
      const available = await checker();
      registry.set(capability, { available, checkedAt: now, ttlMs });
      return available;
    } catch {
      registry.set(capability, { available: false, reason: 'checker threw', checkedAt: now, ttlMs });
      return false;
    }
  }

  // No entry and no checker — default to available (open world assumption)
  return entry?.available ?? true;
}

/**
 * Synchronous canUse() — returns the cached result without async check.
 * Returns null if not yet checked.
 */
export function canUseSync(capability: string): boolean | null {
  const entry = getRegistry().get(capability);
  if (!entry) return null;
  const now = Date.now();
  if ((now - entry.checkedAt) > entry.ttlMs) return null; // expired
  return entry.available;
}

/**
 * Register a capability as available (called by infrastructure extensions
 * when they successfully initialize their dependency).
 *
 * Example: called by memgraph-autostart.ts after Memgraph comes online.
 */
export function registerCapability(capability: string, ttlMs = DEFAULT_TTL_MS): void {
  getRegistry().set(capability, { available: true, checkedAt: Date.now(), ttlMs });
}

/**
 * Mark a capability as unavailable (called on connection failure).
 * Resets TTL so the next canUse() call will re-check after `retryAfterMs`.
 */
export function markUnavailable(capability: string, reason?: string, retryAfterMs = 30_000): void {
  getRegistry().set(capability, {
    available: false,
    reason,
    checkedAt: Date.now(),
    ttlMs: retryAfterMs,
  });
}

/**
 * Invalidate a cached capability check, forcing re-check on next canUse() call.
 */
export function invalidateCapability(capability: string): void {
  getRegistry().delete(capability);
}

/**
 * Returns a summary of all registered capabilities and their status.
 * Used by diagnostic commands.
 */
export function getCapabilityStatus(): Record<string, { available: boolean; reason?: string; ageMs: number }> {
  const registry = getRegistry();
  const now = Date.now();
  const result: Record<string, { available: boolean; reason?: string; ageMs: number }> = {};
  for (const [cap, entry] of registry.entries()) {
    result[cap] = {
      available: entry.available,
      reason: entry.reason,
      ageMs: now - entry.checkedAt,
    };
  }
  return result;
}
