package mapping

import (
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/hex"
	"strings"

	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordmutationv1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/envelope"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"google.golang.org/protobuf/proto"
)

// EncodeMutationFrames validates and normalizes one semantic command, encodes
// the typed inner protobuf deterministically for reproducible Go vectors, and
// splits it into bounded transport frames. The encoded digest protects this
// frame sequence only; Runtime separately computes semantic.CommandDigest for
// idempotency after decoding.
func EncodeMutationFrames(ctx context.Context, request semantic.Request) ([]*runtimev1.MutateRecordsRequest, error) {
	if err := requireRecordMutationSemanticMappingRevision(request); err != nil {
		return nil, err
	}
	if err := semantic.ValidateRequest(ctx, request); err != nil {
		return nil, err
	}
	wireCommand, err := toWireMutationCommand(semantic.NormalizeRequest(request))
	if err != nil {
		return nil, err
	}
	encoded, err := (proto.MarshalOptions{Deterministic: true}).Marshal(wireCommand)
	if err != nil {
		return nil, &semantic.Failure{Category: semantic.FailureInternal}
	}
	if len(encoded) > envelope.MaxMutationEncodedCommandBytes {
		return nil, &semantic.Failure{Category: semantic.FailureResourceExhausted, LimitKind: semantic.LimitWireFrame, Maximum: envelope.MaxMutationEncodedCommandBytes}
	}
	digest := sha256.Sum256(encoded)
	frames := []*runtimev1.MutateRecordsRequest{{Frame: &runtimev1.MutateRecordsRequest_Header{Header: &runtimev1.MutationCommandHeader{
		EncodedCommandBytes: uint64(len(encoded)), EncodedSha256: digest[:],
	}}}}
	for len(encoded) != 0 {
		n := len(encoded)
		if n > envelope.MaxMutationChunkDataBytes {
			n = envelope.MaxMutationChunkDataBytes
		}
		frames = append(frames, &runtimev1.MutateRecordsRequest{Frame: &runtimev1.MutateRecordsRequest_Chunk{
			Chunk: &runtimev1.MutationCommandChunk{Data: append([]byte(nil), encoded[:n]...)},
		}})
		encoded = encoded[n:]
	}
	return frames, nil
}

// MutationFrameDecoder is request-scoped. It accepts exactly one header first,
// bounded non-empty chunks, and no trailing or duplicate frame. Finish fails
// closed unless length, byte digest, header identity, and semantic validation
// all agree.
type MutationFrameDecoder struct {
	header *runtimev1.MutationCommandHeader
	data   []byte
	failed error
}

func (d *MutationFrameDecoder) Add(frame *runtimev1.MutateRecordsRequest) error {
	if d.failed != nil {
		return d.failed
	}
	if frame == nil || frame.GetFrame() == nil {
		return d.fail(invalidMutationData())
	}
	switch value := frame.GetFrame().(type) {
	case *runtimev1.MutateRecordsRequest_Header:
		if d.header != nil || len(d.data) != 0 || value.Header == nil ||
			value.Header.GetEncodedCommandBytes() == 0 ||
			value.Header.GetEncodedCommandBytes() > envelope.MaxMutationEncodedCommandBytes ||
			len(value.Header.GetEncodedSha256()) != sha256.Size {
			return d.fail(invalidMutationData())
		}
		d.header = proto.Clone(value.Header).(*runtimev1.MutationCommandHeader)
		return nil
	case *runtimev1.MutateRecordsRequest_Chunk:
		if d.header == nil || value.Chunk == nil || len(value.Chunk.GetData()) == 0 {
			return d.fail(invalidMutationData())
		}
		if len(value.Chunk.GetData()) > envelope.MaxMutationChunkDataBytes {
			return d.fail(&semantic.Failure{Category: semantic.FailureResourceExhausted, LimitKind: semantic.LimitWireFrame, Maximum: envelope.MaxMutationChunkDataBytes})
		}
		if uint64(len(d.data)+len(value.Chunk.GetData())) > d.header.GetEncodedCommandBytes() {
			return d.fail(invalidMutationData())
		}
		d.data = append(d.data, value.Chunk.GetData()...)
		return nil
	default:
		return d.fail(invalidMutationData())
	}
}

