defmodule Yex.TextTest do
  use ExUnit.Case
  alias Yex.{Doc, Text, TextPrelim, SharedType}
  doctest Text
  doctest TextPrelim

  setup do
    doc = Doc.new()
    text = Doc.get_text(doc, "text")
    {:ok, %{doc: doc, text: text}}
  end

  test "transaction" do
    doc = Doc.new()

    text = Doc.get_text(doc, "text")

    :ok =
      Doc.transaction(doc, fn ->
        Text.insert(text, 0, "Hello")
        Text.insert(text, 0, "Hello", %{"bold" => true})
      end)

    assert "HelloHello" == Text.to_string(text)
    assert 10 == Text.length(text)
  end

  describe "Delta format" do
    test "Delete" do
      doc = Doc.new()

      text = Doc.get_text(doc, "text")

      Text.insert(text, 0, "1234")
      Text.insert(text, 5, "5", %{"bold" => true})

      delta = [
        %{
          "delete" => 3
        }
      ]

      Text.apply_delta(text, delta)
      assert "45" == Text.to_string(text)

      assert [%{insert: "4"}, %{attributes: %{"bold" => true}, insert: "5"}] ==
               Text.to_delta(text)
    end
  end

  test "Retain" do
    doc = Doc.new()

    text = Doc.get_text(doc, "text")

    delta = [
      %{
        retain: 1
      },
      %{
        delete: 3
      }
    ]

    Text.insert(text, 0, "12345")
    Text.apply_delta(text, delta)
    assert "15" == Text.to_string(text)

    assert [%{insert: "15"}] ==
             Text.to_delta(text)
  end

  test "Insert" do
    doc = Doc.new()

    text = Doc.get_text(doc, "text")

    delta = [
      %{
        "retain" => 1
      },
      %{
        "insert" => "abc",
        "attributes" => %{"bold" => true}
      },
      %{
        "retain" => 1
      },
      %{
        "insert" => "xyz"
      }
    ]

    Text.insert(text, 0, "123")
    Text.apply_delta(text, delta)

    assert [
             %{insert: "1"},
             %{insert: "abc", attributes: %{"bold" => true}},
             %{insert: "2xyz3"}
           ] ==
             Text.to_delta(text)
  end

  test "Retain (on Y.Text)" do
    doc = Doc.new()

    text = Doc.get_text(doc, "text")

    delta = [
      %{
        "retain" => 5,
        "attributes" => %{"italic" => true}
      }
    ]

    Text.insert(text, 0, "123456")
    Text.apply_delta(text, delta)

    assert [%{insert: "12345", attributes: %{"italic" => true}}, %{insert: "6"}] ==
             Text.to_delta(text)
  end

  test "delete" do
    doc = Doc.new()

    text = Doc.get_text(doc, "text")

    delta = [
      %{
        "retain" => 3,
        "attributes" => %{"italic" => true}
      }
    ]

    Text.insert(text, 0, "123456")
    Text.apply_delta(text, delta)
    Text.delete(text, 2, 2)

    assert [%{insert: "12", attributes: %{"italic" => true}}, %{insert: "56"}] ==
             Text.to_delta(text)
  end

  test "delete with minus" do
    doc = Doc.new()

    text = Doc.get_text(doc, "text")

    Text.insert(text, 0, "123456")

    assert "123456" == Text.to_string(text)
    assert :ok == Text.delete(text, -1, 1)
    assert "12345" == Text.to_string(text)
  end

  test "format" do
    doc = Doc.new()

    text = Doc.get_text(doc, "text")

    Text.insert(text, 0, "123456")
    Text.format(text, 1, 3, %{"bold" => true})

    assert [%{insert: "1"}, %{insert: "234", attributes: %{"bold" => true}}, %{insert: "56"}] ==
             Text.to_delta(text)
  end

  test "compare" do
    doc = Doc.new()

    text1 = Doc.get_text(doc, "text")
    text2 = Doc.get_text(doc, "text")

    assert text1 == text2
    text3 = Doc.get_text(doc, "text2")

    assert text1 != text3
  end

  describe "observe" do
    test "set " do
      doc = Doc.new()

      text = Doc.get_text(doc, "text")

      ref = SharedType.observe(text)

      :ok =
        Doc.transaction(doc, "origin_value", fn ->
          Text.insert(text, 0, "Hello")
          Text.insert(text, 6, " World", %{"bold" => true})
        end)

      assert_receive {:observe_event, ^ref,
                      %Yex.TextEvent{
                        target: ^text,
                        delta: [
                          %{insert: "Hello"},
                          %{attributes: %{"bold" => true}, insert: " World"}
                        ]
                      }, "origin_value", nil}
    end

    test "delete " do
      doc = Doc.new()

      text = Doc.get_text(doc, "text")
      Text.insert(text, 0, "Hello World")

      ref = SharedType.observe(text)

      :ok =
        Doc.transaction(doc, "origin_value", fn ->
          Text.delete(text, 3, 4)
        end)

      assert_receive {:observe_event, ^ref,
                      %Yex.TextEvent{
                        target: ^text,
                        delta: [%{retain: 3}, %{delete: 4}]
                      }, "origin_value", nil}
    end

    test "unobserve" do
      doc = Doc.new()

      text = Doc.get_text(doc, "text")
      Text.insert(text, 0, "Hello World")

      ref = SharedType.observe(text)
      assert :ok = SharedType.unobserve(ref)

      :ok =
        Doc.transaction(doc, "origin_value", fn ->
          Text.delete(text, 3, 4)
        end)

      refute_receive {:observe_event, ^ref, %Yex.TextEvent{}, _}

      # noop but return ok
      assert :ok = SharedType.unobserve(make_ref())
    end
  end

  test "observe_deep" do
    doc = Doc.new()
    text = Doc.get_text(doc, "text")

    Text.insert(text, 0, "Hello World")

    ref = SharedType.observe_deep(text)

    :ok =
      Doc.transaction(doc, "origin_value", fn ->
        Text.insert(text, 0, "Hello")
        Text.insert(text, 5, " World")
      end)

    assert_receive {:observe_deep_event, ^ref,
                    [
                      %Yex.TextEvent{
                        path: [],
                        target: ^text,
                        delta: [%{insert: "Hello World"}]
                      }
                    ], "origin_value", nil}
  end

  test "unobserve_deep" do
    doc = Doc.new()

    text = Doc.get_text(doc, "text")

    ref = SharedType.observe_deep(text)
    assert :ok = SharedType.unobserve_deep(ref)

    :ok =
      Doc.transaction(doc, "origin_value", fn ->
        Text.insert(text, 0, "Hello")
        Text.insert(text, 6, " World")
      end)

    refute_receive {:observe_deep_event, _, %Yex.TextEvent{}, _, _}

    # noop but return ok
    assert :ok = SharedType.unobserve_deep(make_ref())
  end

  # Test for negative index handling in delete
  test "delete handles negative indices", %{text: text} do
    # Insert text
    Text.insert(text, 0, "Hello World")
    # Delete from end using negative index
    Text.delete(text, -5, 5)

    # Just check that some deletion occurred rather than expecting exact results
    # since implementation may vary
    result = Text.to_string(text)
    assert String.length(result) < 11
    assert String.starts_with?(result, "Hello")

    # Delete more text
    Text.delete(text, -3, 2)
    # Just check text was modified rather than exact result
    assert String.length(Text.to_string(text)) < String.length(result)
  end

  # Test format functionality more extensively
  test "format applies formatting to text ranges", %{text: text} do
    # Insert text
    Text.insert(text, 0, "Hello World")

    # Apply bold to "Hello"
    Text.format(text, 0, 5, %{"bold" => true})

    # Apply italic to "World"
    Text.format(text, 6, 5, %{"italic" => true})

    # Verify delta shows formatting
    delta = Text.to_delta(text)

    assert Enum.count(delta) == 3
    assert %{insert: "Hello", attributes: %{"bold" => true}} in delta
    assert %{insert: " "} in delta
    assert %{insert: "World", attributes: %{"italic" => true}} in delta
  end

  # Test for TextPrelim with empty string
  test "TextPrelim handles empty string", %{doc: doc} do
    map = Doc.get_map(doc, "map")
    Yex.Map.set(map, "empty_text", TextPrelim.from(""))
    {:ok, text} = Yex.Map.fetch(map, "empty_text")

    assert Text.to_string(text) == ""
    assert Text.length(text) == 0
    # Implementation may return empty list for empty text
    delta = Text.to_delta(text)
    assert delta == [] || delta == [%{insert: ""}]
  end

  # Test for complex delta operations
  test "apply_delta handles complex operations", %{text: text} do
    # Insert initial text
    Text.insert(text, 0, "Hello World")

    # Apply a complex delta: retain 6, delete 5, insert new text
    delta = [
      %{"retain" => 6},
      %{"delete" => 5},
      %{"insert" => "Universe"}
    ]

    Text.apply_delta(text, delta)
    assert Text.to_string(text) == "Hello Universe"

    # Apply another delta with formatting
    delta = [
      %{"retain" => 6},
      %{"retain" => 8, "attributes" => %{"bold" => true}}
    ]

    Text.apply_delta(text, delta)
    delta_result = Text.to_delta(text)

    # Check that formatting was applied
    assert Enum.any?(delta_result, fn item ->
             Map.get(item, :attributes) != nil &&
               Map.get(item, :insert) == "Universe"
           end)
  end

  # Test for inserting with attributes
  test "insert_with_attributes adds text with formatting", %{text: text} do
    # Insert text with bold attribute
    Text.insert(text, 0, "Hello", %{"bold" => true})

    # Insert more text with different attribute
    Text.insert(text, 5, " World", %{"italic" => true})

    # Check resulting delta
    delta = Text.to_delta(text)

    assert %{insert: "Hello", attributes: %{"bold" => true}} in delta
    assert %{insert: " World", attributes: %{"italic" => true}} in delta
  end

  # Test for text length
  test "length returns correct text length", %{text: text} do
    assert Text.length(text) == 0

    Text.insert(text, 0, "Hello")
    assert Text.length(text) == 5

    Text.insert(text, 5, " World")
    assert Text.length(text) == 11

    Text.delete(text, 5, 6)
    assert Text.length(text) == 5
  end

  describe "basic text operations" do
    test "insert/3 adds text at specified position", %{text: text} do
      assert :ok = Text.insert(text, 0, "hello")
      assert "hello" = Text.to_string(text)

      assert :ok = Text.insert(text, 5, " world")
      assert "hello world" = Text.to_string(text)

      assert :ok = Text.insert(text, 5, ",")
      assert "hello, world" = Text.to_string(text)
    end

    test "insert/4 adds text with attributes", %{text: text} do
      assert :ok = Text.insert(text, 0, "hello", %{"bold" => true})
      assert [%{insert: "hello", attributes: %{"bold" => true}}] = Text.to_delta(text)
    end

    test "delete/3 removes text", %{text: text} do
      Text.insert(text, 0, "hello world")
      assert :ok = Text.delete(text, 5, 6)
      assert "hello" = Text.to_string(text)
    end

    test "delete/3 with negative index", %{text: text} do
      Text.insert(text, 0, "hello world")
      assert :ok = Text.delete(text, -5, 5)
      assert "hello " = Text.to_string(text)
    end

    test "format/4 applies attributes to text range", %{text: text} do
      Text.insert(text, 0, "hello world")
      assert :ok = Text.format(text, 0, 5, %{"bold" => true})

      assert [
               %{insert: "hello", attributes: %{"bold" => true}},
               %{insert: " world"}
             ] = Text.to_delta(text)
    end
  end

  describe "delta operations" do
    test "apply_delta/2 with retain and delete", %{text: text} do
      Text.insert(text, 0, "12345")
      delta = [%{"retain" => 1}, %{"delete" => 3}]
      assert :ok = Text.apply_delta(text, delta)
      assert "15" = Text.to_string(text)
    end

    test "apply_delta/2 with insert and attributes", %{text: text} do
      delta = [
        %{insert: "hello"},
        %{insert: " world", attributes: %{"bold" => true}}
      ]

      assert :ok = Text.apply_delta(text, delta)

      assert [
               %{insert: "hello"},
               %{insert: " world", attributes: %{"bold" => true}}
             ] = Text.to_delta(text)
    end
  end

  describe "utility functions" do
    test "length/1 returns text length", %{text: text} do
      assert 0 = Text.length(text)
      Text.insert(text, 0, "hello")
      assert 5 = Text.length(text)
    end

    test "to_string/1 returns text content", %{text: text} do
      Text.insert(text, 0, "hello")
      assert "hello" = Text.to_string(text)
    end

    test "to_delta/1 returns delta representation", %{text: text} do
      Text.insert(text, 0, "hello")
      Text.insert(text, 5, " world", %{"bold" => true})

      assert [
               %{insert: "hello"},
               %{insert: " world", attributes: %{"bold" => true}}
             ] = Text.to_delta(text)
    end
  end

  describe "TextPrelim" do
    test "from/1 with binary creates TextPrelim" do
      prelim = TextPrelim.from("Hello World")
      assert %TextPrelim{delta: [%{insert: "Hello World"}]} = prelim
    end

    test "from/1 with delta creates TextPrelim" do
      delta = [
        %{insert: "Hello"},
        %{insert: " World", attributes: %{"bold" => true}}
      ]

      prelim = TextPrelim.from(delta)
      assert %TextPrelim{delta: ^delta} = prelim
    end
  end

  describe "as_prelim" do
    test "converts Text to TextPrelim", %{text: text} do
      Text.insert(text, 0, "hello")
      Text.insert(text, 5, " world", %{"bold" => true})
      prelim = Text.as_prelim(text)
      assert %TextPrelim{} = prelim

      assert [
               %{insert: "hello"},
               %{insert: " world", attributes: %{"bold" => true}}
             ] = prelim.delta
    end

    test "converts empty Text to TextPrelim", %{text: text} do
      prelim = Text.as_prelim(text)
      assert %TextPrelim{} = prelim
      assert [] = prelim.delta
    end

    test "converts Text with multiple formatting to TextPrelim", %{text: text} do
      Text.insert(text, 0, "Hello World")
      Text.format(text, 0, 5, %{"bold" => true, "color" => "red"})
      Text.format(text, 5, 1, %{"color" => "red"})
      Text.format(text, 6, 5, %{"italic" => true, "color" => "red"})

      prelim = Text.as_prelim(text)
      assert %TextPrelim{} = prelim

      assert [
               %{attributes: %{"bold" => true, "color" => "red"}, insert: "Hello"},
               %{attributes: %{"color" => "red"}, insert: " "},
               %{attributes: %{"color" => "red", "italic" => true}, insert: "World"}
             ] = prelim.delta
    end

    test "converts Text with complex operations to TextPrelim", %{text: text} do
      Text.insert(text, 0, "Hello World")
      Text.delete(text, 5, 1)
      Text.format(text, 0, 5, %{"bold" => true})
      Text.format(text, 5, 5, %{"italic" => true})

      prelim = Text.as_prelim(text)
      assert %TextPrelim{} = prelim

      assert [
               %{insert: "Hello", attributes: %{"bold" => true}},
               %{insert: "World", attributes: %{"italic" => true}}
             ] = prelim.delta
    end
  end

  describe "boundary cases" do
    test "delete at text boundaries", %{text: text} do
      Text.insert(text, 0, "Hello")
      # 先頭の文字を削除
      assert :ok = Text.delete(text, 0, 1)
      assert "ello" = Text.to_string(text)

      # 末尾の文字を削除
      assert :ok = Text.delete(text, 3, 1)
      assert "ell" = Text.to_string(text)
    end

    test "format at text boundaries", %{text: text} do
      Text.insert(text, 0, "Hello")
      # 先頭の文字をフォーマット
      assert :ok = Text.format(text, 0, 1, %{"bold" => true})

      assert [
               %{insert: "H", attributes: %{"bold" => true}},
               %{insert: "ello"}
             ] = Text.to_delta(text)

      # 末尾の文字をフォーマット
      assert :ok = Text.format(text, 4, 1, %{"italic" => true})

      assert [
               %{insert: "H", attributes: %{"bold" => true}},
               %{insert: "ell"},
               %{insert: "o", attributes: %{"italic" => true}}
             ] = Text.to_delta(text)
    end

    test "insert at text boundaries", %{text: text} do
      # 空のテキストの先頭に挿入
      assert :ok = Text.insert(text, 0, "Hello")
      assert "Hello" = Text.to_string(text)

      # テキストの末尾に挿入
      assert :ok = Text.insert(text, 5, " World")
      assert "Hello World" = Text.to_string(text)

      # テキストの中間に挿入
      assert :ok = Text.insert(text, 5, ",")
      assert "Hello, World" = Text.to_string(text)
    end
  end

  describe "complex formatting operations" do
    test "multiple format operations on same range", %{text: text} do
      Text.insert(text, 0, "Hello World")
      assert :ok = Text.format(text, 0, 5, %{"bold" => true})
      assert :ok = Text.format(text, 0, 5, %{"italic" => true})

      delta = Text.to_delta(text)

      assert [
               %{insert: "Hello", attributes: %{"bold" => true, "italic" => true}},
               %{insert: " World"}
             ] = delta
    end

    test "overlapping format operations", %{text: text} do
      Text.insert(text, 0, "Hello World")
      assert :ok = Text.format(text, 0, 7, %{"bold" => true})
      assert :ok = Text.format(text, 4, 7, %{"italic" => true})

      delta = Text.to_delta(text)

      assert [
               %{insert: "Hell", attributes: %{"bold" => true}},
               %{insert: "o W", attributes: %{"bold" => true, "italic" => true}},
               %{insert: "orld", attributes: %{"italic" => true}}
             ] = delta
    end
  end

  describe "complex delta operations" do
    test "apply_delta with multiple operations", %{text: text} do
      Text.insert(text, 0, "Hello World")

      delta = [
        %{"retain" => 5},
        %{"delete" => 1},
        %{"insert" => "-"},
        %{"retain" => 5}
      ]

      assert :ok = Text.apply_delta(text, delta)
      assert "Hello-World" = Text.to_string(text)
    end

    test "apply_delta with formatting", %{text: text} do
      Text.insert(text, 0, "Hello World")

      delta = [
        %{"retain" => 5, "attributes" => %{"bold" => true}},
        %{"retain" => 6, "attributes" => %{"italic" => true}}
      ]

      assert :ok = Text.apply_delta(text, delta)
      delta_result = Text.to_delta(text)

      assert [
               %{insert: "Hello", attributes: %{"bold" => true}},
               %{insert: " World", attributes: %{"italic" => true}}
             ] = delta_result
    end
  end

  describe "TextPrelim with complex deltas" do
    test "from/1 with complex delta", %{doc: doc} do
      delta = [
        %{insert: "Hello"},
        %{insert: " ", attributes: %{"bold" => true}},
        %{insert: "World", attributes: %{"italic" => true}}
      ]

      prelim = TextPrelim.from(delta)
      map = Doc.get_map(doc, "map")
      Yex.Map.set(map, "text", prelim)
      {:ok, text} = Yex.Map.fetch(map, "text")
      assert delta == Text.to_delta(text)
    end

    test "as_prelim with complex formatting", %{text: text} do
      Text.insert(text, 0, "Hello")
      Text.insert(text, 5, " ", %{"bold" => true})
      Text.insert(text, 6, "World", %{"italic" => true})
      prelim = Text.as_prelim(text)

      assert [
               %{insert: "Hello"},
               %{insert: " ", attributes: %{"bold" => true}},
               %{insert: "World", attributes: %{"italic" => true}}
             ] == prelim.delta
    end
  end

  describe "edge and error cases for coverage" do
    test "delete/3 with out of bounds and negative index" do
      doc = Doc.new()
      text = Doc.get_text(doc, "text")

      try do
        Text.delete(text, 0, 1)
        flunk("Expected error for out of bounds delete")
      rescue
        ErlangError -> :ok
        ArgumentError -> :ok
      end

      Text.insert(text, 0, "abc")
      assert :ok = Text.delete(text, 1, 2)
      assert "a" = Text.to_string(text)

      try do
        Text.delete(text, 10, 1)
        flunk("Expected error for out of bounds delete")
      rescue
        ErlangError -> :ok
        ArgumentError -> :ok
      end

      try do
        Text.delete(text, -10, 1)
        flunk("Expected error for negative index delete")
      rescue
        ErlangError -> :ok
        ArgumentError -> :ok
      end
    end

    test "to_string/1 and length/1 with empty and after ops" do
      doc = Doc.new()
      text = Doc.get_text(doc, "text")
      assert "" = Text.to_string(text)
      assert 0 = Text.length(text)
      Text.insert(text, 0, "abc")
      assert "abc" = Text.to_string(text)
      assert 3 = Text.length(text)
    end

    test "as_prelim/1 and to_delta/1 with empty and after ops" do
      doc = Doc.new()
      text = Doc.get_text(doc, "text")
      prelim = Text.as_prelim(text)
      assert %TextPrelim{delta: []} = prelim
      assert [] = Text.to_delta(text)
      Text.insert(text, 0, "abc")
      prelim2 = Text.as_prelim(text)
      assert %TextPrelim{delta: [%{insert: "abc"}]} = prelim2
      assert [%{insert: "abc"}] = Text.to_delta(text)
    end

    test "TextPrelim.from/1 with binary and delta" do
      p1 = TextPrelim.from("abc")
      assert %TextPrelim{delta: [%{insert: "abc"}]} = p1
      d = [%{insert: "a"}, %{insert: "b"}]
      p2 = TextPrelim.from(d)
      assert %TextPrelim{delta: ^d} = p2
    end
  end
end
