defmodule BerrypodWeb.CartHook do
  @moduledoc """
  LiveView on_mount hook for cart state and shared event handling.

  Mounted in the public_shop live_session to give all shop LiveViews
  cart state, PubSub sync, and shared event handlers via attach_hook.

  Handles these events so individual LiveViews don't have to:
  - `open_cart_drawer` / `close_cart_drawer` - toggle drawer visibility
  - `remove_item` - remove item from cart
  - `increment` / `decrement` - change item quantity
  - `change_country` - update shipping country
  - `{:cart_updated, cart}` info - cross-tab cart sync via PubSub

  LiveViews with custom cart logic (e.g. add_to_cart) can call
  `update_cart_assigns/2` and `broadcast_and_update/2` directly.
  """

  import Phoenix.Component, only: [assign: 3]
  import Phoenix.LiveView, only: [attach_hook: 4, connected?: 1, push_event: 3]

  alias Berrypod.{Cart, Settings, Shipping}

  def on_mount(:mount_cart, _params, session, socket) do
    cart_items = Cart.get_from_session(session)
    country_code = session["country_code"] || "GB"
    available_countries = Shipping.list_available_countries_with_names()

    socket =
      socket
      |> assign(:country_code, country_code)
      |> assign(:available_countries, available_countries)
      |> update_cart_assigns(cart_items)
      |> assign(:cart_drawer_open, false)
      |> assign(:cart_status, nil)
      |> assign(:stripe_connected, Settings.has_secret?("stripe_api_key"))
      |> attach_hook(:cart_events, :handle_event, &handle_cart_event/3)
      |> attach_hook(:cart_info, :handle_info, &handle_cart_info/2)

    socket =
      if connected?(socket) do
        csrf_token = Map.get(session, "_csrf_token", "default")
        topic = "cart:#{csrf_token}"
        Phoenix.PubSub.subscribe(Berrypod.PubSub, topic)
        assign(socket, :cart_topic, topic)
      else
        assign(socket, :cart_topic, nil)
      end

    {:cont, socket}
  end

  # Shared event handlers

  defp handle_cart_event("open_cart_drawer", _params, socket) do
    {:halt, assign(socket, :cart_drawer_open, true)}
  end

  defp handle_cart_event("close_cart_drawer", _params, socket) do
    {:halt, assign(socket, :cart_drawer_open, false)}
  end

  defp handle_cart_event("change_country", %{"country" => code}, socket) do
    socket =
      socket
      |> assign(:country_code, code)
      |> update_cart_assigns(socket.assigns.raw_cart)
      |> push_event("persist_country", %{code: code})

    {:halt, socket}
  end

  defp handle_cart_event("remove_item", %{"id" => variant_id}, socket) do
    cart = Cart.remove_item(socket.assigns.raw_cart, variant_id)

    socket =
      socket
      |> broadcast_and_update(cart)
      |> assign(:cart_status, "Item removed from cart")

    {:halt, socket}
  end

  defp handle_cart_event("increment", %{"id" => variant_id}, socket) do
    cart = Cart.add_item(socket.assigns.raw_cart, variant_id, 1)
    new_qty = Cart.get_quantity(cart, variant_id)

    socket =
      socket
      |> broadcast_and_update(cart)
      |> assign(:cart_status, "Quantity updated to #{new_qty}")

    {:halt, socket}
  end

  defp handle_cart_event("decrement", %{"id" => variant_id}, socket) do
    current = Cart.get_quantity(socket.assigns.raw_cart, variant_id)
    cart = Cart.update_quantity(socket.assigns.raw_cart, variant_id, current - 1)
    new_qty = Cart.get_quantity(cart, variant_id)

    socket =
      socket
      |> broadcast_and_update(cart)
      |> assign(:cart_status, "Quantity updated to #{new_qty}")

    {:halt, socket}
  end

  defp handle_cart_event(
         "update_quantity_form",
         %{"variant_id" => id, "quantity" => qty_str},
         socket
       ) do
    quantity = String.to_integer(qty_str)
    cart = Cart.update_quantity(socket.assigns.raw_cart, id, quantity)
    new_qty = Cart.get_quantity(cart, id)

    socket =
      socket
      |> broadcast_and_update(cart)
      |> assign(:cart_status, "Quantity updated to #{new_qty}")

    {:halt, socket}
  end

  defp handle_cart_event("remove_item_form", %{"variant_id" => id}, socket) do
    cart = Cart.remove_item(socket.assigns.raw_cart, id)

    socket =
      socket
      |> broadcast_and_update(cart)
      |> assign(:cart_status, "Item removed from cart")

    {:halt, socket}
  end

  defp handle_cart_event(_event, _params, socket), do: {:cont, socket}

  # Shared info handlers

  defp handle_cart_info({:cart_updated, cart}, socket) do
    {:halt, update_cart_assigns(socket, cart)}
  end

  defp handle_cart_info(_msg, socket), do: {:cont, socket}

  # Public helpers for LiveViews with custom cart logic

  @doc """
  Updates all cart-related assigns from raw cart data.
  """
  def update_cart_assigns(socket, cart) do
    %{items: items, count: count, subtotal: subtotal} = Cart.build_state(cart)
    country_code = socket.assigns[:country_code] || "GB"
    subtotal_pence = Cart.calculate_subtotal(items)

    shipping_estimate =
      case Shipping.calculate_for_cart(items, country_code) do
        {:ok, cost} when cost > 0 -> cost
        _ -> nil
      end

    cart_total =
      Cart.format_price(subtotal_pence + (shipping_estimate || 0))

    socket
    |> assign(:raw_cart, cart)
    |> assign(:cart_items, items)
    |> assign(:cart_count, count)
    |> assign(:cart_subtotal, subtotal)
    |> assign(:cart_total, cart_total)
    |> assign(:shipping_estimate, shipping_estimate)
  end

  @doc """
  Broadcasts cart update to other tabs and updates local assigns.

  Uses broadcast_from to avoid notifying self (prevents double-update).
  """
  def broadcast_and_update(socket, cart) do
    if socket.assigns.cart_topic do
      Phoenix.PubSub.broadcast_from(
        Berrypod.PubSub,
        self(),
        socket.assigns.cart_topic,
        {:cart_updated, cart}
      )
    end

    socket
    |> update_cart_assigns(cart)
    |> push_event("persist_cart", %{items: Cart.serialize(cart)})
  end
end
