// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT

package f3

import (
	"context"
	"fmt"

	"code.forgejo.org/f3/gof3/v3/f3"
	"code.forgejo.org/f3/gof3/v3/kind"
	"code.forgejo.org/f3/gof3/v3/options"
	"code.forgejo.org/f3/gof3/v3/path"
	objects_helper "code.forgejo.org/f3/gof3/v3/tree/f3/objects"
	"code.forgejo.org/f3/gof3/v3/tree/generic"
)

type treeF3 struct {
	generic.Tree

	options       options.Interface
	objectsHelper objects_helper.Interface
}

type setFunc func(parent path.Path, node generic.NodeInterface)

type TreeInterface interface {
	generic.TreeInterface

	IsContainer(kind.Kind) bool
	NewFormat(kind kind.Kind) f3.Interface
	CreateChild(ctx context.Context, pathString string, set setFunc)
	GetObjectsHelper() objects_helper.Interface
}

func (o *treeF3) GetChildrenKind(parentKind kind.Kind) kind.Kind {
	if childrenKind, ok := childrenKind[parentKind]; ok {
		return childrenKind
	}
	panic(fmt.Errorf("unexpected kind %s", parentKind))
}

func (o *treeF3) IsContainer(kind kind.Kind) bool {
	if isContainer, ok := isContainer[kind]; ok {
		return isContainer
	}
	return false
}

func (o *treeF3) NewFormat(kind kind.Kind) f3.Interface {
	return f3.New(string(kind))
}

func (o *treeF3) GetObjectsHelper() objects_helper.Interface {
	return o.objectsHelper
}

func (o *treeF3) CreateChild(ctx context.Context, pathString string, set setFunc) {
	p := generic.NewPathFromString(pathString)
	o.Apply(ctx, p, generic.NewApplyOptions(func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
		o.Trace("%s %s | %T %s", parent.String(), node.GetID(), node, node.GetKind())
		child := o.Factory(ctx, o.GetChildrenKind(node.GetKind()))
		child.SetParent(node)
		childParentPath := parent.Append(node)
		if set != nil {
			set(childParentPath, child)
		}
		child.Upsert(ctx)
		child.List(ctx)
	}))
}

func newTreeF3(ctx context.Context, opts options.Interface) generic.TreeInterface {
	tree := &treeF3{}
	tree.Init(tree, opts)

	tree.objectsHelper = objects_helper.NewObjectsHelper()

	tree.SetDriver(GetForgeFactory(opts.GetName())(tree, opts))

	tree.Register(kind.KindRoot, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
		return newFixedChildrenNode(ctx, tree, []kind.Kind{KindForge})
	})
	tree.Register(KindForge, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
		return newFixedChildrenNode(ctx, tree, []kind.Kind{KindTopics, KindUsers, KindOrganizations})
	})
	tree.Register(KindUser, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
		return newFixedChildrenNode(ctx, tree, []kind.Kind{KindProjects})
	})
	tree.Register(KindOrganization, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
		return newFixedChildrenNode(ctx, tree, []kind.Kind{KindProjects})
	})
	tree.Register(KindProject, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
		return newFixedChildrenNode(ctx, tree, []kind.Kind{KindRepositories, KindLabels, KindMilestones, KindIssues, KindPullRequests, KindReleases})
	})
	tree.Register(KindIssue, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
		return newFixedChildrenNode(ctx, tree, []kind.Kind{KindComments, KindReactions, KindAttachments})
	})
	tree.Register(KindRepository, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
		return newRepositoryNode(ctx, tree)
	})
	tree.Register(KindPullRequest, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
		return newFixedChildrenNode(ctx, tree, []kind.Kind{KindComments, KindReactions, KindReviews, KindAttachments})
	})
	tree.Register(KindReview, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
		return newFixedChildrenNode(ctx, tree, []kind.Kind{KindReviewComments, KindReactions})
	})
	tree.Register(KindComment, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
		return newFixedChildrenNode(ctx, tree, []kind.Kind{KindReactions, KindAttachments})
	})
	tree.Register(KindRelease, func(ctx context.Context, k kind.Kind) generic.NodeInterface {
		return newFixedChildrenNode(ctx, tree, []kind.Kind{KindAttachments})
	})

	root := tree.Factory(ctx, kind.KindRoot)
	tree.SetRoot(root)

	return tree
}

func init() {
	generic.RegisterFactory("f3", newTreeF3)
}
