class Opal::CliRunners::Chrome

def self.call(data)

def self.call(data)
  runner = new(data)
  runner.run
end

def chrome_executable

def chrome_executable
  ENV['GOOGLE_CHROME_BINARY'] ||
    if OS.windows?
      [
        'C:/Program Files/Google/Chrome Dev/Application/chrome.exe',
        'C:/Program Files/Google/Chrome/Application/chrome.exe'
      ].each do |path|
        next unless File.exist? path
        return path
      end
    elsif OS.macos?
      '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
    else
      %w[
        google-chrome-stable
        chromium
        chromium-freeworld
        chromium-browser
      ].each do |name|
        next unless system('sh', '-c', "command -v #{name.shellescape}", out: '/dev/null')
        return name
      end
      raise 'Cannot find chrome executable'
    end
end

def chrome_host

def chrome_host
  ENV['CHROME_HOST'] || DEFAULT_CHROME_HOST
end

def chrome_port

def chrome_port
  ENV['CHROME_PORT'] || DEFAULT_CHROME_PORT
end

def chrome_server_running?

def chrome_server_running?
  puts "Connecting to #{chrome_host}:#{chrome_port}..."
  TCPSocket.new(chrome_host, chrome_port).close
  true
rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL
  false
end

def initialize(data)

def initialize(data)
  argv = data[:argv]
  if argv && argv.any?
    warn "warning: ARGV is not supported by the Chrome runner #{argv.inspect}"
  end
  options  = data[:options]
  @output  = options.fetch(:output, $stdout)
  @builder = data[:builder].call
end

def mktmpdir(&block)

def mktmpdir(&block)
  Dir.mktmpdir('chrome-opal-', &block)
end

def mktmpprofile

def mktmpprofile
  Dir.mktmpdir('chrome-opal-profile-')
end

def prepare_files_in(dir)

def prepare_files_in(dir)
  js = builder.to_s
  map = builder.source_map.to_json
  stack = File.binread("#{__dir__}/source-map-support-browser.js")
  ext = builder.output_extension
  module_type = ' type="module"' if builder.esm?
  # Some maps may contain `</script>` fragment (eg. in strings) which would close our
  # `<script>` tag prematurely. For this case, we need to escape the `</script>` tag.
  map_json = map.to_json.gsub(/(<\/scr)(ipt>)/i, '\1"+"\2')
  # Chrome can't handle huge data passed to `addScriptToEvaluateOnLoad`
  # https://groups.google.com/a/chromium.org/forum/#!topic/chromium-discuss/U5qyeX_ydBo
  # The only way is to create temporary files and pass them to chrome.
  File.binwrite("#{dir}/index.#{ext}", js)
  File.binwrite("#{dir}/index.map", map)
  File.binwrite("#{dir}/source-map-support.js", stack)
  File.binwrite("#{dir}/index.html", <<~HTML)
    <html><head>
      <meta charset='utf-8'>
      <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon">
      <script src='./source-map-support.js'></script>
      <script>
      window.opalheadlesschrome = true;
      sourceMapSupport.install({
        retrieveSourceMap: function(path) {
          return path.endsWith('/index.#{ext}') ? {
            url: './index.map', map: #{map_json}
          } : null;
        }
      });
      </script>
    </head><body>
      <script src='./index.#{ext}'#{module_type}></script>
    </body></html>
  HTML
end

def run

def run
  mktmpdir do |dir|
    with_chrome_server do
      prepare_files_in(dir)
      env = {
        'CHROME_HOST' => chrome_host,
        'CHROME_PORT' => chrome_port.to_s,
        'NODE_PATH' => File.join(__dir__, 'node_modules'),
        'OPAL_CDP_EXT' => builder.output_extension
      }
      cmd = [
        RbConfig.ruby,
        "#{__dir__}/../../../exe/opal",
        '--no-exit',
        '-I', __dir__,
        '-r', 'source-map-support-node',
        SCRIPT_PATH,
        dir
      ]
      Kernel.exec(env, *cmd)
    end
  end
end

def run_chrome_server

def run_chrome_server
  raise 'Chrome server can be started only on localhost' if chrome_host != DEFAULT_CHROME_HOST
  profile = mktmpprofile
  # Disable web security with "--disable-web-security" flag to be able to do XMLHttpRequest (see test_openuri.rb)
  # For other options see https://github.com/puppeteer/puppeteer/blob/main/packages/puppeteer-core/src/node/ChromeLauncher.ts
  chrome_server_cmd = %{#{OS.shellescape(chrome_executable)} \
    --allow-pre-commit-input \
    --disable-background-networking \
    --enable-features=NetworkServiceInProcess2 \
    --disable-background-timer-throttling \
    --disable-backgrounding-occluded-windows \
    --disable-breakpad \
    --disable-client-side-phishing-detection \
    --disable-component-extensions-with-background-pages \
    --disable-default-apps \
    --disable-dev-shm-usage \
    --disable-extensions \
    --disable-features=Translate,BackForwardCache,AcceptCHFrame,AvoidUnnecessaryBeforeUnloadCheckSync \
    --disable-hang-monitor \
    --disable-ipc-flooding-protection \
    --disable-popup-blocking \
    --disable-prompt-on-repost \
    --disable-renderer-backgrounding \
    --disable-sync \
    --force-color-profile=srgb \
    --metrics-recording-only \
    --no-first-run \
    --enable-automation \
    --password-store=basic \
    --use-mock-keychain \
    --enable-blink-features=IdleDetection \
    --export-tagged-pdf \
    --headless \
    --user-data-dir=#{profile} \
    --hide-scrollbars \
    --mute-audio \
    --disable-web-security \
    --remote-debugging-port=#{chrome_port} \
    #{ENV['CHROME_OPTS']}}
  chrome_pid = Process.spawn(chrome_server_cmd, in: OS.dev_null, out: OS.dev_null, err: OS.dev_null)
  Timeout.timeout(30) do
    loop do
      break if chrome_server_running?
      sleep 0.5
    end
  end
  yield
rescue Timeout::Error
  puts 'Failed to start chrome server'
  puts 'Make sure that you have it installed and that its version is > 59'
  exit(1)
ensure
  if OS.windows? && chrome_pid
    Process.kill('KILL', chrome_pid) unless system("taskkill /f /t /pid #{chrome_pid} >NUL 2>NUL")
  elsif chrome_pid
    Process.kill('HUP', chrome_pid)
  end
  FileUtils.rm_rf(profile) if profile
end

def with_chrome_server

def with_chrome_server
  if chrome_server_running?
    yield
  else
    run_chrome_server { yield }
  end
end