// Compile-once pattern handles for build tools, indexes, and repeated queries.

///|
/// A reusable compiled glob pattern with stable structural metadata.
pub(all) struct CompiledPattern {
  source : String
  ast : AST
  stats : PatternStats
  prefix : String
  separators : Int
  recursive : Bool
  segments : Int
} derive(Debug, Eq)

///|
fn compiled_segment_count(source : String) -> Int {
  let normalized = normalize_path(source)
  if normalized.is_empty() {
    0
  } else {
    let mut count = 1
    let mut i = 0
    while i < normalized.length() {
      if normalized[i].to_int().unsafe_to_char() == '/' {
        count = count + 1
      }
      i = i + 1
    }
    count
  }
}

///|
fn compiled_stats(pattern : String, ast : AST) -> PatternStats {
  { ..ast_stats(ast), escaped_char_count: count_escaped_chars(pattern) }
}

///|
/// Compiles one pattern and records metadata without changing matching rules.
pub fn compile_pattern(pattern : String) -> Result[CompiledPattern, GlobError] {
  match compile(pattern) {
    Err(err) => Err(err)
    Ok(ast) => {
      let prefix = literal_prefix(pattern)
      let separators = separator_count(pattern)
      let recursive = has_recursive_wildcard(pattern)
      match (prefix, separators, recursive) {
        (Ok(prefix), Ok(separators), Ok(recursive)) =>
          Ok({
            source: pattern,
            ast,
            stats: compiled_stats(pattern, ast),
            prefix,
            separators,
            recursive,
            segments: compiled_segment_count(pattern),
          })
        (Err(err), _, _) => Err(err)
        (_, Err(err), _) => Err(err)
        (_, _, Err(err)) => Err(err)
      }
    }
  }
}

///|
/// Compiles a batch while preserving the caller's pattern order.
pub fn compile_patterns(
  patterns : Array[String],
) -> Result[Array[CompiledPattern], GlobError] {
  let result : Array[CompiledPattern] = []
  for pattern in patterns {
    match compile_pattern(pattern) {
      Ok(compiled) => result.push(compiled)
      Err(err) => return Err(err)
    }
  }
  Ok(result)
}

///|
/// Returns the original source pattern.
pub fn CompiledPattern::pattern(self : CompiledPattern) -> String {
  self.source
}

///|
/// Returns the compiled syntax tree for advanced callers.
pub fn CompiledPattern::ast(self : CompiledPattern) -> AST {
  self.ast
}

///|
/// Returns structural pattern statistics captured at compile time.
pub fn CompiledPattern::stats(self : CompiledPattern) -> PatternStats {
  self.stats
}

///|
/// Returns the literal prefix before the first wildcard.
pub fn CompiledPattern::literal_prefix(self : CompiledPattern) -> String {
  self.prefix
}

///|
/// Returns the number of path separators in the source pattern.
pub fn CompiledPattern::separator_count(self : CompiledPattern) -> Int {
  self.separators
}

///|
/// Returns whether the pattern contains the recursive \\`**\\` construct.
pub fn CompiledPattern::has_recursive_wildcard(self : CompiledPattern) -> Bool {
  self.recursive
}

///|
/// Returns the number of non-empty source path components.
pub fn CompiledPattern::segment_count(self : CompiledPattern) -> Int {
  self.segments
}

///|
/// Matches one path without reparsing the source pattern.
pub fn CompiledPattern::matches(self : CompiledPattern, path : String) -> Bool {
  match_path(self.ast, normalize_path(path))
}

///|
/// Returns true when this handle has at least one wildcard construct.
pub fn CompiledPattern::has_magic(self : CompiledPattern) -> Bool {
  self.stats.has_magic()
}

///|
/// Returns true when this handle is a literal-only pattern.
pub fn CompiledPattern::is_literal(self : CompiledPattern) -> Bool {
  self.stats.is_literal()
}

///|
/// Estimates the number of matching branches exposed by the syntax tree.
pub fn CompiledPattern::estimated_branches(self : CompiledPattern) -> Int {
  let stats = self.stats
  let mut estimate = 1
  estimate = estimate + stats.star_count
  estimate = estimate + stats.globstar_count * 2
  estimate = estimate + stats.question_count
  estimate = estimate + stats.char_class_count
  estimate = estimate + stats.brace_group_count * 2
  estimate
}

///|
/// Returns a compact, deterministic metadata summary useful in logs.
pub fn CompiledPattern::summary(self : CompiledPattern) -> String {
  "pattern=" +
  self.source +
  ",prefix=" +
  self.prefix +
  ",segments=" +
  self.segments.to_string() +
  ",separators=" +
  self.separators.to_string() +
  ",magic=" +
  self.has_magic().to_string() +
  ",recursive=" +
  self.recursive.to_string() +
  ",branches=" +
  self.estimated_branches().to_string()
}

///|
/// Matches one path against any previously compiled pattern.
pub fn match_compiled_any(
  patterns : Array[CompiledPattern],
  path : String,
) -> Bool {
  for pattern in patterns {
    if pattern.matches(path) {
      return true
    }
  }
  false
}

///|
/// Matches one path against every previously compiled pattern.
pub fn match_compiled_all(
  patterns : Array[CompiledPattern],
  path : String,
) -> Bool {
  for pattern in patterns {
    if !pattern.matches(path) {
      return false
    }
  }
  true
}

///|
/// Filters a path list using compiled patterns and removes duplicate results.
pub fn filter_compiled_patterns(
  patterns : Array[CompiledPattern],
  paths : Array[String],
) -> Array[String] {
  let result : Array[String] = []
  for path in paths {
    let normalized = normalize_path(path)
    if match_compiled_any(patterns, normalized) && !result.contains(normalized) {
      result.push(normalized)
    }
  }
  result
}

///|
/// Returns the paths that match none of the compiled patterns.
pub fn filter_not_compiled_patterns(
  patterns : Array[CompiledPattern],
  paths : Array[String],
) -> Array[String] {
  let result : Array[String] = []
  for path in paths {
    let normalized = normalize_path(path)
    if !match_compiled_any(patterns, normalized) && !result.contains(normalized) {
      result.push(normalized)
    }
  }
  result
}