module Lattice
  module CLI
    # Runs offline diagnostics on one or more Crystal source files or
    # directories. Exits with code 1 if any errors are found.
    class Check
      def self.run(paths : Array(String)) : NoReturn
        if paths.empty?
          STDERR.puts "lattice check: no path(s) specified"
          STDERR.puts "Usage: lattice check <file_or_directory> [...]"
          exit 1
        end

        crystal_files = collect_crystal_files(paths)

        if crystal_files.empty?
          STDERR.puts "lattice check: no .cr files found in the specified path(s)"
          exit 1
        end

        error_count = 0

        crystal_files.each do |file|
          errors = run_diagnostics_on(file)
          errors.each do |diag|
            severity_label = format_severity(diag[:severity])
            puts "#{file}:#{diag[:line]}:#{diag[:col]}: #{severity_label}: #{diag[:message]}"
            error_count += 1 if diag[:severity] == :error
          end
        end

        if error_count > 0
          STDERR.puts "\n#{error_count} error(s) found."
          exit 1
        else
          puts "No errors found."
          exit 0
        end
      end

      private def self.collect_crystal_files(paths : Array(String)) : Array(String)
        files = [] of String
        paths.each do |path|
          if File.directory?(path)
            Dir.glob(File.join(path, "**", "*.cr")).each { |f| files << f }
          elsif File.file?(path) && path.ends_with?(".cr")
            files << path
          else
            STDERR.puts "lattice check: warning: skipping '#{path}' (not a .cr file or directory)"
          end
        end
        files.sort
      end

      private def self.run_diagnostics_on(file : String) : Array(NamedTuple(line: Int32, col: Int32, severity: Symbol, message: String))
        results = [] of NamedTuple(line: Int32, col: Int32, severity: Symbol, message: String)
        source = File.read(file)
        parser = ::Crystal::Parser.new(source)
        begin
          parser.parse
        rescue ex : ::Crystal::SyntaxException
          results << {
            line:     ex.line_number,
            col:      ex.column_number,
            severity: :error,
            message:  ex.message || "Syntax error",
          }
        end
        results
      end

      private def self.format_severity(severity : Symbol) : String
        case severity
        when :error   then "error"
        when :warning then "warning"
        else               "info"
        end
      end
    end
  end
end
