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

package f3

import (
	"fmt"
	"slices"

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

type Path interface {
	NodeIDs() []id.NodeID
	OwnerAndProjectID() (owner, project int64)

	AppendID(id string) Path
	Ignore() Path

	generic_path.Path
	Root() Path

	Forge() Path
	SetForge() Path

	Attachments() Path
	SetAttachments() Path

	Comments() Path
	SetComments() Path

	Issues() Path
	SetIssues() Path

	Labels() Path
	SetLabels() Path

	Milestones() Path
	SetMilestones() Path

	Owners() Path
	SetOwners(owners kind.Kind) Path

	Organizations() Path
	SetOrganizations() Path

	Projects() Path
	SetProjects() Path

	PullRequests() Path
	SetPullRequests() Path

	Reactions() Path
	SetReactions() Path

	Releases() Path
	SetReleases() Path

	Repositories() Path
	SetRepositories() Path

	Reviews() Path
	SetReviews() Path

	ReviewComments() Path
	SetReviewComments() Path

	Topics() Path
	SetTopics() Path

	Users() Path
	SetUsers() Path
}

type f3path struct {
	generic_path.Implementation
}

func (o f3path) popKind(k ...kind.Kind) Path {
	firstKind := kind.Kind(o.First().(generic.NodeInterface).GetID().String())
	if !slices.Contains(k, firstKind) {
		panic(fmt.Errorf("%s expected one of %s got %s", o, k, firstKind))
	}
	return ToPath(o.RemoveFirst())
}

func (o f3path) NodeIDs() []id.NodeID {
	nodeIDs := make([]id.NodeID, 0, o.Length())
	collectID := false
	for _, node := range o.Root().All() {
		if collectID {
			nodeIDs = append(nodeIDs, node.(generic.NodeInterface).GetID())
			collectID = false
			continue
		}
		kind := kind.Kind(node.(generic.NodeInterface).GetID().String())
		fixedID, ok := containerChildFixedID[kind]
		if !ok {
			panic(fmt.Errorf("%s unexpected kind %s", o.All(), kind))
		}
		if !fixedID {
			collectID = true
		}
	}
	return nodeIDs
}

func (o f3path) OwnerAndProjectID() (owner, project int64) {
	nodeIDs := o.NodeIDs()
	return nodeIDs[0].Int64(), nodeIDs[1].Int64()
}

func (o f3path) Root() Path { return o.popKind(kind.Kind("")) }

func (o f3path) Forge() Path    { return o.popKind(KindForge) }
func (o f3path) SetForge() Path { return o.appendKind(KindForge) }

func (o f3path) Attachments() Path    { return o.popKind(KindAttachments) }
func (o f3path) SetAttachments() Path { return o.appendKind(KindAttachments) }

func (o f3path) Comments() Path    { return o.popKind(KindComments) }
func (o f3path) SetComments() Path { return o.appendKind(KindComments) }

func (o f3path) Issues() Path    { return o.popKind(KindIssues) }
func (o f3path) SetIssues() Path { return o.appendKind(KindIssues) }

func (o f3path) Labels() Path    { return o.popKind(KindLabels) }
func (o f3path) SetLabels() Path { return o.appendKind(KindLabels) }

func (o f3path) Milestones() Path    { return o.popKind(KindMilestones) }
func (o f3path) SetMilestones() Path { return o.appendKind(KindMilestones) }

func (o f3path) Organizations() Path    { return o.popKind(KindOrganizations) }
func (o f3path) SetOrganizations() Path { return o.appendKind(KindOrganizations) }

func (o f3path) Projects() Path    { return o.popKind(KindProjects) }
func (o f3path) SetProjects() Path { return o.appendKind(KindProjects) }

func (o f3path) PullRequests() Path    { return o.popKind(KindPullRequests) }
func (o f3path) SetPullRequests() Path { return o.appendKind(KindPullRequests) }

func (o f3path) Reactions() Path    { return o.popKind(KindReactions) }
func (o f3path) SetReactions() Path { return o.appendKind(KindReactions) }

func (o f3path) Releases() Path    { return o.popKind(KindReleases) }
func (o f3path) SetReleases() Path { return o.appendKind(KindReleases) }

func (o f3path) Repositories() Path    { return o.popKind(KindRepositories) }
func (o f3path) SetRepositories() Path { return o.appendKind(KindRepositories) }

func (o f3path) Reviews() Path    { return o.popKind(KindReviews) }
func (o f3path) SetReviews() Path { return o.appendKind(KindReviews) }

func (o f3path) ReviewComments() Path    { return o.popKind(KindReviewComments) }
func (o f3path) SetReviewComments() Path { return o.appendKind(KindReviewComments) }

func (o f3path) Topics() Path    { return o.popKind(KindTopics) }
func (o f3path) SetTopics() Path { return o.appendKind(KindTopics) }

func (o f3path) Users() Path    { return o.popKind(KindUsers) }
func (o f3path) SetUsers() Path { return o.appendKind(KindUsers) }

func (o f3path) Owners() Path                    { return o.popKind(KindUsers, KindOrganizations) }
func (o f3path) SetOwners(owners kind.Kind) Path { return o.appendKind(owners) }

func (o f3path) AppendID(id string) Path {
	return ToPath(o.Append(generic.NewNodeFromID(id)))
}

func (o f3path) appendKind(kind kind.Kind) Path {
	return o.AppendID(string(kind))
}

func (o f3path) Ignore() Path {
	return ToPath(o.RemoveFirst())
}

func ToPath(other generic_path.Path) Path {
	return f3path{other.(generic_path.Implementation)}
}

func NewPathFromString(pathString string) Path {
	return ToPath(generic.NewPathFromString(pathString))
}
