import { useCallback } from 'react';
import classnames from 'classnames';

import type { QueryParams } from '~/lib/utils/url';
import type {
  CSSProperties,
  FC,
  HTMLAttributes,
  MouseEvent,
  ReactNode,
} from 'react';

import navigateWindow from '~/lib/utils/navigateWindow';
import { useAppRouter } from '~/src/lib/router2';
import isExternalUrl from '~/src/lib/utils/isExternalUrl';
import openWindow from '~/src/lib/utils/openWindow';

export type LinkClickHandlerParams = {
  event: MouseEvent<HTMLAnchorElement>;
  href?: string;
};

export type LinkClickHandler = (
  props: LinkClickHandlerParams
) => Promise<any> | void;

export interface LinkProps
  extends Pick<HTMLAttributes<HTMLAnchorElement>, 'tabIndex'> {
  href: string;
  title?: string;
  routerQuery?: QueryParams;
  className?: string;
  isUnderlined?: boolean;
  isNoFollow?: boolean;
  inNewTab?: boolean;
  withFocusStyle?: boolean;
  withHoverStyle?: boolean;
  autoFocus?: boolean;
  style?: CSSProperties;
  onClick?: LinkClickHandler;
  children: ReactNode;
  testId?: string;
  noDrag?: boolean;
}

const Link: FC<LinkProps> = ({
  href,
  title,
  routerQuery,
  className,
  isUnderlined = false,
  isNoFollow = false,
  inNewTab = false,
  withFocusStyle = false,
  withHoverStyle = false,
  autoFocus = false,
  style,
  onClick,
  children,
  testId = 'link',
  noDrag = false,
  tabIndex,
}) => {
  const router = useAppRouter();

  const onClickInternal = useCallback(
    async (event: MouseEvent<HTMLAnchorElement>) => {
      // callee can optionally pass an onClick callback if they
      // want to do something before/during the navigation
      if (onClick) {
        const returnValue = onClick({
          event,
          href: event.currentTarget.href,
        });

        // if somewhere up the stack event.preventDefault() was
        // called, then we shouldn't navigate as it's assumed that
        // navigation is being handled somewhere w/ more UX context.
        if (event.defaultPrevented) return;

        // stop the browser performing it's own
        // navigation as we handle it using router
        event.preventDefault();

        // if a promise was returned wait fr it to resolve before
        // routing, this allows us to do things like closing the
        // side-nav smoothly before triggering navigation which
        // can lock up main-thread.
        if (returnValue instanceof Promise) {
          await returnValue;
        }
      } else {
        // let browser handle target=_blank behavior
        if (inNewTab) return;

        // stop the browser performing it's own
        // navigation as we handle it using router
        event.preventDefault();
      }

      // anchor hrefs don't run through router, instead scroll the element into view
      if (href?.[0] === '#') {
        document
          .getElementById(href.slice(1))
          ?.scrollIntoView
          // disable smooth scroll until Chrome fixes in conjunction with `content-visibility: auto`
          // { behavior: 'smooth' }
          ();

        location.hash = href;

        return;
      }

      // external links don't run through next/router
      if (isExternalUrl(href)) {
        if (inNewTab) openWindow(href);
        else navigateWindow(href);
        return;
      }

      // do the clientside navigation
      router.push(href, { routerQuery });
    },
    [href]
  );

  return (
    <a
      href={href}
      style={style}
      data-testid={testId}
      className={classnames(className, {
        withFocusStyle,
        withHoverStyle,
        isUnderlined,
      })}
      onDragStart={noDrag ? (event) => event.preventDefault() : undefined}
      onClick={onClickInternal}
      autoFocus={autoFocus}
      tabIndex={tabIndex}
      title={title}
      aria-label={title}
      target={inNewTab ? '_blank' : undefined}
      rel={resolveRelProp({ inNewTab, isNoFollow })}
    >
      {children}
      <style jsx>{`
        a :global(*) {
          cursor: pointer;
        }

        .withHoverStyle:hover {
          text-decoration: underline;
          color: #fff;
        }

        .isUnderlined {
          display: inline-block;
          color: #888;
          border-bottom: dotted 0.1em #666;
        }

        .isUnderlined.withHoverStyle:hover {
          text-decoration: none;
          color: #fff;
        }

        /* subtle glow when focused via keyboard */
        .withFocusStyle:focus-visible {
          filter: drop-shadow(0px 1px 8px white);
           {
            /* Disabled as showing up in unwanted places. The only place we really want this is in paragraph styling. */
            /* text-decoration: underline; */
          }
        }
      `}</style>
    </a>
  );
};

const resolveRelProp = ({
  inNewTab,
  isNoFollow,
}: Pick<LinkProps, 'inNewTab' | 'isNoFollow'>) => {
  const rel: string[] = [];

  if (inNewTab) rel.push('noopener', 'noreferrer');
  if (isNoFollow) rel.push('nofollow');

  return rel.join(' ') || undefined;
};

export default Link;
