class ReactOnRails::Dev::ServerManager

def box_border(width)

def box_border(width)
  "┌#{'─' * (width - 2)}┐"
end

def box_bottom(width)

def box_bottom(width)
  "└#{'─' * (width - 2)}┘"
end

def box_empty_line(width)

def box_empty_line(width)
  "│#{' ' * (width - 2)}│"
end

def cleanup_socket_files

def cleanup_socket_files
  files = [".overmind.sock", "tmp/sockets/overmind.sock", "tmp/pids/server.pid"]
  killed_any = false
  files.each do |file|
    next unless File.exist?(file)
    puts "   🧹 Removing #{file}"
    File.delete(file)
    killed_any = true
  rescue StandardError
    nil
  end
  killed_any
end

def development_processes

def development_processes
  {
    "rails" => "Rails server",
    "node.*react[-_]on[-_]rails" => "React on Rails Node processes",
    "overmind" => "Overmind process manager",
    "foreman" => "Foreman process manager",
    "ruby.*puma" => "Puma server",
    "webpack-dev-server" => "Webpack dev server",
    "bin/shakapacker-dev-server" => "Shakapacker dev server"
  }
end

def find_port_pids(port)

def find_port_pids(port)
  stdout, _status = Open3.capture2("lsof", "-ti", ":#{port}", err: File::NULL)
  stdout.split("\n").map(&:to_i).reject { |pid| pid == Process.pid }
rescue StandardError
  # lsof command not found or other error (permission denied, etc.)
  []
end

def find_process_pids(pattern)

def find_process_pids(pattern)
  stdout, _status = Open3.capture2("pgrep", "-f", pattern, err: File::NULL)
  stdout.split("\n").map(&:to_i).reject { |pid| pid == Process.pid }
rescue Errno::ENOENT
  # pgrep command not found
  []
end

def format_box_line(content, box_width)

def format_box_line(content, box_width)
  line = "│ #{content}"
  # Use visual length for colored text
  visual_length = Rainbow.uncolor(line).length
  padding = box_width - visual_length - 2
  line + "#{' ' * padding}│"
end

def help_commands

rubocop:disable Metrics/AbcSize
def help_commands
  <<~COMMANDS
    #{Rainbow('🚀 COMMANDS:').cyan.bold}
      #{Rainbow('(none) / hmr').green.bold}        #{Rainbow('Start development server with HMR (default)').white}
                          #{Rainbow('→ Uses:').yellow} Procfile.dev
      #{Rainbow('static').green.bold}              #{Rainbow('Start development server with static assets (no HMR, no FOUC)').white}
                          #{Rainbow('→ Uses:').yellow} Procfile.dev-static-assets
      #{Rainbow('production-assets').green.bold}   #{Rainbow('Start with production-optimized assets (no HMR)').white}
      #{Rainbow('prod').green.bold}                #{Rainbow('Alias for production-assets').white}
                          #{Rainbow('→ Uses:').yellow} Procfile.dev-prod-assets
      #{Rainbow('kill').red.bold}                #{Rainbow('Kill all development processes for a clean start').white}
      #{Rainbow('help').blue.bold}                #{Rainbow('Show this help message').white}
  COMMANDS
end

def help_customization

def help_customization
  <<~CUSTOMIZATION
    #{Rainbow('🔧 CUSTOMIZATION:').cyan.bold}
    Each mode uses a specific Procfile that you can customize for your application:
    #{Rainbow('•').yellow} #{Rainbow('Procfile.dev').green.bold}                 - HMR development with webpack-dev-server
    #{Rainbow('•').yellow} #{Rainbow('Procfile.dev-static-assets').green.bold}   - Static development with webpack --watch
    #{Rainbow('•').yellow} #{Rainbow('Procfile.dev-prod-assets').green.bold}     - Production-optimized assets (port 3001)
    #{Rainbow('Edit these files to customize the development environment for your needs.').white}
  CUSTOMIZATION
end

def help_mode_details

