"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.serveStatic = serveStatic;
exports.sendHtml = sendHtml;
/**
 * Module: server/static-server
 *
 * Static file serving functionality for WebUI dist assets.
 * Development and production both serve the same static build output.
 */
const fs_1 = require("fs");
const path_1 = require("path");
const log_1 = require("../log");
const mime_types_1 = require("./mime-types");
const log = (0, log_1.createLogger)('static-server');
function isFingerprintedAssetPath(pathname) {
    return /^\/assets\/.+-[A-Z0-9]{8,}\.[A-Za-z0-9]+$/.test(pathname);
}
function getStaticCacheControl(pathname, ext) {
    if (ext === '.html') {
        return 'no-store';
    }
    if (isFingerprintedAssetPath(pathname)) {
        return 'public, max-age=31536000, immutable';
    }
    return 'no-cache';
}
/**
 * Serve static files from the webapp build output.
 */
async function serveStatic(pathname, res, options) {
    try {
        let filePath;
        const staticDir = (0, path_1.resolve)(options.domindsInstallRoot, options.staticDir);
        if (pathname === '/' || pathname === '/index.html') {
            filePath = (0, path_1.join)(staticDir, 'index.html');
        }
        else {
            // Remove leading slash and join with static directory
            filePath = (0, path_1.join)(staticDir, pathname.slice(1));
        }
        // Security check: ensure file is within allowed directories
        const resolvedPath = (0, path_1.resolve)(filePath);
        const allowedPath = staticDir;
        if (!resolvedPath.startsWith(allowedPath)) {
            log.warn(`Access denied to file outside allowed paths: ${resolvedPath}`);
            return false;
        }
        if (!(0, fs_1.existsSync)(filePath)) {
            // SPA fallback: serve index.html for client-side routes (e.g. /setup).
            // Only apply when the request path does not look like a static asset.
            const lastSeg = pathname
                .split('/')
                .filter((s) => s !== '')
                .slice(-1)[0] ?? '';
            const looksLikeAsset = lastSeg.includes('.');
            if (!looksLikeAsset) {
                const indexPath = (0, path_1.join)(staticDir, 'index.html');
                if ((0, fs_1.existsSync)(indexPath)) {
                    return await sendFile(indexPath, res, '.html', '/index.html');
                }
            }
            return false;
        }
        const ext = (0, path_1.extname)(filePath);
        return await sendFile(filePath, res, ext, pathname);
    }
    catch (error) {
        log.error('Error serving static file:', error);
        return false;
    }
}
/**
 * Send a file with appropriate headers
 */
async function sendFile(filePath, res, ext, pathname) {
    try {
        const mimeType = (0, mime_types_1.getMimeType)(ext);
        const stats = await fs_1.promises.stat(filePath);
        res.writeHead(200, {
            'Content-Type': mimeType,
            'Content-Length': stats.size,
            'Cache-Control': getStaticCacheControl(pathname, ext),
        });
        const stream = (0, fs_1.createReadStream)(filePath);
        stream.pipe(res);
        return new Promise((resolve, reject) => {
            stream.on('end', () => resolve(true));
            stream.on('error', reject);
        });
    }
    catch (error) {
        log.error(`Error sending file ${filePath}:`, error);
        return false;
    }
}
/**
 * Send an HTML file.
 */
async function sendHtml(filePath, res, mode) {
    try {
        const content = await fs_1.promises.readFile(filePath, 'utf8');
        res.writeHead(200, {
            'Content-Type': 'text/html; charset=utf-8',
            'Cache-Control': 'no-store',
        });
        res.end(content);
    }
    catch (error) {
        log.error(`Error sending HTML file ${filePath}:`, error);
        res.writeHead(500, { 'Content-Type': 'text/plain' });
        res.end('Internal Server Error');
    }
}
