// Copyright F3 Authors
// SPDX-License-Identifier: MIT

package generic

import (
	"context"
	"fmt"

	"code.forgejo.org/f3/gof3/v3/f3"
	"code.forgejo.org/f3/gof3/v3/kind"
	"code.forgejo.org/f3/gof3/v3/logger"
	"code.forgejo.org/f3/gof3/v3/options"
)

type FactoryFun func(ctx context.Context, kind kind.Kind) NodeInterface

type kindMap map[kind.Kind]FactoryFun

type Tree struct {
	logger.Logger

	opts   options.Interface
	driver TreeDriverInterface
	root   NodeInterface
	kind   kindMap
}

func NewTree(opts options.Interface) TreeInterface {
	tree := &Tree{}
	tree.Init(opts)
	return tree
}

func (o *Tree) Init(opts options.Interface) {
	o.kind = make(kindMap)
	o.SetDriver(NewNullTreeDriver())
	o.SetOptions(opts)
	o.SetLogger(opts.(options.LoggerInterface).GetLogger())
}

func (o *Tree) GetOptions() options.Interface     { return o.opts }
func (o *Tree) SetOptions(opts options.Interface) { o.opts = opts }

func (o *Tree) GetRoot() NodeInterface     { return o.root }
func (o *Tree) SetRoot(root NodeInterface) { o.root = root }

func TreeSetDriver(tree TreeInterface, driver TreeDriverInterface) {
	driver.SetTree(tree)
	tree.SetDriver(driver)
}

func (o *Tree) GetRenameMap() TreeRenameMap          { return o.GetDriver().GetRenameMap() }
func (o *Tree) SetRenameMap(renameMap TreeRenameMap) { o.GetDriver().SetRenameMap(renameMap) }

func (o *Tree) GetDriver() TreeDriverInterface       { return o.driver }
func (o *Tree) SetDriver(driver TreeDriverInterface) { o.driver = driver }

func (o *Tree) GetChildrenKind(parentKind kind.Kind) kind.Kind {
	return kind.KindNil
}

func (o *Tree) GetPageSize() int { return o.GetDriver().GetPageSize() }

func (o *Tree) AllocateID() bool { return o.GetDriver().AllocateID() }

func (o *Tree) Walk(ctx context.Context, options *WalkOptions) {
	Walk(ctx, o.GetRoot(), NewPath(), options)
}

func (o *Tree) WalkAndGet(ctx context.Context, options *WalkOptions) {
	WalkAndGet(ctx, o.GetRoot(), NewPath(), options)
}

func TreeClear(ctx context.Context, self TreeInterface) {
	rootKind := self.GetRoot().GetKind()
	self.SetRoot(NodeFactory(ctx, self, rootKind))
}

func (o *Tree) Exists(ctx context.Context, path Path) bool {
	return o.Find(path) != NilNode
}

func (o *Tree) MustFind(path Path) NodeInterface {
	return MustFind(o.GetRoot(), path.RemoveFirst())
}

func (o *Tree) Find(path Path) NodeInterface {
	return Find(o.GetRoot(), path.RemoveFirst())
}

func (o *Tree) FindByIDOrName(ctx context.Context, path Path) NodeInterface {
	return FindByIDOrName(ctx, o.GetRoot(), path.RemoveFirst())
}

func (o *Tree) FindAndGet(ctx context.Context, path Path) NodeInterface {
	return FindAndGet(ctx, o.GetRoot(), path.RemoveFirst())
}

func (o *Tree) Diff(a, b NodeInterface) string {
	return o.GetDriver().Diff(a.GetDriver(), b.GetDriver())
}

func (o *Tree) Apply(ctx context.Context, p Path, options *ApplyOptions) bool {
	if p.Empty() {
		return true
	}
	return Apply(ctx, o.GetRoot(), NewPath(), p.RemoveFirst(), options)
}

