// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package execgraph

import (
	"github.com/zclconf/go-cty/cty"

	"github.com/opentofu/opentofu/internal/addrs"
	"github.com/opentofu/opentofu/internal/engine/internal/exec"
	"github.com/opentofu/opentofu/internal/states"
)

// ResultRef represents a result of type T that will be produced by
// some other operation that is opaque to the recipients of the result.
type ResultRef[T any] interface {
	resultPlaceholderSigil(T)
	AnyResultRef
}

// ResourceInstanceResultRef is an alias for the [ResultRef] type used when
// reporting the final result of applying changes to a resource instance
// object.
//
// We give this its own name just because this particular result type tends
// to be named in function signatures elsewhere in the system and the
// simple name is (subjectively) easier to read than the generic name.
type ResourceInstanceResultRef = ResultRef[*exec.ResourceInstanceObject]

// AnyResultRef is a type-erased [ResultRef], for data
// structures that only need to represent the relationships between results
// and not the types of those results.
type AnyResultRef interface {
	anyResultPlaceholderSigil()
}

// valueResultRef is a [ResultRef] referring to an item in the a graph's
// table of constant values.
type valueResultRef struct {
	index int
}

var _ ResultRef[cty.Value] = valueResultRef{}

// anyResultPlaceholderSigil implements ResultPlaceholder.
func (v valueResultRef) anyResultPlaceholderSigil() {}

// resultPlaceholderSigil implements ResultPlaceholder.
func (v valueResultRef) resultPlaceholderSigil(cty.Value) {}

// resourceInstAddrResultRef is a [ResultRef] referring to an item in the a
// graph's table of constant resource instance addresses.
type resourceInstAddrResultRef struct {
	index int
}

var _ ResultRef[addrs.AbsResourceInstance] = resourceInstAddrResultRef{}

// anyResultPlaceholderSigil implements ResultPlaceholder.
func (v resourceInstAddrResultRef) anyResultPlaceholderSigil() {}

// resultPlaceholderSigil implements ResultPlaceholder.
func (v resourceInstAddrResultRef) resultPlaceholderSigil(addrs.AbsResourceInstance) {}

// deposedKeyResultRef is a [ResultRef] referring to an item in the a
// graph's table of constant "deposed keys".
type deposedKeyResultRef struct {
	index int
}

var _ ResultRef[states.DeposedKey] = deposedKeyResultRef{}

// anyResultPlaceholderSigil implements ResultPlaceholder.
func (v deposedKeyResultRef) anyResultPlaceholderSigil() {}

// resultPlaceholderSigil implements ResultPlaceholder.
func (v deposedKeyResultRef) resultPlaceholderSigil(states.DeposedKey) {}

// providerInstAddrResultRef is a [ResultRef] referring to an item in the a
// graph's table of constant provider instance addresses.
type providerInstAddrResultRef struct {
	index int
}

var _ ResultRef[addrs.AbsProviderInstanceCorrect] = providerInstAddrResultRef{}

// anyResultPlaceholderSigil implements ResultPlaceholder.
func (v providerInstAddrResultRef) anyResultPlaceholderSigil() {}

// resultPlaceholderSigil implements ResultPlaceholder.
func (v providerInstAddrResultRef) resultPlaceholderSigil(addrs.AbsProviderInstanceCorrect) {}

type operationResultRef[T any] struct {
	index int
}

var _ ResultRef[struct{}] = operationResultRef[struct{}]{}
var _ anyOperationResultRef = operationResultRef[struct{}]{}

// anyResultPlaceholderSigil implements ResultPlaceholder.
func (o operationResultRef[T]) anyResultPlaceholderSigil() {}

// resultPlaceholderSigil implements ResultPlaceholder.
func (o operationResultRef[T]) resultPlaceholderSigil(T) {}

// operationResultIndex implements anyOperationResultRef.
func (o operationResultRef[T]) operationResultIndex() int {
	return o.index
}

type anyOperationResultRef interface {
	operationResultIndex() int
}

type waiterResultRef struct {
	index int
}

var _ ResultRef[struct{}] = waiterResultRef{}

// anyResultPlaceholderSigil implements ResultRef.
func (w waiterResultRef) anyResultPlaceholderSigil() {}

// resultPlaceholderSigil implements ResultRef.
func (w waiterResultRef) resultPlaceholderSigil(struct{}) {}

// NilResultRef returns a special result ref which just always produces the
// zero value of type T, without doing any other work or referring to any other
// data.
//
// This should typically only be used for types whose zero value is considered
// to be the "nil" value for the type, such as pointer types, since otherwise
// the recipient cannot distinguish it from a valid result that just happens
// to be the zero value.
func NilResultRef[T any]() ResultRef[T] {
	return nil
}

func appendIndex[E any](s *[]E, new E) int {
	idx := len(*s)
	*s = append(*s, new)
	return idx
}
