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

package generic

import (
	"context"
	"strings"

	"code.forgejo.org/f3/gof3/v3/f3"
	"code.forgejo.org/f3/gof3/v3/path"
	"code.forgejo.org/f3/gof3/v3/util"
)

type ErrorRemapReferencesRelative error

func RemapReferences(ctx context.Context, node NodeInterface, f f3.Interface) {
	for _, reference := range f.GetReferences() {
		toPath := path.NewPath()
		collectTo := func(ctx context.Context, parent, p path.Path, node NodeInterface) {
			element := NewNode()
			mappedID := node.GetMappedID()
			if mappedID.String() == "" {
				node.Trace("mapped ID for %s is not defined", p.ReadableString())
			}
			element.SetID(mappedID)
			toPath = toPath.Append(element.(path.PathElement))
		}
		from := reference.Get()
		isRelative := !strings.HasPrefix(from, "/")
		if isRelative && !strings.HasPrefix(from, "..") {
			panic(NewError[ErrorRemapReferencesRelative]("relative references that do not start with .. are not supported '%s'", from))
		}
		current := node.GetCurrentPath().String()
		fromPath := path.PathAbsolute(NewElementNode, current, from)
		node.GetTree().Apply(ctx, fromPath, NewApplyOptions(collectTo).SetWhere(ApplyEachNode))
		to := toPath.String()
		node.Trace("from '%s' to '%s'", fromPath.ReadableString(), toPath.ReadableString())
		if isRelative {
			currentMapped := node.GetParent().GetCurrentPath().PathMappedString().Join()
			// because the mapped ID of the current node has not been allocated yet
			// and it does not matter as long as it is replaced with ..
			// it will not work at all if a relative reference does not start with ..
			currentMapped += "/PLACEHODLER"
			to = path.PathRelativeString(currentMapped, to)
		}
		node.Trace("convert reference %s => %s", reference.Get(), to)
		reference.Set(to)
	}
}

func NodeCollectReferences(ctx context.Context, node NodeInterface) []path.Path {
	pathToReferences := make(map[string]path.Path, 5)

	tree := node.GetTree()

	collect := func(ctx context.Context, parent path.Path, node NodeInterface) {
		util.MaybeTerminate(ctx)
		f := node.GetSelf().ToFormat()
		for _, reference := range f.GetReferences() {
			absoluteReference := path.PathAbsoluteString(node.GetCurrentPath().String(), reference.Get())
			if _, ok := pathToReferences[absoluteReference]; ok {
				continue
			}
			tree.ApplyAndGet(ctx, path.NewPathFromString(NewElementNode, absoluteReference), NewApplyOptions(func(ctx context.Context, parent, path path.Path, node NodeInterface) {
				pathToReferences[absoluteReference] = node.GetCurrentPath()
			}))
		}
	}

	node.Walk(ctx, path.NewPath(node.(path.PathElement)), NewWalkOptions(collect))

	references := make([]path.Path, 0, len(pathToReferences))

	for _, reference := range pathToReferences {
		tree.Debug("collect %s", reference)
		references = append(references, reference)
	}

	return references
}

func TreeCollectReferences(ctx context.Context, tree TreeInterface, p path.Path) []path.Path {
	var references []path.Path

	tree.Apply(ctx, p, NewApplyOptions(func(ctx context.Context, parent, path path.Path, node NodeInterface) {
		references = NodeCollectReferences(ctx, node)
	}))

	return references
}