rubocop:disable Metrics/AbcSize
def help_mode_details
  <<~MODES
    #{Rainbow('🔥 HMR Development mode (default)').cyan.bold} - #{Rainbow('Procfile.dev').green}:
    #{Rainbow('•').yellow} #{Rainbow('Hot Module Replacement (HMR) enabled').white}
    #{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation (via precompile hook or bin/dev)').white}
    #{Rainbow('•').yellow} #{Rainbow('Webpack dev server for fast recompilation').white}
    #{Rainbow('•').yellow} #{Rainbow('Source maps for debugging').white}
    #{Rainbow('•').yellow} #{Rainbow('May have Flash of Unstyled Content (FOUC)').white}
    #{Rainbow('•').yellow} #{Rainbow('Fast recompilation').white}
    #{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3000/<route>').cyan.underline}
    #{Rainbow('📦 Static development mode').cyan.bold} - #{Rainbow('Procfile.dev-static-assets').green}:
    #{Rainbow('•').yellow} #{Rainbow('No HMR (static assets with auto-recompilation)').white}
    #{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation (via precompile hook or bin/dev)').white}
    #{Rainbow('•').yellow} #{Rainbow('Webpack watch mode for auto-recompilation').white}
    #{Rainbow('•').yellow} #{Rainbow('CSS extracted to separate files (no FOUC)').white}
    #{Rainbow('•').yellow} #{Rainbow('Development environment (faster builds than production)').white}
    #{Rainbow('•').yellow} #{Rainbow('Source maps for debugging').white}
    #{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3000/<route>').cyan.underline}
    #{Rainbow('🏭 Production-assets mode').cyan.bold} - #{Rainbow('Procfile.dev-prod-assets').green}:
    #{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation (via precompile hook or assets:precompile)').white}
    #{Rainbow('•').yellow} #{Rainbow('Asset precompilation with NODE_ENV=production (webpack optimizations)').white}
    #{Rainbow('•').yellow} #{Rainbow('RAILS_ENV=development by default for assets:precompile (avoids credentials)').white}
    #{Rainbow('•').yellow} #{Rainbow('Use --rails-env=production for assets:precompile only (not server processes)').white}
    #{Rainbow('•').yellow} #{Rainbow('Server processes controlled by Procfile.dev-prod-assets environment').white}
    #{Rainbow('•').yellow} #{Rainbow('Optimized, minified bundles with CSS extraction').white}
    #{Rainbow('•').yellow} #{Rainbow('No HMR (static assets)').white}
    #{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3001/<route>').cyan.underline}
  MODES
end

def help_options

rubocop:disable Metrics/AbcSize
def help_options
  <<~OPTIONS
    #{Rainbow('⚙️  OPTIONS:').cyan.bold}
      #{Rainbow('--route ROUTE').green.bold}        #{Rainbow('Specify route to display in URLs (default: root)').white}
      #{Rainbow('--rails-env ENV').green.bold}      #{Rainbow('Override RAILS_ENV for assets:precompile step only (prod mode only)').white}
      #{Rainbow('--verbose, -v').green.bold}        #{Rainbow('Enable verbose output for pack generation').white}
    #{Rainbow('📝 EXAMPLES:').cyan.bold}
      #{Rainbow('bin/dev prod').green.bold}                    #{Rainbow('# NODE_ENV=production, RAILS_ENV=development').white}
      #{Rainbow('bin/dev prod --rails-env=production').green.bold}  #{Rainbow('# NODE_ENV=production, RAILS_ENV=production').white}
      #{Rainbow('bin/dev prod --route=dashboard').green.bold}       #{Rainbow('# Custom route in URLs').white}
  OPTIONS
end

def help_troubleshooting

