import { GlobalEvents } from '@bryntum/calendar';

// Module-level registry of all open shadow roots that host Bryntum widgets.
// Populated lazily by collectOpenShadowRoots() the first time it is called, and
// kept up to date via registerShadowRoot / unregisterShadowRoot.
const openShadowRoots = new Set<ShadowRoot>();

// Walks the DOM tree starting from `root` and collects every open shadow root found.
// Results are cached in openShadowRoots after the first traversal so subsequent calls
// are O(1). Recursive so nested shadow roots are also discovered.
function collectOpenShadowRoots(root : Document | ShadowRoot = document) : ShadowRoot[] {
    if (openShadowRoots.size > 0) {
        return Array.from(openShadowRoots);
    }
    const roots : ShadowRoot[] = [];
    Array.from(root.querySelectorAll('*')).forEach(el => {
        if ((el as Element & { shadowRoot? : ShadowRoot }).shadowRoot) {
            roots.push((el as any).shadowRoot);
            roots.push(...collectOpenShadowRoots((el as any).shadowRoot));
        }
    });
    roots.forEach(r => openShadowRoots.add(r));
    return roots;
}

// Updates the href and data-bryntum-theme attribute of every theme <link> inside all
// registered shadow roots to mirror the new head link after a theme change.
function syncThemeToShadowRoots(headLink : HTMLLinkElement) : void {
    const href       = headLink.href;
    const themeAttr  = headLink.getAttribute('data-bryntum-theme') ?? '';

    collectOpenShadowRoots().forEach(shadowRoot => {
        Array.from(shadowRoot.querySelectorAll<HTMLLinkElement>('link[data-bryntum-theme]')).forEach(link => {
            link.setAttribute('data-bryntum-theme', themeAttr);
            link.href = href;
        });
    });
}

// Adds a shadow root to the registry so it is included in theme sync operations.
// Called by BryntumThemeComboComponent.ngOnInit when it detects it is inside a shadow root.
export function registerShadowRoot(shadowRoot : ShadowRoot) : void {
    openShadowRoots.add(shadowRoot);
}

// Removes a shadow root from the registry.
// Called by BryntumThemeComboComponent.ngOnDestroy to clean up on component teardown.
export function unregisterShadowRoot(shadowRoot : ShadowRoot) : void {
    openShadowRoots.delete(shadowRoot);
}

// Injects a clone of each document.head theme <link> into every registered shadow root
// that does not already have one. This is required because CSS from document.head does
// not cascade into shadow DOM, so Bryntum theme styles would be absent without this.
// Safe to call multiple times — the presence check prevents duplicate injection.
export function initThemeInShadowRoots() : void {
    const headThemeLinks = Array.from(document.head.querySelectorAll<HTMLLinkElement>('link[data-bryntum-theme]'));

    if (!headThemeLinks.length) {
        return;
    }

    collectOpenShadowRoots().forEach(shadowRoot => {
        if (shadowRoot.querySelector('link[data-bryntum-theme]')) {
            return;
        }

        headThemeLinks.forEach(headLink => {
            if (!headLink.href) {
                return;
            }
            // Clone the head link to preserve all attributes, then ensure absolute href
            const clone = headLink.cloneNode() as HTMLLinkElement;
            clone.href = headLink.href;
            shadowRoot.appendChild(clone);
        });
    });
}

// Listens for Bryntum's internal theme-change event and keeps every shadow root's
// theme link in sync with the new document.head link. Fires after the old <link>s
// are removed and the new one is fully loaded in document.head.
// @ts-ignore - ion() is public but not present on GlobalEventsSingleton typings
GlobalEvents.ion({
    theme() : void {
        const link = document.head.querySelector<HTMLLinkElement>('link[data-bryntum-theme]');
        if (link?.href) {
            syncThemeToShadowRoots(link);
        }
    }
});
