# rubocop:disable ParameterLists
#
# TrackedEvent handles data transformation and field enforcements for Spade events
#
module TrackedEvent
  #
  # Provides a Builder for TrackedEvent structs; Enforces defaults
  #
  # @param [String] ldap The staff member's LDAP username
  # @param [Float] client_time The UnixTime at event creation
  # @param [String] user_agent The client's user agent
  # @param [String] client_ip  The client's IP address
  # @param [String] page_url  The url the event fired from
  # @param [String] session_id  The UUID of the client's session ID
  #
  # @return [TrackedEvent::Scaffold] A filled tracked-event builder
  #
  # @example Usage
  #  TrackedEvent.create(
  #   ldap: 'fire',
  #   client_time: Time.now.to_f,
  #   user_agent: "some chrome thing",
  #   client_ip: "127.0.0.1",
  #   page_url: "admin-panel.internal.justin.tv/twitch/user/1234568",
  #   session_id: "b9acf2bc-6934-b3b6-df2d-d6b854e37824"
  #  )
  #
  def self.create(ldap:, client_time: Time.now.to_f, user_agent:, client_ip:, page_url:, session_id:)
    TrackedEvent::Scaffold.new(ldap, client_time, user_agent, client_ip, page_url, session_id)
  end

  #
  # converts snake_case to CamelCase
  #
  # @param [String] string snake_case input
  #
  # @return [String] CamelCase output
  #
  def self.snake_to_camel(string = "")
    string.split('_').map(&:capitalize).join
  end

  # A TrackedEvent Scaffold holds information required by every spade event,
  # and provides methods to create specific structures for any event we send
  # @!parse
  #   extend Class
  # @type [Class::Struct<ldap: String, client_time: Float, user_agent: String, ip: String, url: String, session_id: UUID(String)>]
  Scaffold = Struct.new(:ldap, :client_time, :user_agent, :ip, :url, :session_id)
  class Scaffold

    def assemble_an
      self
    end

    # @!macro [new] eventer
    #   Returns a **`Struct`** for the Event matching the function **name**
    #   with required fields, plus additional fields based on the function's parameters.
    #   @return (see TrackedEvent::Scaffold#build)

    #
    # @return (see TrackedEvent::Scaffold#build)
    #
    # @!macro eventer
    def admin_panel_page_view
      build(context: binding)
    end

    #
    # @param [Integer] total_time_seconds the total time the client has spent on the current page
    # @param [Boolean] tab_is_focused whether or not the tab is in focus at time of event
    # @macro eventer
    #
    # @return (see TrackedEvent::Scaffold#build)
    #
    def admin_panel_page_view_heartbeat(total_time_seconds:, tab_is_focused:)
      build(context: binding)
    end

    #
    # @param [String] button the name/action of the button being pressed
    # @param [String] user_id an optional user_id, if viewing a user profile
    # @param [String] purchase_profile_id an optional Purchase Profile ID, if viewing a purchase profile
    # @param [String] purchase_order_id an optional Purchase Order ID, if viewing a purchase order
    #
    # @return (see TrackedEvent::Scaffold#build)
    #
    # @macro eventer
    def admin_panel_button(button:, user_id: nil, purchase_profile_id: nil, purchase_order_id: nil)
      build(context: binding)
    end

    private

    #
    # uses the calling function's execution context to create event-specific structs
    # based on the Scaffold's required information and the arguments passed to the
    # originating function.
    #
    # @param [Binding] context the calling function's execution context, so we can get
    #   arguments for any calling function without writing builders for each.
    #
    # @return [Struct] an event-specific struct.
    #
    def build(context:)
      args = Hash[
        context.local_variables.map { |param| [param, context.local_variable_get(param)] }
      ].delete_if { |_, v| v.nil? }
      args[:event] = caller_locations(1..1).first.label

      keys = *(binding.local_variables - [:context, :args, :keys, :values, :event])
      values = keys.map { |arg| binding.local_variable_get(arg) } + args.values
      keys += args.keys

      event = self.class.const_get(TrackedEvent.snake_to_camel(args[:event]))
      event.new([[*self.members, *keys], [*self.values, *values]].transpose.to_h)
    end

    def initialize(*)
      self
        .public_methods(false)
        .select { |m| m =~ /admin_panel_/ }
        .each do |tracker|

          params =
            method(tracker)
            .parameters
            .select { |pair| pair[0] == :keyreq || pair[0] == :key }
            .reduce([]) { |acc, pair| acc.push(pair[1]) }
            .push(:event)

          self.class.instance_exec do
            unless self.const_defined?(TrackedEvent.snake_to_camel(tracker.to_s))
              self.const_set(TrackedEvent.snake_to_camel(tracker.to_s), Struct.new(*self.members, *params, keyword_init: true))
            end
          end

        end

      super
    end

  end
end
# rubocop:enable ParameterLists