func (d *MutationFrameDecoder) Finish(ctx context.Context) (semantic.Request, error) {
	if d.failed != nil {
		return semantic.Request{}, d.failed
	}
	if d.header == nil || uint64(len(d.data)) != d.header.GetEncodedCommandBytes() {
		return semantic.Request{}, d.fail(invalidMutationData())
	}
	digest := sha256.Sum256(d.data)
	if !bytes.Equal(digest[:], d.header.GetEncodedSha256()) {
		return semantic.Request{}, d.fail(invalidMutationData())
	}
	var wireCommand runtimev1.MutationCommand
	if err := proto.Unmarshal(d.data, &wireCommand); err != nil {
		return semantic.Request{}, d.fail(invalidMutationData())
	}
	request, err := fromWireMutationCommand(ctx, &wireCommand)
	if err != nil {
		return semantic.Request{}, d.fail(err)
	}
	if err := requireRecordMutationSemanticMappingRevision(request); err != nil {
		return semantic.Request{}, d.fail(err)
	}
	return request, nil
}

func (d *MutationFrameDecoder) fail(err error) error {
	d.failed = err
	return err
}

func toWireMutationCommand(request semantic.Request) (*runtimev1.MutationCommand, error) {
	result := &runtimev1.MutationCommand{
		CommandId: request.CommandID, BatchMode: toWireMutationBatchMode(request.BatchMode),
		MinCatalogRevision: request.MinCatalogRevision, UseCase: toWireMutationUseCase(request.UseCase),
	}
	for _, operation := range request.Operations {
		wireOperation := &runtimev1.MutationOperation{
			Kind: toWireMutationOperationKind(operation.Kind), ObjectId: operation.ObjectID, RecordId: operation.RecordID,
		}
		if operation.ExternalIdentity != nil {
			wireOperation.ExternalIdentity = &runtimev1.MutationExternalIdentity{
				FieldId: operation.ExternalIdentity.FieldID,
				Value:   operation.ExternalIdentity.Value,
			}
		}
		for _, field := range operation.Fields {
			wireField, err := toWireMutationField(field)
			if err != nil {
				return nil, err
			}
			wireOperation.Fields = append(wireOperation.Fields, wireField)
		}
		result.Operations = append(result.Operations, wireOperation)
	}
	return result, nil
}

func fromWireMutationCommand(ctx context.Context, command *runtimev1.MutationCommand) (semantic.Request, error) {
	if command == nil {
		return semantic.Request{}, invalidMutationData()
	}
	useCase, ok := fromWireMutationUseCase(command.GetUseCase())
	if !ok {
		return semantic.Request{}, incompatibleFailureFor(recordMutationCapability)
	}
	result := semantic.Request{
		CommandID: command.GetCommandId(), BatchMode: fromWireMutationBatchMode(command.GetBatchMode()),
		UseCase: useCase, MinCatalogRevision: command.MinCatalogRevision,
	}
	for _, operation := range command.GetOperations() {
		if operation == nil {
			return semantic.Request{}, invalidMutationData()
		}
		semanticOperation := semantic.Operation{
			Kind: fromWireMutationOperationKind(operation.GetKind()), ObjectID: operation.GetObjectId(), RecordID: operation.GetRecordId(),
		}
		if identity := operation.GetExternalIdentity(); identity != nil {
			semanticOperation.ExternalIdentity = &semantic.ExternalIdentity{
				FieldID: identity.GetFieldId(),
				Value:   identity.GetValue(),
			}
		}
		for _, field := range operation.GetFields() {
			semanticField, err := fromWireMutationField(field)
			if err != nil {
				return semantic.Request{}, err
			}
			semanticOperation.Fields = append(semanticOperation.Fields, semanticField)
		}
		result.Operations = append(result.Operations, semanticOperation)
	}
	if err := semantic.ValidateRequest(ctx, result); err != nil {
		return semantic.Request{}, err
	}
	return result, nil
}

