export const PARHELIA_PARENT_BRIDGE_PATH = "/parhelia/editor-bridge/v1/parhelia-bridge.js";
const BRIDGE_SCRIPT_SELECTOR = 'script[data-parhelia-editor-bridge="runtime"]';
const EDITABLE_MARKER_SELECTOR = [
    "[data-component-start]",
    "[data-component-id]",
    "[data-renderingid]",
    "[data-placeholder-start]",
    "[data-placeholder-key]",
    "[data-placeholderkey]",
    'code[type="text/sitecore"]',
    ".scpm",
].join(",");
const DEFAULT_MARKER_WAIT_MS = 1000;
const DEFAULT_SCRIPT_WATCHDOG_MS = 2000;
export function injectParheliaBridgeFromParent(iframe, options = {}) {
    const parentOrigin = options.parentOrigin ?? window.location.origin;
    const bridgePath = options.bridgePath ?? PARHELIA_PARENT_BRIDGE_PATH;
    const markerWaitMs = options.markerWaitMs ?? DEFAULT_MARKER_WAIT_MS;
    const scriptWatchdogMs = options.scriptWatchdogMs ?? DEFAULT_SCRIPT_WATCHDOG_MS;
    let disposed = false;
    let terminal = false;
    let observer;
    let markerTimer;
    let scriptWatchdogTimer;
    let latestAttempt = {
        status: "inaccessible",
    };
    const emit = (attempt) => {
        latestAttempt = attempt;
        options.onUpdate?.(attempt);
    };
    const clearMarkerWait = () => {
        if (observer) {
            observer.disconnect();
            observer = undefined;
        }
        if (markerTimer !== undefined) {
            window.clearTimeout(markerTimer);
            markerTimer = undefined;
        }
    };
    const finish = (attempt) => {
        if (disposed || terminal)
            return;
        terminal = true;
        clearMarkerWait();
        emit(attempt);
    };
    const dispose = () => {
        disposed = true;
        clearMarkerWait();
        if (scriptWatchdogTimer !== undefined) {
            window.clearTimeout(scriptWatchdogTimer);
            scriptWatchdogTimer = undefined;
        }
    };
    const updateScriptLoad = (loadStatus, message) => {
        if (disposed)
            return;
        if (scriptWatchdogTimer !== undefined) {
            window.clearTimeout(scriptWatchdogTimer);
            scriptWatchdogTimer = undefined;
        }
        emit({
            ...latestAttempt,
            status: loadStatus === "error" ? "error" : latestAttempt.status,
            loadStatus,
            message,
        });
    };
    const tryInject = (allowMarkerWait) => {
        if (disposed || terminal)
            return;
        const candidate = readInjectionCandidate(iframe, parentOrigin);
        if (!candidate.ready) {
            finish(candidate.attempt);
            return;
        }
        const { doc, win, finalUrl } = candidate;
        if (win.__PARHELIA_EDITING_BRIDGE_RUNTIME__) {
            finish({ status: "already-runtime", finalUrl });
            return;
        }
        if (doc.querySelector(BRIDGE_SCRIPT_SELECTOR)) {
            finish({ status: "already-script", finalUrl });
            return;
        }
        if (!doc.querySelector(EDITABLE_MARKER_SELECTOR)) {
            if (allowMarkerWait) {
                if (!observer && startMarkerWait(doc)) {
                    emit({ status: "waiting-for-markers", finalUrl });
                }
                return;
            }
            finish({ status: "no-editable-markers", finalUrl });
            return;
        }
        try {
            scriptWatchdogTimer = injectScript(doc, win, finalUrl, parentOrigin, bridgePath, scriptWatchdogMs, {
                finish,
                updateScriptLoad,
            });
        }
        catch (error) {
            finish({
                status: "error",
                finalUrl,
                scriptUrl: new URL(bridgePath, parentOrigin).toString(),
                message: error instanceof Error ? error.message : String(error),
            });
        }
    };
    const startMarkerWait = (doc) => {
        if (!doc.documentElement && !doc.body)
            return false;
        const MutationObserverCtor = doc.defaultView?.MutationObserver ?? window.MutationObserver;
        if (!MutationObserverCtor)
            return false;
        const target = doc.documentElement ?? doc.body;
        observer = new MutationObserverCtor(() => tryInject(true));
        observer.observe(target, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: [
                "class",
                "data-component-start",
                "data-component-id",
                "data-renderingid",
                "data-placeholder-start",
                "data-placeholder-key",
                "data-placeholderkey",
                "type",
            ],
        });
        markerTimer = window.setTimeout(() => tryInject(false), markerWaitMs);
        return true;
    };
    tryInject(true);
    return {
        get attempt() {
            return latestAttempt;
        },
        dispose,
    };
}
export function formatParentBridgeInjectionDiagnostic(attempt) {
    if (!attempt)
        return undefined;
    const parts = [`status=${attempt.status}`];
    if (attempt.loadStatus)
        parts.push(`load=${attempt.loadStatus}`);
    if (attempt.finalUrl)
        parts.push(`url=${attempt.finalUrl}`);
    if (attempt.scriptUrl)
        parts.push(`script=${attempt.scriptUrl}`);
    if (attempt.message)
        parts.push(`message=${attempt.message}`);
    return parts.join("; ");
}
function readInjectionCandidate(iframe, parentOrigin) {
    const doc = getAccessibleIframeDocument(iframe);
    if (!doc) {
        return { ready: false, attempt: { status: "inaccessible" } };
    }
    const win = (doc.defaultView ?? iframe.contentWindow);
    if (!win) {
        return {
            ready: false,
            attempt: {
                status: "inaccessible",
                finalUrl: doc.URL || iframe.src || undefined,
            },
        };
    }
    const finalUrl = readIframeUrl(doc, win, iframe);
    if (!finalUrl || finalUrl === "about:blank") {
        return { ready: false, attempt: { status: "about-blank", finalUrl } };
    }
    let parsedUrl;
    try {
        parsedUrl = new URL(finalUrl, window.location.href);
    }
    catch (error) {
        return {
            ready: false,
            attempt: {
                status: "error",
                finalUrl,
                message: error instanceof Error ? error.message : String(error),
            },
        };
    }
    if (parsedUrl.origin !== parentOrigin) {
        return {
            ready: false,
            attempt: { status: "origin-mismatch", finalUrl },
        };
    }
    if (parsedUrl.searchParams.get("parhelia") !== "1") {
        return { ready: false, attempt: { status: "not-parhelia", finalUrl } };
    }
    return { ready: true, doc, win, finalUrl: parsedUrl.href };
}
function getAccessibleIframeDocument(iframe) {
    try {
        return iframe?.contentDocument ?? null;
    }
    catch {
        return null;
    }
}
function readIframeUrl(doc, win, iframe) {
    try {
        return win.location.href || doc.URL || iframe.src;
    }
    catch {
        return doc.URL || iframe.src;
    }
}
function injectScript(doc, win, finalUrl, parentOrigin, bridgePath, scriptWatchdogMs, callbacks) {
    const scriptUrl = new URL(bridgePath, parentOrigin).toString();
    const script = doc.createElement("script");
    script.id = "parhelia-editing-bridge";
    script.src = bridgePath;
    script.async = false;
    script.setAttribute("data-parhelia-editor-bridge", "runtime");
    script.setAttribute("data-parhelia-parent-origin", parentOrigin);
    script.setAttribute("data-parhelia-allowed-parent-origins", parentOrigin);
    script.setAttribute("data-parhelia-source", "sitecore-parent-injected");
    // Deliberately no data-parhelia-refresh-strategy: a forced strategy overrides the
    // host app's own window.requestRefresh (see the bridge's refresh handler), and for
    // arbitrary injected hosts the app's router-driven refresh (when provided) is the
    // one that keeps a hydrated React tree intact — a forced DOM morph makes React dev
    // recover with a full reload. Hosts that want the morph declare it on their own tag.
    script.onload = () => {
        callbacks.updateScriptLoad(win.__PARHELIA_EDITING_BRIDGE_RUNTIME__
            ? "runtime-detected"
            : "loaded");
    };
    script.onerror = () => {
        callbacks.updateScriptLoad("error", "Bridge script failed to load.");
    };
    (doc.head || doc.documentElement).appendChild(script);
    const watchdog = window.setTimeout(() => {
        callbacks.updateScriptLoad(win.__PARHELIA_EDITING_BRIDGE_RUNTIME__ ? "runtime-detected" : "timeout");
    }, scriptWatchdogMs);
    callbacks.finish({
        status: "injected",
        finalUrl,
        scriptUrl,
        loadStatus: "pending",
    });
    return watchdog;
}
//# sourceMappingURL=parentBridgeInjection.js.map