
require:
   repple ->
      Repl, Editor, Outputter
   codemirror
   "codemirror/addon/runmode/runmode"
   /html
   /dom

provide:
   fade-in
   Cards
   Into
   make-eval
   stdrepl
   stdeditor
   stdsetup
   colorize
   codemirror

globals:
   window
   document


fade-in{out, contents} =
   printed = out.log{contents}
   printed.style.transition = "opacity 500ms";
   printed.style.opacity = 0
   window.getComputedStyle{printed}.opacity
   printed.style.opacity = 1
   undefined


class ReloadAfterPromise:
   constructor{@promise} =
      pass


make-eval{evaluate
          targets
          bindings0 = null
          bindings = null
          dynamic-bindings = null} =
   ev where async ev{text} =
      [@] = this
      try:
         if bindings0:
            @setenv{bindings}
         if dynamic-bindings:
            @setenv with object with
               items{dynamic-bindings} each {k, f} ->
                  {k, f{}}
         try:
            res = await evaluate{text}
         catch ReloadAfterPromise? rp:
            await rp.promise
            return ev{text}
         if Into? res:
            if targets[res.location] as loc:
               fade-in{Outputter{loc, true}, res.contents}
               #success{undefined}
            elif document.get-element-by-id{res.location} as loc2:
               fade-in{Outputter{loc2, true}, res.contents}
               #success{undefined}
            else:
               #success{res.contents}
         else:
            #success{res}
      catch e:
         #failure{e}
      finally:
         if bindings0:
            @setenv{bindings0}

class Into:
   constructor{@location, @contents} =
      pass



class Cards:

   constructor{@config} =
      [@] &: @config
      @order-cards{@cards}

   order-cards{cards} =
      ord = items{cards} each
         {k, {__main => v and {=> Number? order}}} -> v
         else -> continue
      cards.__order = ord.sort with {a, b} -> a.order - b.order
      cards.__index = -1
      items{cards} each {k, v} when not k.starts-with{"__"} ->
         @order-cards{v}

   get{path} =
      target = path.reduce{f, @cards} where f{c, name} =
         if name === null:
            c.__index = [c.__index + 1] mod c.__order.length
            {__main = c.__order[c.__index]}
         else:
            c[name]
      target.__main

   show-card{*path} =
      target = @get{path}
      contents = document.get-element-by-id{target.div-id}.innerHTML
      Into{@target, div[raw] % contents}



fill-bindings{settings} =
   settings.bindings or= {=}
   settings.bindings0 =
      if settings.reset-bindings:
         object with items{settings.bindings} each
            {k, v} -> {k, global[k]}
      else:
         null
   settings.targets or= {=}
   settings

stdrepl{elements, fill-bindings! settings} =
   ;; bindings = settings.bindings or {=}
   ;; dynb = settings.dynamic-bindings
   ;; bindings0 =
   ;;    if settings.reset-bindings:
   ;;       object with items{bindings} each {k, v} -> {k, global[k]}
   ;;    else:
   ;;       null
   ;; targets = settings.targets or {=}
   repl = Repl with {
      elements = elements
      eval = make-eval{
         settings.evaluate
         settings.targets
         settings.bindings0
         settings.bindings
         settings.dynamic-bindings
      }
      setenv = settings.setenv
      continuate = settings.continuate
      codemirror = codemirror
      lang = settings.lang
      theme = settings.theme
      indent = settings.indent
   }
   if not settings.bindings0:
      repl.setenv{settings.bindings}
   repl


dom-node-clear{x} =
   while x.children.length > 0:
      x.removeChild{x.children[0]}

