# frozen_string_literal: true

module Rimless
  # The top-level Apache Avro helpers.
  module AvroHelpers
    extend ActiveSupport::Concern

    class_methods do
      # A top-level avro instance
      mattr_accessor :avro
      # A shared AvroUtils instance
      mattr_accessor :avro_utils

      # A shortcut to encode data using the specified schema to the Apache Avro
      # format. This also applies data sanitation to avoid issues with the low
      # level Apache Avro library (symbolized keys, etc) and it allows
      # deep-relative schema names. When you pass +.deep.deep+ for example
      # (leading period) it will prefix the schema name with the local
      # namespace (so it becomes absolute).
      #
      # @param data [Mixed] the data structure to encode
      # @param schema [String, Symbol] name of the schema that should be used
      # @param opts [Hash{Symbol => Mixed}] additional options
      # @return [String] the Apache Avro blob
      def avro_encode(data, schema:, **opts)
        data = avro_sanitize(data)

        # When the deep-relative form (+.deep.deep[..]+) is present, we add our
        # local namespace, so Avro can resolve it
        schema = avro_utils.namespace + schema.to_s \
          if schema.to_s.start_with? '.'

        avro.encode(data, schema_name: schema.to_s, **opts)
      end
      alias_method :encode, :avro_encode

      # A shortcut to parse a blob of Apache Avro data.
      #
      # @param data [String] the Apache Avro blob
      # @param opts [Hash{Symbol => Mixed}] additional options
      # @return [Mixed] the decoded data structure
      def avro_decode(data, **opts)
        avro.decode(data, **opts).deep_symbolize_keys!
      end
      alias_method :decode, :avro_decode

      # The Apache Avro Ruby gem requires simple typed hashes for encoding.
      # This forces us to convert eg. Grape entity representations into simple
      # string-keyed hashes. Use this method to prepare a hash for the Apache
      # Avro serialization.
      #
      # Note about the implementation: JSON serialization and parsing is the
      # simplest and fastest way to accomplish this.
      #
      # @param hash [Hash{Mixed => Mixed}] the hash to sanitize
      # @return [Hash{String => Mixed}] the simple typed input hash
      def avro_to_h(hash)
        JSON.parse(hash.to_json)
      end
      alias_method :avro_sanitize, :avro_to_h

      # Convert the given deep hash into a sparsed flat hash while transforming
      # all values to strings. This allows to convert a schema-less hash to a
      # Apache Avro compatible map.
      #
      # @see http://avro.apache.org/docs/current/spec.html#Maps
      # @example Convert schema-less hash
      #   avro_schemaless_map(a: { b: { c: true } })
      #   # => { "a.b.c" => "true" }
      #
      # @param hash [Hash{Mixed => Mixed}] the deep hash
      # @return [Hash{String => String}] the flatted and sparsed hash
      def avro_schemaless_h(hash)
        Sparsify(hash, sparse_array: true)
          .transform_values(&:to_s)
          .transform_keys { |key| key.delete('\\') }
      end
      alias_method :avro_schemaless_map, :avro_schemaless_h
    end
  end
end