/**
 * IMP Scanner — Import Resolver
 * Resolves @import references in CLAUDE.md files: broken links, circular refs, deep chains.
 * Finding IDs: CA-IMP-NNN
 */

import { resolve, dirname, basename } from 'node:path';
import { tmpdir } from 'node:os';
import { stat } from 'node:fs/promises';
import { readTextFile } from './lib/file-discovery.mjs';
import { finding, scannerResult } from './lib/output.mjs';
import { SEVERITY } from './lib/severity.mjs';
import { findImports } from './lib/yaml-parser.mjs';
import { truncate } from './lib/string-utils.mjs';

const SCANNER = 'IMP';
const MAX_CHAIN_DEPTH = 5;
const HARD_LIMIT = 20;

/**
 * Check if a file exists.
 * @param {string} absPath
 * @returns {Promise<boolean>}
 */
async function fileExists(absPath) {
  try {
    await stat(absPath);
    return true;
  } catch {
    return false;
  }
}

/**
 * Resolve an import path relative to the containing file.
 * @param {string} importPath
 * @param {string} containingFile
 * @returns {{ resolved: string, hasTilde: boolean }}
 */
function resolveImportPath(importPath, containingFile) {
  const hasTilde = importPath.startsWith('~');
  let resolved;

  if (hasTilde) {
    const home = process.env.HOME || process.env.USERPROFILE || tmpdir();
    resolved = resolve(importPath.replace(/^~/, home));
  } else if (importPath.startsWith('/')) {
    resolved = importPath;
  } else {
    resolved = resolve(dirname(containingFile), importPath);
  }

  return { resolved, hasTilde };
}

/**
 * Walk imports recursively from a starting file via DFS.
 * @param {string} file - Absolute path to current file
 * @param {string[]} chain - Current chain of files (for cycle detection)
 * @param {Set<string>} reported - Set of "from::to" pairs already reported
 * @param {object[]} findings - Accumulator for findings
 */
async function walkImports(file, chain, reported, findings) {
  const content = await readTextFile(file);
  if (!content) return;

  const imports = findImports(content);
  for (const imp of imports) {
    const { resolved, hasTilde } = resolveImportPath(imp.path, file);
    const reportKey = `${file}::${resolved}`;

    // Tilde path warning
    if (hasTilde && !reported.has(`tilde::${resolved}`)) {
      reported.add(`tilde::${resolved}`);
      findings.push(finding({
        scanner: SCANNER,
        severity: SEVERITY.medium,
        title: 'Tilde path in @import',
        description: `@${imp.path} uses ~ which may not expand correctly in all contexts.`,
        file,
        line: imp.line,
        evidence: `@${imp.path}`,
        recommendation: 'Use a relative path or absolute path without tilde expansion.',
      }));
    }

    // Check file existence
    const exists = await fileExists(resolved);
    if (!exists) {
      if (!reported.has(reportKey)) {
        reported.add(reportKey);
        findings.push(finding({
          scanner: SCANNER,
          severity: SEVERITY.high,
          title: 'Broken @import link',
          description: `@${imp.path} references a file that does not exist.`,
          file,
          line: imp.line,
          evidence: `@${imp.path} → ${truncate(resolved, 80)}`,
          recommendation: 'Fix the path or create the missing file.',
        }));
      }
      continue;
    }

    // Circular reference detection
    if (chain.includes(resolved)) {
      if (!reported.has(reportKey)) {
        reported.add(reportKey);
        const cycleStart = chain.indexOf(resolved);
        const cycle = chain.slice(cycleStart).map(f => basename(f)).join(' → ');
        findings.push(finding({
          scanner: SCANNER,
          severity: SEVERITY.medium,
          title: 'Circular @import reference',
          description: `@${imp.path} creates a circular import chain.`,
          file,
          line: imp.line,
          evidence: `${cycle} → ${basename(resolved)}`,
          recommendation: 'Break the circular dependency by removing one of the @imports.',
        }));
      }
      continue;
    }

    // Deep chain warning
    if (chain.length >= MAX_CHAIN_DEPTH) {
      if (!reported.has(`deep::${resolved}`)) {
        reported.add(`deep::${resolved}`);
        findings.push(finding({
          scanner: SCANNER,
          severity: SEVERITY.low,
          title: 'Deep @import chain',
          description: `@${imp.path} is at depth ${chain.length} (>${MAX_CHAIN_DEPTH} hops).`,
          file,
          line: imp.line,
          evidence: `Chain depth: ${chain.length}`,
          recommendation: 'Flatten the import hierarchy to reduce nesting.',
        }));
      }
      continue;
    }

    // Hard limit safety bail
    if (chain.length >= HARD_LIMIT) continue;

    // Recurse
    await walkImports(resolved, [...chain, resolved], reported, findings);
  }
}

/**
 * Scan all CLAUDE.md files for @import issues.
 * @param {string} targetPath
 * @param {{ files: import('./lib/file-discovery.mjs').ConfigFile[] }} discovery
 * @returns {Promise<object>}
 */
export async function scan(targetPath, discovery) {
  const start = Date.now();
  const claudeMdFiles = discovery.files.filter(f => f.type === 'claude-md');
  const findings = [];
  let filesScanned = 0;

  if (claudeMdFiles.length === 0) {
    return scannerResult(SCANNER, 'skipped', [], 0, Date.now() - start);
  }

  const reported = new Set();

  for (const file of claudeMdFiles) {
    const content = await readTextFile(file.absPath);
    if (!content) continue;

    const imports = findImports(content);
    if (imports.length === 0) {
      filesScanned++;
      continue;
    }

    filesScanned++;
    await walkImports(file.absPath, [file.absPath], reported, findings);
  }

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