rubocop:disable Metrics/AbcSize
def help_troubleshooting
  <<~TROUBLESHOOTING
    #{Rainbow('🔧 TROUBLESHOOTING:').cyan.bold}
    #{Rainbow('⚛️  React Refresh Issues:').yellow.bold}
    #{Rainbow('If you see "$RefreshSig$ is not defined" errors:').white}
    #{Rainbow('1.').green} #{Rainbow('Check that both babel plugin and webpack plugin are configured:').white}
       #{Rainbow('•').yellow} #{Rainbow('babel.config.js: \'react-refresh/babel\' plugin (enabled when WEBPACK_SERVE=true)').white}
       #{Rainbow('•').yellow} #{Rainbow('config/webpack/development.js: ReactRefreshWebpackPlugin (enabled when WEBPACK_SERVE=true)').white}
    #{Rainbow('2.').green} #{Rainbow('Ensure you\'re running HMR mode:').white} #{Rainbow('bin/dev').green.bold} #{Rainbow('(not').white} #{Rainbow('bin/dev static').red}#{Rainbow(')').white}
    #{Rainbow('3.').green} #{Rainbow('Try restarting the development server:').white} #{Rainbow('bin/dev kill && bin/dev').green.bold}
    #{Rainbow('4.').green} #{Rainbow('Note: React Refresh only works in HMR mode, not static mode').white}
    #{Rainbow('🚨 General Issues:').yellow.bold}
    #{Rainbow('•').red} #{Rainbow('"Port already in use"').white} #{Rainbow('→ Run:').yellow} #{Rainbow('bin/dev kill').green.bold}
    #{Rainbow('•').red} #{Rainbow('"Webpack compilation failed"').white} #{Rainbow('→ Check console for specific errors').white}
    #{Rainbow('•').red} #{Rainbow('"Process manager not found"').white} #{Rainbow('→ Install:').yellow} #{Rainbow('brew install overmind').green.bold} #{Rainbow('(or').white} #{Rainbow('gem install foreman').green.bold}#{Rainbow(')').white}
    #{Rainbow('•').red} #{Rainbow('"Assets not loading"').white} #{Rainbow('→ Verify Procfile.dev is present and check server logs').white}
    #{Rainbow('📚 Need help? Visit:').blue.bold} #{Rainbow('https://www.shakacode.com/react-on-rails/docs/').cyan.underline}
  TROUBLESHOOTING
end

def help_usage

def help_usage
  Rainbow("📋 Usage: bin/dev [command] [options]").bold
end

def kill_port_processes(ports)

def kill_port_processes(ports)
  killed_any = false
  ports.each do |port|
    pids = find_port_pids(port)
    next unless pids.any?
    puts "   ☠️  Killing process on port #{port} (PIDs: #{pids.join(', ')})"
    terminate_processes(pids)
    killed_any = true
  end
  killed_any
end

def kill_processes

def kill_processes
  puts "🔪 Killing all development processes..."
  puts ""
  killed_any = kill_running_processes || kill_port_processes([3000, 3001]) || cleanup_socket_files
  print_kill_summary(killed_any)
end

def kill_running_processes

def kill_running_processes
  killed_any = false
  development_processes.each do |pattern, description|
    pids = find_process_pids(pattern)
    next unless pids.any?
    puts "   ☠️  Killing #{description} (PIDs: #{pids.join(', ')})"
    terminate_processes(pids)
    killed_any = true
  end
  killed_any
end

def print_kill_summary(killed_any)

def print_kill_summary(killed_any)
  if killed_any
    puts ""
    puts "✅ All processes terminated and sockets cleaned"
    puts "💡 You can now run 'bin/dev' for a clean start"
  else
    puts "   ℹ️  No development processes found running"
  end
end

def print_procfile_info(procfile, route: nil)

def print_procfile_info(procfile, route: nil)
  port = procfile_port(procfile)
  box_width = 60
  url = route ? "http://localhost:#{port}/#{route}" : "http://localhost:#{port}"
  puts ""
  puts box_border(box_width)
  puts box_empty_line(box_width)
  puts format_box_line("📋 Using Procfile: #{procfile}", box_width)
  puts format_box_line("🔧 Customize this file for your app's needs", box_width)
  puts box_empty_line(box_width)
  puts format_box_line("💡 Access at: #{Rainbow(url).cyan.underline}",
                       box_width)
  puts box_empty_line(box_width)
  puts box_bottom(box_width)
  puts ""
end

def print_server_info(title, features, port = 3000, route: nil)

def print_server_info(title, features, port = 3000, route: nil)
  puts title
  features.each { |feature| puts "   - #{feature}" }
  puts ""
  puts ""
  url = route ? "http://localhost:#{port}/#{route}" : "http://localhost:#{port}"
  puts "💡 Access at: #{Rainbow(url).cyan.underline}"
  puts ""
end

def procfile_port(procfile)

def procfile_port(procfile)
  procfile == "Procfile.dev-prod-assets" ? 3001 : 3000
end

def run_development(procfile, verbose: false, route: nil)

def run_development(procfile, verbose: false, route: nil)
  print_procfile_info(procfile, route: route)
  PackGenerator.generate(verbose: verbose)
  ProcessManager.ensure_procfile(procfile)
  ProcessManager.run_with_process_manager(procfile)
end

def run_from_command_line(args = ARGV)

