import { useEffect, useState } from 'preact/hooks'
import { Icon } from '../atoms/icon.js'
import { track } from '../shared/analytics.js'
import h from '../shared/h.js'
import { ARROW_TAIL_UP } from '../shared/icons.js'
import t from '../shared/t.js'
import updateHead from '../shared/update-head.js'
import { tw } from '../shared/twind.js'

const SCROLL_THRESHOLD = 200 // In pixels.
const DEBOUNCE = 100 // In milliseconds.

// -----------------------------------------------------------------------------
// Page component
// -----------------------------------------------------------------------------

export function Page ({ _model, children, ...props }) {
  const { links } = _model
  const getMeta = name => links.find(l => l.name === name)?.value ?? undefined
  const aaUrl = getMeta('adobeAnalyticsUrl')
  updateMetas(_model)

  // Track after render.
  useEffect(() => track(aaUrl), [aaUrl])

  // Render the page in a div. This makes scrollbar handling inside overlays much simpler.
  // The relative positioning is necessary, since the window does not scroll. The body does.
  // This makes absolutely positioned elements work correctly.
  return h('div', {
    ...props,
    className: 'font-sans text-base bg-bga min-h-dvh text-txa relative',
    children: [
      h(SkipToMain),
      ...children,
      h(BackToTop)
    ]
  })
}

function SkipToMain () {
  return h('a', {
    href: '#main',
    _variant: 'bold',
    'data-name': 'SkipToMain',
    // Use z-20 for floating elements.
    className: 'p-4 font-bold transition-all rounded-lg fixed start-6 bg-bgb text-txb -top-14 opacity-0 focus:top-6 focus:opacity-100 z-20 print:hidden',
    children: t('Skip to main content')
  })
}

function shouldBeVisible () {
  return document.body.scrollTop >= SCROLL_THRESHOLD
}

function BackToTop () {
  const [isVisible, setVisibility] = useState(shouldBeVisible())

  useEffect(() => {
    let resizeTimeout
    document.addEventListener('scroll', () => {
      clearTimeout(resizeTimeout)
      resizeTimeout = setTimeout(() => {
        setVisibility(shouldBeVisible())
      }, DEBOUNCE)
    }, true)
  })

  const name = t('Back to top')
  return isVisible && h('button', {
    // Use z-20 for floating elements.
    className: 'clickable fixed bottom-6 end-6 shadow bg-bga text-txa animate-expand rounded-lg w-10 h-10 hover:text-bga hover:bg-txa transition-all flex justify-center items-center z-20 print:hidden',
    title: name,
    'aria-label': name,
    'data-name': 'BackToTop',
    onClick () {
      document.body.scrollTop = 0
      location.hash = '#'
    },
    children: h(Icon, ARROW_TAIL_UP)
  })
}

// -----------------------------------------------------------------------------
// Page metadata management
// -----------------------------------------------------------------------------

// SEO complains for relative URL's
function getAbsoluteURL (value) {
  return new URL(value, location.href)?.href
}