func toWireMutationField(field semantic.FieldValue) (*runtimev1.MutationFieldValue, error) {
	result := &runtimev1.MutationFieldValue{Name: field.Name, DeclaredKind: toWireMutationValueKind(field.Kind)}
	if field.Null {
		result.Value = &runtimev1.MutationFieldValue_NullValue{NullValue: &runtimev1.MutationNullValue{}}
		return result, nil
	}
	switch field.Kind {
	case semantic.ValueKindString:
		result.Value = &runtimev1.MutationFieldValue_StringValue{StringValue: *field.String}
	case semantic.ValueKindBoolean:
		result.Value = &runtimev1.MutationFieldValue_BooleanValue{BooleanValue: *field.Boolean}
	case semantic.ValueKindInteger:
		result.Value = &runtimev1.MutationFieldValue_IntegerValue{IntegerValue: *field.Integer}
	case semantic.ValueKindDecimal:
		result.Value = &runtimev1.MutationFieldValue_DecimalValue{DecimalValue: &runtimev1.MutationDecimalValue{Coefficient: field.Decimal.Coefficient, Scale: field.Decimal.Scale}}
	case semantic.ValueKindDate:
		result.Value = &runtimev1.MutationFieldValue_DateValue{DateValue: &runtimev1.MutationDateValue{CalendarDate: *field.Date}}
	case semantic.ValueKindDateTime:
		result.Value = &runtimev1.MutationFieldValue_DatetimeValue{DatetimeValue: &runtimev1.MutationDateTimeValue{UtcRfc3339: *field.DateTime}}
	case semantic.ValueKindReference:
		result.Value = &runtimev1.MutationFieldValue_ReferenceValue{ReferenceValue: &runtimev1.MutationReferenceValue{Identifier: *field.Reference}}
	case semantic.ValueKindNumber:
		result.Value = &runtimev1.MutationFieldValue_NumberValue{NumberValue: *field.Number}
	case semantic.ValueKindJSON:
		result.Value = &runtimev1.MutationFieldValue_CanonicalJsonValue{CanonicalJsonValue: *field.JSON}
	default:
		return nil, incompatibleFailureFor(recordMutationCapability)
	}
	return result, nil
}

func fromWireMutationField(field *runtimev1.MutationFieldValue) (semantic.FieldValue, error) {
	if field == nil {
		return semantic.FieldValue{}, invalidMutationData()
	}
	result := semantic.FieldValue{Name: field.GetName(), Kind: fromWireMutationValueKind(field.GetDeclaredKind())}
	switch value := field.GetValue().(type) {
	case *runtimev1.MutationFieldValue_StringValue:
		result.String = &value.StringValue
	case *runtimev1.MutationFieldValue_BooleanValue:
		result.Boolean = &value.BooleanValue
	case *runtimev1.MutationFieldValue_IntegerValue:
		result.Integer = &value.IntegerValue
	case *runtimev1.MutationFieldValue_DecimalValue:
		if value.DecimalValue == nil {
			return semantic.FieldValue{}, invalidMutationData()
		}
		result.Decimal = &semantic.Decimal{Coefficient: value.DecimalValue.GetCoefficient(), Scale: value.DecimalValue.GetScale()}
	case *runtimev1.MutationFieldValue_DateValue:
		if value.DateValue == nil {
			return semantic.FieldValue{}, invalidMutationData()
		}
		v := value.DateValue.GetCalendarDate()
		result.Date = &v
	case *runtimev1.MutationFieldValue_DatetimeValue:
		if value.DatetimeValue == nil {
			return semantic.FieldValue{}, invalidMutationData()
		}
		v := value.DatetimeValue.GetUtcRfc3339()
		result.DateTime = &v
	case *runtimev1.MutationFieldValue_ReferenceValue:
		if value.ReferenceValue == nil {
			return semantic.FieldValue{}, invalidMutationData()
		}
		v := value.ReferenceValue.GetIdentifier()
		result.Reference = &v
	case *runtimev1.MutationFieldValue_NullValue:
		if value.NullValue == nil {
			return semantic.FieldValue{}, invalidMutationData()
		}
		result.Null = true
	case *runtimev1.MutationFieldValue_NumberValue:
		result.Number = &value.NumberValue
	case *runtimev1.MutationFieldValue_CanonicalJsonValue:
		result.JSON = &value.CanonicalJsonValue
	default:
		return semantic.FieldValue{}, invalidMutationData()
	}
	return result, nil
}

