defmodule Berrypod.Pages.BlockEditor do
  @moduledoc """
  Pure functions for block list manipulation.

  Used by both the admin page editor and the live page editor sidebar.
  No side effects — takes a block list in, returns a block list out.
  """

  alias Berrypod.Pages.{BlockTypes, Defaults}

  # ── Block operations ─────────────────────────────────────────────

  def move_up(blocks, block_id) do
    idx = Enum.find_index(blocks, &(&1["id"] == block_id))

    if idx && idx > 0 do
      block = Enum.at(blocks, idx)
      name = block_display_name(block)

      new_blocks =
        blocks
        |> List.delete_at(idx)
        |> List.insert_at(idx - 1, block)

      {:ok, new_blocks, "#{name} moved to position #{idx}"}
    else
      :noop
    end
  end

  def move_down(blocks, block_id) do
    idx = Enum.find_index(blocks, &(&1["id"] == block_id))

    if idx && idx < length(blocks) - 1 do
      block = Enum.at(blocks, idx)
      name = block_display_name(block)

      new_blocks =
        blocks
        |> List.delete_at(idx)
        |> List.insert_at(idx + 1, block)

      {:ok, new_blocks, "#{name} moved to position #{idx + 2}"}
    else
      :noop
    end
  end

  def remove_block(blocks, block_id) do
    block = Enum.find(blocks, &(&1["id"] == block_id))
    name = block_display_name(block)
    new_blocks = Enum.reject(blocks, &(&1["id"] == block_id))
    {:ok, new_blocks, "#{name} removed"}
  end

  def duplicate_block(blocks, block_id) do
    idx = Enum.find_index(blocks, &(&1["id"] == block_id))

    if idx do
      original = Enum.at(blocks, idx)

      copy = %{
        "id" => Defaults.generate_block_id(),
        "type" => original["type"],
        "settings" => original["settings"] || %{}
      }

      name = block_display_name(original)
      new_blocks = List.insert_at(blocks, idx + 1, copy)
      {:ok, new_blocks, "#{name} duplicated"}
    else
      :noop
    end
  end

  def add_block(blocks, type) do
    block_def = BlockTypes.get(type)

    if block_def do
      default_settings =
        block_def
        |> Map.get(:settings_schema, [])
        |> Enum.into(%{}, fn field -> {field.key, field.default} end)

      new_block = %{
        "id" => Defaults.generate_block_id(),
        "type" => type,
        "settings" => default_settings
      }

      {:ok, blocks ++ [new_block], "#{block_def.name} added"}
    else
      :noop
    end
  end

  def update_settings(blocks, block_id, new_settings) do
    block = Enum.find(blocks, &(&1["id"] == block_id))

    if block do
      schema =
        case BlockTypes.get(block["type"]) do
          %{settings_schema: s} -> s
          _ -> []
        end

      coerced = coerce_settings(new_settings, schema)

      new_blocks =
        Enum.map(blocks, fn b ->
          if b["id"] == block_id do
            Map.put(b, "settings", Map.merge(b["settings"] || %{}, coerced))
          else
            b
          end
        end)

      {:ok, new_blocks}
    else
      :noop
    end
  end

  # ── Repeater operations ──────────────────────────────────────────

  def repeater_add(blocks, block_id, field_key) do
    block = Enum.find(blocks, &(&1["id"] == block_id))

    if block do
      schema = BlockTypes.get(block["type"])
      field = Enum.find(schema.settings_schema, &(&1.key == field_key))
      empty_item = Map.new(field.item_schema, fn f -> {f.key, f.default} end)

      items = (block["settings"] || %{})[field_key] || []
      new_items = items ++ [empty_item]
      new_blocks = update_block_field(blocks, block_id, field_key, new_items)
      {:ok, new_blocks, "Item added"}
    else
      :noop
    end
  end

  def repeater_remove(blocks, block_id, field_key, index) do
    block = Enum.find(blocks, &(&1["id"] == block_id))

    if block do
      items = (block["settings"] || %{})[field_key] || []
      new_items = List.delete_at(items, index)
      new_blocks = update_block_field(blocks, block_id, field_key, new_items)
      {:ok, new_blocks, "Item removed"}
    else
      :noop
    end
  end

  def repeater_move(blocks, block_id, field_key, index, direction) do
    block = Enum.find(blocks, &(&1["id"] == block_id))

    if block do
      items = (block["settings"] || %{})[field_key] || []
      target = if direction == "up", do: index - 1, else: index + 1

      if target >= 0 and target < length(items) do
        item = Enum.at(items, index)

        new_items =
          items
          |> List.delete_at(index)
          |> List.insert_at(target, item)

        new_blocks = update_block_field(blocks, block_id, field_key, new_items)
        {:ok, new_blocks, "Item moved #{direction}"}
      else
        :noop
      end
    else
      :noop
    end
  end

  # ── Helpers ──────────────────────────────────────────────────────

  def block_display_name(nil), do: "Block"

  def block_display_name(block) do
    case BlockTypes.get(block["type"]) do
      %{name: name} -> name
      _ -> block["type"]
    end
  end

  def has_settings?(block) do
    case BlockTypes.get(block["type"]) do
      %{settings_schema: [_ | _]} -> true
      _ -> false
    end
  end

  def settings_with_defaults(block) do
    schema =
      case BlockTypes.get(block["type"]) do
        %{settings_schema: s} -> s
        _ -> []
      end

    defaults = Map.new(schema, fn field -> {field.key, field.default} end)
    Map.merge(defaults, block["settings"] || %{})
  end

  def coerce_settings(params, schema) do
    type_map = Map.new(schema, fn field -> {field.key, field} end)

    Map.new(params, fn {key, value} ->
      case type_map[key] do
        %{type: :number, default: default} ->
          {key, parse_number(value, default)}

        %{type: :repeater} ->
          {key, indexed_map_to_list(value)}

        _ ->
          {key, value}
      end
    end)
  end

  def parse_number(value, default) when is_binary(value) do
    case Integer.parse(value) do
      {n, ""} -> n
      _ -> default
    end
  end

  def parse_number(value, _default) when is_integer(value), do: value
  def parse_number(_value, default), do: default

  defp update_block_field(blocks, block_id, field_key, new_value) do
    Enum.map(blocks, fn b ->
      if b["id"] == block_id do
        settings = Map.put(b["settings"] || %{}, field_key, new_value)
        Map.put(b, "settings", settings)
      else
        b
      end
    end)
  end

  defp indexed_map_to_list(map) when is_map(map) do
    map
    |> Enum.sort_by(fn {k, _v} -> String.to_integer(k) end)
    |> Enum.map(fn {_k, v} -> v end)
  end

  defp indexed_map_to_list(value), do: value
end
