#lang racket/base

(require scribble/manual scribble/core scribble/decode
         racket/list racket/sandbox)

(provide (all-from-out scribble/manual)
         RL GUIDE REFERENCE cmd defcmd check-all-documented)

(define RL '(lib "readline/readline.scrbl"))
(define GUIDE '(lib "scribblings/guide/guide.scrbl"))
(define REFERENCE '(lib "scribblings/reference/reference.scrbl"))

(define commands
  (let ([c #f])
    (λ ()
      (unless c
        (define e (call-with-trusted-sandbox-configuration
                   (λ () (make-evaluator 'racket/base))))
        (e '(require xrepl/xrepl))
        (e '(current-namespace (module->namespace 'xrepl/xrepl)))
        (set! c (e '(for*/list ([cs (in-list (reverse commands-dict))]
                                [c (in-list (reverse (cdr cs)))])
                      (list (car (command-names c))
                            (cdr (command-names c))
                            (command-argline c)
                            (command-blurb c)))))
        (kill-evaluator e))
      c)))
(define documented '())

(define (cmd* name0 . more)
  (define name (if (symbol? name0) name0 (string->symbol name0)))
  (define full-name
    (or (and (assq name (commands)) name)
        (for/or ([c (in-list (commands))]) (and (memq name (cadr c)) (car c)))
        (error 'cmd "unknown command: ~s" name)))
  (define content
    (litchar (let ([s (format ",~a" name)])
               (if (pair? more) (apply string-append s " " more) s))))
  (link-element "plainlink" content `(xrepl ,(format "~a" full-name))))

(define-syntax-rule (cmd name more ...) (cmd* 'name more ...))

(define (cmd-index name)
  (define namestr (format ",~a" name))
  (define tag `(xrepl ,(format "~a" name)))
  (define content (cmd* name))
  (define ielem
    (index-element #f content tag (list namestr) (list content)
                   'xrepl-command))
  (toc-target-element #f (list ielem) tag))

(define (defcmd* name . text)
  (set! documented (cons name documented))
  (define-values [other-names argline blurb]
    (apply values (cond [(assq name (commands)) => cdr]
                        [else (error 'defcmd "unknown command: ~s" name)])))
  (define header
    (list (cmd-index name) (litchar (string-append " " (or argline "")))))
  (define desc
    (list (hspace 2) (make-element 'italic blurb)))
  (define synonyms
    (and (pair? other-names)
         (list (hspace 2)
               "[Synonyms: "
               (add-between (map (λ (n) (litchar (format ",~a" n)))
                                 other-names)
                            " ")
               "]")))
  (splice
   (list* (tabular #:style 'boxed `((,header) (,desc)
                                    ,@(if synonyms `((,synonyms)) `())))
          "\n" "\n" text)))

(define-syntax-rule (defcmd name text ...) (defcmd* 'name text ...))

(define (check-all-documented)
  (unless (= (length documented) (length (remove-duplicates documented)))
    (error 'xrepl-docs "some commands were documented multiple times"))
  (let ([missing (remove* documented (map car (commands)))])
    (when (pair? missing)
      (error 'xrepl-docs "missing command documentation: ~s" missing))))
