import { PktElementWithSlot } from '@/base-elements/element-with-slot'
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 { createRef, Ref, ref } from 'lit/directives/ref.js'
import { slotContent } from '@/directives/slot-content'
import {
  User,
  Representing,
  UserMenuItem,
  THeaderMenu,
  TLogOutButtonPlacement,
  THeaderPosition,
  THeaderScrollBehavior,
  TSlotMenuVariant,
  IPktHeader,
  Booleanish,
  booleanishConverter,
} from './types'
import { formatLastLoggedIn } from './header-utils'

import '@/components/button'
import '@/components/icon'
import '@/components/link'
import '@/components/textinput'
import './header-user-menu'

const CDN_LOGO_PATH = 'https://punkt-cdn.oslo.kommune.no/latest/logos/'

export class PktHeaderService extends PktElementWithSlot<IPktHeader> implements IPktHeader {
  @property({ type: String, attribute: 'service-name' }) serviceName?: string
  @property({ type: String, attribute: 'service-link' }) serviceLink?: string
  @property({ type: String, attribute: 'logo-link' }) logoLink?: string
  @property({ type: String, attribute: 'search-placeholder' }) searchPlaceholder = 'Søk'
  @property({ type: String, attribute: 'search-value' }) searchValue = ''
  @property({ type: Number, attribute: 'mobile-breakpoint' }) mobileBreakpoint: number = 768
  @property({ type: Number, attribute: 'tablet-breakpoint' }) tabletBreakpoint: number = 1280
  @property({ type: String, attribute: 'opened-menu' }) openedMenu: THeaderMenu = 'none'
  @property({ type: String, attribute: 'log-out-button-placement' })
  logOutButtonPlacement: TLogOutButtonPlacement = 'none'
  @property({ type: String }) position: THeaderPosition = 'fixed'
  @property({ type: String, attribute: 'scroll-behavior' }) scrollBehavior: THeaderScrollBehavior =
    'hide'
  @property({ type: String, attribute: 'slot-menu-variant' })
  slotMenuVariant: TSlotMenuVariant = 'icon-only'
  @property({ type: String, attribute: 'slot-menu-text' }) slotMenuText = 'Meny'

  @property({ type: Boolean, attribute: 'hide-logo', converter: booleanishConverter })
  hideLogo: Booleanish = false
  @property({ type: Boolean, converter: booleanishConverter }) compact: Booleanish = false
  @property({ type: Boolean, attribute: 'show-search', converter: booleanishConverter })
  showSearch: Booleanish = false
  @property({
    type: Boolean,
    attribute: 'can-change-representation',
    converter: booleanishConverter,
  })
  canChangeRepresentation: Booleanish = false
  @property({ type: Boolean, attribute: 'has-log-out', converter: booleanishConverter })
  hasLogOut: Booleanish = false

  @property({ type: Object }) user?: User
  @property({ type: Array, attribute: 'user-menu' }) userMenu?: UserMenuItem[]
  @property({ type: Object }) representing?: Representing

  @state() private isMobile = false
  @state() private isTablet = false
  @state() private openMenu: THeaderMenu = 'none'
  @state() private isHidden = false
  @state() private componentWidth = typeof window !== 'undefined' ? window.innerWidth : 0
  @state() private alignSlotRight = false
  @state() private alignSearchRight = false

  private headerRef: Ref<HTMLElement> = createRef()
  private userContainerRef: Ref<HTMLElement> = createRef()
  private slotContainerRef: Ref<HTMLElement> = createRef()
  private searchContainerRef: Ref<HTMLElement> = createRef()
  private slotContentRef: Ref<HTMLElement> = createRef()
  private searchMenuRef: Ref<HTMLElement> = createRef()

  private resizeObserver?: ResizeObserver
  private lastScrollPosition = 0
  private savedScrollY = 0
  private lastFocusedElement: HTMLElement | null = null
  private shouldRestoreFocus = false

