import { Plugin, PluginKey } from "prosemirror-state";
import { createFileInfo } from "../../../../../util/files";
import { Node, Slice, Fragment } from "prosemirror-model";
export const pluginKey = new PluginKey('imageInserterPlugin');
export const createImageInserterPlugin = (imagePastedCallback) => {
    return new Plugin({
        key: pluginKey,
        props: {
            handlePaste: (view, event, slice) => {
                return processPasteEvent(view, event, slice);
            },
            handleDOMEvents: {
                imagePasted: (_, event) => {
                    imagePastedCallback(event.detail);
                },
            },
        },
    });
};
export const imageInserterFactory = (view, base64Data, fileInfo) => {
    return {
        fileInfo: fileInfo,
        insertThumbnail: createThumbnailInserter(view, base64Data, fileInfo),
        insertImage: createImageInserter(view, fileInfo),
        insertFailedThumbnail: createFailedThumbnailInserter(view, fileInfo),
    };
};
const createThumbnailInserter = (view, base64Data, fileInfo) => () => {
    const { state, dispatch } = view;
    const { schema } = state;
    const imageNodeAttrs = createImageNodeAttrs(base64Data, fileInfo, 'loading');
    const placeholderNode = schema.nodes.image.create(imageNodeAttrs);
    const transaction = state.tr.replaceSelectionWith(placeholderNode);
    dispatch(transaction);
};
const createImageInserter = (view, fileInfo) => (src) => {
    const { state, dispatch } = view;
    const { schema } = state;
    const tr = state.tr;
    state.doc.descendants((node, pos) => {
        if (node.attrs.fileInfoId === fileInfo.id) {
            const imageNodeAttrs = createImageNodeAttrs(src !== null && src !== void 0 ? src : node.attrs.src, fileInfo, 'success');
            const imageNode = schema.nodes.image.create(imageNodeAttrs);
            tr.replaceWith(pos, pos + node.nodeSize, imageNode);
            return false;
        }
    });
    dispatch(tr);
};
const createFailedThumbnailInserter = (view, fileInfo) => () => {
    const { state, dispatch } = view;
    const { schema } = state;
    const tr = state.tr;
    state.doc.descendants((node, pos) => {
        if (node.attrs.fileInfoId === fileInfo.id) {
            const imageNodeAttrs = createImageNodeAttrs(node.attrs.src, fileInfo, 'failed');
            const errorPlaceholderNode = schema.nodes.image.create(imageNodeAttrs);
            tr.replaceWith(pos, pos + node.nodeSize, errorPlaceholderNode);
            return false;
        }
    });
    dispatch(tr);
};
function createImageNodeAttrs(src, fileInfo, state) {
    return {
        src: src,
        alt: fileInfo.filename,
        fileInfoId: fileInfo.id,
        state: state,
    };
}
/**
 * Check if a given ProseMirror node or fragment contains any image nodes.
 * @param node - The ProseMirror node or fragment to check.
 * @returns A boolean indicating whether the node contains any image nodes.
 */
const isImageNode = (node) => {
    if (node instanceof Node) {
        if (node.type.name === 'image') {
            return true;
        }
        let found = false;
        // eslint-disable-next-line unicorn/no-array-for-each
        node.content.forEach((child) => {
            if (isImageNode(child)) {
                found = true;
            }
        });
        return found;
    }
    else if (node instanceof Fragment) {
        let found = false;
        // eslint-disable-next-line unicorn/no-array-for-each
        node.forEach((child) => {
            if (isImageNode(child)) {
                found = true;
            }
        });
        return found;
    }
    return false;
};
/**
 * Filter out image nodes from a ProseMirror fragment.
 * @param fragment - The ProseMirror fragment to filter.
 * @returns A new fragment with image nodes removed.
 */
const filterImageNodes = (fragment) => {
    const filteredChildren = [];
    // eslint-disable-next-line unicorn/no-array-for-each
    fragment.forEach((child) => {
        if (!isImageNode(child)) {
            if (child.content.size > 0) {
                const filteredContent = filterImageNodes(child.content);
                const newNode = child.copy(filteredContent);
                filteredChildren.push(newNode);
            }
            else {
                filteredChildren.push(child);
            }
        }
    });
    return Fragment.fromArray(filteredChildren);
};
/**
 * Process a paste event and trigger an imagePasted event if an image file is pasted.
 * If an HTML image element is pasted, this image is filtered out from the slice content.
 *
 * @param view - The ProseMirror editor view.
 * @param event - The paste event.
 * @param slice
 * @returns A boolean; True if an image file was pasted to prevent default paste behavior, otherwise false.
 */
const processPasteEvent = (view, event, slice) => {
    const clipboardData = event.clipboardData;
    if (!clipboardData) {
        return false;
    }
    const isImageFilePasted = handlePastedImages(view, clipboardData);
    const filteredSlice = new Slice(filterImageNodes(slice.content), slice.openStart, slice.openEnd);
    if (filteredSlice.content.childCount < slice.content.childCount) {
        const { state, dispatch } = view;
        const tr = state.tr.replaceSelection(filteredSlice);
        dispatch(tr);
        return true;
    }
    return isImageFilePasted;
};
/**
 * Processes any image files found in the clipboard data and dispatches an imagePasted event.
 *
 * @param view - The ProseMirror editor view
 * @param clipboardData - The clipboard data transfer object containing potential image files
 * @returns True if at least one valid image file was found and processed, false otherwise
 */
function handlePastedImages(view, clipboardData) {
    let isImageFilePasted = false;
    const files = [...(clipboardData.files || [])];
    for (const file of files) {
        if (isImageFile(file, clipboardData)) {
            isImageFilePasted = true;
            const reader = new FileReader();
            reader.onloadend = () => {
                view.dom.dispatchEvent(new CustomEvent('imagePasted', {
                    detail: imageInserterFactory(view, reader.result, createFileInfo(file)),
                }));
            };
            reader.readAsDataURL(file);
        }
    }
    return isImageFilePasted;
}
/**
 * Determines if a file is an image that should be processed by the image handler.
 *
 * This function checks both the file's MIME type and the clipboard HTML content.
 * It filters out HTML content from Excel and HTML tables, as they are not relevant for image processing.
 *
 * @param file - The file object to check
 * @param clipboardData - The full clipboard data transfer object to examine for context
 * @returns True if the file is an image that should be processed, false otherwise
 */
function isImageFile(file, clipboardData) {
    var _a, _b;
    if (!isContentTypeImage(file)) {
        return false;
    }
    const html = (_b = (_a = clipboardData === null || clipboardData === void 0 ? void 0 : clipboardData.getData('text/html')) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : '';
    return !isHtmlFromExcel(html) && !isHtmlTable(html);
}
function isContentTypeImage(file) {
    if (!(file === null || file === void 0 ? void 0 : file.type)) {
        return false;
    }
    return file.type.startsWith('image/');
}
function isHtmlFromExcel(html) {
    if (!html) {
        return false;
    }
    return (html.includes('name=generator content="microsoft excel"') ||
        html.includes('xmlns:x="urn:schemas-microsoft-com:office:excel"'));
}
function isHtmlTable(html) {
    if (!html) {
        return false;
    }
    return html.includes('<table');
}
