'use client'

import classNames from 'classnames'
import { ForwardedRef, forwardRef, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import { type THeaderFooterApi, DEFAULT_HEADER_LOGO_LINK } from 'shared-types'
import { fetchHeaderFooterData, selectLocaleData } from 'shared-utils/header-footer'
import { getDeepActiveElement, getFocusableElementsDeep } from 'shared-utils/focus-trap'

import { useHeaderChrome } from '../../hooks/useHeaderChrome'
import { useScrollLock } from '../../hooks/useScrollLock'
import { PktButton } from '../button/Button'
import { PktHeaderMenu } from '../header-menu/HeaderMenu'
import { PktHeaderUserMenu } from '../headerUserMenu/UserMenu'
import { PktIcon } from '../icon/Icon'
import { PktSearchInput } from '../searchinput/SearchInput'
import { IPktHeaderGlobal } from './types'

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

if (typeof window !== 'undefined') {
  window.pktLogoPath = window.pktLogoPath || defaultLogoPath
}

const MENU_ID = 'pkt-header-global-menu'
const MENU_TOGGLE_ID = 'pkt-header-global-menu-toggle'
const SEARCH_PANEL_ID = 'pkt-header-global-search-panel'
const SEARCH_TOGGLE_ID = 'pkt-header-global-search-toggle'

type GlobalMenu = 'none' | 'menu' | 'user' | 'search'

export const PktHeaderGlobal = forwardRef(
  (
    {
      className,
      'data-mode': dataMode,
      dataUrl,
      data,
      locale = 'nb-NO',
      logoLink = DEFAULT_HEADER_LOGO_LINK,
      logoClick,
      logoPath = typeof window !== 'undefined' ? window.pktLogoPath : defaultLogoPath,
      showSearch = true,
      searchPlaceholder,
      onSearch,
      showContact = true,
      user,
      userMenu,
      representing,
      canChangeRepresentation = false,
      changeRepresentation,
      logOutButtonPlacement = 'none',
      logOut,
      position = 'fixed',
      scrollBehavior = 'hide',
      mobileBreakpoint = 768,
      tabletBreakpoint = 1024,
      onDataLoaded,
      onDataError,
    }: IPktHeaderGlobal,
    ref: ForwardedRef<HTMLElement>,
  ) => {
    const shouldHideOnScroll = scrollBehavior === 'hide'

    const [openMenu, setOpenMenu] = useState<GlobalMenu>('none')
    const [fetchedData, setFetchedData] = useState<THeaderFooterApi | undefined>(undefined)
    const [loadError, setLoadError] = useState(false)

    const onDataLoadedRef = useRef(onDataLoaded)
    const onDataErrorRef = useRef(onDataError)
    useEffect(() => {
      onDataLoadedRef.current = onDataLoaded
    }, [onDataLoaded])
    useEffect(() => {
      onDataErrorRef.current = onDataError
    }, [onDataError])

    const { setRef, isMobile, isTablet, isFixed, isSticky, hidden, captureFocus, flagRestoreFocus, restoreFocus } =
      useHeaderChrome({
        forwardedRef: ref,
        mobileBreakpoint,
        tabletBreakpoint,
        position,
        scrollBehavior,
        pauseScrollHide: openMenu !== 'none',
      })

    useScrollLock(openMenu === 'menu' || openMenu === 'search')

    const headerElRef = useRef<HTMLElement | null>(null)
    const setHeaderRef = useCallback(
      (element: HTMLElement | null) => {
        headerElRef.current = element
        setRef(element)
      },
      [setRef],
    )
    useLayoutEffect(() => {
      if (openMenu !== 'menu' && openMenu !== 'search') return
      const overlay = document.getElementById(openMenu === 'menu' ? MENU_ID : SEARCH_PANEL_ID)
      const rect = headerElRef.current?.getBoundingClientRect()
      if (!overlay || !rect) return
      overlay.style.setProperty('--pkt-header-global-top', `${rect.top}px`)
      overlay.style.setProperty('--pkt-header-global-left', `${rect.left}px`)
      overlay.style.setProperty('--pkt-header-global-width', `${rect.width}px`)
    }, [openMenu, isMobile, isTablet])

    useEffect(() => {
      if (data) onDataLoadedRef.current?.(data)
    }, [data])

    useEffect(() => {
      if (data) return
      const controller = new AbortController()
      fetchHeaderFooterData<string>(dataUrl, controller.signal)
        .then((payload) => {
          if (controller.signal.aborted) return
          setFetchedData(payload)
          setLoadError(false)
          onDataLoadedRef.current?.(payload)
        })
        .catch((error: Error) => {
          if (error.name === 'AbortError') return
          setLoadError(true)
          onDataErrorRef.current?.(error)
        })
      return () => controller.abort()
    }, [data, dataUrl])

    const effectiveData = data ?? fetchedData
    const localeData = useMemo(() => selectLocaleData(effectiveData, locale), [effectiveData, locale])

    useEffect(() => {
      const handleClickOutside = (event: MouseEvent) => {
        const target = event.target as Element
        if (
          openMenu === 'user' &&
          !target.closest('.pkt-header-global__user-container') &&
          !target.closest('.pkt-header-global__user-menu')
        ) {
          setOpenMenu('none')
        }
        if (openMenu === 'menu' && !target.closest('.pkt-header-menu') && !target.closest(`#${MENU_TOGGLE_ID}`)) {
          setOpenMenu('none')
        }
        if (
          openMenu === 'search' &&
          !target.closest('.pkt-header-global__search-panel-inner') &&
          !target.closest(`#${SEARCH_TOGGLE_ID}`)
        ) {
          setOpenMenu('none')
        }
      }

      const handleEscapeKey = (event: KeyboardEvent) => {
        if (event.key === 'Escape' && openMenu !== 'none') {
          event.preventDefault()
          flagRestoreFocus()
          setOpenMenu('none')
        }
      }

      if (openMenu !== 'none') {
        document.addEventListener('mousedown', handleClickOutside)
        document.addEventListener('keydown', handleEscapeKey)
        return () => {
          document.removeEventListener('mousedown', handleClickOutside)
          document.removeEventListener('keydown', handleEscapeKey)
        }
      } else {
        restoreFocus()
      }
    }, [openMenu, restoreFocus, flagRestoreFocus])

    useEffect(() => {
      if (openMenu !== 'menu' && openMenu !== 'search') return
      const panelId = openMenu === 'menu' ? MENU_ID : SEARCH_PANEL_ID
      const toggleId = openMenu === 'menu' ? MENU_TOGGLE_ID : SEARCH_TOGGLE_ID
      const getPanel = () => document.getElementById(panelId)
      const getToggle = () => document.getElementById(toggleId)

      const closeToTrigger = (toggle: HTMLElement) => {
        setOpenMenu('none')
        ;(toggle.querySelector<HTMLElement>('button') ?? toggle).focus()
      }

      const handleKeydown = (event: KeyboardEvent) => {
        if (event.key !== 'Tab') return
        const panel = getPanel()
        const toggle = getToggle()
        if (!panel || !toggle) return
        const focusables = getFocusableElementsDeep(panel)
        const active = getDeepActiveElement()

        if (active && toggle.contains(active)) {
          if (!event.shiftKey && focusables.length > 0) {
            event.preventDefault()
            focusables[0].focus()
          }
          return
        }

        if (focusables.length === 0) return
        const first = focusables[0]
        const last = focusables[focusables.length - 1]
        const tabbingForwardOut = !event.shiftKey && active === last
        const tabbingBackwardOut = event.shiftKey && active === first
        if (tabbingForwardOut || tabbingBackwardOut) {
          event.preventDefault()
          closeToTrigger(toggle)
        }
      }

      const handleFocusIn = (event: FocusEvent) => {
        const target = event.target as Node | null
        if (!target) return
        const panel = getPanel()
        if (!panel) return
        const toggle = getToggle()
        if (panel.contains(target) || toggle?.contains(target)) return
        setOpenMenu('none')
      }

      document.addEventListener('keydown', handleKeydown)
      document.addEventListener('focusin', handleFocusIn)
      return () => {
        document.removeEventListener('keydown', handleKeydown)
        document.removeEventListener('focusin', handleFocusIn)
      }
    }, [openMenu])

    const formattedLastLoggedIn = useMemo(() => {
      if (!user?.lastLoggedIn) return undefined
      if (typeof user.lastLoggedIn === 'string') return user.lastLoggedIn
      return new Date(user.lastLoggedIn).toLocaleString('nb-NO', {
        year: 'numeric',
        month: 'long',
        day: 'numeric',
      })
    }, [user?.lastLoggedIn])

    const handleMenuToggle = useCallback(
      (mode: GlobalMenu) => {
        setOpenMenu((current) => {
          if (current === mode) return 'none'
          captureFocus()
          return mode
        })
      },
      [captureFocus],
    )

    const showLogoutInUserMenu = logOutButtonPlacement === 'userMenu' || logOutButtonPlacement === 'both'

    const search = localeData?.search
    const contact = localeData?.header?.contact
    const menuText = localeData?.i18n?.menu || 'Meny'
    const searchText = localeData?.i18n?.search || 'Søk'
    const isMenuOpen = openMenu === 'menu'
    const isUserOpen = openMenu === 'user'
    const isSearchOpen = openMenu === 'search'
    const compact = isMobile
    const hasSearch = Boolean(showSearch && search)
    const searchId = search?.input.id || 'pkt-header-global-search'
    const isLoading = !localeData && !loadError

    const headerElement = (
      <header
        className={classNames(
          'pkt-header-global',
          isMobile && 'pkt-header-global--mobile',
          isTablet && 'pkt-header-global--tablet',
          isFixed && 'pkt-header-global--fixed',
          isSticky && 'pkt-header-global--sticky',
          shouldHideOnScroll && 'pkt-header-global--scroll-to-hide',
          hidden && 'pkt-header-global--hidden',
          className,
        )}
        data-mode={dataMode}
        ref={setHeaderRef}
      >
        <div className="pkt-header-global__logo-area">
          <span className="pkt-header-global__logo">
            <a href={logoLink} aria-label="Til hovedsiden" onClick={logoClick}>
              <PktIcon name="oslologo" aria-hidden="true" path={logoPath} />
            </a>
          </span>
        </div>

        <div className="pkt-header-global__content">
          {!compact && hasSearch && search && (
            <div className="pkt-header-global__search">
              <PktSearchInput
                appearance="global"
                inputSize={isTablet ? 'small' : 'medium'}
                fullwidth
                id={searchId}
                name={search.input.name}
                aria-label={searchText}
                placeholder={searchPlaceholder ?? search.input.placeholder}
                action={search.endpoint}
                method="get"
                onSearch={onSearch}
                onChange={() => {}}
              />
            </div>
          )}

          {compact && hasSearch && (
            <PktButton
              id={SEARCH_TOGGLE_ID}
              className="pkt-header-global__search-toggle"
              skin="primary"
              color={isSearchOpen ? undefined : 'yellow'}
              variant="icon-only"
              iconName={isSearchOpen ? 'close' : 'magnifying-glass-big'}
              size={isMobile ? 'small' : 'medium'}
              state={isSearchOpen ? 'active' : 'normal'}
              aria-expanded={isSearchOpen}
              aria-controls={SEARCH_PANEL_ID}
              onClick={() => handleMenuToggle('search')}
            >
              {searchText}
            </PktButton>
          )}

          <PktButton
            id={MENU_TOGGLE_ID}
            className="pkt-header-global__menu-toggle"
            skin={compact ? 'tertiary' : 'secondary'}
            variant={compact ? 'icon-only' : 'icon-right'}
            iconName={isMenuOpen ? 'close' : 'menu'}
            size={isTablet ? 'small' : 'medium'}
            state={isMenuOpen ? 'active' : 'normal'}
            aria-expanded={isMenuOpen}
            aria-controls={MENU_ID}
            disabled={isLoading}
            onClick={() => handleMenuToggle('menu')}
          >
            {menuText}
          </PktButton>

          {!compact && showContact && contact && !user && (
            <a
              className={classNames(
                'pkt-header-global__contact',
                'pkt-btn',
                'pkt-btn--secondary',
                'pkt-btn--label-only',
                isTablet ? 'pkt-btn--small' : 'pkt-btn--medium',
              )}
              href={contact.url}
            >
              <span className="pkt-btn__text">{contact.text}</span>
            </a>
          )}
        </div>

        {user && (
          <div className="pkt-header-global__user">
            <div className={classNames('pkt-header-global__user-container', isUserOpen && 'is-open')}>
              <PktButton
                className="pkt-header-global__user-button"
                skin="secondary"
                size={isTablet ? 'small' : 'medium'}
                state={isUserOpen ? 'active' : 'normal'}
                variant="icons-right-and-left"
                iconName="user"
                secondIconName={isUserOpen ? 'chevron-thin-up' : 'chevron-thin-down'}
                onClick={() => handleMenuToggle('user')}
              >
                <span className="pkt-sr-only">Brukermeny: </span>
                <span translate="no">{representing?.name || user.name}</span>
              </PktButton>
              {isUserOpen && (
                <div className="pkt-header-global__user-menu is-open">
                  <PktHeaderUserMenu
                    user={user}
                    formattedLastLoggedIn={formattedLastLoggedIn}
                    representing={representing}
                    userMenu={userMenu}
                    canChangeRepresentation={canChangeRepresentation}
                    changeRepresentation={changeRepresentation}
                    logoutOnClick={showLogoutInUserMenu ? logOut : undefined}
                  />
                </div>
              )}
            </div>
          </div>
        )}
      </header>
    )

    const menuOverlay = isMenuOpen && (
      <div
        className={classNames(
          'pkt-header-global__menu',
          isMobile && 'pkt-header-global__menu--mobile',
          isTablet && !isMobile && 'pkt-header-global__menu--tablet',
        )}
        id={MENU_ID}
      >
        {localeData ? (
          <PktHeaderMenu data={effectiveData} locale={locale} open mobileBreakpoint={mobileBreakpoint} />
        ) : (
          <nav className="pkt-header-menu pkt-header-menu--open">
            <p className="pkt-header-menu__error">Kunne ikke laste meny.</p>
          </nav>
        )}
      </div>
    )

    const searchOverlay = isSearchOpen && hasSearch && search && (
      <div
        className={classNames(
          'pkt-header-global__search-panel',
          isMobile && 'pkt-header-global__search-panel--mobile',
          isTablet && !isMobile && 'pkt-header-global__search-panel--tablet',
        )}
        id={SEARCH_PANEL_ID}
      >
        <div className="pkt-header-global__search-panel-inner">
          <PktSearchInput
            appearance="global"
            fullwidth
            id={searchId}
            name={search.input.name}
            aria-label={searchText}
            placeholder={searchPlaceholder ?? search.input.placeholder}
            action={search.endpoint}
            method="get"
            onSearch={onSearch}
            onChange={() => {}}
            autoFocus
          />
        </div>
      </div>
    )

    if (isFixed) {
      return (
        <div className="pkt-header-global-wrapper">
          {headerElement}
          <div
            className={classNames(
              'pkt-header-global-spacer',
              isMobile && 'pkt-header-global-spacer--mobile',
              isTablet && !isMobile && 'pkt-header-global-spacer--tablet',
              user && 'pkt-header-global-spacer--has-user',
            )}
          />
          {searchOverlay}
          {menuOverlay}
        </div>
      )
    }

    return (
      <>
        {headerElement}
        {searchOverlay}
        {menuOverlay}
      </>
    )
  },
)

PktHeaderGlobal.displayName = 'PktHeaderGlobal'
