import React from 'react';

import {
    PluginTitle as DashKitPluginTitle,
    PLUGIN_ROOT_ATTR_NAME,
    pluginTitle,
} from '@gravity-ui/dashkit';
import type {Plugin, PluginTitleProps} from '@gravity-ui/dashkit';
import block from 'bem-cn-lite';
import debounce from 'lodash/debounce';
import {CustomPaletteBgColors, EXPORT_PRINT_HIDDEN_ATTR} from 'shared';
import type {DashTabItemTitle} from 'shared';
import {
    adjustWidgetLayout as dashkitAdjustWidgetLayout,
    usePreparedWrapSettings,
    useTextColorStyles,
} from 'ui/components/DashKit/utils';
import {MarkdownHelpPopover} from 'ui/components/MarkdownHelpPopover/MarkdownHelpPopover';
import {DL} from 'ui/constants';

import {useBeforeLoad} from '../../../../hooks/useBeforeLoad';
import type {CommonPluginSettings} from '../../DashKit';
import {useWidgetContext} from '../../context/WidgetContext';
import {RendererWrapper} from '../RendererWrapper/RendererWrapper';

import {AnchorLink} from './AnchorLink/AnchorLink';
import {
    getFontStyleBySize,
    getHelpIconSizeBySize,
    getTopOffsetBySize,
    isTitleOverflowed,
} from './utils';

import './Title.scss';

const b = block('dashkit-plugin-title-container');

type PluginTitleObjectSettings = CommonPluginSettings & {
    hideAnchor?: boolean;
    hideHint?: boolean;
};

type Props = PluginTitleProps & PluginTitleObjectSettings;

type PluginTitle = Plugin<Props> &
    CommonPluginSettings & {
        setSettings: (settings: PluginTitleObjectSettings) => PluginTitle;
        hideAnchor?: boolean;
        hideHint?: boolean;
    };

const WIDGET_RESIZE_DEBOUNCE_TIMEOUT = 100;

// text can be placed directly on the upper border of container,
// in which case a small negative offset is needed
const MIN_AVAILABLE_TOP_OFFSET = -5;

