class Utils::Finder

def build_paths

def build_paths
  paths = []
  visit = -> filename {
    s  = filename.stat
    bn = filename.pathname.basename
    if !s ||
        s.directory? && @config.discover.prune?(bn) ||
        s.file? && @config.discover.skip?(bn)
    then
      @args[?v] and warn "Pruning #{filename.inspect}."
      prune
    end
    true
  }
  find(*@roots, visit: visit) do |filename|
    filename.stat.directory? and filename << ?/
    paths << filename
  end
  paths.uniq!
  paths
end

def create_paths

def create_paths
  paths = build_paths
  File.secure_write(index_path) do |output|
    output.puts paths
  end
  paths
end

def discover_roots(roots)

def discover_roots(roots)
  roots ||= []
  roots.inject([]) { |rs, r| rs.concat Dir[r] }
end

def index_expired?(path)

def index_expired?(path)
  if duration = @config.discover.index_expire_after
    Time.now - duration >= File.mtime(path)
  else
    false
  end
rescue Errno::ENOENT
  false
end

def index_path

def index_path
  roots = @roots.map { |r| File.expand_path(r) }.uniq.sort
  filename = "finder-paths-" +
    Digest::MD5.new.update(roots.inspect).hexdigest
  dirname = File.join(Dir.tmpdir, File.basename($0))
  FileUtils.mkdir_p dirname
  File.join(dirname, filename)
end

def initialize(opts = {})

def initialize(opts = {})
  @args  = opts[:args] || {}
  @roots = discover_roots(opts[:roots])
  @config = opts[:config] || Utils::ConfigFile.new
  if @args[?l] || @args[?L]
    @pattern = nil
  else
    pattern_opts = opts.subhash(:pattern) | {
      :cset  => @args[?a],
      :icase => @args[?i] != ?n,
    }
    @pattern = choose(@args[?p], pattern_opts)
  end
  @paths  = []
  reset_index
end

def load_paths

def load_paths
  lines = File.readlines(index_path)
  @args[?v] and warn "Loaded index #{index_path.inspect}."
  lines.empty? and raise Errno::ENOENT
  @args[?d] or lines = lines.grep_v(%r{/$})
  lines.map(&:chomp!)
rescue Errno::ENOENT
  return create_paths
end

def reset_index

def reset_index
  path = index_path
  if @args[?r] || index_expired?(path)
    @args[?v] and warn "Resetting index #{path.inspect}."
    FileUtils.rm_f path
    mize_cache_clear
  end
  self
end

def search_directly

def search_directly
  search_paths build_paths
end

def search_index

def search_index
  paths = load_paths
  search_paths(paths)
end

def search_index

def search_index
  search_paths load_paths
end

def search_paths(paths)

def search_paths(paths)
  suffixes = Array(@args[?I])
  suffixes.full? do |s|
    paths.select! { |path| s.include?(File.extname(path)[1..-1]) }
  end
  paths = paths.map! do |path|
    if @pattern.nil?
      [ [ path.count(?/), path ], path, path ]
    elsif match = @pattern.match(path)
      if FuzzyPattern === @pattern
        current = 0
        marked_path = ''
        score, e = path.size, nil
        for i in 1...match.size
          match[i] or next
          b = match.begin(i)
          e ||= b
          marked_path << path[current...b]
          marked_path << red(path[b, 1])
          score += (b - e) * (path.size - b)
          e = match.end(i)
          current = b + 1
        end
        marked_path << match.post_match
        [ score, path, marked_path ]
      else
        marked_path = path[0...match.begin(0)] <<
          red(path[match.begin(0)...match.end(0)]) <<
          path[match.end(0)..-1]
        [ 0, path, marked_path ]
      end
    end
  end
  paths.compact!
  @paths, @output = paths.sort.transpose.values_at(-2, -1)
  if n = @args[?n]&.to_i
    @paths = @paths&.first(n) || []
    @output = @output&.first(n) || []
  end
  self
end