# frozen_string_literal: true

module Scim
  module Kit
    module V2
      # Represents a scim Attribute type
      class AttributeType
        include Templatable
        attr_accessor :canonical_values, :case_exact, :description
        attr_accessor :multi_valued, :required
        attr_reader :mutability, :name, :type, :attributes
        attr_reader :reference_types, :returned, :uniqueness

        def initialize(name:, type: :string)
          @name = name.to_s.underscore
          @type = DATATYPES[type.to_sym] ? type.to_sym : (raise TYPE_ERROR)
          @description = name.to_s.camelize(:lower)
          @multi_valued = false
          @required = false
          @case_exact = false
          @mutability = Mutability::READ_WRITE
          @returned = Returned::DEFAULT
          @uniqueness = Uniqueness::NONE
          @attributes = []
        end

        def mutability=(value)
          @mutability = Mutability.find(value)
        end

        def returned=(value)
          @returned = Returned.find(value)
        end

        def uniqueness=(value)
          @uniqueness = Uniqueness.find(value)
        end

        def add_attribute(name:, type: :string)
          attribute = AttributeType.new(name: name, type: type)
          yield attribute if block_given?
          @type = :complex
          attributes << attribute
        end

        def reference_types=(value)
          @type = :reference
          @reference_types = value
        end

        def complex?
          type_is?(:complex)
        end

        def coerce(value)
          return value if value.nil?
          return value if complex?

          if multi_valued
            return value unless value.respond_to?(:to_a)

            value.to_a.map { |x| coerce_single(x) }
          else
            coerce_single(value)
          end
        end

        def valid?(value)
          if multi_valued
            return false unless value.respond_to?(:to_a)

            return value.to_a.all? { |x| validate(x) }
          end

          complex? ? valid_complex?(value) : valid_simple?(value)
        end

        class << self
          def from(hash)
            x = new(name: hash[:name], type: hash[:type])
            %i[
              canonicalValues caseExact description multiValued mutability
              referenceTypes required returned uniqueness
            ].each do |y|
              x.public_send("#{y.to_s.underscore}=", hash[y]) if hash.key?(y)
            end
            x
          end
        end

        private

        def coerce_single(value)
          COERCION.fetch(type, ->(x) { x }).call(value)
        rescue StandardError => error
          Scim::Kit.logger.error(error)
          value
        end

        def validate(value)
          complex? ? valid_complex?(value) : valid_simple?(value)
        end

        def valid_simple?(value)
          VALIDATIONS[type]&.call(value)
        end

        def valid_complex?(item)
          return false unless item.is_a?(Hash)

          item.keys.each do |key|
            return false unless type_for(key)&.valid?(item[key])
          end
        end

        def type_for(name)
          name = name.to_s.underscore
          attributes.find { |x| x.name.to_s.underscore == name }
        end

        def string?
          type_is?(:string)
        end

        def reference?
          type_is?(:reference)
        end

        def type_is?(expected_type)
          type.to_sym == expected_type
        end
      end
    end
  end
end
