;; lib/go/std/strings.sx — Go's `strings` package, v0 subset.
;;
;; Exposed as `go-std-strings`, a (:go-package "strings" ENTRIES) value.
;; Register with `(go-env-extend env "strings" go-std-strings)` to make
;; `strings.X(...)` call sites work in evaluated Go code.
;;
;; Each entry is (FIELD-NAME (list :go-fn PARAMS BODY)) — the same
;; shape user-defined Go functions get. Bodies are written in SX
;; directly via go-builtin closures wrapping host-level string ops
;; for speed, OR as parsed Go source for fidelity. v0 uses
;; go-builtin wrappers — simpler and fast.

;; ── helpers: implement go-std-strings entries as builtins ────────

(define
  go-strings-contains
  (fn (args)
    (cond
      (not (= (len args) 2))
      (list :eval-error :strings-contains-arity (len args))
      :else
      (let ((s (first args)) (sub (nth args 1)))
        (cond
          (not (string? s)) (list :eval-error :strings-not-string s)
          (not (string? sub)) (list :eval-error :strings-not-string sub)
          :else
          (go-strings-index-of s sub 0))))))

(define
  go-strings-index-of
  ;; Returns true if SUB appears in S at or after START, else false.
  (fn (s sub start)
    (let ((slen (len s)) (sublen (len sub)))
      (cond
        (= sublen 0) true
        (> (+ start sublen) slen) false
        (go-strings-match-at s sub start 0) true
        :else (go-strings-index-of s sub (+ start 1))))))

(define
  go-strings-match-at
  (fn (s sub start k)
    (cond
      (>= k (len sub)) true
      (= (nth s (+ start k)) (nth sub k))
      (go-strings-match-at s sub start (+ k 1))
      :else false)))

(define
  go-strings-has-prefix
  (fn (args)
    (cond
      (not (= (len args) 2))
      (list :eval-error :strings-hasprefix-arity (len args))
      :else
      (let ((s (first args)) (p (nth args 1)))
        (cond
          (not (string? s)) (list :eval-error :strings-not-string s)
          (not (string? p)) (list :eval-error :strings-not-string p)
          (> (len p) (len s)) false
          :else (go-strings-match-at s p 0 0))))))

(define
  go-strings-has-suffix
  (fn (args)
    (cond
      (not (= (len args) 2))
      (list :eval-error :strings-hassuffix-arity (len args))
      :else
      (let ((s (first args)) (suf (nth args 1)))
        (cond
          (not (string? s)) (list :eval-error :strings-not-string s)
          (not (string? suf)) (list :eval-error :strings-not-string suf)
          (> (len suf) (len s)) false
          :else
          (go-strings-match-at s suf (- (len s) (len suf)) 0))))))

(define
  go-strings-index
  (fn (args)
    (cond
      (not (= (len args) 2))
      (list :eval-error :strings-index-arity (len args))
      :else
      (let ((s (first args)) (sub (nth args 1)))
        (cond
          (not (string? s)) (list :eval-error :strings-not-string s)
          (not (string? sub)) (list :eval-error :strings-not-string sub)
          :else (go-strings-index-loop s sub 0))))))

(define
  go-strings-index-loop
  (fn (s sub start)
    (let ((slen (len s)) (sublen (len sub)))
      (cond
        (= sublen 0) 0
        (> (+ start sublen) slen) -1
        (go-strings-match-at s sub start 0) start
        :else (go-strings-index-loop s sub (+ start 1))))))

(define
  go-strings-repeat
  (fn (args)
    (cond
      (not (= (len args) 2))
      (list :eval-error :strings-repeat-arity (len args))
      :else
      (let ((s (first args)) (n (nth args 1)))
        (cond
          (not (string? s)) (list :eval-error :strings-not-string s)
          (< n 0) (list :eval-error :strings-repeat-negative n)
          :else (go-strings-repeat-loop s n ""))))))

(define
  go-strings-repeat-loop
  (fn (s n acc)
    (cond
      (<= n 0) acc
      :else (go-strings-repeat-loop s (- n 1) (str acc s)))))

(define
  go-strings-count
  (fn (args)
    (cond
      (not (= (len args) 2))
      (list :eval-error :strings-count-arity (len args))
      :else
      (let ((s (first args)) (sub (nth args 1)))
        (cond
          (not (string? s)) (list :eval-error :strings-not-string s)
          (not (string? sub)) (list :eval-error :strings-not-string sub)
          :else (go-strings-count-loop s sub 0 0))))))

