defmodule Segment.Analytics do
  @moduledoc """
    The `Segment.Analytics` module is the easiest way to send Segment events and provides convenience methods for `track`, `identify,` `screen`, `alias`, `group`, and `page` calls

    The functions will then delegate the call to the configured service implementation which can be changed with:
    ```elixir
    config :segment, sender_impl: Segment.Analytics.Batcher,
    ```
    By default (if no configuration is given) it will use `Segment.Analytics.Batcher` to send events in a batch periodically
  """
  alias Segment.Analytics.{Track, Identify, Screen, Context, Alias, Group, Page}

  @type segment_id :: String.t() | integer()

  @doc """
    Make a call to Segment with an event. Should be of type `Track, Identify, Screen, Alias, Group or Page`
  """
  @spec send(Segment.segment_event()) :: :ok
  def send(%{__struct__: mod} = event)
      when mod in [Track, Identify, Screen, Alias, Group, Page] do
    call(event)
  end

  @doc """
    `track` lets you record the actions your users perform. Every action triggers what Segment call an “event”, which can also have associated properties as defined in the
    `Segment.Analytics.Track` struct

    See [https://segment.com/docs/spec/track/](https://segment.com/docs/spec/track/)
  """
  @spec track(Segment.Analytics.Track.t()) :: :ok
  def track(t = %Track{}) do
    call(t)
  end

  @doc """
    `track` lets you record the actions your users perform. Every action triggers what Segment call an “event”, which can also have associated properties. `track/4` takes a `user_id`, an
    `event_name`, optional additional `properties` and an optional `Segment.Analytics.Context` struct.

    See [https://segment.com/docs/spec/track/](https://segment.com/docs/spec/track/)
  """
  @spec track(segment_id(), String.t(), map(), Segment.Analytics.Context.t()) :: :ok
  def track(user_id, event_name, properties \\ %{}, context \\ Context.new()) do
    %Track{
      userId: user_id,
      event: event_name,
      properties: properties,
      context: context
    }
    |> call
  end

  @doc """
    `identify` lets you tie a user to their actions and record traits about them as defined in the
    `Segment.Analytics.Identify` struct

    See [https://segment.com/docs/spec/identify/](https://segment.com/docs/spec/identify/)
  """
  @spec identify(Segment.Analytics.Identify.t()) :: :ok
  def identify(i = %Identify{}) do
    call(i)
  end

  @doc """
  `identify` lets you tie a user to their actions and record traits about them. `identify/3` takes a `user_id`, optional additional `traits` and an optional `Segment.Analytics.Context` struct.

  See [https://segment.com/docs/spec/identify/](https://segment.com/docs/spec/identify/)
  """
  @spec identify(segment_id(), map(), Segment.Analytics.Context.t()) :: :ok
  def identify(user_id, traits \\ %{}, context \\ Context.new()) do
    %Identify{userId: user_id, traits: traits, context: context}
    |> call
  end

  @doc """
    `screen` let you record whenever a user sees a screen of your mobile app with properties defined in the
    `Segment.Analytics.Screen` struct

  See [https://segment.com/docs/spec/screen/](https://segment.com/docs/spec/screen/)
  """
  @spec screen(Segment.Analytics.Screen.t()) :: :ok
  def screen(s = %Screen{}) do
    call(s)
  end

  @doc """
  `screen` let you record whenever a user sees a screen of your mobile app. `screen/4` takes a `user_id`, an optional `screen_name`, optional `properties` and an optional `Segment.Analytics.Context` struct.

  See [https://segment.com/docs/spec/screen/](https://segment.com/docs/spec/screen/)
  """
  @spec screen(segment_id(), String.t(), map(), Segment.Analytics.Context.t()) :: :ok
  def screen(user_id, screen_name \\ "", properties \\ %{}, context \\ Context.new()) do
    %Screen{
      userId: user_id,
      name: screen_name,
      properties: properties,
      context: context
    }
    |> call
  end

  @doc """
    `alias` is how you associate one identity with another with properties defined in the `Segment.Analytics.Alias` struct

  See [https://segment.com/docs/spec/alias/](https://segment.com/docs/spec/alias/)
  """
  @spec alias(Segment.Analytics.Alias.t()) :: :ok
  def alias(a = %Alias{}) do
    call(a)
  end

  @doc """
  `alias` is how you associate one identity with another. `alias/3` takes a `user_id` and a `previous_id` to map from. It also takes an optional `Segment.Analytics.Context` struct.

  See [https://segment.com/docs/spec/alias/](https://segment.com/docs/spec/alias/)
  """
  @spec alias(segment_id(), segment_id(), Segment.Analytics.Context.t()) :: :ok
  def alias(user_id, previous_id, context \\ Context.new()) do
    %Alias{userId: user_id, previousId: previous_id, context: context}
    |> call
  end

  @doc """
  The `group` call is how you associate an individual user with a group with the properties in the defined in the `Segment.Analytics.Group` struct

  See [https://segment.com/docs/spec/group/](https://segment.com/docs/spec/group/)
  """
  @spec group(Segment.Analytics.Group.t()) :: :ok
  def group(g = %Group{}) do
    call(g)
  end

  @doc """
  The `group` call is how you associate an individual user with a group. `group/4` takes a `user_id` and a `group_id` to associate it with. It also takes optional `traits` of the group and
  an optional `Segment.Analytics.Context` struct.

  See [https://segment.com/docs/spec/group/](https://segment.com/docs/spec/group/)
  """
  @spec group(segment_id(), segment_id(), map(), Segment.Analytics.Context.t()) :: :ok
  def group(user_id, group_id, traits \\ %{}, context \\ Context.new()) do
    %Group{userId: user_id, groupId: group_id, traits: traits, context: context}
    |> call
  end

  @doc """
  The `page` call lets you record whenever a user sees a page of your website with the properties defined in the `Segment.Analytics.Page` struct

  See [https://segment.com/docs/spec/page/](https://segment.com/docs/spec/page/)
  """
  @spec page(Segment.Analytics.Page.t()) :: :ok
  def page(p = %Page{}) do
    call(p)
  end

  @doc """
  The `page` call lets you record whenever a user sees a page of your website. `page/4` takes a `user_id` and an optional `page_name`, optional `properties` and an optional `Segment.Analytics.Context` struct.

  See [https://segment.com/docs/spec/page/](https://segment.com/docs/spec/page/)
  """
  @spec page(segment_id(), String.t(), map(), Segment.Analytics.Context.t()) :: :ok
  def page(user_id, page_name \\ "", properties \\ %{}, context \\ Context.new()) do
    %Page{userId: user_id, name: page_name, properties: properties, context: context}
    |> call
  end

  @spec call(Segment.segment_event()) :: :ok
  def call(event) do
    Segment.Config.service().call(event)
  end
end
