package gotree

import (
	"strings"
)

const (
	newLine      = "\n"
	emptySpace   = "    "
	middleItem   = "├── "
	continueItem = "│   "
	lastItem     = "╰── "
)

type (
	tree struct {
		text  string
		items []Tree
	}

	// Tree is tree interface
	Tree interface {
		Add(text string) Tree
		AddTree(tree Tree)
		Items() []Tree
		Text() string
		Print() string
	}

	printer struct {
	}

	// Printer is printer interface
	Printer interface {
		Print(Tree) string
	}
)

// New returns a new GoTree.Tree
func New(text string) Tree {
	return &tree{
		text:  text,
		items: []Tree{},
	}
}

// Add adds a node to the tree
func (t *tree) Add(text string) Tree {
	n := New(text)
	t.items = append(t.items, n)
	return n
}

// AddTree adds a tree as an item
func (t *tree) AddTree(tree Tree) {
	t.items = append(t.items, tree)
}

// Text returns the node's value
func (t *tree) Text() string {
	return t.text
}

// Items returns all items in the tree
func (t *tree) Items() []Tree {
	return t.items
}

// Print returns a visual representation of the tree
func (t *tree) Print() string {
	return newPrinter().Print(t)
}

func newPrinter() Printer {
	return &printer{}
}

// Print prints a tree to a string
func (p *printer) Print(t Tree) string {
	return t.Text() + newLine + p.printItems(t.Items(), []bool{})
}

func (p *printer) printText(text string, spaces []bool, last bool) string {
	var result strings.Builder
	for _, space := range spaces {
		if space {
			result.WriteString(emptySpace)
		} else {
			result.WriteString(continueItem)
		}
	}

	indicator := middleItem
	if last {
		indicator = lastItem
	}

	var out strings.Builder
	lines := strings.Split(text, "\n")
	for i := range lines {
		text := lines[i]
		if i == 0 {
			out.WriteString(result.String() + indicator + text + newLine)
			continue
		}
		if last {
			indicator = emptySpace
		} else {
			indicator = continueItem
		}
		out.WriteString(result.String() + indicator + text + newLine)
	}

	return out.String()
}

func (p *printer) printItems(t []Tree, spaces []bool) string {
	var result strings.Builder
	for i, f := range t {
		last := i == len(t)-1
		result.WriteString(p.printText(f.Text(), spaces, last))
		if len(f.Items()) > 0 {
			spacesChild := append(spaces, last)
			result.WriteString(p.printItems(f.Items(), spacesChild))
		}
	}
	return result.String()
}
