package agentstate

import "fmt"

type AgentStateEnvelopeV2 struct {
	Snapshot *AgentStateSnapshotV2
	Delta    *AgentStateDeltaV2
}

func SnapshotEnvelope(snapshot AgentStateSnapshotV2) AgentStateEnvelopeV2 {
	copy := cloneSnapshot(snapshot)
	return AgentStateEnvelopeV2{Snapshot: &copy}
}

func DeltaEnvelope(delta AgentStateDeltaV2) AgentStateEnvelopeV2 {
	copy := cloneDelta(delta)
	return AgentStateEnvelopeV2{Delta: &copy}
}

func (envelope AgentStateEnvelopeV2) Validate() error {
	switch {
	case envelope.Snapshot != nil && envelope.Delta == nil:
		return envelope.Snapshot.Validate()
	case envelope.Snapshot == nil && envelope.Delta != nil:
		return envelope.Delta.Validate()
	default:
		return fmt.Errorf("agent state envelope requires exactly one snapshot or delta")
	}
}

func (envelope AgentStateEnvelopeV2) Base() EnvelopeBaseV2 {
	if envelope.Snapshot != nil {
		return envelope.Snapshot.EnvelopeBaseV2
	}
	if envelope.Delta != nil {
		return envelope.Delta.EnvelopeBaseV2
	}
	return EnvelopeBaseV2{}
}

func cloneSnapshot(snapshot AgentStateSnapshotV2) AgentStateSnapshotV2 {
	snapshot.Deliveries = cloneDeliveries(snapshot.Deliveries)
	snapshot.Diagnostics = cloneDiagnostics(snapshot.Diagnostics)
	return snapshot
}

func cloneDelta(delta AgentStateDeltaV2) AgentStateDeltaV2 {
	if delta.Execution != nil {
		execution := *delta.Execution
		delta.Execution = &execution
	}
	delta.DeliveryUpserts = cloneDeliveries(delta.DeliveryUpserts)
	delta.DeliveryRemovals = append([]string(nil), delta.DeliveryRemovals...)
	delta.DiagnosticUpserts = cloneDiagnostics(delta.DiagnosticUpserts)
	delta.DiagnosticRemovals = append([]string(nil), delta.DiagnosticRemovals...)
	return delta
}

func cloneDeliveries(deliveries []WakeDeliveryState) []WakeDeliveryState {
	if deliveries == nil {
		return nil
	}
	result := make([]WakeDeliveryState, len(deliveries))
	for index, delivery := range deliveries {
		result[index] = cloneWakeDelivery(delivery)
	}
	return result
}

func cloneDiagnostics(diagnostics []AgentDiagnostic) []AgentDiagnostic {
	if diagnostics == nil {
		return nil
	}
	result := make([]AgentDiagnostic, len(diagnostics))
	for index, diagnostic := range diagnostics {
		diagnostic.LocalWakeIDs = append([]string(nil), diagnostic.LocalWakeIDs...)
		result[index] = diagnostic
	}
	return result
}

func cloneState(state State) State {
	state.Deliveries = cloneDeliveries(state.Deliveries)
	state.Diagnostics = cloneDiagnostics(state.Diagnostics)
	return state
}