  connectedCallback() {
    super.connectedCallback()
    this.setupScrollListener()
  }

  disconnectedCallback() {
    super.disconnectedCallback()
    this.resizeObserver?.disconnect()
    window.removeEventListener('scroll', this.handleScroll)
    this.unlockScroll()
  }

  firstUpdated() {
    this.setupResizeObserver()
  }

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

    if (changedProperties.has('openedMenu') && this.openedMenu !== this.openMenu) {
      this.openMenu = this.openedMenu
    }

    if (changedProperties.has('mobileBreakpoint') || changedProperties.has('tabletBreakpoint')) {
      this.updateIsMobile()
      this.updateIsTablet()
    }

    if (changedProperties.has('openMenu')) {
      const previousOpenMenu = changedProperties.get('openMenu') as THeaderMenu | undefined
      if (
        this.openMenu !== 'none' &&
        (previousOpenMenu === 'none' || previousOpenMenu === undefined)
      ) {
        document.addEventListener('mousedown', this.handleClickOutside)
        document.addEventListener('keydown', this.handleEscapeKey)

        if (this.openMenu === 'slot' || this.openMenu === 'search') {
          requestAnimationFrame(() => {
            this.checkDropdownAlignment(this.openMenu as 'slot' | 'search')
          })
        }
      } else if (this.openMenu === 'none' && previousOpenMenu !== 'none') {
        document.removeEventListener('mousedown', this.handleClickOutside)
        document.removeEventListener('keydown', this.handleEscapeKey)
        this.restoreFocus()
      }
    }