func (o *Tree) ApplyAndGet(ctx context.Context, path Path, options *ApplyOptions) bool {
	if path.Empty() {
		return true
	}
	return ApplyAndGet(ctx, o.GetRoot(), path.RemoveFirst(), options)
}

func (o *Tree) GetFactory(kind kind.Kind) FactoryFun {
	return o.kind[kind]
}

func (o *Tree) SetFactory(kind kind.Kind, factory FactoryFun) {
	o.kind[kind] = factory
}

type SetFunc func(parent Path, node NodeInterface)

func TreeCreateChildFromPath(ctx context.Context, tree TreeInterface, pathString string, set SetFunc) {
	p := NewNodePathFromString(pathString)
	tree.Apply(ctx, p, NewApplyOptions(func(ctx context.Context, parent, _ Path, node NodeInterface) {
		child := NodeFactory(ctx, tree, tree.GetChildrenKind(node.GetKind()))
		child.SetParent(node)
		if set != nil {
			set(parent, child)
		}
		child.Upsert(ctx)
		tree.Trace("created child %v", child.GetID())
	}))
}

func NodeCreateChild(ctx context.Context, tree TreeInterface, parentPath Path, kind kind.Kind, set SetFunc) (Path, NodeInterface) {
	child := NodeFactory(ctx, tree, kind)
	child.SetParent(parentPath.Last())
	childPath := parentPath.Append(child)
	if set != nil {
		set(parentPath, child)
	}
	child.Upsert(ctx)
	tree.Trace("created child %v", child.GetID())
	return childPath, child
}

func NodeFactory(ctx context.Context, self TreeInterface, kind kind.Kind) NodeInterface {
	var node NodeInterface
	if factory := self.GetFactory(kind); factory != nil {
		node = factory(ctx, kind)
	} else {
		node = NewNode()
	}
	node.SetIsNil(false)
	node.SetKind(kind)
	node.SetTree(self)
	if self.GetDriver() != nil {
		nodeDriver := self.GetDriver().Factory(ctx, kind)
		nodeDriver.SetTreeDriver(self.GetDriver())
		node.SetDriver(nodeDriver)
	}
	return node
}

func TreeRename(tree TreeInterface, kind kind.Kind, f f3.Interface) {
	renameMap := tree.GetRenameMap()
	if renameMap == nil {
		return
	}

	fromName := f.GetName()
	tree.Trace("kind = %s, %+v %v", kind, f, renameMap[kind])
	if fromName == "" || fromName == f.GetID() {
		return
	}

	if rename, ok := renameMap[kind]; ok {
		tree.Trace("%s: ", kind)
		if toName, ok := rename[fromName]; ok {
			tree.Trace("%s: %s is renamed to %s", kind, fromName, toName)
			f.SetName(toName)
		}
	}
}

func TreeSetRename(tree TreeInterface, fromPath, toPath Path) {
	renameMap := tree.GetRenameMap()
	if renameMap == nil {
		return
	}
	if fromPath.Length() != toPath.Length() {
		panic(fmt.Errorf("from / to path length must be the same %d != %d", fromPath.Length(), toPath.Length()))
	}
	for i := 0; i < fromPath.Length(); i++ {
		fromNode := fromPath.Get(i)
		kind := fromNode.GetKind()
		fromFormat := fromNode.ToFormat()
		if !fromFormat.HasName() {
			continue
		}
		fromName := fromFormat.GetName()
		fromID := fromFormat.GetID()
		if fromID == fromName {
			// renaming only makes sense if the name is distinct from the ID
			// if they are equal it will interfere with ID remapping
			continue
		}
		toName := toPath.Get(i).GetID().String()
		if fromName == toName {
			continue
		}
		if renameMap[kind] == nil {
			renameMap[kind] = make(map[string]string, 10)
		}
		renameMap[kind][fromName] = toName
		tree.Trace("%s: %s will be renamed to %s", kind, fromName, toName)
	}
	tree.SetRenameMap(renameMap)
}
