defmodule BerrypodWeb.PageEditorHook do
  @moduledoc """
  LiveView on_mount hook for the live page editor sidebar.

  Mounted in the public_shop live_session. When an admin visits any shop
  page with `?edit=true` in the URL, this hook activates editing mode:
  loads a working copy of the page's blocks, attaches event handlers for
  block manipulation, and sets assigns that trigger the editor sidebar
  in `PageRenderer.render_page/1`.

  Non-admin users are unaffected — the hook just assigns `editing: false`.

  ## Actions

  - `:mount_page_editor` — sets up editing assigns and attaches hooks
  """

  import Phoenix.Component, only: [assign: 3]
  import Phoenix.LiveView, only: [attach_hook: 4, put_flash: 3, push_navigate: 2]

  alias Berrypod.Pages
  alias Berrypod.Pages.{BlockEditor, BlockTypes}

  def on_mount(:mount_page_editor, _params, _session, socket) do
    socket =
      socket
      |> assign(:editing, false)
      |> assign(:editing_blocks, nil)
      |> assign(:editor_dirty, false)
      |> assign(:editor_history, [])
      |> assign(:editor_future, [])
      |> assign(:editor_expanded, MapSet.new())
      |> assign(:editor_show_picker, false)
      |> assign(:editor_picker_filter, "")
      |> assign(:editor_allowed_blocks, nil)
      |> assign(:editor_live_region_message, nil)
      |> assign(:editor_current_path, nil)
      |> assign(:editor_sidebar_open, true)
      |> assign(:editor_image_picker_block_id, nil)
      |> assign(:editor_image_picker_field_key, nil)
      |> assign(:editor_image_picker_images, [])
      |> assign(:editor_image_picker_search, "")
      |> attach_hook(:editor_params, :handle_params, &handle_editor_params/3)
      |> attach_hook(:editor_events, :handle_event, &handle_editor_event/3)
      |> attach_hook(:editor_info, :handle_info, &handle_editor_info/2)

    {:cont, socket}
  end

  # ── handle_params: detect ?edit=true ─────────────────────────────

  defp handle_editor_params(_params, uri, socket) do
    parsed = URI.parse(uri)
    query = URI.decode_query(parsed.query || "")
    wants_edit = query["edit"] == "true"

    # Always store the current path for the edit button and "done" navigation
    socket = assign(socket, :editor_current_path, parsed.path)

    cond do
      wants_edit and socket.assigns.is_admin and socket.assigns[:page] ->
        # Page already loaded — enter edit mode and halt (no need for module handle_params)
        {:halt, enter_edit_mode(socket)}

      wants_edit and socket.assigns.is_admin ->
        # Page not loaded yet (e.g. Shop.Content loads in handle_params),
        # defer initialisation until after the LiveView sets @page
        send(self(), :editor_deferred_init)
        {:cont, assign(socket, :editing, true)}

      socket.assigns.editing and not wants_edit ->
        # Exiting edit mode — halt since we've handled the transition
        {:halt, exit_edit_mode(socket)}

      true ->
        {:cont, socket}
    end
  end

  # ── handle_info: deferred init ───────────────────────────────────

  defp handle_editor_info(:editor_deferred_init, socket) do
    if socket.assigns.editing and is_nil(socket.assigns.editing_blocks) and socket.assigns[:page] do
      {:halt, enter_edit_mode(socket)}
    else
      {:cont, socket}
    end
  end

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

  # ── handle_event: editor_* events ────────────────────────────────

  defp handle_editor_event("editor_" <> action, params, socket) do
    if socket.assigns.editing do
      handle_editor_action(action, params, socket)
    else
      {:cont, socket}
    end
  end

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

  # ── Block manipulation actions ───────────────────────────────────

  defp handle_editor_action("move_up", %{"id" => id}, socket) do
    case BlockEditor.move_up(socket.assigns.editing_blocks, id) do
      {:ok, new_blocks, message} ->
        {:halt, apply_mutation(socket, new_blocks, message, :reorder)}

      :noop ->
        {:halt, socket}
    end
  end

  defp handle_editor_action("move_down", %{"id" => id}, socket) do
    case BlockEditor.move_down(socket.assigns.editing_blocks, id) do
      {:ok, new_blocks, message} ->
        {:halt, apply_mutation(socket, new_blocks, message, :reorder)}

      :noop ->
        {:halt, socket}
    end
  end

  defp handle_editor_action("remove_block", %{"id" => id}, socket) do
    {:ok, new_blocks, message} = BlockEditor.remove_block(socket.assigns.editing_blocks, id)
    {:halt, apply_mutation(socket, new_blocks, message, :content)}
  end

  defp handle_editor_action("duplicate_block", %{"id" => id}, socket) do
    case BlockEditor.duplicate_block(socket.assigns.editing_blocks, id) do
      {:ok, new_blocks, message} ->
        {:halt, apply_mutation(socket, new_blocks, message, :content)}

      :noop ->
        {:halt, socket}
    end
  end

  defp handle_editor_action("add_block", %{"type" => type}, socket) do
    case BlockEditor.add_block(socket.assigns.editing_blocks, type) do
      {:ok, new_blocks, message} ->
        socket =
          socket
          |> apply_mutation(new_blocks, message, :content)
          |> assign(:editor_show_picker, false)

        {:halt, socket}

      :noop ->
        {:halt, socket}
    end
  end

  defp handle_editor_action("update_block_settings", params, socket) do
    block_id = params["block_id"]
    new_settings = params["block_settings"] || %{}

    case BlockEditor.update_settings(socket.assigns.editing_blocks, block_id, new_settings) do
      {:ok, new_blocks} ->
        {:halt, apply_mutation(socket, new_blocks, "Settings updated", :content)}

      :noop ->
        {:halt, socket}
    end
  end

  # ── Repeater actions ─────────────────────────────────────────────

  defp handle_editor_action(
         "repeater_add",
         %{"block-id" => block_id, "field" => field_key},
         socket
       ) do
    case BlockEditor.repeater_add(socket.assigns.editing_blocks, block_id, field_key) do
      {:ok, new_blocks, message} ->
        {:halt, apply_mutation(socket, new_blocks, message, :content)}

      :noop ->
        {:halt, socket}
    end
  end

  defp handle_editor_action(
         "repeater_remove",
         %{"block-id" => block_id, "field" => field_key, "index" => index_str},
         socket
       ) do
    index = String.to_integer(index_str)

    case BlockEditor.repeater_remove(socket.assigns.editing_blocks, block_id, field_key, index) do
      {:ok, new_blocks, message} ->
        {:halt, apply_mutation(socket, new_blocks, message, :content)}

      :noop ->
        {:halt, socket}
    end
  end

  defp handle_editor_action(
         "repeater_move",
         %{"block-id" => block_id, "field" => field_key, "index" => index_str, "dir" => dir},
         socket
       ) do
    index = String.to_integer(index_str)

    case BlockEditor.repeater_move(
           socket.assigns.editing_blocks,
           block_id,
           field_key,
           index,
           dir
         ) do
      {:ok, new_blocks, message} ->
        {:halt, apply_mutation(socket, new_blocks, message, :reorder)}

      :noop ->
        {:halt, socket}
    end
  end

  # ── UI state actions ─────────────────────────────────────────────

  defp handle_editor_action("toggle_expand", %{"id" => block_id}, socket) do
    expanded = socket.assigns.editor_expanded
    block = Enum.find(socket.assigns.editing_blocks, &(&1["id"] == block_id))
    block_name = BlockEditor.block_display_name(block)

    {new_expanded, action} =
      if MapSet.member?(expanded, block_id) do
        {MapSet.delete(expanded, block_id), "collapsed"}
      else
        {MapSet.put(expanded, block_id), "expanded"}
      end

    {:halt,
     socket
     |> assign(:editor_expanded, new_expanded)
     |> assign(:editor_live_region_message, "#{block_name} settings #{action}")}
  end

  defp handle_editor_action("toggle_sidebar", _params, socket) do
    {:halt, assign(socket, :editor_sidebar_open, !socket.assigns.editor_sidebar_open)}
  end

  defp handle_editor_action("show_picker", _params, socket) do
    {:halt,
     socket
     |> assign(:editor_show_picker, true)
     |> assign(:editor_picker_filter, "")}
  end

  defp handle_editor_action("hide_picker", _params, socket) do
    {:halt, assign(socket, :editor_show_picker, false)}
  end

  defp handle_editor_action("filter_picker", %{"value" => value}, socket) do
    {:halt, assign(socket, :editor_picker_filter, value)}
  end

  # ── Image picker actions ───────────────────────────────────────

  defp handle_editor_action(
         "show_image_picker",
         %{"block-id" => block_id, "field" => field_key},
         socket
       ) do
    images = Berrypod.Media.list_images()

    {:halt,
     socket
     |> assign(:editor_image_picker_block_id, block_id)
     |> assign(:editor_image_picker_field_key, field_key)
     |> assign(:editor_image_picker_images, images)
     |> assign(:editor_image_picker_search, "")}
  end

  defp handle_editor_action("hide_image_picker", _params, socket) do
    {:halt,
     socket
     |> assign(:editor_image_picker_block_id, nil)
     |> assign(:editor_image_picker_field_key, nil)}
  end

  defp handle_editor_action("image_picker_search", %{"value" => value}, socket) do
    {:halt, assign(socket, :editor_image_picker_search, value)}
  end

  defp handle_editor_action("pick_image", %{"image-id" => image_id}, socket) do
    block_id = socket.assigns.editor_image_picker_block_id
    field_key = socket.assigns.editor_image_picker_field_key

    case BlockEditor.update_settings(socket.assigns.editing_blocks, block_id, %{
           field_key => image_id
         }) do
      {:ok, new_blocks} ->
        socket =
          socket
          |> apply_mutation(new_blocks, "Image selected", :content)
          |> assign(:editor_image_picker_block_id, nil)
          |> assign(:editor_image_picker_field_key, nil)

        {:halt, socket}

      :noop ->
        {:halt, socket}
    end
  end

  defp handle_editor_action(
         "clear_image",
         %{"block-id" => block_id, "field" => field_key},
         socket
       ) do
    case BlockEditor.update_settings(socket.assigns.editing_blocks, block_id, %{
           field_key => ""
         }) do
      {:ok, new_blocks} ->
        {:halt, apply_mutation(socket, new_blocks, "Image cleared", :content)}

      :noop ->
        {:halt, socket}
    end
  end

  # ── Undo / redo ──────────────────────────────────────────────────

  defp handle_editor_action("undo", _params, socket) do
    case socket.assigns.editor_history do
      [prev | rest] ->
        future = [socket.assigns.editing_blocks | socket.assigns.editor_future]

        socket =
          socket
          |> assign(:editing_blocks, prev)
          |> assign(:editor_history, rest)
          |> assign(:editor_future, future)
          |> assign(:editor_dirty, true)
          |> assign(:editor_live_region_message, "Undone")
          |> reload_block_data(prev)

        {:halt, socket}

      [] ->
        {:halt, socket}
    end
  end

  defp handle_editor_action("redo", _params, socket) do
    case socket.assigns.editor_future do
      [next | rest] ->
        history = [socket.assigns.editing_blocks | socket.assigns.editor_history]

        socket =
          socket
          |> assign(:editing_blocks, next)
          |> assign(:editor_history, history)
          |> assign(:editor_future, rest)
          |> assign(:editor_dirty, true)
          |> assign(:editor_live_region_message, "Redone")
          |> reload_block_data(next)

        {:halt, socket}

      [] ->
        {:halt, socket}
    end
  end

  # ── Page actions ─────────────────────────────────────────────────

  defp handle_editor_action("save", _params, socket) do
    %{page: page, editing_blocks: blocks} = socket.assigns

    case Pages.save_page(page.slug, %{title: page.title, blocks: blocks}) do
      {:ok, _saved_page} ->
        updated_page = Pages.get_page(page.slug)

        socket =
          socket
          |> assign(:page, updated_page)
          |> assign(:editing_blocks, updated_page.blocks)
          |> assign(:editor_dirty, false)
          |> assign(:editor_history, [])
          |> assign(:editor_future, [])
          |> put_flash(:info, "Page saved")

        {:halt, socket}

      {:error, _changeset} ->
        {:halt, put_flash(socket, :error, "Failed to save page")}
    end
  end

  defp handle_editor_action("reset_defaults", _params, socket) do
    slug = socket.assigns.page.slug
    :ok = Pages.reset_page(slug)
    page = Pages.get_page(slug)

    socket =
      socket
      |> assign(:page, page)
      |> assign(:editing_blocks, page.blocks)
      |> assign(:editor_dirty, false)
      |> assign(:editor_history, [])
      |> assign(:editor_future, [])
      |> reload_block_data(page.blocks)
      |> put_flash(:info, "Page reset to defaults")

    {:halt, socket}
  end

  defp handle_editor_action("done", _params, socket) do
    path = socket.assigns.editor_current_path || "/"
    {:halt, push_navigate(socket, to: path)}
  end

  # Catch-all for unknown editor actions
  defp handle_editor_action(_action, _params, socket), do: {:halt, socket}

  # ── Private helpers ──────────────────────────────────────────────

  defp enter_edit_mode(socket) do
    page = socket.assigns.page
    allowed = BlockTypes.allowed_for(page.slug)

    socket
    |> assign(:editing, true)
    |> assign(:editing_blocks, page.blocks)
    |> assign(:editor_dirty, false)
    |> assign(:editor_history, [])
    |> assign(:editor_future, [])
    |> assign(:editor_expanded, MapSet.new())
    |> assign(:editor_show_picker, false)
    |> assign(:editor_picker_filter, "")
    |> assign(:editor_allowed_blocks, allowed)
    |> assign(:editor_live_region_message, nil)
    |> assign(:editor_sidebar_open, true)
    |> assign(:editor_image_picker_block_id, nil)
    |> assign(:editor_image_picker_field_key, nil)
    |> assign(:editor_image_picker_images, [])
    |> assign(:editor_image_picker_search, "")
  end

  defp exit_edit_mode(socket) do
    socket
    |> assign(:editing, false)
    |> assign(:editing_blocks, nil)
    |> assign(:editor_dirty, false)
    |> assign(:editor_history, [])
    |> assign(:editor_future, [])
    |> assign(:editor_expanded, MapSet.new())
    |> assign(:editor_show_picker, false)
    |> assign(:editor_picker_filter, "")
    |> assign(:editor_allowed_blocks, nil)
    |> assign(:editor_live_region_message, nil)
    |> assign(:editor_sidebar_open, true)
    |> assign(:editor_image_picker_block_id, nil)
    |> assign(:editor_image_picker_field_key, nil)
    |> assign(:editor_image_picker_images, [])
    |> assign(:editor_image_picker_search, "")
  end

  defp apply_mutation(socket, new_blocks, message, type) do
    history =
      [socket.assigns.editing_blocks | socket.assigns.editor_history] |> Enum.take(50)

    socket =
      socket
      |> assign(:editing_blocks, new_blocks)
      |> assign(:editor_history, history)
      |> assign(:editor_future, [])
      |> assign(:editor_dirty, true)
      |> assign(:editor_live_region_message, message)

    case type do
      :content -> reload_block_data(socket, new_blocks)
      :reorder -> socket
    end
  end

  defp reload_block_data(socket, blocks) do
    extra = Pages.load_block_data(blocks, socket.assigns)
    Enum.reduce(extra, socket, fn {key, value}, acc -> assign(acc, key, value) end)
  end
end