// Metadata handlers.
const handlers = {
  alternate ({ children }) {
    // Remove all alternate links.
    for (const el of document.querySelectorAll('link[rel="alternate"]')) { el.remove() }

    for (const { name, value } of (children || [])) {
      updateHead('link', { rel: 'alternate', hreflang: name }, { href: getAbsoluteURL(value), hreflang: name })
    }
  },

  canonical ({ value }) {
    const canonicalUrl = value && getAbsoluteURL(value)
    updateHead('link', { rel: 'canonical' }, { href: canonicalUrl })
    updateHead('meta', { property: 'og:url' }, { content: canonicalUrl })
  },

  externalDisclaimerText ({ value }) {
    updateHead('meta', { name: 'external-disclaimer-text' }, { content: value })
  },

  dateModified ({ value }) {
    updateHead('meta', { itemprop: 'dateModified' }, { content: value })
  },

  errorUrl ({ value }) {
    updateHead('meta', { name: 'error-url' }, { content: value })
  },

  keywords ({ value }) {
    updateHead('meta', { name: 'keywords' }, { content: value })
  },

  'og:type' ({ value }) {
    updateHead('meta', { property: 'og:type' }, { content: value })
  },

  'twitter:card' ({ value }) {
    updateHead('meta', { name: 'twitter:card' }, { content: value })
  },

  schema ({ value }) {
    updateHeadJsonLd('local-business', value)
  },

  homeTitle ({ value }) {
    updateHead('meta', { name: 'application-name' }, { content: value })
    updateHead('meta', { name: 'apple-mobile-web-app-title' }, { content: value })
  },

  homeUrl ({ value }) {
    updateHead('meta', { name: 'home-url' }, { content: value })
  },

  locale ({ value }) {
    updateHead('meta', { property: 'og:locale' }, { content: value })
    // The locale meta uses Java locale format for the sake of og:locale.
    // The lang uses IETF BCP 47 and needs to be converted.
    document.documentElement.lang = value?.replace('_', '-')
  },

  textDirection ({ value }) {
    document.documentElement.dir = value
  },

  title ({ value }) {
    document.title = value
  },

  themeCss ({ value }) {
    updateHead('link', { rel: 'stylesheet', 'data-id': 'theme' }, { href: value })
  },

  themeMode ({ value }) {
    if (value === 'bw') {
      tw('grayscale')
      document.documentElement.classList.add('grayscale')
    } else {
      document.documentElement.classList.remove('grayscale')
    }
  },

  themePath ({ value }) {
    let appleTouchIcon, manifest, faviconIco, faviconSvg

    if (value) {
      const path = `${value}/resources/favicon/`
      appleTouchIcon = `${path}apple-touch-icon.png`
      manifest = `${path}manifest.json`
      faviconIco = `${path}favicon.ico`
      faviconSvg = `${path}favicon.svg`
    }

    updateHead('link', { rel: 'apple-touch-icon' }, { href: appleTouchIcon })
    updateHead('link', { rel: 'manifest' }, { href: manifest })

    // Remove all icons without data format.
    for (const el of document.querySelectorAll('link[rel=icon]:not([data-format])')) { el.remove() }

    // Create new icons with data format. From that point onwards only the href will be updated.
    updateHead('link', { rel: 'icon', 'data-format': 'ico' }, { href: faviconIco })
    updateHead('link', { rel: 'icon', 'data-format': 'svg' }, { href: faviconSvg })
  }
}

function updateHeadJsonLd (id, value) {
  const selector = `script[type="application/ld+json"][data-schema="${id}"]`
  const existing = document.head.querySelector(selector)

  if (!value) {
    existing?.remove()
    return
  }

  const el = existing ?? document.createElement('script')
  el.type = 'application/ld+json'
  el.setAttribute('data-schema', id)
  el.textContent = value

  if (!existing) {
    document.head.appendChild(el)
  }
}

function updateMetas (model) {
  // Set the title.
  if (model.title) {
    handlers.title(model.title)
  }

  // Create a copy of handler references to track which ones were used.
  const unusedHandlers = { ...handlers }
  for (const link of model.links) {
    // Inject new dynamic elements.
    if (handlers[link.name]) {
      delete unusedHandlers[link.name]
      handlers[link.name](link, model)
    }
  }

  // Remove elements that are not present in the model.
  for (const handler of Object.values(unusedHandlers)) {
    handler({})
  }

  updateHead('meta', { property: 'og:title' }, { content: document.title })
  updateHead('meta', { itemprop: 'name' }, { content: document.title })
  updateHead('meta', { name: 'description' }, { content: model.text })
  updateHead('meta', { property: 'og:description' }, { content: model.text })
  updateHead('meta', { itemprop: 'description' }, { content: model.text })
  updateHead('meta', { property: 'og:image' }, { content: model.image.src })
  updateHead('meta', { property: 'og:image:alt' }, { content: model.image.alt })
  updateHead('meta', { name: 'twitter:image:alt' }, { content: model.image.alt })
}
