package basicnode

import (
	"github.com/ipld/go-ipld-prime/datamodel"
	"github.com/ipld/go-ipld-prime/linking"
)

var (
	//_ datamodel.Node          = &anyNode{}
	_ datamodel.NodePrototype = Prototype__Any{}
	_ datamodel.NodeBuilder   = &anyBuilder{}
	//_ datamodel.NodeAssembler = &anyAssembler{}
)

// Note that we don't use a "var _" declaration to assert that Chooser
// implements traversal.LinkTargetNodePrototypeChooser, to keep basicnode's
// dependencies fairly light.

// Chooser implements traversal.LinkTargetNodePrototypeChooser.
//
// It can be used directly when loading links into the "any" prototype,
// or with another chooser layer on top, such as:
//
//	prototypeChooser := dagpb.AddSupportToChooser(basicnode.Chooser)
func Chooser(_ datamodel.Link, _ linking.LinkContext) (datamodel.NodePrototype, error) {
	return Prototype.Any, nil
}

// -- Node interface methods -->

// Unimplemented at present -- see "REVIEW" comment on anyNode.

// -- NodePrototype -->

type Prototype__Any struct{}

func (Prototype__Any) NewBuilder() datamodel.NodeBuilder {
	return &anyBuilder{}
}

// -- NodeBuilder -->

// anyBuilder is a builder for any kind of node.
//
// anyBuilder is a little unusual in its internal workings:
// unlike most builders, it doesn't embed the corresponding assembler,
// nor will it end up using anyNode,
// but instead embeds a builder for each of the kinds it might contain.
// This is because we want a more granular return at the end:
// if we used anyNode, and returned a pointer to just the relevant part of it,
// we'd have all the extra bytes of anyNode still reachable in GC terms
// for as long as that handle to the interior of it remains live.
type anyBuilder struct {
	// kind is set on first interaction, and used to select which builder to delegate 'Build' to!
	// As soon as it's been set to a value other than zero (being "Invalid"), all other Assign/Begin calls will fail since something is already in progress.
	// May also be set to the magic value '99', which means "i dunno, I'm just carrying another node of unknown prototype".
	kind datamodel.Kind

	// Only one of the following ends up being used...
	//  but we don't know in advance which one, so all are embeded here.
	//   This uses excessive space, but amortizes allocations, and all will be
	//    freed as soon as the builder is done.
	// Builders are only used for recursives;
	//  scalars are simple enough we just do them directly.
	// 'scalarNode' may also hold another Node of unknown prototype (possibly not even from this package),
	//  in which case this is indicated by 'kind==99'.

	mapBuilder  plainMap__Builder
	listBuilder plainList__Builder
	scalarNode  datamodel.Node
}

func (nb *anyBuilder) Reset() {
	*nb = anyBuilder{}
}

func (nb *anyBuilder) BeginMap(sizeHint int64) (datamodel.MapAssembler, error) {
	if nb.kind != datamodel.Kind_Invalid {
		panic("misuse")
	}
	nb.kind = datamodel.Kind_Map
	nb.mapBuilder.w = &plainMap{}
	return nb.mapBuilder.BeginMap(sizeHint)
}
func (nb *anyBuilder) BeginList(sizeHint int64) (datamodel.ListAssembler, error) {
	if nb.kind != datamodel.Kind_Invalid {
		panic("misuse")
	}
	nb.kind = datamodel.Kind_List
	nb.listBuilder.w = &plainList{}
	return nb.listBuilder.BeginList(sizeHint)
}
func (nb *anyBuilder) AssignNull() error {
	if nb.kind != datamodel.Kind_Invalid {
		panic("misuse")
	}
	nb.kind = datamodel.Kind_Null
	return nil
}
func (nb *anyBuilder) AssignBool(v bool) error {
	if nb.kind != datamodel.Kind_Invalid {
		panic("misuse")
	}
	nb.kind = datamodel.Kind_Bool
	nb.scalarNode = NewBool(v)
	return nil
}
func (nb *anyBuilder) AssignInt(v int64) error {
	if nb.kind != datamodel.Kind_Invalid {
		panic("misuse")
	}
	nb.kind = datamodel.Kind_Int
	nb.scalarNode = NewInt(v)
	return nil
}
func (nb *anyBuilder) AssignFloat(v float64) error {
	if nb.kind != datamodel.Kind_Invalid {
		panic("misuse")
	}
	nb.kind = datamodel.Kind_Float
	nb.scalarNode = NewFloat(v)
	return nil
}
func (nb *anyBuilder) AssignString(v string) error {
	if nb.kind != datamodel.Kind_Invalid {
		panic("misuse")
	}
	nb.kind = datamodel.Kind_String
	nb.scalarNode = NewString(v)
	return nil
}
func (nb *anyBuilder) AssignBytes(v []byte) error {
	if nb.kind != datamodel.Kind_Invalid {
		panic("misuse")
	}
	nb.kind = datamodel.Kind_Bytes
	nb.scalarNode = NewBytes(v)
	return nil
}
func (nb *anyBuilder) AssignLink(v datamodel.Link) error {
	if nb.kind != datamodel.Kind_Invalid {
		panic("misuse")
	}
	nb.kind = datamodel.Kind_Link
	nb.scalarNode = NewLink(v)
	return nil
}
func (nb *anyBuilder) AssignNode(v datamodel.Node) error {
	if nb.kind != datamodel.Kind_Invalid {
		panic("misuse")
	}
	nb.kind = 99
	nb.scalarNode = v
	return nil
}
func (anyBuilder) Prototype() datamodel.NodePrototype {
	return Prototype.Any
}

func (nb *anyBuilder) Build() datamodel.Node {
	switch nb.kind {
	case datamodel.Kind_Invalid:
		panic("misuse")
	case datamodel.Kind_Map:
		return nb.mapBuilder.Build()
	case datamodel.Kind_List:
		return nb.listBuilder.Build()
	case datamodel.Kind_Null:
		return datamodel.Null
	case datamodel.Kind_Bool:
		return nb.scalarNode
	case datamodel.Kind_Int:
		return nb.scalarNode
	case datamodel.Kind_Float:
		return nb.scalarNode
	case datamodel.Kind_String:
		return nb.scalarNode
	case datamodel.Kind_Bytes:
		return nb.scalarNode
	case datamodel.Kind_Link:
		return nb.scalarNode
	case 99:
		return nb.scalarNode
	default:
		panic("unreachable")
	}
}

// -- NodeAssembler -->

// ... oddly enough, we seem to be able to put off implementing this
//  until we also implement something that goes full-hog on amortization
//   and actually has a slab of `anyNode`.  Which so far, nothing does.
//    See "REVIEW" comment on anyNode.
// type anyAssembler struct {
// 	w *anyNode
// }