    if (changedProperties.has('openMenu') || changedProperties.has('isMobile')) {
      this.updateScrollLock()
    }
  }

  private setupResizeObserver() {
    const headerElement = this.headerRef.value
    if (!headerElement) return

    this.componentWidth = headerElement.offsetWidth
    this.updateIsMobile()
    this.updateIsTablet()

    this.resizeObserver = new ResizeObserver((entries) => {
      for (const entry of entries) {
        if (entry.borderBoxSize && entry.borderBoxSize.length > 0) {
          this.componentWidth = entry.borderBoxSize[0].inlineSize
        } else {
          this.componentWidth = entry.contentRect.width
        }
        this.updateIsMobile()
        this.updateIsTablet()
      }
    })
    this.resizeObserver.observe(headerElement)
  }

  private updateIsMobile() {
    this.isMobile = this.componentWidth < this.mobileBreakpoint
  }

  private updateIsTablet() {
    this.isTablet = this.componentWidth < this.tabletBreakpoint
  }

  private updateScrollLock() {
    const shouldLock = this.position === 'fixed' && this.isMobile && this.openMenu !== 'none'
    if (shouldLock) {
      this.lockScroll()
    } else {
      this.unlockScroll()
    }
  }

  private lockScroll() {
    const body = document.body
    const docEl = document.documentElement

    this.savedScrollY = window.scrollY || window.pageYOffset

    const scrollBarWidth = window.innerWidth - docEl.clientWidth
    if (scrollBarWidth > 0) {
      body.style.paddingRight = `${scrollBarWidth}px`
    }

    body.style.position = 'fixed'
    body.style.top = `-${this.savedScrollY}px`
    body.style.left = '0'
    body.style.right = '0'
    body.style.width = '100%'
    body.style.overflow = 'hidden'

    docEl.classList.add('is-scroll-locked')
  }

  private unlockScroll() {
    const body = document.body
    const docEl = document.documentElement

    if (!docEl.classList.contains('is-scroll-locked')) return

    body.style.removeProperty('position')
    body.style.removeProperty('top')
    body.style.removeProperty('left')
    body.style.removeProperty('right')
    body.style.removeProperty('width')
    body.style.removeProperty('overflow')
    body.style.removeProperty('padding-right')
    docEl.classList.remove('is-scroll-locked')

    window.scrollTo({ top: this.savedScrollY })
  }

  private setupScrollListener() {
    window.addEventListener('scroll', this.handleScroll)
  }

  private handleScroll = () => {
    if (!this.shouldHideOnScroll) return

    const currentScrollPosition = window.pageYOffset || document.documentElement.scrollTop
    if (currentScrollPosition < 0) return
    if (Math.abs(currentScrollPosition - this.lastScrollPosition) < 60) return

    this.isHidden = currentScrollPosition > this.lastScrollPosition
    this.lastScrollPosition = currentScrollPosition
  }

  private handleClickOutside = (event: MouseEvent) => {
    const target = event.target as Element

    if (
      this.user &&
      this.openMenu === 'user' &&
      !target.closest('.pkt-header-service__user-container')
    ) {
      this.openMenu = 'none'
    }
    if (this.openMenu === 'slot' && !target.closest('.pkt-header-service__slot-container')) {
      this.openMenu = 'none'
    }
    if (
      this.openMenu === 'search' &&
      !target.closest('.pkt-header-service__search-container') &&
      !target.closest('.pkt-header-service__search-input')
    ) {
      this.openMenu = 'none'
    }
  }

  private handleFocusOut = (event: FocusEvent, menuType: THeaderMenu) => {
    const relatedTarget = event.relatedTarget as HTMLElement | null

    let containerRef: Ref<HTMLElement>
    switch (menuType) {
      case 'user':
        containerRef = this.userContainerRef
        break
      case 'slot':
        containerRef = this.slotContainerRef
        break
      case 'search':
        containerRef = this.searchContainerRef
        break
      default:
        return
    }

    const container = containerRef.value
    if (!container) return

    if (!relatedTarget || !container.contains(relatedTarget)) {
      this.openMenu = 'none'
    }
  }

  private handleEscapeKey = (event: KeyboardEvent) => {
    if (event.key === 'Escape' && this.openMenu !== 'none') {
      event.preventDefault()
      this.shouldRestoreFocus = true
      this.openMenu = 'none'
    }
  }

  private restoreFocus() {
    if (
      this.shouldRestoreFocus &&
      this.lastFocusedElement &&
      document.contains(this.lastFocusedElement)
    ) {
      this.lastFocusedElement.focus()
    }
    this.lastFocusedElement = null
    this.shouldRestoreFocus = false
  }

  private checkDropdownAlignment(mode: 'slot' | 'search') {
    const containerRef = mode === 'slot' ? this.slotContainerRef : this.searchContainerRef
    const dropdownRef = mode === 'slot' ? this.slotContentRef : this.searchMenuRef
    if (!containerRef.value || !dropdownRef.value || !this.isTablet || this.isMobile) return

    const buttonRect = containerRef.value.getBoundingClientRect()
    const dropdownWidth = dropdownRef.value.offsetWidth
    const wouldOverflow = buttonRect.left + dropdownWidth > window.innerWidth

    if (mode === 'slot') {
      this.alignSlotRight = wouldOverflow
    } else {
      this.alignSearchRight = wouldOverflow
    }
  }

  private handleMenuToggle(mode: THeaderMenu) {
    if (this.openMenu !== 'none') {
      this.openMenu = 'none'
    } else {
      this.lastFocusedElement = document.activeElement as HTMLElement

      this.openMenu = mode
    }
  }

  private handleLogoClick(e: Event) {
    this.dispatchEvent(
      new CustomEvent('logo-click', {
        bubbles: true,
        composed: true,
        detail: { originalEvent: e },
      }),
    )
  }

  private handleServiceClick(e: Event) {
    this.dispatchEvent(
      new CustomEvent('service-click', {
        bubbles: true,
        composed: true,
        detail: { originalEvent: e },
      }),
    )
  }

  private handleLogout() {
    this.dispatchEvent(
      new CustomEvent('log-out', {
        bubbles: true,
        composed: true,
      }),
    )
  }

  private handleSearch(query: string) {
    this.dispatchEvent(
      new CustomEvent('search', {
        detail: { query },
        bubbles: true,
        composed: true,
      }),
    )
  }

  private handleSearchChange(query: string) {
    this.dispatchEvent(
      new CustomEvent('search-change', {
        detail: { query },
        bubbles: true,
        composed: true,
      }),
    )
  }

  private handleSearchInputChange(e: Event) {
    const value = (e.target as HTMLInputElement).value
    this.handleSearchChange(value)
  }

  private handleSearchKeyDown(e: KeyboardEvent) {
    if (e.key === 'Enter') {
      this.handleSearch((e.target as HTMLInputElement).value)
    }
  }

  private get formattedLastLoggedIn(): string | undefined {
    return formatLastLoggedIn(this.user?.lastLoggedIn)
  }

  private get isFixed(): boolean {
    return this.position === 'fixed'
  }

  private get shouldHideOnScroll(): boolean {
    return this.scrollBehavior === 'hide'
  }

  private get showLogoutInHeader(): boolean {
    return (
      this.hasLogOut &&
      (this.logOutButtonPlacement === 'header' || this.logOutButtonPlacement === 'both')
    )
  }

  private get showLogoutInUserMenu(): boolean {
    return (
      this.hasLogOut &&
      (this.logOutButtonPlacement === 'userMenu' || this.logOutButtonPlacement === 'both')
    )
  }

  private renderLogo() {
    if (this.hideLogo) return nothing

    const logoIcon = html`
      <pkt-icon name="oslologo" aria-hidden="true" path=${CDN_LOGO_PATH}></pkt-icon>
    `

    // If logoLink is a non-empty string, render as link
    if (this.logoLink && typeof this.logoLink === 'string') {
      return html`
        <span class="pkt-header-service__logo">
          <a href=${this.logoLink} aria-label="Tilbake til forside" @click=${this.handleLogoClick}>
            ${logoIcon}
          </a>
        </span>
      `
    }

    // If logo-link attribute is present but empty, render as clickable button
    if (this.hasAttribute('logo-link')) {
      return html`
        <span class="pkt-header-service__logo">
          <button
            aria-label="Tilbake til forside"
            class="pkt-link-button pkt-link pkt-header-service__logo-link"
            @click=${this.handleLogoClick}
          >
            ${logoIcon}
          </button>
        </span>
      `
    }

    return html`
      <span class="pkt-header-service__logo" @click=${this.handleLogoClick}>${logoIcon}</span>
    `
  }

  private renderServiceName() {
    if (!this.serviceName) return nothing

    // If serviceLink is a non-empty string, render as link (but still dispatch event on click)
    if (this.serviceLink && typeof this.serviceLink === 'string') {
      return html`
        <span class="pkt-header-service__service-name">
          <pkt-link
            href=${this.serviceLink}
            class="pkt-header-service__service-link"
            @click=${this.handleServiceClick}
          >
            ${this.serviceName}
          </pkt-link>
        </span>
      `
    }

    // If service-link attribute is present but empty, render as clickable button
    if (this.hasAttribute('service-link')) {
      return html`
        <span class="pkt-header-service__service-name">
          <button
            class="pkt-link-button pkt-link pkt-header-service__service-link"
            @click=${this.handleServiceClick}
          >
            ${this.serviceName}
          </button>
        </span>
      `
    }

    // No link - just render the text
    return html`
      <span class="pkt-header-service__service-name" @click=${this.handleServiceClick}>
        <span class="pkt-header-service__service-link">${this.serviceName}</span>
      </span>
    `
  }

  private renderSlotContainer(): TemplateResult | typeof nothing {
    if (!this.hasSlotContent()) return nothing
    const slotContainerClasses = classMap({
      'pkt-header-service__slot-container': true,
      'is-open': this.openMenu === 'slot',
    })

    const slotContentClasses = classMap({
      'pkt-header-service__slot-content': true,
      'align-right': this.alignSlotRight,
    })

    return html`
      <div
        class=${slotContainerClasses}
        @focusout=${(e: FocusEvent) => this.handleFocusOut(e, 'slot')}
        ${ref(this.slotContainerRef)}
      >
        ${this.isTablet && this.hasSlotContent()
          ? html`
              <pkt-button
                skin="secondary"
                variant=${this.slotMenuVariant}
                iconName="menu"
                size=${this.isMobile ? 'small' : 'medium'}
                state=${this.openMenu === 'slot' ? 'active' : 'normal'}
                @click=${() => this.handleMenuToggle('slot')}
                aria-expanded=${this.openMenu === 'slot'}
                aria-controls="mobile-slot-menu"
                aria-label="Åpne meny"
              >
                ${this.slotMenuText}
              </pkt-button>
            `
          : nothing}
        <div
          class=${slotContentClasses}
          id="mobile-slot-menu"
          role=${this.isTablet ? 'menu' : nothing}
          aria-label=${this.isTablet ? 'Meny' : nothing}
          ${ref(this.slotContentRef)}
        >
          ${slotContent(this)}
        </div>
      </div>
    `
  }

  private renderSearch() {
    if (!this.showSearch) return nothing

    if (this.isTablet) {
      const searchContainerClasses = classMap({
        'pkt-header-service__search-container': true,
        'is-open': this.openMenu === 'search',
      })

      const searchMenuClasses = classMap({
        'pkt-header-service__mobile-menu': true,
        'is-open': this.openMenu === 'search',
        'align-right': this.alignSearchRight,
      })

      return html`
        <div
          class=${searchContainerClasses}
          @focusout=${(e: FocusEvent) => this.handleFocusOut(e, 'search')}
          ${ref(this.searchContainerRef)}
        >
          <pkt-button
            skin="secondary"
            variant="icon-only"
            iconName="magnifying-glass-big"
            size=${this.isMobile ? 'small' : 'medium'}
            @click=${() => this.handleMenuToggle('search')}
            state=${this.openMenu === 'search' ? 'active' : 'normal'}
            aria-expanded=${this.openMenu === 'search'}
            aria-controls="mobile-search-menu"
            aria-label="Åpne søkefelt"
          >
            Søk
          </pkt-button>
          <div class=${searchMenuClasses} ${ref(this.searchMenuRef)}>
            ${this.openMenu === 'search'
              ? html`
                  <pkt-textinput
                    id="mobile-search-menu"
                    class="pkt-header-service__search-input"
                    type="search"
                    label="Søk"
                    useWrapper="false"
                    placeholder=${this.searchPlaceholder}
                    value=${this.searchValue}
                    autofocus
                    fullwidth
                    @input=${this.handleSearchInputChange}
                    @keydown=${(e: KeyboardEvent) => {
                      if (e.key === 'Enter') {
                        this.handleSearch((e.target as HTMLInputElement).value)
                      }
                    }}
                  ></pkt-textinput>
                `
              : nothing}
          </div>
        </div>
      `
    }

    return html`
      <pkt-textinput
        id="header-service-search"
        class="pkt-header-service__search-input"
        type="search"
        label="Søk"
        useWrapper="false"
        placeholder=${this.searchPlaceholder}
        value=${this.searchValue}
        @input=${this.handleSearchInputChange}
        @keydown=${this.handleSearchKeyDown}
      ></pkt-textinput>
    `
  }

  private renderUserButton() {
    if (!this.user) return nothing

    const userMenuClasses = classMap({
      'pkt-header-service__user-menu': this.isMobile === false,
      'pkt-header-service__mobile-menu': this.isMobile === true,
      'is-open': this.openMenu === 'user',
    })

    return html`
      <div
        class="pkt-header-service__user-container"
        @focusout=${(e: FocusEvent) => this.handleFocusOut(e, 'user')}
        ${ref(this.userContainerRef)}
      >
        <pkt-button
          class=${classMap({
            'pkt-header-service__user-button': true,
            'pkt-header-service__user-button--mobile': this.isMobile,
          })}
          skin="secondary"
          size=${this.isMobile ? 'small' : 'medium'}
          state=${this.openMenu === 'user' ? 'active' : 'normal'}
          variant="icons-right-and-left"
          iconName="user"
          secondIconName=${this.openMenu === 'user' ? 'chevron-thin-up' : 'chevron-thin-down'}
          @click=${() => this.handleMenuToggle('user')}
        >
          <span class="pkt-sr-only">Brukermeny: </span>
          <span>${this.representing?.name || this.user.name}</span>
        </pkt-button>
        ${this.openMenu === 'user' && this.user
          ? html`
              <div class=${userMenuClasses}>
                <pkt-header-user-menu
                  .user=${this.user}
                  formatted-last-logged-in=${this.formattedLastLoggedIn || nothing}
                  .representing=${this.representing}
                  .userMenu=${this.userMenu}
                  ?can-change-representation=${this.canChangeRepresentation}
                  ?logout-on-click=${this.showLogoutInUserMenu}
                  @change-representation=${() =>
                    this.dispatchEvent(
                      new CustomEvent('change-representation', { bubbles: true, composed: true }),
                    )}
                  @log-out=${this.handleLogout}
                ></pkt-header-user-menu>
              </div>
            `
          : nothing}
      </div>
    `
  }

  private renderHeader() {
    const headerClasses = classMap({
      'pkt-header-service': true,
      'pkt-header-service--compact': this.compact,
      'pkt-header-service--mobile': this.isMobile,
      'pkt-header-service--tablet': this.isTablet,
      'pkt-header-service--fixed': this.isFixed,
      'pkt-header-service--scroll-to-hide': this.shouldHideOnScroll,
      'pkt-header-service--hidden': this.isHidden,
    })

    const logoAreaClasses = classMap({
      'pkt-header-service__logo-area': true,
      'pkt-header-service__logo-area--without-service': !this.serviceName,
    })

    return html`
      <header class=${headerClasses} ${ref(this.headerRef)}>
        <div class=${logoAreaClasses}>${this.renderLogo()} ${this.renderServiceName()}</div>

        ${this.hasSlotContent() || this.showSearch || this.showLogoutInHeader
          ? html`<div class="pkt-header-service__content">
              ${this.renderSlotContainer()} ${this.renderSearch()}
              ${this.isTablet && this.showLogoutInHeader
                ? html`
                    <pkt-button
                      skin="secondary"
                      size=${this.isMobile ? 'small' : 'medium'}
                      variant="icon-only"
                      iconName="exit"
                      @click=${this.handleLogout}
                    >
                      Logg ut
                    </pkt-button>
                  `
                : nothing}
            </div>`
          : nothing}
        ${this.user || (!this.isTablet && this.showLogoutInHeader)
          ? html`<div class="pkt-header-service__user">
              ${this.renderUserButton()}
              ${!this.isTablet && this.showLogoutInHeader
                ? html`
                    <pkt-button
                      skin="tertiary"
                      size="medium"
                      variant="icon-right"
                      iconName="exit"
                      @click=${this.handleLogout}
                    >
                      Logg ut
                    </pkt-button>
                  `
                : nothing}
            </div>`
          : nothing}
      </header>
    `
  }

  render() {
    const headerElement = this.renderHeader()

    if (this.isFixed) {
      const spacerClasses = classMap({
        'pkt-header-service-spacer': true,
        'pkt-header-service-spacer--compact': this.compact,
        'pkt-header-service-spacer--has-user': !!this.user,
        'pkt-header-service-spacer--mobile': this.isMobile,
        'pkt-header-service-spacer--tablet': this.isTablet,
      })

      return html`
        <div class="pkt-header-service-wrapper">
          ${headerElement}
          <div class=${spacerClasses}></div>
        </div>
      `
    }

    return headerElement
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'pkt-header-service': PktHeaderService
  }
}

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