import { PktElement } from '@/base-elements/element'
import { html, nothing, type PropertyValues, type TemplateResult } from 'lit'
import { customElement, property, state } from 'lit/decorators.js'
import { classMap } from 'lit/directives/class-map.js'
import { booleanishConverter, type Booleanish } from 'shared-types'
import {
  DEFAULT_HEADER_FOOTER_URL,
  deriveSocialIcon,
  fetchHeaderFooterData,
  mapOdsIcon,
  selectLocaleData,
} from 'shared-utils/header-menu'
import type {
  IHeaderFooterLocaleData,
  IHeaderMenuButton,
  IHeaderMenuLink,
  IHeaderMenuSection,
  IHeaderMenuServices,
  IPktHeaderMenu,
  THeaderFooterApi,
  THeaderMenuLocale,
} from './types'

import '@/components/accordion'
import '@/components/icon'

type LoadState = 'idle' | 'loading' | 'ready' | 'error'

export class PktHeaderMenu extends PktElement<IPktHeaderMenu> implements IPktHeaderMenu {
  @property({ type: String, attribute: 'data-url' })
  dataUrl: string = DEFAULT_HEADER_FOOTER_URL

  @property({ type: Object, attribute: false })
  data?: THeaderFooterApi

  @property({ type: String })
  locale: THeaderMenuLocale = 'nb-NO'

  @property({ type: Boolean, reflect: true, converter: booleanishConverter })
  open: Booleanish = false

  @property({ type: String, attribute: 'aria-labelledby', reflect: true })
  ariaLabelledBy: string = ''

  @property({ type: Number, attribute: 'mobile-breakpoint' })
  mobileBreakpoint: number = 768

  @state() private loadState: LoadState = 'idle'
  @state() private fetchedData?: THeaderFooterApi
  @state() private isMobile = false

  private abortController?: AbortController
  private mediaQuery?: MediaQueryList

  override connectedCallback(): void {
    super.connectedCallback()
    this.classList.add('pkt-header-menu')
    this.updateOpenClass()
    this.setupMediaQuery()
    if (!this.data) {
      void this.loadData()
    } else {
      this.loadState = 'ready'
      this.dispatchEvent(
        new CustomEvent('data-loaded', {
          detail: { data: this.data },
          bubbles: true,
          composed: true,
        }),
      )
    }
  }

  private updateOpenClass() {
    this.classList.toggle('pkt-header-menu--open', Boolean(this.open))
  }

  override disconnectedCallback(): void {
    super.disconnectedCallback()
    this.abortController?.abort()
    this.teardownMediaQuery()
  }

  override updated(changedProperties: PropertyValues): void {
    super.updated(changedProperties)

    if (changedProperties.has('open')) {
      this.updateOpenClass()
    }

    if (
      changedProperties.has('mobileBreakpoint') &&
      changedProperties.get('mobileBreakpoint') !== undefined
    ) {
      this.setupMediaQuery()
    }

    // Re-fetch only when dataUrl genuinely changes after first load.
    // The initial fetch is kicked off from connectedCallback, so we
    // skip the noop "field default" change that fires on first update.
    if (
      changedProperties.has('dataUrl') &&
      changedProperties.get('dataUrl') !== undefined &&
      !this.data
    ) {
      void this.loadData()
    }

    if (changedProperties.has('data') && changedProperties.get('data') !== undefined && this.data) {
      this.loadState = 'ready'
      this.dispatchEvent(
        new CustomEvent('data-loaded', {
          detail: { data: this.data },
          bubbles: true,
          composed: true,
        }),
      )
    }
  }

  private setupMediaQuery() {
    this.teardownMediaQuery()
    if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return
    this.mediaQuery = window.matchMedia(`(max-width: ${this.mobileBreakpoint - 1}px)`)
    this.isMobile = this.mediaQuery.matches
    this.mediaQuery.addEventListener('change', this.handleMediaChange)
  }

  private teardownMediaQuery() {
    this.mediaQuery?.removeEventListener('change', this.handleMediaChange)
    this.mediaQuery = undefined
  }

  private handleMediaChange = (event: MediaQueryListEvent) => {
    this.isMobile = event.matches
  }

  private async loadData() {
    this.abortController?.abort()
    this.abortController = new AbortController()
    this.loadState = 'loading'

    try {
      const data = await fetchHeaderFooterData<string>(this.dataUrl, this.abortController.signal)
      this.fetchedData = data
      this.loadState = 'ready'
      this.dispatchEvent(
        new CustomEvent('data-loaded', {
          detail: { data },
          bubbles: true,
          composed: true,
        }),
      )
    } catch (error) {
      if ((error as Error).name === 'AbortError') return
      this.loadState = 'error'
      this.dispatchEvent(
        new CustomEvent('data-error', {
          detail: { error },
          bubbles: true,
          composed: true,
        }),
      )
    }
  }

  private get effectiveData(): THeaderFooterApi | undefined {
    return this.data ?? this.fetchedData
  }

  private get localeData(): IHeaderFooterLocaleData | undefined {
    return selectLocaleData(this.effectiveData, this.locale)
  }

