// Package materializer ports scripts/distillation/transforms.ts +
// build_evidence_index.ts to Go. Source rows in data/_kb/*.jsonl are
// transformed into EvidenceRecord rows under data/evidence/YYYY/MM/DD/.
//
// Per ADR-001 #4: port LOGIC, not bit-identical reproducibility — but
// on-wire JSON layout matches the TS shape so Bun and Go runs stay
// interchangeable for tooling that reads either output.
package materializer

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"sort"
)

// CanonicalSha256 returns the hex SHA-256 of `obj` after sorting all
// object keys recursively. Matches the TS canonicalSha256 in
// auditor/schemas/distillation/types.ts so a row hashed by either
// runtime gets the same sig_hash.
//
// Determinism contract: identical input → identical hash, regardless
// of the producer's serialization order.
func CanonicalSha256(obj any) (string, error) {
	ordered := orderKeys(obj)
	buf, err := json.Marshal(ordered)
	if err != nil {
		return "", fmt.Errorf("canonical marshal: %w", err)
	}
	sum := sha256.Sum256(buf)
	return hex.EncodeToString(sum[:]), nil
}

// orderKeys recursively sorts every map's keys. For arrays we keep the
// element order (arrays are inherently ordered). Scalars pass through.
func orderKeys(v any) any {
	switch t := v.(type) {
	case map[string]any:
		keys := make([]string, 0, len(t))
		for k := range t {
			keys = append(keys, k)
		}
		sort.Strings(keys)
		out := make(orderedMap, 0, len(keys))
		for _, k := range keys {
			out = append(out, kvPair{Key: k, Value: orderKeys(t[k])})
		}
		return out
	case []any:
		out := make([]any, len(t))
		for i, e := range t {
			out[i] = orderKeys(e)
		}
		return out
	default:
		return v
	}
}

// orderedMap preserves insertion order on JSON marshal. We populate it
// in sorted-key order so the produced bytes are stable.
type orderedMap []kvPair

type kvPair struct {
	Key   string
	Value any
}

func (om orderedMap) MarshalJSON() ([]byte, error) {
	if len(om) == 0 {
		return []byte("{}"), nil
	}
	out := []byte{'{'}
	for i, kv := range om {
		if i > 0 {
			out = append(out, ',')
		}
		k, err := json.Marshal(kv.Key)
		if err != nil {
			return nil, err
		}
		out = append(out, k...)
		out = append(out, ':')
		v, err := json.Marshal(kv.Value)
		if err != nil {
			return nil, err
		}
		out = append(out, v...)
	}
	out = append(out, '}')
	return out, nil
}
