# frozen_string_literal: true

require "cases/helper"

module ActiveRecord
  class AdapterSpecificRegistryTest < ActiveRecord::TestCase
    test "a class can be registered for a symbol" do
      registry = Type::AdapterSpecificRegistry.new
      registry.register(:foo, ::String)
      registry.register(:bar, ::Array)

      assert_equal "", registry.lookup(:foo)
      assert_equal [], registry.lookup(:bar)
    end

    test "a block can be registered" do
      registry = Type::AdapterSpecificRegistry.new
      registry.register(:foo) do |*args|
        [*args, "block for foo"]
      end
      registry.register(:bar) do |*args|
        [*args, "block for bar"]
      end

      assert_equal [:foo, 1, "block for foo"], registry.lookup(:foo, 1)
      assert_equal [:foo, 2, "block for foo"], registry.lookup(:foo, 2)
      assert_equal [:bar, 1, 2, 3, "block for bar"], registry.lookup(:bar, 1, 2, 3)
    end

    test "filtering by adapter" do
      registry = Type::AdapterSpecificRegistry.new
      registry.register(:foo, String, adapter: :sqlite3)
      registry.register(:foo, Array, adapter: :postgresql)

      assert_equal "", registry.lookup(:foo, adapter: :sqlite3)
      assert_equal [], registry.lookup(:foo, adapter: :postgresql)
    end

    test "an error is raised if both a generic and adapter specific type match" do
      registry = Type::AdapterSpecificRegistry.new
      registry.register(:foo, String)
      registry.register(:foo, Array, adapter: :postgresql)

      assert_raises TypeConflictError do
        registry.lookup(:foo, adapter: :postgresql)
      end
      assert_equal "", registry.lookup(:foo, adapter: :sqlite3)
    end

    test "a generic type can explicitly override an adapter specific type" do
      registry = Type::AdapterSpecificRegistry.new
      registry.register(:foo, String, override: true)
      registry.register(:foo, Array, adapter: :postgresql)

      assert_equal "", registry.lookup(:foo, adapter: :postgresql)
      assert_equal "", registry.lookup(:foo, adapter: :sqlite3)
    end

    test "a generic type can explicitly allow an adapter type to be used instead" do
      registry = Type::AdapterSpecificRegistry.new
      registry.register(:foo, String, override: false)
      registry.register(:foo, Array, adapter: :postgresql)

      assert_equal [], registry.lookup(:foo, adapter: :postgresql)
      assert_equal "", registry.lookup(:foo, adapter: :sqlite3)
    end

    test "a reasonable error is given when no type is found" do
      registry = Type::AdapterSpecificRegistry.new

      e = assert_raises(ArgumentError) do
        registry.lookup(:foo)
      end

      assert_equal "Unknown type :foo", e.message
    end

    test "construct args are passed to the type" do
      registry = Type::AdapterSpecificRegistry.new
      registry.register(:foo, type)

      assert_equal type.new, registry.lookup(:foo)
      assert_equal type.new(:ordered_arg), registry.lookup(:foo, :ordered_arg)
      assert_equal type.new(keyword: :arg), registry.lookup(:foo, keyword: :arg)
      assert_equal type.new(keyword: :arg), registry.lookup(:foo, keyword: :arg, adapter: :postgresql)
    end

    test "registering a modifier" do
      decoration = Struct.new(:value)
      registry = Type::AdapterSpecificRegistry.new
      registry.register(:foo, String)
      registry.register(:bar, Hash)
      registry.add_modifier({ array: true }, decoration)

      assert_equal decoration.new(""), registry.lookup(:foo, array: true)
      assert_equal decoration.new({}), registry.lookup(:bar, array: true)
      assert_equal "", registry.lookup(:foo)
    end

    test "registering multiple modifiers" do
      decoration = Struct.new(:value)
      other_decoration = Struct.new(:value)
      registry = Type::AdapterSpecificRegistry.new
      registry.register(:foo, String)
      registry.add_modifier({ array: true }, decoration)
      registry.add_modifier({ range: true }, other_decoration)

      assert_equal "", registry.lookup(:foo)
      assert_equal decoration.new(""), registry.lookup(:foo, array: true)
      assert_equal other_decoration.new(""), registry.lookup(:foo, range: true)
      assert_equal(
        decoration.new(other_decoration.new("")),
        registry.lookup(:foo, array: true, range: true)
      )
    end

    test "registering adapter specific modifiers" do
      decoration = Struct.new(:value)
      registry = Type::AdapterSpecificRegistry.new
      registry.register(:foo, type)
      registry.add_modifier({ array: true }, decoration, adapter: :postgresql)

      assert_equal(
        decoration.new(type.new(keyword: :arg)),
        registry.lookup(:foo, array: true, adapter: :postgresql, keyword: :arg)
      )
      assert_equal(
        type.new(array: true),
        registry.lookup(:foo, array: true, adapter: :sqlite3)
      )
    end

    TYPE = Class.new do
      attr_reader :args

      def initialize(args = nil)
        @args = args
      end

      def ==(other) self.args == other.args end
    end

    private def type; TYPE end
  end
end
