"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UiEmulatorUtils = void 0;
/*
 * ******************************************************************
 * Copyright (c) 2025 Broadcom. All Rights Reserved.
 * Broadcom Confidential. The term "Broadcom" refers to Broadcom Inc.
 * and/or its subsidiaries.
 * ******************************************************************
 */
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const child_process_1 = require("child_process");
const cp = __importStar(require("child_process"));
const chokidar = __importStar(require("chokidar"));
const ws_1 = require("ws");
class UiEmulatorUtils {
    static loadJsonConfig(rootDir, name) {
        const jsonPath = path_1.default.resolve(rootDir, name);
        if (!fs_1.default.existsSync(jsonPath)) {
            return null;
        }
        const fileContent = fs_1.default.readFileSync(jsonPath).toString();
        return JSON.parse(fileContent);
    }
    static storeJsonConfig(rootDir, name, content) {
        const jsonPath = path_1.default.resolve(rootDir, name);
        fs_1.default.writeFileSync(jsonPath, JSON.stringify(content, null, 2));
    }
    static loadFile(rootDir, name) {
        const jsonPath = path_1.default.resolve(rootDir, name);
        if (!fs_1.default.existsSync(jsonPath)) {
            return null;
        }
        const fileContent = fs_1.default.readFileSync(jsonPath).toString();
        return fileContent;
    }
    static storeFile(rootDir, name, content) {
        const jsonPath = path_1.default.resolve(rootDir, name);
        fs_1.default.writeFileSync(jsonPath, content);
    }
    static discoverPluginModule(packageRoot, elementsRootRelativePath, elementFolderName) {
        const srcRoot = path_1.default.join(elementFolderName, 'src');
        return {
            elementsRootRelativePath,
            srcRoot,
            elementFolderName,
        };
    }
    /**
     * Configures and serves the ui-emulator angular application, hosting provided ui plugin elements.
     * @param uiEmulatorConfigRoot - absolute root path to ui plugin folder
     * @param elementsRootRelativePath - relative path to root dir containing all plugin folders
     * @param elementFolderNames - all ui plugin folders
     * @param vcdConfig - VCD Configuration object
     * @param watch - Enable or Disable the watch for changes in UI Plugin src folders
     * @param ignoredFiles - Regex that ignores certain files from the Emulator's watch for changes
     * @param serveArgs - Arguments passed to ng serve
     */
    static serve(uiEmulatorConfigRoot, elementsRootRelativePath, elementFolderNames, vcdConfig, watch = true, ignoredFiles = "", serveArgs) {
        return __awaiter(this, void 0, void 0, function* () {
            ignoredFiles = this.normalizeIgnoreFilesRegExp(ignoredFiles);
            const rootDir = path_1.default.join(uiEmulatorConfigRoot, '.env'); // Extract as optional parameter?
            const PROXY_TARGET_PLACEHOLDER = "https://<insert your VCD endpoint here>";
            const emulatorPath = "node_modules/@vcd/ui-emulator";
            const emulatorRoot = path_1.default.resolve(emulatorPath);
            const angularJson = this.loadJsonConfig(emulatorRoot, 'angular.json');
            const tsconfigJson = this.loadJsonConfig(emulatorRoot, 'tsconfig.app.json');
            const environment = this.loadJsonConfig(rootDir, 'environment.json');
            const proxyConfig = this.loadFile(rootDir, 'proxy.conf.mjs');
            let newProxyConfig = proxyConfig;
            const argIndexPort = serveArgs.findIndex((argument) => {
                return argument === "--port";
            });
            let defaultDevServerPort = "4200";
            if (argIndexPort !== -1) {
                const serverPort = serveArgs[argIndexPort + 1];
                defaultDevServerPort = serverPort;
            }
            const bootstrapDetails = {
                "devMode": false,
                "locale": vcdConfig.locale || "en",
                "scope": vcdConfig.scope,
                "builtInExtensions": [],
                "thirdPartyExtensions": {
                    "hyperplugin2": "/assets/hyperplugin2",
                    "hyperplugin3": "/assets/hyperplugin3",
                    "hyperplugin19": "/assets/hyperplugin19",
                },
                "cell": {
                    "address": `http://localhost:${defaultDevServerPort}`,
                    "organization": vcdConfig.orgName,
                },
                "flexUrl": "",
            };
            let pluginModules = [];
            try {
                console.log('Setting auth token');
                environment.credentials = {
                    jwt: vcdConfig.jwt,
                };
                if (vcdConfig.authtoken) {
                    environment.credentials.authtoken = vcdConfig.authtoken;
                }
                console.log('Updating proxy config');
                while (newProxyConfig.includes(PROXY_TARGET_PLACEHOLDER)) {
                    newProxyConfig = newProxyConfig.replace(PROXY_TARGET_PLACEHOLDER, vcdConfig.cellUrl);
                }
                console.log('Updating Plugins');
                if (vcdConfig.orgId) {
                    bootstrapDetails.cell["orgId"] = vcdConfig.orgId;
                }
                pluginModules = elementFolderNames.map((element) => {
                    bootstrapDetails.thirdPartyExtensions[element] = `/assets/${element}`;
                    return this.discoverPluginModule(uiEmulatorConfigRoot, elementsRootRelativePath, element);
                });
                console.log('Updating angular.json');
                angularJson.projects.emulator.sourceRoot = `${emulatorPath}/src`;
                angularJson.projects.emulator.architect.build.options.index = `${emulatorPath}/src/index.html`;
                angularJson.projects.emulator.architect.build.options.main = `${emulatorPath}/src/main.ts`;
                angularJson.projects.emulator.architect.build.options.tsConfig = "tsconfig.emulator.json";
                angularJson.projects.emulator.architect.build.options.styles = [
                    "node_modules/@cds/core/global.min.css",
                    "node_modules/@cds/core/styles/theme.dark.min.css",
                    "node_modules/@clr/ui/clr-ui.min.css",
                    "node_modules/@vcd/ui-emulator/src/styles.scss"
                ];
                angularJson.projects.emulator.architect.build.options.scripts = [
                    "node_modules/@vcd/ui-emulator/ng-hyper-plugin-framework/polyfills/custom-window.js",
                    "node_modules/@vcd/ui-emulator/ng-hyper-plugin-framework/polyfills/scoped-custom-element-registry.js",
                    "node_modules/systemjs/dist/system.src.js"
                ];
                angularJson.projects.emulator.architect.serve.configurations.development = {
                    "browserTarget": "emulator:build:development",
                    "proxyConfig": ".env/proxy.conf.runtime.mjs"
                };
                angularJson.projects.emulator.architect.build.options.assets = [
                    `${emulatorPath}/src/favicon.ico`,
                    `${emulatorPath}/src/assets`,
                    ...pluginModules.map((pm) => {
                        return {
                            glob: '**/*',
                            input: `./${path_1.default.join(pm.elementsRootRelativePath, pm.srcRoot, "../dist")}`,
                            output: `assets/${pm.elementFolderName}`
                        };
                    })
                ];
                angularJson.projects.emulator.architect.build.options.fileReplacements = [
                    {
                        "replace": "node_modules/@vcd/ui-emulator/.env/environment.runtime.json",
                        "with": ".env/environment.runtime.json"
                    }
                ];
                console.log('Updating tsconfig.emulator.json');
                tsconfigJson.extends = `./${emulatorPath}/tsconfig.json`;
                tsconfigJson.files = [
                    `${emulatorPath}/src/main.ts`
                ];
                tsconfigJson.include = [
                    '.env/*.json',
                    `${emulatorPath}/src/**/*.ts`,
                    `${emulatorPath}/ng-hyper-plugin-framework/**/*.ts`
                ];
                tsconfigJson.exclude = [
                    `${emulatorPath}/src/assets`,
                ];
                const windowBootstrapDetails = `
                window["_bootstrapDetails"] = ${JSON.stringify(bootstrapDetails, null, 4)}
            `.trim();
                fs_1.default.writeFileSync(path_1.default.resolve(emulatorRoot, "src", "bootstrapDetails.js"), windowBootstrapDetails);
            }
            catch (e) {
                console.log('Error configuring environment.', e);
            }
            finally {
                this.storeJsonConfig(rootDir, 'environment.runtime.json', environment);
                this.storeFile(rootDir, 'proxy.conf.runtime.mjs', newProxyConfig);
                this.storeJsonConfig(uiEmulatorConfigRoot, 'angular.json', angularJson);
                this.storeJsonConfig(uiEmulatorConfigRoot, 'tsconfig.emulator.json', tsconfigJson);
            }
            if (watch) {
                const wss = createWebSocketServer(angularJson.projects.emulator.architect.build.options.index);
                const uiPluginBuilds = pluginModules.map((pluginModule) => {
                    const pluginSrcRoot = path_1.default.resolve(path_1.default.join(pluginModule.elementsRootRelativePath, pluginModule.srcRoot));
                    const pluginRootDir = path_1.default.resolve(path_1.default.join(pluginSrcRoot, "./../"));
                    return this.buildUIPlugin(pluginRootDir);
                });
                Promise.all(uiPluginBuilds)
                    .then(() => {
                    this.startDevServer(uiEmulatorConfigRoot, serveArgs);
                    pluginModules.forEach((pluginModule) => {
                        const pluginSrcRoot = path_1.default.resolve(path_1.default.join(pluginModule.elementsRootRelativePath, pluginModule.srcRoot));
                        console.log(`Watching ${pluginSrcRoot} for changes`);
                        this.watchUIPluginsSources(wss, pluginSrcRoot, ignoredFiles);
                    });
                });
            }
            else {
                this.startDevServer(uiEmulatorConfigRoot, serveArgs);
            }
            return Promise.resolve();
        });
    }
    static watchUIPluginsSources(wss, pluginSrcRoot, ignoredFiles, skipFirstEvent = true) {
        let ignored = null;
        if (ignoredFiles) {
            ignored = new RegExp(ignoredFiles);
        }
        const watcher = chokidar.watch(pluginSrcRoot, { ignored, persistent: true });
        const pluginRootDir = path_1.default.resolve(path_1.default.join(pluginSrcRoot, "./../"));
        let child_process = null;
        watcher.on("all", () => {
            if (child_process || skipFirstEvent) {
                skipFirstEvent = false;
                return;
            }
            child_process = cp.spawn("npm", ["run", "build"], {
                cwd: pluginRootDir,
                stdio: "inherit",
                shell: this.isPlatformWindows(),
            });
            child_process.on('close', (code) => {
                console.log(`Build finished with code: ${code}`);
                child_process = null;
                wss.clients.forEach((client) => {
                    if (client.readyState === client.OPEN) {
                        client.send(JSON.stringify({ type: "reload" }));
                    }
                });
            });
        });
    }
    static startDevServer(uiEmulatorConfigRoot, serveArgs) {
        (0, child_process_1.spawn)('ng', ['serve', ...(serveArgs ? serveArgs : [])], {
            cwd: path_1.default.resolve(uiEmulatorConfigRoot),
            stdio: 'inherit',
            shell: this.isPlatformWindows(),
        });
    }
    static buildUIPlugin(pluginRootDir) {
        return new Promise((resolve) => {
            const child_process = cp.spawn("npm", ["run", "build"], {
                cwd: pluginRootDir,
                stdio: "inherit",
                shell: this.isPlatformWindows(),
            });
            child_process.on('close', (code) => {
                console.log(`Build finished with code: ${code}`);
                resolve();
            });
        });
    }
    static normalizeIgnoreFilesRegExp(regex) {
        if (!regex) {
            return "";
        }
        if (regex.startsWith("/")) {
            regex = regex.substring(1, regex.length);
        }
        if (regex.endsWith("/")) {
            regex = regex.substring(0, regex.length - 1);
        }
        return regex;
    }
    static isPlatformWindows() {
        return process.platform === "win32";
    }
}
exports.UiEmulatorUtils = UiEmulatorUtils;
function createWebSocketServer(indexHtml) {
    const wss = new ws_1.WebSocketServer({ port: 3000 });
    wss.on("connection", (ws) => {
        ws.send(JSON.stringify({ type: "connected" }));
    });
    updateIndexHtml(indexHtml);
    return wss;
}
function updateIndexHtml(filePath) {
    const scriptToInject = `
<script>
    const socket = new WebSocket("ws://localhost:3000");

    socket.onmessage = (event) => {
        const message = JSON.parse(event.data);
        if (message.type === "reload") {
            console.log("Reloading page...");
            location.reload();
        }
    };
</script>`;
    // Read the existing index.html file
    fs_1.default.readFile(filePath, "utf-8", (err, data) => {
        if (err) {
            console.error("Error reading the file:", err);
            return;
        }
        // Check if the script is already present to prevent duplicates
        if (data.includes("new WebSocket")) {
            console.log("Wss already exists in the file.");
            return;
        }
        // Inject the script before the closing </body> tag
        const updatedHtml = data.replace("</body>", `${scriptToInject}\n</body>`);
        // Write the modified content back to index.html
        fs_1.default.writeFile(filePath, updatedHtml, "utf8", (err) => {
            if (err) {
                console.error("Error writing to the file:", err);
            }
            else {
                console.log("Wss successfully injected into index.html.");
            }
        });
    });
}
