defmodule Mobilizon.GraphQL.Schema.SearchType do
  @moduledoc """
  Schema representation for Search
  """
  use Absinthe.Schema.Notation

  alias Mobilizon.Actors.Actor
  alias Mobilizon.Events.Event
  alias Mobilizon.GraphQL.Resolvers.Search
  alias Mobilizon.Service.GlobalSearch.{EventResult, GroupResult}

  interface :event_search_result do
    meta(:authorize, :all)
    field(:id, :id, description: "Internal ID for this event")
    field(:uuid, :uuid, description: "The Event UUID")
    field(:url, :string, description: "The ActivityPub Event URL")
    field(:title, :string, description: "The event's title")
    field(:begins_on, :datetime, description: "Datetime for when the event begins")
    field(:ends_on, :datetime, description: "Datetime for when the event ends")
    field(:status, :event_status, description: "Status of the event")
    field(:picture, :media, description: "The event's picture")
    field(:physical_address, :address, description: "The event's physical address")
    field(:attributed_to, :actor, description: "Who the event is attributed to (often a group)")
    field(:organizer_actor, :actor, description: "The event's organizer (as a person)")
    field(:tags, list_of(:tag), description: "The event's tags")
    field(:category, :event_category, description: "The event's category")
    field(:options, :event_options, description: "The event options")

    field(:participant_stats, :participant_stats,
      description: "Statistics on the event's participants"
    )

    resolve_type(fn
      %Event{}, _ ->
        :event

      %EventResult{}, _ ->
        :event_result

      _, _ ->
        nil
    end)
  end

  @desc "Search event result"
  object :event_result do
    meta(:authorize, :all)
    interfaces([:event_search_result])
    field(:id, :id, description: "Internal ID for this event")
    field(:uuid, :uuid, description: "The Event UUID")
    field(:url, :string, description: "The ActivityPub Event URL")
    field(:title, :string, description: "The event's title")
    field(:begins_on, :datetime, description: "Datetime for when the event begins")
    field(:ends_on, :datetime, description: "Datetime for when the event ends")
    field(:status, :event_status, description: "Status of the event")
    field(:picture, :media, description: "The event's picture")
    field(:physical_address, :address, description: "The event's physical address")
    field(:attributed_to, :actor, description: "Who the event is attributed to (often a group)")
    field(:organizer_actor, :actor, description: "The event's organizer (as a person)")
    field(:tags, list_of(:tag), description: "The event's tags")
    field(:category, :event_category, description: "The event's category")
    field(:options, :event_options, description: "The event options")

    field(:participant_stats, :participant_stats,
      description: "Statistics on the event's participants"
    )
  end

  interface :group_search_result do
    meta(:authorize, :all)
    field(:id, :id, description: "Internal ID for this group")
    field(:url, :string, description: "The ActivityPub actor's URL")
    field(:type, :actor_type, description: "The type of Actor (Person, Group,…)")
    field(:name, :string, description: "The actor's displayed name")
    field(:domain, :string, description: "The actor's domain if (null if it's this instance)")
    field(:summary, :string, description: "The actor's summary")
    field(:preferred_username, :string, description: "The actor's preferred username")
    field(:avatar, :media, description: "The actor's avatar media")
    field(:banner, :media, description: "The actor's banner media")
    field(:followers_count, :integer, description: "Number of followers for this actor")
    field(:members_count, :integer, description: "Number of followers for this actor")
    field(:physical_address, :address, description: "The type of the event's address")

    resolve_type(fn
      %Actor{type: :Group}, _ ->
        :group

      %GroupResult{}, _ ->
        :group_result

      _, _ ->
        nil
    end)
  end

  @desc "Search group result"
  object :group_result do
    meta(:authorize, :all)
    interfaces([:group_search_result])
    field(:id, :id, description: "Internal ID for this group")
    field(:url, :string, description: "The ActivityPub actor's URL")
    field(:type, :actor_type, description: "The type of Actor (Person, Group,…)")
    field(:name, :string, description: "The actor's displayed name")
    field(:domain, :string, description: "The actor's domain if (null if it's this instance)")
    field(:summary, :string, description: "The actor's summary")
    field(:preferred_username, :string, description: "The actor's preferred username")
    field(:avatar, :media, description: "The actor's avatar media")
    field(:banner, :media, description: "The actor's banner media")
    field(:followers_count, :integer, description: "Number of followers for this actor")
    field(:members_count, :integer, description: "Number of followers for this actor")
    field(:physical_address, :address, description: "The type of the event's address")
  end

  @desc "Search persons result"
  object :persons do
    meta(:authorize, [:administrator, :moderator])
    field(:total, non_null(:integer), description: "Total elements")
    field(:elements, non_null(list_of(:person)), description: "Person elements")
  end

  @desc "Search groups result"
  object :groups do
    meta(:authorize, :all)
    field(:total, non_null(:integer), description: "Total elements")
    field(:elements, non_null(list_of(:group_search_result)), description: "Group elements")
  end

  @desc "Search events result"
  object :events do
    meta(:authorize, :all)
    field(:total, non_null(:integer), description: "Total elements")
    field(:elements, non_null(list_of(:event_search_result)), description: "Event elements")
  end

  @desc """
  A entity that can be interacted with from a remote instance
  """
  interface :interactable do
    field(:url, :string, description: "A public URL for the entity")

    resolve_type(fn
      %Actor{type: :Group}, _ ->
        :group

      %Event{}, _ ->
        :event

      _, _ ->
        nil
    end)
  end

  enum :event_type do
    value(:in_person,
      description:
        "The event will happen in person. It can also be livestreamed, but has a physical address"
    )

    value(:online, description: "The event will only happen online. It has no physical address")
  end

  enum :search_target do
    value(:internal,
      description: "Search on content from this instance and from the followed instances"
    )

    value(:global, description: "Search using the global fediverse search")
  end

  enum :search_group_sort_options do
    value(:match_desc, description: "The pertinence of the result")
    value(:member_count_desc, description: "The members count of the group")
  end

  enum :search_event_sort_options do
    value(:match_desc, description: "The pertinence of the result")
    value(:start_time_desc, description: "The start date of the result")
    value(:created_at_desc, description: "When the event was published")
    value(:created_at_asc, description: "When the event was published")
    value(:participant_count_desc, description: "With the most participants")
  end

  object :search_queries do
    @desc "Search persons"
    field :search_persons, :persons do
      arg(:term, :string, default_value: "", description: "Search term")
      arg(:page, :integer, default_value: 1, description: "Result page")
      arg(:limit, :integer, default_value: 10, description: "Results limit per page")
      middleware(Rajska.QueryAuthorization, permit: [:administrator, :moderator], scope: false)
      resolve(&Search.search_persons/3)
    end

    @desc "Search groups"
    field :search_groups, :groups do
      arg(:term, :string, default_value: "", description: "Search term")
      arg(:location, :string, description: "A geohash for coordinates")

      arg(:exclude_my_groups, :boolean,
        description: "Whether to include the groups the current actor is member or follower"
      )

      arg(:minimum_visibility, :group_visibility,
        description: "The minimum visibility the group must have"
      )

      arg(:radius, :float,
        default_value: 50,
        description: "Radius around the location to search in"
      )

      arg(:language_one_of, list_of(:string),
        description: "The list of languages this event can be in"
      )

      arg(:boost_languages, list_of(:string),
        description: "The user's languages that can benefit from a boost in search results"
      )

      arg(:search_target, :search_target,
        default_value: :internal,
        description: "The target of the search (internal or global)"
      )

      arg(:bbox, :string, description: "The bbox to search groups into")
      arg(:zoom, :integer, description: "The zoom level for searching groups")

      arg(:page, :integer, default_value: 1, description: "Result page")
      arg(:limit, :integer, default_value: 10, description: "Results limit per page")

      arg(:sort_by, :search_group_sort_options,
        default_value: :match_desc,
        description: "How to sort search results"
      )

      middleware(Rajska.QueryAuthorization, permit: :all)
      resolve(&Search.search_groups/3)
    end

    @desc "Search events"
    field :search_events, :events do
      arg(:term, :string, default_value: "")
      arg(:tags, :string, description: "A comma-separated string listing the tags")
      arg(:location, :string, description: "A geohash for coordinates")
      arg(:type, :event_type, description: "Whether the event is online or in person")
      arg(:category, :string, description: "The category for the event")

      arg(:category_one_of, list_of(:string),
        description: "The list of categories the event can be in"
      )

      arg(:status_one_of, list_of(:event_status),
        description: "The list of statuses this event can have"
      )

      arg(:language_one_of, list_of(:string),
        description: "The list of languages this event can be in"
      )

      arg(:boost_languages, list_of(:string),
        description: "The user's languages that can benefit from a boost in search results"
      )

      arg(:search_target, :search_target,
        default_value: :internal,
        description: "The target of the search (internal or global)"
      )

      arg(:radius, :float,
        default_value: 50,
        description: "Radius around the location to search in"
      )

      arg(:bbox, :string, description: "The bbox to search events into")
      arg(:zoom, :integer, description: "The zoom level for searching events")

      arg(:page, :integer, default_value: 1, description: "Result page")
      arg(:limit, :integer, default_value: 10, description: "Results limit per page")
      arg(:begins_on, :datetime, description: "Filter events by their start date")
      arg(:ends_on, :datetime, description: "Filter events by their end date")

      arg(:sort_by, :search_event_sort_options,
        default_value: :match_desc,
        description: "How to sort search results"
      )

      middleware(Rajska.QueryAuthorization, permit: :all)
      resolve(&Search.search_events/3)
    end

    @desc "Interact with an URI"
    field :interact, :interactable do
      arg(:uri, non_null(:string), description: "The URI for to interact with")
      middleware(Rajska.QueryAuthorization, permit: :all)
      resolve(&Search.interact/3)
    end
  end
end
