# frozen_string_literal: true

require 'spec_helper'

describe Rambling::Trie::Nodes::Raw do
  let(:node) { described_class.new }

  describe '#new' do
    it 'is not a word' do
      expect(node).not_to be_word
    end
  end

  it_behaves_like 'a trie node implementation' do
    def add_word_to_tree word
      add_word node, word
    end

    def add_words_to_tree words
      add_words node, words
    end

    def assign_letter letter
      node.letter = letter
    end
  end

  describe '#compressed?' do
    it 'returns false' do
      expect(node).not_to be_compressed
    end
  end

  describe '#add' do
    context 'when the node has no parent' do
      before { node.parent = nil }

      context 'when adding an empty string' do
        before { add_word node, '' }

        it 'does not add new children' do
          expect(node.children.size).to eq 0
        end

        it 'does not mark itself as terminal' do
          expect(node).not_to be_terminal
        end

        it 'returns the node' do
          expect(add_word(node, '')).to eq node
        end
      end
    end

    context 'when the node has no branches' do
      before { add_word node, 'abc' }

      it 'adds only one child' do
        expect(node.children.size).to eq 1
      end

      # rubocop:disable RSpec/MultipleExpectations
      it 'adds the full subtree' do
        expect(node[:a]).not_to be_nil
        expect(node[:a][:b]).not_to be_nil
        expect(node[:a][:b][:c]).not_to be_nil
      end
      # rubocop:enable RSpec/MultipleExpectations

      # rubocop:disable RSpec/MultipleExpectations
      it 'marks only the last child as terminal' do
        expect(node).not_to be_terminal
        expect(node[:a]).not_to be_terminal
        expect(node[:a][:b]).not_to be_terminal
        expect(node[:a][:b][:c]).to be_terminal
      end
      # rubocop:enable RSpec/MultipleExpectations
    end

    context 'when a word is added more than once' do
      before do
        add_word node, 'ack'
        add_word node, 'ack'
      end

      # rubocop:disable RSpec/MultipleExpectations
      it 'only counts it once' do
        expect(node.children.size).to eq 1
        expect(node[:a].children.size).to eq 1
        expect(node[:a][:c].children.size).to eq 1
        expect(node[:a][:c][:k].children.size).to eq 0
      end
      # rubocop:enable RSpec/MultipleExpectations

      # rubocop:disable RSpec/MultipleExpectations
      it 'does not change the terminal nodes in the tree' do
        expect(node).not_to be_terminal
        expect(node[:a]).not_to be_terminal
        expect(node[:a][:c]).not_to be_terminal
        expect(node[:a][:c][:k]).to be_terminal
      end
      # rubocop:enable RSpec/MultipleExpectations

      it 'still returns the "added" node' do
        child = add_word node, 'ack'
        expect(child.letter).to eq :a
      end
    end

    context 'when the word does not exist in the tree but the letters do' do
      before { add_words node, %w(ack a) }

      it 'does not add another branch' do
        expect(node.children.size).to eq 1
      end

      # rubocop:disable RSpec/MultipleExpectations
      it 'marks the corresponding node as terminal' do
        expect(node).not_to be_terminal
        expect(node[:a]).to be_terminal
        expect(node[:a][:c]).not_to be_terminal
        expect(node[:a][:c][:k]).to be_terminal
      end
      # rubocop:enable RSpec/MultipleExpectations

      it 'returns the added node' do
        child = add_word node, 'a'
        expect(child.letter).to eq :a
      end
    end

    context 'when the node has a letter and a parent' do
      let(:parent) { described_class.new }
      let(:node) { described_class.new :a, parent }

      context 'when adding an empty string' do
        before { add_word node, '' }

        it 'does not alter the node letter' do
          expect(node.letter).to eq :a
        end

        it 'does not change the node children' do
          expect(node.children.size).to eq 0
        end

        it 'changes the node to terminal' do
          expect(node).to be_terminal
        end

        it 'returns the node' do
          expect(add_word(node, '')).to eq node
        end
      end

      context 'when adding a one letter word' do
        before { add_word node, 'b' }

        it 'does not alter the node letter' do
          expect(node.letter).to eq :a
        end

        # rubocop:disable RSpec/MultipleExpectations
        it 'adds a child with the expected letter' do
          expect(node.children.size).to eq 1
          expect(node.children.first.letter).to eq :b
        end
        # rubocop:enable RSpec/MultipleExpectations

        it 'reports it has the expected letter a key' do
          expect(node).to have_key(:b)
        end

        it 'returns the child corresponding to the key' do
          expect(node[:b]).to eq node.children_tree[:b]
        end

        it 'does not mark itself as terminal' do
          expect(node).not_to be_terminal
        end

        it 'marks the first child as terminal' do
          expect(node[:b]).to be_terminal
        end
      end

      context 'when adding a large word' do
        before { add_word node, 'mplified' }

        it 'marks the last letter as terminal' do
          expect(node[:m][:p][:l][:i][:f][:i][:e][:d]).to be_terminal
        end

        # rubocop:disable RSpec/ExampleLength, RSpec/MultipleExpectations
        it 'does not mark any other letter as terminal' do
          expect(node[:m][:p][:l][:i][:f][:i][:e]).not_to be_terminal
          expect(node[:m][:p][:l][:i][:f][:i]).not_to be_terminal
          expect(node[:m][:p][:l][:i][:f]).not_to be_terminal
          expect(node[:m][:p][:l][:i]).not_to be_terminal
          expect(node[:m][:p][:l]).not_to be_terminal
          expect(node[:m][:p]).not_to be_terminal
          expect(node[:m]).not_to be_terminal
        end
        # rubocop:enable RSpec/ExampleLength, RSpec/MultipleExpectations
      end
    end
  end
end
