package packs

import "encoding/json"

// requireSourceBackedPackConstruction keeps SourceBackedPackOption values on
// their intended construction-only path. A successfully constructed pack owns
// derived execution state; replaying an option afterward would otherwise make
// that state disagree with its manifest hashes and runtime declarations.
func (p *SourceBackedPack) requireSourceBackedPackConstruction() {
	if p == nil {
		panic("packs: source-backed pack option applied to nil pack")
	}
	if p.constructionComplete {
		panic("packs: source-backed pack option applied after construction")
	}
}

// cloneResolvedPackSource detaches the complete mutable source graph before
// component construction. It preserves nil versus non-nil empty collections so
// source semantics do not change while ownership moves to the constructed pack.
func cloneResolvedPackSource(source *ResolvedPackSource) *ResolvedPackSource {
	if source == nil {
		return nil
	}
	out := *source
	out.ManifestBytes = cloneSourceBytes(source.ManifestBytes)
	out.FileRefs = cloneSourceSlice(source.FileRefs)
	if source.Files != nil {
		out.Files = make(map[string][]byte, len(source.Files))
		for name, payload := range source.Files {
			out.Files[name] = cloneSourceBytes(payload)
		}
	}
	if source.Manifest != nil {
		manifest := *source.Manifest
		if source.Manifest.Provenance != nil {
			provenance := *source.Manifest.Provenance
			manifest.Provenance = &provenance
		}
		manifest.Dependencies = cloneSourceSlice(source.Manifest.Dependencies)
		manifest.HostRequirements = cloneSourceSlice(source.Manifest.HostRequirements)
		manifest.Extensions = json.RawMessage(cloneSourceBytes(source.Manifest.Extensions))
		manifest.Components = cloneManifestComponents(source.Manifest.Components)
		out.Manifest = &manifest
	}
	return &out
}

func cloneManifestComponents(components []ManifestComponent) []ManifestComponent {
	if components == nil {
		return nil
	}
	out := make([]ManifestComponent, len(components))
	for index, component := range components {
		out[index] = component
		out[index].Payload = json.RawMessage(cloneSourceBytes(component.Payload))
		out[index].DependsOn = cloneSourceSlice(component.DependsOn)
	}
	return out
}

func cloneSourceBytes(value []byte) []byte {
	if value == nil {
		return nil
	}
	return append(make([]byte, 0, len(value)), value...)
}

func cloneSourceSlice[T any](value []T) []T {
	if value == nil {
		return nil
	}
	return append(make([]T, 0, len(value)), value...)
}