func ToWireMutationResponse(request semantic.Request, response semantic.Response) (*runtimev1.MutateRecordsResponse, error) {
	if err := requireRecordMutationSemanticMappingRevisions(request, response); err != nil {
		return nil, err
	}
	if err := semantic.ValidateResponse(request, response); err != nil {
		return nil, err
	}
	results, err := toWireMutationResults(response.Results)
	if err != nil {
		return nil, err
	}
	digest, err := digestToWire(response.CommandDigest)
	if err != nil {
		return nil, &semantic.Failure{Category: semantic.FailureInternal}
	}
	return &runtimev1.MutateRecordsResponse{
		CommandId: response.CommandID, Results: results,
		Receipts: toWireReceipts(response.Receipts, recordMutationCapability), OutcomeExpiresAtUtc: response.OutcomeExpiresAtUTC,
		SemanticSha256: digest,
	}, nil
}

func FromWireMutationResponse(request semantic.Request, response *runtimev1.MutateRecordsResponse) (semantic.Response, error) {
	if err := requireRecordMutationSemanticMappingRevision(request); err != nil {
		return semantic.Response{}, err
	}
	if response == nil {
		return semantic.Response{}, incompatibleFailureFor(recordMutationCapability)
	}
	receipts, err := fromWireReceipts(response.GetReceipts(), recordMutationCapability)
	if err != nil {
		return semantic.Response{}, err
	}
	results, err := fromWireMutationResults(response.GetResults())
	if err != nil {
		return semantic.Response{}, err
	}
	digest, err := digestFromWire(response.GetSemanticSha256())
	if err != nil {
		return semantic.Response{}, err
	}
	result := semantic.Response{
		CommandID: response.GetCommandId(), Results: results, Receipts: receipts,
		OutcomeExpiresAtUTC: response.GetOutcomeExpiresAtUtc(), CommandDigest: digest,
	}
	if err := semantic.ValidateResponse(request, result); err != nil {
		return semantic.Response{}, err
	}
	if err := requireRecordMutationSemanticMappingRevision(result); err != nil {
		return semantic.Response{}, err
	}
	return result, nil
}

func ToWireMutationStatusRequest(ctx context.Context, request semantic.StatusRequest) (*runtimev1.GetMutationStatusRequest, error) {
	if err := requireRecordMutationSemanticMappingRevision(request); err != nil {
		return nil, err
	}
	if err := semantic.ValidateStatusRequest(ctx, request); err != nil {
		return nil, err
	}
	digest, err := digestToWire(request.CommandDigest)
	if err != nil {
		return nil, invalidMutationData()
	}
	return &runtimev1.GetMutationStatusRequest{CommandId: request.CommandID, SemanticSha256: digest}, nil
}

func FromWireMutationStatusRequest(ctx context.Context, request *runtimev1.GetMutationStatusRequest) (semantic.StatusRequest, error) {
	if request == nil {
		return semantic.StatusRequest{}, invalidMutationData()
	}
	digest, err := digestFromWire(request.GetSemanticSha256())
	if err != nil {
		return semantic.StatusRequest{}, err
	}
	result := semantic.StatusRequest{CommandID: request.GetCommandId(), CommandDigest: digest}
	if err := semantic.ValidateStatusRequest(ctx, result); err != nil {
		return semantic.StatusRequest{}, err
	}
	if err := requireRecordMutationSemanticMappingRevision(result); err != nil {
		return semantic.StatusRequest{}, err
	}
	return result, nil
}

func ToWireMutationStatusResponse(shape semantic.CommandShape, request semantic.StatusRequest, response semantic.StatusResponse) (*runtimev1.GetMutationStatusResponse, error) {
	if err := requireRecordMutationSemanticMappingRevisions(shape, request, response); err != nil {
		return nil, err
	}
	if err := semantic.ValidateStatusResponse(shape, request, response); err != nil {
		return nil, err
	}
	results, err := toWireMutationResults(response.Results)
	if err != nil {
		return nil, err
	}
	digest, err := digestToWire(response.CommandDigest)
	if err != nil {
		return nil, &semantic.Failure{Category: semantic.FailureInternal}
	}
	result := &runtimev1.GetMutationStatusResponse{
		CommandId: response.CommandID, State: toWireMutationCommandState(response.State),
		Results: results, OutcomeExpiresAtUtc: response.OutcomeExpiresAtUTC,
		SemanticSha256: digest,
	}
	if response.Receipts != nil {
		result.Receipts = toWireReceipts(*response.Receipts, recordMutationCapability)
	}
	return result, nil
}

