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

  alias WhoNeedHelp.{Help, Messaging, Pagination, Tracking, Trust}
  alias WhoNeedHelpWeb.Presence

  @e2e_routes Application.compile_env(:who_need_help, :e2e_routes, false)
  @initial_message_window 50

  @impl true
  def mount(%{"id" => id}, _session, socket) do
    case Help.get_request(socket.assigns.current_scope, id) do
      {:ok, request} ->
        native_client =
          connected?(socket) &&
            get_connect_params(socket)["client_type"] == "android"

        socket =
          socket
          |> assign(:subscribed_assignment_id, nil)
          |> assign(:tracking_presence_key, nil)
          |> assign(:tracking_session_id, nil)
          |> assign(:native_client, native_client)
          |> assign(
            :e2e_runtime_node,
            if(@e2e_routes && connected?(socket), do: to_string(Node.self()), else: nil)
          )
          |> assign(
            :e2e_boot_id,
            if(@e2e_routes && connected?(socket),
              do: :persistent_term.get({WhoNeedHelp, :e2e_boot_id}),
              else: nil
            )
          )

        if connected?(socket), do: Help.subscribe_request(id)

        socket = maybe_subscribe_assignment(socket, request)

        tracking_session =
          request.assignment &&
            Tracking.active_session(socket.assigns.current_scope, request.assignment)

        socket =
          socket
          |> assign(
            :tracking_session_id,
            if(tracking_session, do: tracking_session.id, else: nil)
          )
          |> load(request, not is_nil(tracking_session))
          |> maybe_track_browser_presence()
          |> maybe_start_native_tracking()

        {:ok, socket}

      {:error, :not_found} ->
        {:ok,
         socket
         |> put_flash(:error, gettext("This request is unavailable."))
         |> push_navigate(to: ~p"/requests")}
    end
  end

  @impl true
  def handle_info({event, request}, socket) when event in [:request_updated, :request_created] do
    case Help.get_request(socket.assigns.current_scope, request.id) do
      {:ok, request} ->
        socket = maybe_subscribe_assignment(socket, request)
        {:noreply, sync_tracking_after_request_update(socket, request)}

      {:error, :not_found} ->
        {:noreply,
         socket
         |> put_flash(:error, gettext("This request is no longer available."))
         |> push_navigate(to: ~p"/requests")}
    end
  end

  def handle_info({:new_message, message}, socket) do
    {:noreply, put_realtime_message(socket, message)}
  end

  def handle_info({:position_updated, user_id, position, evidence}, socket) do
    positions = Map.put(socket.assigns.positions, user_id, position)

    {:noreply,
     socket
     |> assign_positions(positions)
     |> assign_assignment_evidence(evidence)}
  end

  def handle_info({:tracking_stopped, user_id}, socket) do
    positions = Map.delete(socket.assigns.positions, user_id)

    socket =
      socket
      |> assign_positions(positions)
      |> then(fn socket ->
        if user_id == socket.assigns.current_scope.user.id do
          socket
          |> assign(:tracking_active, false)
          |> assign(:tracking_session_id, nil)
          |> maybe_untrack_browser_presence()
        else
          socket
        end
      end)

    {:noreply, socket}
  end

  @impl true
  def handle_event("accept", _, socket) do
    case Help.accept_request(socket.assigns.current_scope, socket.assigns.request.id) do
      {:ok, _} ->
        {:noreply,
         put_flash(socket, :info, gettext("You are matched. Use chat to coordinate safely."))}

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

  def handle_event("cancel-request", _, socket) do
    case Help.cancel_request(socket.assigns.current_scope, socket.assigns.request.id) do
      {:ok, _request} ->
        {:noreply, put_flash(socket, :info, gettext("Request cancelled."))}

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

  def handle_event("start", _, socket),
    do: transition(socket, &Help.start_assignment/2, gettext("Help is in progress."))

  def handle_event("confirm", _, socket),
    do: transition(socket, &Help.confirm_completion/2, gettext("Your confirmation was saved."))

  def handle_event("withdraw", _, socket),
    do:
      transition(socket, &Help.withdraw_assignment/2, gettext("You withdrew from this request."))

  def handle_event("verify-code", %{"handover" => %{"code" => code}}, socket) do
    case Help.verify_handover(socket.assigns.current_scope, socket.assigns.assignment.id, code) do
      {:ok, _} ->
        {:noreply, put_flash(socket, :info, gettext("Handover code verified."))}

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

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

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

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

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

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

  def handle_event("start-tracking", _, socket) do
    case Tracking.start_session(socket.assigns.current_scope, socket.assigns.assignment) do
      {:ok, tracking_session} ->
        {:noreply,
         socket
         |> assign(:tracking_active, true)
         |> assign(:tracking_session_id, tracking_session.id)
         |> maybe_track_browser_presence()
         |> maybe_start_native_tracking()}

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

  def handle_event("location-update", params, socket) do
    case Tracking.update_position(socket.assigns.current_scope, socket.assigns.assignment, params) do
      {:ok, _} -> {:noreply, socket}
      {:error, _} -> {:noreply, socket}
    end
  end

  def handle_event("location-error", _, socket) do
    {:noreply, put_flash(socket, :error, gettext("The browser could not share your location."))}
  end

  def handle_event("stop-tracking", _, socket) do
    case Tracking.stop_session(socket.assigns.current_scope, socket.assigns.assignment) do
      {:ok, _} ->
        {:noreply,
         socket
         |> assign(:tracking_active, false)
         |> assign(:tracking_session_id, nil)
         |> maybe_untrack_browser_presence()
         |> maybe_stop_native_tracking()}

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

  def handle_event("review", %{"review" => params}, socket) do
    case Trust.submit_review(socket.assigns.current_scope, socket.assigns.assignment, params) do
      {:ok, _} ->
        {:noreply,
         put_flash(
           socket,
           :info,
           gettext("Review saved. It appears after both participants review.")
         )}

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

  def handle_event("report", %{"report" => params}, socket) do
    target =
      cond do
        socket.assigns.report_message_id ->
          %{"message_id" => socket.assigns.report_message_id}

        Help.requester?(socket.assigns.current_scope, socket.assigns.request) &&
            socket.assigns.assignment ->
          %{"assignment_id" => socket.assigns.assignment.id}

        true ->
          %{"request_id" => socket.assigns.request.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: "request-report-form"})}

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

  def handle_event("select-report-message", %{"id" => message_id}, socket) do
    message =
      Enum.find(socket.assigns.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", _, socket),
    do: {:noreply, assign(socket, :report_message_id, nil)}

  def handle_event("block-user", _, socket) do
    case Trust.block(socket.assigns.current_scope, socket.assigns.other_user_id) do
      {:ok, _block} ->
        if socket.assigns.tracking_active and socket.assigns.assignment do
          Tracking.stop_session(socket.assigns.current_scope, socket.assigns.assignment)
        end

        {:noreply,
         socket
         |> maybe_untrack_browser_presence()
         |> assign(:blocked_by_current, true)
         |> assign(:messages, [])
         |> assign(:messages_cursor, nil)
         |> assign(:positions, %{})
         |> assign(:tracking_active, false)
         |> assign(:tracking_session_id, nil)
         |> assign(
           :markers,
           Jason.encode!(request_markers(socket.assigns.current_scope, socket.assigns.request))
         )
         |> put_flash(
           :info,
           gettext("User blocked. Their requests and new messages are hidden.")
         )}

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

  def handle_event("unblock-user", _, socket) do
    case Trust.unblock(socket.assigns.current_scope, socket.assigns.other_user_id) do
      {:ok, _result} ->
        {:noreply,
         socket
         |> assign(:blocked_by_current, false)
         |> put_flash(:info, gettext("User unblocked."))}

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

  defp maybe_start_native_tracking(
         %{assigns: %{native_client: true, tracking_active: true, assignment: assignment}} =
           socket
       )
       when not is_nil(assignment) do
    push_event(socket, "native-tracking-start", %{assignment_id: assignment.id})
  end

  defp maybe_start_native_tracking(socket), do: socket

  defp maybe_stop_native_tracking(%{assigns: %{native_client: true}} = socket) do
    push_event(socket, "native-tracking-stop", %{})
  end

  defp maybe_stop_native_tracking(socket), do: socket

  defp maybe_track_browser_presence(
         %{
           assigns: %{
             native_client: false,
             tracking_active: true,
             tracking_presence_key: nil,
             tracking_session_id: tracking_session_id,
             assignment: assignment,
             current_scope: %{user: user}
           }
         } = socket
       )
       when not is_nil(assignment) do
    if connected?(socket) do
      case Presence.track_browser(self(), assignment.id, user.id, tracking_session_id) do
        {:ok, key} -> assign(socket, :tracking_presence_key, key)
        _error -> socket
      end
    else
      socket
    end
  end

  defp maybe_track_browser_presence(socket), do: socket

  defp maybe_untrack_browser_presence(%{assigns: %{tracking_presence_key: key}} = socket)
       when is_binary(key) do
    Presence.untrack_browser(self(), key)
    assign(socket, :tracking_presence_key, nil)
  end

  defp maybe_untrack_browser_presence(socket), do: socket

  defp transition(socket, fun, success) do
    case fun.(socket.assigns.current_scope, socket.assigns.assignment.id) do
      {:ok, _} -> {:noreply, put_flash(socket, :info, success)}
      {:error, reason} -> {:noreply, put_flash(socket, :error, message(reason))}
    end
  end

  defp load(socket, request, tracking_active) do
    assignment = request.assignment
    participant = assignment && Help.participant?(socket.assigns.current_scope, assignment)
    current_user_id = socket.assigns.current_scope.user.id

    other_user_id =
      cond do
        request.requester_id != current_user_id -> request.requester_id
        assignment -> assignment.helper_id
        true -> nil
      end

    messages_page =
      if participant,
        do: Messaging.paginate_messages(socket.assigns.current_scope, assignment),
        else: %WhoNeedHelp.Pagination.Page{}

    positions =
      if participant,
        do: Tracking.list_current_positions(socket.assigns.current_scope, assignment),
        else: %{}

    reputations =
      [request.requester_id, assignment && assignment.helper_id]
      |> Enum.reject(&is_nil/1)
      |> Trust.reputations()

    socket
    |> assign(:page_title, request.title)
    |> assign(:request, request)
    |> assign(
      :structured_details,
      structured_details(request, Gettext.get_locale(WhoNeedHelpWeb.Gettext))
    )
    |> assign(:requester_reputation, Map.fetch!(reputations, request.requester_id))
    |> assign(
      :helper_reputation,
      if(assignment, do: Map.fetch!(reputations, assignment.helper_id), else: nil)
    )
    |> assign(:assignment, assignment)
    |> assign(:participant, participant)
    |> assign(
      :review_submitted,
      Trust.review_submitted?(socket.assigns.current_scope, assignment)
    )
    |> assign(:other_user_id, other_user_id)
    |> assign(
      :blocked_by_current,
      other_user_id && Trust.blocked_by?(current_user_id, other_user_id)
    )
    |> assign(:messages, messages_page.entries)
    |> assign(:messages_cursor, messages_page.next_cursor)
    |> assign(:message_window_limit, max(length(messages_page.entries), @initial_message_window))
    |> assign(:report_message_id, nil)
    |> assign(:positions, positions)
    |> assign(:tracking_active, tracking_active)
    |> assign(:message_form, to_form(%{"body" => ""}, as: :message))
    |> assign(:handover_form, to_form(%{"code" => ""}, as: :handover))
    |> assign(:review_form, to_form(%{"rating" => "5", "comment" => ""}, as: :review))
    |> assign(:report_form, report_form())
    |> assign(
      :markers,
      Jason.encode!(
        request_markers(socket.assigns.current_scope, request) ++ position_markers(positions)
      )
    )
  end

  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
    messages = append_message(socket.assigns.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(:messages, messages)
      |> assign(:messages_cursor, Pagination.encode(oldest.inserted_at, oldest.id))
    else
      assign(socket, :messages, messages)
    end
  end

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

  defp maybe_subscribe_assignment(socket, request) do
    assignment = request.assignment

    if connected?(socket) && assignment &&
         Help.participant?(socket.assigns.current_scope, assignment) &&
         socket.assigns.subscribed_assignment_id != assignment.id do
      Messaging.subscribe(assignment.id)
      Tracking.subscribe(assignment.id)
      assign(socket, :subscribed_assignment_id, assignment.id)
    else
      socket
    end
  end

  defp sync_tracking_after_request_update(socket, request) do
    tracking_was_active = socket.assigns.tracking_active

    tracking_active =
      (tracking_was_active and request.assignment) &&
        request.assignment.status in [:accepted, :in_progress]

    socket = load(socket, request, tracking_active)

    if tracking_was_active and not tracking_active do
      socket
      |> assign(:tracking_session_id, nil)
      |> maybe_untrack_browser_presence()
      |> maybe_stop_native_tracking()
    else
      socket
    end
  end

  defp assign_positions(socket, positions) do
    markers =
      request_markers(socket.assigns.current_scope, socket.assigns.request) ++
        position_markers(positions)

    socket
    |> assign(:positions, positions)
    |> assign(:markers, Jason.encode!(markers))
  end

  defp assign_assignment_evidence(%{assigns: %{assignment: nil}} = socket, _evidence),
    do: socket

  defp assign_assignment_evidence(socket, evidence) do
    assignment = socket.assigns.assignment

    assign(socket, :assignment, %{
      assignment
      | helper_movement_observed_at:
          evidence.helper_movement_observed_at || assignment.helper_movement_observed_at,
        proximity_observed_at: evidence.proximity_observed_at || assignment.proximity_observed_at
    })
  end

  defp structured_details(request, locale) do
    request.category
    |> WhoNeedHelp.Catalog.structured_fields()
    |> Enum.flat_map(fn field ->
      case request.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 request_markers(scope, request) do
    case Help.request_coordinates(scope, request) do
      nil ->
        []

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

  defp position_markers(positions) do
    Enum.map(positions, fn {_user_id, point} ->
      %{
        latitude: point.latitude,
        longitude: point.longitude,
        exact: true,
        title: gettext("Shared location"),
        location:
          gettext(
            "Last update: %{time}",
            time: Calendar.strftime(point.captured_at, "%d.%m.%Y, %H:%M:%S UTC")
          )
      }
    end)
  end

  defp message(:own_request), do: gettext("You cannot accept your own request.")
  defp message(:not_open), do: gettext("Another helper already accepted this request.")
  defp message(:invalid_code), do: gettext("That handover code is not correct.")
  defp message(:forbidden), do: gettext("You are not allowed to do that.")

  defp message(:invalid_transition),
    do: gettext("That step is not available in the current state.")

  defp message(:account_not_eligible),
    do: gettext("Confirm your account and ensure it is active first.")

  defp message(:blocked), do: gettext("This interaction is blocked.")
  defp message(:expired), do: gettext("This request has expired.")
  defp message(:not_found), do: gettext("This request is no longer available.")
  defp message(:assignment_inactive), do: gettext("This match is no longer active.")
  defp message(:tracking_not_active), do: gettext("Live location sharing is not active.")
  defp message(:rate_limited), do: gettext("Too many actions in the configured time window.")
  defp message(%Ecto.Changeset{}), do: gettext("Please check the entered data.")

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

  defp urgency_label(:now), do: gettext("Now")
  defp urgency_label("now"), do: gettext("Now")
  defp urgency_label(:today), do: gettext("Today")
  defp urgency_label("today"), do: gettext("Today")
  defp urgency_label(:scheduled), do: gettext("Scheduled")
  defp urgency_label("scheduled"), do: gettext("Scheduled")
  defp urgency_label(value), do: value

  defp status_label(:open), do: gettext("Open")
  defp status_label(:matched), do: gettext("Matched")
  defp status_label(:accepted), do: gettext("Accepted")
  defp status_label(:in_progress), do: gettext("In progress")
  defp status_label(:completed), do: gettext("Completed")
  defp status_label(:cancelled), do: gettext("Cancelled")
  defp status_label(value), do: value

  defp visibility_label(:approximate_public), do: gettext("Approximate publicly")
  defp visibility_label(:hidden), do: gettext("Hidden")
  defp visibility_label(:exact_for_active_match), do: gettext("Exact only for active match")
  defp visibility_label(:exact_public), do: gettext("Exact publicly")
  defp visibility_label(value), do: value

  @impl true
  def render(assigns) do
    ~H"""
    <Layouts.app flash={@flash} current_scope={@current_scope}>
      <div
        :if={@e2e_runtime_node}
        id="e2e-runtime-node"
        data-node={@e2e_runtime_node}
        data-boot-id={@e2e_boot_id}
        data-position-count={map_size(@positions)}
        hidden
        aria-hidden="true"
      >
      </div>
      <.link navigate={~p"/requests"} class="btn btn-ghost btn-sm">
        {gettext("← All requests")}
      </.link>
      <div class="alert alert-warning mt-4 text-sm">
        <.icon name="hero-exclamation-triangle" class="size-5" />
        <span>
          {gettext(
            "This is not an emergency service. Contact local emergency services for immediate danger."
          )}
        </span>
      </div>
      <div class="mt-4 grid gap-7 lg:grid-cols-[minmax(0,1fr)_25rem]">
        <div class="space-y-6">
          <section class="rounded-3xl border border-base-300 bg-base-100 p-7">
            <div class="flex flex-wrap gap-2">
              <span class="badge badge-success">{WhoNeedHelp.Catalog.Category.name(
                @request.category,
                Gettext.get_locale(WhoNeedHelpWeb.Gettext)
              )}</span>
              <span class="badge badge-error badge-outline">{urgency_label(@request.urgency)}</span>
              <span class="badge">{status_label(@request.status)}</span>
            </div>
            <h1 class="mt-5 text-4xl font-black tracking-tight">{@request.title}</h1>
            <p class="mt-5 whitespace-pre-wrap leading-7 text-base-content/75">
              {@request.description}
            </p>
            <div :if={@request.pickup_instructions} class="mt-5 rounded-2xl bg-base-200 p-5">
              <div class="text-xs font-bold uppercase tracking-wide">{gettext("Pickup notes")}</div>
              <p class="mt-2 whitespace-pre-wrap text-sm">{@request.pickup_instructions}</p>
            </div>
            <div :if={@structured_details != []} class="mt-5 rounded-2xl bg-success/10 p-5">
              <div class="text-xs font-bold uppercase tracking-wide">
                {gettext("Category details")}
              </div>
              <dl class="mt-3 grid gap-2 text-sm sm:grid-cols-2">
                <div :for={{label, value} <- @structured_details}>
                  <dt class="text-base-content/50">{label}</dt>
                  <dd class="font-semibold">{value}</dd>
                </div>
              </dl>
            </div>
            <div class="mt-6 grid gap-3 border-t border-base-300 pt-5 text-sm sm:grid-cols-2">
              <div>
                <span class="text-base-content/50">{gettext("Area:")}</span>
                {@request.location_label}
              </div>
              <div>
                <span class="text-base-content/50">{gettext("Requester:")}</span>
                {@request.requester.display_name}
              </div>
              <div>
                <span class="text-base-content/50">{gettext("Expires:")}</span>
                {Calendar.strftime(
                  @request.expires_at,
                  "%d.%m.%Y, %H:%M UTC"
                )}
              </div>
              <div>
                <span class="text-base-content/50">{gettext("Location:")}</span>
                {visibility_label(@request.location_visibility)}
              </div>
            </div>
            <div
              :if={@request.requester.social_identities != []}
              class="mt-4 flex flex-wrap items-center gap-2"
            >
              <span class="text-xs text-base-content/50">{gettext("Requester links:")}</span>
              <a
                :for={identity <- @request.requester.social_identities}
                href={identity.profile_url}
                target="_blank"
                rel="noopener noreferrer nofollow ugc"
                class="badge badge-warning badge-outline"
              >
                {identity.provider} · {if identity.verified_at,
                  do: gettext("verified"),
                  else: gettext("unverified")}
              </a>
            </div>
            <div class="mt-4 flex flex-wrap gap-2 text-xs">
              <span class="badge badge-outline">
                {gettext("Requester: %{count} completed",
                  count: @requester_reputation.completed
                )}
              </span>
              <span class="badge badge-outline">
                {gettext("%{count} unique people", count: @requester_reputation.unique_people)}
              </span>
              <span class="badge badge-outline">
                {gettext("rating %{rating}", rating: @requester_reputation.rating || "—")}
              </span>
            </div>
          </section>

          <section :if={@participant} class="rounded-3xl border border-base-300 p-6">
            <div class="flex items-center justify-between">
              <h2 class="text-xl font-bold">{gettext("Private match chat")}</h2>
              <span class="badge badge-success badge-sm">{gettext("participants only")}</span>
            </div>
            <div
              id="messages"
              class="mt-5 max-h-80 space-y-3 overflow-y-auto rounded-2xl bg-base-200 p-4"
            >
              <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={@messages == []} class="py-6 text-center text-sm text-base-content/50">
                {gettext("No messages yet.")}
              </p>
              <div
                :for={message <- @messages}
                class={[
                  "chat",
                  message.sender_id == @current_scope.user.id && "chat-end",
                  message.sender_id != @current_scope.user.id && "chat-start"
                ]}
              >
                <div class="chat-header text-xs">{message.sender.display_name}</div>
                <div class="chat-bubble">{message.body}</div>
                <button
                  :if={message.sender_id != @current_scope.user.id}
                  phx-click="select-report-message"
                  phx-value-id={message.id}
                  class="btn btn-ghost btn-xs mt-1 text-error"
                >
                  {gettext("Report message")}
                </button>
              </div>
            </div>
            <.form
              id="message-form"
              for={@message_form}
              phx-submit="send-message"
              class="mt-4 flex gap-2"
            >
              <.input
                id="message-body"
                field={@message_form[:body]}
                placeholder={gettext("Write a safe coordination message…")}
                class="grow"
              />
              <.button id="send-message-button" class="btn btn-primary self-end">
                {gettext("Send")}
              </.button>
            </.form>
          </section>

          <section
            :if={@assignment && @assignment.status == :completed && @participant}
            class="rounded-3xl bg-base-200 p-6"
          >
            <h2 class="text-xl font-bold">{gettext("Double-blind review")}</h2>
            <p class="mt-1 text-sm text-base-content/60">
              {gettext("Your review is revealed only after both participants submit.")}
            </p>
            <p :if={@review_submitted} class="mt-4 text-sm text-base-content/70">
              {gettext("Review saved. It appears after both participants review.")}
            </p>
            <.form
              :if={!@review_submitted}
              for={@review_form}
              phx-submit="review"
              class="mt-4 space-y-3"
            >
              <.input
                field={@review_form[:rating]}
                type="select"
                label={gettext("Rating")}
                options={[
                  {gettext("5 — excellent"), 5},
                  {gettext("4 — good"), 4},
                  {gettext("3 — okay"), 3},
                  {gettext("2 — poor"), 2},
                  {gettext("1 — unsafe"), 1}
                ]}
              />
              <.input
                field={@review_form[:comment]}
                type="textarea"
                label={gettext("Comment (optional)")}
              />
              <.button class="btn btn-outline">{gettext("Submit review")}</.button>
            </.form>
          </section>
        </div>

        <aside class="space-y-5">
          <div
            id="show-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-3xl bg-neutral p-6 text-neutral-content">
            <h2 class="text-lg font-bold">{gettext("Action center")}</h2>
            <%= cond do %>
              <% @request.status == :open && @request.requester_id != @current_scope.user.id -> %>
                <p class="mt-2 text-sm opacity-70">
                  {gettext("Accept only if you can complete this safely and for free.")}
                </p>
                <button phx-click="accept" class="btn btn-success mt-4 w-full">
                  {gettext("I can help")}
                </button>
              <% @request.status == :open -> %>
                <p class="mt-3 text-sm opacity-70">
                  {gettext("Waiting for a nearby volunteer.")}
                </p>
                <button
                  :if={Help.requester?(@current_scope, @request)}
                  phx-click="cancel-request"
                  class="btn btn-error btn-outline mt-4 w-full"
                >
                  {gettext("Cancel request")}
                </button>
              <% @participant -> %>
                <div class="mt-4 space-y-3">
                  <div class="rounded-xl bg-white/10 p-3 text-sm">
                    {gettext("Helper: %{name}", name: @assignment.helper.display_name)}
                  </div>
                  <div class="grid grid-cols-3 gap-2 rounded-xl bg-white/10 p-3 text-center text-xs">
                    <div>
                      <strong class="block text-lg">{@helper_reputation.completed}</strong>
                      {gettext("completed")}
                    </div>
                    <div>
                      <strong class="block text-lg">{@helper_reputation.unique_people}</strong>
                      {gettext("people")}
                    </div>
                    <div>
                      <strong class="block text-lg">{@helper_reputation.rating || "—"}</strong>
                      {gettext("rating")}
                    </div>
                  </div>
                  <div class="flex flex-wrap gap-2 rounded-xl bg-white/10 p-3 text-xs">
                    <span class={[
                      "badge badge-sm",
                      @assignment.proximity_observed_at && "badge-success",
                      is_nil(@assignment.proximity_observed_at) && "badge-ghost"
                    ]}>
                      {if @assignment.proximity_observed_at,
                        do: gettext("proximity supported"),
                        else: gettext("no proximity evidence")}
                    </span>
                    <span class={[
                      "badge badge-sm",
                      @assignment.helper_movement_observed_at && "badge-success",
                      is_nil(@assignment.helper_movement_observed_at) && "badge-ghost"
                    ]}>
                      {if @assignment.helper_movement_observed_at,
                        do: gettext("helper movement supported"),
                        else: gettext("no movement evidence")}
                    </span>
                    <p class="w-full opacity-60">
                      {gettext(
                        "Location sharing is optional; missing evidence is not proof of abuse."
                      )}
                    </p>
                  </div>
                  <div
                    :if={@assignment.helper.social_identities != []}
                    class="flex flex-wrap gap-2 rounded-xl bg-white/10 p-3"
                  >
                    <a
                      :for={identity <- @assignment.helper.social_identities}
                      href={identity.profile_url}
                      target="_blank"
                      rel="noopener noreferrer nofollow ugc"
                      class="badge badge-warning badge-outline"
                    >
                      {identity.provider} · {if identity.verified_at,
                        do: gettext("verified"),
                        else: gettext("unverified")}
                    </a>
                  </div>
                  <button
                    :if={Help.helper?(@current_scope, @assignment) && @assignment.status == :accepted}
                    phx-click="start"
                    class="btn btn-success w-full"
                  >
                    {gettext("Start helping")}
                  </button>
                  <button
                    :if={@assignment.status in [:accepted, :in_progress]}
                    phx-click="confirm"
                    class="btn btn-outline w-full"
                  >
                    {gettext("Confirm my part is complete")}
                  </button>
                  <button
                    :if={
                      Help.helper?(@current_scope, @assignment) &&
                        @assignment.status in [:accepted, :in_progress]
                    }
                    phx-click="withdraw"
                    class="btn btn-error btn-outline w-full"
                  >
                    {gettext("Withdraw from this request")}
                  </button>
                  <button
                    :if={
                      Help.requester?(@current_scope, @request) &&
                        @assignment.status in [:accepted, :in_progress]
                    }
                    phx-click="cancel-request"
                    class="btn btn-error btn-outline w-full"
                  >
                    {gettext("Cancel request")}
                  </button>

                  <div
                    :if={Help.requester?(@current_scope, @request)}
                    class="rounded-xl bg-white/10 p-4 text-center"
                  >
                    <div class="text-xs opacity-65">
                      {gettext("HANDOVER CODE FOR THE HELPER")}
                    </div>
                    <div class="mt-1 font-mono text-3xl font-black tracking-[.25em]">
                      {Help.handover_code(@request.id)}
                    </div>
                  </div>

                  <.form
                    :if={
                      Help.helper?(@current_scope, @assignment) &&
                        is_nil(@assignment.handover_verified_at)
                    }
                    for={@handover_form}
                    phx-submit="verify-code"
                  >
                    <.input
                      field={@handover_form[:code]}
                      label={gettext("Enter handover code")}
                      maxlength="6"
                    />
                    <.button class="btn btn-primary mt-2 w-full">
                      {gettext("Verify handover")}
                    </.button>
                  </.form>

                  <a
                    :if={
                      @assignment.status == :completed &&
                        Help.requester?(@current_scope, @request) &&
                        @assignment.helper.tip_url
                    }
                    href={@assignment.helper.tip_url}
                    target="_blank"
                    rel="noopener noreferrer nofollow ugc"
                    class="btn btn-outline w-full"
                  >
                    {gettext("Optional thank-you link")}
                  </a>
                </div>
              <% true -> %>
                <p class="mt-3 text-sm opacity-70">
                  {gettext("This request already has a helper.")}
                </p>
            <% end %>
          </div>

          <div
            :if={@participant && @assignment.status in [:accepted, :in_progress]}
            class="rounded-3xl border border-base-300 p-5"
          >
            <h3 class="font-bold">{gettext("Live location")}</h3>
            <p class="mt-2 text-xs text-base-content/55">
              {gettext(
                "In a browser this works only while the page remains open. The Android app shows a persistent notification and can continue after it is minimized. Raw coordinates are removed when sharing stops."
              )}
            </p>
            <div :if={@tracking_active} id="live-tracking" phx-hook="LiveTracking"></div>
            <button
              :if={!@tracking_active}
              id="share-location-button"
              phx-click="start-tracking"
              class="btn btn-outline btn-sm mt-4 w-full"
            >
              {gettext("Share location")}
            </button>
            <button
              :if={@tracking_active}
              id="stop-location-button"
              phx-click="stop-tracking"
              class="btn btn-error btn-sm mt-4 w-full"
            >
              {gettext("Stop and delete position")}
            </button>
          </div>

          <div :if={@other_user_id} class="rounded-3xl border border-base-300 p-5">
            <h3 class="font-bold">{gettext("Safety controls")}</h3>
            <p class="mt-2 text-xs text-base-content/55">
              {gettext(
                "Reports go to moderators. Blocking hides requests in both directions and prevents new chat messages."
              )}
            </p>
            <button
              :if={!@blocked_by_current}
              phx-click="block-user"
              class="btn btn-outline btn-sm mt-4 w-full"
            >
              {gettext("Block this user")}
            </button>
            <button
              :if={@blocked_by_current}
              phx-click="unblock-user"
              class="btn btn-outline btn-sm mt-4 w-full"
            >
              {gettext("Unblock this user")}
            </button>
            <.form
              for={@report_form}
              id="request-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-error/10 p-3 text-xs"
              >
                <span>{gettext("The report will be scoped to the selected chat 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("Report reason")}
                options={[
                  {gettext("Dangerous request"), "dangerous_request"},
                  {gettext("Harassment"), "harassment"},
                  {gettext("Fraud"), "fraud"},
                  {gettext("Prohibited item"), "prohibited_item"},
                  {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>
          </div>
          <.link
            navigate={~p"/legal/content-removal?location=/requests/#{@request.id}"}
            class="btn btn-ghost btn-sm w-full"
          >
            {gettext("Submit a legal content-removal notice")}
          </.link>
        </aside>
      </div>
    </Layouts.app>
    """
  end
end