rubocop:disable Metrics/AbcSize
def run_from_command_line(args = ARGV)
  require "optparse"
  options = { route: nil, rails_env: nil, verbose: false }
  OptionParser.new do |opts|
    opts.banner = "Usage: dev [command] [options]"
    opts.on("--route ROUTE", "Specify the route to display in URLs (default: root)") do |route|
      options[:route] = route
    end
    opts.on("--rails-env ENV", "Override RAILS_ENV for assets:precompile step only (prod mode only)") do |env|
      options[:rails_env] = env
    end
    opts.on("-v", "--verbose", "Enable verbose output for pack generation") do
      options[:verbose] = true
    end
    opts.on("-h", "--help", "Prints this help") do
      show_help
      exit
    end
  end.parse!(args)
  # Get the command (anything that's not parsed as an option)
  command = args[0]
  # Main execution
  case command
  when "production-assets", "prod"
    start(:production_like, nil, verbose: options[:verbose], route: options[:route],
                                 rails_env: options[:rails_env])
  when "static"
    start(:static, "Procfile.dev-static-assets", verbose: options[:verbose], route: options[:route])
  when "kill"
    kill_processes
  when "help", "--help", "-h"
    show_help
  when "hmr", nil
    start(:development, "Procfile.dev", verbose: options[:verbose], route: options[:route])
  else
    puts "Unknown argument: #{command}"
    puts "Run 'dev help' for usage information"
    exit 1
  end
end

def run_production_like(_verbose: false, route: nil, rails_env: nil)

rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
def run_production_like(_verbose: false, route: nil, rails_env: nil)
  procfile = "Procfile.dev-prod-assets"
  features = [
    "Precompiling assets with production optimizations",
    "Running Rails server on port 3001",
    "No HMR (Hot Module Replacement)",
    "CSS extracted to separate files (no FOUC)"
  ]
  # NOTE: Pack generation happens automatically during assets:precompile
  # either via precompile hook or via the configuration.rb adjust_precompile_task
  print_procfile_info(procfile, route: route)
  print_server_info(
    "🏭 Starting production-like development server...",
    features,
    3001,
    route: route
  )
  # Precompile assets with production webpack optimizations (includes pack generation automatically)
  env = { "NODE_ENV" => "production" }
  # Validate and sanitize rails_env to prevent shell injection
  if rails_env
    unless rails_env.match?(/\A[a-z0-9_]+\z/i)
      puts "❌ Invalid rails_env: '#{rails_env}'. Must contain only letters, numbers, and underscores."
      exit 1
    end
    env["RAILS_ENV"] = rails_env
  end
  argv = ["bundle", "exec", "rails", "assets:precompile"]
  puts "🔨 Precompiling assets with production webpack optimizations..."
  puts ""
  puts Rainbow("ℹ️  Asset Precompilation Environment:").blue
  puts "   • NODE_ENV=production → Webpack optimizations (minification, compression)"
  if rails_env
    puts "   • RAILS_ENV=#{rails_env} → Custom Rails environment for assets:precompile only"
    puts "   • Note: RAILS_ENV=production requires credentials, database setup, etc."
    puts "   • Server processes will use environment from Procfile.dev-prod-assets"
  else
    puts "   • RAILS_ENV=development → Simpler Rails setup (no credentials needed)"
    puts "   • Use --rails-env=production for assets:precompile step only"
    puts "   • Server processes will use environment from Procfile.dev-prod-assets"
    puts "   • Gets production webpack bundles without production Rails complexity"
  end
  puts ""
  env_display = env.map { |k, v| "#{k}=#{v}" }.join(" ")
  puts "#{Rainbow('💻 Running:').blue} #{env_display} #{argv.join(' ')}"
  puts ""
  # Capture both stdout and stderr
  require "open3"
  stdout, stderr, status = Open3.capture3(env, *argv)
  if status.success?
    puts "✅ Assets precompiled successfully"
    ProcessManager.ensure_procfile(procfile)
    ProcessManager.run_with_process_manager(procfile)
  else
    puts "❌ Asset precompilation failed"
    puts ""
    # Combine and display all output
    all_output = []
    all_output << stdout unless stdout.empty?
    all_output << stderr unless stderr.empty?
    unless all_output.empty?
      puts Rainbow("📋 Full Command Output:").red.bold
      puts Rainbow("─" * 60).red
      all_output.each { |output| puts output }
      puts Rainbow("─" * 60).red
      puts ""
    end
    puts Rainbow("🛠️  To debug this issue:").yellow.bold
    command_display = "#{env_display} #{argv.join(' ')}"
    puts "#{Rainbow('1.').cyan} #{Rainbow('Run the command separately to see detailed output:').white}"
    puts "   #{Rainbow(command_display).cyan}"
    puts ""
    puts "#{Rainbow('2.').cyan} #{Rainbow('Add --trace for full stack trace:').white}"
    puts "   #{Rainbow("#{command_display} --trace").cyan}"
    puts ""
    puts "#{Rainbow('3.').cyan} #{Rainbow('Or try with development webpack (faster, less optimized):').white}"
    puts "   #{Rainbow('NODE_ENV=development bundle exec rails assets:precompile').cyan}"
    puts ""
    puts Rainbow("💡 Common fixes:").yellow.bold
    # Provide specific guidance based on error content
    error_content = "#{stderr} #{stdout}".downcase
    if error_content.include?("secret_key_base")
      puts "#{Rainbow('•').yellow} #{Rainbow('Missing secret_key_base:').white.bold} " \
           "Run #{Rainbow('bin/rails credentials:edit').cyan}"
    end
    if error_content.include?("database") || error_content.include?("relation") ||
       error_content.include?("table")
      puts "#{Rainbow('•').yellow} #{Rainbow('Database issues:').white.bold} " \
           "Run #{Rainbow('bin/rails db:create db:migrate').cyan}"
    end
    if error_content.include?("gem") || error_content.include?("bundle") || error_content.include?("load error")
      puts "#{Rainbow('•').yellow} #{Rainbow('Missing dependencies:').white.bold} " \
           "Run #{Rainbow('bundle install && npm install').cyan}"
    end
    if error_content.include?("webpack") || error_content.include?("module") ||
       error_content.include?("compilation")
      puts "#{Rainbow('•').yellow} #{Rainbow('Webpack compilation:').white.bold} " \
           "Check JavaScript/webpack errors above"
    end
    # Always show these general options
    puts "#{Rainbow('•').yellow} #{Rainbow('Environment config:').white} " \
         "Check #{Rainbow('config/environments/production.rb').cyan}"
    puts ""
    puts Rainbow("ℹ️  Alternative for development:").blue
    puts "   #{Rainbow('bin/dev static').green}  # Static assets without production optimizations"
    puts ""
    exit 1
  end