func FromWireMutationStatusResponse(shape semantic.CommandShape, request semantic.StatusRequest, response *runtimev1.GetMutationStatusResponse) (semantic.StatusResponse, error) {
	if err := requireRecordMutationSemanticMappingRevisions(shape, request); err != nil {
		return semantic.StatusResponse{}, err
	}
	if response == nil {
		return semantic.StatusResponse{}, incompatibleFailureFor(recordMutationCapability)
	}
	results, err := fromWireMutationResults(response.GetResults())
	if err != nil {
		return semantic.StatusResponse{}, err
	}
	if len(results) == 0 {
		results = nil
	}
	digest, err := digestFromWire(response.GetSemanticSha256())
	if err != nil {
		return semantic.StatusResponse{}, err
	}
	result := semantic.StatusResponse{
		CommandID: response.GetCommandId(), State: fromWireMutationCommandState(response.GetState()),
		Results: results, OutcomeExpiresAtUTC: response.GetOutcomeExpiresAtUtc(), CommandDigest: digest,
	}
	if response.GetReceipts() != nil {
		receipts, err := fromWireReceipts(response.GetReceipts(), recordMutationCapability)
		if err != nil {
			return semantic.StatusResponse{}, err
		}
		result.Receipts = &receipts
	}
	if err := semantic.ValidateStatusResponse(shape, request, result); err != nil {
		return semantic.StatusResponse{}, err
	}
	if err := requireRecordMutationSemanticMappingRevision(result); err != nil {
		return semantic.StatusResponse{}, err
	}
	return result, nil
}

func toWireMutationResults(results []semantic.OperationResult) ([]*runtimev1.MutationOperationResult, error) {
	out := make([]*runtimev1.MutationOperationResult, 0, len(results))
	for _, result := range results {
		wireResult := &runtimev1.MutationOperationResult{
			Index: result.Index, Kind: toWireMutationOperationKind(result.Kind), RecordId: result.RecordID,
			Success: result.Success, Created: result.Created,
		}
		for _, operationError := range result.Errors {
			wireKind, ok := mutationErrorToWire[operationError.Kind]
			if !ok {
				return nil, incompatibleFailureFor(recordMutationCapability)
			}
			wireError := &runtimev1.MutationOperationError{
				Kind: wireKind, AuthoredMessage: operationError.AuthoredMessage,
				Fields: append([]string(nil), operationError.Fields...),
			}
			if operationError.CreateFromSourceGuard != nil {
				wireError.CreateFromSourceGuard = &runtimev1.MutationCreateFromSourceGuard{
					ObjectId:  operationError.CreateFromSourceGuard.ObjectID,
					ActionIds: append([]string(nil), operationError.CreateFromSourceGuard.ActionIDs...),
				}
			}
			wireResult.Errors = append(wireResult.Errors, wireError)
		}
		out = append(out, wireResult)
	}
	return out, nil
}

func fromWireMutationResults(results []*runtimev1.MutationOperationResult) ([]semantic.OperationResult, error) {
	out := make([]semantic.OperationResult, 0, len(results))
	for _, result := range results {
		if result == nil {
			return nil, &semantic.Failure{Category: semantic.FailureInternal}
		}
		semanticResult := semantic.OperationResult{
			Index: result.GetIndex(), Kind: fromWireMutationOperationKind(result.GetKind()),
			RecordID: result.GetRecordId(), Success: result.GetSuccess(), Created: result.Created,
			Errors: make([]semantic.OperationError, 0, len(result.GetErrors())),
		}
		for _, operationError := range result.GetErrors() {
			if operationError == nil {
				return nil, &semantic.Failure{Category: semantic.FailureInternal}
			}
			semanticError := semantic.OperationError{
				Kind: fromWireMutationErrorKind(operationError.GetKind()), AuthoredMessage: operationError.GetAuthoredMessage(),
				Fields: append([]string{}, operationError.GetFields()...),
			}
			if details := operationError.GetCreateFromSourceGuard(); details != nil {
				semanticError.CreateFromSourceGuard = &semantic.CreateFromSourceGuard{
					ObjectID: details.GetObjectId(), ActionIDs: append([]string(nil), details.GetActionIds()...),
				}
			}
			semanticResult.Errors = append(semanticResult.Errors, semanticError)
		}
		out = append(out, semanticResult)
	}
	return out, nil
}

