require 'json'
require 'forwardable'
require 'active_support/core_ext/string'
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/hash/conversions' # to_xml
require 'active_support/concern'
require 'active_support/time'

require 'dry-types'
require 'graphiti_errors'

require 'jsonapi/serializable'

require "graphiti/version"
require "graphiti/jsonapi_serializable_ext"
require "graphiti/configuration"
require "graphiti/context"
require "graphiti/errors"
require "graphiti/types"
require "graphiti/schema"
require "graphiti/schema_diff"
require "graphiti/adapters/abstract"
require "graphiti/resource/sideloading"
require "graphiti/resource/links"
require "graphiti/resource/configuration"
require "graphiti/resource/dsl"
require "graphiti/resource/interface"
require "graphiti/resource/polymorphism"
require "graphiti/resource/documentation"
require "graphiti/resource/persistence"
require "graphiti/resource/remote"
require "graphiti/sideload"
require "graphiti/sideload/has_many"
require "graphiti/sideload/belongs_to"
require "graphiti/sideload/has_one"
require "graphiti/sideload/many_to_many"
require "graphiti/sideload/polymorphic_belongs_to"
require "graphiti/resource"
require "graphiti/resource_proxy"
require "graphiti/query"
require "graphiti/scope"
require "graphiti/deserializer"
require "graphiti/renderer"
require "graphiti/hash_renderer"
require "graphiti/filter_operators"
require "graphiti/scoping/base"
require "graphiti/scoping/sort"
require "graphiti/scoping/paginate"
require "graphiti/scoping/extra_attributes"
require "graphiti/scoping/filterable"
require "graphiti/scoping/default_filter"
require "graphiti/scoping/filter"
require "graphiti/stats/dsl"
require "graphiti/stats/payload"
require "graphiti/delegates/pagination"
require "graphiti/util/include_params"
require "graphiti/util/field_params"
require "graphiti/util/hash"
require "graphiti/util/relationship_payload"
require "graphiti/util/persistence"
require "graphiti/util/validation_response"
require "graphiti/util/sideload"
require "graphiti/util/hooks"
require "graphiti/util/attribute_check"
require "graphiti/util/serializer_attributes"
require "graphiti/util/serializer_relationships"
require "graphiti/util/class"
require "graphiti/util/link"
require "graphiti/util/remote_serializer"
require "graphiti/util/remote_params"
require 'graphiti/adapters/null'
require 'graphiti/adapters/graphiti_api'
require "graphiti/extensions/extra_attribute"
require "graphiti/extensions/boolean_attribute"
require "graphiti/extensions/temp_id"
require "graphiti/serializer"
require "graphiti/debugger"

if defined?(ActiveRecord)
  require 'graphiti/adapters/active_record'
end

if defined?(Rails)
  require 'graphiti/railtie'
  require 'graphiti/rails'
  require 'graphiti/responders'
end

module Graphiti
  autoload :Base, 'graphiti/base'

  def self.included(klass)
    klass.instance_eval do
      include Base
    end
  end

  # @api private
  def self.context
    Thread.current[:context] ||= {}
  end

  # @api private
  def self.context=(val)
    Thread.current[:context] = val
  end

  # @api private
  def self.with_context(obj, namespace = nil)
    begin
      prior = self.context
      self.context = { object: obj, namespace: namespace }
      yield
    ensure
      self.context = prior
    end
  end

  def self.config
    @config ||= Configuration.new
  end

  # @example
  #   Graphiti.configure do |c|
  #     c.raise_on_missing_sideload = false
  #   end
  #
  # @see Configuration
  def self.configure
    yield config
  end

  def self.resources
    @resources ||= []
  end

  def self.broadcast(name, payload)
    name = "graphiti.#{name}"
    ActiveSupport::Notifications.instrument(name, payload) do
      yield payload if block_given?
    end
  end

  def self.logger
    @logger ||= stdout_logger
  end

  def self.stdout_logger
    logger = Logger.new($stdout)
    logger.formatter = proc do |severity, datetime, progname, msg|
      "#{msg}\n"
    end
    logger
  end

  def self.logger=(val)
    @logger = val
  end

  def self.log(msg, color = :white, bold = false)
    colored = ActiveSupport::LogSubscriber.new.send(:color, msg, color, bold)
    logger.debug(colored)
  end

  # When we add a sideload, we need to do configuration, such as
  # adding the relationship to the Resource's serializer.
  # However, the sideload's Resource class may not be loaded yet.
  #
  # This is not a problem when Rails autoloading, but is a problem
  # when *eager* loading, or not using Rails.
  #
  # So, load every Resource class then call Graphiti.setup!
  def self.setup!
    resources.each do |r|
      r.apply_sideloads_to_serializer
    end
  end
end

require "graphiti/runner"

# Because we set 2 magic variables when processing the graph,
# as_json will fail on a PORO with stack level too deep
#
# #as_json calls #instance_variables, defined in
# active_support/core_ext/object/instance_variables.rb
#
# So, override that to not see these magic vars.
module InstanceVariableOverride
  def instance_values
    values = super
    if @__graphiti_serializer
      values.reject! do |v|
        ['__graphiti_serializer', '__graphiti_resource'].include?(v)
      end
    end
    values
  end
end
class Object
  prepend InstanceVariableOverride
end

ActiveSupport.run_load_hooks(:graphiti, Graphiti)