# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1

module VagrantPlugins
  module CommandServe
    class Mappers
      class ConfigDataFromSpec < Mapper
        def initialize
          super(
            inputs: [Input.new(type: SDK::FuncSpec::Value) { |arg|
                arg.type == "hashicorp.vagrant.sdk.Args.ConfigData" &&
                  !arg&.value&.value.nil?
              }
            ],
            output: SDK::Args::ConfigData,
            func: method(:converter),
          )
        end

        def converter(fv)
          SDK::Args::ConfigData.decode(fv.value.value)
        end
      end

      class ConfigMergeFromSpec < Mapper
        include Util::HasLogger

        def initialize
          super(
            inputs: [
              Input.new(type: SDK::FuncSpec::Value) { |arg|
                arg.type == "hashicorp.vagrant.sdk.Config.Merge" &&
                  !arg&.value&.value.nil?
              }
            ],
            output: SDK::Config::Merge,
            func: method(:converter),
          )
        end

        def converter(fv)
          SDK::Config::Merge.decode(fv.value.value)
        end
      end

      class ConfigFinalizeFromSpec < Mapper
        def initialize
          super(
            inputs: [
              Input.new(type: SDK::FuncSpec::Value) { |arg|
                arg.type == "hashicorp.vagrant.sdk.Config.Finalize" &&
                  !arg&.value&.value.nil?
              }
            ],
            output: SDK::Config::Finalize,
            func: method(:converter),
          )
        end

        def converter(fv)
          SDK::Config::Finalize.decode(fv.value.value)
        end
      end

      class ConfigFinalizeResponseProtoFromConfigDataProto < Mapper
        def initialize
          super(
            inputs: [
              Input.new(type: SDK::Args::ConfigData),
            ],
            output: SDK::Config::FinalizeResponse,
            func: method(:converter),
          )
        end

        def converter(c)
          SDK::Config::FinalizeResponse.new(data: c)
        end
      end

      class ConfigFromConfigDataProto < Mapper
        include Util::HasLogger

        def initialize
          super(
            inputs: [
              Input.new(type: SDK::Args::ConfigData),
            ],
            output: Vagrant::Plugin::V2::Config,
            func: method(:converter)
          )
        end

        def converter(c)
          c.to_ruby
        end
      end

      class RootConfigToHashProto < Mapper
        include Util::HasLogger

        def initialize
          super(
            inputs: [
              Input.new(type: Vagrant::Config::V2::Root),
            ],
            output: SDK::Args::Hash,
            func: method(:converter),
          )
        end

        def converter(c)
          c.to_proto
        end
      end

      class ConfigToProto < Mapper
        include Util::HasLogger

        def initialize
          super(
            inputs: [
              Input.new(type: Vagrant::Plugin::V2::Config),
              Input.new(type: Mappers),
            ],
            output: SDK::Args::ConfigData,
            func: method(:converter),
          )
        end

        def converter(c, m)
          data = Hash.new.tap do |h|
            c.instance_variables.each do |v|
              h[v.to_s.sub('@', '')] = c.instance_variable_get(v)
            end
          end

          # Include a unique identifier for this configuration instance. This
          # will allow us to identifier it later when it is decoded.
          if !data.key?("_vagrant_config_identifier")
            data["_vagrant_config_identifier"] = SecureRandom.uuid
          end

          entries = data.map do |k, v|
            begin
              SDK::Args::HashEntry.new(
                key: m.map(k, to: Google::Protobuf::Any),
                value: m.map(v, to: Google::Protobuf::Any),
              )
            rescue Internal::Graph::Search::NoPathError, TypeError
              logger.warn("failed to map '#{k}' value of type `#{v.class}'")
              nil
            end
          end.compact
          SDK::Args::ConfigData.new(
            data: SDK::Args::Hash.new(entries: entries),
            source: m.map(c.class, to: SDK::Args::Class),
          )
        end
      end

      class RawRubyValueToProto < Mapper
        include Util::HasLogger

        def initialize
          super(
            inputs: [
              Input.new(type: Object, origin_restricted: true),
              Input.new(type: Mappers),
            ],
            output: SDK::Config::RawRubyValue,
            func: method(:converter)
          )
        end

        def converter(o, m)
          klass = m.map(o.class, to: SDK::Args::Class)
          data = Hash.new.tap do |h|
            o.instance_variables.each do |v|
              h[v.to_s.sub('@', '')] = o.instance_variable_get(v)
            end
          end

          entries = data.map do |k, v|
            next if v.is_a?(Log4r::Logger)
            begin
              SDK::Args::HashEntry.new(
                key: m.map(k, to: Google::Protobuf::Any),
                value: m.map(v, to: Google::Protobuf::Any),
              )
            rescue Internal::Graph::Search::NoPathError, TypeError
              logger.warn("failed to map '#{k}' value of type `#{v.class}'")
              nil
            end
          end.compact
          SDK::Config::RawRubyValue.new(
            source: klass,
            data: SDK::Args::Hash.new(entries: entries)
          )
        end

        def extra_weight
          1_000_000_000_000
        end
      end

      class RawRubyValueFromProto < Mapper
        include Util::HasLogger

        def initialize
          super(
            inputs: [
              Input.new(type: SDK::Config::RawRubyValue, origin_restricted: true),
              Input.new(type: Mappers)
            ],
            output: Object,
            func: method(:converter),
          )
        end

        def converter(r, m)
          base_klass = m.map(r.source, to: Class)
          if [0, -1].include?(base_klass.instance_method(:initialize).arity)
            klass = base_klass
          else
            klass = Class.new(base_klass)
            klass.class_eval("
              def self.class
                #{base_klass.name}
              end
              def initialize
              end
            ")
          end
          instance = klass.new
          data = m.map(r.data, to: Hash)

          data.each_pair do |k, v|
            instance.instance_variable_set("@#{k}", v)
          end
          instance
        end
      end
    end
  end
end
