// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1

package plans

import (
	"fmt"

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

	"github.com/hashicorp/terraform/internal/addrs"
	"github.com/hashicorp/terraform/internal/configs"
	"github.com/hashicorp/terraform/internal/lang/marks"
	"github.com/hashicorp/terraform/internal/providers"
	"github.com/hashicorp/terraform/internal/tfdiags"
)

type ActionInvocationInstance struct {
	Addr addrs.AbsActionInstance

	ActionTrigger ActionTrigger

	// Provider is the address of the provider configuration that was used
	// to plan this action, and thus the configuration that must also be
	// used to apply it.
	ProviderAddr addrs.AbsProviderConfig

	ConfigValue cty.Value
}

func (ai *ActionInvocationInstance) Equals(other *ActionInvocationInstance) bool {
	// Since the trigger can be the same if it's a CLI invocation we also compare the action addr
	return ai.Addr.Equal(other.Addr) && ai.ActionTrigger.Equals(other.ActionTrigger)
}

type ActionTrigger interface {
	actionTriggerSigil()

	TriggerEvent() configs.ActionTriggerEvent

	String() string

	Equals(to ActionTrigger) bool

	Less(other ActionTrigger) bool
}

var (
	_ ActionTrigger = (*ResourceActionTrigger)(nil)
	_ ActionTrigger = (*InvokeActionTrigger)(nil)
)

// ResourceActionTrigger contains the action trigger configuration from the resource.
type ResourceActionTrigger struct {
	TriggeringResourceAddr addrs.AbsResourceInstance
	// Information about the trigger
	// The event that triggered this action invocation.
	ActionTriggerEvent configs.ActionTriggerEvent
	// The index of the action_trigger block that triggered this invocation.
	ActionTriggerBlockIndex int
	// The index of the action in the events list of the action_trigger block
	ActionsListIndex int
}

func (t *ResourceActionTrigger) TriggerEvent() configs.ActionTriggerEvent {
	return t.ActionTriggerEvent
}

func (t *ResourceActionTrigger) actionTriggerSigil() {}

func (t *ResourceActionTrigger) String() string {
	return t.TriggeringResourceAddr.String()
}

func (t *ResourceActionTrigger) Equals(other ActionTrigger) bool {
	o, ok := other.(*ResourceActionTrigger)
	if !ok {
		return false
	}

	return t.TriggeringResourceAddr.Equal(o.TriggeringResourceAddr) &&
		t.ActionTriggerBlockIndex == o.ActionTriggerBlockIndex &&
		t.ActionsListIndex == o.ActionsListIndex &&
		t.ActionTriggerEvent == o.ActionTriggerEvent
}

func (t *ResourceActionTrigger) Less(other ActionTrigger) bool {
	o, ok := other.(*ResourceActionTrigger)
	if !ok {
		return false // We always want to show non-lifecycle actions first
	}

	return t.TriggeringResourceAddr.Less(o.TriggeringResourceAddr) ||
		(t.TriggeringResourceAddr.Equal(o.TriggeringResourceAddr) &&
			t.ActionTriggerBlockIndex < o.ActionTriggerBlockIndex) ||
		(t.TriggeringResourceAddr.Equal(o.TriggeringResourceAddr) &&
			t.ActionTriggerBlockIndex == o.ActionTriggerBlockIndex &&
			t.ActionsListIndex < o.ActionsListIndex &&
			t.ActionTriggerEvent < o.ActionTriggerEvent)
}

// InvokeActionTrigger contains the configuration for an action triggered
// (invoked) directly via CLI command.
type InvokeActionTrigger struct{}

func (t *InvokeActionTrigger) actionTriggerSigil() {}

func (t *InvokeActionTrigger) String() string {
	return "CLI"
}

func (t *InvokeActionTrigger) TriggerEvent() configs.ActionTriggerEvent {
	return configs.Invoke
}

func (t *InvokeActionTrigger) Equals(other ActionTrigger) bool {
	_, ok := other.(*InvokeActionTrigger)
	if !ok {
		return false
	}

	return true // InvokeActionTriggers are always considered equal
}

func (t *InvokeActionTrigger) Less(other ActionTrigger) bool {
	// always return true, actions that are equal are already ordered by
	// address externally. these actions should go first anyway.
	return true
}

// Encode produces a variant of the receiver that has its change values
// serialized so it can be written to a plan file. Pass the implied type of the
// corresponding resource type schema for correct operation.
func (ai *ActionInvocationInstance) Encode(schema *providers.ActionSchema) (*ActionInvocationInstanceSrc, error) {

	ret := &ActionInvocationInstanceSrc{
		Addr:          ai.Addr,
		ActionTrigger: ai.ActionTrigger,
		ProviderAddr:  ai.ProviderAddr,
	}

	if ai.ConfigValue != cty.NilVal {
		ty := cty.DynamicPseudoType
		if schema != nil {
			ty = schema.ConfigSchema.ImpliedType()
		}

		unmarkedConfigValue, pvms := ai.ConfigValue.UnmarkDeepWithPaths()
		sensitivePaths, otherMarks := marks.PathsWithMark(pvms, marks.Sensitive)
		if len(otherMarks) > 0 {
			return nil, fmt.Errorf("%s: error serializing action invocation with unexpected marks on config value: %#v. This is a bug in Terraform.", tfdiags.FormatCtyPath(otherMarks[0].Path), otherMarks[0].Marks)
		}

		var err error
		ret.ConfigValue, err = NewDynamicValue(unmarkedConfigValue, ty)
		ret.SensitiveConfigPaths = sensitivePaths
		if err != nil {
			return nil, err
		}
	}

	return ret, nil

}

type ActionInvocationInstances []*ActionInvocationInstance

func (ais ActionInvocationInstances) DeepCopy() ActionInvocationInstances {
	if ais == nil {
		return ais
	}

	ret := make(ActionInvocationInstances, len(ais))
	for i, ai := range ais {
		ret[i] = ai.DeepCopy()
	}
	return ret
}

func (ai *ActionInvocationInstance) DeepCopy() *ActionInvocationInstance {
	if ai == nil {
		return ai
	}

	ret := *ai
	return &ret
}
