interface IdentityContext {
    viewerId?: string;
    userId?: string;
    accountId?: string;
    language?: string;
}

interface TargetingRule {
    pathContains?: string | string[];
    pathEquals?: string;
    queryParam?: string;
    selector?: string;
    eventName?: string;
    delayMs?: number;
}

interface EmbedOptions {
    width?: number | string;
    height?: number | string;
    host?: string;
    identity?: IdentityContext;
    variables?: Record<string, string | number | boolean | null | undefined>;
}

interface MountOptions extends EmbedOptions {
    mode?: "inline" | "modal";
    target?: string | Element;
    autoShow?: boolean;
    targeting?: TargetingRule;
    closeOnBackdrop?: boolean;
    zIndex?: number;
}

interface BootConfig extends MountOptions {
    demoId: string;
}

interface MountedDemo {
    iframe: HTMLIFrameElement;
    container: HTMLElement;
    show: () => void;
    hide: () => void;
    destroy: () => void;
}

function resolveContainer(target: string | Element | undefined): Element | null {
    if (!target) return null;
    if (typeof target === "string") return document.querySelector(target);
    return target;
}

function normalizeHost(host?: string): string {
    const fallback = "https://showrunner.dev";
    const value = (host ?? fallback).trim();
    return value.endsWith("/") ? value.slice(0, -1) : value;
}

function toCssSize(value: number | string | undefined, fallback: string): string {
    if (value == null) return fallback;
    if (typeof value === "number") return `${value}px`;
    const trimmed = value.trim();
    return trimmed || fallback;
}

function randomId(prefix: string): string {
    const random = Math.random().toString(36).slice(2, 10);
    const stamp = Date.now().toString(36).slice(-4);
    return `${prefix}_${random}${stamp}`;
}

function encodeVars(
    vars: Record<string, string | number | boolean | null | undefined> | undefined
): string | null {
    if (!vars) return null;

    const compact = Object.entries(vars).reduce<Record<string, string>>((acc, [key, raw]) => {
        if (raw == null) return acc;
        const safeKey = key.trim();
        if (!safeKey) return acc;
        acc[safeKey] = String(raw);
        return acc;
    }, {});

    if (Object.keys(compact).length === 0) return null;
    return JSON.stringify(compact);
}

function buildDemoUrl(demoId: string, options: EmbedOptions = {}): string {
    const host = normalizeHost(options.host);
    const url = new URL(`${host}/embed/${encodeURIComponent(demoId)}`);

    const identity = options.identity;
    const viewerId = identity?.viewerId?.trim() || randomId("viewer");
    url.searchParams.set("vid", viewerId);

    if (identity?.userId?.trim()) {
        url.searchParams.set("uid", identity.userId.trim());
    }
    if (identity?.accountId?.trim()) {
        url.searchParams.set("aid", identity.accountId.trim());
    }
    if (identity?.language?.trim()) {
        url.searchParams.set("lang", identity.language.trim());
    }

    const encoded = encodeVars(options.variables);
    if (encoded) {
        url.searchParams.set("vars", encoded);
    }

    return url.toString();
}

function createIframe(demoId: string, options: EmbedOptions = {}): HTMLIFrameElement {
    const iframe = document.createElement("iframe");
    iframe.src = buildDemoUrl(demoId, options);
    iframe.loading = "lazy";
    iframe.allowFullscreen = true;
    iframe.title = `ShowRunner demo ${demoId}`;
    iframe.width = String(options.width ?? 960);
    iframe.height = String(options.height ?? 600);
    iframe.style.width = toCssSize(options.width, "100%");
    iframe.style.height = toCssSize(options.height, "100%");
    iframe.style.maxWidth = "100%";
    iframe.style.border = "0";
    iframe.style.borderRadius = "12px";
    iframe.style.background = "#070b16";
    iframe.style.boxShadow = "0 18px 42px rgba(0,0,0,0.32)";
    return iframe;
}

function matchesPath(rule: TargetingRule): boolean {
    const current = window.location.pathname;

    if (rule.pathEquals && current !== rule.pathEquals) {
        return false;
    }

    if (rule.pathContains) {
        const required = Array.isArray(rule.pathContains) ? rule.pathContains : [rule.pathContains];
        if (!required.some((candidate) => candidate && current.includes(candidate))) {
            return false;
        }
    }

    return true;
}

function matchesQuery(rule: TargetingRule): boolean {
    if (!rule.queryParam) return true;
    return new URLSearchParams(window.location.search).has(rule.queryParam);
}

function waitForSelector(selector: string, onReady: () => void): () => void {
    const immediate = document.querySelector(selector);
    if (immediate) {
        onReady();
        return () => {};
    }

    const observer = new MutationObserver(() => {
        const node = document.querySelector(selector);
        if (!node) return;
        observer.disconnect();
        onReady();
    });

    observer.observe(document.documentElement, {
        childList: true,
        subtree: true,
    });

    const timeout = window.setTimeout(() => {
        observer.disconnect();
    }, 30000);

    return () => {
        window.clearTimeout(timeout);
        observer.disconnect();
    };
}

