require "json"
require "net/http"
require "uri"

$__codemode_binding = TOPLEVEL_BINDING
$__codemode_frame_mutex = Mutex.new
$__codemode_current_cell = nil
$__codemode_capture_cell = nil
$__codemode_connection = nil
$__codemode_all_tools = [].freeze
ALL_TOOLS = $__codemode_all_tools
$__codemode_strings = nil
$__codemode_frame_io = STDOUT.dup
$__codemode_frame_io.sync = true

begin
  stdout_read, stdout_write = IO.pipe
  STDOUT.reopen(stdout_write)
  STDOUT.sync = true
  stdout_write.close
  $__codemode_stdout_capture = stdout_read
rescue StandardError
  $__codemode_stdout_capture = nil
end

begin
  stderr_read, stderr_write = IO.pipe
  STDERR.reopen(stderr_write)
  STDERR.sync = true
  stderr_write.close
  $__codemode_stderr_capture = stderr_read
rescue StandardError
  $__codemode_stderr_capture = nil
end

begin
  $__codemode_protocol_stdin = STDIN.dup
  STDIN.reopen(File.open(File::NULL, "r"))
rescue StandardError
  $__codemode_protocol_stdin = STDIN
end

def __codemode_emit(frame)
  line = JSON.generate(frame)
  $__codemode_frame_mutex.synchronize do
    $__codemode_frame_io.write(line)
    $__codemode_frame_io.write("\n")
    $__codemode_frame_io.flush
  end
rescue StandardError
  nil
end

def __codemode_error(error)
  { "name" => error.class.name, "message" => error.message.to_s, "stack" => error.backtrace&.join("\n") }
end

def __codemode_emit_stream(stream, data)
  return if data.nil? || data.empty? || $__codemode_capture_cell.nil?
  __codemode_emit({ "type" => "text", "stream" => stream, "data" => data })
end

class CodemodeStreamProxy
  def initialize(stream)
    @stream = stream
  end

  def write(*values)
    data = values.join
    __codemode_emit_stream(@stream, data)
    data.bytesize
  end

  def print(*values)
    write(*values)
    nil
  end

  def puts(*values)
    values = [""] if values.empty?
    values.each { |value| write(value.to_s.end_with?("\n") ? value.to_s : "#{value}\n") }
    nil
  end

  def printf(format, *values)
    write(Kernel.format(format, *values))
    nil
  end

  def flush
    self
  end

  def sync
    true
  end

  def sync=(_value)
    true
  end

  def tty?
    false
  end
end

def __codemode_start_capture(io, stream)
  return if io.nil?
  Thread.new do
    loop do
      data = io.readpartial(65_536)
      __codemode_emit_stream(stream, data)
    rescue EOFError, IOError, Errno::EBADF
      break
    end
  end
end

def __codemode_value_repr(value)
  JSON.generate(value)
rescue JSON::GeneratorError
  value.inspect
end

CODEMODE_NON_DISPLAY_NODES = %i[
  LASGN IASGN GASGN CVASGN DASGN OP_ASGN OP_CDECL CDECL MASGN CASGN
  DEFN DEFS CLASS MODULE SCLASS ALIAS UNDEF
].freeze

def __codemode_ast_last(node)
  return nil unless node.is_a?(RubyVM::AbstractSyntaxTree::Node)
  case node.type
  when :SCOPE
    __codemode_ast_last(node.children[2])
  when :BLOCK
    children = node.children.compact
    children.empty? ? nil : __codemode_ast_last(children.last)
  else
    node
  end
end

def __codemode_should_display_result?(source)
  return true unless defined?(RubyVM::AbstractSyntaxTree)
  node = RubyVM::AbstractSyntaxTree.parse(source)
  last = __codemode_ast_last(node)
  return true if last.nil?
  !CODEMODE_NON_DISPLAY_NODES.include?(last.type)
rescue StandardError, SyntaxError
  true
end

require_relative "prelude"

$stdout = CodemodeStreamProxy.new("stdout")
$stderr = CodemodeStreamProxy.new("stderr")
__codemode_start_capture($__codemode_stdout_capture, "stdout")
__codemode_start_capture($__codemode_stderr_capture, "stderr")

def __codemode_run_cell(message)
  started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  cell_id = message["cellId"].to_s
  $__codemode_current_cell = cell_id
  $__codemode_capture_cell = cell_id
  $__codemode_strings = message["strings"].is_a?(Hash) ? message["strings"] : {}
  begin
    source = message["code"].to_s
    value = eval(source, $__codemode_binding, "(pi-codemode-rb)")
    STDOUT.flush
    STDERR.flush
    Thread.pass
    frame = {
      "type" => "result",
      "cellId" => cell_id,
      "ok" => true,
      "durationMs" => ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started) * 1000).round,
    }
    frame["valueRepr"] = __codemode_value_repr(value) if !value.nil? && __codemode_should_display_result?(source)
    __codemode_emit(frame)
  rescue Exception => error
    __codemode_emit({
      "type" => "result",
      "cellId" => cell_id,
      "ok" => false,
      "error" => __codemode_error(error),
      "durationMs" => ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started) * 1000).round,
    })
  ensure
    $__codemode_capture_cell = nil
    $__codemode_current_cell = nil
    $__codemode_strings = nil
  end
end

$__codemode_protocol_stdin.each_line do |line|
  message = JSON.parse(line)
  case message["type"]
  when "init"
    $__codemode_connection = message["connection"]
    tools = $__codemode_connection.is_a?(Hash) ? $__codemode_connection["tools"] : nil
    $__codemode_all_tools = (tools.is_a?(Array) ? tools : []).map { |item| { "name" => item["name"].to_s, "description" => item["description"] }.freeze }.freeze
    Object.send(:remove_const, :ALL_TOOLS)
    Object.const_set(:ALL_TOOLS, $__codemode_all_tools)
    __codemode_emit({ "type" => "ready" })
  when "run"
    __codemode_run_cell(message)
  when "close"
    __codemode_emit({ "type" => "closed" })
    exit 0
  end
rescue JSON::ParserError => error
  __codemode_emit({ "type" => "init-failed", "error" => __codemode_error(error) })
end
