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

import { Inject, Injectable } from "@angular/core";
import { of, Observable, throwError as _throw, from } from "rxjs";
import { catchError, concatMap, map } from "rxjs/operators";
import { AngularAppBundle } from "../../extensibility.model";
import { ExtensionLoaderStep, ExternalExtensionLoaderState } from "../extension-loader.model";
import { ExternalExtensionLoaderErrorObservable } from "./external-extension-loader-error";
import { PluginsManagerService } from "../../plugins-manager/plugins-manager.service";
import { BootstrapDetails } from "../../../models/bootstrap-details";
import { BOOTSTRAP_DETAILS } from "../../../tokens";
import { logger } from "../../../../logging/logger";

export const PLUGIN_BUNDLE_URL = "/bundle.js";

/**
 * Enough of a stub for SystemJS for us to use.
 */
export type SystemJsStub = {
    /**
     * Update SystemJS config.
     */
    config(config: any): void;

    /**
     * Import SystemJS module.
     * @param name module name.
     */
    import: (name: string) => Promise<any>;
};

@Injectable()
export class LoadExternalExtensionBundleStep implements ExtensionLoaderStep<ExternalExtensionLoaderState> {
    private systemJs: SystemJsStub = window["System"];

    constructor(
        @Inject(BOOTSTRAP_DETAILS) private BOOTSTRAP_DETAILS: BootstrapDetails,
        private pluginManger: PluginsManagerService,
    ) {}

    /**
     * Load an extensions bundle from {extensionRoot}/bundle.js.
     * State inputs: manifest, spec
     * State outputs: bundle
     */
    public perform(state: ExternalExtensionLoaderState): Observable<ExternalExtensionLoaderState> {
        try {
            const packageName = "extensions/" + state.manifest.urn;
            const bundleLocation = state.spec.resourcePath + PLUGIN_BUNDLE_URL;

            this.systemJs.config({
                packages: {
                    [packageName]: {
                        main: bundleLocation,
                        format: "amd",
                    },
                },
            });

            logger.log(
                `[${state.spec.id} (${state.manifest.name})] Loading bundle and dependencies from: ${bundleLocation}`
            );

            return from(this.pluginManger.load(state)).pipe(
                concatMap(() => {
                    return from(this.systemJs.import(bundleLocation));
                }),
                concatMap((bundle) => {
                    const angularAppBundle: AngularAppBundle<{ name: string }> = bundle;
                    /**
                     * This would be true if the plugin being loaded is Hyper Plugin, in that case
                     * systemJsSetup function is expected to bootstrap all the required npm dependencies
                     * that are shared with every plugin in the runtime.
                     */
                    if (angularAppBundle.systemJsSetup) {
                        angularAppBundle.systemJsSetup();

                        const customWindowSystem = angularAppBundle.customWindow["System"];
                        const customWindowSystemJS = angularAppBundle.customWindow["SystemJS"];

                        if (!customWindowSystem || !customWindowSystemJS) {
                            console.error("[SystemJS] Hyper Plugin's SystemJS is missing.");
                        }

                        window["System"] = customWindowSystem;
                        window["SystemJS"] = customWindowSystemJS;

                        const loadedPlugins: Promise<void> = angularAppBundle.loadPlugins(this.BOOTSTRAP_DETAILS);
                        if (loadedPlugins instanceof Promise) {
                            return from(loadedPlugins).pipe(
                                map(() => {
                                    return { ...state, bundle };
                                })
                            );
                        }
                    }

                    return of({ ...state, bundle });
                }),
                catchError((err, caught) => {
                    console.error(`[${state?.spec?.id} (${state?.manifest?.name})] Could not load bundle`, err);
                    return of({ ...state });
                })
            );
        } catch (e) {
            console.error(`[${state?.spec?.id} (${state?.manifest?.name})] Could not load bundle`, e);
            return new ExternalExtensionLoaderErrorObservable();
        }
    }
}