stdeditor{elements, fill-bindings! settings, repl} =
   ;; bindings = settings.bindings or {=}
   ;; dynb = settings.dynamic-bindings
   ;; bindings0 =
   ;;    if settings.reset-bindings:
   ;;       object with items{bindings} each {k, v} -> {k, global[k]}
   ;;    else:
   ;;       null
   ;; targets = settings.targets or {=}
   ;; ev = make-eval{settings.evaluate, targets, bindings0, bindings, dynb}
   ev = make-eval{
      settings.evaluate
      settings.targets
      settings.bindings0
      settings.bindings
      settings.dynamic-bindings
   }
   ed = Editor with {
      elements = elements
      ;; line-numbers = true
      async eval{text} =
         if repl:
            await repl.process{"compile()", true}
            repl.sink{}
            #success{undefined}
         else:
            await ev.call{this, text}
      setenv = settings.setenv
      prepare{} =
         if elements.aside:
            dom-node-clear{elements.aside}
      success{v} =
         if elements.aside and v !== undefined:
            Outputter{elements.aside, true}.log{v}
      failure{err} =
         if elements.aside:
            Outputter{elements.aside, true}.log{err}
      codemirror = codemirror
      lang = settings.lang
      theme = settings.theme
      indent = settings.indent
   }
   if not settings.bindings0:
      ed.setenv{settings.bindings}
   ed



div-names = ___.split{"\n"}.map{x -> x.trim{}}.filter{x -> x} with
   """
   repl
   aside
   scrollback
   inputline
   replarea
   editor
   editorarea
   """


async stdsetup{config} =

   ;; Get all relevant divs
   di = config.div-id
   elements = object with div-names each name ->
      {name, document.query-selector{'#{di} .repple-{name}'}}

   targets = {
      aside = elements.aside
   }

   c = Cards with {
      cards = config.cards
      target = .aside
   }

   out-aside =
      if elements.aside:
         Outputter{elements.aside}
      else:
         null

   log = out-aside??.log.bind{out-aside}
   h{tag, *args} =
      {props, children} = match args:
         {Array? or String? or ENode?, *} -> {{=}, args}
         {props, *children} -> {props, children}
      ENode{tag.split{R" *(?=[.#]|$)"}, props, children}

   bindings = {
      show-card = c.show-card.bind{c}
      = dom
      = Into
      = Outputter
      = ReloadAfterPromise
      = log
      = h
      logh{text} = log{div[raw] % text}
      compile{} =
         if editor and repl:
            repl.process{editor.cm.get-value{}, false}
         else:
            throw E.compile with
               '"compile" function only valid if there is an editor and a repl'
   } & [config.bindings or {=}]

   dynamic-bindings = {
      $out{} = repl??.repple.io[repl.repple.current]
      $repl{} = repl
      $editor{} = editor
      $repple{} = repl
   }

   settings = config & {
      = targets
      = bindings
      = dynamic-bindings
   }

   repl = if{elements.repl, stdrepl{elements, settings}, null}
   editor = if{elements.editor, stdeditor{elements, settings, repl}, null}

   if repl:
      repl.process{config.code or "", false}

   if editor:
      editor.cm.set-value{config.code}
      if not repl:
         editor.eval{config.code}

   focus{} =
      if repl:
         repl.cm.focus{}
      elif editor:
         editor.cm.focus{}

   if config.body-focus:
      document.body.onclick{} =
         if document.get-selection{}.to-string{} == "":
            focus{}

   if config.focus:
      focus{}

   rval = {= repl, = editor, = config}
   global[config.variable] = rval

   if repl:
      repl.setenv{}

   colorize{config.lang, config.theme}

   match config.after:
      Function? f ->
         f{rval}
      String? name ->
         global[name]{rval}
      else ->
         rval

   rval

colorize{lang, theme} =
   document.query-selector-all{".repple-paste"} each elem ->
      hl = {}
      codemirror.run-mode{elem.text-content, lang} with {text, style} ->
         hl.push with
            if style:
               then: span[^[".cm-" + style]] % text
               else: span % text
      elem.innerHTML =
         html with
            span %
               class = 'cm-s-{theme}'
               hl

