// MorphTransition: SVG elements morphed via VT proxy <div>s, text via Marp's VT.
// We wrap Marp's startViewTransition callback to reposition proxy divs after navigation
// so VT captures old=FROM positions and new=TO positions and animates between them.

function stampMap(svgEl) {
  const map = new Map()
  for (const g of svgEl.querySelectorAll('[data-element-id]')) {
    map.set(g.getAttribute('data-element-id'), g)
  }
  return map
}

;(function () {
  const origStartVT = document.startViewTransition?.bind(document)
  if (!origStartVT) return
  document.startViewTransition = function (callback) {
    if (window.__morphVTIntercept) {
      const intercept = window.__morphVTIntercept
      window.__morphVTIntercept = null
      const vt = origStartVT(async () => {
        await callback()
        intercept.afterNav()
      })
      intercept.onVT(vt)
      return vt
    }
    return origStartVT(callback)
  }
})()

function _createProxy (el, parentSvgEl, rect) {
  const svgRect = parentSvgEl.getBoundingClientRect()
  const vb      = parentSvgEl.viewBox.baseVal
  const scaleX  = vb.width  / svgRect.width
  const scaleY  = vb.height / svgRect.height
  const vbX = (rect.left - svgRect.left) * scaleX + (vb.x || 0)
  const vbY = (rect.top  - svgRect.top)  * scaleY + (vb.y || 0)
  const vbW = rect.width  * scaleX
  const vbH = rect.height * scaleY

  const div = document.createElement('div')
  div.style.cssText = `position:fixed;top:${rect.top}px;left:${rect.left}px;` +
    `width:${rect.width}px;height:${rect.height}px;overflow:visible;pointer-events:none`

  const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
  svg.setAttribute('viewBox', `${vbX} ${vbY} ${vbW} ${vbH}`)
  svg.style.cssText = `width:${rect.width}px;height:${rect.height}px;overflow:visible`
  const defs = parentSvgEl.querySelector('defs')
  if (defs) svg.appendChild(defs.cloneNode(true))
  svg.appendChild(el.cloneNode(true))
  div.appendChild(svg)
  return div
}

function _repositionProxy (div, el, parentSvgEl, rect) {
  div.style.top    = `${rect.top}px`
  div.style.left   = `${rect.left}px`
  div.style.width  = `${rect.width}px`
  div.style.height = `${rect.height}px`

  const svgRect = parentSvgEl.getBoundingClientRect()
  const vb      = parentSvgEl.viewBox.baseVal
  const scaleX  = vb.width  / svgRect.width
  const scaleY  = vb.height / svgRect.height
  const vbX = (rect.left - svgRect.left) * scaleX + (vb.x || 0)
  const vbY = (rect.top  - svgRect.top)  * scaleY + (vb.y || 0)
  const vbW = rect.width  * scaleX
  const vbH = rect.height * scaleY

  const svg = div.querySelector('svg')
  if (!svg) return
  svg.setAttribute('viewBox', `${vbX} ${vbY} ${vbW} ${vbH}`)
  svg.style.width  = `${rect.width}px`
  svg.style.height = `${rect.height}px`
  const g = svg.querySelector('[data-element-id]')
  if (g) {
    const clone = el.cloneNode(true)
    clone.style.removeProperty('opacity')
    g.replaceWith(clone)
  }
}

class MorphTransition {
  constructor (from, to) {
    this._from      = from
    this._to        = to
    this._proxies   = []
    this._hiddenEls = []
    this._vt        = null
  }

  cancel () {
    if (this._vt) { this._vt.skipTransition(); this._vt = null }
    window.__morphVTIntercept = null
    this._cleanup()
  }

  _cleanup () {
    for (const { proxy } of this._proxies) proxy.remove()
    this._proxies = []
    for (const el of this._hiddenEls) el.style.removeProperty('opacity')
    this._hiddenEls = []
  }

  capture () {
    const fromSvgEl = this._from.querySelector('.excalidraw-stage svg')
    const toSvgEl   = this._to.querySelector('.excalidraw-stage svg')
    if (!fromSvgEl || !toSvgEl) return

    const fromMap = stampMap(fromSvgEl)
    const toMap   = stampMap(toSvgEl)

    for (const [id, fromEl] of fromMap) {
      const toEl = toMap.get(id)
      if (!toEl) continue
      const rect = fromEl.getBoundingClientRect()
      if (!rect.width && !rect.height) continue

      const proxy = _createProxy(fromEl, fromSvgEl, rect)
      proxy.style.setProperty('view-transition-name', `svgp-${id}`)
      document.body.appendChild(proxy)
      this._proxies.push({ id, proxy })

      // Hide actual elements — they'd otherwise double-render in the root VT snapshot.
      fromEl.style.opacity = '0'
      this._hiddenEls.push(fromEl)
      toEl.style.opacity = '0'
      this._hiddenEls.push(toEl)
    }

    if (!this._proxies.length) return

    window.__morphVTIntercept = {
      afterNav: () => {
        const toMap = stampMap(toSvgEl)
        for (const { id, proxy } of this._proxies) {
          const toEl = toMap.get(id)
          if (!toEl) continue
          _repositionProxy(proxy, toEl, toSvgEl, toEl.getBoundingClientRect())
        }
      },
      onVT: (vt) => {
        this._vt = vt
        vt.finished.then(() => this._cleanup())
      },
    }
  }

  play () {
    // VT handles all animation. Text morphs via named spans. SVG morphs via proxy divs.
  }
}

window.__transitions.register('morph', (from, to) =>
  new MorphTransition(from, to)
)
