import React, { useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import throttle from 'lodash/throttle';
import MastheadLogo from './masthead-logo';
import MenuDisclosure from '../menu-disclosure/menu-disclosure';
import NavigationDisclosureIcon from '../navigation-disclosure/navigation-disclosure-icon';
import AccountLogin from './account-login';
import getAccountData from './account-data';
import MastheadLinks from './masthead-links';
import MastheadNavigation from './masthead-navigation';
import AccountNavigation from './account-navigation';
import CallToAction from '../call-to-action/call-to-action';
import SkipToContent from './skip-to-content';
import {
  breakpointLarge,
  breakpointMedium,
  breakpointXLarge,
  keyCodes,
  urls as defaultUrls,
  urlsPropShape,
} from '../../constants';
import 'reset-css/reset.css';
import '../color/color';
import '../grid/grid';
import '../interactions/interactions';
import '../media-queries/media-queries';
import '../rule/rule';
import '../typography/typography';
import css from './masthead.css';
import SearchForm from './search-form';

const initialWindowWidth = () => {
  if (typeof window !== 'undefined') {
    return window.innerWidth;
  }
  return undefined;
};

// Used React.memo() to address issue flagged by linter, so toggle function is only created once
/* eslint-disable react/jsx-no-bind */
const Masthead = React.memo(
  ({
    isLoggedIn,
    loginUrl,
    logoutUrl,
    overrideUrls,
    selectedLink,
    showSubscribe,
    showGiftGivingButton,
    subscribeCtaText,
    subscribeUrl,
    testId,
    skipTargetId,
    manageMyAccountUrl,
    savedStoriesUrl,
    gsClientAdminUrl,
    mySubscriptionUrl,
    personalInfoUrl,
    billingUrl,
    newslettersAndPreferencesUrl,
    giftSubscriptionsUrl,
    helpUrl,
    isClientAdmin,
    isGroupSubscriber,
    fullName,
    temporaryExtraItem,
    aiSearchCta,
    userType,
  }) => {
    const [windowWidth, setWindowWidth] = useState(initialWindowWidth());
    const [menuIsOpen, setMenuIsOpen] = useState(false);
    const [accountIsOpen, setAccountIsOpen] = useState(false);
    const [searchIsOpen, setSearchIsOpen] = useState(false);
    const [navToggleActive, setNavToggleActive] = useState(false);
    const mastheadContainerElement = useRef();
    const mastheadElement = useRef();
    const inverse = menuIsOpen || searchIsOpen;
    const processedAccountData = getAccountData(
      loginUrl,
      logoutUrl,
      manageMyAccountUrl,
      savedStoriesUrl,
      gsClientAdminUrl,
      mySubscriptionUrl,
      personalInfoUrl,
      billingUrl,
      newslettersAndPreferencesUrl,
      giftSubscriptionsUrl,
      helpUrl,
    );
    if (showSubscribe) {
      processedAccountData['account-logged-in'] = processedAccountData[
        'account-logged-in'
      ].filter(link => link.name !== 'Billing');
    }
    if (isGroupSubscriber || isClientAdmin) {
      processedAccountData['account-logged-in'] = processedAccountData[
        'account-logged-in'
      ].filter(
        link => link.name !== 'Gift subscriptions' && link.name !== 'Billing',
      );
    }
    if (!isClientAdmin) {
      processedAccountData['account-logged-in'] = processedAccountData[
        'account-logged-in'
      ].filter(link => link.name !== 'Admin account');
      processedAccountData['account-logged-in-main'] = processedAccountData[
        'account-logged-in-main'
      ].filter(link => link.name !== 'Admin account');
    }
    const urls = { ...defaultUrls, ...overrideUrls };
    let focusableElements = [];

    const getFocusableElements = () => {
      const mastheadElements = mastheadElement.current
        ? mastheadElement.current.querySelectorAll(
            'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
          )
        : [];
      return Array.from(mastheadElements).filter(element => {
        if (element.offsetParent === null) {
          return false;
        }
        const computedStyles = window.getComputedStyle(element);
        return (
          computedStyles.getPropertyValue('display') !== 'none' &&
          computedStyles.getPropertyValue('visibility') !== 'hidden'
        );
      });
    };

    const toggle = e => {
      e.preventDefault();
      setAccountIsOpen(false);
      setSearchIsOpen(false);
      setMenuIsOpen(!menuIsOpen);
    };
    const toggleAccount = e => {
      e.preventDefault();
      setMenuIsOpen(false);
      setSearchIsOpen(false);
      setAccountIsOpen(!accountIsOpen);
    };
    const toggleSearch = e => {
      e.preventDefault();
      setMenuIsOpen(false);
      setAccountIsOpen(false);
      setSearchIsOpen(!searchIsOpen);
    };

    const keyDown = e => {
      // Tab into the masthead navigation dropdown from the menu disclosure. Required as
      // this sits in a non-intuitive order in the DOM.
      if (e.keyCode === keyCodes.TAB) {
        if (!e.shiftKey) {
          document.getElementById(e.target.type).focus();
        }
      }
    };

    function getFirstFocusableElement(elements, isSmallScreen) {
      const firstElementClass = isSmallScreen
        ? 'ds-navigation-disclosure--icon-search'
        : 'weekly-edition-link';
      return Array.from(elements).find(element =>
        Array.from(element.classList).includes(firstElementClass),
      );
    }

    const trapFocus = e => {
      const KEY_TAB = 9;
      const KEY_ESC = 27;
      let lastFocusableElement;
      let firstFocusableElement;

      // See https://github.com/alphagov/govuk_publishing_components/blob/master/app/assets/javascripts/govuk_publishing_components/components/modal-dialogue.js#L82
      switch (e.keyCode) {
        case KEY_TAB:
          focusableElements = getFocusableElements();
          firstFocusableElement = getFirstFocusableElement(
            focusableElements,
            windowWidth < breakpointLarge,
          );
          lastFocusableElement =
            focusableElements[focusableElements.length - 1];
          if (e.shiftKey) {
            if (document.activeElement === firstFocusableElement) {
              e.preventDefault();
              lastFocusableElement.focus();
            }
          } else if (document.activeElement === lastFocusableElement) {
            e.preventDefault();
            firstFocusableElement.focus();
          }
          break;
        case KEY_ESC:
          setMenuIsOpen(false);
          setSearchIsOpen(false);
          setAccountIsOpen(false);
          break;
        default:
          break;
      }
    };

    // Toggle keyboard event listener when masthead menu is open
    useEffect(() => {
      if (menuIsOpen || searchIsOpen || accountIsOpen) {
        window.addEventListener('keydown', trapFocus);
      }
      return () => {
        window.removeEventListener('keydown', trapFocus);
      };
    }, [menuIsOpen, searchIsOpen, accountIsOpen]);

    // The window width is calculated as the dropdown menus exhibit changes in behaviour at
    // different breakpoints.
    useEffect(() => {
      const milliseconds = 200;
      const handleResize = throttle(() => {
        // On a number of mobile browsers the resize event handler fires on
        // scrolling due to the browser toolbar disappearing. To prevent unwanted
        // behaviour we check the width, ignoring any height changes.
        const newWindowWidth = window.innerWidth;
        if (newWindowWidth !== windowWidth) {
          setWindowWidth(newWindowWidth);
          if (breakpointMedium >= newWindowWidth) {
            setAccountIsOpen(false);
            setSearchIsOpen(false);
          }
        }
      }, milliseconds);
      setNavToggleActive(breakpointXLarge > windowWidth);

      window.addEventListener('resize', handleResize);
      return () => {
        window.removeEventListener('resize', handleResize);
      };
    });

    // handle closing of overlay if user clicks outside of the masthead
    useEffect(() => {
      if (!menuIsOpen && !searchIsOpen) {
        return undefined;
      }
      const handleClickOutside = e => {
        const clickIsNotInsideOverlay =
          mastheadContainerElement.current &&
          !mastheadContainerElement.current.contains(e.target);

        if (menuIsOpen && clickIsNotInsideOverlay) {
          setMenuIsOpen(false);
        }

        if (searchIsOpen && clickIsNotInsideOverlay) {
          setSearchIsOpen(false);
        }
      };

      document.addEventListener('mousedown', handleClickOutside);

      return () => {
        document.removeEventListener('mousedown', handleClickOutside);
      };
    }, [menuIsOpen, searchIsOpen]);

    return (
      <header
        data-test-id={testId}
        ref={mastheadContainerElement}
        className={classnames(css['ds-masthead'], {
          [css['is-open']]: menuIsOpen,
          [css['account-is-open']]: accountIsOpen,
          [css['search-is-open']]: searchIsOpen,
        })}
      >
        <div className={css['ds-layout-masthead']} ref={mastheadElement}>
          <MastheadLogo href={`${urls.economist}/`} />
          <SkipToContent targetId={skipTargetId} inverse={inverse} />
          <div className={css['ds-masthead-nav']}>
            <nav aria-label='Primary' className={css['ds-masthead-nav-alpha']}>
              <ul>
                <li
                  className={`${css['ds-masthead-nav-alpha__item']} ${
                    css['ds-masthead-nav-alpha__item--menu']
                  }`}
                >
                  <MenuDisclosure
                    url='#sections'
                    menuIsOpen={menuIsOpen}
                    inverse={inverse}
                    onClick={toggle}
                    onKeyDown={keyDown}
                    type='menu-nav'
                    hideMenuText={showSubscribe}
                  />
                </li>
                <MastheadLinks
                  inverse={inverse}
                  selectedLink={selectedLink}
                  links={urls}
                />
                <li
                  className={`${css['ds-masthead-nav-alpha__item']} ${
                    css['ds-masthead-nav-alpha__item--search']
                  }`}
                >
                  <NavigationDisclosureIcon
                    text='Search'
                    onClick={toggleSearch}
                    expanded={searchIsOpen}
                    href='#'
                    icon='search'
                    inverse={inverse}
                    onKeyDown={keyDown}
                    type='search-form'
                  />
                </li>
              </ul>
            </nav>
            <div className={css['ds-masthead-nav-beta']}>
              {temporaryExtraItem && (
                <div
                  className={`${css['ds-masthead-nav-beta__item']} ${
                    css['ds-masthead-nav-beta__item--extra-item']
                  }`}
                >
                  {temporaryExtraItem}
                </div>
              )}
              {showSubscribe && (
                <div
                  className={`${css['ds-masthead-nav-beta__item']} ${
                    css['ds-masthead-nav-beta__item--call-to-action']
                  }`}
                >
                  <CallToAction
                    text={subscribeCtaText}
                    /* subscribeUrl is deprecated. See prop documentation */
                    href={subscribeUrl || urls.subscribe}
                    inverse={inverse}
                  />
                </div>
              )}
              {!showSubscribe && showGiftGivingButton && (
                <div
                  className={`${css['ds-masthead-nav-beta__item']} ${
                    css['ds-masthead-nav-beta__item--call-to-action']
                  }`}
                >
                  <CallToAction
                    text='Give a gift'
                    href='https://subscribenow.economist.com/gift'
                    inverse={inverse}
                    data-analytics='masthead-gift-cta'
                  />
                </div>
              )}
              <div
                className={`${css['ds-masthead-nav-beta__item']} ${
                  css['ds-masthead-nav-beta__item--log-in']
                }`}
              >
                <AccountLogin
                  isLoggedIn={isLoggedIn}
                  loginUrl={loginUrl}
                  inverse={inverse}
                  toggleAccount={toggleAccount}
                  expanded={accountIsOpen}
                  className='my-account-link'
                  fullName={fullName}
                />
              </div>
            </div>
          </div>
          <MastheadNavigation
            accountData={processedAccountData}
            menuIsOpen={menuIsOpen}
            showSubscribe={showSubscribe}
            urls={urls}
            /* subscribeUrl is deprecated. See prop documentation */
            subscribeUrl={subscribeUrl || urls.subscribe}
            showNavToggle={navToggleActive}
            isLoggedIn={isLoggedIn}
          />
          <AccountNavigation
            accountData={processedAccountData}
            userType={userType}
          />
          <SearchForm
            searchUrl={urls.search}
            searchIsOpen={searchIsOpen}
            aiSearchCta={aiSearchCta}
          />
        </div>
        <div id='skip-to-content-target' />
      </header>
    );
  },
);

Masthead.displayName = 'Masthead';

Masthead.defaultProps = {
  isLoggedIn: false,
  loginUrl: '',
  logoutUrl: '',
  overrideUrls: {},
  selectedLink: null,
  showSubscribe: true,
  showGiftGivingButton: false,
  subscribeCtaText: 'Subscribe',
  skipTargetId: 'skip-to-content-target',
  subscribeUrl: null,
  testId: 'Masthead',
  manageMyAccountUrl: '',
  savedStoriesUrl: '',
  gsClientAdminUrl: '',
  mySubscriptionUrl: '',
  personalInfoUrl: '',
  billingUrl: '',
  newslettersAndPreferencesUrl: '',
  giftSubscriptionsUrl: '',
  helpUrl: '',
  isClientAdmin: false,
  isGroupSubscriber: false,
  fullName: '',
  temporaryExtraItem: undefined,
  aiSearchCta: null,
  userType: null,
};

Masthead.propTypes = {
  isLoggedIn: PropTypes.bool,
  loginUrl: PropTypes.string,
  logoutUrl: PropTypes.string,
  /** any url that needs to be overwritten in the default urls */
  overrideUrls: urlsPropShape,
  selectedLink: PropTypes.oneOf(['weekly-edition', 'the-world-in-brief']),
  showSubscribe: PropTypes.bool,
  showGiftGivingButton: PropTypes.bool,
  subscribeCtaText: PropTypes.string,
  /** Ref of the custom element for skip-to-content link to target */
  skipTargetId: PropTypes.string,
  testId: PropTypes.string,
  /**
   * deprecated: subscribeUrl should be passed in through the overrideUrls prop as overrideUrls.subscribe
   */
  subscribeUrl: PropTypes.string,
  manageMyAccountUrl: PropTypes.string,
  savedStoriesUrl: PropTypes.string,
  gsClientAdminUrl: PropTypes.string,
  mySubscriptionUrl: PropTypes.string,
  personalInfoUrl: PropTypes.string,
  billingUrl: PropTypes.string,
  newslettersAndPreferencesUrl: PropTypes.string,
  giftSubscriptionsUrl: PropTypes.string,
  helpUrl: PropTypes.string,
  isClientAdmin: PropTypes.bool,
  isGroupSubscriber: PropTypes.bool,
  fullName: PropTypes.string,
  temporaryExtraItem: PropTypes.node,
  aiSearchCta: PropTypes.node,
  userType: PropTypes.string,
};

export default Masthead;
