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

package f3

import (
	"context"
	"fmt"
	"slices"

	"code.forgejo.org/f3/gof3/v3/f3"
	"code.forgejo.org/f3/gof3/v3/id"
	"code.forgejo.org/f3/gof3/v3/kind"
	"code.forgejo.org/f3/gof3/v3/path"
	"code.forgejo.org/f3/gof3/v3/tree/generic"
	"code.forgejo.org/f3/gof3/v3/util"
)

type ForgeDriverInterface interface {
	SetNative(native any)
	GetNativeID() string
	GetHelper() any
}

func ConvertToAny[T any](s ...T) []any {
	a := make([]any, 0, len(s))
	for _, e := range s {
		a = append(a, e)
	}
	return a
}

func ConvertNativeChild(ctx context.Context, tree generic.TreeInterface, parent generic.NodeInterface, kind kind.Kind, nativeChild any) generic.NodeInterface {
	child := tree.Factory(ctx, kind)
	child.SetParent(parent)
	childDriver := child.GetDriver().(ForgeDriverInterface)
	childDriver.SetNative(nativeChild)
	child.SetID(id.NewNodeID(childDriver.GetNativeID()))
	return child
}

func ConvertListed(ctx context.Context, node generic.NodeInterface, nativeChildren ...any) generic.ChildrenSlice {
	children := generic.NewChildrenSlice(len(nativeChildren))

	tree := node.GetTree()
	f3Tree := tree.(TreeInterface)
	kind := f3Tree.GetChildrenKind(node.GetKind())

	for _, nativeChild := range nativeChildren {
		children = append(children, ConvertNativeChild(ctx, tree, node, kind, nativeChild))
	}
	return children
}

func GetFirstNodeKind(node generic.NodeInterface, kind ...kind.Kind) generic.NodeInterface {
	if slices.Contains(kind, node.GetKind()) {
		return node
	}
	parent := node.GetParent()
	if parent == generic.NilNode {
		return generic.NilNode
	}
	return GetFirstNodeKind(parent, kind...)
}

func GetFirstFormat[T f3.Interface](node generic.NodeInterface) T {
	f := node.NewFormat()
	switch f.(type) {
	case T:
		return node.ToFormat().(T)
	}
	parent := node.GetParent()
	if parent == generic.NilNode {
		panic(fmt.Errorf("no parent of the desired type"))
	}
	return GetFirstFormat[T](parent)
}

func GetProject(node generic.NodeInterface) generic.NodeInterface {
	return GetFirstNodeKind(node, KindProject)
}

func GetProjectID(node generic.NodeInterface) int64 {
	return util.ParseInt(GetProject(node).GetID().String())
}

func GetProjectName(node generic.NodeInterface) string {
	return GetProject(node).ToFormat().(*f3.Project).Name
}

func GetOwner(node generic.NodeInterface) generic.NodeInterface {
	return GetFirstNodeKind(node, KindUser, KindOrganization)
}

func GetOwnerID(node generic.NodeInterface) int64 {
	return GetOwner(node).GetID().Int64()
}

func GetReactionable(node generic.NodeInterface) generic.NodeInterface {
	return GetFirstNodeKind(node, KindComment, KindIssue, KindPullRequest)
}

func GetReactionableID(node generic.NodeInterface) int64 {
	return GetReactionable(node).GetID().Int64()
}

func GetAttachable(node generic.NodeInterface) generic.NodeInterface {
	return GetFirstNodeKind(node, KindComment, KindIssue, KindPullRequest, KindRelease)
}

func GetAttachableID(node generic.NodeInterface) int64 {
	return GetAttachable(node).GetID().Int64()
}

func GetCommentable(node generic.NodeInterface) generic.NodeInterface {
	return GetFirstNodeKind(node, KindIssue, KindPullRequest)
}

func GetCommentableID(node generic.NodeInterface) int64 {
	return GetCommentable(node).GetID().Int64()
}

func GetComment(node generic.NodeInterface) generic.NodeInterface {
	return GetFirstNodeKind(node, KindComment, KindReviewComment)
}

func GetCommentID(node generic.NodeInterface) int64 {
	return GetComment(node).GetID().Int64()
}

func GetPullRequest(node generic.NodeInterface) generic.NodeInterface {
	return GetFirstNodeKind(node, KindPullRequest)
}

func GetPullRequestID(node generic.NodeInterface) int64 {
	return GetPullRequest(node).GetID().Int64()
}

func GetReview(node generic.NodeInterface) generic.NodeInterface {
	return GetFirstNodeKind(node, KindReview)
}

func GetReviewID(node generic.NodeInterface) int64 {
	return GetReview(node).GetID().Int64()
}

func GetReviewComment(node generic.NodeInterface) generic.NodeInterface {
	return GetFirstNodeKind(node, KindReviewComment)
}

func GetReviewCommentID(node generic.NodeInterface) int64 {
	return GetReviewComment(node).GetID().Int64()
}

func GetOwnerName(node generic.NodeInterface) string {
	owner := GetOwner(node)
	if owner == generic.NilNode {
		panic(fmt.Errorf("no user or organization parent for %s", node))
	}
	switch f := owner.ToFormat().(type) {
	case *f3.User:
		return f.UserName
	case *f3.Organization:
		return f.Name
	default:
		panic(fmt.Errorf("unexpected type %T", owner.ToFormat()))
	}
}

func GetUsernameFromID(ctx context.Context, tree TreeInterface, id int64) string {
	var name string
	getName := func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
		name = node.ToFormat().(*f3.User).UserName
	}
	p := NewUserPath(id)
	if !tree.ApplyAndGet(ctx, p, generic.NewApplyOptions(getName)) {
		panic(fmt.Errorf("%s not found", p))
	}
	return name
}
