//
// Copyright (c) 2014, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   21 Dec 2014  Andy Frank  Creation
//

using concurrent
using dom

**
** Dialog manages a modal window above page content.
**
** See also: [docDomkit]`docDomkit::Modals#dialog`
**
@Js class Dialog : Box
{
  new make() : super()
  {
    this.uid = nextId.val
    nextId.val = uid+1
    this.style.addClass("domkit-Dialog")
    this->tabIndex = 0
  }

  ** 'Str' or 'Elem' content displayed in title bar, or
  ** 'null' to hide title bar.
  Obj? title := null

  ** Protected sub-class callback invoked directly before dialog is opened.
  protected virtual Void onBeforeOpen() {}

  ** Protected sub-class callback invoked directly after dialog is opened.
  protected virtual Void onAfterOpen() {}

  ** Callback when a key is pressed while Dialog is open, including
  ** events that where dispatched outside the dialog.
  protected Void onKeyDown(|Event e| f) { this.cbKeyDown = f }

  ** Open this dialog in the current Window. If dialog
  ** is already open this method does nothing.
  Void open()
  {
    onBeforeOpen

    mask := Elem {
      it.id = "domkitDialog-mask-$uid"
      it->tabIndex = 0
      it.style.addClass("domkit-Dialog-mask")
      it.style->opacity = "0"
      it.onEvent("keydown", false) |e| { cbKeyDown?.call(e) }
    }

    this.frame = Elem
    {
      it.style.addClass("domkit-Dialog-frame")
      it.style.setAll([
        "transform": "scale(0.75)",
        "opacity": "0.0"
      ])
    }

    if (title != null)
    {
      telem := title as Elem ?:
        Label { it.style.addClass("def-label"); it.text=title.toStr }

      frame.add(Elem {
        it.style.addClass("domkit-Dialog-title")
        it.add(telem)
        it.onEvent("mousedown", false) |e| {
          e.stop
          vp  := Win.cur.viewport
          doc := Win.cur.doc
          off := doc.body.relPos(e.pagePos)
          fps := frame.pos
          fsz := frame.size
          Obj? fmove
          Obj? fup

          fmove = doc.onEvent("mousemove", true) |de| {
            pos := doc.body.relPos(de.pagePos)
            fx  := (pos.x.toInt - (off.x.toInt - fps.x.toInt)).max(0).min(vp.w.toInt - fsz.w.toInt)
            fy  := (pos.y.toInt - (off.y.toInt - fps.y.toInt)).max(0).min(vp.h.toInt - fsz.h.toInt)
            mask.style->display = "block"
            frame.style->position = "absolute"
            frame.style->left = "${fx}px"
            frame.style->top  = "${fy}px"
          }

          fup = doc.onEvent("mouseup", true) |de| {
            de.stop
            doc.removeEvent("mousemove", true, fmove)
            doc.removeEvent("mouseup",   true, fup)
          }
        }
      })
    }

    frame.add(this)
    mask.add(frame)

    body := Win.cur.doc.body
    body.add(mask)
    fireMounted

    mask.transition(["opacity":"1"], null, 100ms)
    frame.transition([
      "transform": "scale(1)",
      "opacity": "1"
    ], null, 100ms) { this.focus; onAfterOpen(); fireOpen }
  }

  ** Close this dialog. If dialog is already closed
  ** this method does nothing.
  Void close()
  {
    mask := Win.cur.doc.elemById("domkitDialog-mask-$uid")
    mask?.transition(["opacity":"0"], null, 100ms)
    frame?.transition(["transform": "scale(0.75)", "opacity": "0"], null, 100ms)
    {
      mask?.parent?.remove(mask)
      fireClose
    }
  }

  ** Callback when dialog is mounted but not yet visible.
  @NoDoc Void onMounted(|This| f) { cbMounted = f }

  ** Callback when dialog is opened.
  Void onOpen(|This| f) { cbOpen = f }

  ** Callback when popup is closed.
  Void onClose(|This| f) { cbClose = f }

  private Void fireMounted() { cbMounted?.call(this) }
  private Void fireOpen()    { cbOpen?.call(this)  }
  private Void fireClose()   { cbClose?.call(this) }

  private const Int uid
  private static const AtomicRef nextId := AtomicRef(0)

  private Elem? frame     := null
  private Func? cbMounted := null
  private Func? cbOpen    := null
  private Func? cbClose   := null
  private Func? cbKeyDown := null
}