import { runInOp } from "../display/operations"
import { addToScrollPos } from "../display/scrolling"
import { regLineChange } from "../display/view_tracking"
import { heightAtLine, lineIsHidden } from "../line/spans"
import { lineNo, updateLineHeight } from "../line/utils_line"
import { widgetHeight } from "../measurement/widgets"
import { changeLine } from "./changes"
import { eventMixin } from "../util/event"

// Line widgets are block elements displayed above or below a line.

export function LineWidget(doc, node, options) {
  if (options) for (let opt in options) if (options.hasOwnProperty(opt))
    this[opt] = options[opt]
  this.doc = doc
  this.node = node
}
eventMixin(LineWidget)

function adjustScrollWhenAboveVisible(cm, line, diff) {
  if (heightAtLine(line) < ((cm.curOp && cm.curOp.scrollTop) || cm.doc.scrollTop))
    addToScrollPos(cm, null, diff)
}

LineWidget.prototype.clear = function() {
  let cm = this.doc.cm, ws = this.line.widgets, line = this.line, no = lineNo(line)
  if (no == null || !ws) return
  for (let i = 0; i < ws.length; ++i) if (ws[i] == this) ws.splice(i--, 1)
  if (!ws.length) line.widgets = null
  let height = widgetHeight(this)
  updateLineHeight(line, Math.max(0, line.height - height))
  if (cm) runInOp(cm, () => {
    adjustScrollWhenAboveVisible(cm, line, -height)
    regLineChange(cm, no, "widget")
  })
}
LineWidget.prototype.changed = function() {
  let oldH = this.height, cm = this.doc.cm, line = this.line
  this.height = null
  let diff = widgetHeight(this) - oldH
  if (!diff) return
  updateLineHeight(line, line.height + diff)
  if (cm) runInOp(cm, () => {
    cm.curOp.forceUpdate = true
    adjustScrollWhenAboveVisible(cm, line, diff)
  })
}

export function addLineWidget(doc, handle, node, options) {
  let widget = new LineWidget(doc, node, options)
  let cm = doc.cm
  if (cm && widget.noHScroll) cm.display.alignWidgets = true
  changeLine(doc, handle, "widget", line => {
    let widgets = line.widgets || (line.widgets = [])
    if (widget.insertAt == null) widgets.push(widget)
    else widgets.splice(Math.min(widgets.length - 1, Math.max(0, widget.insertAt)), 0, widget)
    widget.line = line
    if (cm && !lineIsHidden(doc, line)) {
      let aboveVisible = heightAtLine(line) < doc.scrollTop
      updateLineHeight(line, line.height + widgetHeight(widget))
      if (aboveVisible) addToScrollPos(cm, null, widget.height)
      cm.curOp.forceUpdate = true
    }
    return true
  })
  return widget
}