func toWireMutationBatchMode(value semantic.BatchMode) runtimev1.MutationBatchMode {
	return map[semantic.BatchMode]runtimev1.MutationBatchMode{
		semantic.BatchIndependent: runtimev1.MutationBatchMode_MUTATION_BATCH_MODE_INDEPENDENT,
		semantic.BatchAtomic:      runtimev1.MutationBatchMode_MUTATION_BATCH_MODE_ATOMIC,
	}[value]
}

func fromWireMutationBatchMode(value runtimev1.MutationBatchMode) semantic.BatchMode {
	return map[runtimev1.MutationBatchMode]semantic.BatchMode{
		runtimev1.MutationBatchMode_MUTATION_BATCH_MODE_INDEPENDENT: semantic.BatchIndependent,
		runtimev1.MutationBatchMode_MUTATION_BATCH_MODE_ATOMIC:      semantic.BatchAtomic,
	}[value]
}

func toWireMutationUseCase(value semantic.MutationUseCase) runtimev1.MutationUseCase {
	return map[semantic.MutationUseCase]runtimev1.MutationUseCase{
		semantic.MutationUseCaseUnspecified: runtimev1.MutationUseCase_MUTATION_USE_CASE_UNSPECIFIED,
		semantic.MutationUseCaseSingle:      runtimev1.MutationUseCase_MUTATION_USE_CASE_SINGLE_RECORD,
		semantic.MutationUseCaseCollection:  runtimev1.MutationUseCase_MUTATION_USE_CASE_COLLECTION,
	}[value]
}

func fromWireMutationUseCase(value runtimev1.MutationUseCase) (semantic.MutationUseCase, bool) {
	result, ok := map[runtimev1.MutationUseCase]semantic.MutationUseCase{
		runtimev1.MutationUseCase_MUTATION_USE_CASE_UNSPECIFIED:   semantic.MutationUseCaseUnspecified,
		runtimev1.MutationUseCase_MUTATION_USE_CASE_SINGLE_RECORD: semantic.MutationUseCaseSingle,
		runtimev1.MutationUseCase_MUTATION_USE_CASE_COLLECTION:    semantic.MutationUseCaseCollection,
	}[value]
	return result, ok
}

func toWireMutationOperationKind(value semantic.OperationKind) runtimev1.MutationOperationKind {
	return map[semantic.OperationKind]runtimev1.MutationOperationKind{
		semantic.OperationCreate: runtimev1.MutationOperationKind_MUTATION_OPERATION_KIND_CREATE,
		semantic.OperationUpdate: runtimev1.MutationOperationKind_MUTATION_OPERATION_KIND_UPDATE,
		semantic.OperationDelete: runtimev1.MutationOperationKind_MUTATION_OPERATION_KIND_DELETE,
		semantic.OperationUpsert: runtimev1.MutationOperationKind_MUTATION_OPERATION_KIND_UPSERT,
	}[value]
}

func fromWireMutationOperationKind(value runtimev1.MutationOperationKind) semantic.OperationKind {
	return map[runtimev1.MutationOperationKind]semantic.OperationKind{
		runtimev1.MutationOperationKind_MUTATION_OPERATION_KIND_CREATE: semantic.OperationCreate,
		runtimev1.MutationOperationKind_MUTATION_OPERATION_KIND_UPDATE: semantic.OperationUpdate,
		runtimev1.MutationOperationKind_MUTATION_OPERATION_KIND_DELETE: semantic.OperationDelete,
		runtimev1.MutationOperationKind_MUTATION_OPERATION_KIND_UPSERT: semantic.OperationUpsert,
	}[value]
}

