/**
 * DIS Scanner — Disabled-Tools-Still-In-Schema Detector (v5 N4)
 *
 * Detects tools that appear in BOTH `permissions.deny` and `permissions.allow`
 * within the same settings.json file. The deny list wins, so the allow entry
 * is dead config — but it still loads on every turn and signals confused
 * intent. Often arises from copy-paste edits where one list was updated and
 * the other was forgotten.
 *
 * Compares tool identity by the bare tool name (everything before the first
 * `(`). `Bash(npm:*)` and `Bash` are treated as the same tool for collision
 * purposes — a deny on `Bash` blocks all `Bash(...)` allows.
 *
 * Finding ID: CA-DIS-NNN. Severity: low.
 *
 * Zero external dependencies.
 */

import { readTextFile } from './lib/file-discovery.mjs';
import { finding, scannerResult } from './lib/output.mjs';
import { SEVERITY } from './lib/severity.mjs';
import { parseJson } from './lib/yaml-parser.mjs';

const SCANNER = 'DIS';

/**
 * Bare tool name = everything before the first `(`. `Bash(npm:*)` → `Bash`.
 */
function bareTool(entry) {
  if (typeof entry !== 'string') return null;
  const idx = entry.indexOf('(');
  return (idx === -1 ? entry : entry.slice(0, idx)).trim();
}

/**
 * Find tools whose bare name appears in both deny and allow within the same
 * settings.json. Returns array of { tool, allowEntry, denyEntry }.
 */
function findDenyAllowOverlaps(settings) {
  if (!settings || typeof settings !== 'object') return [];
  const perms = settings.permissions;
  if (!perms || typeof perms !== 'object') return [];

  const allowList = Array.isArray(perms.allow) ? perms.allow : [];
  const denyList = Array.isArray(perms.deny) ? perms.deny : [];
  if (allowList.length === 0 || denyList.length === 0) return [];

  const denyByBare = new Map();
  for (const d of denyList) {
    const bare = bareTool(d);
    if (bare && !denyByBare.has(bare)) denyByBare.set(bare, d);
  }

  const overlaps = [];
  const seen = new Set();
  for (const a of allowList) {
    const bare = bareTool(a);
    if (!bare) continue;
    if (denyByBare.has(bare) && !seen.has(bare)) {
      overlaps.push({ tool: bare, allowEntry: a, denyEntry: denyByBare.get(bare) });
      seen.add(bare);
    }
  }
  return overlaps;
}

/**
 * Main scanner entry point.
 *
 * @param {string} targetPath
 * @param {{files: Array<{absPath:string, relPath:string, type:string}>}} discovery
 */
export async function scan(targetPath, discovery) {
  const start = Date.now();
  const findings = [];
  let filesScanned = 0;

  for (const f of discovery.files) {
    if (f.type !== 'settings-json') continue;
    filesScanned++;
    const content = await readTextFile(f.absPath);
    if (!content) continue;
    const parsed = parseJson(content);
    if (!parsed) continue;
    const overlaps = findDenyAllowOverlaps(parsed);
    if (overlaps.length === 0) continue;

    const evidence = overlaps.slice(0, 5)
      .map(o => `${o.tool}: allow="${o.allowEntry}" + deny="${o.denyEntry}"`)
      .join('; ');
    findings.push(finding({
      scanner: SCANNER,
      severity: SEVERITY.low,
      title: 'Tool listed in both permissions.deny and permissions.allow',
      description:
        `${f.relPath || f.absPath} contains ${overlaps.length} tool` +
        `${overlaps.length === 1 ? '' : 's'} present in both deny and allow lists. ` +
        'The deny list wins — the allow entries are dead config but still load on ' +
        'every turn and may confuse future readers about intent.',
      file: f.absPath,
      evidence,
      recommendation:
        'Remove the redundant allow entries. If you actually want this tool enabled, ' +
        'remove it from the deny list instead. Settings should express intent clearly.',
      category: 'permissions-hygiene',
    }));
  }

  return scannerResult(SCANNER, 'ok', findings, filesScanned, Date.now() - start);
}
