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

package generic

import (
	"context"
	"testing"

	"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"

	"github.com/stretchr/testify/assert"
)

type referenceFormat struct {
	f3.Common
	R *f3.Reference
}

func (o *referenceFormat) Clone() f3.Interface {
	clone := &referenceFormat{}
	*clone = *o
	return clone
}

func (o *referenceFormat) GetReferences() f3.References {
	references := o.Common.GetReferences()
	if o.R != nil {
		references = append(references, o.R)
	}
	return references
}

type referencesNodeDriver struct {
	NullDriver

	f referenceFormat
}

func (o *referencesNodeDriver) GetIDFromName(ctx context.Context, name string) id.NodeID {
	return id.NilID
}

func (o *referencesNodeDriver) Get(context.Context) bool {
	return true
}

func (o *referencesNodeDriver) NewFormat() f3.Interface {
	return &referenceFormat{}
}

func (o *referencesNodeDriver) ToFormat() f3.Interface {
	return &o.f
}

func (o *referencesNodeDriver) FromFormat(f f3.Interface) {
	o.f = *f.(*referenceFormat)
}

func newReferencesNodeDriver() NodeDriverInterface {
	return &referencesNodeDriver{}
}

type testTreeReferencesDriver struct {
	NullTreeDriver
}

func newTestTreeReferencesDriver() TreeDriverInterface {
	return &testTreeReferencesDriver{}
}

func (o *testTreeReferencesDriver) Factory(ctx context.Context, kind kind.Kind) NodeDriverInterface {
	d := newReferencesNodeDriver()
	d.SetTreeDriver(o)
	return d
}

var kindTestNodeReferences = kind.Kind("references")

type testNodeReferences struct {
	testNode
}

func newTestTreeReferences() TreeInterface {
	tree := &testTree{}
	tree.Init(tree, newTestOptions())
	tree.SetDriver(newTestTreeReferencesDriver())
	tree.Register(kindTestNodeReferences, func(ctx context.Context, kind kind.Kind) NodeInterface {
		node := &testNodeReferences{}
		return node.Init(node)
	})
	return tree
}

func TestTreeCollectReferences(t *testing.T) {
	tree := newTestTreeReferences()
	root := tree.Factory(context.Background(), kindTestNodeReferences)
	tree.SetRoot(root)

	one := tree.Factory(context.Background(), kindTestNodeReferences)
	one.FromFormat(&referenceFormat{R: f3.NewReference("/somewhere")})
	one.SetID(id.NewNodeID("one"))
	root.SetChild(one)

	// the second node that has the same reference is here to ensure
	// they are deduplicated
	two := tree.Factory(context.Background(), kindTestNodeReferences)
	two.FromFormat(&referenceFormat{R: f3.NewReference("/somewhere")})
	two.SetID(id.NewNodeID("two"))
	root.SetChild(two)

	references := TreeCollectReferences(context.Background(), tree, path.NewPathFromString(NewElementNode, "/"))
	assert.Len(t, references, 1)
	assert.EqualValues(t, "/somewhere", references[0].String())
}

func TestRemapReferences(t *testing.T) {
	tree := newTestTreeReferences()

	root := tree.Factory(context.Background(), kindTestNodeReferences)
	tree.SetRoot(root)

	one := tree.Factory(context.Background(), kindTestNodeReferences)
	one.SetID(id.NewNodeID("one"))
	one.SetMappedID(id.NewNodeID("remappedone"))
	one.SetParent(root)
	root.SetChild(one)

	two := tree.Factory(context.Background(), kindTestNodeReferences)
	two.FromFormat(&referenceFormat{R: f3.NewReference("/one")})
	two.SetID(id.NewNodeID("two"))
	two.SetMappedID(id.NewNodeID("two"))
	two.SetParent(root)
	root.SetChild(two)

	{
		f := two.ToFormat()
		RemapReferences(context.Background(), two, f)
		r := f.GetReferences()
		if assert.Len(t, r, 1) {
			assert.Equal(t, "/remappedone", r[0].Get())
		}
	}

	three := tree.Factory(context.Background(), kindTestNodeReferences)
	three.FromFormat(&referenceFormat{R: f3.NewReference("../../one")})
	three.SetID(id.NewNodeID("three"))
	three.SetMappedID(id.NewNodeID("three"))
	three.SetParent(two)
	two.SetChild(three)

	{
		f := three.ToFormat()
		RemapReferences(context.Background(), three, f)
		r := f.GetReferences()
		if assert.Len(t, r, 1) {
			assert.Equal(t, "../../remappedone", r[0].Get())
		}
	}

	assert.Panics(t, func() { RemapReferences(context.Background(), three, &referenceFormat{R: f3.NewReference("./one")}) })
}
