/*
 * Copyright 2018-2025 VMware, Inc. All rights reserved. VMware Confidential
 */

import { Observable, Subject } from "rxjs";
import { filter } from "rxjs/operators";
import { HYPER_PLUGIN_STATE, HyperPluginState, REGISTERED_EXTENSION_POINTS } from "./extensibility.model";
import { ExtensionComponentRegistration } from "./extension-component-registration";

export { ExtensionComponentRegistration } from "./extension-component-registration";

/**
 * Comparator function that provides stable sort for registrations based on their ordering field (lower value
 * comes first), followed by their ID field in the case of a tie.
 */
function registrationOrderingComparator(a: ExtensionComponentRegistration, b: ExtensionComponentRegistration): number {
    const comparison = a.ordering - b.ordering;
    if (comparison != 0) {
        return comparison;
    }
    return a.id - b.id;
}

/**
 * Implement a registry for (Angular) components provided by extensions.  Allows registering arbitrary components
 * under arbitrary "scopes."
 *
 * A scope is string keying for splitting registrations into different 'areas' of the UI where components may
 * be injected, for example the dashboard or VM details.  Scopes are intended to be URNs.
 *
 * Parts of the UI that wish to render these components can obtain registered components - likely by scope -
 * containing all necessary references to add the component to an (Angular) view.
 */
export class ExtensionComponentRegistry {
    constructor() {
        this.loadAlreadyRegisteredComponents();
    }

    /**
     * Registrations by scope.  Registrations are stored in a sorted order to avoid constant need for sorting.
     */
    private registrations: Map<string, ExtensionComponentRegistration[]> = new Map<
        string,
        ExtensionComponentRegistration[]
    >();

    /**
     * Subject dispatched to whenever a component is registered.
     */
    private componentRegisteredSubject = new Subject<ExtensionComponentRegistration>();

    /**
     * This observable emits whenever a component is registered.
     */
    readonly allRegistrationsObservable = this.componentRegisteredSubject.asObservable();

    /**
     * This observable whenever a component is registered under the given scope.
     * @param scope given scope
     */
    public scopedRegistrationsObservable(scope: string): Observable<ExtensionComponentRegistration> {
        return this.allRegistrationsObservable.pipe(filter((reg) => reg.scope == scope));
    }

    /**
     * This returns whether a given scope has any components registered or not.
     * @param scope given scope
     * @return if there are any components registered for the scope.
     */
    public hasRegisteredComponents(scope: string): boolean {
        return this.registrations.has(scope);
    }

    /**
     * Obtain all component registrations under a given scope.
     * @param scope given scope
     * @return copy of all registrations, ordered
     */
    public getRegisteredComponents(scope: string): ExtensionComponentRegistration[] {
        if (!this.registrations.has(scope)) {
            return [];
        }
        return [...this.registrations.get(scope) as ExtensionComponentRegistration[]];
    }

    /**
     * Register an extension component.
     * @param registration new component registration.
     */
    public registerExtensionComponent(registration: ExtensionComponentRegistration): void {
        if (!this.registrations.has(registration.scope)) {
            this.registrations.set(registration.scope, []);
        }

        const registrations = this.registrations.get(registration.scope) as ExtensionComponentRegistration[];
        registrations.push(registration);
        registrations.sort(registrationOrderingComparator);
        this.componentRegisteredSubject.next(registration);
        this.hyperPluginStateAddExtensionPointRegistration(registration);
    }

    private loadAlreadyRegisteredComponents(): void {
        let hyperPluginState: HyperPluginState = window[HYPER_PLUGIN_STATE];
        
        if (!hyperPluginState) {
            return;
        }

        hyperPluginState[REGISTERED_EXTENSION_POINTS].forEach((registration) => {
            let scope = this.registrations.get(registration.scope);
            if (!scope) {
                this.registrations.set(registration.scope, []);
                scope = this.registrations.get(registration.scope);
            }

            scope.push(registration);
        });
    }

    private hyperPluginStateAddExtensionPointRegistration(registration: ExtensionComponentRegistration) {
        let hyperPluginState: HyperPluginState = window[HYPER_PLUGIN_STATE];
        hyperPluginState[REGISTERED_EXTENSION_POINTS].push(registration);
    }
}
