;; Canonical package declaration.
(define-package "self-healing"
  :description "Fixpoint-based self-healing build pipelines"
  :exports '(self-healing)
  :effects '(build.run.op infer.op)
  :requires '(oracle/core)
  :version "0.1.0"
)
;;; Self-Healing Build Library
;;;
;;; Implements fixpoint-based self-healing builds using iteration that
;;; supports async effects (build.run.op, infer.op, file operations).
;;;
;;; The key insight: fixing code is an iterative process that converges.
;;; We run: build -> check errors -> fix one -> rebuild -> repeat
;;; until errors are empty or we hit max iterations.
;;;
;;; Usage:
;;;   (self-healing-build 5)  ; Try up to 5 fix iterations
;;;   (self-healing-build-detect-cycles 5)  ; With cycle detection

;; ============================================================
;; Helper Functions (not in core primitives)
;; ============================================================

;; List accessor helpers
(define (caar x) (car (car x)))
(define (cadar x) (car (cdr (car x))))
(define (cadddr x) (car (cdr (cdr (cdr x)))))

;; assoc: find pair with matching key in alist
(define (assoc key alist)
  (cond
    ((null? alist) #f)
    ((equal? key (caar alist)) (car alist))
    (else (assoc key (cdr alist)))))

;; record-ref: access field in a Map value (returned by build effect) or alist
;; Works with:
;; 1. Runtime Map values with entries like [[{tag: Str, s: "key"}, value], ...]
;; 2. Alist format: ((key . value) ...)
;; 3. Alist with symbol keys: ((file . "test.ts") (line . 42) ...)
;; NOTE: OmegaLLM requires if conditions to be booleans, so we use explicit checks
(define (record-ref m key)
  (if (eq? m #f)
      #f
      ;; First try assoc for alist format (most common in tests)
      (let ((found (assoc key m)))
        (if (pair? found)
            (cdr found)
            ;; Try with string key for Map format
            (let ((key-str (if (symbol? key) (symbol->string key) key)))
              (record-ref-impl m key-str))))))

;; Implementation: iterate through map entries to find key by string
(define (record-ref-impl m key-str)
  (cond
    ;; Handle Map tag (runtime maps have entries field)
    ((and (pair? m) (eq? (car m) 'Map))
     (record-ref-entries (cadr m) key-str))
    ;; Handle raw entries list (vector of [key, val] pairs)
    ((pair? m)
     (record-ref-entries m key-str))
    ;; Handle vector/list of entries
    (else #f)))

;; Search entries for matching key (handles both Map entries and alists)
(define (record-ref-entries entries key-str)
  (cond
    ((null? entries) #f)
    ((pair? entries)
     (let ((entry (car entries)))
       (cond
         ;; Entry is [key, val] pair from Map - check if key matches
         ((and (pair? entry)
               (pair? (car entry))
               (eq? (caar entry) 'Str)
               (equal? (cadar entry) key-str))
          (cadr entry))  ; Return the value
         ;; Entry is (key . val) alist style with string key
         ((and (pair? entry)
               (equal? (car entry) key-str))
          (cdr entry))
         ;; Entry is (symbol . val) alist - convert symbol to string
         ((and (pair? entry)
               (symbol? (car entry))
               (equal? (symbol->string (car entry)) key-str))
          (cdr entry))
         (else (record-ref-entries (cdr entries) key-str)))))
    (else #f)))

;; ============================================================
;; Build State Representation
;; ============================================================

;; Build state: (build-state errors iteration cost)
(define (make-build-state errors iteration cost)
  (list 'build-state errors iteration cost))

(define (build-state? x)
  (and (pair? x) (eq? (car x) 'build-state)))

(define (state-errors state) (cadr state))
(define (state-iteration state) (caddr state))
(define (state-cost state) (cadddr state))

;; Update state after fix attempt
(define (state-with-errors state new-errors)
  (make-build-state new-errors
                    (+ 1 (state-iteration state))
                    (+ 1 (state-cost state))))

;; State equality (for convergence detection)
(define (states-equal? s1 s2)
  (equal? (state-errors s1) (state-errors s2)))

;; Error hash for cycle detection (concatenate file:line pairs)
(define (state-error-hash state)
  (let ((errors (state-errors state)))
    (if (null? errors)
        "empty"
        (fold-left
          (lambda (acc e)
            (string-append acc
              (string-append (or (record-ref e 'file) "?") ":")
              (number->string (or (record-ref e 'line) 0))
              ";"))
          ""
          errors))))

;; ============================================================
;; Initial State from Build
;; ============================================================

(define (initial-build-state)
  (let ((result (effect build.run.op)))
    (make-build-state
      (or (record-ref result 'errors) '())
      0    ; iteration
      0))) ; cost

;; ============================================================
;; Error Formatting
;; ============================================================

(define (format-errors errors)
  (if (null? errors)
      "(no errors)"
      (fold-left
        (lambda (acc e)
          (string-append acc
            "- " (or (record-ref e 'file) "unknown") ":"
            (number->string (or (record-ref e 'line) 0)) ": "
            (or (record-ref e 'message) "no message")
            " (" (or (record-ref e 'code) "?") ")\n"))
        ""
        errors)))

(define (format-error e)
  (string-append
    (or (record-ref e 'file) "unknown") ":"
    (number->string (or (record-ref e 'line) 0)) ":"
    (number->string (or (record-ref e 'col) 0)) ": "
    (or (record-ref e 'message) "no message")))

;; ============================================================
;; LLM-Based Error Fixer
;; ============================================================

;; Fix errors using LLM reentry
;; The LLM can use omega_eval to read files, write fixes, and verify
(define (fix-errors-with-llm state)
  (let ((errors (state-errors state)))
    (if (null? errors)
        state  ; No change if no errors

        ;; Ask LLM to fix the first error
        (let ((first-error (car errors)))
          (display (string-append "  Asking LLM to fix: " (format-error first-error) "\n"))

          (let ((fix-result
                  (effect infer.op
                    (string-append
                      "Fix this TypeScript error. You have access to omega_eval for file operations.\n\n"
                      "Error:\n" (format-error first-error) "\n\n"
                      "Available commands:\n"
                      "1. omega_eval(\"(effect file.read.op \\\"path\\\")\") - read a file\n"
                      "2. omega_eval(\"(effect file.write.op \\\"path\\\" \\\"content\\\")\") - write a file\n\n"
                      "Steps:\n"
                      "1. Read the file that has the error\n"
                      "2. Understand the error and generate a fix\n"
                      "3. Write the fixed content back\n"
                      "4. Use omega_return with a summary of what you fixed\n"))))

            ;; After LLM finishes, rebuild and return new state
            (let ((new-result (effect build.run.op)))
              (state-with-errors state (or (record-ref new-result 'errors) '()))))))))

;; ============================================================
;; Self-Healing Loop (Manual Iteration - supports effects)
;; ============================================================

;; Internal recursive helper for the loop
(define (self-healing-loop-iter state max-attempts)
  (let ((errors (state-errors state))
        (iteration (state-iteration state)))

    (display (string-append "Iteration " (number->string iteration)
                           ": " (number->string (length errors)) " errors\n"))

    (cond
      ;; Success: no errors
      ((null? errors)
       (list 'success state iteration))

      ;; Max attempts reached
      ((>= iteration max-attempts)
       (list 'partial state iteration))

      ;; Try to fix
      (else
       (let ((new-state (fix-errors-with-llm state)))
         ;; Check for convergence (same errors = stuck)
         (if (states-equal? state new-state)
             (list 'partial new-state iteration)
             (self-healing-loop-iter new-state max-attempts)))))))

;; Main entry point: self-healing build
(define (self-healing-build max-attempts)
  (display "=== Self-Healing Build ===\n")
  (display (string-append "Max attempts: " (number->string max-attempts) "\n\n"))

  (let* ((initial (initial-build-state))
         (result (self-healing-loop-iter initial max-attempts)))

    (let ((kind (car result))
          (final-state (cadr result))
          (iterations (caddr result)))

      (cond
        ((eq? kind 'success)
         (display "\n✓ Build is now clean!\n")
         (list 'result
               (cons 'kind 'success)
               (cons 'iterations iterations)
               (cons 'errors 0)))

        ((eq? kind 'partial)
         (display (string-append "\n⚠ Reached limit with "
                                (number->string (length (state-errors final-state)))
                                " errors remaining\n"))
         (list 'result
               (cons 'kind 'partial)
               (cons 'iterations iterations)
               (cons 'errors (length (state-errors final-state)))))

        (else
         (display "\n✗ Self-healing failed\n")
         (list 'result
               (cons 'kind 'failure)
               (cons 'iterations iterations)
               (cons 'errors (length (state-errors final-state)))))))))

;; ============================================================
;; Self-Healing with Cycle Detection
;; ============================================================

;; Internal recursive helper with cycle detection
(define (self-healing-cycle-iter state max-attempts seen-hashes)
  (let ((errors (state-errors state))
        (iteration (state-iteration state))
        (current-hash (state-error-hash state)))

    (display (string-append "Iteration " (number->string iteration)
                           ": " (number->string (length errors)) " errors"
                           " [hash: " current-hash "]\n"))

    (cond
      ;; Success: no errors
      ((null? errors)
       (list 'success state iteration #f))

      ;; Cycle detected: same error set seen before
      ((member current-hash seen-hashes)
       (display "⚠ Cycle detected - same error set seen before\n")
       (list 'cycle state iteration current-hash))

      ;; Max attempts reached
      ((>= iteration max-attempts)
       (list 'partial state iteration #f))

      ;; Try to fix
      (else
       (let ((new-state (fix-errors-with-llm state)))
         (self-healing-cycle-iter new-state max-attempts
                                  (cons current-hash seen-hashes)))))))

;; Self-healing with cycle detection
(define (self-healing-build-detect-cycles max-attempts)
  (display "=== Self-Healing Build (Cycle Detection) ===\n")
  (display (string-append "Max attempts: " (number->string max-attempts) "\n\n"))

  (let* ((initial (initial-build-state))
         (result (self-healing-cycle-iter initial max-attempts '())))

    (let ((kind (car result))
          (final-state (cadr result))
          (iterations (caddr result))
          (cycle-hash (cadddr result)))

      (cond
        ((eq? kind 'success)
         (display "\n✓ Build is now clean!\n")
         (list 'result
               (cons 'kind 'success)
               (cons 'iterations iterations)
               (cons 'errors 0)))

        ((eq? kind 'cycle)
         (display (string-append "\n⚠ Stopped due to cycle at hash: " cycle-hash "\n"))
         (list 'result
               (cons 'kind 'cycle)
               (cons 'iterations iterations)
               (cons 'errors (length (state-errors final-state)))
               (cons 'cycle-hash cycle-hash)))

        ((eq? kind 'partial)
         (display (string-append "\n⚠ Reached limit with "
                                (number->string (length (state-errors final-state)))
                                " errors remaining\n"))
         (list 'result
               (cons 'kind 'partial)
               (cons 'iterations iterations)
               (cons 'errors (length (state-errors final-state)))))

        (else
         (display "\n✗ Self-healing failed\n")
         (list 'result
               (cons 'kind 'failure)
               (cons 'iterations iterations)
               (cons 'errors (length (state-errors final-state)))))))))

;; ============================================================
;; Utility: Check build status without healing
;; ============================================================

(define (check-build-status)
  (let ((result (effect build.run.op)))
    (list
      (cons 'ok (record-ref result 'ok))
      (cons 'errors (length (or (record-ref result 'errors) '())))
      (cons 'warnings (length (or (record-ref result 'warnings) '())))
      (cons 'duration_ms (record-ref result 'duration_ms)))))
;; Manifest export anchors.
(define self-healing #t)
