import { Fragment, Slice } from 'prosemirror-model';
import { Plugin } from 'prosemirror-state';
import { trackTransactionMetrics } from "../../../core/index.js";
import { logger } from "../../../logger.js";
import "../../../types/spec.js";
import { tryCatch } from "../../../utils/helpers.js";
import { isNodeSelection, isTextSelection, isWholeSelection } from "../../../utils/selection.js";
import { serializeForClipboard } from "../../../utils/serialize-for-clipboard.js";
import { BaseNode, pType } from "../../base/BaseSchema/index.js";
import { isInsideCode } from "./code.js";
import { DataTransferType, extractTextContentFromHtml, isIosSafariShare } from "./utils.js";
export const clipboard = ({ textParser, mdParser, serializer, pasteFileHandler, }) => {
    return new 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;
                    let data;
                    let isPasteHandled = false;
                    if (isIosSafariShare(e.clipboardData) &&
                        (data = e.clipboardData.getData(DataTransferType.UriList))) {
                        const schema = view.state.schema;
                        view.dispatch(trackTransactionMetrics(view.state.tr.replaceSelection(
                        // paste link inline
                        new Slice(Fragment.from(pType(schema).create(null, schema.text(data))), 1, 1)), 'paste', { clipboardDataFormat: DataTransferType.UriList }));
                        isPasteHandled = true;
                        e.preventDefault();
                        return true;
                    }
                    if (!e.clipboardData.types.includes(DataTransferType.Yfm) &&
                        (data = e.clipboardData.getData(DataTransferType.Html))) {
                        const textFromHtml = extractTextContentFromHtml(data);
                        if (textFromHtml) {
                            const res = tryCatch(() => textParser.parse(textFromHtml));
                            if (res.success) {
                                const docNode = res.result;
                                const slice = getSliceFromMarkupFragment(docNode.content);
                                view.dispatch(trackTransactionMetrics(view.state.tr.replaceSelection(slice), 'paste', { clipboardDataFormat: DataTransferType.Html }));
                                isPasteHandled = true;
                            }
                            else {
                                logger.error(res.error);
                                console.error(res.error);
                            }
                        }
                        else
                            return false; // default html pasting
                    }
                    if (!isPasteHandled &&
                        ((data = e.clipboardData.getData(DataTransferType.Yfm)) ||
                            (data = e.clipboardData.getData(DataTransferType.Text)))) {
                        let parser;
                        let dataFormat;
                        if (e.clipboardData.types.includes(DataTransferType.Yfm)) {
                            parser = mdParser;
                            dataFormat = DataTransferType.Yfm;
                        }
                        else {
                            parser = textParser;
                            dataFormat = DataTransferType.Text;
                        }
                        const codeType = isInsideCode(view.state);
                        if (codeType) {
                            const schema = view.state.schema;
                            const insideCodeData = e.clipboardData.getData(DataTransferType.Text);
                            view.dispatch(trackTransactionMetrics(view.state.tr.replaceSelectionWith(schema.text(codeType === 'inline'
                                ? insideCodeData.trim()
                                : insideCodeData)), 'paste', { clipboardDataFormat: DataTransferType.Text, code: codeType }));
                            isPasteHandled = true;
                        }
                        else {
                            const res = tryCatch(() => parser.parse(data));
                            if (res.success) {
                                const docNode = res.result;
                                const slice = getSliceFromMarkupFragment(docNode.content);
                                view.dispatch(trackTransactionMetrics(view.state.tr.replaceSelection(slice), 'paste', { clipboardDataFormat: dataFormat }));
                                isPasteHandled = true;
                            }
                            else {
                                logger.error(res.error);
                                console.error(res.error);
                            }
                        }
                    }
                    if (e.clipboardData.files.length && pasteFileHandler) {
                        for (const file of Array.from(e.clipboardData.files)) {
                            pasteFileHandler(file);
                        }
                        isPasteHandled = true;
                    }
                    if (isPasteHandled) {
                        e.preventDefault();
                        return true;
                    }
                    return false;
                },
            },
        },
    });
};
function getSliceFromMarkupFragment(fragment) {
    let start = 0;
    let end = 0;
    if (fragment.firstChild?.isTextblock) {
        start = 1;
        if (fragment.childCount === 1) {
            end = 1;
        }
    }
    return new 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 } = serializeForClipboard(view, sel.content());
    return { markup, text, html: dom.innerHTML };
}
function setClipboardData(data, result) {
    data.clearData();
    if (typeof result.markup === 'string')
        data.setData(DataTransferType.Yfm, result.markup);
    if (typeof result.html === 'string')
        data.setData(DataTransferType.Html, result.html);
    data.setData(DataTransferType.Text, result.text);
}
function getCopyContent(state) {
    const sel = state.selection;
    if (isWholeSelection(sel)) {
        return new Slice(state.doc.content, 0, 0);
    }
    if (isTextSelectionWithinSameTextBlock(sel)) {
        return new Slice(createFragmentFromInlineSelection(state), 0, 0);
    }
    if (isNodeSelection(sel)) {
        const { node } = sel;
        if (node.type.spec.complex) {
            if (node.isTextblock) {
                return new Slice(Fragment.from(state.schema.node(BaseNode.Paragraph, {}, node.content)), 1, 1);
            }
            else {
                const { complex } = node.type.spec;
                if (complex === 'root') {
                    return new Slice(Fragment.from(node), 1, 1);
                }
                else if (complex === 'leaf') {
                    return new Slice(Fragment.from(node.content), 1, 1);
                }
                else {
                    // TODO: handle copy of intermediate complex block node
                }
            }
        }
        else {
            return new Slice(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 !== 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 Slice(slice.content.replaceChild(0, state.schema.node(BaseNode.Paragraph, {}, firstChild.content)), slice.openStart, slice.openStart);
        }
    }
    return slice;
}
function isTextSelectionWithinSameTextBlock(sel) {
    return 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 Fragment.from(state.schema.node(BaseNode.Paragraph, {}, inlineSlice.content));
    }
    return 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