func toWireMutationValueKind(value semantic.ValueKind) runtimev1.MutationValueKind {
	return map[semantic.ValueKind]runtimev1.MutationValueKind{
		semantic.ValueKindString:    runtimev1.MutationValueKind_MUTATION_VALUE_KIND_STRING,
		semantic.ValueKindBoolean:   runtimev1.MutationValueKind_MUTATION_VALUE_KIND_BOOLEAN,
		semantic.ValueKindInteger:   runtimev1.MutationValueKind_MUTATION_VALUE_KIND_INTEGER,
		semantic.ValueKindDecimal:   runtimev1.MutationValueKind_MUTATION_VALUE_KIND_DECIMAL,
		semantic.ValueKindDate:      runtimev1.MutationValueKind_MUTATION_VALUE_KIND_DATE,
		semantic.ValueKindDateTime:  runtimev1.MutationValueKind_MUTATION_VALUE_KIND_DATETIME,
		semantic.ValueKindReference: runtimev1.MutationValueKind_MUTATION_VALUE_KIND_REFERENCE,
		semantic.ValueKindNull:      runtimev1.MutationValueKind_MUTATION_VALUE_KIND_NULL,
		semantic.ValueKindNumber:    runtimev1.MutationValueKind_MUTATION_VALUE_KIND_NUMBER,
		semantic.ValueKindJSON:      runtimev1.MutationValueKind_MUTATION_VALUE_KIND_JSON,
	}[value]
}

func fromWireMutationValueKind(value runtimev1.MutationValueKind) semantic.ValueKind {
	return map[runtimev1.MutationValueKind]semantic.ValueKind{
		runtimev1.MutationValueKind_MUTATION_VALUE_KIND_STRING:    semantic.ValueKindString,
		runtimev1.MutationValueKind_MUTATION_VALUE_KIND_BOOLEAN:   semantic.ValueKindBoolean,
		runtimev1.MutationValueKind_MUTATION_VALUE_KIND_INTEGER:   semantic.ValueKindInteger,
		runtimev1.MutationValueKind_MUTATION_VALUE_KIND_DECIMAL:   semantic.ValueKindDecimal,
		runtimev1.MutationValueKind_MUTATION_VALUE_KIND_DATE:      semantic.ValueKindDate,
		runtimev1.MutationValueKind_MUTATION_VALUE_KIND_DATETIME:  semantic.ValueKindDateTime,
		runtimev1.MutationValueKind_MUTATION_VALUE_KIND_REFERENCE: semantic.ValueKindReference,
		runtimev1.MutationValueKind_MUTATION_VALUE_KIND_NULL:      semantic.ValueKindNull,
		runtimev1.MutationValueKind_MUTATION_VALUE_KIND_NUMBER:    semantic.ValueKindNumber,
		runtimev1.MutationValueKind_MUTATION_VALUE_KIND_JSON:      semantic.ValueKindJSON,
	}[value]
}

func fromWireMutationErrorKind(value runtimev1.MutationErrorKind) semantic.ErrorKind {
	for semanticValue, wireValue := range mutationErrorToWire {
		if value == wireValue {
			return semanticValue
		}
	}
	return ""
}

var mutationErrorToWire = map[semantic.ErrorKind]runtimev1.MutationErrorKind{
	semantic.ErrorProcessingHalted:     runtimev1.MutationErrorKind_MUTATION_ERROR_KIND_PROCESSING_HALTED,
	semantic.ErrorAtomicRolledBack:     runtimev1.MutationErrorKind_MUTATION_ERROR_KIND_ATOMIC_OPERATION_ROLLED_BACK,
	semantic.ErrorInternal:             runtimev1.MutationErrorKind_MUTATION_ERROR_KIND_INTERNAL,
	semantic.ErrorInvalidFieldForWrite: runtimev1.MutationErrorKind_MUTATION_ERROR_KIND_INVALID_FIELD_FOR_WRITE,
	semantic.ErrorCustomValidation:     runtimev1.MutationErrorKind_MUTATION_ERROR_KIND_CUSTOM_VALIDATION,
	semantic.ErrorInvalidReference:     runtimev1.MutationErrorKind_MUTATION_ERROR_KIND_INVALID_REFERENCE,
	semantic.ErrorFieldIntegrity:       runtimev1.MutationErrorKind_MUTATION_ERROR_KIND_FIELD_INTEGRITY,
	semantic.ErrorInvalidOperation:     runtimev1.MutationErrorKind_MUTATION_ERROR_KIND_INVALID_OPERATION,
	semantic.ErrorInvalidField:         runtimev1.MutationErrorKind_MUTATION_ERROR_KIND_INVALID_FIELD,
	semantic.ErrorMissingOrDenied:      runtimev1.MutationErrorKind_MUTATION_ERROR_KIND_MISSING_OR_DENIED,
	semantic.ErrorMalformedIdentifier:  runtimev1.MutationErrorKind_MUTATION_ERROR_KIND_MALFORMED_IDENTIFIER,
	semantic.ErrorInvalidIdentifier:    runtimev1.MutationErrorKind_MUTATION_ERROR_KIND_INVALID_IDENTIFIER,
	semantic.ErrorAutomation:           runtimev1.MutationErrorKind_MUTATION_ERROR_KIND_AUTOMATION,
	semantic.ErrorAccessDenied:         runtimev1.MutationErrorKind_MUTATION_ERROR_KIND_ACCESS_DENIED,
	semantic.ErrorPolicyDenied:         runtimev1.MutationErrorKind_MUTATION_ERROR_KIND_POLICY_DENIED,
	semantic.ErrorRequiredFieldMissing: runtimev1.MutationErrorKind_MUTATION_ERROR_KIND_REQUIRED_FIELD_MISSING,
	semantic.ErrorObjectNotFound:       runtimev1.MutationErrorKind_MUTATION_ERROR_KIND_OBJECT_NOT_FOUND,
}

