"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.YfmHtmlBlockView = exports.STOP_EVENT_CLASSNAME = exports.cnYfmHtmlBlock = void 0;
exports.generateID = generateID;
const jsx_runtime_1 = require("react/jsx-runtime");
const react_1 = require("react");
const html_extension_1 = require("@diplodoc/html-extension");
const icons_1 = require("@gravity-ui/icons");
const uikit_1 = require("@gravity-ui/uikit");
const classname_1 = require("../../../../classname.js");
const SharedState_1 = require("../../../behavior/SharedState/index.js");
const TextInput_1 = require("../../../../forms/TextInput.js");
const common_1 = require("../../../../i18n/common/index.js");
const lodash_1 = require("../../../../lodash.js");
const hooks_1 = require("../../../../react-utils/hooks.js");
const useSharedEditingState_1 = require("../../../../react-utils/useSharedEditingState.js");
const remove_node_1 = require("../../../../utils/remove-node.js");
const const_1 = require("../YfmHtmlBlockSpecs/const.js");
require("./YfmHtmlBlock.css");
exports.cnYfmHtmlBlock = (0, classname_1.cn)('yfm-html-block');
exports.STOP_EVENT_CLASSNAME = 'prosemirror-stop-event';
const b = exports.cnYfmHtmlBlock;
function generateID() {
    return Math.random().toString(36).substr(2, 8);
}
const DEFAULT_PADDING = 20;
const DEFAULT_DELAY = 100;
const createLinkCLickHandler = (value, document) => (event) => {
    event.preventDefault();
    const targetId = value.getAttribute('href');
    if (targetId) {
        const targetElement = document.querySelector(targetId);
        if (targetElement) {
            targetElement.scrollIntoView({ behavior: 'smooth' });
        }
    }
};
const YfmHtmlBlockPreview = ({ html, onClick, config }) => {
    const ref = (0, react_1.useRef)(null);
    const styles = (0, react_1.useRef)({});
    const classNames = (0, react_1.useRef)([]);
    const resizeConfig = (0, react_1.useRef)({});
    const [height, setHeight] = (0, react_1.useState)('100%');
    (0, react_1.useEffect)(() => {
        setStyles(config?.styles);
        setClassNames(config?.classNames);
    }, [config, ref.current?.contentWindow?.document?.body]);
    const handleLoadIFrame = () => {
        const contentWindow = ref.current?.contentWindow;
        handleResizeIFrame();
        if (contentWindow) {
            const frameDocument = contentWindow.document;
            frameDocument.addEventListener('dblclick', () => {
                onClick();
            });
        }
    };
    const handleResizeIFrame = () => {
        if (ref.current) {
            const contentWindow = ref.current?.contentWindow;
            if (contentWindow) {
                const body = contentWindow.document.body;
                if (body) {
                    const height = body.scrollHeight +
                        (resizeConfig.current?.padding || DEFAULT_PADDING) +
                        'px';
                    setHeight(height);
                }
            }
        }
    };
    const setClassNames = (newClassNames) => {
        const body = ref.current?.contentWindow?.document.body;
        if (body && newClassNames) {
            const previousClassNames = classNames.current;
            // remove all classes that were in previousClassNames but are not in classNames
            previousClassNames.forEach((className) => {
                if (!newClassNames.includes(className)) {
                    body.classList.remove(className);
                }
            });
            // add classes that are in classNames
            newClassNames.forEach((className) => {
                if (!body.classList.contains(className)) {
                    body.classList.add(className);
                }
            });
            classNames.current = newClassNames;
        }
    };
    const setStyles = (newStyles) => {
        const body = ref.current?.contentWindow?.document.body;
        if (body && newStyles) {
            const previousStyles = styles.current;
            // remove all styles that are in previousStyles but not in styles
            Object.keys(previousStyles).forEach((property) => {
                if (!Object.prototype.hasOwnProperty.call(newStyles, property)) {
                    body.style.removeProperty(property);
                }
            });
            // sdd or update styles that are in styles
            Object.keys(newStyles).forEach((property) => {
                body.style.setProperty(property, newStyles[property]);
            });
            // update current styles to the new styles
            styles.current = newStyles;
        }
    };
    // finds all relative links (href^="#") and changes their click behavior
    const createAnchorLinkHandlers = (type) => () => {
        const document = ref.current?.contentWindow.document;
        if (document) {
            document.querySelectorAll('a[href^="#"]').forEach((value) => {
                const handler = createLinkCLickHandler(value, document);
                if (type === 'add') {
                    value.addEventListener('click', handler);
                }
                else {
                    value.removeEventListener('click', handler);
                }
            });
        }
    };
    (0, react_1.useEffect)(() => {
        ref.current?.addEventListener('load', handleLoadIFrame);
        ref.current?.addEventListener('load', createAnchorLinkHandlers('add'));
        return () => {
            ref.current?.removeEventListener('load', handleLoadIFrame);
            ref.current?.removeEventListener('load', createAnchorLinkHandlers('remove'));
        };
    }, [html]);
    (0, react_1.useEffect)(() => {
        if (ref.current) {
            const resizeObserver = new window.ResizeObserver((0, lodash_1.debounce)(handleResizeIFrame, DEFAULT_DELAY));
            resizeObserver.observe(ref.current);
        }
    }, [ref.current?.contentWindow?.document?.body]);
    return ((0, jsx_runtime_1.jsx)("iframe", { style: {
            height,
        }, ref: ref, title: generateID(), frameBorder: 0, className: b('content'), srcDoc: html }));
};
const CodeEditMode = ({ initialText, onSave, onCancel, options: { autoSave } }) => {
    const { value, handleChange, handleManualSave, isSaveDisabled, isAutoSaveEnabled } = (0, hooks_1.useAutoSave)({
        initialValue: initialText || '\n',
        onSave,
        onClose: onCancel,
        autoSave,
    });
    return ((0, jsx_runtime_1.jsx)("div", { className: b({ editing: true }), children: (0, jsx_runtime_1.jsxs)("div", { className: b('editor'), children: [(0, jsx_runtime_1.jsx)(TextInput_1.TextAreaFixed, { controlProps: {
                        className: exports.STOP_EVENT_CLASSNAME,
                    }, value: value, onUpdate: handleChange, autoFocus: true }), (0, jsx_runtime_1.jsx)("div", { className: b('controls'), children: (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)(uikit_1.Button, { onClick: onCancel, view: 'flat', children: (0, jsx_runtime_1.jsx)("span", { className: exports.STOP_EVENT_CLASSNAME, children: isAutoSaveEnabled ? (0, common_1.i18n)('close') : (0, common_1.i18n)('cancel') }) }), !isAutoSaveEnabled && ((0, jsx_runtime_1.jsx)(uikit_1.Button, { onClick: handleManualSave, view: 'action', disabled: isSaveDisabled, children: (0, jsx_runtime_1.jsx)("span", { className: exports.STOP_EVENT_CLASSNAME, children: (0, common_1.i18n)('save') }) }))] }) })] }) }));
};
const YfmHtmlBlockView = ({ onChange, node, getPos, view, options }) => {
    const { useConfig, sanitize, styles, baseTarget = '_parent', head: headContent = '' } = options;
    const entityId = node.attrs[const_1.YfmHtmlBlockConsts.NodeAttrs.EntityId];
    const entityKey = (0, react_1.useMemo)(() => SharedState_1.SharedStateKey.define({ name: entityId }), [entityId]);
    const config = useConfig?.();
    const [editing, setEditing, unsetEditing] = (0, useSharedEditingState_1.useSharedEditingState)(view, entityKey);
    const [menuOpen, _openMenu, closeMenu, toggleMenuOpen] = (0, hooks_1.useBooleanState)(false);
    const [anchorElement, setAnchorElement] = (0, hooks_1.useElementState)();
    if (editing) {
        return ((0, jsx_runtime_1.jsx)(CodeEditMode, { initialText: node.attrs[const_1.YfmHtmlBlockConsts.NodeAttrs.srcdoc], onCancel: unsetEditing, onSave: (v) => {
                onChange({ [const_1.YfmHtmlBlockConsts.NodeAttrs.srcdoc]: v });
            }, options: options }));
    }
    let additional = baseTarget ? `<base target="${baseTarget}">` : '';
    if (styles) {
        const stylesContent = typeof styles === 'string'
            ? `<link rel="stylesheet" href="${styles}" />`
            : `<style>${(0, html_extension_1.getStyles)(styles)}</style>`;
        additional += stylesContent;
    }
    const head = `<head>${headContent || additional}</head>`;
    const body = `<body>${node.attrs[const_1.YfmHtmlBlockConsts.NodeAttrs.srcdoc] ?? ''}</body>`;
    const html = `<!DOCTYPE html><html>${head}${body}</html>`;
    const sanitizeFunction = typeof sanitize === 'function' ? sanitize : sanitize?.body;
    const resultHtml = sanitizeFunction ? sanitizeFunction(html) : html;
    return ((0, jsx_runtime_1.jsxs)("div", { className: b(), onDoubleClick: setEditing, children: [(0, jsx_runtime_1.jsx)(uikit_1.Label, { className: b('label'), icon: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { size: 16, data: icons_1.Eye }), children: (0, common_1.i18n)('preview') }), (0, jsx_runtime_1.jsx)(YfmHtmlBlockPreview, { html: resultHtml, onClick: setEditing, config: config }), (0, jsx_runtime_1.jsxs)("div", { className: b('menu'), children: [(0, jsx_runtime_1.jsx)(uikit_1.Button, { onClick: toggleMenuOpen, ref: setAnchorElement, size: "s", className: exports.STOP_EVENT_CLASSNAME, children: (0, jsx_runtime_1.jsx)(uikit_1.Icon, { data: icons_1.Ellipsis, className: exports.STOP_EVENT_CLASSNAME }) }), (0, jsx_runtime_1.jsx)(uikit_1.Popup, { anchorElement: anchorElement, open: menuOpen, onOpenChange: closeMenu, placement: "bottom-end", children: (0, jsx_runtime_1.jsxs)(uikit_1.Menu, { children: [(0, jsx_runtime_1.jsx)(uikit_1.Menu.Item, { onClick: () => {
                                        setEditing();
                                        closeMenu();
                                    }, children: (0, common_1.i18n)('edit') }), (0, jsx_runtime_1.jsx)(uikit_1.Menu.Item, { onClick: () => {
                                        const pos = getPos();
                                        if (pos === undefined)
                                            return;
                                        (0, remove_node_1.removeNode)({
                                            node,
                                            pos,
                                            tr: view.state.tr,
                                            dispatch: view.dispatch,
                                        });
                                    }, children: (0, common_1.i18n)('remove') })] }) })] })] }));
};
exports.YfmHtmlBlockView = YfmHtmlBlockView;
//# sourceMappingURL=YfmHtmlBlockView.js.map
