import { collectFile } from '../../db/query-builder.js';
import { EVERY_SYMBOL_KIND, VALID_ROLES } from '../../domain/queries.js';
import { ConfigError } from '../../shared/errors.js';
import type { CliContext, CommandDefinition, CommandOpts } from '../types.js';

function validateFilters(opts: CommandOpts): void {
  if (opts.kind && !(EVERY_SYMBOL_KIND as readonly string[]).includes(opts.kind)) {
    throw new ConfigError(`Invalid kind "${opts.kind}". Valid: ${EVERY_SYMBOL_KIND.join(', ')}`);
  }
  if (opts.role && !(VALID_ROLES as readonly string[]).includes(opts.role)) {
    throw new ConfigError(`Invalid role "${opts.role}". Valid: ${VALID_ROLES.join(', ')}`);
  }
}

function parseWeights(raw: unknown): Record<string, number> | undefined {
  if (!raw) return undefined;
  try {
    return JSON.parse(raw as string);
  } catch (err: unknown) {
    throw new ConfigError('Invalid --weights JSON', { cause: err as Error });
  }
}

async function runHotspots(opts: CommandOpts, ctx: CliContext): Promise<void> {
  const { hotspotsData, formatHotspots } = await import('../../presentation/structure.js');
  const metric = opts.sort === 'risk' ? 'fan-in' : opts.sort;
  const data = hotspotsData(opts.db, {
    metric,
    level: opts.level,
    limit: parseInt(opts.limit as string, 10),
    offset: opts.offset ? parseInt(opts.offset as string, 10) : undefined,
    noTests: ctx.resolveNoTests(opts),
  });
  if (!ctx.outputResult(data, 'hotspots', opts)) {
    console.log(formatHotspots(data));
  }
}

export const command: CommandDefinition = {
  name: 'triage',
  description:
    'Ranked audit queue by composite risk score (connectivity + complexity + churn + role)',
  options: [
    ['-d, --db <path>', 'Path to graph.db'],
    ['-n, --limit <number>', 'Max results to return', '20'],
    [
      '--level <level>',
      'Granularity: function (default) | file | directory. File/directory level shows hotspots',
      'function',
    ],
    [
      '--sort <metric>',
      'Sort metric: risk | complexity | churn | fan-in | mi (function level); fan-in | fan-out | density | coupling (file/directory level)',
      'risk',
    ],
    ['--min-score <score>', 'Only show symbols with risk score >= threshold'],
    [
      '--role <role>',
      'Filter by role (entry, core, utility, adapter, leaf, dead, dead-leaf, dead-entry, dead-ffi, dead-unresolved)',
    ],
    ['-f, --file <path>', 'Scope to a specific file (partial match, repeatable)', collectFile],
    ['-k, --kind <kind>', 'Filter by symbol kind (function, method, class)'],
    ['-T, --no-tests', 'Exclude test/spec files from results'],
    ['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
    ['-j, --json', 'Output as JSON'],
    ['--offset <number>', 'Skip N results (default: 0)'],
    ['--ndjson', 'Newline-delimited JSON output'],
    ['--weights <json>', 'Custom weights JSON (e.g. \'{"fanIn":1,"complexity":0}\')'],
  ],
  async execute(_args, opts, ctx) {
    if (opts.level === 'file' || opts.level === 'directory') {
      await runHotspots(opts, ctx);
      return;
    }

    validateFilters(opts);
    const weights = parseWeights(opts.weights);
    const { triage } = await import('../../presentation/triage.js');
    triage(opts.db, {
      limit: parseInt(opts.limit as string, 10),
      offset: opts.offset ? parseInt(opts.offset as string, 10) : undefined,
      sort: opts.sort,
      minScore: opts.minScore,
      role: opts.role,
      file: opts.file,
      kind: opts.kind,
      noTests: ctx.resolveNoTests(opts),
      json: opts.json,
      ndjson: opts.ndjson,
      weights,
    });
  },
};
