#!/usr/bin/env node

/**
 * Manifest scanner CLI (v5 N2) — produce a ranked list of every token source
 * loaded for a given repo path. Built on top of readActiveConfig so the source
 * inventory is identical to whats-active; this CLI flattens and ranks them.
 *
 * Output JSON shape:
 *   {
 *     meta: { repoPath, generatedAt, durationMs },
 *     sources: [
 *       { kind: 'claude-md'|'plugin'|'skill'|'mcp-server'|'hook',
 *         name: string, source: string, estimated_tokens: number },
 *       ...
 *     ],
 *     total: <sum of sources.estimated_tokens>
 *   }
 *
 * Usage:
 *   node manifest.mjs [path] [--json] [--output-file <path>]
 *
 * Exit codes: 0=ok, 3=unrecoverable error.
 * Zero external dependencies.
 */

import { resolve } from 'node:path';
import { writeFile, stat } from 'node:fs/promises';
import { readActiveConfig } from './lib/active-config-reader.mjs';

/**
 * Flatten an activeConfig snapshot into a single ranked array of sources.
 */
export function buildManifest(activeConfig) {
  const sources = [];

  for (const f of activeConfig.claudeMd?.files || []) {
    const tokens = estimateClaudeMdEntryTokens(f, activeConfig);
    sources.push({
      kind: 'claude-md',
      name: f.path,
      source: f.scope,
      estimated_tokens: tokens,
    });
  }

  for (const p of activeConfig.plugins || []) {
    sources.push({
      kind: 'plugin',
      name: p.name,
      source: p.path,
      estimated_tokens: p.estimatedTokens || 0,
    });
  }

  for (const s of activeConfig.skills || []) {
    sources.push({
      kind: 'skill',
      name: s.name,
      source: s.pluginName ? `plugin:${s.pluginName}` : s.source || 'user',
      estimated_tokens: s.estimatedTokens || 0,
    });
  }

  for (const m of activeConfig.mcpServers || []) {
    if (m && m.enabled === false) continue;
    sources.push({
      kind: 'mcp-server',
      name: m.name,
      source: m.source || 'unknown',
      estimated_tokens: m.estimatedTokens || 0,
    });
  }

  for (const h of activeConfig.hooks || []) {
    sources.push({
      kind: 'hook',
      name: `${h.event}${h.matcher ? `:${h.matcher}` : ''}`,
      source: h.source || h.sourcePath || 'unknown',
      estimated_tokens: h.estimatedTokens || 0,
    });
  }

  sources.sort((a, b) => b.estimated_tokens - a.estimated_tokens);
  const total = sources.reduce((s, x) => s + (x.estimated_tokens || 0), 0);
  return { sources, total };
}

/**
 * Distribute the cascade-level estimated tokens across the individual files
 * proportional to their byte size. claudeMd.estimatedTokens is computed for
 * the cascade as a whole, but for ranking we want per-file figures.
 */
function estimateClaudeMdEntryTokens(file, activeConfig) {
  const totalBytes = activeConfig.claudeMd?.totalBytes || 0;
  const totalTokens = activeConfig.claudeMd?.estimatedTokens || 0;
  if (totalBytes === 0 || totalTokens === 0) return 0;
  const share = (file.bytes || 0) / totalBytes;
  return Math.round(totalTokens * share);
}

async function main() {
  const args = process.argv.slice(2);
  let targetPath = '.';
  let outputFile = null;
  let jsonMode = false;
  // --raw is accepted for CLI surface consistency but is a no-op here:
  // manifest produces a token-source inventory, not findings.
  let rawMode = false;

  for (let i = 0; i < args.length; i++) {
    if (args[i] === '--json') jsonMode = true;
    else if (args[i] === '--raw') rawMode = true;
    else if (args[i] === '--output-file' && args[i + 1]) outputFile = args[++i];
    else if (!args[i].startsWith('-')) targetPath = args[i];
  }

  const absPath = resolve(targetPath);
  try {
    const s = await stat(absPath);
    if (!s.isDirectory()) {
      process.stderr.write(`Error: ${absPath} is not a directory\n`);
      process.exit(3);
    }
  } catch {
    process.stderr.write(`Error: path does not exist: ${absPath}\n`);
    process.exit(3);
  }

  const start = Date.now();
  const activeConfig = await readActiveConfig(absPath, { verbose: true });
  const manifest = buildManifest(activeConfig);

  const output = {
    meta: {
      tool: 'config-audit:manifest',
      repoPath: absPath,
      generatedAt: new Date().toISOString(),
      durationMs: Date.now() - start,
    },
    sources: manifest.sources,
    total: manifest.total,
  };

  const json = JSON.stringify(output, null, 2);

  if (outputFile) {
    await writeFile(outputFile, json, 'utf-8');
  }

  if (jsonMode || rawMode || !outputFile) {
    process.stdout.write(json + '\n');
  }
}

const isDirectRun = process.argv[1] && resolve(process.argv[1]) === resolve(new URL(import.meta.url).pathname);
if (isDirectRun) {
  main().catch(err => {
    process.stderr.write(`Fatal: ${err.message}\n`);
    process.exit(3);
  });
}