const titlePlugin: PluginTitle = {
    ...pluginTitle,
    setSettings(settings: PluginTitleObjectSettings) {
        const {hideAnchor, hideHint, entryWidgetSettings} = settings;

        titlePlugin.hideAnchor = hideAnchor;
        titlePlugin.hideHint = hideHint;
        titlePlugin.entryWidgetSettings = entryWidgetSettings;
        return titlePlugin;
    },
    renderer: function PluginTitleRenderer(
        props: Props,
        forwardedRef: React.LegacyRef<DashKitPluginTitle> | undefined,
    ) {
        const rootNodeRef = React.useRef<HTMLDivElement>(null);
        const contentRef = React.useRef<HTMLDivElement>(null);
        const extraRef = React.useRef<HTMLDivElement>(null);

        useWidgetContext({
            id: props.id,
            elementRef: rootNodeRef,
        });

        const [isInlineExtraElements, setIsInlineExtraElements] = React.useState<boolean | null>(
            false,
        );
        const [extraElementsTop, setExtraElementsTop] = React.useState<number | undefined>(
            undefined,
        );

        const data = props.data as DashTabItemTitle['data'];

        const handleUpdate = useBeforeLoad(props.onBeforeLoad);

        /**
         * Ref layout so that we could use actual state without passing link to layout object
         */
        const layoutRef = React.useRef(props.layout);
        layoutRef.current = props.layout;

        /**
         * call common for charts & selectors adjust function for widget
         */
        const adjustLayout = React.useCallback(
            debounce((needSetDefault) => {
                dashkitAdjustWidgetLayout({
                    widgetId: props.id,
                    needSetDefault,
                    rootNode: rootNodeRef,
                    gridLayout: props.gridLayout,
                    layout: layoutRef.current,
                    // TODO: optimize call times in future
                    cb: (...args) => {
                        return props.adjustWidgetLayout(...args);
                    },
                    mainNodeSelector: `[${PLUGIN_ROOT_ATTR_NAME}="title"]`,
                    scrollableNodeSelector: `.${b()}`,
                    needHeightReset: true,
                });
            }, WIDGET_RESIZE_DEBOUNCE_TIMEOUT),
            [props.id, props.adjustWidgetLayout, props.gridLayout],
        );

        React.useEffect(() => {
            adjustLayout(!data.autoHeight);
        }, [adjustLayout, data.autoHeight, props.data?.text, props.data?.size]);

        const content = <DashKitPluginTitle {...props} ref={forwardedRef} />;

        const showHint = Boolean(!titlePlugin.hideHint && data.hint?.enabled && data.hint.text);
        const showAnchor = !titlePlugin.hideAnchor && !DL.IS_MOBILE;

        const withInlineExtraElements = (showAnchor || showHint) && isInlineExtraElements;

        const withAbsoluteAnchor = showAnchor && !isInlineExtraElements;
        const withAbsoluteHint = showHint && !isInlineExtraElements;

        const {style, hasInternalMargins} = usePreparedWrapSettings({
            data,
            entryWidgetSettings: titlePlugin.entryWidgetSettings,
            defaultOldColor: CustomPaletteBgColors.NONE,
        });

        const textColorStyles = useTextColorStyles(data.textColor, data.textSettings?.color);
        const wrapperStyles = {...style, ...textColorStyles};

        const currentLayout = props.layout.find(({i}) => i === props.id) || {
            x: null,
            y: null,
            h: null,
            w: null,
        };

        React.useEffect(() => {
            handleUpdate?.();
            // eslint-disable-next-line react-hooks/exhaustive-deps
        }, [
            currentLayout.x,
            currentLayout.y,
            currentLayout.h,
            currentLayout.w,
            data.background,
            data.size,
            data.text,
        ]);

        const calculateExtraElements = React.useCallback(() => {
            if (contentRef.current && rootNodeRef.current) {
                const contentRect = contentRef.current.getBoundingClientRect();
                const rootRect = rootNodeRef.current.getBoundingClientRect();

                const offsetTop = contentRect.top - rootRect.top;

                const titleElement = contentRef.current.children[0];
                const isWidthFits =
                    titleElement instanceof HTMLDivElement && extraRef.current
                        ? !isTitleOverflowed(contentRef.current, extraRef.current)
                        : contentRect.width <= rootRect.width;

                const calculatedAnchorTop =
                    offsetTop < MIN_AVAILABLE_TOP_OFFSET || isWidthFits ? 0 : offsetTop;

                setExtraElementsTop(calculatedAnchorTop);
                setIsInlineExtraElements(isWidthFits);
            }
        }, []);

        React.useLayoutEffect(() => {
            if (showAnchor || showHint) {
                calculateExtraElements();
            } else {
                setIsInlineExtraElements(false);
            }
        }, [
            currentLayout.x,
            currentLayout.h,
            currentLayout.w,
            data.text,
            data.size,
            calculateExtraElements,
            showAnchor,
            showHint,
        ]);

        React.useEffect(() => {
            if (!showAnchor && !showHint) {
                return undefined;
            }

            const debouncedCalculateAnchor = debounce(
                calculateExtraElements,
                WIDGET_RESIZE_DEBOUNCE_TIMEOUT,
            );
            window.addEventListener('resize', debouncedCalculateAnchor);

            return () => {
                window.removeEventListener('resize', debouncedCalculateAnchor);
            };
        }, [showAnchor, showHint]);

        const getStyles = () => {
            const fontStyles = getFontStyleBySize(data.size);

            if (isInlineExtraElements) {
                return fontStyles;
            }

            return {
                ...fontStyles,
                top: showAnchor
                    ? extraElementsTop
                    : getTopOffsetBySize(data.size, hasInternalMargins),
            };
        };

        return (
            <RendererWrapper id={props.id} type="title" nodeRef={rootNodeRef} style={wrapperStyles}>
                <div
                    className={b({
                        'with-auto-height': Boolean(data.autoHeight),
                        'with-internal-margins': Boolean(hasInternalMargins),
                        'with-inline-extra-elements': Boolean(withInlineExtraElements),
                        'with-absolute-anchor': withAbsoluteAnchor && !withAbsoluteHint,
                        'with-absolute-hint': withAbsoluteHint && !withAbsoluteAnchor,
                        'with-absolute-hint-and-anchor': withAbsoluteHint && withAbsoluteAnchor,
                        absolute: !isInlineExtraElements,
                        relative: !showAnchor,
                    })}
                    ref={contentRef}
                >
                    {content}
                    <div
                        className={b('extra-elements-container', {
                            absolute: !isInlineExtraElements,
                        })}
                        style={getStyles()}
                        ref={extraRef}
                        {...{[EXPORT_PRINT_HIDDEN_ATTR]: true}}
                    >
                        {showHint && (
                            <MarkdownHelpPopover
                                iconSize={getHelpIconSizeBySize(data.size)}
                                markdown={data.hint?.text ?? ''}
                                className={b('hint')}
                                popoverProps={{
                                    placement: 'bottom',
                                }}
                            />
                        )}
                        <AnchorLink to={data.text} show={showAnchor} />
                    </div>
                </div>
            </RendererWrapper>
        );
    },
};

export default titlePlugin;
