;; Shell prelude — sugar functions over shell.* effects
;; Loaded into OmegaSandbox when shell is initialized.
;;
;; NOTE: All functions use FIXED arity (no rest args / variadic).
;; Omega-lisp's serialize/hydrate mangles rest-arg binding names
;; (e.g. payload -> payload$bid#4), breaking forked sessions.
;; Fixed-arity lambdas survive hydration correctly.

;; ─── Working directory state ─────────────────────────────────────────────────

(define *cwd* "")

;; String helpers (not built into omega-lisp)
(define (starts-with? s prefix)
  (if (> (string-length prefix) (string-length s))
    #f
    (equal? (substring s 0 (string-length prefix)) prefix)))

;; Find last occurrence of char in string, return index or -1
(define (string-last-index-of s ch)
  (let loop ((i (- (string-length s) 1)))
    (cond
      ((< i 0) -1)
      ((equal? (substring s i (+ i 1)) ch) i)
      (else (loop (- i 1))))))

;; Get parent path: "system.health" → "system", "system" → "", "" → ""
(define (parent-path p)
  (let ((idx (string-last-index-of p ".")))
    (if (< idx 0) "" (substring p 0 idx))))

;; Resolve a path relative to *cwd*
;; "/" = root, ".." = parent, "" = cwd, "foo" = cwd.foo, "/foo" = foo (absolute)
(define (resolve-path path)
  (cond
    ((equal? path "") *cwd*)
    ((equal? path "/") "")
    ((equal? path "..") (parent-path *cwd*))
    ((starts-with? path "/") (substring path 1 (string-length path)))
    ((equal? *cwd* "") path)
    (else (string-append *cwd* "." path))))

;; Change working directory — validates target exists via $introspect
;; ".." and "/" skip validation (parent prefix may not be an actor; root always exists)
(define (cd path)
  (let ((resolved (resolve-path path)))
    (if (or (equal? path "..") (equal? path "/") (equal? resolved ""))
      (begin (set! *cwd* resolved) *cwd*)
      (let ((result (effect shell.introspect resolved)))
        (if (get result "ok")
          (begin (set! *cwd* resolved) *cwd*)
          (error (string-append "cd: no such actor: " resolved)))))))

;; Print working directory
(define (pwd) *cwd*)

;; ─── Core actor operations ───────────────────────────────────────────────────

;; Invoke an actor: (invoke "target" "op") — no payload
;; For payload, use: (invoke-with "target" "op" "{payload}")
(define (invoke target op)
  (effect shell.invoke (resolve-path target) op "{}"))

;; Invoke with explicit payload: (invoke-with "target" "op" "{json}")
(define (invoke-with target op payload)
  (effect shell.invoke (resolve-path target) op payload))

;; Emit a fire-and-forget event: (emit "event") — no payload
;; For payload, use: (emit-with "event" "{payload}")
(define (emit event)
  (effect shell.emit event "{}"))

;; Emit with explicit payload: (emit-with "event" "{json}")
(define (emit-with event payload)
  (effect shell.emit event payload))

;; Respond with text to caller
(define (respond text)
  (effect shell.respond text))

;; Shorthand: list children of an actor as a proper Scheme list of mount strings.
;; Returns (mount1 mount2 ...) — composable with map, filter, etc.
(define (ls mount)
  (effect shell.ls (resolve-path mount)))

;; Shorthand: get full introspect metadata for an actor
(define (stat mount)
  (effect shell.introspect (resolve-path mount)))

;; Shorthand: get source code of an actor
(define (cat mount)
  (effect shell.invoke (resolve-path mount) "$source-code" "{}"))

;; Shorthand: get children list from an actor (alias for ls)
(define (children mount)
  (effect shell.ls (resolve-path mount)))

;; Shorthand: health-check an actor via $ping
(define (health-check mount)
  (effect shell.invoke (resolve-path mount) "$ping" "{}"))

;; Echo: invoke an actor with a payload value (like echo > in Unix)
(define (echo mount op val)
  (effect shell.invoke (resolve-path mount) op val))

;; Ps: list actors with health status — returns list of (mount . status) pairs
(define (ps mount)
  (map (lambda (m) (list m (health-check m))) (ls mount)))

;; Shorthand: prompt an actor (escapes quotes, backslashes, newlines, tabs)
(define (prompt mount text)
  (let* ((e1 (string-replace-all text "\\" "\\\\"))
         (e2 (string-replace-all e1 "\"" "\\\""))
         (e3 (string-replace-all e2 "\n" "\\n"))
         (e4 (string-replace-all e3 "\t" "\\t"))
         (e5 (string-replace-all e4 "\r" "\\r")))
    (effect shell.invoke (resolve-path mount) "$prompt"
      (string-append "{\"text\":\"" e5 "\"}"))))

;; Pipe: thread a value through 1-3 functions (fixed arity, hydration-safe)
;; For more stages, nest: (pipe3 (pipe3 v f1 f2 f3) f4 f5 f6)
(define (pipe1 val f1) (f1 val))
(define (pipe2 val f1 f2) (f2 (f1 val)))
(define (pipe3 val f1 f2 f3) (f3 (f2 (f1 val))))

;; Backward compat: pipe is pipe2 (most common case: 2 transforms)
(define pipe pipe2)

;; ─── Additional tool sugar ──────────────────────────────────────────────────

;; Evaluate Lisp code in the REPL sandbox
(define (evaluate-lisp code)
  (effect shell.eval code))

;; Define a live component from code string
(define (define-component name code)
  (effect shell.define-component name code))

;; Load a saved component by name
(define (load-component name)
  (effect shell.load-component name))

;; Save a component to the store
(define (save-component name code)
  (effect shell.save-component name code))

;; List available components
(define (list-components)
  (effect shell.list-components))

;; Search components by query
(define (find-components query)
  (effect shell.find-components query))

;; Get source code of a component
(define (get-component-source name)
  (effect shell.get-component-source name))

;; Publish a component to the registry
(define (publish-component name)
  (effect shell.publish-component name))

;; Install a component from the registry
(define (install-component name)
  (effect shell.install-component name))

;; Resolve natural language intent to an op
(define (resolve-intent text)
  (effect shell.resolve-intent text))

;; Query trace/observability data
(define (trace-query query)
  (effect shell.trace-query query))

;; List REPL packages
(define (repl-packages)
  (effect shell.repl-packages))

;; Get documentation for a symbol
(define (repl-doc symbol)
  (effect shell.repl-doc symbol))

;; ─── Process management (VISION §13 — matrix.spawn/kill) ─────────────────

;; Spawn a new child actor: (mkdir "parent" "childId") — uses DynamicActor
;; For custom class: (mkdir-class "parent" "childId" "ComponentClass")
(define (mkdir parent child-id)
  (effect shell.spawn (resolve-path parent) child-id "DynamicActor"))

;; Spawn with explicit component class
(define (mkdir-class parent child-id class)
  (effect shell.spawn (resolve-path parent) child-id class))

;; Kill (dispose) an actor: (rm "mount")
(define (rm mount)
  (effect shell.kill (resolve-path mount)))

;; ─── Session management (VISION §7 — fork/watch) ────────────────────────────

;; Fork the current session — creates COW copy with all definitions inherited.
;; Returns { ok: true, sessionId: "new-id" }
(define (fork new-session-id)
  (effect shell.fork new-session-id ""))

;; Fork with restricted capabilities (comma-separated effect names)
(define (fork-restricted new-session-id allowed-effects)
  (effect shell.fork new-session-id allowed-effects))

;; Poll an actor's state N times, collecting results.
;; (watch-poll "system.health" "health.status" 5) → list of 5 results
;; For true async subscription, a shell.subscribe effect is needed (future).
(define (watch-poll mount op times)
  (let ((resolved (resolve-path mount)))
    (let loop ((i 0) (results (list)))
      (if (>= i times)
        (reverse results)
        (loop (+ i 1) (cons (effect shell.invoke resolved op "{}") results))))))
