defmodule Livebook.NotebookTest do
  use ExUnit.Case, async: true

  alias Livebook.Notebook
  alias Livebook.Notebook.{Section, Cell}

  describe "fetch_cell_sibling/3" do
    test "returns error given invalid cell id" do
      notebook = Notebook.new()

      assert :error == Notebook.fetch_cell_sibling(notebook, "1", 0)
    end

    test "returns sibling cell if there is one at the given offset" do
      cell1 = Cell.new(:markdown)
      cell2 = Cell.new(:markdown)
      cell3 = Cell.new(:markdown)
      cell4 = Cell.new(:markdown)

      notebook = %{
        Notebook.new()
        | sections: [
            %{Section.new() | cells: [cell1, cell2, cell3, cell4]}
          ]
      }

      assert {:ok, cell1} == Notebook.fetch_cell_sibling(notebook, cell2.id, -1)
      assert {:ok, cell3} == Notebook.fetch_cell_sibling(notebook, cell2.id, 1)
      assert {:ok, cell4} == Notebook.fetch_cell_sibling(notebook, cell2.id, 2)
    end

    test "returns error if the offset is out of range" do
      cell1 = Cell.new(:markdown)
      cell2 = Cell.new(:markdown)

      notebook = %{
        Notebook.new()
        | sections: [
            %{Section.new() | cells: [cell1, cell2]}
          ]
      }

      assert :error == Notebook.fetch_cell_sibling(notebook, cell2.id, -2)
      assert :error == Notebook.fetch_cell_sibling(notebook, cell2.id, 1)
      assert :error == Notebook.fetch_cell_sibling(notebook, cell2.id, 2)
    end
  end

  describe "move_cell/3" do
    test "preserves empty sections" do
      cell1 = Cell.new(:markdown)

      notebook = %{
        Notebook.new()
        | sections: [
            %{Section.new() | cells: [cell1]},
            %{Section.new() | cells: []}
          ]
      }

      new_notebook = Notebook.move_cell(notebook, cell1.id, 1)

      assert %{sections: [%{cells: []}, %{cells: [^cell1]}]} = new_notebook
    end
  end

  describe "cell_dependency_graph/1" do
    test "computes a linear graph for regular sections" do
      notebook = %{
        Notebook.new()
        | sections: [
            %{
              Section.new()
              | id: "s1",
                cells: [
                  %{Cell.new(:markdown) | id: "c1"},
                  %{Cell.new(:elixir) | id: "c2"}
                ]
            },
            %{
              Section.new()
              | id: "s2",
                cells: [
                  %{Cell.new(:input) | id: "c3"},
                  %{Cell.new(:elixir) | id: "c4"}
                ]
            }
          ]
      }

      assert Notebook.cell_dependency_graph(notebook) == %{
               "c4" => "c3",
               "c3" => "c2",
               "c2" => "c1",
               "c1" => nil
             }
    end

    test "ignores empty sections" do
      notebook = %{
        Notebook.new()
        | sections: [
            %{Section.new() | id: "s1", cells: [%{Cell.new(:elixir) | id: "c1"}]},
            %{Section.new() | id: "s2", cells: []},
            %{Section.new() | id: "s3", cells: [%{Cell.new(:elixir) | id: "c2"}]}
          ]
      }

      assert Notebook.cell_dependency_graph(notebook) == %{
               "c2" => "c1",
               "c1" => nil
             }
    end

    test "computes a non-linear graph if there are branching sections" do
      notebook = %{
        Notebook.new()
        | sections: [
            %{
              Section.new()
              | id: "s1",
                cells: [
                  %{Cell.new(:elixir) | id: "c1"},
                  %{Cell.new(:elixir) | id: "c2"}
                ]
            },
            %{
              Section.new()
              | id: "s2",
                parent_id: "s1",
                cells: [
                  %{Cell.new(:elixir) | id: "c3"},
                  %{Cell.new(:elixir) | id: "c4"}
                ]
            },
            %{
              Section.new()
              | id: "s3",
                cells: [
                  %{Cell.new(:elixir) | id: "c5"},
                  %{Cell.new(:elixir) | id: "c6"}
                ]
            }
          ]
      }

      assert Notebook.cell_dependency_graph(notebook) == %{
               "c6" => "c5",
               "c5" => "c2",
               "c4" => "c3",
               "c3" => "c2",
               "c2" => "c1",
               "c1" => nil
             }
    end

    test "handles branching sections pointing to empty sections" do
      notebook = %{
        Notebook.new()
        | sections: [
            %{
              Section.new()
              | id: "s1",
                cells: [
                  %{Cell.new(:elixir) | id: "c1"}
                ]
            },
            %{
              Section.new()
              | id: "s2",
                cells: []
            },
            %{
              Section.new()
              | id: "s3",
                parent_id: "s2",
                cells: [
                  %{Cell.new(:elixir) | id: "c2"}
                ]
            }
          ]
      }

      assert Notebook.cell_dependency_graph(notebook) == %{
               "c2" => "c1",
               "c1" => nil
             }
    end

    test "handles branching sections placed further in the notebook" do
      notebook = %{
        Notebook.new()
        | sections: [
            %{
              Section.new()
              | id: "s1",
                cells: [
                  %{Cell.new(:elixir) | id: "c1"}
                ]
            },
            %{
              Section.new()
              | id: "s2",
                cells: [
                  %{Cell.new(:elixir) | id: "c2"}
                ]
            },
            %{
              Section.new()
              | id: "s3",
                parent_id: "s1",
                cells: [
                  %{Cell.new(:elixir) | id: "c3"}
                ]
            },
            %{
              Section.new()
              | id: "s4",
                cells: [
                  %{Cell.new(:elixir) | id: "c4"}
                ]
            }
          ]
      }

      assert Notebook.cell_dependency_graph(notebook) == %{
               "c4" => "c2",
               "c3" => "c1",
               "c2" => "c1",
               "c1" => nil
             }
    end

    test "given :cell_filter option, includes only the matching cells" do
      notebook = %{
        Notebook.new()
        | sections: [
            %{
              Section.new()
              | id: "s1",
                cells: [
                  %{Cell.new(:elixir) | id: "c1"},
                  %{Cell.new(:markdown) | id: "c2"},
                  %{Cell.new(:elixir) | id: "c3"}
                ]
            }
          ]
      }

      assert Notebook.cell_dependency_graph(notebook, cell_filter: &is_struct(&1, Cell.Elixir)) ==
               %{
                 "c3" => "c1",
                 "c1" => nil
               }
    end
  end

  describe "input_cell_for_prompt/3" do
    test "returns an error if no input matches the given prompt" do
      cell1 = Cell.new(:elixir)

      notebook = %{
        Notebook.new()
        | sections: [
            %{Section.new() | cells: [cell1]}
          ]
      }

      assert :error = Notebook.input_cell_for_prompt(notebook, cell1.id, "name")
    end

    test "returns an input field if one is matching" do
      cell1 = %{Cell.new(:input) | name: "name", value: "Jake Peralta"}
      cell2 = Cell.new(:elixir)

      notebook = %{
        Notebook.new()
        | sections: [
            %{Section.new() | cells: [cell1, cell2]}
          ]
      }

      assert {:ok, ^cell1} = Notebook.input_cell_for_prompt(notebook, cell2.id, "name")
    end

    test "returns the first input if there are many with the same name" do
      cell1 = %{Cell.new(:input) | name: "name", value: "Amy Santiago"}
      cell2 = %{Cell.new(:input) | name: "name", value: "Jake Peralta"}
      cell3 = Cell.new(:elixir)

      notebook = %{
        Notebook.new()
        | sections: [
            %{Section.new() | cells: [cell1, cell2, cell3]}
          ]
      }

      assert {:ok, ^cell2} = Notebook.input_cell_for_prompt(notebook, cell3.id, "name")
    end

    test "returns longest-prefix input if many match the prompt" do
      cell1 = %{Cell.new(:input) | name: "name", value: "Amy Santiago"}
      cell2 = %{Cell.new(:input) | name: "nam", value: "Jake Peralta"}
      cell3 = Cell.new(:elixir)

      notebook = %{
        Notebook.new()
        | sections: [
            %{Section.new() | cells: [cell1, cell2, cell3]}
          ]
      }

      assert {:ok, ^cell1} = Notebook.input_cell_for_prompt(notebook, cell3.id, "name: ")
    end
  end
end
