defmodule Berrypod.Pages.BlockEditorTest do
  use ExUnit.Case, async: true

  alias Berrypod.Pages.BlockEditor

  defp make_block(id, type, settings \\ %{}) do
    %{"id" => id, "type" => type, "settings" => settings}
  end

  defp three_blocks do
    [
      make_block("a", "hero", %{"title" => "Welcome"}),
      make_block("b", "featured_products"),
      make_block("c", "image_text", %{"title" => "About us"})
    ]
  end

  describe "move_up/2" do
    test "moves a block up one position" do
      {:ok, blocks, msg} = BlockEditor.move_up(three_blocks(), "b")

      assert [%{"id" => "b"}, %{"id" => "a"}, %{"id" => "c"}] = blocks
      assert msg =~ "moved to position"
    end

    test "returns :noop for the first block" do
      assert :noop = BlockEditor.move_up(three_blocks(), "a")
    end

    test "returns :noop for unknown id" do
      assert :noop = BlockEditor.move_up(three_blocks(), "zzz")
    end
  end

  describe "move_down/2" do
    test "moves a block down one position" do
      {:ok, blocks, msg} = BlockEditor.move_down(three_blocks(), "b")

      assert [%{"id" => "a"}, %{"id" => "c"}, %{"id" => "b"}] = blocks
      assert msg =~ "moved to position"
    end

    test "returns :noop for the last block" do
      assert :noop = BlockEditor.move_down(three_blocks(), "c")
    end
  end

  describe "remove_block/2" do
    test "removes the specified block" do
      {:ok, blocks, msg} = BlockEditor.remove_block(three_blocks(), "b")

      assert length(blocks) == 2
      refute Enum.any?(blocks, &(&1["id"] == "b"))
      assert msg =~ "removed"
    end
  end

  describe "duplicate_block/2" do
    test "inserts a copy after the original" do
      {:ok, blocks, msg} = BlockEditor.duplicate_block(three_blocks(), "a")

      assert length(blocks) == 4
      assert Enum.at(blocks, 0)["id"] == "a"
      # Copy is at index 1 with a new ID
      copy = Enum.at(blocks, 1)
      assert copy["type"] == "hero"
      assert copy["id"] != "a"
      assert copy["settings"] == %{"title" => "Welcome"}
      assert msg =~ "duplicated"
    end

    test "returns :noop for unknown id" do
      assert :noop = BlockEditor.duplicate_block(three_blocks(), "zzz")
    end
  end

  describe "add_block/2" do
    test "appends a new block of the given type" do
      {:ok, blocks, msg} = BlockEditor.add_block(three_blocks(), "hero")

      assert length(blocks) == 4
      new = List.last(blocks)
      assert new["type"] == "hero"
      assert is_binary(new["id"])
      assert msg =~ "added"
    end

    test "returns :noop for unknown block type" do
      assert :noop = BlockEditor.add_block(three_blocks(), "nonexistent_block_type")
    end
  end

  describe "update_settings/3" do
    test "merges new settings into the block" do
      {:ok, blocks} =
        BlockEditor.update_settings(three_blocks(), "a", %{"title" => "New title"})

      hero = Enum.find(blocks, &(&1["id"] == "a"))
      assert hero["settings"]["title"] == "New title"
    end

    test "returns :noop for unknown block id" do
      assert :noop = BlockEditor.update_settings(three_blocks(), "zzz", %{"title" => "Nope"})
    end
  end

  describe "block_display_name/1" do
    test "returns the block type name from registry" do
      block = make_block("a", "hero")
      assert BlockEditor.block_display_name(block) == "Hero banner"
    end

    test "returns the raw type for unknown types" do
      block = make_block("a", "weird_thing")
      assert BlockEditor.block_display_name(block) == "weird_thing"
    end

    test "handles nil" do
      assert BlockEditor.block_display_name(nil) == "Block"
    end
  end

  describe "has_settings?/1" do
    test "returns true for blocks with settings schema" do
      assert BlockEditor.has_settings?(make_block("a", "hero"))
    end

    test "returns false for blocks without settings" do
      refute BlockEditor.has_settings?(make_block("a", "category_nav"))
    end
  end

  describe "settings_with_defaults/1" do
    test "fills in default values for missing settings" do
      block = make_block("a", "hero", %{})
      result = BlockEditor.settings_with_defaults(block)

      # Hero has a title field with a default
      assert is_binary(result["title"])
    end

    test "preserves existing settings" do
      block = make_block("a", "hero", %{"title" => "Custom"})
      result = BlockEditor.settings_with_defaults(block)

      assert result["title"] == "Custom"
    end
  end

  describe "coerce_settings/2" do
    test "passes through string values" do
      schema = [%{key: "title", type: :text, default: ""}]
      result = BlockEditor.coerce_settings(%{"title" => "Hello"}, schema)
      assert result["title"] == "Hello"
    end

    test "parses number fields" do
      schema = [%{key: "columns", type: :number, default: 4}]
      result = BlockEditor.coerce_settings(%{"columns" => "3"}, schema)
      assert result["columns"] == 3
    end

    test "falls back to default for invalid numbers" do
      schema = [%{key: "columns", type: :number, default: 4}]
      result = BlockEditor.coerce_settings(%{"columns" => "abc"}, schema)
      assert result["columns"] == 4
    end
  end

  describe "parse_number/2" do
    test "parses valid integer strings" do
      assert BlockEditor.parse_number("42", 0) == 42
    end

    test "returns integer values as-is" do
      assert BlockEditor.parse_number(7, 0) == 7
    end

    test "returns default for non-numeric strings" do
      assert BlockEditor.parse_number("nope", 5) == 5
    end

    test "returns default for nil" do
      assert BlockEditor.parse_number(nil, 10) == 10
    end
  end
end
