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

package workdir

import (
	"encoding/json"
	"errors"
	"fmt"

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

	"github.com/hashicorp/terraform/internal/configs/configschema"
	"github.com/hashicorp/terraform/internal/plans"
)

var (
	_ ConfigState                     = &BackendConfigState{}
	_ DeepCopier[BackendConfigState]  = &BackendConfigState{}
	_ PlanDataProvider[plans.Backend] = &BackendConfigState{}
)

// BackendConfigState describes the physical storage format for the backend state
// in a working directory, and provides the lowest-level API for decoding it.
type BackendConfigState struct {
	Type      string          `json:"type"`   // Backend type
	ConfigRaw json.RawMessage `json:"config"` // Backend raw config
	Hash      uint64          `json:"hash"`   // Hash of portion of configuration from config files
}

// Empty returns true if there is no active backend.
//
// In practice this typically means that the working directory is using the
// implied local backend, but that decision is made by the caller.
func (s *BackendConfigState) Empty() bool {
	return s == nil || s.Type == ""
}

// Config decodes the type-specific configuration object using the provided
// schema and returns the result as a cty.Value.
//
// An error is returned if the stored configuration does not conform to the
// given schema, or is otherwise invalid.
func (s *BackendConfigState) Config(schema *configschema.Block) (cty.Value, error) {
	ty := schema.ImpliedType()
	if s == nil {
		return cty.NullVal(ty), nil
	}
	return ctyjson.Unmarshal(s.ConfigRaw, ty)
}

// SetConfig replaces (in-place) the type-specific configuration object using
// the provided value and associated schema.
//
// An error is returned if the given value does not conform to the implied
// type of the schema.
func (s *BackendConfigState) SetConfig(val cty.Value, schema *configschema.Block) error {
	if s == nil {
		return errors.New("SetConfig called on nil BackendConfigState receiver")
	}
	ty := schema.ImpliedType()
	buf, err := ctyjson.Marshal(val, ty)
	if err != nil {
		return err
	}
	s.ConfigRaw = buf
	return nil
}

// PlanData produces an alternative representation of the receiver that is
// suitable for storing in a plan. The current workspace must additionally
// be provided, to be stored alongside the backend configuration.
//
// The backend configuration schema is required in order to properly
// encode the backend-specific configuration settings.
//
// As backends are not implemented by providers, the provider schema argument should always be nil
func (s *BackendConfigState) PlanData(schema *configschema.Block, _ *configschema.Block, workspaceName string) (*plans.Backend, error) {
	if s == nil {
		return nil, nil
	}

	configVal, err := s.Config(schema)
	if err != nil {
		return nil, fmt.Errorf("failed to decode backend config: %w", err)
	}
	return plans.NewBackend(s.Type, configVal, schema, workspaceName)
}

func (s *BackendConfigState) DeepCopy() *BackendConfigState {
	if s == nil {
		return nil
	}
	ret := &BackendConfigState{
		Type: s.Type,
		Hash: s.Hash,
	}

	if s.ConfigRaw != nil {
		ret.ConfigRaw = make([]byte, len(s.ConfigRaw))
		copy(ret.ConfigRaw, s.ConfigRaw)
	}
	return ret
}
