package contracts

import (
	"crypto/sha256"
	"encoding/hex"
)

// ArtifactKind is the stable logical identity of a domain artifact.
type ArtifactKind string

// ArtifactSchema describes the durable representation owned by an artifact
// codec. JSONSchema is cloned whenever framework ownership changes.
type ArtifactSchema struct {
	ID         string `json:"id"`
	Name       string `json:"name"`
	Version    string `json:"version"`
	JSONSchema []byte `json:"-"`
}

// SerializedArtifact is the domain-neutral representation of a typed
// artifact at an explicit serialization boundary.
type SerializedArtifact struct {
	Kind      ArtifactKind   `json:"kind"`
	Schema    ArtifactSchema `json:"schema"`
	MediaType string         `json:"media_type"`
	Content   []byte         `json:"-"`
	Metadata  map[string]any `json:"metadata,omitempty"`
}

// SerializedOutput associates a domain-neutral artifact with the pipeline
// operation that produced it. Provenance remains outside codec-owned bytes.
type SerializedOutput struct {
	StepID        string             `json:"step_id,omitempty"`
	LaneID        string             `json:"lane_id"`
	NormalizerKey string             `json:"normalizer_key"`
	SourceID      string             `json:"source_id,omitempty"`
	Artifact      SerializedArtifact `json:"artifact"`
}

// ArtifactCodec owns the stable encoding for one concrete artifact type.
type ArtifactCodec[T any] interface {
	Kind() ArtifactKind
	Schema() ArtifactSchema
	MediaType() string
	// EncodeCandidate serializes a stage result before semantic validation. It
	// must not apply validity checks owned by typed validators; Encode remains
	// the strict final-artifact boundary used after validation succeeds.
	EncodeCandidate(T) ([]byte, error)
	Encode(T) ([]byte, error)
	Decode([]byte) (T, error)
}

// CandidateArtifactCodec extends an artifact codec with strict representation
// decoding for values that have not yet passed semantic validation.
type CandidateArtifactCodec[T any] interface {
	ArtifactCodec[T]
	DecodeCandidate([]byte) (T, error)
}

// DigestArtifactSchema returns the SHA-256 digest of the exact JSON Schema
// bytes. Schema formatting is therefore part of the registered identity.
func DigestArtifactSchema(schema ArtifactSchema) string {
	sum := sha256.Sum256(schema.JSONSchema)
	return "sha256:" + hex.EncodeToString(sum[:])
}

func CloneArtifactSchema(schema ArtifactSchema) ArtifactSchema {
	schema.JSONSchema = append([]byte(nil), schema.JSONSchema...)
	return schema
}

func CloneSerializedArtifact(artifact SerializedArtifact) SerializedArtifact {
	artifact.Schema = CloneArtifactSchema(artifact.Schema)
	artifact.Content = append([]byte(nil), artifact.Content...)
	artifact.Metadata = cloneArtifactMetadata(artifact.Metadata)
	return artifact
}

// CloneSerializedArtifactPointer returns an independently owned artifact when
// one is present.
func CloneSerializedArtifactPointer(artifact *SerializedArtifact) *SerializedArtifact {
	if artifact == nil {
		return nil
	}
	cloned := CloneSerializedArtifact(*artifact)
	return &cloned
}

func CloneSerializedOutput(output SerializedOutput) SerializedOutput {
	output.Artifact = CloneSerializedArtifact(output.Artifact)
	return output
}

func cloneArtifactMetadata(metadata map[string]any) map[string]any {
	if len(metadata) == 0 {
		return nil
	}
	out := make(map[string]any, len(metadata))
	for key, value := range metadata {
		out[key] = value
	}
	return out
}
