import React, { useRef, useEffect, useState, useMemo } from 'react';
import styled, { css } from 'styled-components';

import type { ResolvedBannerConfig } from '@redocly/config';
import type { JSX } from 'react';

import { useThemeHooks, useBannerTelemetry } from '@redocly/theme/core/hooks';
import { getNavbarElement } from '@redocly/theme/core/utils';
import { MarkdownLinkContext } from '@redocly/theme/core/contexts';
import { Markdown } from '@redocly/theme/components/Markdown/Markdown';
import { CloseIcon } from '@redocly/theme/icons/CloseIcon/CloseIcon';
import { Button } from '@redocly/theme/components/Button/Button';

const ANIMATION_DURATION = 0.4;

type BannerProps = {
  className?: string;
};

export type DisplayBanner = ResolvedBannerConfig & {
  color: NonNullable<ResolvedBannerConfig['color']>;
  dismissible: NonNullable<ResolvedBannerConfig['dismissible']>;
};

function toDisplayBanner(banner: ResolvedBannerConfig): DisplayBanner {
  return {
    ...banner,
    color: banner.color ?? 'info',
    dismissible: banner.dismissible ?? false,
  };
}

function setBannerHeight(height: number): void {
  document.documentElement.style.setProperty('--banner-height', `${height}px`);
}

export function Banner({ className }: BannerProps): JSX.Element | null {
  const { useBanner, useMarkdocRenderer } = useThemeHooks();
  const { banner, dismissBanner } = useBanner();
  const [displayBanner, setDisplayBanner] = useState<DisplayBanner | undefined>(undefined);
  const [isVisible, setIsVisible] = useState(false);

  const { sendBannerViewedMessage, sendBannerDismissedMessage, sendBannerLinkClickedMessage } =
    useBannerTelemetry(displayBanner);

  const markdownContent = useMarkdocRenderer(displayBanner?.ast);
  const bannerRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (banner) {
      setDisplayBanner(toDisplayBanner(banner));
      requestAnimationFrame(() => {
        requestAnimationFrame(() => {
          setIsVisible(true);
        });
      });
    } else {
      setIsVisible(false);
      const timer = setTimeout(() => {
        setDisplayBanner(undefined);
      }, 400);
      return () => clearTimeout(timer);
    }
  }, [banner]);

  useEffect(() => {
    if (!displayBanner) {
      const timer = setTimeout(() => {
        setBannerHeight(0);
      }, 400);
      return () => clearTimeout(timer);
    }

    const bannerElement = bannerRef.current;
    if (!bannerElement) return;

    if (!isVisible) {
      setBannerHeight(0);
      return;
    }

    const updateHeight = (): void => {
      const height = bannerElement.getBoundingClientRect().height;
      setBannerHeight(height);
    };

    updateHeight();

    if (window.location.hash) {
      setTimeout(
        () => {
          const hash = window.location.hash;
          const el = document.getElementById(hash.slice(1));
          if (el) {
            const navbar = getNavbarElement();
            const navbarHeight = navbar?.getBoundingClientRect().height ?? 0;
            const elementTop = el.getBoundingClientRect().top + window.scrollY;
            const scrollPosition = elementTop - navbarHeight;
            if (Math.abs(window.scrollY - scrollPosition) > 1) {
              window.scrollTo({ top: scrollPosition });
            }
          }
        },
        ANIMATION_DURATION * 1000 + 100,
      );
    }

    const resizeObserver = new ResizeObserver(updateHeight);
    resizeObserver.observe(bannerElement);

    return () => {
      resizeObserver.disconnect();
    };
  }, [displayBanner, isVisible]);

  useEffect(() => {
    if (isVisible) {
      sendBannerViewedMessage();
    }
  }, [isVisible, sendBannerViewedMessage]);

  const markdownLinkContextValue = useMemo(
    () => ({
      onMarkdownLinkClick: sendBannerLinkClickedMessage,
    }),
    [sendBannerLinkClickedMessage],
  );

  if (!displayBanner) {
    return null;
  }

  return (
    <BannerWrapper
      ref={bannerRef}
      data-component-name="Banner/Banner"
      className={className}
      $color={displayBanner.color}
      $isVisible={isVisible}
    >
      <BannerContent $color={displayBanner.color}>
        <MarkdownLinkContext.Provider value={markdownLinkContextValue}>
          <Markdown compact>{markdownContent}</Markdown>
        </MarkdownLinkContext.Provider>
      </BannerContent>
      {displayBanner.dismissible && (
        <DismissButton
          variant="ghost"
          size="var(--banner-button-size)"
          icon={<CloseIcon color={`var(--banner-${displayBanner.color}-icon-color)`} size="16px" />}
          onClick={() => {
            dismissBanner(displayBanner.hash);
            sendBannerDismissedMessage();
          }}
          aria-label="Dismiss banner"
        />
      )}
    </BannerWrapper>
  );
}

const BannerContent = styled.div<{ $color: DisplayBanner['color'] }>`
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;

  p {
    margin: 0;
    color: var(--banner-text-color);
    text-align: center;

    a:not([role='button']) {
      color: var(--banner-link-color);
      text-decoration: ${({ $color }) => $color && `var(--banner-${$color}-link-decoration)`};

      &:hover, &:focus {
        text-decoration: ${({ $color }) => $color && `var(--banner-${$color}-link-decoration-hover)`};
      }

      &:visited {
        text-decoration: ${({ $color }) => $color && `var(--banner-${$color}-link-decoration)`};
      }
    }

    [data-component-name='Button/Button'] {
      --button-font-size: var(--banner-button-font-size);
      --button-border-radius: var(--banner-button-border-radius);
      --button-padding: var(--banner-button-padding-inline);
      --button-line-height: var(--banner-button-line-height);
      --button-icon-size: var(--banner-button-icon-size);
      --button-icon-padding: var(--banner-button-icon-padding);
      --button-icon-left-padding: var(--banner-button-icon-left-padding);
      --button-icon-right-padding: var(--banner-button-icon-right-padding);
      margin: var(--banner-button-margin);
    }
  }

  p > * {
    vertical-align: bottom;
  }
`;

const BannerWrapper = styled.div<{ $color?: DisplayBanner['color']; $isVisible?: boolean }>`
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--banner-padding);
  color: var(--banner-text-color);
  min-height: var(--banner-min-height);
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  width: 100%;
  z-index: var(--z-index-overlay);
  transform: ${({ $isVisible }) => ($isVisible ? 'translateY(0)' : 'translateY(-100%)')};
  transition: transform ${ANIMATION_DURATION}s ease-out;
  ${({ $color }) =>
    $color &&
    css`
      background-color: var(--banner-${$color}-bg-color);

      ${BannerContent} {
        p {
          color: var(--banner-${$color}-text-color);
        }

        a:not([role='button']) {
          color: var(--banner-${$color}-link-color);
        }
      }
    `}
`;

const DismissButton = styled(Button)`
  width: var(--banner-button-size);
  height: var(--banner-button-size);
  padding: var(--banner-button-padding);
`;