// supportedRecordMutationSemanticMappingRevision is the one semantic shape
// implemented by this mapper. Every older or future revision fails closed.
const supportedRecordMutationSemanticMappingRevision uint32 = 7

type recordMutationSemanticMappingRevisioner interface {
	RecordMutationSemanticMappingRevision() uint32
}

func requireRecordMutationSemanticMappingRevision(value any) error {
	revisioned, ok := value.(recordMutationSemanticMappingRevisioner)
	if !ok {
		// An unversioned semantic value cannot prove that every fact has a
		// wire representation. Fail closed instead of silently projecting a
		// newer independently released semantic shape through an older mapper.
		return incompatibleFailureFor(recordMutationCapability)
	}
	if revisioned.RecordMutationSemanticMappingRevision() != supportedRecordMutationSemanticMappingRevision {
		return incompatibleFailureFor(recordMutationCapability)
	}
	return nil
}

func requireRecordMutationSemanticMappingRevisions(values ...any) error {
	for _, value := range values {
		if err := requireRecordMutationSemanticMappingRevision(value); err != nil {
			return err
		}
	}
	return nil
}

func toWireMutationCommandState(value semantic.CommandState) runtimev1.MutationCommandState {
	return map[semantic.CommandState]runtimev1.MutationCommandState{
		semantic.CommandAccepted:  runtimev1.MutationCommandState_MUTATION_COMMAND_STATE_ACCEPTED,
		semantic.CommandExecuting: runtimev1.MutationCommandState_MUTATION_COMMAND_STATE_EXECUTING,
		semantic.CommandCompleted: runtimev1.MutationCommandState_MUTATION_COMMAND_STATE_COMPLETED,
	}[value]
}

func fromWireMutationCommandState(value runtimev1.MutationCommandState) semantic.CommandState {
	return map[runtimev1.MutationCommandState]semantic.CommandState{
		runtimev1.MutationCommandState_MUTATION_COMMAND_STATE_ACCEPTED:  semantic.CommandAccepted,
		runtimev1.MutationCommandState_MUTATION_COMMAND_STATE_EXECUTING: semantic.CommandExecuting,
		runtimev1.MutationCommandState_MUTATION_COMMAND_STATE_COMPLETED: semantic.CommandCompleted,
	}[value]
}

func invalidMutationData() error {
	return &semantic.Failure{Category: semantic.FailureInvalidRequest, InvalidComponent: semantic.InvalidComponentCommandData}
}

func digestToWire(value string) ([]byte, error) {
	if !strings.HasPrefix(value, "sha256:") {
		return nil, invalidMutationData()
	}
	decoded, err := hex.DecodeString(strings.TrimPrefix(value, "sha256:"))
	if err != nil || len(decoded) != sha256.Size {
		return nil, invalidMutationData()
	}
	return decoded, nil
}

func digestFromWire(value []byte) (string, error) {
	if len(value) != sha256.Size {
		return "", invalidMutationData()
	}
	return "sha256:" + hex.EncodeToString(value), nil
}
