import { Drag } from './drag'
import { Node } from './node'
import { NodeList } from './nodelist'
import { NodeView } from './nodeview'
import { Zoom } from './zoom'
import { Mouse, Transform, ZoomSource } from './utils'

export class Area {
  el: HTMLElement
  container: HTMLElement
  mouse: Mouse = { x: 0, y: 0 }
  nodeList = new NodeList()
  viewport = { width: 0, height: 0 }
  lastZoom = 0
  lastTranslate = { x: 0, y: 0 }
  onClickCallback = (_nodeView: NodeView, _nodeList: NodeList, _e: MouseEvent): void => {}

  // translate(143.40070190429685px, 90.96899261474609px) scale(0.41740129740650794)
  // transform: Transform = { k: 0.41740129740650794, x: 143.40070190429685, y: 90.96899261474609 }
  transform: Transform = { k: 0.1, x: 0, y: 0 }

  private _startPosition: Transform | null = null
  private _zoom: Zoom
  private _drag: Drag

  constructor(container: HTMLElement, addNodeOnDblClick: boolean) {
    this.container = container

    this.viewport = { width: container.clientWidth, height: container.clientHeight }
    this.nodeList.setViewport(this.viewport)
   
    const inner = document.createElement('div')
    inner.className = 'inner'

    const el = this.el = inner
    el.style.transformOrigin = '0 0'

    this._zoom = new Zoom(container, el as HTMLElement, 0.08, this.onZoom.bind(this))
    this._drag = new Drag(container, this.onTranslate.bind(this), this.onStart.bind(this), () => {}, this.onIncrementTranslate.bind(this))

    if (addNodeOnDblClick) {
      inner.addEventListener('dblclick', (e: MouseEvent) => {
        e.stopPropagation()
        e.stopImmediatePropagation()
  
        const node = new Node((this.nodeList.nodes.length + 1).toString())
        node.position = [ e.offsetX - 5, e.offsetY - 5 ]
  
        this.addNode(node, this.onClickCallback)
      })
    }

    this.container.addEventListener('pointermove', this.pointerMove.bind(this))
    this.container.appendChild(inner)

    this.update()
  }

  pointerMove(e: PointerEvent) {
    const { clientX, clientY } = e
    const rect = this.el.getBoundingClientRect()
    const x = clientX - rect.left
    const y = clientY - rect.top
    const k = this.transform.k
      
    this.mouse = { x: x / k, y: y / k }
  }

  onStart() {
    this._startPosition = { ...this.transform }
  }

  onTranslate(dx: number, dy: number) {
    if (this._zoom.translating) return // lock translation while zoom on multitouch
    if (this._startPosition) this.translate(this._startPosition.x + dx, this._startPosition.y + dy)
  }

  onIncrementTranslate(dx: number, dy: number) {
    if (this._zoom.translating) return // lock translation while zoom on multitouch

    this.lastTranslate.x -= dx
    this.lastTranslate.y -= dy

    this.translate(this.lastTranslate.x, this.lastTranslate.y)
  }

  onZoom(delta: number, ox: number, oy: number, source: ZoomSource) {
    this.zoom(this.transform.k * (1 + delta), ox, oy, source)
    this.update()
  }

  translate(x: number, y: number) {
    const params = { transform: this.transform, x, y }

    this.transform.x = params.x
    this.transform.y = params.y

    this.update()
  }

  zoom(zoom: number, ox = 0, oy = 0, source: ZoomSource) {
    const k = this.transform.k
    const params = { transform: this.transform, zoom, source }

    const d = (k - params.zoom) / ((k - zoom) || 1)

    this.transform.k = params.zoom || 1
    this.transform.x += ox * d
    this.transform.y += oy * d

    this.applyZoom(params.zoom)
    this.update()
  }

  setTransform(zoom: number, ox = 0, oy = 0) {
    this.transform.k = zoom
    this.transform.x = ox
    this.transform.y = oy
  }

  applyZoom(zoom: number) {
    this.lastZoom = zoom
    this.nodeList.nodes.forEach(n => {
      n.applyZoom(zoom)
    })
  }

  addNode(node: Node, onClick = (_nodeView: NodeView, _nodeList: NodeList, _e: MouseEvent): void => {}) {
    const nv = new NodeView(node, this.nodeList, onClick)
    nv.applyZoom(this.lastZoom)

    this.onClickCallback = onClick
    this.nodeList.nodes.push(nv)
    this.appendChild(nv.el)
  }

  update() {
    const t = this.transform
    this.lastTranslate = { x: t.x, y: t.y }
    this.el.style.transform = `translate(${t.x}px, ${t.y}px) scale(${t.k})`
  }

  private appendChild(el: HTMLElement) {
    this.el.appendChild(el)
  }

  private removeChild(el: HTMLElement) {
    this.el.removeChild(el)
  }
}
