import { Deferred } from "@frak-labs/frame-connector";
import { BACKUP_KEY } from "../../constants";
import type { FrakLifecycleEvent } from "../../types";
import {
    isFrakDeepLink,
    triggerDeepLinkWithFallback,
} from "../../utils/browser/deepLinkWithFallback";
import { isInAppBrowser, isIOS } from "../../utils/browser/inAppBrowser";
import { changeIframeVisibility } from "../../utils/iframe/iframeHelper";

// iOS in-app browsers (Instagram, Facebook) silently swallow server-side 302
// redirects to custom URL schemes (x-safari-https://) in WKWebView; direct
// window.location.href assignment works.
const isIOSInAppBrowser = isIOS && isInAppBrowser;

/** @ignore */
export type IframeLifecycleManager = {
    isConnected: Promise<boolean>;
    handleEvent: (messageEvent: FrakLifecycleEvent) => void;
};

/**
 * Handle backup storage
 */
function handleBackup(backup: string | undefined): void {
    if (backup) {
        localStorage.setItem(BACKUP_KEY, backup);
    } else {
        localStorage.removeItem(BACKUP_KEY);
    }
}

/**
 * Compute final redirect URL with parameter substitution
 */
function computeRedirectUrl(
    baseRedirectUrl: string,
    mergeToken?: string
): string {
    try {
        const redirectUrl = new URL(baseRedirectUrl);
        if (!redirectUrl.searchParams.has("u")) {
            return baseRedirectUrl;
        }

        // Append merge token to the page URL so it survives
        // the backend /common/social redirect chain
        const finalPageUrl = appendMergeToken(window.location.href, mergeToken);

        redirectUrl.searchParams.delete("u");
        redirectUrl.searchParams.append("u", finalPageUrl);

        return redirectUrl.toString();
    } catch {
        return baseRedirectUrl;
    }
}

/**
 * Redirect current page to Safari via x-safari-https:// scheme.
 * Used on iOS in-app browsers where backend 302 → custom scheme fails.
 */
function redirectToSafari(mergeToken?: string) {
    const url = new URL(window.location.href);
    if (mergeToken) {
        url.searchParams.set("fmt", mergeToken);
    }
    const scheme =
        url.protocol === "http:" ? "x-safari-http" : "x-safari-https";
    window.location.href = `${scheme}://${url.host}${url.pathname}${url.search}${url.hash}`;
}

/**
 * Check if this is a social/in-app-browser escape redirect (contains /common/social)
 */
function isSocialRedirect(url: string): boolean {
    return url.includes("/common/social");
}

/**
 * Append merge token to a URL as the `fmt` query parameter.
 */
function appendMergeToken(urlString: string, mergeToken?: string): string {
    if (!mergeToken) return urlString;
    try {
        const url = new URL(urlString);
        url.searchParams.set("fmt", mergeToken);
        return url.toString();
    } catch {
        const sep = urlString.includes("?") ? "&" : "?";
        return `${urlString}${sep}fmt=${encodeURIComponent(mergeToken)}`;
    }
}

/**
 * Handle redirect with deep link fallback
 */
function handleRedirect(
    iframe: HTMLIFrameElement,
    baseRedirectUrl: string,
    targetOrigin: string,
    mergeToken?: string,
    openInNewTab?: boolean
): void {
    // If requested, open in a new tab instead of navigating the current page.
    // This preserves the merchant page while triggering universal links.
    // Requires the iframe postMessage to include user activation delegation.
    if (openInNewTab) {
        const finalUrl = computeRedirectUrl(baseRedirectUrl, mergeToken);
        window.open(finalUrl, "_blank");
        return;
    }

    if (isFrakDeepLink(baseRedirectUrl)) {
        const finalUrl = computeRedirectUrl(baseRedirectUrl, mergeToken);
        triggerDeepLinkWithFallback(finalUrl, {
            onFallback: () => {
                iframe.contentWindow?.postMessage(
                    {
                        clientLifecycle: "deep-link-failed",
                        data: { originalUrl: finalUrl },
                    },
                    targetOrigin
                );
            },
        });
    } else if (isIOSInAppBrowser && isSocialRedirect(baseRedirectUrl)) {
        // iOS WKWebView silently swallows 302 redirects to custom URL
        // schemes — bypass the server redirect entirely
        redirectToSafari(mergeToken);
    } else {
        const finalUrl = computeRedirectUrl(baseRedirectUrl, mergeToken);
        window.location.href = finalUrl;
    }
}

/**
 * Create a new iframe lifecycle handler
 * @param args
 * @param args.iframe - The iframe element used for wallet communication
 * @param args.targetOrigin - The wallet URL origin for postMessage security
 * @ignore
 */
export function createIFrameLifecycleManager({
    iframe,
    targetOrigin,
}: {
    iframe: HTMLIFrameElement;
    targetOrigin: string;
}): IframeLifecycleManager {
    // Create the isConnected listener
    const isConnectedDeferred = new Deferred<boolean>();

    // Build the handler itself
    const handler = (messageEvent: FrakLifecycleEvent) => {
        if (!("iframeLifecycle" in messageEvent)) return;

        const { iframeLifecycle: event, data } = messageEvent;

        switch (event) {
            // Resolve the isConnected promise
            case "connected":
                isConnectedDeferred.resolve(true);
                break;
            // Perform a frak backup
            case "do-backup":
                handleBackup(data.backup);
                break;
            // Remove frak backup
            case "remove-backup":
                localStorage.removeItem(BACKUP_KEY);
                break;
            // Change iframe visibility
            case "show":
            case "hide":
                changeIframeVisibility({ iframe, isVisible: event === "show" });
                break;
            // Redirect handling
            case "redirect":
                handleRedirect(
                    iframe,
                    data.baseRedirectUrl,
                    targetOrigin,
                    data.mergeToken,
                    data.openInNewTab
                );
                break;
        }
    };

    return {
        handleEvent: handler,
        isConnected: isConnectedDeferred.promise,
    };
}
