# frozen_string_literal: true

require "ostruct"

RSpec.describe Dry::Transformer do
  let(:container) { Module.new { extend Dry::Transformer::Registry } }
  let(:klass) { Dry::Transformer[container] }
  let(:transformer) { klass.new }

  describe ".import" do
    it "allows importing functions into an auto-configured registry" do
      klass = Class.new(Dry::Transformer::Pipe) do
        import Dry::Transformer::ArrayTransformations
        import Dry::Transformer::Coercions

        define! do
          map_array(&:to_symbol)
        end
      end

      transformer = klass.new

      expect(transformer.(%w[foo bar])).to eql([:foo, :bar])
    end
  end

  describe ".new" do
    it "supports arguments" do
      klass = Class.new(Dry::Transformer::Pipe) do
        import Dry::Transformer::ArrayTransformations
        import Dry::Transformer::Coercions

        define! do
          map_array(&:to_symbol)
        end

        def initialize(good)
          super()
          @good = good
        end

        def good?
          @good
        end
      end

      transformer = klass.new(true)

      expect(transformer).to be_good
    end
  end

  describe ".container" do
    it "returns the configured container" do
      expect(klass.container).to be(container)
    end

    context "with setter argument" do
      let(:container) { double(:custom_container) }

      it "sets and returns the container" do
        klass.container(container)

        expect(klass.container).to be(container)
      end
    end
  end

  describe "inheritance" do
    let(:container) do
      Module.new do
        extend Dry::Transformer::Registry

        def self.arbitrary(value, fn)
          fn[value]
        end
      end
    end

    let(:superclass) do
      Class.new(Dry::Transformer[container]) do
        define! do
          arbitrary ->(v) { v + 1 }
        end
      end
    end

    let(:subclass) do
      Class.new(superclass) do
        define! do
          arbitrary ->(v) { v * 2 }
        end
      end
    end

    it "inherits container from superclass" do
      expect(subclass.container).to be(superclass.container)
    end

    it "inherits transproc from superclass" do
      expect(superclass.new.call(2)).to be(3)
      expect(subclass.new.call(2)).to be(6)
    end
  end

  describe ".[]" do
    subject(:subclass) { klass[another_container] }

    let(:another_container) { double("Dry::Transformer") }

    it "sets a container" do
      expect(subclass.container).to be(another_container)
    end

    it "returns a class" do
      expect(subclass).to be_a(Class)
    end

    it "creates a subclass of Transformer" do
      expect(subclass).to be < Dry::Transformer::Pipe
    end

    it "does not change super class" do
      expect(klass.container).to be(container)
    end

    it "does not inherit transproc" do
      expect(klass[container].new.transproc).to be_nil
    end

    context "with predefined transformer" do
      let(:klass) do
        Class.new(Dry::Transformer[container]) do
          container.import Dry::Transformer::Coercions
          container.import Dry::Transformer::HashTransformations

          define! do
            map_value :attr, t(:to_symbol)
          end
        end
      end

      it "inherits parent's transproc" do
        expect(klass[container].new.transproc).to eql(klass.new.transproc)
      end
    end
  end

  describe ".define!" do
    let(:container) do
      Module.new do
        extend Dry::Transformer::Registry

        import Dry::Transformer::HashTransformations

        def self.to_symbol(v)
          v.to_sym
        end
      end
    end

    let(:klass) { Dry::Transformer[container] }

    it "defines anonymous transproc" do
      transproc = klass.define! do
        map_value(:attr, t(:to_symbol))
      end

      expect(transproc.new.transproc[attr: "abc"]).to eq(attr: :abc)
    end

    it "does not affect original transformer" do
      Class.new(klass).define! do
        map_value(:attr, :to_sym.to_proc)
      end

      expect(klass.new.transproc).to be_nil
    end

    context "with custom container" do
      let(:container) do
        Module.new do
          extend Dry::Transformer::Registry

          def self.arbitrary(value, fn)
            fn[value]
          end
        end
      end

      let(:klass) { described_class[container] }

      it "uses a container from the transformer" do
        transproc = klass.define! do
          arbitrary ->(v) { v + 1 }
        end.new

        expect(transproc.call(2)).to eq 3
      end
    end

    context "with predefined transformer" do
      let(:klass) do
        Class.new(described_class[container]) do
          define! do
            map_value :attr, ->(v) { v + 1 }
          end
        end
      end

      it "builds transformation from the DSL definition" do
        transproc = klass.new

        expect(transproc.call(attr: 2)).to eql(attr: 3)
      end
    end
  end

  describe ".t" do
    subject(:klass) { Dry::Transformer[container] }

    let(:container) do
      Module.new do
        extend Dry::Transformer::Registry

        import Dry::Transformer::HashTransformations
        import Dry::Transformer::Conditional

        def self.custom(value, suffix)
          value + suffix
        end
      end
    end

    it "returns a registed function" do
      expect(klass.t(:custom, "_bar")).to eql(container[:custom, "_bar"])
    end

    it "is useful in DSL" do
      transproc = Class.new(klass).define! do
        map_value :a, t(:custom, "_bar")
      end.new

      expect(transproc.call(a: "foo")).to eq(a: "foo_bar")
    end

    it "works in nested block" do
      transproc = Class.new(klass).define! do
        map_values do
          is String, t(:custom, "_bar")
        end
      end.new

      expect(transproc.call(a: "foo", b: :symbol)).to eq(a: "foo_bar", b: :symbol)
    end
  end

  describe "#call" do
    let(:container) do
      Module.new do
        extend Dry::Transformer::Registry

        import Dry::Transformer::HashTransformations
        import Dry::Transformer::ArrayTransformations
        import Dry::Transformer::ClassTransformations
      end
    end

    let(:klass) do
      Class.new(Dry::Transformer[container]) do
        define! do
          map_array do
            symbolize_keys
            rename_keys user_name: :name
            nest :address, [:city, :street, :zipcode]

            map_value :address do
              constructor_inject Test::Address
            end

            constructor_inject Test::User
          end
        end
      end
    end

    let(:input) do
      [
        {"user_name" => "Jane",
         "city" => "NYC",
         "street" => "Street 1",
         "zipcode" => "123"}
      ]
    end

    let(:expected_output) do
      [
        Test::User.new(
          name: "Jane",
          address: Test::Address.new(
            city: "NYC",
            street: "Street 1",
            zipcode: "123"
          )
        )
      ]
    end

    before do
      module Test
        class User < OpenStruct
          include Dry::Equalizer(:name, :address)
        end

        class Address < OpenStruct
          include Dry::Equalizer(:city, :street, :zipcode)
        end
      end
    end

    it "transforms input" do
      expect(transformer.(input)).to eql(expected_output)
    end

    context "with custom registry" do
      let(:klass) do
        Class.new(Dry::Transformer[registry]) do
          define! do
            append " is awesome"
          end
        end
      end

      let(:registry) do
        Module.new do
          extend Dry::Transformer::Registry

          def self.append(value, suffix)
            value + suffix
          end
        end
      end

      it "uses custom functions" do
        expect(transformer.("transproc")).to eql("transproc is awesome")
      end
    end
  end
end
