"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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ViewCreator = void 0;
const path = __importStar(require("path"));
const types_1 = require("./types");
const cachedFileReader_1 = require("../utils/cachedFileReader");
const ViewLifecycle_1 = require("./ViewLifecycle");
/**
 * ViewCreator
 *
 * Handles creation and injection of views.
 * Extracted from ViewManager for better separation of concerns.
 */
class ViewCreator {
    basePath;
    tilingManager;
    floatingWindows;
    devToolsManager;
    nextOverlayZIndex = types_1.Z_LAYERS.OVERLAY_MIN;
    nextViewId = 1;
    constructor(basePath, tilingManager, floatingWindows, devToolsManager) {
        this.basePath = basePath;
        this.tilingManager = tilingManager;
        this.floatingWindows = floatingWindows;
        this.devToolsManager = devToolsManager;
    }
    /**
     * Check if a frontend entry points to a remote URL
     */
    isRemoteEntry(entry) {
        return /^https?:\/\//i.test(entry);
    }
    /**
     * Determine whether the app frame should be injected
     */
    shouldInjectAppFrame(windowConfig) {
        return windowConfig?.injections?.appFrame !== false;
    }
    /**
     * Get the CSS injection mode from window config
     * Returns "full" by default if not specified
     */
    getCSSInjectionMode(windowConfig) {
        const cssOption = windowConfig?.injections?.css;
        if (cssOption === undefined) {
            return "full";
        }
        return cssOption;
    }
    /**
     * Callback that strips X-Frame-Options and CSP headers from responses.
     */
    static embeddingHeadersFilter(details, callback) {
        const responseHeaders = { ...details.responseHeaders };
        for (const key of Object.keys(responseHeaders)) {
            const lowerKey = key.toLowerCase();
            if (lowerKey === "x-frame-options" ||
                lowerKey === "content-security-policy") {
                delete responseHeaders[key];
            }
        }
        callback({ responseHeaders });
    }
    /**
     * Inject Eden CSS into the view
     * Makes design tokens and utilities available to all apps
     * @param view - The WebContentsView to inject CSS into
     * @param mode - "full" for complete CSS or "tokens" for only CSS custom properties
     */
    async injectEdenCSS(view, mode) {
        if (view.webContents.isDestroyed()) {
            console.error("[ViewCreator] Cannot inject CSS - webContents destroyed");
            return false;
        }
        try {
            const edenCssPath = path.join(this.basePath, "edencss");
            const cssFileName = mode === "full" ? "eden.css" : "eden-tokens.css";
            const cssPath = path.join(edenCssPath, cssFileName);
            const css = await cachedFileReader_1.cachedFileReader.readAsync(cssPath, "utf-8");
            await view.webContents.insertCSS(css);
            console.log(`[ViewCreator] Injected Eden CSS (${mode})`);
            return true;
        }
        catch (err) {
            console.error("[ViewCreator] Failed to inject Eden CSS:", err);
            return false;
        }
    }
    /**
     * Inject app frame script into the view
     * Adds a title bar with close button to each app
     */
    async injectAppFrame(view, appId, viewMode, windowConfig, bounds) {
        if (view.webContents.isDestroyed()) {
            console.error("[ViewCreator] Cannot inject frame - webContents destroyed");
            return false;
        }
        try {
            // Inject CSS first
            const frameCSSPath = path.join(this.basePath, "app-frame/frame.css");
            const frameCSS = await cachedFileReader_1.cachedFileReader.readAsync(frameCSSPath, "utf-8");
            await view.webContents.insertCSS(frameCSS);
            // Inject bundled JavaScript
            const frameScriptPath = path.join(this.basePath, "app-frame/frame-injector.js");
            const frameScript = await cachedFileReader_1.cachedFileReader.readAsync(frameScriptPath, "utf-8");
            // Pre-populate window.edenFrame with config
            const configScript = `
        window.edenFrame = {
          _internal: {
            appId: "${appId}",
            injected: false,
            config: ${JSON.stringify(windowConfig || {})},
            currentMode: "${viewMode}",
            bounds: ${JSON.stringify(bounds || { x: 0, y: 0, width: 0, height: 0 })}
          }
        };
      `;
            // Execute config initialization followed by frame script
            await view.webContents.executeJavaScript(configScript + frameScript);
            console.log(`[ViewCreator] App frame injected (mode: ${viewMode})`);
            return true;
        }
        catch (error) {
            console.error("[ViewCreator] Failed to inject app frame:", error);
            return false;
        }
    }
    /**
     * Calculate view bounds, tile index, and Z-index based on view type and mode
     */
    calculateViewBounds(isOverlay, viewMode, appId, bounds, windowConfig, existingViews) {
        if (isOverlay) {
            return this.calculateOverlayBounds(appId, bounds, windowConfig);
        }
        else if (viewMode === "floating") {
            return this.calculateFloatingAppBounds(appId, bounds, windowConfig);
        }
        else {
            return this.calculateTiledAppBounds(appId, bounds, existingViews);
        }
    }
    /**
     * Calculate bounds and Z-index for overlay views
     */
    calculateOverlayBounds(appId, bounds, windowConfig) {
        const zIndex = this.nextOverlayZIndex++;
        const viewBounds = bounds || this.floatingWindows.calculateInitialBounds(windowConfig);
        console.log(`[ViewCreator] Creating overlay view for ${appId} at Z=${zIndex}`);
        return { viewBounds, zIndex, tileIndex: undefined };
    }
    /**
     * Calculate bounds and Z-index for floating app views
     */
    calculateFloatingAppBounds(appId, bounds, windowConfig) {
        const viewBounds = bounds || this.floatingWindows.calculateInitialBounds(windowConfig);
        const zIndex = this.floatingWindows.getNextZIndex();
        console.log(`[ViewCreator] Creating floating app view for ${appId}`);
        return { viewBounds, zIndex, tileIndex: undefined };
    }
    /**
     * Calculate bounds and tile index for tiled app views
     */
    calculateTiledAppBounds(appId, bounds, existingViews) {
        console.log(`[ViewCreator] Creating tiled app view for ${appId}`);
        if (this.tilingManager.isEnabled()) {
            const tileIndex = this.tilingManager.getNextTileIndex(existingViews);
            const visibleCount = this.tilingManager.getVisibleTiledCount(existingViews) + 1;
            const viewBounds = this.tilingManager.calculateTileBounds(tileIndex, visibleCount);
            return { viewBounds, tileIndex, zIndex: undefined };
        }
        // Fallback if tiling disabled but mode is tiled (shouldn't happen with determineViewMode)
        const viewBounds = bounds || { x: 0, y: 0, width: 800, height: 600 };
        return { viewBounds, tileIndex: undefined, zIndex: undefined };
    }
    /**
     * Create a view for an app (regular or overlay)
     * ViewType is determined by manifest.overlay flag
     */
    createView(appId, manifest, installPath, bounds, existingViews, launchArgs) {
        // Extract manifest properties
        const windowConfig = manifest.window;
        const isOverlay = !!manifest.overlay;
        const viewType = isOverlay ? "overlay" : "app";
        // Note: createView is only called when manifest.frontend exists
        // (ProcessManager validates this before calling)
        const frontendEntry = manifest.frontend.entry;
        // Determine view mode
        const viewMode = isOverlay
            ? "floating"
            : this.tilingManager.determineViewMode(windowConfig?.mode);
        // Calculate bounds and Z-index based on view type and mode
        const { viewBounds, tileIndex, zIndex } = this.calculateViewBounds(isOverlay, viewMode, appId, bounds, windowConfig, existingViews);
        // View identifiers and paths
        const viewId = this.nextViewId++;
        const preloadScript = path.join(this.basePath, "app-runtime/app-preload.js");
        const view = ViewLifecycle_1.ViewLifecycle.createView({
            preloadScript,
            additionalArguments: [
                `--app-id=${appId}`,
                `--launch-args=${JSON.stringify(launchArgs || [])}`,
            ],
        });
        console.log(`[ViewCreator] Creating ${viewType} view for ${appId} with preload: ${preloadScript}`);
        // Register DevTools shortcut on this view
        this.devToolsManager.registerShortcut(view);
        // Set bounds
        view.setBounds(viewBounds);
        // Strip embedding headers if enabled
        if (manifest.frontend?.allowEmbedding) {
            view.webContents.session.webRequest.onHeadersReceived({ urls: ["*://*/*"] }, ViewCreator.embeddingHeadersFilter);
        }
        // Load the frontend HTML or remote URL
        if (this.isRemoteEntry(frontendEntry)) {
            console.log(`[ViewCreator] Loading remote frontend for ${appId}: ${frontendEntry}`);
            view.webContents.loadURL(frontendEntry);
        }
        else {
            const frontendPath = path.join(installPath, frontendEntry);
            view.webContents.loadFile(frontendPath);
        }
        // Setup injections after view is loaded
        view.webContents.on("did-finish-load", () => {
            // Inject the Eden CSS first (based on mode)
            const cssMode = this.getCSSInjectionMode(windowConfig);
            if (cssMode !== "none") {
                this.injectEdenCSS(view, cssMode).catch((err) => {
                    console.error(`[ViewCreator] Failed to inject Eden CSS for ${appId}:`, err);
                });
            }
            // Inject the app frame script (only for app views, not overlays)
            if (viewType === "app" && this.shouldInjectAppFrame(windowConfig)) {
                this.injectAppFrame(view, appId, viewMode, windowConfig, viewBounds).catch((err) => {
                    console.error(`[ViewCreator] Failed to inject app frame for ${appId}:`, err);
                });
            }
        });
        // Return view info
        return {
            id: viewId,
            view,
            appId,
            manifest,
            bounds: viewBounds,
            visible: true,
            mode: viewMode,
            viewType,
            tileIndex,
            zIndex,
            launchArgs,
        };
    }
}
exports.ViewCreator = ViewCreator;
//# sourceMappingURL=ViewCreator.js.map