import { r as registerInstance, c as createEvent, g as getElement, h } from './index-FUW5zvoZ.js';

/**
 * AuthHero Widget - Branding Utilities
 *
 * Converts AuthHero branding and theme configurations to CSS custom properties.
 */
// --- Inline WCAG contrast utilities (small footprint, no external deps) ---
function parseHexColor(hex) {
    const match = hex.match(/^#([0-9a-f]{3})$/i) || hex.match(/^#([0-9a-f]{6})$/i);
    if (!match)
        return null;
    let clean = match[1];
    if (clean.length === 3) {
        clean =
            clean[0] + clean[0] + clean[1] + clean[1] + clean[2] + clean[2];
    }
    const num = parseInt(clean, 16);
    return [(num >> 16) & 255, (num >> 8) & 255, num & 255];
}
function srgbLuminance(hex) {
    const rgb = parseHexColor(hex);
    if (!rgb)
        return NaN;
    const [r, g, b] = rgb.map((c) => {
        const s = c / 255;
        return s <= 0.04045 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
    });
    return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
function wcagContrastRatio(hex1, hex2) {
    const l1 = srgbLuminance(hex1);
    const l2 = srgbLuminance(hex2);
    if (isNaN(l1) || isNaN(l2))
        return NaN;
    return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
}
function autoTextColor(backgroundHex, mode = "light") {
    const blackContrast = wcagContrastRatio(backgroundHex, "#000000");
    const whiteContrast = wcagContrastRatio(backgroundHex, "#ffffff");
    const BIAS = 1.35;
    if (mode === "light") {
        return blackContrast > whiteContrast * BIAS ? "#000000" : "#ffffff";
    }
    return blackContrast * BIAS > whiteContrast ? "#000000" : "#ffffff";
}
function darkenHex(hex, percent) {
    const rgb = parseHexColor(hex);
    if (!rgb)
        return hex;
    const [r, g, b] = rgb;
    const toHex = (v) => Math.max(0, Math.round(v * (1 - percent)))
        .toString(16)
        .padStart(2, "0");
    return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
function lightenHex(hex, percent) {
    const rgb = parseHexColor(hex);
    if (!rgb)
        return hex;
    const [r, g, b] = rgb;
    const toHex = (v) => Math.min(255, Math.round(v + (255 - v) * percent))
        .toString(16)
        .padStart(2, "0");
    return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
function ensureContrastColor(fg, bg, minRatio = 4.5) {
    if (wcagContrastRatio(fg, bg) >= minRatio)
        return fg;
    const shouldDarken = srgbLuminance(bg) > 0.5;
    let adjusted = fg;
    for (let i = 1; i <= 10; i++) {
        adjusted = shouldDarken ? darkenHex(fg, i * 0.1) : lightenHex(fg, i * 0.1);
        if (wcagContrastRatio(adjusted, bg) >= minRatio)
            return adjusted;
    }
    return shouldDarken ? "#000000" : "#ffffff";
}
/**
 * Convert a style enum to border radius value
 */
function styleToRadius(style, baseRadius) {
    if (baseRadius !== undefined)
        return `${baseRadius}px`;
    switch (style) {
        case "pill":
            return "9999px";
        case "rounded":
            return "8px";
        case "sharp":
            return "0";
        default:
            return undefined;
    }
}
/**
 * Convert branding configuration to CSS custom properties
 */
function brandingToCssVars(branding) {
    if (!branding)
        return {};
    const vars = {};
    // Primary color
    if (branding.colors?.primary) {
        vars["--ah-color-primary"] = branding.colors.primary;
        // Generate hover variant (slightly darker)
        vars["--ah-color-primary-hover"] = branding.colors.primary;
    }
    // Page background
    if (branding.colors?.page_background) {
        const bg = branding.colors.page_background;
        if (bg.type === "solid" && bg.start) {
            vars["--ah-page-bg"] = bg.start;
        }
        else if (bg.type === "gradient" && bg.start && bg.end) {
            const angle = bg.angle_deg ?? 180;
            vars["--ah-page-bg"] =
                `linear-gradient(${angle}deg, ${bg.start}, ${bg.end})`;
        }
    }
    // Logo URL (stored for reference, used via prop)
    if (branding.logo_url) {
        vars["--ah-logo-url"] = `url(${branding.logo_url})`;
    }
    // Font URL
    if (branding.font?.url) {
        vars["--ah-font-url"] = branding.font.url;
    }
    return vars;
}
/**
 * Convert theme configuration to CSS custom properties
 */
function themeToCssVars(theme) {
    if (!theme)
        return {};
    const vars = {};
    // Border radii
    if (theme.borders) {
        const b = theme.borders;
        // Widget
        if (b.widget_corner_radius !== undefined) {
            vars["--ah-widget-radius"] = `${b.widget_corner_radius}px`;
        }
        if (b.widget_border_weight !== undefined) {
            vars["--ah-widget-border-width"] = `${b.widget_border_weight}px`;
        }
        if (b.show_widget_shadow === false) {
            vars["--ah-widget-shadow"] = "none";
        }
        // Buttons
        const btnRadius = styleToRadius(b.buttons_style, b.button_border_radius);
        if (btnRadius) {
            vars["--ah-btn-radius"] = btnRadius;
        }
        if (b.button_border_weight !== undefined) {
            vars["--ah-btn-border-width"] = `${b.button_border_weight}px`;
        }
        // Inputs
        const inputRadius = styleToRadius(b.inputs_style, b.input_border_radius);
        if (inputRadius) {
            vars["--ah-input-radius"] = inputRadius;
        }
        if (b.input_border_weight !== undefined) {
            vars["--ah-input-border-width"] = `${b.input_border_weight}px`;
        }
    }
    // Colors
    if (theme.colors) {
        const c = theme.colors;
        // Primary button
        if (c.primary_button) {
            vars["--ah-color-primary"] = c.primary_button;
            vars["--ah-color-primary-hover"] = c.primary_button;
            // Honor explicit primary_button_label when set; otherwise auto-pick.
            if (c.primary_button_label) {
                vars["--ah-color-text-on-primary"] = c.primary_button_label;
            }
            else {
                vars["--ah-color-text-on-primary"] = autoTextColor(c.primary_button, "light");
                const darkText = autoTextColor(c.primary_button, "dark");
                if (darkText !== vars["--ah-color-text-on-primary"]) {
                    vars["--ah-color-text-on-primary-dark"] = darkText;
                }
            }
        }
        else if (c.primary_button_label) {
            vars["--ah-color-text-on-primary"] = c.primary_button_label;
        }
        // Secondary button
        if (c.secondary_button_border) {
            vars["--ah-btn-secondary-border"] = c.secondary_button_border;
        }
        if (c.secondary_button_label) {
            vars["--ah-btn-secondary-text"] = c.secondary_button_label;
        }
        // Text colors
        if (c.body_text) {
            vars["--ah-color-text"] = c.body_text;
        }
        if (c.header) {
            vars["--ah-color-text-header"] = c.header;
        }
        if (c.input_labels_placeholders) {
            vars["--ah-color-text-label"] = c.input_labels_placeholders;
            vars["--ah-color-text-muted"] = c.input_labels_placeholders;
        }
        if (c.input_filled_text) {
            vars["--ah-color-input-text"] = c.input_filled_text;
        }
        // Backgrounds
        if (c.widget_background) {
            vars["--ah-color-bg"] = c.widget_background;
        }
        if (c.input_background) {
            vars["--ah-color-input-bg"] = c.input_background;
        }
        // Borders
        if (c.widget_border) {
            vars["--ah-widget-border-color"] = c.widget_border;
        }
        if (c.input_border) {
            vars["--ah-color-border"] = c.input_border;
        }
        // Links — ensure contrast against widget background
        if (c.links_focused_components) {
            const widgetBg = c.widget_background || "#ffffff";
            vars["--ah-color-link"] = ensureContrastColor(c.links_focused_components, widgetBg);
        }
        // Focus/hover
        if (c.base_focus_color) {
            vars["--ah-color-focus-ring"] = c.base_focus_color;
        }
        if (c.base_hover_color) {
            vars["--ah-color-primary-hover"] = c.base_hover_color;
        }
        // Semantic colors
        if (c.error) {
            vars["--ah-color-error"] = c.error;
        }
        if (c.success) {
            vars["--ah-color-success"] = c.success;
        }
        // Icons
        if (c.icons) {
            vars["--ah-color-icon"] = c.icons;
        }
        // Ensure input border contrasts against both widget and input backgrounds
        const widgetBg = c.widget_background || "#ffffff";
        const inputBg = c.input_background || widgetBg;
        const inputBorder = vars["--ah-color-border"] || c.input_border || "#c9cace";
        // Check contrast against the surface behind the border (worst of the two)
        const contrastVsWidget = wcagContrastRatio(inputBorder, widgetBg);
        const contrastVsInput = wcagContrastRatio(inputBorder, inputBg);
        const worstContrast = Math.min(contrastVsWidget, contrastVsInput);
        if (worstContrast < 3.0) {
            const bgToCheck = contrastVsWidget < contrastVsInput ? widgetBg : inputBg;
            vars["--ah-color-border"] = ensureContrastColor(inputBorder, bgToCheck, 3.0);
        }
    }
    // Fonts
    if (theme.fonts) {
        const f = theme.fonts;
        // reference_text_size is the base font size in pixels (default 16px)
        const baseSize = f.reference_text_size || 16;
        // Font sizes can be stored as percentages (Auth0 convention, e.g. 150 = 150% of base)
        // or as absolute pixel values (e.g. 24 = 24px). Values >= 50 are treated as
        // percentages; values < 50 are treated as direct pixel values.
        const fontSizePx = (size) => size >= 50 ? Math.round((size / 100) * baseSize) : size;
        if (f.font_url) {
            vars["--ah-font-url"] = f.font_url;
        }
        if (f.reference_text_size) {
            vars["--ah-font-size-base"] = `${f.reference_text_size}px`;
        }
        // Title, subtitle, etc. sizes are percentages of the base size
        if (f.title?.size) {
            vars["--ah-font-size-title"] = `${fontSizePx(f.title.size)}px`;
        }
        if (f.subtitle?.size) {
            vars["--ah-font-size-subtitle"] = `${fontSizePx(f.subtitle.size)}px`;
        }
        if (f.body_text?.size) {
            vars["--ah-font-size-body"] = `${fontSizePx(f.body_text.size)}px`;
        }
        if (f.input_labels?.size) {
            vars["--ah-font-size-label"] = `${fontSizePx(f.input_labels.size)}px`;
        }
        if (f.buttons_text?.size) {
            vars["--ah-font-size-btn"] = `${fontSizePx(f.buttons_text.size)}px`;
        }
        if (f.links?.size) {
            vars["--ah-font-size-link"] = `${fontSizePx(f.links.size)}px`;
        }
        if (f.links_style === "underlined") {
            vars["--ah-link-decoration"] = "underline";
        }
        // Font weights - bold option sets font-weight to 700
        if (f.title?.bold !== undefined) {
            vars["--ah-font-weight-title"] = f.title.bold ? "700" : "400";
        }
        if (f.subtitle?.bold !== undefined) {
            vars["--ah-font-weight-subtitle"] = f.subtitle.bold ? "700" : "400";
        }
        if (f.body_text?.bold !== undefined) {
            vars["--ah-font-weight-body"] = f.body_text.bold ? "700" : "400";
        }
        if (f.input_labels?.bold !== undefined) {
            vars["--ah-font-weight-label"] = f.input_labels.bold ? "700" : "400";
        }
        if (f.buttons_text?.bold !== undefined) {
            vars["--ah-font-weight-btn"] = f.buttons_text.bold ? "600" : "400";
        }
        if (f.links?.bold !== undefined) {
            vars["--ah-font-weight-link"] = f.links.bold ? "700" : "400";
        }
    }
    // Widget settings
    if (theme.widget) {
        const w = theme.widget;
        if (w.header_text_alignment) {
            vars["--ah-title-align"] = w.header_text_alignment;
        }
        if (w.logo_height) {
            vars["--ah-logo-height"] = `${w.logo_height}px`;
        }
        if (w.logo_position) {
            const positionMap = {
                center: "center",
                left: "flex-start",
                right: "flex-end",
            };
            if (w.logo_position === "none") {
                vars["--ah-logo-display"] = "none";
            }
            else {
                vars["--ah-logo-align"] = positionMap[w.logo_position] ?? "center";
            }
        }
        if (w.social_buttons_layout) {
            // 'top' means social buttons above fields, 'bottom' means below
            // Divider is always order 1 (middle)
            if (w.social_buttons_layout === "top") {
                vars["--ah-social-order"] = "0";
                vars["--ah-divider-order"] = "1";
                vars["--ah-fields-order"] = "2";
            }
            else {
                vars["--ah-social-order"] = "2";
                vars["--ah-divider-order"] = "1";
                vars["--ah-fields-order"] = "0";
            }
        }
    }
    // Page background
    if (theme.page_background) {
        const pb = theme.page_background;
        if (pb.background_color) {
            vars["--ah-page-bg"] = pb.background_color;
        }
        if (pb.background_image_url) {
            vars["--ah-page-bg-image"] = `url(${pb.background_image_url})`;
        }
    }
    return vars;
}
/**
 * Merge branding and theme into a single CSS variables object
 */
function mergeThemeVars(branding, theme) {
    return {
        ...brandingToCssVars(branding),
        ...themeToCssVars(theme),
    };
}
/**
 * Apply CSS variables to an element's style
 */
function applyCssVars(element, vars) {
    Object.entries(vars).forEach(([key, value]) => {
        element.style.setProperty(key, value);
    });
}

/**
 * Sanitize HTML to only allow safe formatting tags
 *
 * Allowed tags:
 * - <br>, <br/> - Line breaks
 * - <em>, <i> - Italic
 * - <strong>, <b> - Bold
 * - <u> - Underline
 * - <span> - Generic inline container (for styling)
 * - <a> - Links (href attribute only, with target="_blank" and rel="noopener")
 *
 * All other tags and attributes are stripped.
 */
// Allowed tags and their allowed attributes
const ALLOWED_TAGS = {
    br: [],
    em: [],
    i: [],
    strong: [],
    b: [],
    u: [],
    span: ["class"],
    a: ["href", "class"],
};
/**
 * Sanitize HTML string to only allow safe formatting tags
 *
 * @param html - The HTML string to sanitize
 * @returns Sanitized HTML string safe for innerHTML
 */
function sanitizeHtml(html) {
    if (!html)
        return "";
    // If no < character present, return as-is (optimization)
    // Must check for any < to prevent bypassing sanitization with malformed tags
    // like "<img src=x onerror=..." which forgiving HTML parsers may still execute
    if (!html.includes("<")) {
        return html;
    }
    // Use a simple regex-based approach that's safe for our limited use case
    // This avoids needing DOMParser which may not be available in all environments
    let result = html;
    // First, escape all HTML
    result = result
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;")
        .replace(/'/g, "&#39;");
    // Then selectively re-enable allowed tags
    for (const [tag, allowedAttrs] of Object.entries(ALLOWED_TAGS)) {
        // Self-closing tags (like <br> and <br/>)
        if (tag === "br") {
            result = result.replace(/&lt;br\s*\/?&gt;/gi, "<br>");
            continue;
        }
        // Opening tags with optional attributes
        const openingPattern = new RegExp(`&lt;${tag}((?:\\s+[a-z-]+(?:=&quot;[^&]*&quot;|=&#39;[^&]*&#39;)?)*)\\s*&gt;`, "gi");
        result = result.replace(openingPattern, (_match, attrsStr) => {
            // Parse and filter attributes
            const filteredAttrs = [];
            if (attrsStr) {
                // Unescape the attributes string for parsing
                const unescapedAttrs = attrsStr
                    .replace(/&quot;/g, '"')
                    .replace(/&#39;/g, "'")
                    .replace(/&amp;/g, "&")
                    .replace(/&lt;/g, "<")
                    .replace(/&gt;/g, ">");
                // Extract attributes
                const attrPattern = /([a-z-]+)=["']([^"']*)["']/gi;
                let attrMatch;
                while ((attrMatch = attrPattern.exec(unescapedAttrs)) !== null) {
                    const [, attrName, attrValue] = attrMatch;
                    if (attrName && allowedAttrs.includes(attrName.toLowerCase())) {
                        // For href, validate it's a safe URL
                        if (attrName.toLowerCase() === "href") {
                            if (isSafeUrl(attrValue || "")) {
                                filteredAttrs.push(`${attrName}="${escapeAttr(attrValue || "")}"`);
                            }
                        }
                        else {
                            filteredAttrs.push(`${attrName}="${escapeAttr(attrValue || "")}"`);
                        }
                    }
                }
            }
            // For <a> tags, always add security attributes
            if (tag === "a") {
                filteredAttrs.push('target="_blank"');
                filteredAttrs.push('rel="noopener noreferrer"');
            }
            const attrsOutput = filteredAttrs.length
                ? " " + filteredAttrs.join(" ")
                : "";
            return `<${tag}${attrsOutput}>`;
        });
        // Closing tags
        const closingPattern = new RegExp(`&lt;/${tag}&gt;`, "gi");
        result = result.replace(closingPattern, `</${tag}>`);
    }
    return result;
}
/**
 * Check if a URL is safe (http, https, or relative)
 */
function isSafeUrl(url) {
    if (!url)
        return false;
    // Allow relative URLs
    if (url.startsWith("/") || url.startsWith("#") || url.startsWith("?")) {
        return true;
    }
    // Allow http and https
    try {
        const parsed = new URL(url, "https://example.com");
        return parsed.protocol === "http:" || parsed.protocol === "https:";
    }
    catch {
        return false;
    }
}
/**
 * Escape attribute value
 */
function escapeAttr(value) {
    return value
        .replace(/&/g, "&amp;")
        .replace(/"/g, "&quot;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;");
}

const authheroWidgetCss = () => `:host{display:block;font-family:var(--ah-font-family, 'ulp-font', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, sans-serif);font-size:var(--ah-font-size-base, 16px);line-height:var(--ah-line-height-base, 1.5);color:var(--ah-color-text, #1e212a);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.widget-container{width:var(--ah-widget-max-width, 400px);margin:0 auto;background-color:var(--ah-color-bg, #ffffff);border-radius:var(--ah-widget-radius, 5px);box-shadow:var(--ah-widget-shadow, 0 4px 22px 0 rgba(0, 0, 0, 0.11));box-sizing:border-box}.widget-header{padding:var(--ah-header-padding, 40px 48px 24px)}.widget-body{padding:var(--ah-body-padding, 0 48px 40px)}.logo-wrapper{display:var(--ah-logo-display, flex);justify-content:var(--ah-logo-align, center);margin-bottom:8px}.logo{display:block;height:var(--ah-logo-height, 52px);max-width:100%;width:auto;object-fit:contain}.title{font-size:var(--ah-font-size-title, 24px);font-weight:var(--ah-font-weight-title, 700);text-align:var(--ah-title-align, center);margin:var(--ah-title-margin, 24px 0 24px);color:var(--ah-color-header, #1e212a);line-height:1.2}.description{font-size:var(--ah-font-size-subtitle, 16px);text-align:var(--ah-title-align, center);margin:var(--ah-description-margin, 0 0 8px);color:var(--ah-color-text, #1e212a);line-height:1.5}.message{padding:12px 16px;border-radius:4px;margin-bottom:16px;font-size:14px;line-height:1.5}.message-error{background-color:var(--ah-color-error-bg, #ffeaea);color:var(--ah-color-error, #d03c38);border-left:3px solid var(--ah-color-error, #d03c38)}.message-success{background-color:var(--ah-color-success-bg, #e6f9f1);color:var(--ah-color-success, #13a769);border-left:3px solid var(--ah-color-success, #13a769)}form{display:flex;flex-direction:column}.form-content{display:flex;flex-direction:column}.social-section{display:flex;flex-direction:column;gap:8px;order:var(--ah-social-order, 2)}.fields-section{display:flex;flex-direction:column;order:var(--ah-fields-order, 0)}.divider{display:flex;align-items:center;text-align:center;margin:16px 0;order:var(--ah-divider-order, 1)}.divider::before,.divider::after{content:'';flex:1;border-bottom:1px solid var(--ah-color-border-muted, #c9cace)}.divider-text{padding:0 10px;font-size:12px;font-weight:400;color:var(--ah-color-text-muted, #65676e);text-transform:uppercase;letter-spacing:0}.links{display:flex;flex-direction:column;align-items:center;gap:8px;margin-top:16px}.link-wrapper{font-size:14px;color:var(--ah-color-text, #1e212a)}.link{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:14px;font-weight:var(--ah-font-weight-link, 400);transition:color 150ms ease}.link:hover{text-decoration:underline}.link:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.widget-footer{margin-top:16px;text-align:center;font-size:12px;color:var(--ah-color-text-muted, #65676e)}.widget-footer a{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:12px;transition:color 150ms ease}.widget-footer a:hover{text-decoration:underline}.widget-footer a:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.loading-spinner{width:32px;height:32px;margin:24px auto;border:3px solid var(--ah-color-border-muted, #e0e1e3);border-top-color:var(--ah-color-primary, #635dff);border-radius:50%;animation:spin 0.8s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.error-message{text-align:center;color:var(--ah-color-error, #d03c38);padding:16px;font-size:14px}@media (max-width: 480px){:host{display:block;width:100%;min-height:100vh;background-color:var(--ah-color-bg, #ffffff)}.widget-container{box-shadow:none;border-radius:0;width:100%;margin:0}.widget-header{padding:24px 16px 16px}.widget-body{padding:0 16px 24px}}`;

const AuthheroWidget = class {
    constructor(hostRef) {
        registerInstance(this, hostRef);
        this.formSubmit = createEvent(this, "formSubmit");
        this.buttonClick = createEvent(this, "buttonClick");
        this.linkClick = createEvent(this, "linkClick");
        this.navigate = createEvent(this, "navigate");
        this.flowComplete = createEvent(this, "flowComplete");
        this.flowError = createEvent(this, "flowError");
        this.screenChange = createEvent(this, "screenChange");
    }
    get el() { return getElement(this); }
    /**
     * The UI screen configuration from the server.
     * Can be passed as a JSON string or object.
     * Follows Auth0 Forms component schema.
     */
    screen;
    /**
     * API endpoint to fetch the initial screen from.
     * If provided, the widget will fetch the screen on load.
     * Can include {screenId} placeholder which will be replaced with the current screen.
     * Example: "/u2/screen/{screenId}" or "https://auth.example.com/u2/screen/{screenId}"
     */
    apiUrl;
    /**
     * Base URL for all API calls. Used when widget is embedded on a different domain.
     * If not provided, relative URLs are used.
     * Example: "https://auth.example.com"
     */
    baseUrl;
    /**
     * Login session state token. Required for social login and maintaining session.
     */
    state;
    /**
     * Current screen ID. Used with apiUrl to fetch screen configuration.
     * When statePersistence is 'url', this is synced with the URL.
     */
    screenId;
    watchScreenId() {
        this.updateDataScreenAttribute();
    }
    /**
     * OAuth/OIDC parameters for social login redirects.
     * Can be passed as a JSON string or object.
     */
    authParams;
    /**
     * Where to persist state and screen ID.
     * - 'url': Updates URL path/query (default for standalone pages)
     * - 'session': Uses sessionStorage (for embedded widgets)
     * - 'memory': No persistence, state only in memory
     * @default 'memory'
     */
    statePersistence = "memory";
    /**
     * Storage key prefix for session/local storage persistence.
     * @default 'authhero_widget'
     */
    storageKey = "authhero_widget";
    /**
     * Branding configuration from AuthHero API.
     * Controls logo, primary color, and page background.
     * Can be passed as a JSON string or object.
     */
    branding;
    /**
     * Theme configuration from AuthHero API.
     * Controls colors, borders, fonts, and layout.
     * Can be passed as a JSON string or object.
     */
    theme;
    /**
     * Whether the widget is in a loading state.
     */
    loading = false;
    /**
     * Whether the widget should automatically submit forms to the action URL.
     * When false (default), the widget only emits events and the consuming
     * application handles all HTTP requests.
     * When true, the widget handles form submission and screen updates.
     * @default false
     */
    autoSubmit = false;
    /**
     * Whether the widget should handle navigation automatically.
     * When true, social login buttons redirect, links navigate, etc.
     * When false, only events are emitted.
     * @default false (same as autoSubmit when not specified)
     */
    autoNavigate;
    /**
     * Internal parsed screen state.
     */
    _screen;
    /**
     * Internal parsed auth params state.
     */
    _authParams;
    /**
     * Internal parsed branding state.
     */
    _branding;
    /**
     * Internal parsed theme state.
     */
    _theme;
    /**
     * Form data collected from inputs.
     */
    formData = {};
    /**
     * AbortController for an in-flight conditional mediation request.
     * Aborted on screen change or component disconnect.
     */
    conditionalMediationAbort;
    /**
     * Emitted when the form is submitted.
     * The consuming application should handle the submission unless autoSubmit is true.
     */
    formSubmit;
    /**
     * Emitted when a non-submit button is clicked (social login, back, etc.).
     * The consuming application decides what to do based on id/type/value.
     */
    buttonClick;
    /**
     * Emitted when a link is clicked.
     * The consuming application decides how to handle navigation.
     */
    linkClick;
    /**
     * Emitted when the widget wants to navigate (e.g., after successful auth).
     * The consuming application decides how to handle navigation.
     */
    navigate;
    /**
     * Emitted when auth flow completes with a redirect URL.
     * The consuming application can redirect or extract the code.
     */
    flowComplete;
    /**
     * Emitted when an error occurs.
     */
    flowError;
    /**
     * Emitted when the screen changes.
     */
    screenChange;
    watchScreen(newValue) {
        // Abort any in-flight conditional mediation when screen changes
        this.conditionalMediationAbort?.abort();
        this.conditionalMediationAbort = undefined;
        if (typeof newValue === "string") {
            try {
                this._screen = JSON.parse(newValue);
            }
            catch {
                console.error("Failed to parse screen JSON");
            }
        }
        else {
            this._screen = newValue;
        }
        if (this._screen) {
            this.formData = {};
            this.initFormDataFromDefaults(this._screen);
            this.screenChange.emit(this._screen);
            this.updateDataScreenAttribute();
        }
    }
    /**
     * Initialize formData from component default_value configs.
     * This pre-fills form fields with values resolved on the server
     * (e.g. from user profile context).
     */
    initFormDataFromDefaults(screen) {
        const defaults = {};
        for (const comp of screen.components || []) {
            if ("config" in comp &&
                comp.config &&
                "default_value" in comp.config &&
                comp.config.default_value) {
                const val = comp.config.default_value;
                if (typeof val === "string" && val !== "") {
                    defaults[comp.id] = val;
                }
            }
        }
        if (Object.keys(defaults).length > 0) {
            this.formData = { ...defaults, ...this.formData };
        }
    }
    /**
     * Updates the data-screen attribute on the host element and its parent container.
     * This allows external CSS to target different screens using attribute selectors.
     * The parent container (e.g. widget-container div from SSR) is also updated
     * so that page-level CSS selectors work during SPA navigation.
     */
    updateDataScreenAttribute() {
        const screenName = this._screen?.name || this.screenId;
        if (screenName) {
            this.el.setAttribute("data-screen", screenName);
        }
        else {
            this.el.removeAttribute("data-screen");
        }
        // Also update the nearest ancestor widget-container's data-screen
        // (identified by the data-authhero-widget-container marker). We use
        // closest() instead of parentElement because WidgetContent in
        // u2-routes.tsx wraps the widget in an intermediate
        // <div data-screen={screenId}>, so parentElement would hit that wrapper
        // rather than the marked .widget-container. This keeps page-level CSS
        // selectors like .widget-container[data-screen="..."] in sync during
        // client-side navigation without mutating arbitrary consumer-owned
        // elements.
        const container = this.el.closest("[data-authhero-widget-container]");
        if (container) {
            if (screenName) {
                container.setAttribute("data-screen", screenName);
            }
            else {
                container.removeAttribute("data-screen");
            }
        }
    }
    watchBranding(newValue) {
        if (typeof newValue === "string") {
            try {
                this._branding = JSON.parse(newValue);
            }
            catch {
                console.error("Failed to parse branding JSON");
            }
        }
        else {
            this._branding = newValue;
        }
        this.applyThemeStyles();
    }
    watchTheme(newValue) {
        if (typeof newValue === "string") {
            try {
                this._theme = JSON.parse(newValue);
            }
            catch {
                console.error("Failed to parse theme JSON");
            }
        }
        else {
            this._theme = newValue;
        }
        this.applyThemeStyles();
    }
    watchAuthParams(newValue) {
        if (typeof newValue === "string") {
            try {
                this._authParams = JSON.parse(newValue);
            }
            catch {
                console.error("Failed to parse authParams JSON");
            }
        }
        else {
            this._authParams = newValue;
        }
    }
    /**
     * Apply branding and theme as CSS custom properties
     */
    applyThemeStyles() {
        const vars = mergeThemeVars(this._branding, this._theme);
        applyCssVars(this.el, vars);
    }
    /**
     * Focus the first input field in the form.
     * Called after screen changes to ensure keyboard navigation works properly.
     */
    focusFirstInput() {
        // Use requestAnimationFrame to ensure DOM has updated
        requestAnimationFrame(() => {
            const shadowRoot = this.el.shadowRoot;
            if (!shadowRoot)
                return;
            // Find all authhero-node components and look for inputs in their shadow DOMs
            const nodes = shadowRoot.querySelectorAll("authhero-node");
            for (const node of Array.from(nodes)) {
                const nodeShadow = node.shadowRoot;
                if (nodeShadow) {
                    const input = nodeShadow.querySelector('input:not([type="hidden"]):not([type="checkbox"]):not([disabled]), textarea:not([disabled])');
                    if (input) {
                        input.focus();
                        return;
                    }
                }
            }
        });
    }
    /**
     * Get the effective autoNavigate value (defaults to autoSubmit if not set)
     */
    get shouldAutoNavigate() {
        return this.autoNavigate ?? this.autoSubmit;
    }
    /**
     * Build the full URL for API calls
     */
    buildUrl(path) {
        if (this.baseUrl) {
            return new URL(path, this.baseUrl).toString();
        }
        return path;
    }
    /**
     * Load state from URL or storage based on statePersistence setting
     */
    loadPersistedState() {
        if (this.statePersistence === "url") {
            const url = new URL(window.location.href);
            const stateParam = url.searchParams.get("state");
            if (stateParam && !this.state) {
                this.state = stateParam;
            }
        }
        else if (this.statePersistence === "session") {
            try {
                const stored = sessionStorage.getItem(`${this.storageKey}_state`);
                if (stored && !this.state) {
                    this.state = stored;
                }
                const storedScreenId = sessionStorage.getItem(`${this.storageKey}_screenId`);
                if (storedScreenId && !this.screenId) {
                    this.screenId = storedScreenId;
                }
            }
            catch {
                // sessionStorage not available
            }
        }
    }
    /**
     * Save state to URL or storage based on statePersistence setting
     */
    persistState() {
        if (this.statePersistence === "url") {
            const url = new URL(window.location.href);
            if (this.state) {
                url.searchParams.set("state", this.state);
            }
            if (this.screenId) {
                url.searchParams.set("screen", this.screenId);
            }
            window.history.replaceState({}, "", url.toString());
        }
        else if (this.statePersistence === "session") {
            try {
                if (this.state) {
                    sessionStorage.setItem(`${this.storageKey}_state`, this.state);
                }
                if (this.screenId) {
                    sessionStorage.setItem(`${this.storageKey}_screenId`, this.screenId);
                }
            }
            catch {
                // sessionStorage not available
            }
        }
    }
    handlePopState = (event) => {
        if (!this.apiUrl)
            return;
        // Restore the widget state token from history if present
        if (event.state?.state) {
            this.state = event.state.state;
        }
        // Derive screen from history state or from the current URL
        const screen = event.state?.screen ?? this.extractScreenIdFromHref(location.href);
        if (screen) {
            this.fetchScreen(screen);
        }
    };
    connectedCallback() {
        window.addEventListener("popstate", this.handlePopState);
    }
    disconnectedCallback() {
        window.removeEventListener("popstate", this.handlePopState);
        this.conditionalMediationAbort?.abort();
        this.conditionalMediationAbort = undefined;
    }
    async componentWillLoad() {
        // Parse initial props - this prevents unnecessary state changes during hydration that cause flashes
        // Also check the element attribute as a fallback for hydration scenarios
        if (!this._screen) {
            const screenValue = this.screen || this.el?.getAttribute("screen");
            if (screenValue) {
                this.watchScreen(screenValue);
            }
        }
        if (!this._branding) {
            this.watchBranding(this.branding);
        }
        if (!this._theme) {
            this.watchTheme(this.theme);
        }
        if (!this._authParams) {
            this.watchAuthParams(this.authParams);
        }
        // Load persisted state if available
        this.loadPersistedState();
        // Fetch screen from API if URL provided and no screen prop
        if (this.apiUrl && !this._screen) {
            await this.fetchScreen(this.screenId);
        }
    }
    /**
     * Fetch screen configuration from the API
     * @param screenIdOverride Optional screen ID to fetch (overrides this.screenId)
     * @param nodeId Optional node ID for flow navigation
     */
    async fetchScreen(screenIdOverride, nodeId) {
        if (!this.apiUrl)
            return false;
        const currentScreenId = screenIdOverride || this.screenId;
        // Build the API URL, replacing {screenId} placeholder if present
        let url = this.apiUrl;
        if (currentScreenId && url.includes("{screenId}")) {
            url = url.replace("{screenId}", encodeURIComponent(currentScreenId));
        }
        // Add state and nodeId as query params
        const urlObj = new URL(url, this.baseUrl || window.location.origin);
        if (this.state) {
            urlObj.searchParams.set("state", this.state);
        }
        if (nodeId) {
            urlObj.searchParams.set("nodeId", nodeId);
        }
        this.loading = true;
        try {
            const response = await fetch(this.buildUrl(urlObj.pathname + urlObj.search), {
                credentials: "include",
                headers: {
                    Accept: "application/json",
                },
            });
            if (response.ok) {
                const data = await response.json();
                // Handle different response formats
                if (data.screen) {
                    this._screen = data.screen;
                    if (data.branding) {
                        this._branding = data.branding;
                        this.applyThemeStyles();
                    }
                    // Update state if returned
                    if (data.state) {
                        this.state = data.state;
                    }
                    // Update screenId if returned in response
                    if (data.screenId) {
                        this.screenId = data.screenId;
                    }
                }
                else {
                    // Response is the screen itself
                    this._screen = data;
                }
                if (this._screen) {
                    // If we fetched with a screenId override, update our stored screenId
                    if (currentScreenId && currentScreenId !== this.screenId) {
                        this.screenId = currentScreenId;
                    }
                    this.initFormDataFromDefaults(this._screen);
                    this.screenChange.emit(this._screen);
                    this.updateDataScreenAttribute();
                    this.persistState();
                    this.focusFirstInput();
                    // Start WebAuthn ceremony if returned with the screen (e.g. conditional mediation)
                    if (data.ceremony) {
                        this.performWebAuthnCeremony(data.ceremony);
                    }
                    return true;
                }
            }
            else {
                const error = await response
                    .json()
                    .catch(() => ({ message: "Failed to load screen" }));
                this.flowError.emit({
                    message: error.message || "Failed to load screen",
                });
            }
        }
        catch (error) {
            console.error("Failed to fetch screen:", error);
            this.flowError.emit({
                message: error instanceof Error ? error.message : "Failed to fetch screen",
            });
        }
        finally {
            this.loading = false;
        }
        return false;
    }
    handleInputChange = (name, value) => {
        this.formData = {
            ...this.formData,
            [name]: value,
        };
    };
    handleSubmit = async (e, overrideData) => {
        e.preventDefault();
        if (!this._screen || this.loading)
            return;
        let submitData = { ...this.formData, ...(overrideData || {}) };
        // Merge hidden input values from DOM (may have been set programmatically
        // by inline scripts, e.g. passkey management buttons)
        const form = this.el.shadowRoot?.querySelector("form");
        if (form) {
            const hiddenInputs = form.querySelectorAll('input[type="hidden"]');
            hiddenInputs.forEach((input) => {
                if (input.name && input.value) {
                    submitData[input.name] = input.value;
                }
            });
        }
        // Always emit the submit event
        this.formSubmit.emit({
            screen: this._screen,
            data: submitData,
        });
        // If autoSubmit is disabled, let the consuming app handle it
        if (!this.autoSubmit) {
            return;
        }
        // For GET actions (or missing method), navigate directly instead of fetching
        if (!this._screen.method || this._screen.method.toUpperCase() === "GET") {
            window.location.href = this.buildUrl(this._screen.action);
            return;
        }
        // Submit to the server
        this.loading = true;
        try {
            const response = await fetch(this.buildUrl(this._screen.action), {
                method: this._screen.method,
                credentials: "include",
                headers: {
                    "Content-Type": "application/json",
                    Accept: "application/json",
                },
                body: JSON.stringify({ data: submitData }),
            });
            const contentType = response.headers.get("content-type");
            if (contentType?.includes("text/html")) {
                // OAuth 2.0 Form Post Response Mode: the server delivers the
                // authorization response as a self-submitting HTML form (instead
                // of a 302 + Location). The fetch response can't be rendered
                // in-place, so swap our document for it — the form's onload
                // handler then POSTs to the redirect_uri.
                const html = await response.text();
                document.open();
                document.write(html);
                document.close();
                return;
            }
            if (contentType?.includes("application/json")) {
                const result = await response.json();
                // Handle different response types
                if (result.redirect) {
                    // Auth complete - emit complete event
                    this.flowComplete.emit({ redirectUrl: result.redirect });
                    // Also emit navigate for backwards compatibility
                    this.navigate.emit({ url: result.redirect });
                    // Auto-navigate if enabled
                    if (this.shouldAutoNavigate) {
                        window.location.href = result.redirect;
                    }
                }
                else if (!response.ok && result.screen) {
                    // Handle validation errors (400 response) — preserve user input
                    this._screen = result.screen;
                    this.initFormDataFromDefaults(result.screen);
                    this.screenChange.emit(result.screen);
                    this.updateDataScreenAttribute();
                    this.focusFirstInput();
                }
                else if (result.screen) {
                    // Next screen (success)
                    this._screen = result.screen;
                    this.formData = {};
                    this.initFormDataFromDefaults(result.screen);
                    this.screenChange.emit(result.screen);
                    this.updateDataScreenAttribute();
                    // Update screenId if returned in response
                    if (result.screenId) {
                        this.screenId = result.screenId;
                    }
                    // Persist state (especially for session storage mode)
                    this.persistState();
                    // Update URL path if navigateUrl is provided (client-side navigation)
                    if (result.navigateUrl && this.shouldAutoNavigate) {
                        window.history.pushState({ screen: result.screenId, state: this.state }, "", result.navigateUrl);
                    }
                    // Apply branding if included
                    if (result.branding) {
                        this._branding = result.branding;
                        this.applyThemeStyles();
                    }
                    // Update state if returned
                    if (result.state) {
                        this.state = result.state;
                        this.persistState();
                    }
                    // Perform WebAuthn ceremony if present (structured data, not script)
                    if (result.ceremony) {
                        this.performWebAuthnCeremony(result.ceremony);
                    }
                    // Focus first input on new screen
                    this.focusFirstInput();
                }
                else if (result.complete) {
                    // Flow complete without redirect
                    this.flowComplete.emit({});
                }
                else if (!response.ok && result.error) {
                    // Server returned an error without a screen (e.g. 500)
                    // Show the error on the current screen
                    if (this._screen) {
                        this._screen = {
                            ...this._screen,
                            messages: [
                                ...(this._screen.messages || []),
                                { text: result.error, type: "error" },
                            ],
                        };
                    }
                    this.flowError.emit({ message: result.error });
                }
            }
        }
        catch (err) {
            console.error("Form submission failed:", err);
            this.flowError.emit({
                message: err instanceof Error ? err.message : "Form submission failed",
            });
        }
        finally {
            this.loading = false;
        }
    };
    /**
     * Override form.submit() so that scripts (e.g. WebAuthn ceremony) that call
     * form.submit() go through the widget's JSON fetch pipeline instead of a
     * native form-urlencoded POST.
     */
    overrideFormSubmit() {
        const shadowRoot = this.el.shadowRoot;
        if (!shadowRoot)
            return;
        const form = shadowRoot.querySelector("form");
        if (!form)
            return;
        form.submit = () => {
            const formData = new FormData(form);
            const data = {};
            formData.forEach((value, key) => {
                if (typeof value === "string") {
                    data[key] = value;
                }
            });
            const syntheticEvent = { preventDefault: () => { } };
            this.handleSubmit(syntheticEvent, data);
        };
    }
    /**
     * Validate and execute a structured WebAuthn ceremony returned by the server.
     * Instead of injecting arbitrary script content, this parses the ceremony JSON,
     * validates the expected fields, and calls the WebAuthn API natively.
     */
    performWebAuthnCeremony(ceremony) {
        if (!this.isValidWebAuthnCeremony(ceremony)) {
            console.error("Invalid WebAuthn ceremony payload", ceremony);
            return;
        }
        if (ceremony.type === "webauthn-authentication-conditional") {
            // Conditional mediation runs in the background, no requestAnimationFrame needed
            this.executeWebAuthnConditionalMediation(ceremony);
            return;
        }
        requestAnimationFrame(() => {
            this.overrideFormSubmit();
            if (ceremony.type === "webauthn-authentication") {
                this.executeWebAuthnAuthentication(ceremony);
            }
            else {
                this.executeWebAuthnRegistration(ceremony);
            }
        });
    }
    /**
     * Schema validation for WebAuthn ceremony payloads.
     * Checks required fields and types before invoking browser APIs.
     */
    isValidWebAuthnCeremony(data) {
        if (typeof data !== "object" || data === null)
            return false;
        const obj = data;
        if (typeof obj.successAction !== "string")
            return false;
        const opts = obj.options;
        if (typeof opts !== "object" || opts === null)
            return false;
        const o = opts;
        if (typeof o.challenge !== "string")
            return false;
        if (obj.type === "webauthn-registration") {
            const rp = o.rp;
            if (typeof rp !== "object" || rp === null)
                return false;
            if (typeof rp.id !== "string" ||
                typeof rp.name !== "string")
                return false;
            const user = o.user;
            if (typeof user !== "object" || user === null)
                return false;
            const u = user;
            if (typeof u.id !== "string" ||
                typeof u.name !== "string" ||
                typeof u.displayName !== "string")
                return false;
            if (!Array.isArray(o.pubKeyCredParams))
                return false;
            return true;
        }
        if (obj.type === "webauthn-authentication" ||
            obj.type === "webauthn-authentication-conditional") {
            return true;
        }
        return false;
    }
    /**
     * Perform the WebAuthn navigator.credentials.create() ceremony and submit
     * the credential result via the form.
     */
    async executeWebAuthnRegistration(ceremony) {
        const opts = ceremony.options;
        const b64uToBuf = (s) => {
            s = s.replace(/-/g, "+").replace(/_/g, "/");
            while (s.length % 4)
                s += "=";
            const b = atob(s);
            const a = new Uint8Array(b.length);
            for (let i = 0; i < b.length; i++)
                a[i] = b.charCodeAt(i);
            return a.buffer;
        };
        const bufToB64u = (b) => {
            const a = new Uint8Array(b);
            let s = "";
            for (let i = 0; i < a.length; i++)
                s += String.fromCharCode(a[i]);
            return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
        };
        const findForm = () => {
            const shadowRoot = this.el?.shadowRoot;
            if (shadowRoot) {
                const f = shadowRoot.querySelector("form");
                if (f)
                    return f;
            }
            return document.querySelector("form");
        };
        try {
            const publicKey = {
                challenge: b64uToBuf(opts.challenge),
                rp: { id: opts.rp.id, name: opts.rp.name },
                user: {
                    id: b64uToBuf(opts.user.id),
                    name: opts.user.name,
                    displayName: opts.user.displayName,
                },
                pubKeyCredParams: opts.pubKeyCredParams.map((p) => ({
                    alg: p.alg,
                    type: p.type,
                })),
                timeout: opts.timeout,
                attestation: (opts.attestation ||
                    "none"),
                authenticatorSelection: opts.authenticatorSelection
                    ? {
                        residentKey: (opts.authenticatorSelection.residentKey ||
                            "preferred"),
                        userVerification: (opts.authenticatorSelection.userVerification ||
                            "preferred"),
                    }
                    : undefined,
            };
            if (opts.excludeCredentials?.length) {
                publicKey.excludeCredentials = opts.excludeCredentials.map((c) => ({
                    id: b64uToBuf(c.id),
                    type: c.type,
                    transports: (c.transports || []),
                }));
            }
            const cred = (await navigator.credentials.create({
                publicKey,
            }));
            const response = cred.response;
            const resp = {
                id: cred.id,
                rawId: bufToB64u(cred.rawId),
                type: cred.type,
                response: {
                    attestationObject: bufToB64u(response.attestationObject),
                    clientDataJSON: bufToB64u(response.clientDataJSON),
                },
                clientExtensionResults: cred.getClientExtensionResults(),
                authenticatorAttachment: cred.authenticatorAttachment || undefined,
            };
            if (typeof response.getTransports === "function") {
                resp.response.transports =
                    response.getTransports();
            }
            const form = findForm();
            if (form) {
                const cf = form.querySelector('[name="credential-field"]') ||
                    form.querySelector("#credential-field");
                const af = form.querySelector('[name="action-field"]') ||
                    form.querySelector("#action-field");
                if (cf)
                    cf.value = JSON.stringify(resp);
                if (af)
                    af.value = ceremony.successAction;
                form.submit();
            }
        }
        catch (e) {
            console.error("WebAuthn registration error:", e);
            const form = findForm();
            if (form) {
                const af = form.querySelector('[name="action-field"]') ||
                    form.querySelector("#action-field");
                if (af)
                    af.value = "error";
                form.submit();
            }
        }
    }
    /**
     * Perform the WebAuthn navigator.credentials.get() ceremony (explicit modal)
     * and submit the credential result via the form.
     */
    async executeWebAuthnAuthentication(ceremony) {
        const opts = ceremony.options;
        const b64uToBuf = (s) => {
            s = s.replace(/-/g, "+").replace(/_/g, "/");
            while (s.length % 4)
                s += "=";
            const b = atob(s);
            const a = new Uint8Array(b.length);
            for (let i = 0; i < b.length; i++)
                a[i] = b.charCodeAt(i);
            return a.buffer;
        };
        const bufToB64u = (b) => {
            const a = new Uint8Array(b);
            let s = "";
            for (let i = 0; i < a.length; i++)
                s += String.fromCharCode(a[i]);
            return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
        };
        const findForm = () => {
            const shadowRoot = this.el?.shadowRoot;
            if (shadowRoot) {
                const f = shadowRoot.querySelector("form");
                if (f)
                    return f;
            }
            return document.querySelector("form");
        };
        try {
            const publicKey = {
                challenge: b64uToBuf(opts.challenge),
                rpId: opts.rpId,
                timeout: opts.timeout,
                userVerification: opts.userVerification || "preferred",
            };
            if (opts.allowCredentials?.length) {
                publicKey.allowCredentials = opts.allowCredentials.map((c) => ({
                    id: b64uToBuf(c.id),
                    type: c.type,
                    transports: (c.transports || []),
                }));
            }
            const cred = (await navigator.credentials.get({
                publicKey,
            }));
            const response = cred.response;
            const resp = {
                id: cred.id,
                rawId: bufToB64u(cred.rawId),
                type: cred.type,
                response: {
                    authenticatorData: bufToB64u(response.authenticatorData),
                    clientDataJSON: bufToB64u(response.clientDataJSON),
                    signature: bufToB64u(response.signature),
                },
                clientExtensionResults: cred.getClientExtensionResults(),
                authenticatorAttachment: cred.authenticatorAttachment || undefined,
            };
            if (response.userHandle) {
                resp.response.userHandle = bufToB64u(response.userHandle);
            }
            const form = findForm();
            if (form) {
                const cf = form.querySelector('[name="credential-field"]') ||
                    form.querySelector("#credential-field");
                const af = form.querySelector('[name="action-field"]') ||
                    form.querySelector("#action-field");
                if (cf)
                    cf.value = JSON.stringify(resp);
                if (af)
                    af.value = ceremony.successAction;
                form.submit();
            }
        }
        catch (e) {
            console.error("WebAuthn authentication error:", e);
            const form = findForm();
            if (form) {
                const af = form.querySelector('[name="action-field"]') ||
                    form.querySelector("#action-field");
                if (af)
                    af.value = "error";
                form.submit();
            }
        }
    }
    /**
     * Execute WebAuthn conditional mediation (autofill-assisted passkeys).
     * Runs in the background — the browser shows passkey suggestions in the
     * username field's autofill dropdown. Silently ignored if unsupported.
     */
    async executeWebAuthnConditionalMediation(ceremony) {
        // Feature detection
        if (!window.PublicKeyCredential ||
            !PublicKeyCredential.isConditionalMediationAvailable) {
            return;
        }
        const available = await PublicKeyCredential.isConditionalMediationAvailable();
        if (!available)
            return;
        // Abort any previous conditional mediation request
        this.conditionalMediationAbort?.abort();
        const abortController = new AbortController();
        this.conditionalMediationAbort = abortController;
        const opts = ceremony.options;
        const b64uToBuf = (s) => {
            s = s.replace(/-/g, "+").replace(/_/g, "/");
            while (s.length % 4)
                s += "=";
            const b = atob(s);
            const a = new Uint8Array(b.length);
            for (let i = 0; i < b.length; i++)
                a[i] = b.charCodeAt(i);
            return a.buffer;
        };
        const bufToB64u = (b) => {
            const a = new Uint8Array(b);
            let s = "";
            for (let i = 0; i < a.length; i++)
                s += String.fromCharCode(a[i]);
            return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
        };
        try {
            const cred = (await navigator.credentials.get({
                mediation: "conditional",
                signal: abortController.signal,
                publicKey: {
                    challenge: b64uToBuf(opts.challenge),
                    rpId: opts.rpId,
                    timeout: opts.timeout,
                    userVerification: opts.userVerification ||
                        "preferred",
                },
            }));
            const response = cred.response;
            const resp = {
                id: cred.id,
                rawId: bufToB64u(cred.rawId),
                type: cred.type,
                response: {
                    authenticatorData: bufToB64u(response.authenticatorData),
                    clientDataJSON: bufToB64u(response.clientDataJSON),
                    signature: bufToB64u(response.signature),
                },
                clientExtensionResults: cred.getClientExtensionResults(),
                authenticatorAttachment: cred.authenticatorAttachment || undefined,
            };
            if (response.userHandle) {
                resp.response.userHandle = bufToB64u(response.userHandle);
            }
            // Submit via the widget's form handling
            this.formData["credential-field"] = JSON.stringify(resp);
            this.formData["action-field"] = ceremony.successAction;
            // Ensure form submit override is set up, then submit
            this.overrideFormSubmit();
            const shadowRoot = this.el?.shadowRoot;
            const form = shadowRoot?.querySelector("form");
            if (form) {
                // Set the hidden input values directly
                const cf = form.querySelector('[name="credential-field"]') ||
                    form.querySelector("#credential-field");
                const af = form.querySelector('[name="action-field"]') ||
                    form.querySelector("#action-field");
                if (cf)
                    cf.value = JSON.stringify(resp);
                if (af)
                    af.value = ceremony.successAction;
                form.submit();
            }
        }
        catch (e) {
            // Silently ignore AbortError and NotAllowedError
            if (e?.name === "AbortError" || e?.name === "NotAllowedError")
                return;
            console.error("Conditional mediation error:", e);
        }
    }
    handleButtonClick = (detail) => {
        // If this is a submit button click, trigger form submission
        if (detail.type === "submit") {
            // For GET screens (or missing method), navigate directly — no form submission needed
            if ((!this._screen?.method ||
                this._screen.method.toUpperCase() === "GET") &&
                this._screen?.action) {
                window.location.href = this.buildUrl(this._screen.action);
                return;
            }
            // Include the clicked button's ID in form data so the server
            // can identify which button was clicked (e.g. for multi-button screens
            // like mfa-login-options where each factor has its own submit button)
            const submitData = { ...this.formData, [detail.id]: "true" };
            this.formData = submitData;
            // Create a synthetic submit event and call handleSubmit
            // Pass submitData directly to avoid @State() timing issues
            const syntheticEvent = { preventDefault: () => { } };
            this.handleSubmit(syntheticEvent, submitData);
            return;
        }
        // Always emit the event
        this.buttonClick.emit(detail);
        // Handle social login if autoNavigate is enabled
        if (detail.type === "SOCIAL" && detail.value && this.shouldAutoNavigate) {
            // Check if this provider has an href (e.g. passwordless connections)
            const providerHref = this.getProviderHref(detail.value);
            if (providerHref) {
                const screenId = this.extractScreenIdFromHref(providerHref);
                if (screenId && this.apiUrl) {
                    this.navigateToScreen(screenId, providerHref);
                }
                else {
                    window.location.href = providerHref;
                }
                return;
            }
            this.handleSocialLogin(detail.value);
            return;
        }
        // Handle resend button
        if (detail.type === "RESEND_BUTTON" && this.shouldAutoNavigate) {
            this.handleResend();
            return;
        }
    };
    /**
     * Handle social login redirect
     */
    handleSocialLogin(connection) {
        const params = this._authParams || {};
        const queryParams = {
            connection,
        };
        // Add state
        if (this.state) {
            queryParams.state = this.state;
        }
        else if (params.state) {
            queryParams.state = params.state;
        }
        // Add client_id
        if (params.client_id) {
            queryParams.client_id = params.client_id;
        }
        // Add optional params
        if (params.redirect_uri)
            queryParams.redirect_uri = params.redirect_uri;
        if (params.scope)
            queryParams.scope = params.scope;
        if (params.audience)
            queryParams.audience = params.audience;
        if (params.nonce)
            queryParams.nonce = params.nonce;
        if (params.response_type)
            queryParams.response_type = params.response_type;
        const socialUrl = this.buildUrl("/authorize?" + new URLSearchParams(queryParams).toString());
        // Emit navigate event and redirect
        this.navigate.emit({ url: socialUrl });
        window.location.href = socialUrl;
    }
    /**
     * Handle resend button click (e.g., resend OTP code)
     */
    async handleResend() {
        if (!this._screen?.action)
            return;
        try {
            const url = this._screen.action +
                (this._screen.action.includes("?") ? "&" : "?") +
                "action=resend";
            await fetch(this.buildUrl(url), {
                method: "POST",
                credentials: "include",
            });
        }
        catch (error) {
            console.error("Resend failed:", error);
        }
    }
    /**
     * Extract screen ID from a u2 link href.
     * Handles patterns like /u2/login/{screenId}?state=... and /u2/{screenId}?state=...
     * Returns the screen ID or null if the href doesn't match a known pattern.
     */
    extractScreenIdFromHref(href) {
        try {
            const url = new URL(href, window.location.origin);
            const path = url.pathname;
            // Match /u2/login/{screenId} (e.g., /u2/login/identifier)
            const loginMatch = path.match(/\/u2\/login\/([^/]+)$/);
            if (loginMatch)
                return loginMatch[1];
            // Match /u2/{screenId} (e.g., /u2/signup, /u2/enter-password)
            const u2Match = path.match(/\/u2\/([^/]+)$/);
            if (u2Match && u2Match[1] !== "login" && u2Match[1] !== "screen") {
                return u2Match[1];
            }
            return null;
        }
        catch {
            return null;
        }
    }
    handleLinkClick = (e, link) => {
        // Emit the event so the consuming app can handle it
        this.linkClick.emit({
            id: link.id,
            href: link.href,
            text: link.text,
        });
        // If autoNavigate is not enabled, prevent default and let the app decide
        if (!this.shouldAutoNavigate) {
            e.preventDefault();
            return;
        }
        // Try client-side navigation: extract screen ID and fetch via API
        const screenId = this.extractScreenIdFromHref(link.href);
        if (screenId && this.apiUrl) {
            e.preventDefault();
            this.navigateToScreen(screenId, link.href);
            return;
        }
        // Fall back to browser navigation for non-u2 links
    };
    /**
     * Navigate to a screen client-side by fetching it from the API.
     * Updates the widget state and browser URL without a full page reload.
     */
    async navigateToScreen(screenId, displayUrl) {
        const success = await this.fetchScreen(screenId);
        if (success) {
            // Push browser history so back/forward works
            window.history.pushState({ screen: screenId, state: this.state }, "", displayUrl);
        }
        else {
            // On failure, fall back to hard navigation
            window.location.href = displayUrl;
        }
    }
    /**
     * Look up the href for a social provider from the current screen's SOCIAL components.
     * Returns the href if found (e.g. for passwordless connections), or null.
     */
    getProviderHref(connectionName) {
        if (!this._screen)
            return null;
        for (const comp of this._screen.components) {
            const c = comp;
            if (c.type === "SOCIAL" && c.config?.provider_details) {
                const match = c.config.provider_details.find((d) => d.name === connectionName);
                if (match?.href)
                    return match.href;
            }
        }
        return null;
    }
    /**
     * Check if a component is a social button.
     */
    isSocialComponent(component) {
        // Check the type property directly - FormComponent has a 'type' field
        // SocialField has type 'SOCIAL'
        return component.type === "SOCIAL";
    }
    /**
     * Check if a component is a divider.
     */
    isDividerComponent(component) {
        return component.type === "DIVIDER";
    }
    render() {
        const screen = this._screen;
        if (this.loading && !screen) {
            return (h("div", { class: "widget-container" }, h("div", { class: "loading-spinner" })));
        }
        if (!screen) {
            return (h("div", { class: "widget-container" }, h("div", { class: "error-message" }, "No screen configuration provided")));
        }
        // Use the local screen variable for all rendering
        const screenErrors = screen.messages?.filter((m) => m.type === "error") || [];
        const screenSuccesses = screen.messages?.filter((m) => m.type === "success") || [];
        const allComponents = [...(screen.components ?? [])];
        const components = allComponents
            .filter((c) => c.visible !== false)
            .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
        const hiddenComponents = allComponents.filter((c) => c.visible === false);
        // Separate social, divider, and field components for layout ordering
        const socialComponents = components.filter((c) => this.isSocialComponent(c));
        const fieldComponents = components.filter((c) => !this.isSocialComponent(c) && !this.isDividerComponent(c));
        const dividerComponent = components.find((c) => this.isDividerComponent(c));
        const hasDivider = !!dividerComponent;
        const dividerText = dividerComponent?.config?.text || "Or";
        // Build dynamic exportparts for social buttons including provider-specific parts
        const getExportParts = (component) => {
            const baseParts = [
                "social-buttons",
                "button",
                "button-secondary",
                "button-social",
                "button-social-content",
                "button-social-text",
                "button-social-subtitle",
                "social-icon",
            ];
            const config = component.config;
            const providers = config?.providers ?? [];
            const providerParts = providers.flatMap((p) => {
                const safe = p.replace(/[^a-zA-Z0-9-]/g, "-");
                return [
                    `button-social-${safe}`,
                    `button-social-content-${safe}`,
                    `button-social-text-${safe}`,
                    `button-social-subtitle-${safe}`,
                    `social-icon-${safe}`,
                ];
            });
            return [...baseParts, ...providerParts].join(", ");
        };
        // Get logo URL from theme.widget (takes precedence) or branding
        const logoUrl = this._theme?.widget?.logo_url || this._branding?.logo_url;
        return (h("div", { class: "widget-container", part: "container", "data-authstack-container": true }, h("header", { class: "widget-header", part: "header" }, logoUrl && (h("div", { class: "logo-wrapper", part: "logo-wrapper" }, h("img", { class: "logo", part: "logo", src: logoUrl, alt: "Logo" }))), screen.title && (h("h1", { class: "title", part: "title", innerHTML: sanitizeHtml(screen.title) })), screen.description && (h("p", { class: "description", part: "description", innerHTML: sanitizeHtml(screen.description) }))), h("div", { class: "widget-body", part: "body" }, screenErrors.map((err) => (h("div", { class: "message message-error", part: "message message-error", key: err.id ?? err.text }, err.text))), screenSuccesses.map((msg) => (h("div", { class: "message message-success", part: "message message-success", key: msg.id ?? msg.text }, msg.text))), h("form", { onSubmit: this.handleSubmit, action: screen.action, method: screen.method || "POST", part: "form" }, hiddenComponents.map((c) => (h("input", { type: "hidden", name: c.id, id: c.id, key: c.id, value: this.formData[c.id] || "" }))), h("div", { class: "form-content" }, socialComponents.length > 0 && (h("div", { class: "social-section", part: "social-section" }, socialComponents.map((component) => (h("authhero-node", { key: component.id, component: component, value: this.formData[component.id], onFieldChange: (e) => this.handleInputChange(e.detail.id, e.detail.value), onButtonClick: (e) => this.handleButtonClick(e.detail), disabled: this.loading, exportparts: getExportParts(component) }))))), socialComponents.length > 0 &&
            fieldComponents.length > 0 &&
            hasDivider && (h("div", { class: "divider", part: "divider" }, h("span", { class: "divider-text" }, dividerText))), h("div", { class: "fields-section", part: "fields-section" }, fieldComponents.map((component) => (h("authhero-node", { key: component.id, component: component, value: this.formData[component.id], onFieldChange: (e) => this.handleInputChange(e.detail.id, e.detail.value), onButtonClick: (e) => this.handleButtonClick(e.detail), disabled: this.loading })))))), screen.links && screen.links.length > 0 && (h("div", { class: "links", part: "links" }, screen.links.map((link) => (h("span", { class: "link-wrapper", part: "link-wrapper", key: link.id ?? link.href }, link.linkText ? (h("span", null, link.text, " ", h("a", { href: link.href, class: "link", part: "link", onClick: (e) => this.handleLinkClick(e, {
                id: link.id,
                href: link.href,
                text: link.linkText || link.text,
            }) }, link.linkText))) : (h("a", { href: link.href, class: "link", part: "link", onClick: (e) => this.handleLinkClick(e, {
                id: link.id,
                href: link.href,
                text: link.text,
            }) }, link.text))))))), screen.footer && (h("div", { class: "widget-footer", part: "footer", innerHTML: sanitizeHtml(screen.footer) })))));
    }
    static get watchers() { return {
        "screenId": [{
                "watchScreenId": 0
            }],
        "screen": [{
                "watchScreen": 0
            }],
        "branding": [{
                "watchBranding": 0
            }],
        "theme": [{
                "watchTheme": 0
            }],
        "authParams": [{
                "watchAuthParams": 0
            }]
    }; }
};
AuthheroWidget.style = authheroWidgetCss();

export { AuthheroWidget as authhero_widget };
