import { CSSUnit } from '../Types'
import repaint from '../util/repaint'
import Tether from './Tether'

interface TetherCoordinates {
  element: HTMLElement
  x: 'l' | 'r'
  y: 't' | 'b'
  w: CSSUnit
  h: CSSUnit
  t: CSSUnit
  l: CSSUnit
}

export default class TetherLayout {
  #oldUpdates: WeakMap<HTMLElement, TetherCoordinates>
  #tethers: Tether[]
  #update: () => void
  #updateInterval: number | null

  constructor() {
    this.#oldUpdates = new WeakMap()
    this.#tethers = []
    this.#updateInterval = null
    this.#update = repaint(this.updateImmediately.bind(this))
  }

  get tethers() {
    return this.#tethers
  }

  add(tether: Tether): void {
    this.#tethers.push(tether)
    this.start()
  }

  remove(tether: Tether): void {
    const tethers = this.#tethers
    const index = tethers.indexOf(tether)

    if (index > -1) {
      tethers.splice(index, 1)
    }

    if (tethers.length < 0) {
      this.stop()
    }
  }

  start(): void {
    this.updateImmediately()

    if (this.#updateInterval === null) {
      window.addEventListener('resize', this.#update, true)
      window.addEventListener('scroll', this.#update, true)
      this.#updateInterval = setInterval(this.#update, 50)
    }
  }

  stop(): void {
    if (this.#updateInterval !== null) {
      window.removeEventListener('resize', this.#update)
      window.removeEventListener('scroll', this.#update)
      clearInterval(this.#updateInterval)
      this.#updateInterval = null
    }
  }

  updateImmediately(): void {
    const tethers = this.#tethers

    if (tethers.length === 0) {
      return
    }

    const updates: Array<TetherCoordinates> = []
    const root = document.documentElement
    let rw2 = root.clientWidth / 2
    let rh2 = root.clientHeight / 2
    const isRTL = root.dir === 'rtl'

    for (const tether of tethers) {
      const e = tether.element
      const rr = tether.reference.getBoundingClientRect()
      const w = rr.width
      const h = rr.height

      if (w === 0 && h === 0) {
        continue
      }

      let top = rr.top
      let left = rr.left
      let viewportWidth = root.clientWidth

      // Adjust top and left values if inside fixed-position popover.
      if (e.parentElement?.hasAttribute('popover')) {
        top = tether.reference.offsetTop
        left = tether.reference.offsetLeft

        const popover = e.parentElement
        viewportWidth = popover.clientWidth
        rw2 = viewportWidth / 2
        rh2 = popover.clientHeight / 2
      }

      // Calculate horizontal position, accounting for RTL direction
      let horizontalPosition: 'l' | 'r'
      let calculatedLeft = left

      if (isRTL) {
        // In RTL, we need to mirror the positioning
        // Convert left position to right position, then back to left for calculation
        const rightPosition = viewportWidth - left - w
        calculatedLeft = rightPosition
        // Determine which side based on mirrored position
        horizontalPosition = rightPosition < rw2 ? 'l' : 'r'
      } else {
        // In LTR, use normal logic: left side uses 'l', right side uses 'r'
        horizontalPosition = left < rw2 ? 'l' : 'r'
      }

      updates.push({
        element: e,
        x: horizontalPosition,
        y: top + h / 2 < rh2 ? 't' : 'b',
        l: `${calculatedLeft}px`,
        w: `${w}px`,
        t: `${top}px`,
        h: `${h}px`,
      })
    }

    for (const update of updates) {
      const e = update.element
      const oldUpdate = this.#oldUpdates.get(e) || ({} as TetherCoordinates)
      const es = e.style

      if (update.x !== oldUpdate.x) e.setAttribute('data-tether-rx', update.x)
      if (update.y !== oldUpdate.y) e.setAttribute('data-tether-ry', update.y)
      if (update.l !== oldUpdate.l) es.setProperty('--Tether-rl', update.l)
      if (update.w !== oldUpdate.w) es.setProperty('--Tether-rw', update.w)
      if (update.t !== oldUpdate.t) es.setProperty('--Tether-rt', update.t)
      if (update.h !== oldUpdate.h) es.setProperty('--Tether-rh', update.h)

      this.#oldUpdates.set(e, update)
    }
  }
}