(define
  go-strings-count-loop
  (fn (s sub start acc)
    (let ((idx (go-strings-index-loop s sub start)))
      (cond
        (< idx 0) acc
        :else
        (go-strings-count-loop s sub (+ idx (max 1 (len sub))) (+ acc 1))))))

(define
  go-strings-join
  (fn (args)
    (cond
      (not (= (len args) 2))
      (list :eval-error :strings-join-arity (len args))
      :else
      (let ((sep (nth args 1)) (xs (first args)))
        (cond
          (not (string? sep)) (list :eval-error :strings-not-string sep)
          (not (and (list? xs) (= (first xs) :go-slice)))
          (list :eval-error :strings-join-not-slice xs)
          :else (go-strings-join-loop (nth xs 1) sep ""))))))

(define
  go-strings-join-loop
  (fn (xs sep acc)
    (cond
      (= (len xs) 0) acc
      (= (len acc) 0) (go-strings-join-loop (rest xs) sep (first xs))
      :else
      (go-strings-join-loop (rest xs) sep (str acc sep (first xs))))))

;; ── case conversion ──────────────────────────────────────────────

(define
  go-strings-char-to-upper
  (fn (c)
    (cond
      (and (>= c "a") (<= c "z"))
      ;; ASCII uppercase shift: 'a' is 0x61, 'A' is 0x41 → diff 0x20.
      ;; SX has no charcode primitive, so use a char-pair table.
      (go-strings-letter-toggle c true)
      :else c)))

(define
  go-strings-char-to-lower
  (fn (c)
    (cond
      (and (>= c "A") (<= c "Z"))
      (go-strings-letter-toggle c false)
      :else c)))

(define
  go-strings-letter-toggle
  ;; Toggle a single ASCII letter's case via direct mapping.
  ;; `to-upper?` true means input is lowercase, output uppercase.
  (fn (c to-upper?)
    (cond
      to-upper?
      (cond
        (= c "a") "A" (= c "b") "B" (= c "c") "C" (= c "d") "D"
        (= c "e") "E" (= c "f") "F" (= c "g") "G" (= c "h") "H"
        (= c "i") "I" (= c "j") "J" (= c "k") "K" (= c "l") "L"
        (= c "m") "M" (= c "n") "N" (= c "o") "O" (= c "p") "P"
        (= c "q") "Q" (= c "r") "R" (= c "s") "S" (= c "t") "T"
        (= c "u") "U" (= c "v") "V" (= c "w") "W" (= c "x") "X"
        (= c "y") "Y" (= c "z") "Z" :else c)
      :else
      (cond
        (= c "A") "a" (= c "B") "b" (= c "C") "c" (= c "D") "d"
        (= c "E") "e" (= c "F") "f" (= c "G") "g" (= c "H") "h"
        (= c "I") "i" (= c "J") "j" (= c "K") "k" (= c "L") "l"
        (= c "M") "m" (= c "N") "n" (= c "O") "o" (= c "P") "p"
        (= c "Q") "q" (= c "R") "r" (= c "S") "s" (= c "T") "t"
        (= c "U") "u" (= c "V") "v" (= c "W") "w" (= c "X") "x"
        (= c "Y") "y" (= c "Z") "z" :else c))))

(define
  go-strings-map-chars
  (fn (s i acc char-fn)
    (cond
      (>= i (len s)) acc
      :else
      (go-strings-map-chars s (+ i 1) (str acc (char-fn (nth s i))) char-fn))))

(define
  go-strings-to-upper
  (fn (args)
    (cond
      (not (= (len args) 1))
      (list :eval-error :strings-toupper-arity (len args))
      :else
      (let ((s (first args)))
        (cond
          (not (string? s)) (list :eval-error :strings-not-string s)
          :else (go-strings-map-chars s 0 "" go-strings-char-to-upper))))))

(define
  go-strings-to-lower
  (fn (args)
    (cond
      (not (= (len args) 1))
      (list :eval-error :strings-tolower-arity (len args))
      :else
      (let ((s (first args)))
        (cond
          (not (string? s)) (list :eval-error :strings-not-string s)
          :else (go-strings-map-chars s 0 "" go-strings-char-to-lower))))))

;; ── TrimSpace ────────────────────────────────────────────────────

(define
  go-strings-is-space?
  (fn (c)
    (or (= c " ") (= c "\t") (= c "\n") (= c "\r"))))

(define
  go-strings-trim-left
  (fn (s i)
    (cond
      (>= i (len s)) i
      (go-strings-is-space? (nth s i)) (go-strings-trim-left s (+ i 1))
      :else i)))

