import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Id, MediaPlanRelease, Nullable } from 'backend-api/models';
import { bem } from 'utils/bem';
import { debounce } from 'lodash';
import { useProjectReleaseTypes } from 'hooks';
import { LoadingState } from 'common/types';
import { Icon } from 'common/components/icon';
import { Body, H3, SecondaryLabel } from 'common/components/typography';
import { Input } from 'common/components/form';
import {
  BaseOption,
  ClassNamesProps,
  Select,
  SelectComponents,
  SelectMenuProps,
  SelectType,
} from 'common/components/select';
import { ContentLoader } from 'common/components/loaders';
import { getMediaPlanRelease, updateMediaPlanRelease } from 'media-plan/actions';
import { projectReleaseSelector } from 'media-plan/selectors';
import { INITIAL_RELEASE_NAME, INITIAL_RELEASE_TYPE, TYPES_DROPDOWN_IDENTIFIER } from './constants';
import { BEM_CLASS, SReleaseDetailsPopup } from './s-release-details-popup';

interface ReleaseDetailsPopupProps {
  mediaPlanId: Id;
  projectId: Id;
  isOpened: boolean;
  isDisabled: boolean;
  controlRef: React.RefObject<HTMLButtonElement>;
  onClose(): void;
}

const classes = bem(BEM_CLASS);