function embed(
    demoId: string,
    target: string | Element,
    options: EmbedOptions = {}
): HTMLIFrameElement {
    const container = resolveContainer(target);
    if (!container) {
        throw new Error("ShowRunner embed target not found");
    }

    const iframe = createIframe(demoId, options);
    container.innerHTML = "";
    container.appendChild(iframe);
    return iframe;
}

function mount(demoId: string, options: MountOptions = {}): MountedDemo {
    const mode = options.mode ?? "modal";
    const closeOnBackdrop = options.closeOnBackdrop !== false;
    const zIndex = Number.isFinite(options.zIndex) ? Number(options.zIndex) : 2147482000;
    const iframe = createIframe(demoId, options);

    const root = document.createElement("div");
    root.dataset.showrunnerDemo = demoId;

    if (mode === "inline") {
        root.style.display = "none";
        root.style.width = toCssSize(options.width, "100%");
        root.style.height = toCssSize(options.height, "620px");
        root.appendChild(iframe);

        const container = resolveContainer(options.target) ?? document.body;
        container.appendChild(root);

        const show = () => {
            root.style.display = "block";
        };

        const hide = () => {
            root.style.display = "none";
        };

        const destroy = () => {
            root.remove();
        };

        setupTargeting(options, show);

        if (options.autoShow !== false && !options.targeting) {
            show();
        }

        return { iframe, container: root, show, hide, destroy };
    }

    // Modal mode
    root.style.position = "fixed";
    root.style.inset = "0";
    root.style.display = "none";
    root.style.alignItems = "center";
    root.style.justifyContent = "center";
    root.style.background = "rgba(8,11,20,0.68)";
    root.style.backdropFilter = "blur(2px)";
    root.style.padding = "20px";
    root.style.zIndex = String(zIndex);

    const panel = document.createElement("div");
    panel.style.position = "relative";
    panel.style.width = toCssSize(options.width, "min(1100px, 100%)");
    panel.style.height = toCssSize(options.height, "min(78vh, 760px)");
    panel.style.maxWidth = "100%";
    panel.style.borderRadius = "14px";
    panel.style.border = "1px solid rgba(255,255,255,0.18)";
    panel.style.background = "#0a1020";
    panel.style.padding = "10px";
    panel.style.boxShadow = "0 28px 70px rgba(0,0,0,0.45)";
    panel.appendChild(iframe);

    const closeButton = document.createElement("button");
    closeButton.type = "button";
    closeButton.textContent = "Close";
    closeButton.style.position = "absolute";
    closeButton.style.top = "12px";
    closeButton.style.right = "12px";
    closeButton.style.minHeight = "30px";
    closeButton.style.padding = "0 10px";
    closeButton.style.borderRadius = "8px";
    closeButton.style.border = "1px solid rgba(255,255,255,0.24)";
    closeButton.style.background = "rgba(7,11,20,0.86)";
    closeButton.style.color = "#dce6ff";
    closeButton.style.cursor = "pointer";

    panel.appendChild(closeButton);
    root.appendChild(panel);
    document.body.appendChild(root);

    const show = () => {
        root.style.display = "flex";
    };

    const hide = () => {
        root.style.display = "none";
    };

    const destroy = () => {
        root.remove();
    };

    closeButton.addEventListener("click", hide);

    if (closeOnBackdrop) {
        root.addEventListener("click", (event) => {
            if (event.target === root) {
                hide();
            }
        });
    }

    setupTargeting(options, show);

    if (options.autoShow !== false && !options.targeting) {
        show();
    }

    return { iframe, container: root, show, hide, destroy };
}

function setupTargeting(options: MountOptions, show: () => void): void {
    if (options.autoShow === false) {
        return;
    }

    const rule = options.targeting;
    if (!rule) {
        return;
    }

    if (!matchesPath(rule) || !matchesQuery(rule)) {
        return;
    }

    const triggerShow = () => {
        const delay = Number(rule.delayMs ?? 0);
        if (delay > 0) {
            window.setTimeout(show, delay);
            return;
        }
        show();
    };

    if (rule.eventName) {
        const eventName = rule.eventName;
        const handler = () => {
            triggerShow();
            window.removeEventListener(eventName, handler as EventListener);
        };
        window.addEventListener(eventName, handler as EventListener, { once: true });
        return;
    }

    if (rule.selector) {
        const cleanup = waitForSelector(rule.selector, triggerShow);
        window.addEventListener(
            "beforeunload",
            () => {
                cleanup();
            },
            { once: true }
        );
        return;
    }

    triggerShow();
}

function boot(config: BootConfig | BootConfig[]): MountedDemo[] {
    const entries = Array.isArray(config) ? config : [config];
    return entries.map((entry) => {
        const { demoId, ...options } = entry;
        return mount(demoId, options);
    });
}

const api = { embed, mount, boot };

(window as Window & { ShowRunner?: typeof api }).ShowRunner = api;

export { boot, embed, mount };
export type { BootConfig, EmbedOptions, IdentityContext, MountedDemo, MountOptions, TargetingRule };
