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

package jsonformat

import (
	"github.com/opentofu/opentofu/internal/command/jsonformat/computed"
	"github.com/opentofu/opentofu/internal/command/jsonformat/differ"
	"github.com/opentofu/opentofu/internal/command/jsonformat/structured"
	"github.com/opentofu/opentofu/internal/command/jsonformat/structured/attribute_path"
	"github.com/opentofu/opentofu/internal/command/jsonplan"
	"github.com/opentofu/opentofu/internal/plans"
)

func precomputeDiffs(plan Plan, mode plans.Mode) diffs {
	diffs := diffs{
		outputs: make(map[string]computed.Diff),
	}

	for _, drift := range plan.ResourceDrift {

		var relevantAttrs attribute_path.Matcher
		if mode == plans.RefreshOnlyMode {
			// For a refresh only plan, we show all the drift.
			relevantAttrs = attribute_path.AlwaysMatcher()
		} else {
			matcher := attribute_path.Empty(true)

			// Otherwise we only want to show the drift changes that are
			// relevant.
			for _, attr := range plan.RelevantAttributes {
				if len(attr.Resource) == 0 || attr.Resource == drift.Address {
					matcher = attribute_path.AppendSingle(matcher, attr.Attr)
				}
			}

			if len(matcher.Paths) > 0 {
				relevantAttrs = matcher
			}
		}

		if relevantAttrs == nil {
			// If we couldn't build a relevant attribute matcher, then we are
			// not going to show anything for this drift.
			continue
		}

		schema := plan.getSchema(drift)
		change := structured.FromJsonChange(drift.Change, relevantAttrs)
		diffs.drift = append(diffs.drift, diff{
			change: drift,
			diff:   differ.ComputeDiffForBlock(change, schema.Block),
		})
	}

	for _, change := range plan.ResourceChanges {
		schema := plan.getSchema(change)
		structuredChange := structured.FromJsonChange(change.Change, attribute_path.AlwaysMatcher())
		diffs.changes = append(diffs.changes, diff{
			change: change,
			diff:   differ.ComputeDiffForBlock(structuredChange, schema.Block),
		})
	}

	for key, output := range plan.OutputChanges {
		change := structured.FromJsonChange(output, attribute_path.AlwaysMatcher())
		diffs.outputs[key] = differ.ComputeDiffForOutput(change)
	}

	return diffs
}

type diffs struct {
	drift   []diff
	changes []diff
	outputs map[string]computed.Diff
}

func (d diffs) Empty() bool {
	for _, change := range d.changes {
		if change.diff.Action != plans.NoOp || change.Moved() {
			return false
		}
	}

	for _, output := range d.outputs {
		if output.Action != plans.NoOp {
			return false
		}
	}

	return true
}

type diff struct {
	change jsonplan.ResourceChange
	diff   computed.Diff
}

func (d diff) Moved() bool {
	return len(d.change.PreviousAddress) > 0 && d.change.PreviousAddress != d.change.Address
}

func (d diff) Importing() bool {
	return d.change.Change.Importing != nil
}