export const ReleaseDetailsPopup = React.memo(
  ({ mediaPlanId, projectId, isOpened, isDisabled, controlRef, onClose }: ReleaseDetailsPopupProps) => {
    const rootRef = useRef<HTMLDivElement>(null);

    const dispatch = useDispatch();
    const projectReleaseTypes = useProjectReleaseTypes();

    const [position, setPosition] = useState(0);
    const [releaseName, setReleaseName] = useState<string>(INITIAL_RELEASE_NAME);
    const [releaseType, setReleaseType] = useState<number>(INITIAL_RELEASE_TYPE);

    const {
      loading,
      data: { name, type },
    } = useSelector(projectReleaseSelector);

    const options = useMemo<BaseOption[]>(
      () => projectReleaseTypes?.map(({ id, name }) => ({ id, value: name })) ?? [],
      [projectReleaseTypes]
    );

    const selectClassNames = useCallback<(className: string) => ClassNamesProps>(
      className => ({
        menuPortal: `${className} ${TYPES_DROPDOWN_IDENTIFIER}`,
        menu: classes('types-menu'),
        valueContainer: {
          root: classes('types-value-container-root'),
        },
      }),
      []
    );

    const selectComponents = useCallback<(props: SelectMenuProps) => SelectComponents<BaseOption, false>>(
      ({ isMenuOpen, openMenu, closeMenu }) => ({
        Control: ({ innerProps, innerRef, isFocused, children }) => (
          <div
            {...innerProps}
            ref={innerRef}
            className={classes('types-control', { isFocused })}
            onClick={isMenuOpen ? closeMenu : openMenu}
          >
            {children}
          </div>
        ),
      }),
      []
    );

    const debouncedReleaseUpdate = useMemo(
      () =>
        debounce(({ name, type }: MediaPlanRelease) => {
          dispatch(
            updateMediaPlanRelease.request({
              projectId,
              mediaPlanId,
              payload: { name, type },
            })
          );
        }, 700),
      [dispatch, mediaPlanId, projectId]
    );

    const handleReleaseNameChange = useCallback(
      (value?: string) => {
        const name = value ?? INITIAL_RELEASE_NAME;

        setReleaseName(name);
        debouncedReleaseUpdate({ name, type: undefined });
      },
      [debouncedReleaseUpdate]
    );

    const handleReleaseTypeChange = useCallback(
      (value: Nullable<number>) => {
        const type = value ?? INITIAL_RELEASE_TYPE;

        setReleaseType(type);
        debouncedReleaseUpdate({ name: undefined, type: value });
      },
      [debouncedReleaseUpdate]
    );

    const handleReleaseTypeKeyDown = useCallback(
      (event: React.KeyboardEvent<HTMLElement>) => {
        if (event.key === 'Backspace') {
          setReleaseType(INITIAL_RELEASE_TYPE);
          debouncedReleaseUpdate({ name: undefined, type: null });
        }
      },
      [debouncedReleaseUpdate]
    );

    const handleRefreshClick = useCallback(
      (e: React.MouseEvent<HTMLButtonElement>) => {
        e.stopPropagation();

        dispatch(getMediaPlanRelease.request({ mediaPlanId, projectId }));
      },
      [dispatch, mediaPlanId, projectId]
    );

    useLayoutEffect(() => {
      const controlElement = controlRef?.current;

      if (controlElement && isOpened) {
        const rect = controlElement.getBoundingClientRect();

        setPosition(rect.height);
      }
    }, [controlRef, isOpened]);

    useEffect(() => {
      const handleClickAway = (event: MouseEvent) => {
        const selectDropdownClicked = event.composedPath().some(target => {
          if (target instanceof Element) {
            return target.classList.contains(TYPES_DROPDOWN_IDENTIFIER);
          }

          return false;
        });

        if (!selectDropdownClicked && !rootRef.current?.contains(event.target as Node)) {
          onClose();
        }
      };

      document.addEventListener('mousedown', handleClickAway);

      return () => {
        document.removeEventListener('mousedown', handleClickAway);
      };
    }, [onClose]);

    useEffect(() => {
      if (loading === LoadingState.Finished) {
        setReleaseName(name ?? INITIAL_RELEASE_NAME);
        setReleaseType(type ?? INITIAL_RELEASE_TYPE);
      }
    }, [loading, name, type]);

    useEffect(() => {
      if (!mediaPlanId) return;
      dispatch(getMediaPlanRelease.request({ mediaPlanId, projectId }));
    }, [dispatch, mediaPlanId, projectId]);

    const renderContent = useCallback(
      (className: string) => {
        switch (loading) {
          case LoadingState.Idle:
          case LoadingState.Started:
            return (
              <div className={classes('loading')}>
                <ContentLoader />
              </div>
            );
          case LoadingState.Failed:
            return (
              <div className={classes('error')}>
                <Body className={classes('error-text')}>
                  An error occurred while loading Release
                  <br />
                  Details data, please try again.
                </Body>
                <button className={classes('error-refresh')} onClick={handleRefreshClick}>
                  Refresh
                </button>
              </div>
            );
          case LoadingState.Finished:
            return (
              <>
                <div className={classes('content-item')}>
                  <SecondaryLabel className={classes('content-title')}>Release Name:</SecondaryLabel>
                  <div className={classes('content-field')}>
                    <Input
                      className={classes('release-name-input', { isDisabled })}
                      value={releaseName}
                      placeholder="Type name..."
                      onChange={handleReleaseNameChange}
                      bgStyle="grey"
                      maxLength={100}
                      disabled={isDisabled}
                    />
                  </div>
                </div>
                <div className={classes('content-item')}>
                  <SecondaryLabel className={classes('content-title')}>Release Type:</SecondaryLabel>
                  <div className={classes('content-field')}>
                    <Select
                      type={SelectType.Base}
                      valueId={releaseType}
                      valueHandler={handleReleaseTypeChange}
                      options={options}
                      classNames={selectClassNames(className)}
                      placeholder="Select or type"
                      selectComponents={selectComponents}
                      isDisabled={isDisabled}
                      onKeyDown={handleReleaseTypeKeyDown}
                    />
                  </div>
                </div>
              </>
            );
        }
      },
      [
        handleRefreshClick,
        handleReleaseNameChange,
        handleReleaseTypeChange,
        handleReleaseTypeKeyDown,
        isDisabled,
        loading,
        options,
        releaseName,
        releaseType,
        selectClassNames,
        selectComponents,
      ]
    );

    return isOpened ? (
      <SReleaseDetailsPopup position={position}>
        {className => (
          <div className={className}>
            <div ref={rootRef} className={classes('root')}>
              <div className={classes('header')}>
                <H3 className={classes('title')}>Release Details</H3>
                <button className={classes('close')} onClick={onClose}>
                  <Icon className={classes('close-icon')} color="inherit" name="close" size="general" />
                </button>
              </div>
              <div className={classes('content')}>{renderContent(className)}</div>
            </div>
          </div>
        )}
      </SReleaseDetailsPopup>
    ) : null;
  }
);
