require "colorize"

module Lattice
  module CLI
    # Inspects the local environment for Lattice prerequisites and prints a
    # pass/fail report.  Exits with code 1 if any required tools are missing.
    class Doctor
      REQUIRED_TOOLS = ["crystal"]
      OPTIONAL_TOOLS = ["ameba"]

      def self.run : NoReturn
        puts "Lattice Doctor — environment check"
        puts "=" * 40

        ok = true

        ok = false unless check_crystal_version
        ok = false unless check_shard_dependencies
        check_optional_tools

        puts "=" * 40
        if ok
          puts "All required checks passed.".colorize(:green)
          exit 0
        else
          STDERR.puts "One or more required checks failed.".colorize(:red)
          exit 1
        end
      end

      MIN_CRYSTAL_VERSION = {1, 20, 0}

      private def self.check_crystal_version : Bool
        output = `crystal --version 2>&1`.strip
        unless $?.success?
          fail_check("Crystal compiler", "not found — install Crystal from https://crystal-lang.org")
          return false
        end

        first_line = output.lines.first? || output
        # Parse the version number from e.g. "Crystal 1.20.0 (2026-04-16)"
        if m = first_line.match(/Crystal (\d+)\.(\d+)\.(\d+)/)
          major, minor, patch = m[1].to_i, m[2].to_i, m[3].to_i
          actual = {major, minor, patch}
          if actual >= MIN_CRYSTAL_VERSION
            pass("Crystal compiler", first_line)
            true
          else
            fail_check("Crystal compiler",
              "#{first_line} — Lattice 0.7.4 requires Crystal >= 1.20.0")
            false
          end
        else
          pass("Crystal compiler", first_line)
          true
        end
      end

      private def self.check_shard_dependencies : Bool
        if File.exists?("shard.lock")
          pass("shard.lock", "present")
          true
        else
          warn_check("shard dependencies", "shard.lock not found — run `shards install`")
          # Not a hard failure; some projects omit the lock file.
          true
        end
      end

      private def self.check_optional_tools : Nil
        if find_in_path("ameba")
          pass("ameba linter", "found (inspection integration active)")
        else
          warn_check("ameba linter", "not found — ameba inspections will be skipped\n" \
                     "  Install from https://github.com/crystal-ameba/ameba#installation")
        end
      end

      private def self.find_in_path(binary : String) : Bool
        path_dirs = (ENV["PATH"]? || "").split(Process::PATH_DELIMITER)
        path_dirs.any? do |dir|
          check_executable(File.join(dir, binary)) ||
            check_executable(File.join(dir, "#{binary}.exe"))
        end
      end

      private def self.pass(label : String, detail : String) : Nil
        puts "  [PASS] #{label}: #{detail}".colorize(:green)
      end

      private def self.fail_check(label : String, detail : String) : Nil
        puts "  [FAIL] #{label}: #{detail}".colorize(:red)
      end

      private def self.check_executable(path : String) : Bool
        info = File.info?(path, follow_symlinks: true)
        return false unless info
        info.type.file?
      end

      private def self.warn_check(label : String, detail : String) : Nil
        puts "  [WARN] #{label}: #{detail}".colorize(:yellow)
      end
    end
  end
end
