#!/usr/bin/env node
/**
 * pattern-matcher.cjs - Gitignore-spec compliant pattern matching
 *
 * Uses 'ignore' package for .ckignore parsing and path matching.
 * Supports negation patterns (!) for allowlisting.
 */

const Ignore = require('./vendor/ignore.cjs');
const fs = require('fs');
const path = require('path');

// Default patterns if .ckignore doesn't exist or is empty
// Only includes directories with HEAVY file counts (1000+ files typical)
const DEFAULT_PATTERNS = [
  // JavaScript/TypeScript - package dependencies & build outputs
  'node_modules',
  'dist',
  'build',
  '.next',
  '.nuxt',
  // Python - virtualenvs & cache
  '__pycache__',
  '.venv',
  'venv',
  // Go/PHP - vendor dependencies
  'vendor',
  // Rust/Java - compiled outputs
  'target',
  // Version control
  '.git',
  // Test coverage (can be large with reports)
  'coverage',
];

function readPatternsFromFile(filePath) {
  if (!filePath || !fs.existsSync(filePath)) {
    return [];
  }

  try {
    return fs.readFileSync(filePath, 'utf-8')
      .split('\n')
      .map(line => line.trim())
      .filter(line => line && !line.startsWith('#'));
  } catch (error) {
    console.error('WARN: Failed to read .ckignore:', error.message);
    return [];
  }
}

/**
 * Load patterns from the shipped .ckignore plus an optional project override.
 * Falls back to DEFAULT_PATTERNS if the shipped file doesn't exist or is empty.
 *
 * @param {string} ckignorePath - Path to shipped/global .ckignore file
 * @param {string} [projectCkignorePath] - Optional project-local .ckignore path
 * @returns {string[]} Array of patterns
 */
function loadPatterns(ckignorePath, projectCkignorePath) {
  const shippedPatterns = readPatternsFromFile(ckignorePath);
  const projectPatterns = readPatternsFromFile(projectCkignorePath);
  const basePatterns = shippedPatterns.length > 0 ? shippedPatterns : DEFAULT_PATTERNS;
  return [...basePatterns, ...projectPatterns];
}

/**
 * Create a matcher from patterns
 * Normalizes patterns to match anywhere in the path tree
 *
 * @param {string[]} patterns - Array of patterns from .ckignore
 * @returns {Object} Matcher object with ig instance and pattern info
 */
function createMatcher(patterns) {
  const ig = Ignore();

  // Normalize patterns to match anywhere in path tree
  // e.g., "node_modules" becomes "**\/node_modules" and "**\/node_modules/**"
  const normalizedPatterns = [];

  for (const p of patterns) {
    if (p.startsWith('!')) {
      // Negation pattern - un-ignore
      const inner = p.slice(1);
      if (inner.includes('/') || inner.includes('*')) {
        // Already has path or glob - use as-is
        normalizedPatterns.push(p);
      } else {
        // Simple dir name - match anywhere
        normalizedPatterns.push(`!**/${inner}`);
        normalizedPatterns.push(`!**/${inner}/**`);
      }
    } else {
      // Block pattern
      if (p.includes('/') || p.includes('*')) {
        // Already has path or glob - use as-is
        normalizedPatterns.push(p);
      } else {
        // Simple dir name - match the dir and contents anywhere
        normalizedPatterns.push(`**/${p}`);
        normalizedPatterns.push(`**/${p}/**`);
        // Also match at root
        normalizedPatterns.push(p);
        normalizedPatterns.push(`${p}/**`);
      }
    }
  }

  ig.add(normalizedPatterns);

  return {
    ig,
    patterns: normalizedPatterns,
    original: patterns
  };
}

/**
 * Check if a path should be blocked
 *
 * @param {Object} matcher - Matcher object from createMatcher
 * @param {string} testPath - Path to test
 * @returns {Object} { blocked: boolean, pattern?: string }
 */
function matchPath(matcher, testPath) {
  if (!testPath || typeof testPath !== 'string') {
    return { blocked: false };
  }

  // Normalize path separators (Windows backslash to forward slash)
  let normalized = testPath.replace(/\\/g, '/');

  // Remove leading ./ if present
  if (normalized.startsWith('./')) {
    normalized = normalized.slice(2);
  }

  // Strip leading / for absolute paths (ignore lib requires relative paths)
  while (normalized.startsWith('/')) {
    normalized = normalized.slice(1);
  }

  // Strip leading ../ segments (resolve parent references)
  while (normalized.startsWith('../')) {
    normalized = normalized.slice(3);
  }

  // Empty after normalization = not a blockable path
  if (!normalized) {
    return { blocked: false };
  }

  // Check if path is ignored (blocked)
  const blocked = matcher.ig.ignores(normalized);

  if (blocked) {
    // Find which original pattern matched for error message
    const matchedPattern = findMatchingPattern(matcher.original, normalized);
    return { blocked: true, pattern: matchedPattern };
  }

  return { blocked: false };
}

/**
 * Find which original pattern matched (for error messages)
 *
 * @param {string[]} originalPatterns - Original patterns from .ckignore
 * @param {string} path - The path that was blocked
 * @returns {string} The pattern that matched
 */
function findMatchingPattern(originalPatterns, path) {
  for (const p of originalPatterns) {
    if (p.startsWith('!')) continue; // Skip negations

    // Simple substring check for common cases
    const pattern = p.replace(/\*\*/g, '').replace(/\*/g, '');
    if (pattern && path.includes(pattern)) {
      return p;
    }

    // For more complex patterns, use ignore to test individually
    const tempIg = Ignore();
    if (p.includes('/') || p.includes('*')) {
      tempIg.add(p);
    } else {
      tempIg.add([`**/${p}`, `**/${p}/**`, p, `${p}/**`]);
    }

    if (tempIg.ignores(path)) {
      return p;
    }
  }

  return originalPatterns.find(p => !p.startsWith('!')) || 'unknown';
}

module.exports = {
  loadPatterns,
  createMatcher,
  matchPath,
  findMatchingPattern,
  DEFAULT_PATTERNS
};
