import { PktElement } from '@/base-elements/element'
import { ElementProps } from '@/types/typeUtils'
import { PktIconName } from '@oslokommune/punkt-assets/dist/icons/icon'
import { html, PropertyValues } from 'lit'
import { customElement, property } from 'lit/decorators.js'
import { unsafeSVG } from 'lit/directives/unsafe-svg.js'

const defaultPath = 'https://punkt-cdn.oslo.kommune.no/latest/icons/'

// Allow global override of icon fetch
if (typeof window !== 'undefined') {
  window.pktFetch = window.pktFetch === undefined ? fetch : window.pktFetch
  window.pktIconPath = window.pktIconPath || defaultPath
}

const errorSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"></svg>'
const dlCache: { [key: string]: Promise<string> } = {}
const MAX_RETRIES = 2
const RETRY_DELAY = 1500

const isSessionStorageAvailable =
  typeof Storage !== 'undefined' && typeof sessionStorage !== 'undefined'

const fetchIcon = (url: string): Promise<string> =>
  window
    .pktFetch!(url)
    .then((response: Response) => {
      if (!response.ok) {
        throw new Error('Missing icon: ' + url)
      }
      return response.text()
    })

const fetchIconWithRetry = async (url: string, retries = MAX_RETRIES): Promise<string> => {
  try {
    return await fetchIcon(url)
  } catch (error) {
    if (retries > 0) {
      await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY))
      return fetchIconWithRetry(url, retries - 1)
    }
    // eslint-disable-next-line no-console
    console.error('Failed to fetch icon: ' + url)
    return errorSvg
  }
}

const downloadIconOrGetFromCache = async (
  name: PktIconName,
  path: string | undefined,
): Promise<string | null> => {
  const key = path + name + '.svg'

  if (isSessionStorageAvailable && sessionStorage.getItem(key)) {
    return sessionStorage.getItem(key)
  }

  // If a fetch is already in-flight for this icon, await the same promise
  if (key in dlCache) {
    return dlCache[key]
  }

  if (typeof window !== 'undefined' && typeof window.pktFetch === 'function') {
    dlCache[key] = fetchIconWithRetry(key).then((text) => {
      if (text !== errorSvg && isSessionStorageAvailable) {
        sessionStorage.setItem(key, text)
      }
      delete dlCache[key]
      return text
    })
    return dlCache[key]
  }
  return errorSvg
}

type Props = ElementProps<PktIcon, 'path' | 'name'>

export class PktIcon extends PktElement<Props> {
  @property({ type: String, reflect: false })
  path: string | undefined = typeof window !== 'undefined' ? window.pktIconPath : defaultPath

  @property({ type: String, reflect: true })
  name: PktIconName = ''

  @property({ type: SVGElement })
  private icon: ReturnType<typeof unsafeSVG> = unsafeSVG(errorSvg)

  @property({ type: Array, noAccessor: true })
  private _updatedProps: string[] = []

  connectedCallback(): void {
    super.connectedCallback()
    this.classList.add('pkt-icon')
  }
  async attributeChangedCallback(name: string, _old: string | null, value: string | null) {
    super.attributeChangedCallback(name, _old, value)
    if (name === 'name' || name === 'path') this.getIcon(this.name)
  }

  protected async updated(_changedProperties: PropertyValues) {
    super.updated(_changedProperties)
    if (_changedProperties.has('name') || _changedProperties.has('path')) {
      this.getIcon(this.name)
    }
  }

  protected async getIcon(name: PktIconName = '') {
    if (this._updatedProps.length > 0) {
      if (!this.path) this.path = typeof window !== 'undefined' ? window.pktIconPath : defaultPath
      try {
        this.icon = unsafeSVG(
          await downloadIconOrGetFromCache(this.name || '', this.path),
        ) as SVGElement
      } catch {
        this.icon = unsafeSVG(errorSvg)
      }
      this._updatedProps = []
    } else {
      if (!this._updatedProps.includes(name)) {
        this._updatedProps.push(name)
      }
    }
  }

  render() {
    return html`${this.name && this.icon}`
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'pkt-icon': PktIcon
  }
}

try {
  customElement('pkt-icon')(PktIcon)
} catch (e) {
  console.warn('Forsøker å definere <pkt-icon>, men den er allerede definert')
}
