;; Deterministic serialization for content addressing

(define-library (sx canonical)
  (export
    canonical-serialize
    canonical-number
    canonical-dict
    content-id
    content-id-short
    make-bytecode-module
    bytecode-module?
    bytecode-module-version
    bytecode-module-source-hash
    bytecode-module-code
    make-code-object
    make-provenance)
  (begin

(define
  canonical-serialize
  :effects ()
  (fn
    (val)
    (case
      (type-of val)
      "nil"
      "nil"
      "boolean"
      (if val "true" "false")
      "number"
      (canonical-number val)
      "string"
      (str "\"" (escape-string val) "\"")
      "symbol"
      (symbol-name val)
      "keyword"
      (str ":" (keyword-name val))
      "list"
      (str "(" (join " " (map canonical-serialize val)) ")")
      "dict"
      (canonical-dict val)
      :else (str val))))

;; Normalize number representation (no trailing zeros)
(define
  canonical-number
  :effects ()
  (fn
    (n)
    (let
      ((s (str n)))
      (if
        (ends-with? s ".0")
        (slice s 0 (- (len s) 2))
        (if
          (contains-char? s ".")
          (let
            ((trimmed (trim-right s "0")))
            (if (ends-with? trimmed ".") (str trimmed "0") trimmed))
          s)))))

;; Serialize dict with sorted keys
(define
  canonical-dict
  :effects ()
  (fn
    (d)
    (let
      ((sorted-keys (sort (keys d))))
      (str
        "{"
        (join
          " "
          (reduce
            (fn
              (acc key)
              (concat
                acc
                (list (str ":" key) (canonical-serialize (dict-get d key)))))
            (list)
            sorted-keys))
        "}"))))

;; Compute SHA3-256 content ID from an expression
(define
  content-id
  :effects ()
  (fn (expr) (sha3-256 (canonical-serialize expr))))

;; First 16 chars of content ID (short form)
(define
  content-id-short
  :effects ()
  (fn (expr) (slice (content-id expr) 0 16)))

;; Create a bytecode module container
(define
  make-bytecode-module
  :effects ()
  (fn
    (version source-hash code)
    (list (quote sxbc) version source-hash code)))

;; Type predicate for bytecode modules
(define
  bytecode-module?
  :effects ()
  (fn
    (expr)
    (and (list? expr) (>= (len expr) 4) (= (first expr) (quote sxbc)))))

;; Get module format version
(define bytecode-module-version :effects () (fn (m) (nth m 1)))

;; Get source content hash
(define bytecode-module-source-hash :effects () (fn (m) (nth m 2)))

;; Get compiled bytecode
(define bytecode-module-code :effects () (fn (m) (nth m 3)))

;; Create a code object (arity + constants + bytecode)
(define
  make-code-object
  :effects ()
  (fn
    (arity upvalue-count bytecode constants)
    (let
      ((parts (list (quote code))))
      (when (> arity 0) (set! parts (concat parts (list :arity arity))))
      (when
        (> upvalue-count 0)
        (set! parts (concat parts (list :upvalue-count upvalue-count))))
      (concat parts (list :bytecode bytecode :constants constants)))))

;; Create provenance record (author, timestamp, source)
(define
  make-provenance
  :effects ()
  (fn
    (source-cid bytecode-cid compiler-cid timestamp)
    (list
      (quote provenance)
      :source-cid source-cid
      :bytecode-cid bytecode-cid
      :compiler-cid compiler-cid
      :timestamp timestamp)))


)) ;; end define-library

;; Re-export to global namespace for backward compatibility
(import (sx canonical))
