const { h, register, preactHooks } = globalThis.ami
const { useState, useEffect, useRef, useMemo } = preactHooks

const LOCALE = (document.documentElement.lang || navigator.language).replace('-', '_').toLowerCase()
const HASH_PARAM = 'iframe'
const POLLING_INTERVAL = 200
const DEFAULT_HEIGHT = 800
const DEFAULT_ALLOW = 'geolocation'
const ALLOWED_PROTOCOLS = new Set(['http:', 'https:'])
const PARAMS_TO_PASS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'gclid']

function RIOIframe ({ src, height: defaultHeight = DEFAULT_HEIGHT, title }) {
  const id = useMemo(() => `_${globalThis.crypto.randomUUID()}`, [])
  const ref = useRef(null)
  const [height, setHeight] = useState(defaultHeight)
  const [url, setUrl] = useState({})

  useEffect(() => {
    if (!ref.current) {
      return null
    }

    const iframe = ref.current
    const isFirst = document.querySelector('rio-iframe') === iframe.parentNode.host

    const currUrl = getUrl(src, isFirst)
    setUrl(currUrl)

    const postMessage = () => iframe.contentWindow.postMessage({ id, locale: LOCALE, index: 0 }, currUrl?.origin)

    const onMessage = event => {
      // Ignore messages from other iframes (by id).
      if (event?.data?.id !== id) {
        return
      }

      // Set the height.
      if (event.data.height) {
        setHeight(event.data.height)
      }

      // Update the URL (first iframe only).
      if (isFirst && event.data.url) {
        // Only Unicode characters will be encoded in the event.data.url.
        // Make sure the entire URL is encoded in the hash without double-encoding any potential Unicode characters.
        location.hash = `${HASH_PARAM}=${toBase64(decodeURI(event.data.url))}`
      }
    }

    // Poll iframe when loaded.
    const onLoad = () => {
      postMessage()
      const intervalRef = setInterval(() => iframe.contentWindow ? postMessage() : clearInterval(intervalRef),
        POLLING_INTERVAL)
    }

    window.addEventListener('message', onMessage)
    iframe.addEventListener('load', onLoad)

    // Unbind event listeners on unmount.
    return () => {
      window.removeEventListener('message', onMessage)
      iframe.removeEventListener('load', onLoad)
    }
  }, [src])

  return h('iframe', {
    id,
    height: url?.href ? height : 0,
    className: 'block w-full',
    src: url?.href,
    ref,
    title,
    allow: DEFAULT_ALLOW,
    allowfullscreen: 'allowfullscreen',
    loading: 'lazy'
  })
}

function getUrl (src, isFirst) {
  let url = sanitizeUrl(src)
  if (!url) {
    return url
  }

  if (isFirst) {
    url = deepLink(url)
  }
  updateParams(url)
  return url
}

function sanitizeUrl (url) {
  if (!url) {
    return null
  }

  const urlObj = new URL(url, globalThis.location.href)

  if (!ALLOWED_PROTOCOLS.has(urlObj.protocol)) {
    console.error(`Unsupported iframe protocol: ${url}`)
    return null
  }

  return urlObj
}

function deepLink (urlObj) {
  const hashParams = new URLSearchParams(location.hash.substring(1))
  if (hashParams.has(HASH_PARAM)) {
    return new URL(fromBase64(hashParams.get(HASH_PARAM)), urlObj)
  }

  return urlObj
}

function updateParams (urlObj) {
  const pageParams = new URLSearchParams(location.search)

  for (const param of PARAMS_TO_PASS) {
    pageParams.has(param) && urlObj.searchParams.set(param, pageParams.get(param))
  }
}

function toBase64 (str) {
  const textEncoder = new globalThis.TextEncoder()
  // eslint-disable-next-line unicorn/prefer-code-point -- fromCharCode is required to match charCodeAt in fromBase64
  return btoa(String.fromCharCode(...textEncoder.encode(str)))
}

function fromBase64 (str) {
  const textDecoder = new globalThis.TextDecoder()
  // eslint-disable-next-line unicorn/prefer-code-point -- fromCharCode is required to match fromCharCode in toBase64
  return textDecoder.decode(Uint8Array.from([...atob(str)].map(c => c.charCodeAt(0))))
}

register(RIOIframe, 'rio-iframe')
