"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.clipboard = void 0;
const prosemirror_model_1 = require("prosemirror-model");
const prosemirror_state_1 = require("prosemirror-state");
const core_1 = require("../../../core/index.js");
const logger_1 = require("../../../logger.js");
require("../../../types/spec.js");
const helpers_1 = require("../../../utils/helpers.js");
const selection_1 = require("../../../utils/selection.js");
const serialize_for_clipboard_1 = require("../../../utils/serialize-for-clipboard.js");
const BaseSchema_1 = require("../../base/BaseSchema/index.js");
const code_1 = require("./code.js");
const utils_1 = require("./utils.js");
const clipboard = ({ logger, textParser, mdParser, serializer, pasteFileHandler, }) => {
    return new prosemirror_state_1.Plugin({
        props: {
            handleDOMEvents: {
                copy(view, e) {
                    if (!e.clipboardData)
                        return false;
                    const result = serializeSelected(view, serializer);
                    if (result) {
                        e.preventDefault();
                        setClipboardData(e.clipboardData, result);
                        return true;
                    }
                    return false;
                },
                cut(view, e) {
                    if (!e.clipboardData)
                        return false;
                    const result = serializeSelected(view, serializer);
                    if (result) {
                        e.preventDefault();
                        setClipboardData(e.clipboardData, result);
                        view.dispatch(view.state.tr
                            .deleteSelection()
                            .scrollIntoView()
                            .setMeta('uiEvent', 'cut'));
                        return true;
                    }
                    return false;
                },
                paste(view, e) {
                    if (!e.clipboardData)
                        return false;
                    const pasteLogger = logger.nested({
                        domEvent: 'paste',
                        dataTypes: e.clipboardData.types,
                    });
                    let data;
                    let isPasteHandled = false;
                    if ((0, utils_1.isIosSafariShare)(e.clipboardData) &&
                        (data = e.clipboardData.getData(utils_1.DataTransferType.UriList))) {
                        const schema = view.state.schema;
                        view.dispatch((0, core_1.trackTransactionMetrics)(view.state.tr.replaceSelection(
                        // paste link inline
                        new prosemirror_model_1.Slice(prosemirror_model_1.Fragment.from((0, BaseSchema_1.pType)(schema).create(null, schema.text(data))), 1, 1)), 'paste', { clipboardDataFormat: utils_1.DataTransferType.UriList }));
                        pasteLogger.event({
                            event: 'paste-uri-list',
                        });
                        isPasteHandled = true;
                        e.preventDefault();
                        return true;
                    }
                    if (!e.clipboardData.types.includes(utils_1.DataTransferType.Yfm) &&
                        (data = e.clipboardData.getData(utils_1.DataTransferType.Html))) {
                        const textFromHtml = (0, utils_1.extractTextContentFromHtml)(data);
                        if (textFromHtml) {
                            const res = (0, helpers_1.tryCatch)(() => textParser.parse(textFromHtml));
                            if (res.success) {
                                const docNode = res.result;
                                const slice = getSliceFromMarkupFragment(docNode.content);
                                view.dispatch((0, core_1.trackTransactionMetrics)(view.state.tr.replaceSelection(slice), 'paste', { clipboardDataFormat: utils_1.DataTransferType.Html }));
                                pasteLogger.event({
                                    event: 'paste-parsed-html',
                                });
                                isPasteHandled = true;
                            }
                            else {
                                logger_1.globalLogger.error(res.error);
                                pasteLogger.error(res.error, {
                                    event: 'parse-html',
                                });
                                console.error(res.error);
                            }
                        }
                        else
                            return false; // default html pasting
                    }
                    if (!isPasteHandled &&
                        ((data = e.clipboardData.getData(utils_1.DataTransferType.Yfm)) ||
                            (data = e.clipboardData.getData(utils_1.DataTransferType.Text)))) {
                        let parser;
                        let dataFormat;
                        if (e.clipboardData.types.includes(utils_1.DataTransferType.Yfm)) {
                            parser = mdParser;
                            dataFormat = utils_1.DataTransferType.Yfm;
                        }
                        else {
                            parser = textParser;
                            dataFormat = utils_1.DataTransferType.Text;
                        }
                        const codeType = (0, code_1.isInsideCode)(view.state);
                        if (codeType) {
                            const schema = view.state.schema;
                            const insideCodeData = e.clipboardData.getData(utils_1.DataTransferType.Text);
                            pasteLogger.event({
                                event: 'paste-text-to-code',
                                codeType,
                            });
                            view.dispatch((0, core_1.trackTransactionMetrics)(view.state.tr.replaceSelectionWith(schema.text(codeType === 'inline'
                                ? insideCodeData.trim()
                                : insideCodeData)), 'paste', { clipboardDataFormat: utils_1.DataTransferType.Text, code: codeType }));
                            isPasteHandled = true;
                        }
                        else {
                            const res = (0, helpers_1.tryCatch)(() => parser.parse(data));
                            if (res.success) {
                                const docNode = res.result;
                                const slice = getSliceFromMarkupFragment(docNode.content);
                                pasteLogger.event({
                                    event: 'paste-parsed-content',
                                    dataFormat,
                                });
                                view.dispatch((0, core_1.trackTransactionMetrics)(view.state.tr.replaceSelection(slice), 'paste', { clipboardDataFormat: dataFormat }));
                                isPasteHandled = true;
                            }
                            else {
                                logger_1.globalLogger.error(res.error);
                                pasteLogger.error(res.error, {
                                    event: 'paste',
                                    dataFormat,
                                });
                                console.error(res.error);
                            }
                        }
                    }
                    if (e.clipboardData.files.length && pasteFileHandler) {
                        pasteLogger.event({
                            event: 'paste-files',
                        });
                        for (const file of Array.from(e.clipboardData.files)) {
                            pasteFileHandler(file);
                        }
                        isPasteHandled = true;
                    }
                    if (isPasteHandled) {
                        e.preventDefault();
                        return true;
                    }
                    return false;
                },
            },
        },
    });
};
exports.clipboard = clipboard;
function getSliceFromMarkupFragment(fragment) {
    let start = 0;
    let end = 0;
    if (fragment.firstChild?.isTextblock) {
        start = 1;
        if (fragment.childCount === 1) {
            end = 1;
        }
    }
    return new prosemirror_model_1.Slice(fragment, start, end);
}
function serializeSelected(view, serializer) {
    const sel = view.state.selection;
    if (sel.empty)
        return null;
    if (getSharedDepthNode(sel).type.spec.code) {
        const fragment = sel.content().content;
        return { text: fragment.textBetween(0, fragment.size) };
    }
    // FIXME: Verify and use Node instead of Fragment
    const markup = serializer.serialize(getCopyContent(view.state).content);
    const { dom, text } = (0, serialize_for_clipboard_1.serializeForClipboard)(view, sel.content());
    return { markup, text, html: dom.innerHTML };
}
function setClipboardData(data, result) {
    data.clearData();
    if (typeof result.markup === 'string')
        data.setData(utils_1.DataTransferType.Yfm, result.markup);
    if (typeof result.html === 'string')
        data.setData(utils_1.DataTransferType.Html, result.html);
    data.setData(utils_1.DataTransferType.Text, result.text);
}
function getCopyContent(state) {
    const sel = state.selection;
    if ((0, selection_1.isWholeSelection)(sel)) {
        return new prosemirror_model_1.Slice(state.doc.content, 0, 0);
    }
    if (isTextSelectionWithinSameTextBlock(sel)) {
        return new prosemirror_model_1.Slice(createFragmentFromInlineSelection(state), 0, 0);
    }
    if ((0, selection_1.isNodeSelection)(sel)) {
        const { node } = sel;
        if (node.type.spec.complex) {
            if (node.isTextblock) {
                return new prosemirror_model_1.Slice(prosemirror_model_1.Fragment.from(state.schema.node(BaseSchema_1.BaseNode.Paragraph, {}, node.content)), 1, 1);
            }
            else {
                const { complex } = node.type.spec;
                if (complex === 'root') {
                    return new prosemirror_model_1.Slice(prosemirror_model_1.Fragment.from(node), 1, 1);
                }
                else if (complex === 'leaf') {
                    return new prosemirror_model_1.Slice(prosemirror_model_1.Fragment.from(node.content), 1, 1);
                }
                else {
                    // TODO: handle copy of intermediate complex block node
                }
            }
        }
        else {
            return new prosemirror_model_1.Slice(prosemirror_model_1.Fragment.from(node), 1, 1);
        }
    }
    let slice = getSelectionContent(sel);
    // replace first node with paragraph if needed
    if (slice.content.firstChild?.isTextblock &&
        slice.content.firstChild.type.name !== BaseSchema_1.BaseNode.Paragraph) {
        let shouldTransformFirstChildToPara = true;
        const { firstChild } = slice.content;
        // if all text selected inside first node, don't replace it with paragraph
        if (firstChild.eq(sel.$from.parent)) {
            shouldTransformFirstChildToPara = false;
        }
        if (shouldTransformFirstChildToPara) {
            slice = new prosemirror_model_1.Slice(slice.content.replaceChild(0, state.schema.node(BaseSchema_1.BaseNode.Paragraph, {}, firstChild.content)), slice.openStart, slice.openStart);
        }
    }
    return slice;
}
function isTextSelectionWithinSameTextBlock(sel) {
    return (0, selection_1.isTextSelection)(sel) && sel.$from.sameParent(sel.$to) && sel.$from.parent.isTextblock;
}
function createFragmentFromInlineSelection(state) {
    const sel = state.selection;
    const inlineSlice = state.doc.slice(sel.from, sel.to);
    const isComplexTextblock = Boolean(sel.$from.parent.type.spec.complex);
    const isAllContentSelected = sel.$from.parent.content.eq(inlineSlice.content);
    if (isComplexTextblock || !isAllContentSelected) {
        // copy selected inline content to paragraph if current node is complex
        // or if only part of it's content was selected
        return prosemirror_model_1.Fragment.from(state.schema.node(BaseSchema_1.BaseNode.Paragraph, {}, inlineSlice.content));
    }
    return prosemirror_model_1.Fragment.from(sel.$from.parent.copy(inlineSlice.content));
}
/**
 * Like `selection.content()`, but smarter.
 * Copy a structure of complex nodes,
 * e.g. if select part of cut title it creates slice with yfm-cut –> yfm-cut-title -> selected text
 * it works well with simple nodes, but to handle cases as described above, custom logic needed
 */
function getSelectionContent(sel) {
    const sharedNodeType = getSharedDepthNode(sel).type;
    const sharedNodeComplex = sharedNodeType.spec.complex;
    const includeParents = sharedNodeComplex && sharedNodeComplex !== 'leaf';
    return sel.$from.doc.slice(sel.$from.pos, sel.to, includeParents);
}
function getSharedDepthNode({ $from, $to }) {
    return $from.node($from.sharedDepth($to.pos));
}
//# sourceMappingURL=clipboard.js.map
