defmodule WhoNeedHelpWeb.ActivityLive.Show do
  use WhoNeedHelpWeb, :live_view

  alias WhoNeedHelp.{Activities, Pagination, Trust}

  @initial_message_window 50

  @impl true
  def mount(%{"id" => id}, _session, socket) do
    case Activities.get_activity(socket.assigns.current_scope, id) do
      {:ok, activity} ->
        if connected?(socket), do: Activities.subscribe_activity(activity.id)
        {:ok, load(socket, activity)}

      {:error, _reason} ->
        {:ok,
         socket
         |> put_flash(:error, gettext("Activity is unavailable."))
         |> push_navigate(to: ~p"/activities")}
    end
  end

  @impl true
  def handle_info({:activity_message, message}, socket) do
    {:noreply, put_realtime_message(socket, message)}
  end

  def handle_info({event, _payload}, socket)
      when event in [
             :activity_updated,
             :join_requested,
             :participant_approved,
             :participant_declined,
             :participant_left
           ] do
    {:noreply, reload(socket)}
  end

  @impl true
  def handle_event("join", _params, socket) do
    respond(
      socket,
      Activities.request_to_join(socket.assigns.current_scope, socket.assigns.activity.id)
    )
  end

  def handle_event("approve", %{"id" => id}, socket) do
    respond(socket, Activities.approve_participant(socket.assigns.current_scope, id))
  end

  def handle_event("decline", %{"id" => id}, socket) do
    respond(socket, Activities.decline_participant(socket.assigns.current_scope, id))
  end

  def handle_event("leave", _params, socket) do
    respond(
      socket,
      Activities.leave_activity(socket.assigns.current_scope, socket.assigns.activity.id)
    )
  end

  def handle_event("cancel", _params, socket) do
    respond(
      socket,
      Activities.cancel_activity(socket.assigns.current_scope, socket.assigns.activity.id)
    )
  end

  def handle_event("complete", _params, socket) do
    respond(
      socket,
      Activities.complete_activity(socket.assigns.current_scope, socket.assigns.activity.id)
    )
  end

  def handle_event("send-message", %{"activity_message" => params}, socket) do
    case Activities.send_message(
           socket.assigns.current_scope,
           socket.assigns.activity.id,
           params
         ) do
      {:ok, message} ->
        {:noreply,
         socket
         |> put_realtime_message(message)
         |> push_event("reset-message-form", %{id: "activity-message-form"})}

      {:error, reason} ->
        {:noreply, put_flash(socket, :error, error_message(reason))}
    end
  end

  def handle_event("load-earlier-messages", _params, socket) do
    page =
      Activities.paginate_messages(
        socket.assigns.current_scope,
        socket.assigns.activity,
        after: socket.assigns.messages_cursor
      )

    activity = socket.assigns.activity
    existing_ids = MapSet.new(activity.messages, & &1.id)
    older = Enum.reject(page.entries, &MapSet.member?(existing_ids, &1.id))

    {:noreply,
     socket
     |> assign(:activity, %{activity | messages: older ++ activity.messages})
     |> assign(:messages_cursor, page.next_cursor)
     |> update(:message_window_limit, &(&1 + length(older)))}
  end

  def handle_event("report", %{"report" => params}, socket) do
    target =
      if socket.assigns.report_message_id do
        %{"activity_message_id" => socket.assigns.report_message_id}
      else
        %{"activity_id" => socket.assigns.activity.id}
      end

    case Trust.report(socket.assigns.current_scope, Map.merge(params, target)) do
      {:ok, _report} ->
        {:noreply,
         socket
         |> assign(:report_form, report_form())
         |> assign(:report_message_id, nil)
         |> put_flash(:info, gettext("Report sent to moderators."))
         |> push_event("reset-message-form", %{id: "activity-report-form"})}

      {:error, reason} ->
        {:noreply, put_flash(socket, :error, error_message(reason))}
    end
  end

  def handle_event("select-report-message", %{"id" => message_id}, socket) do
    message =
      Enum.find(socket.assigns.activity.messages, fn message ->
        message.id == message_id and message.sender_id != socket.assigns.current_scope.user.id
      end)

    if message do
      {:noreply, assign(socket, :report_message_id, message.id)}
    else
      {:noreply,
       put_flash(socket, :error, gettext("That message cannot be reported from this view."))}
    end
  end

  def handle_event("clear-report-message", _params, socket) do
    {:noreply, assign(socket, :report_message_id, nil)}
  end

  def handle_event("block-organizer", _params, socket) do
    case Trust.block(socket.assigns.current_scope, socket.assigns.activity.creator_id) do
      {:ok, _block} ->
        {:noreply,
         socket
         |> put_flash(
           :info,
           gettext("Organizer blocked. Their activities and messages are hidden.")
         )
         |> push_navigate(to: ~p"/activities")}

      {:error, reason} ->
        {:noreply, put_flash(socket, :error, error_message(reason))}
    end
  end

  defp respond(socket, {:ok, _value}), do: {:noreply, reload(socket)}

  defp respond(socket, {:error, reason}) do
    {:noreply, put_flash(socket, :error, error_message(reason))}
  end

  defp reload(socket) do
    case Activities.get_activity(socket.assigns.current_scope, socket.assigns.activity.id) do
      {:ok, activity} ->
        load(socket, activity)

      {:error, _} ->
        socket
        |> put_flash(:error, gettext("Activity is unavailable."))
        |> push_navigate(to: ~p"/activities")
    end
  end

  defp load(socket, activity) do
    user = socket.assigns.current_scope.user
    viewer_participant = Activities.participant_for(activity, user.id)
    organizer? = activity.creator_id == user.id
    approved? = Activities.approved_participant?(activity, user.id)
    coordinates = Activities.coordinates_for(socket.assigns.current_scope, activity)
    messages = Activities.paginate_messages(socket.assigns.current_scope, activity)
    activity = %{activity | messages: messages.entries}

    markers =
      case coordinates do
        nil ->
          []

        coordinates ->
          [
            Map.merge(coordinates, %{
              id: activity.id,
              title: activity.title,
              location: activity.location_label
            })
          ]
      end

    socket
    |> assign(:page_title, activity.title)
    |> assign(:activity, activity)
    |> assign(:messages_cursor, messages.next_cursor)
    |> assign(:message_window_limit, max(length(messages.entries), @initial_message_window))
    |> assign(:viewer_participant, viewer_participant)
    |> assign(:organizer?, organizer?)
    |> assign(:approved?, approved?)
    |> assign(:coordinates, coordinates)
    |> assign(
      :structured_details,
      structured_details(activity, Gettext.get_locale(WhoNeedHelpWeb.Gettext))
    )
    |> assign(:markers, Jason.encode!(markers))
    |> assign(:message_form, to_form(%{"body" => ""}, as: :activity_message))
    |> assign(:report_message_id, nil)
    |> assign(:report_form, report_form())
  end

  defp can_request_join?(activity, nil), do: activity.status == :open

  defp can_request_join?(activity, participant) do
    activity.status == :open and participant.status in [:declined, :left]
  end

  defp pending_participants(activity) do
    Enum.filter(activity.participants, &(&1.status == :requested))
  end

  defp approved_participants(activity) do
    Enum.filter(activity.participants, &(&1.status == :approved))
  end

  defp ordered_messages(activity), do: Enum.sort_by(activity.messages, & &1.inserted_at, DateTime)

  defp append_message(messages, message) do
    if Enum.any?(messages, &(&1.id == message.id)), do: messages, else: messages ++ [message]
  end

  defp put_realtime_message(socket, message) do
    activity = socket.assigns.activity
    messages = append_message(activity.messages, message)
    window_limit = socket.assigns.message_window_limit

    if length(messages) > window_limit do
      messages = Enum.take(messages, -window_limit)
      oldest = hd(messages)

      socket
      |> assign(:activity, %{activity | messages: messages})
      |> assign(:messages_cursor, Pagination.encode(oldest.inserted_at, oldest.id))
    else
      assign(socket, :activity, %{activity | messages: messages})
    end
  end

  defp structured_details(activity, locale) do
    activity.category
    |> WhoNeedHelp.Catalog.structured_fields()
    |> Enum.flat_map(fn field ->
      case activity.structured_data[field["key"]] do
        nil ->
          []

        value ->
          label = localized(field["label"], locale) || field["key"]

          [{label, structured_value(field, value, locale)}]
      end
    end)
  end

  defp structured_value(%{"type" => "select"} = field, value, locale) do
    option =
      Enum.find(field["options"] || [], fn
        %{"value" => candidate} -> to_string(candidate) == to_string(value)
        candidate -> to_string(candidate) == to_string(value)
      end)

    case option do
      %{"label" => labels} -> localized(labels, locale) || to_string(value)
      nil -> to_string(value)
      candidate -> to_string(candidate)
    end
  end

  defp structured_value(%{"type" => "boolean"}, value, _locale)
       when value in [true, "true", "1"],
       do: gettext("Yes")

  defp structured_value(%{"type" => "boolean"}, _value, _locale), do: gettext("No")
  defp structured_value(_field, value, _locale), do: to_string(value)

  defp localized(labels, locale) when is_map(labels),
    do: labels[to_string(locale)] || labels["en"]

  defp localized(_labels, _locale), do: nil

  defp report_form do
    to_form(%{"reason" => "harassment", "details" => ""}, as: :report)
  end

  defp error_message(:already_joined),
    do: gettext("You already have a join request for this activity.")

  defp error_message(:capacity_reached),
    do: gettext("The approved group has reached its capacity.")

  defp error_message(:join_closed), do: gettext("Join requests are closed.")
  defp error_message(:blocked), do: gettext("This action is unavailable.")
  defp error_message(:cannot_report_self), do: gettext("You cannot report your own content.")
  defp error_message(:forbidden), do: gettext("You are not allowed to do that.")

  defp error_message(:invalid_transition),
    do: gettext("This activity state has already changed.")

  defp error_message(:not_open), do: gettext("This activity is no longer open.")
  defp error_message(:not_found), do: gettext("This activity is no longer available.")

  defp error_message(:organizer_already_joined),
    do: gettext("The organizer is already attending.")

  defp error_message(:organizer_cannot_leave),
    do: gettext("The organizer cannot leave the activity.")

  defp error_message(:rate_limited),
    do: gettext("Too many actions in the configured time window.")

  defp error_message(%Ecto.Changeset{}), do: gettext("Please check the submitted message.")

  defp error_message(_reason), do: gettext("Could not complete the action. Please try again.")

  defp status_label(:open), do: gettext("Open")
  defp status_label(:completed), do: gettext("Completed")
  defp status_label(:cancelled), do: gettext("Cancelled")
  defp status_label(value), do: value

  @impl true
  def render(assigns) do
    ~H"""
    <Layouts.app flash={@flash} current_scope={@current_scope}>
      <.link navigate={~p"/activities"} class="btn btn-ghost btn-sm mb-4">
        {gettext("← Back to activities")}
      </.link>

      <div class="grid gap-7 lg:grid-cols-[minmax(0,1fr)_22rem]">
        <main class="space-y-6">
          <section class="rounded-3xl border border-base-300 p-6">
            <div class="flex flex-wrap items-center gap-2">
              <span class="badge badge-info badge-outline">
                {WhoNeedHelp.Catalog.category_path(
                  @activity.category,
                  Gettext.get_locale(WhoNeedHelpWeb.Gettext)
                )}
              </span>
              <span class="badge">{status_label(@activity.status)}</span>
              <span class="badge badge-outline">
                {gettext("%{approved}/%{capacity} approved",
                  approved: length(approved_participants(@activity)),
                  capacity: @activity.capacity
                )}
              </span>
            </div>
            <h1 class="mt-4 text-4xl font-black">{@activity.title}</h1>
            <p class="mt-4 whitespace-pre-wrap text-base-content/70">{@activity.description}</p>

            <dl class="mt-6 grid gap-4 rounded-2xl bg-base-200 p-5 sm:grid-cols-2">
              <div>
                <dt class="text-xs text-base-content/50">{gettext("Starts")}</dt>
                <dd class="font-semibold">
                  {Calendar.strftime(@activity.starts_at, "%d.%m.%Y, %H:%M UTC")}
                </dd>
              </div>
              <div>
                <dt class="text-xs text-base-content/50">{gettext("Join requests close")}</dt>
                <dd class="font-semibold">
                  {Calendar.strftime(@activity.join_deadline, "%d.%m.%Y, %H:%M UTC")}
                </dd>
              </div>
              <div>
                <dt class="text-xs text-base-content/50">{gettext("Meeting area")}</dt>
                <dd class="font-semibold">{@activity.location_label}</dd>
              </div>
              <div>
                <dt class="text-xs text-base-content/50">{gettext("Organizer")}</dt>
                <dd class="font-semibold">
                  {@activity.creator.display_name || gettext("Community member")}
                </dd>
              </div>
              <div
                :if={@activity.creator.social_identities != []}
                class="sm:col-span-2"
              >
                <dt class="text-xs text-base-content/50">{gettext("Organizer social links")}</dt>
                <dd class="mt-2 flex flex-wrap gap-2">
                  <a
                    :for={identity <- @activity.creator.social_identities}
                    href={identity.profile_url}
                    target="_blank"
                    rel="noopener noreferrer nofollow ugc"
                    class={[
                      "badge badge-outline",
                      identity.verified_at && "badge-success",
                      is_nil(identity.verified_at) && "badge-warning"
                    ]}
                  >
                    {identity.provider} · {if identity.verified_at,
                      do: gettext("verified"),
                      else: gettext("unverified")}
                  </a>
                </dd>
              </div>
            </dl>

            <div :if={@structured_details != []} class="mt-5">
              <h2 class="font-bold">{gettext("Plan details")}</h2>
              <dl class="mt-3 grid gap-2 sm:grid-cols-2">
                <div
                  :for={{label, value} <- @structured_details}
                  class="rounded-xl border border-base-300 p-3"
                >
                  <dt class="text-xs text-base-content/50">{label}</dt>
                  <dd class="font-medium">{value}</dd>
                </div>
              </dl>
            </div>
          </section>

          <section :if={@approved?} class="rounded-3xl border border-info/30 bg-info/5 p-6">
            <h2 class="text-xl font-bold">{gettext("Approved group chat")}</h2>
            <p class="mt-1 text-sm text-base-content/60">
              {gettext(
                "Only the organizer and approved participants can read or send these messages."
              )}
            </p>
            <div id="activity-messages" class="mt-5 max-h-96 space-y-3 overflow-y-auto">
              <button
                :if={@messages_cursor}
                type="button"
                phx-click="load-earlier-messages"
                class="btn btn-ghost btn-xs w-full"
              >
                {gettext("Load earlier messages")}
              </button>
              <p :if={@activity.messages == []} class="text-sm text-base-content/50">
                {gettext("No messages yet.")}
              </p>
              <article
                :for={message <- ordered_messages(@activity)}
                class="rounded-2xl bg-base-100 p-4"
              >
                <div class="flex items-center justify-between gap-2">
                  <div class="text-xs font-semibold text-info">
                    {message.sender.display_name || gettext("Participant")}
                  </div>
                  <button
                    :if={message.sender_id != @current_scope.user.id}
                    type="button"
                    phx-click="select-report-message"
                    phx-value-id={message.id}
                    class="btn btn-ghost btn-xs"
                  >
                    {gettext("Report")}
                  </button>
                </div>
                <p class="mt-1 whitespace-pre-wrap text-sm">{message.body}</p>
              </article>
            </div>
            <.form
              :if={@activity.status == :open}
              for={@message_form}
              id="activity-message-form"
              phx-submit="send-message"
              class="mt-4 flex gap-2"
            >
              <.input
                field={@message_form[:body]}
                placeholder={gettext("Message the approved group")}
                class="flex-1"
              />
              <.button class="btn btn-info self-end">{gettext("Send")}</.button>
            </.form>
          </section>
        </main>

        <aside class="space-y-5">
          <div
            id="activity-detail-map"
            phx-hook="AidMap"
            phx-update="ignore"
            data-markers={@markers}
            data-map-unavailable-label={
              gettext("The map is unavailable in this browser. Request details remain usable.")
            }
            class="aid-map"
          />
          <div class="rounded-2xl bg-base-200 p-5 text-sm">
            <h2 class="font-bold">{gettext("Location privacy")}</h2>
            <p class="mt-2 text-base-content/65">
              <%= if @approved? do %>
                {gettext("You are approved, so the map can show the exact meeting coordinates.")}
              <% else %>
                {gettext(
                  "The public map is approximate or hidden. Exact coordinates appear only after approval."
                )}
              <% end %>
            </p>
          </div>

          <div :if={not @organizer?} class="rounded-2xl border border-base-300 p-5">
            <button
              :if={can_request_join?(@activity, @viewer_participant)}
              phx-click="join"
              class="btn btn-info w-full"
            >
              {gettext("Request to join")}
            </button>
            <div :if={@viewer_participant && @viewer_participant.status == :requested}>
              <span class="badge badge-warning">{gettext("Approval pending")}</span>
              <p class="mt-2 text-sm text-base-content/60">
                {gettext("The organizer has not approved your request yet.")}
              </p>
            </div>
            <button
              :if={
                @activity.status == :open && @viewer_participant &&
                  @viewer_participant.status == :approved
              }
              phx-click="leave"
              class="btn btn-outline btn-error w-full"
            >
              {gettext("Leave activity")}
            </button>
          </div>

          <section :if={@organizer?} class="rounded-2xl border border-info/30 p-5">
            <h2 class="font-bold">{gettext("Organizer controls")}</h2>
            <div class="mt-4 space-y-3">
              <article
                :for={participant <- pending_participants(@activity)}
                class="rounded-xl bg-base-200 p-3"
              >
                <div class="font-semibold">
                  {participant.user.display_name || gettext("Community member")}
                </div>
                <div
                  :if={participant.user.social_identities != []}
                  class="mt-2 flex flex-wrap gap-1"
                >
                  <a
                    :for={identity <- participant.user.social_identities}
                    href={identity.profile_url}
                    target="_blank"
                    rel="noopener noreferrer nofollow ugc"
                    class={[
                      "badge badge-xs badge-outline",
                      identity.verified_at && "badge-success",
                      is_nil(identity.verified_at) && "badge-warning"
                    ]}
                  >
                    {identity.provider} · {if identity.verified_at,
                      do: gettext("verified"),
                      else: gettext("unverified")}
                  </a>
                </div>
                <div class="mt-2 flex gap-2">
                  <button
                    phx-click="approve"
                    phx-value-id={participant.id}
                    class="btn btn-info btn-xs"
                  >
                    {gettext("Approve")}
                  </button>
                  <button
                    phx-click="decline"
                    phx-value-id={participant.id}
                    class="btn btn-ghost btn-xs"
                  >
                    {gettext("Decline")}
                  </button>
                </div>
              </article>
              <p :if={pending_participants(@activity) == []} class="text-sm text-base-content/50">
                {gettext("No pending join requests.")}
              </p>
            </div>
            <div :if={@activity.status == :open} class="mt-5 grid gap-2">
              <button phx-click="complete" class="btn btn-success btn-sm">
                {gettext("Mark completed")}
              </button>
              <button phx-click="cancel" class="btn btn-outline btn-error btn-sm">
                {gettext("Cancel activity")}
              </button>
            </div>
          </section>

          <section class="rounded-2xl bg-base-200 p-5">
            <h2 class="font-bold">{gettext("Approved participants")}</h2>
            <ul class="mt-3 space-y-2 text-sm">
              <li :for={participant <- approved_participants(@activity)}>
                {participant.user.display_name || gettext("Community member")}
                <span :if={participant.role == :organizer} class="badge badge-xs ml-1">
                  {gettext("organizer")}
                </span>
                <div
                  :if={participant.user.social_identities != []}
                  class="mt-1 flex flex-wrap gap-1"
                >
                  <a
                    :for={identity <- participant.user.social_identities}
                    href={identity.profile_url}
                    target="_blank"
                    rel="noopener noreferrer nofollow ugc"
                    class={[
                      "badge badge-xs badge-outline",
                      identity.verified_at && "badge-success",
                      is_nil(identity.verified_at) && "badge-warning"
                    ]}
                  >
                    {identity.provider} · {if identity.verified_at,
                      do: gettext("verified"),
                      else: gettext("unverified")}
                  </a>
                </div>
              </li>
            </ul>
          </section>

          <section
            :if={not @organizer? or @report_message_id}
            class="rounded-2xl border border-error/30 p-5"
          >
            <h2 class="font-bold">{gettext("Safety controls")}</h2>
            <button
              :if={not @organizer?}
              phx-click="block-organizer"
              class="btn btn-outline btn-error btn-sm mt-3 w-full"
            >
              {gettext("Block organizer")}
            </button>
            <.form
              for={@report_form}
              id="activity-report-form"
              phx-submit="report"
              class="mt-4 space-y-2"
            >
              <div
                :if={@report_message_id}
                class="flex items-center justify-between rounded-xl bg-warning/10 p-3 text-xs"
              >
                <span>{gettext("The report is scoped to the selected group message.")}</span>
                <button
                  type="button"
                  phx-click="clear-report-message"
                  class="btn btn-ghost btn-xs"
                >
                  {gettext("Clear")}
                </button>
              </div>
              <.input
                field={@report_form[:reason]}
                type="select"
                label={gettext("Reason")}
                options={[
                  {gettext("Harassment"), "harassment"},
                  {gettext("Dangerous plan"), "dangerous_request"},
                  {gettext("Fraud"), "fraud"},
                  {gettext("Spam"), "spam"},
                  {gettext("Impersonation"), "impersonation"},
                  {gettext("Other"), "other"}
                ]}
              />
              <.input
                field={@report_form[:details]}
                type="textarea"
                label={gettext("What happened?")}
              />
              <.button class="btn btn-error btn-outline w-full">
                {gettext("Send report")}
              </.button>
            </.form>
          </section>
          <.link
            navigate={~p"/legal/content-removal?location=/activities/#{@activity.id}"}
            class="btn btn-ghost btn-sm w-full"
          >
            {gettext("Submit a legal content-removal notice")}
          </.link>
        </aside>
      </div>
    </Layouts.app>
    """
  end
end