(define
  go-strings-trim-right
  (fn (s end)
    (cond
      (<= end 0) 0
      (go-strings-is-space? (nth s (- end 1))) (go-strings-trim-right s (- end 1))
      :else end)))

(define
  go-strings-substr
  ;; Substring [lo, hi) — naive but predictable.
  (fn (s lo hi)
    (cond
      (>= lo hi) ""
      :else
      (go-strings-substr-loop s lo hi ""))))

(define
  go-strings-substr-loop
  (fn (s i hi acc)
    (cond
      (>= i hi) acc
      :else (go-strings-substr-loop s (+ i 1) hi (str acc (nth s i))))))

(define
  go-strings-trim-space
  (fn (args)
    (cond
      (not (= (len args) 1))
      (list :eval-error :strings-trimspace-arity (len args))
      :else
      (let ((s (first args)))
        (cond
          (not (string? s)) (list :eval-error :strings-not-string s)
          :else
          (let ((lo (go-strings-trim-left s 0)))
            (let ((hi (go-strings-trim-right s (len s))))
              (go-strings-substr s lo hi))))))))

;; ── Split ────────────────────────────────────────────────────────

(define
  go-strings-split
  (fn (args)
    (cond
      (not (= (len args) 2))
      (list :eval-error :strings-split-arity (len args))
      :else
      (let ((s (first args)) (sep (nth args 1)))
        (cond
          (not (string? s)) (list :eval-error :strings-not-string s)
          (not (string? sep)) (list :eval-error :strings-not-string sep)
          (= (len sep) 0)
          ;; Empty separator: real Go splits to all chars; v0 keeps
          ;; behaviour simple — single-element slice.
          (list :go-slice (list s))
          :else
          (list :go-slice (go-strings-split-loop s sep 0 (list))))))))

(define
  go-strings-split-loop
  (fn (s sep start acc)
    (let ((idx (go-strings-index-loop s sep start)))
      (cond
        (< idx 0)
        (go-strings-split-finalize acc (go-strings-substr s start (len s)))
        :else
        (go-strings-split-loop s sep (+ idx (len sep))
          (go-strings-split-finalize acc
            (go-strings-substr s start idx)))))))

(define
  go-strings-split-finalize
  ;; Append a piece to acc, growing the list in order.
  (fn (acc piece)
    (cond
      (= (len acc) 0) (list piece)
      :else (go-name-concat acc (list piece)))))

;; ── Replace ──────────────────────────────────────────────────────

(define
  go-strings-replace
  ;; Replace(s, old, new, n). n < 0 = all.
  (fn (args)
    (cond
      (not (= (len args) 4))
      (list :eval-error :strings-replace-arity (len args))
      :else
      (let ((s (first args)) (old (nth args 1))
            (newv (nth args 2)) (n (nth args 3)))
        (cond
          (not (string? s)) (list :eval-error :strings-not-string s)
          (not (string? old)) (list :eval-error :strings-not-string old)
          (not (string? newv)) (list :eval-error :strings-not-string newv)
          (= (len old) 0) s
          :else (go-strings-replace-loop s old newv n 0 ""))))))

(define
  go-strings-replace-loop
  (fn (s old newv n start acc)
    (let ((idx (go-strings-index-loop s old start)))
      (cond
        (or (< idx 0) (= n 0))
        (str acc (go-strings-substr s start (len s)))
        :else
        (go-strings-replace-loop s old newv
          (cond (< n 0) -1 :else (- n 1))
          (+ idx (len old))
          (str acc (go-strings-substr s start idx) newv))))))

;; ── go-std-strings package value ─────────────────────────────────

(define
  go-std-strings
  (list :go-package "strings"
    (list
      (list "Contains"  (list :go-builtin-fn go-strings-contains))
      (list "HasPrefix" (list :go-builtin-fn go-strings-has-prefix))
      (list "HasSuffix" (list :go-builtin-fn go-strings-has-suffix))
      (list "Index"     (list :go-builtin-fn go-strings-index))
      (list "Count"     (list :go-builtin-fn go-strings-count))
      (list "Repeat"    (list :go-builtin-fn go-strings-repeat))
      (list "Join"      (list :go-builtin-fn go-strings-join))
      (list "ToUpper"   (list :go-builtin-fn go-strings-to-upper))
      (list "ToLower"   (list :go-builtin-fn go-strings-to-lower))
      (list "TrimSpace" (list :go-builtin-fn go-strings-trim-space))
      (list "Split"     (list :go-builtin-fn go-strings-split))
      (list "Replace"   (list :go-builtin-fn go-strings-replace)))))
