require "gql_serializer/version"
require "gql_serializer/extensions"
require "gql_serializer/configuration"


module GqlSerializer

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

  def self.configure
    yield configuration
  end


  def self.parse_query(input)
    query = input.dup
    query.strip!
    query.gsub!(/[\r\n\t ]+/, ' ')
    query.gsub!(/\{ /, '{')
    query.gsub!(/ }/, '}')

    result, _ = self.parse_it(query)
    result
  end

  def self.query_include(model, hasharray)
    include_array = []
    relations = model.reflections.keys
    hasharray.each do |e|
      if e.is_a? String
        key = e.split(':')[0]
        include_array.push(key) if relations.include?(key)
      elsif e.is_a? Hash
        key = e.keys.first.split(':')[0]
        relation_model = model.reflections[key].klass
        relation_hasharray = self.query_include(relation_model, e.values.first)
        if relation_hasharray.empty?
          include_array.push(key)
        else
          include_array.push({key => relation_hasharray})
        end
      end
    end
    include_array
  end

  def self.serialize(record, hasharray, options)

    if record.nil?
      return nil
    end

    if record.respond_to? :map
      return record.map do |r|
        self.serialize(r, hasharray, options)
      end
    end

    hash = {}
    model = record.class
    all_relations = model.reflections.keys

    relations = hasharray.filter do |e|
      next true if !e.is_a?(String)

      key, alias_key = e.split(':')
      all_relations.include?(key)
    end

    attributes = hasharray - relations
    attributes = model.attribute_names if attributes.empty?

    attributes.each do |e|
      key, alias_key = e.split(':')
      alias_key = apply_case(alias_key || key, options[:case])

      hash[alias_key] = coerce_value(record.public_send(key))
    end

    relations.each do |e|
      if e.is_a? String
        key, alias_key = e.split(':')
        alias_key = apply_case(alias_key || key, options[:case])

        rel_records = record.public_send(key)
        hash[alias_key] = self.serialize(rel_records, [], options)
      else
        key, alias_key = e.keys.first.split(':')
        alias_key = apply_case(alias_key || key, options[:case])

        rel_records = record.public_send(key)
        hash[alias_key] = self.serialize(rel_records, e.values.first, options)
      end
    end

    hash
  end

  def self.coerce_value(value)
    return value.to_f if value.is_a? BigDecimal
    return value.new_offset(0).strftime("%FT%TZ") if value.is_a? DateTime
    return value.utc.iso8601 if value.is_a? Time
    value
  end


  private

  def self.apply_case(key, key_case)
    case key_case
    when Configuration::CAMEL_CASE
      result = key.camelize
      result[0] = result[0].downcase
    when Configuration::SNAKE_CASE
      result = key.underscore
    else
      result = key
    end

    result
  end

  def self.parse_it(query)
    result = []
    while query&.length&.> 0
      if query[0] == ' '
        query.strip!
        next
      elsif query[0] == '}'
        return result, query[1..-1]
      end

      next_key = query[/[_a-zA-Z0-9:]+/]
      query = query[next_key.length..-1]
      query.strip!

      if query.nil? || query.empty? || query[0].match?(/[_a-zA-Z0-9:]/)
        result.push(next_key)

      elsif query&.[](0) == '{'
        query = query[1..-1]
        obj, query = parse_it(query)
        result.push(next_key => obj)

      elsif query[0] == '}'
        result.push(next_key)
        return result, query[1..-1]

      else
        raise "unsupported character '#{query[0]}'"

      end
    end
    return result, nil
  end


end