# frozen_string_literal: true

require 'opal/rewriters/base'

module Opal
  module Rewriters
    # This module attempts to run some optimizations or compatibility
    # improvements against some libraries used with Opal.
    #
    # This should be a last resort and must not break functionality in
    # existing applications.
    class TargetedPatches < Base
      def on_def(node)
        name, args, body = *node

        if body && body.type == :begin && body.children.length >= 2
          # parser/rubyxx.rb - racc generated code often looks like:
          #
          #     def _reduce_219(val, _values, result)
          #       result = @builder.op_assign(val[0], val[1], val[2])
          #       result
          #     end
          #
          # This converter transform this into just
          #
          #     def _reduce_219(val, _values, result)
          #       @builder.op_assign(val[0], val[1], val[2])
          #     end

          calls = body.children
          assignment, ret = calls.last(2)
          if assignment.type == :lvasgn && ret.type == :lvar &&
             assignment.children.first == ret.children.first

            if calls.length == 2
              node.updated(nil, [name, args, assignment.children[1]])
            else
              calls = calls[0..-3] << assignment.children[1]
              node.updated(nil, [name, args, body.updated(nil, calls)])
            end
          else
            super
          end
        else
          super
        end
      end

      def on_array(node)
        children = node.children

        # Optimize large arrays produced by lexer, but mainly we are interested
        # in improving compile times, by reducing the tree for the further
        # compilation efforts (also reducing the bundle size a bit)
        #
        # This particular patch reduces compile time of the following command
        # by 12.5%:
        #
        #     OPAL_CACHE_DISABLE=true OPAL_PREFORK_DISABLE=true bin/opal \
        #         --no-source-map -ropal-parser -ropal/platform -ce \
        #         'puts ::Opal.compile($stdin.read)' > _Cnow.js
        #
        # So, in short, an array of a kind:
        #
        #     [1, 2, 3, nil, nil, :something, :abc, nil, ...]
        #
        # Becomes compiled to:
        #
        #     Opal.large_array_unpack("1,2,3,,something,abc,,...")

        if children.length > 32
          ssin_array = children.all? do |child|
            # Break for wrong types
            next false unless %i[str sym int nil].include?(child.type)
            # Break for strings that may conflict with our numbers, nils and separator
            next false if %i[str sym].include?(child.type) && child.children.first.to_s =~ /\A[0-9-]|\A\z|,/
            # Break for too numbers out of range, as there may be decoding issues
            next false if child.type == :int && !(-1_000_000..1_000_000).cover?(child.children.first)
            true
          end

          if ssin_array
            str = children.map { |i| i.children.first.to_s }.join(',')
            node.updated(:jscall, [s(:js_tmp, :Opal), :large_array_unpack, s(:sym, str)])
          else
            super
          end
        else
          super
        end
      end
    end
  end
end
