// Map to keep track of active Crepe editor instances by elementId (container ID)
const editorCache = new Map();

/**
 * Asynchronously injects a stylesheet link tag into the document head
 * and returns a Promise that resolves when the stylesheet is fully loaded.
 */
async function ensureStylesheet(href) {
    if (document.querySelector(`link[href="${href}"]`)) {
        return Promise.resolve();
    }
    return new Promise((resolve, reject) => {
        const link = document.createElement('link');
        link.rel = 'stylesheet';
        link.href = href;
        link.onload = () => resolve();
        link.onerror = (err) => reject(new Error(`Failed to load stylesheet: ${href}. ${err}`));
        document.head.appendChild(link);
    });
}

/**
 * Initializes a Milkdown Crepe editor on the specified element.
 */
export async function initEditor(elementId, dotNetHelper, initialMarkdown) {
    const container = document.getElementById(elementId);
    if (!container) {
        console.error(`[Milkdown] Container with ID "${elementId}" not found.`);
        return;
    }

    try {
        // Condition 2: Prevent FOUC by loading stylesheets before instantiating the editor
        await ensureStylesheet('/_content/NexusReader.UI.Shared/css/vendor/milkdown-crepe.css');

        // Dynamically import the local JS bundle
        await import('/_content/NexusReader.UI.Shared/js/vendor/milkdown-crepe.js');

        // Get Crepe constructor from the global window.milkdownCrepe namespace
        const Crepe = window.milkdownCrepe?.Crepe;
        if (!Crepe) {
            throw new Error("Crepe constructor not found on window.milkdownCrepe");
        }

        // Initialize the Crepe editor instance with custom ImageBlock upload handler
        const crepe = new Crepe({
            root: container,
            defaultValue: initialMarkdown || "",
            featureConfigs: {
                [Crepe.Feature.ImageBlock]: {
                    onUpload: async (file) => {
                        try {
                            const streamRef = DotNet.createJSStreamReference(file);
                            const url = await dotNetHelper.invokeMethodAsync('UploadImageFromJs', file.name, file.type, streamRef);
                            return url;
                        } catch (err) {
                            console.error("[Milkdown] Failed to upload image from JS (onUpload):", err);
                            throw err;
                        }
                    }
                }
            }
        });

        // Configure custom uploader using the uploadConfig context slice
        crepe.editor.config((ctx) => {
            try {
                ctx.update('uploadConfig', (prev) => ({
                    ...prev,
                    uploader: async (files, schema) => {
                        const nodes = [];
                        for (let i = 0; i < files.length; i++) {
                            const file = files[i];
                            if (file.type.startsWith('image/')) {
                                try {
                                    const streamRef = DotNet.createJSStreamReference(file);
                                    const uploadedUrl = await dotNetHelper.invokeMethodAsync('UploadImageFromJs', file.name, file.type, streamRef);
                                    if (uploadedUrl) {
                                        const node = schema.nodes.image.create({ src: uploadedUrl, alt: file.name });
                                        nodes.push(node);
                                    }
                                } catch (err) {
                                    console.error("[Milkdown] Failed to upload image in custom uploader:", err);
                                }
                            }
                        }
                        return nodes;
                    }
                }));
            } catch (err) {
                console.error("[Milkdown] Failed to configure uploadConfig uploader:", err);
            }
        });

        // Hook into the Crepe content update listener system with 300ms JS debounce
        let debounceTimeout = null;
        crepe.on((listener) => {
            listener.markdownUpdated((ctx, markdown, prevMarkdown) => {
                if (debounceTimeout) {
                    clearTimeout(debounceTimeout);
                }
                debounceTimeout = setTimeout(() => {
                    dotNetHelper.invokeMethodAsync('OnEditorContentChanged', markdown)
                        .catch(err => console.error("[Milkdown] Failed to notify editor content changed:", err));
                }, 300);
            });
        });

        // Store the editor instance in the map
        editorCache.set(elementId, crepe);

        // Create the editor view asynchronously
        await crepe.create();

        console.log(`[Milkdown] Editor successfully initialized on element: ${elementId}`);
    } catch (error) {
        console.error(`[Milkdown] Failed to initialize editor on "${elementId}":`, error);
    }
}

/**
 * Retrieves the current Markdown content from a specific editor instance.
 */
export function getMarkdownContent(elementId) {
    const crepe = editorCache.get(elementId);
    if (!crepe) {
        console.warn(`[Milkdown] No editor instance found for element: ${elementId}`);
        return "";
    }
    return crepe.getMarkdown();
}

/**
 * Safely disposes of the editor instance to prevent memory leaks in WASM.
 */
export async function destroyEditor(elementId) {
    const crepe = editorCache.get(elementId);
    if (crepe) {
        try {
            await crepe.destroy();
            console.log(`[Milkdown] Editor instance successfully destroyed: ${elementId}`);
        } catch (error) {
            console.error(`[Milkdown] Error destroying editor for element "${elementId}":`, error);
        }
        editorCache.delete(elementId);
    }
}

/**
 * Safely retrieves all localStorage keys starting with the "nexus-bkp-" prefix.
 */
export function getBackupKeys() {
    const keys = [];
    try {
        for (let i = 0; i < localStorage.length; i++) {
            const key = localStorage.key(i);
            if (key && key.startsWith('nexus-bkp-')) {
                keys.push(key);
            }
        }
    } catch (err) {
        console.error("[Milkdown] Error listing localStorage keys:", err);
    }
    return keys;
}
