# frozen_string_literal: true

require 'oj'
require 'set'
require_relative 'attribute'
require_relative 'inflector'
require_relative 'entity/base'

module BrightSerializer
  module Serializer
    SUPPORTED_TRANSFORMATION = %i[camel camel_lower dash underscore].freeze
    DEFAULT_OJ_OPTIONS = { mode: :compat, time_format: :ruby, use_to_json: true }.freeze

    def self.included(base)
      base.extend ClassMethods
      base.instance_variable_set(:@attributes_to_serialize, [])
    end

    def initialize(object, **options)
      @object = object
      @params = options.delete(:params)
      @fields = Set.new(options.delete(:fields))
    end

    def serialize(object)
      self.class.attributes_to_serialize.each_with_object({}) do |attribute, result|
        next if @fields.any? && !@fields.include?(attribute.key)
        next unless attribute.condition?(object, @params)

        result[attribute.transformed_key] = attribute.serialize(object, @params)
      end
    end

    def serializable_hash
      if @object.respond_to?(:each) && !@object.respond_to?(:each_pair)
        @object.map { |o| serialize o }
      else
        serialize(@object)
      end
    end

    alias to_hash serializable_hash

    def serializable_json(*_args)
      ::Oj.dump(to_hash, DEFAULT_OJ_OPTIONS)
    end

    alias to_json serializable_json

    module ClassMethods
      attr_reader :attributes_to_serialize, :transform_method

      def inherited(subclass)
        super
        subclass.instance_variable_set(:@attributes_to_serialize, []) unless subclass.attributes_to_serialize
        subclass.attributes_to_serialize.concat(@attributes_to_serialize)
        subclass.instance_variable_set(:@transform_method, @transform_method) unless subclass.transform_method
      end

      def attributes(*attributes, **options, &block)
        attributes.each do |key|
          attribute = Attribute.new(key, options[:if], options[:entity], &block)
          attribute.transformed_key = run_transform_key(key)
          @attributes_to_serialize << attribute
        end
      end

      alias attribute attributes

      def set_key_transform(transform_name) # rubocop:disable Naming/AccessorMethodName
        unless SUPPORTED_TRANSFORMATION.include?(transform_name)
          raise ArgumentError "Invalid transformation: #{SUPPORTED_TRANSFORMATION}"
        end

        @transform_method = transform_name
      end

      def run_transform_key(input)
        if transform_method
          Inflector.send(@transform_method, input.to_s).to_sym
        else
          input.to_sym
        end
      end

      def entity
        {}.tap do |result|
          @attributes_to_serialize.each do |attribute|
            entity_value = attribute.entity&.to_h || BrightSerializer::Entity::Base::DEFAULT_DEFINITION
            result.merge!(attribute.transformed_key => entity_value)
          end
        end
      end

      def entity_name
        name.split('::').last.downcase
      end
    end
  end
end