end

def run_static_development(procfile, verbose: false, route: nil)

def run_static_development(procfile, verbose: false, route: nil)
  print_procfile_info(procfile, route: route)
  features = [
    "Using shakapacker --watch (no HMR)",
    "CSS extracted to separate files (no FOUC)",
    "Development environment (source maps, faster builds)",
    "Auto-recompiles on file changes"
  ]
  # Add pack generation info if not using precompile hook
  unless ReactOnRails::PackerUtils.shakapacker_precompile_hook_configured?
    features.unshift("Generating React on Rails packs")
  end
  print_server_info(
    "⚡ Starting development server with static assets...",
    features,
    route: route
  )
  PackGenerator.generate(verbose: verbose)
  ProcessManager.ensure_procfile(procfile)
  ProcessManager.run_with_process_manager(procfile)
end

def show_help

def show_help
  puts help_usage
  puts ""
  puts help_commands
  puts ""
  puts help_options
  puts ""
  puts help_customization
  puts ""
  puts help_mode_details
  puts ""
  puts help_troubleshooting
end

def start(mode = :development, procfile = nil, verbose: false, route: nil, rails_env: nil)

def start(mode = :development, procfile = nil, verbose: false, route: nil, rails_env: nil)
  case mode
  when :production_like
    run_production_like(_verbose: verbose, route: route, rails_env: rails_env)
  when :static
    procfile ||= "Procfile.dev-static-assets"
    run_static_development(procfile, verbose: verbose, route: route)
  when :development, :hmr
    procfile ||= "Procfile.dev"
    run_development(procfile, verbose: verbose, route: route)
  else
    raise ArgumentError, "Unknown mode: #{mode}"
  end
end

def terminate_processes(pids)

def terminate_processes(pids)
  pids.each do |pid|
    Process.kill("TERM", pid)
  rescue Errno::ESRCH, ArgumentError, RangeError
    # Process already stopped, or invalid signal/PID - silently skip
    nil
  rescue Errno::EPERM
    # Permission denied - warn the user
    puts "   ⚠️  Process #{pid} - permission denied (process owned by another user)"
    nil
  end
end