import type { IMatcher, IPathMatchResult } from '../types.js';

/**
 * PathMatcher provides comprehensive path matching functionality
 * Supporting exact matches, wildcards, and parameter extraction
 */
export class PathMatcher implements IMatcher<IPathMatchResult> {
  /**
   * Convert a path pattern to a regex and extract parameter names
   * Supports:
   * - Exact paths: /api/users
   * - Wildcards: /api/*
   * - Parameters: /api/users/:id
   * - Mixed: /api/users/:id/*
   */
  private static patternToRegex(pattern: string): { 
    regex: RegExp; 
    paramNames: string[] 
  } {
    const paramNames: string[] = [];
    let regexPattern = pattern;

    // Escape special regex characters except : and *
    regexPattern = regexPattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');

    // Handle path parameters (:param)
    regexPattern = regexPattern.replace(/:(\w+)/g, (match, paramName) => {
      paramNames.push(paramName);
      return '([^/]+)'; // Match any non-slash characters
    });

    // Handle wildcards
    regexPattern = regexPattern.replace(/\*/g, '(.*)');

    // Ensure the pattern matches from start
    regexPattern = `^${regexPattern}`;
    
    // If pattern doesn't end with wildcard, ensure it matches to end
    // But only for patterns that don't have parameters or wildcards
    if (!pattern.includes('*') && !pattern.includes(':') && !pattern.endsWith('/')) {
      regexPattern = `${regexPattern}$`;
    }

    return {
      regex: new RegExp(regexPattern),
      paramNames
    };
  }

  /**
   * Match a path pattern against a request path
   * @param pattern The pattern to match
   * @param path The request path to test
   * @returns Match result with params and remainder
   */
  static match(pattern: string, path: string): IPathMatchResult {
    // Handle null/undefined cases
    if (!pattern || !path) {
      return { matches: false };
    }

    // Normalize paths (remove trailing slashes unless it's just "/")
    const normalizedPattern = pattern === '/' ? '/' : pattern.replace(/\/$/, '');
    const normalizedPath = path === '/' ? '/' : path.replace(/\/$/, '');

    // Exact match (most common case)
    if (normalizedPattern === normalizedPath) {
      return {
        matches: true,
        pathMatch: normalizedPath,
        pathRemainder: '',
        params: {}
      };
    }

    // Pattern matching (wildcards and parameters)
    const { regex, paramNames } = this.patternToRegex(normalizedPattern);
    const match = normalizedPath.match(regex);

    if (!match) {
      return { matches: false };
    }

    // Extract parameters
    const params: Record<string, string> = {};
    paramNames.forEach((name, index) => {
      params[name] = match[index + 1];
    });

    // Calculate path match and remainder
    let pathMatch = match[0];
    let pathRemainder = normalizedPath.substring(pathMatch.length);

    // Handle wildcard captures
    if (normalizedPattern.includes('*') && match.length > paramNames.length + 1) {
      const wildcardCapture = match[match.length - 1];
      if (wildcardCapture) {
        // Ensure pathRemainder includes leading slash if it had one
        pathRemainder = wildcardCapture.startsWith('/') ? wildcardCapture : '/' + wildcardCapture;
        pathMatch = normalizedPath.substring(0, normalizedPath.length - wildcardCapture.length);
      }
    }

    // Clean up path match (remove trailing slash if present)
    if (pathMatch !== '/' && pathMatch.endsWith('/')) {
      pathMatch = pathMatch.slice(0, -1);
    }

    return {
      matches: true,
      pathMatch,
      pathRemainder,
      params
    };
  }

  /**
   * Check if a pattern contains parameters or wildcards
   */
  static isDynamicPattern(pattern: string): boolean {
    return pattern.includes(':') || pattern.includes('*');
  }

  /**
   * Calculate the specificity of a path pattern
   * Higher values mean more specific patterns
   */
  static calculateSpecificity(pattern: string): number {
    if (!pattern) return 0;

    let score = 0;
    
    // Exact paths are most specific
    if (!this.isDynamicPattern(pattern)) {
      score += 100;
    }
    
    // Count path segments
    const segments = pattern.split('/').filter(s => s.length > 0);
    score += segments.length * 10;
    
    // Count static segments (more static = more specific)
    const staticSegments = segments.filter(s => !s.startsWith(':') && s !== '*');
    score += staticSegments.length * 20;
    
    // Penalize wildcards and parameters
    const wildcards = (pattern.match(/\*/g) || []).length;
    const params = (pattern.match(/:/g) || []).length;
    score -= wildcards * 30; // Wildcards are very generic
    score -= params * 10;    // Parameters are somewhat generic
    
    // Bonus for longer patterns
    score += pattern.length;
    
    return score;
  }

  /**
   * Find all matching patterns from a list
   * Returns patterns sorted by specificity (most specific first)
   */
  static findAllMatches(patterns: string[], path: string): Array<{
    pattern: string;
    result: IPathMatchResult;
  }> {
    const matches = patterns
      .map(pattern => ({
        pattern,
        result: this.match(pattern, path)
      }))
      .filter(({ result }) => result.matches);

    // Sort by specificity (highest first)
    return matches.sort((a, b) => 
      this.calculateSpecificity(b.pattern) - this.calculateSpecificity(a.pattern)
    );
  }

  /**
   * Instance method for interface compliance
   */
  match(pattern: string, path: string): IPathMatchResult {
    return PathMatcher.match(pattern, path);
  }
}