  render() {
    if (this.loadState === 'loading') {
      return html`
        <nav aria-busy="true">
          <p class="pkt-header-menu__loading">Laster meny…</p>
        </nav>
      `
    }

    if (this.loadState === 'error' || !this.localeData) {
      return html`
        <nav aria-hidden=${!this.open}>
          <p class="pkt-header-menu__error">Kunne ikke laste meny.</p>
        </nav>
      `
    }

    const { megamenu, i18n } = this.localeData
    const navAriaLabel = i18n?.navAriaLabel || 'Hovedmeny'

    if (this.isMobile) {
      return html`
        <nav aria-label=${navAriaLabel}>
          ${this.renderMobileAccordion(megamenu.services, megamenu.sections)}
          ${this.renderButtons(megamenu.buttons, true)}
          ${this.renderFooter(megamenu.links, megamenu.some)}
        </nav>
      `
    }

    return html`
      <nav aria-label=${navAriaLabel}>
        ${this.renderServices(megamenu.services)} ${this.renderButtons(megamenu.buttons, false)}
        ${this.renderSections(megamenu.sections)}
        ${this.renderFooter(megamenu.links, megamenu.some)}
      </nav>
    `
  }

  private renderMobileAccordion(
    services: IHeaderMenuServices,
    sections: IHeaderMenuSection[],
  ): TemplateResult {
    return html`
      <div class="pkt-header-menu__sections">
        <pkt-accordion
          class="pkt-header-menu__sections-inner"
          skin="plus-minus"
          name="header-menu-accordion"
        >
          <pkt-accordion-item
            class="pkt-header-menu__section"
            skin="plus-minus"
            id="pkt-header-menu-services"
            title=${services.title}
          >
            ${this.renderServicesList(services)}
          </pkt-accordion-item>
          ${sections.map(
            (section, index) => html`
              <pkt-accordion-item
                class="pkt-header-menu__section"
                skin="plus-minus"
                id=${`pkt-header-menu-section-${index}`}
                title=${section.title}
              >
                ${this.renderSectionList(section.links)}
              </pkt-accordion-item>
            `,
          )}
        </pkt-accordion>
      </div>
    `
  }

  private renderServicesList(services: IHeaderMenuServices): TemplateResult {
    return html`
      <ul class="pkt-header-menu__services-list">
        ${services.links.map(
          (link) => html`
            <li class="pkt-header-menu__service">
              <a class="pkt-header-menu__service-link" href=${link.url}>
                <pkt-icon
                  class="pkt-header-menu__service-icon"
                  name=${mapOdsIcon(link.icon)}
                  aria-hidden="true"
                ></pkt-icon>
                <span class="pkt-header-menu__service-text">${link.text}</span>
              </a>
            </li>
          `,
        )}
      </ul>
    `
  }

  private renderServices(services: IHeaderMenuServices): TemplateResult {
    return html`
      <div class="pkt-header-menu__services">
        <h2 class="pkt-header-menu__services-title">${services.title}</h2>
        ${this.renderServicesList(services)}
      </div>
    `
  }

  private renderButtons(
    buttons: IHeaderMenuButton[] | undefined,
    isMobilePlacement: boolean,
  ): TemplateResult | typeof nothing {
    if (!buttons || buttons.length === 0) return nothing

    const classes = classMap({
      'pkt-header-menu__buttons': true,
      'pkt-header-menu__buttons--mobile': isMobilePlacement,
    })

    return html`
      <div class=${classes}>
        ${buttons.map(
          (button) => html`
            <a
              class="pkt-btn pkt-btn--secondary pkt-btn--icon-right pkt-btn--small"
              href=${button.url}
            >
              <pkt-icon
                class="pkt-btn__icon"
                name=${button.iconName ? mapOdsIcon(button.iconName) : 'user'}
                aria-hidden="true"
              ></pkt-icon>
              <span class="pkt-btn__text">${button.text}</span>
            </a>
          `,
        )}
      </div>
    `
  }

  private renderSections(sections: IHeaderMenuSection[]): TemplateResult | typeof nothing {
    if (!sections || sections.length === 0) return nothing

    return html`
      <div class="pkt-header-menu__sections">
        <div class="pkt-header-menu__sections-inner">
          ${sections.map(
            (section) => html`
              <div class="pkt-header-menu__section">
                <h2 class="pkt-header-menu__section-title">${section.title}</h2>
                ${this.renderSectionList(section.links)}
              </div>
            `,
          )}
        </div>
      </div>
    `
  }

  private renderSectionList(links: IHeaderMenuLink[]): TemplateResult {
    return html`
      <ul class="pkt-header-menu__section-list">
        ${links.map(
          (link) => html`
            <li>
              <a class="pkt-header-menu__section-link" href=${link.url}>${link.text}</a>
            </li>
          `,
        )}
      </ul>
    `
  }

  private renderFooter(
    links: IHeaderMenuLink[],
    some: IHeaderMenuLink[],
  ): TemplateResult | typeof nothing {
    if ((!links || links.length === 0) && (!some || some.length === 0)) return nothing

    return html`
      <div class="pkt-header-menu__footer">
        ${links && links.length > 0
          ? html`
              <ul class="pkt-header-menu__footer-list">
                ${links.map(
                  (link) => html`
                    <li>
                      <a class="pkt-header-menu__footer-link" href=${link.url}>${link.text}</a>
                    </li>
                  `,
                )}
              </ul>
            `
          : nothing}
        ${some && some.length > 0
          ? html`
              <ul class="pkt-header-menu__footer-list pkt-header-menu__footer-list--social">
                ${some.map((entry) => this.renderSocialLink(entry))}
              </ul>
            `
          : nothing}
      </div>
    `
  }

  private renderSocialLink(entry: IHeaderMenuLink): TemplateResult {
    const iconName = deriveSocialIcon(entry.url, entry.text)
    return html`
      <li>
        <a class="pkt-header-menu__social-link" href=${entry.url} aria-label=${entry.text}>
          ${iconName
            ? html`<pkt-icon name=${iconName} aria-hidden="true"></pkt-icon>`
            : html`<span>${entry.text}</span>`}
        </a>
      </li>
    `
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'pkt-header-menu': PktHeaderMenu
  }
}

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