/**
 * styleSheetRegistry — single source of truth for custom CSS/JS injection.
 *
 * APPROACH
 * --------
 * CSS  — One CSSStyleSheet object is constructed per URL (Constructable
 *        Stylesheets API).  Any shadow root that needs the styles calls
 *        adoptStyleSheet(url, shadowRoot); the same object is pushed onto
 *        adoptedStyleSheets so the browser parses the rules exactly once,
 *        regardless of how many roots adopt it.  The parent knowledge-box
 *        triggers the first fetch; child chat-message calls re-use the
 *        already-resolved Promise and adopt the cached sheet at zero cost.
 *
 * JS   — Scripts are injected into the caller's shadow root (or element)
 *        exactly once per root per URL via a two-level cache: a WeakMap
 *        tracks which URLs have been appended to each root, and a url →
 *        Promise map shares the single load event across all roots so the
 *        network request fires only once.  Concurrent callers await the same
 *        load event; subsequent callers for the same URL in the same root
 *        return immediately.
 *
 * SINGLE CODE PATH
 * ----------------
 * Both ai12z-knowledge-box and ai12z-chat-message import from this module.
 * The parent box makes the first call (triggering the fetch / DOM append).
 * Child calls get the cached result with no side-effects.
 * There is no third injection point; do not add one here.
 */
import { isValidHttpsUrl } from "./utils";
// ---------------------------------------------------------------------------
// CSS – Constructable Stylesheets
// ---------------------------------------------------------------------------
/**
 * True when the browser supports the Constructable Stylesheets API:
 *   - CSSStyleSheet.replace()  (required to build sheets from text)
 *   - Document.adoptedStyleSheets  (required to attach them)
 * Safari 15 and Safari 16.0 fail both checks; they get the <link> fallback.
 */
const supportsConstructable = typeof CSSStyleSheet !== 'undefined' &&
    'replace' in CSSStyleSheet.prototype &&
    'adoptedStyleSheets' in Document.prototype;
const _cssCache = new Map();
/** Per-shadow-root set of URLs already injected via the <link> fallback path. */
const _linkFallbackCache = new WeakMap();
/**
 * Fetch `url` exactly once, build a CSSStyleSheet from its text, and adopt
 * it in `shadowRoot`.  Safe to call from multiple components / shadow roots —
 * repeated calls for the same URL share the cached sheet.
 */
export function adoptStyleSheet(url, shadowRoot) {
    if (!url || !shadowRoot || !isValidHttpsUrl(url))
        return Promise.resolve();
    // ── Legacy fallback for Safari 15 / Safari 16.0 ──────────────────────────
    // Constructable Stylesheets are not supported; append a <link> element to
    // the shadow root instead.  The WeakMap cache ensures one <link> per URL
    // per shadow root, mirroring the once-per-URL guarantee of the main path.
    if (!supportsConstructable) {
        let adopted = _linkFallbackCache.get(shadowRoot);
        if (!adopted) {
            adopted = new Set();
            _linkFallbackCache.set(shadowRoot, adopted);
        }
        if (!adopted.has(url)) {
            adopted.add(url);
            const linkEl = document.createElement('link');
            linkEl.rel = 'stylesheet';
            linkEl.href = url;
            shadowRoot.appendChild(linkEl);
        }
        return Promise.resolve();
    }
    // ── Constructable Stylesheets path (Chrome, Firefox, Safari 16.4+) ────────
    if (!_cssCache.has(url)) {
        const sheetPromise = fetch(url)
            .then(r => {
            if (!r.ok)
                throw new Error(`[ai12z] adoptStyleSheet: HTTP ${r.status} for ${url}`);
            return r.text();
        })
            .then(css => {
            const sheet = new CSSStyleSheet();
            return sheet.replace(css).then(() => sheet);
        })
            .catch(err => {
            _cssCache.delete(url); // allow a retry on next call
            return Promise.reject(err);
        });
        _cssCache.set(url, sheetPromise);
    }
    return _cssCache.get(url)
        .then(sheet => {
        if (!shadowRoot.adoptedStyleSheets.includes(sheet)) {
            shadowRoot.adoptedStyleSheets = [...shadowRoot.adoptedStyleSheets, sheet];
        }
    })
        .catch(err => {
        console.error('[ai12z] adoptStyleSheet failed for', url, err);
    });
}
// ---------------------------------------------------------------------------
// JS – shadow-root-level, exactly-once injection per root
// ---------------------------------------------------------------------------
/**
 * Global load-event cache: url → Promise.  The network request fires only
 * once regardless of how many roots call loadScriptOnce for the same URL.
 */
const _scriptLoadCache = new Map();
/**
 * Per-root injection tracker: mirrors _linkFallbackCache for CSS.
 * Ensures each shadow root / element gets the <script> appended exactly once.
 */
const _scriptRootCache = new WeakMap();
/**
 * Append `url` as a <script> into `el` (shadow root or element) exactly once
 * per root per URL.  The underlying network request is shared across all roots
 * via a url → Promise cache, so the script is only fetched once.
 */
export function loadScriptOnce(url, el) {
    if (!url || !isValidHttpsUrl(url)) {
        if (url)
            console.warn('[ai12z] Blocked script injection — URL must use HTTPS.', url);
        return Promise.resolve();
    }
    // ── Per-root deduplication ──────────────────────────────────────────────
    let rootSet = _scriptRootCache.get(el);
    if (!rootSet) {
        rootSet = new Set();
        _scriptRootCache.set(el, rootSet);
    }
    if (rootSet.has(url)) {
        // Already injected into this root.  Return the shared load Promise.
        // If the load cache no longer has an entry (stale rootSet marker after a
        // previous error), remove it and fall through to re-inject rather than
        // resolving early and letting callers assume the script is ready.
        const cached = _scriptLoadCache.get(url);
        if (cached)
            return cached;
        rootSet.delete(url);
    }
    rootSet.add(url);
    // ── Append script element into this root ────────────────────────────────
    if (!_scriptLoadCache.has(url)) {
        // First root to request this URL — track the load event.
        const p = new Promise((resolve, reject) => {
            const script = document.createElement('script');
            script.src = url;
            script.async = false;
            script.onload = () => resolve();
            script.onerror = () => reject(new Error(`[ai12z] Failed to load script: ${url}`));
            el.appendChild(script);
        });
        _scriptLoadCache.set(url, p.catch(err => {
            _scriptLoadCache.delete(url);
            rootSet.delete(url);
            return Promise.reject(err);
        }));
    }
    else {
        // Already loading/loaded globally; inject into this root without a new
        // load listener (the shared Promise already tracks completion).
        const script = document.createElement('script');
        script.src = url;
        script.async = false;
        el.appendChild(script);
    }
    return _scriptLoadCache.get(url).catch(err => {
        console.warn('[ai12z] loadScriptOnce:', err);
    });
}
