/**
 * HStudio Web Server
 *
 * Express + WebSocket server for browser-based IDE.
 * Handles all IPC-like requests from web clients.
 */
import express from 'express';
import { createServer } from 'http';
import { WebSocketServer, WebSocket } from 'ws';
import { join, dirname } from 'path';
import { homedir, networkInterfaces } from 'os';
import { fileURLToPath } from 'url';
import { existsSync, readFileSync } from 'fs';
// Import handlers
import { setupFileSystemHandler } from './handlers/file-system.js';
import { setupTerminalHandler, TerminalManager } from './handlers/terminal.js';
import { setupClaudeCodeHandler } from './handlers/claude-code.js';
import { setupGeminiCodeHandler } from './handlers/gemini-code.js';
import { setupGitHandler } from './handlers/git.js';
import { setupSettingsHandler, settingsManager } from './handlers/settings.js';
import { setupAIHandler } from './handlers/ai.js';
import { createProxyMiddleware } from 'http-proxy-middleware';
// ES module equivalent of __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Read version from web-server's package.json
const pkgPath = join(__dirname, '..', 'package.json');
const pkg = existsSync(pkgPath) ? JSON.parse(readFileSync(pkgPath, 'utf-8')) : { version: '1.0.2' };
// Get local IP address for network access
function getLocalIP() {
    const platform = process.platform;
    const nets = networkInterfaces();
    // Collect all valid LAN IPs
    const lanIPs = [];
    // Mac/Linux priority order
    const priorityOrderMac = ['en0', 'en1', 'en2', 'eth0', 'eth1', 'Wi-Fi', 'Ethernet'];
    // Windows priority order (Chinese and English names)
    const priorityOrderWin = ['Wi-Fi', 'Ethernet', '以太网', '无线网络连接', 'WLAN'];
    const priorityOrder = platform === 'darwin' ? priorityOrderMac : priorityOrderWin;
    for (const name of Object.keys(nets)) {
        for (const net of nets[name] || []) {
            // Skip internal and non-IPv4 addresses
            if (net.family === 'IPv4' && !net.internal) {
                lanIPs.push({ name, ip: net.address });
            }
        }
    }
    if (lanIPs.length === 0) {
        return 'localhost'; // fallback
    }
    // Select by priority order
    for (const priorityName of priorityOrder) {
        const found = lanIPs.find(item => item.name === priorityName || item.name.includes(priorityName));
        if (found) {
            return found.ip;
        }
    }
    // Return first valid IP
    return lanIPs[0].ip;
}
// Server configuration
const DEFAULT_PORT = 8181;
const DEFAULT_CWD = homedir();
// Parse command line arguments
function parseArgs() {
    const args = process.argv.slice(2);
    // Support PORT environment variable
    let port = parseInt(process.env.PORT || '8181', 10) || DEFAULT_PORT;
    let cwd = DEFAULT_CWD;
    let open = true;
    for (let i = 0; i < args.length; i++) {
        if (args[i] === '--port' && args[i + 1]) {
            port = parseInt(args[i + 1], 10);
            i++;
        }
        else if (args[i] === '--cwd' && args[i + 1]) {
            cwd = args[i + 1];
            i++;
        }
        else if (args[i] === '--no-open') {
            open = false;
        }
    }
    return { port, cwd, open };
}
// Session manager for multi-client support
class SessionManager {
    sessions = new Map();
    terminalManager = new TerminalManager();
    addSession(ws) {
        const sessionId = `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
        this.sessions.set(sessionId, ws);
        console.log(`[Session] Added: ${sessionId}, Total: ${this.sessions.size}`);
        return sessionId;
    }
    removeSession(sessionId) {
        this.sessions.delete(sessionId);
        // Kill all terminals for this session
        this.terminalManager.killSessionTerminals(sessionId);
        console.log(`[Session] Removed: ${sessionId}, Total: ${this.sessions.size}`);
    }
    getSession(sessionId) {
        return this.sessions.get(sessionId);
    }
    getTerminalManager() {
        return this.terminalManager;
    }
    broadcast(message, exclude) {
        const data = JSON.stringify(message);
        this.sessions.forEach((ws) => {
            if (ws !== exclude && ws.readyState === WebSocket.OPEN) {
                ws.send(data);
            }
        });
    }
}
// Main server setup
function setupServer() {
    const { port, cwd } = parseArgs();
    // 首次启动时确保配置文件存在
    settingsManager.load();
    // Get local IP for display
    const localIP = getLocalIP();
    const host = localIP || 'localhost';
    const app = express();
    const server = createServer(app);
    const wss = new WebSocketServer({ server });
    const sessionManager = new SessionManager();
    // Health check endpoint (must be before static file serving)
    app.get('/health', (_req, res) => {
        res.json({ status: 'ok', version: pkg.version });
    });
    // API Proxy - forward /api requests to backend server (for auth, track, etc.)
    // Exclude /api/settings which is handled locally
    const apiProxy = createProxyMiddleware({
        target: process.env.API_TARGET || 'http://124.222.100.193:9000',
        changeOrigin: true,
        pathFilter: (path) => {
            // Only proxy paths that are NOT handled locally
            return path.startsWith('/api/') &&
                !path.startsWith('/api/settings') &&
                !path.startsWith('/health');
        },
        on: {
            error: (err, _req, res) => {
                console.error('[Proxy] Error:', err.message);
                const response = res;
                if (!response.headersSent) {
                    response.status(502).json({ error: 'Backend server unavailable' });
                }
            },
        },
    });
    app.use(apiProxy);
    // Settings API endpoints for web clients (local handlers)
    app.get('/api/settings', (_req, res) => {
        try {
            const settings = settingsManager.get();
            res.json({ success: true, settings });
        }
        catch (error) {
            res.status(500).json({ success: false, error: String(error) });
        }
    });
    app.post('/api/settings', express.json(), (req, res) => {
        try {
            const updates = req.body;
            settingsManager.update(updates);
            res.json({ success: true });
        }
        catch (error) {
            res.status(500).json({ success: false, error: String(error) });
        }
    });
    app.put('/api/settings/:path', express.json(), (req, res) => {
        try {
            const pathStr = req.params.path;
            const { value } = req.body;
            settingsManager.updatePath(pathStr, value);
            res.json({ success: true });
        }
        catch (error) {
            res.status(500).json({ success: false, error: String(error) });
        }
    });
    app.delete('/api/settings', (_req, res) => {
        try {
            settingsManager.reset();
            res.json({ success: true });
        }
        catch (error) {
            res.status(500).json({ success: false, error: String(error) });
        }
    });
    app.get('/api/settings/:path', (req, res) => {
        try {
            const pathStr = req.params.path;
            const value = settingsManager.getPath(pathStr);
            res.json({ success: true, value });
        }
        catch (error) {
            res.status(500).json({ success: false, error: String(error) });
        }
    });
    // Static files - serve the built web app (only if web directory exists)
    const staticPath = join(__dirname, '..', 'web');
    const webDirExists = existsSync(staticPath);
    if (webDirExists) {
        app.use(express.static(staticPath));
        // Fallback to index.html for SPA routing
        app.get('*', (_req, res) => {
            res.sendFile(join(staticPath, 'index.html'));
        });
    }
    else {
        console.log('[Server] Web directory not found, skipping static file serving');
        console.log('[Server] Expected path:', staticPath);
        // Serve a simple message for the root path
        app.get('/', (_req, res) => {
            res.json({
                status: 'ok',
                version: pkg.version,
                message: 'Web server is running in API mode. Use frontend dev server for UI.'
            });
        });
    }
    // WebSocket connection handler
    wss.on('connection', (ws) => {
        const sessionId = sessionManager.addSession(ws);
        console.log(`[WebSocket] New connection: ${sessionId}`);
        // Setup handlers with session context
        const handlers = {
            'fs:': setupFileSystemHandler(ws, sessionId),
            'terminal:': setupTerminalHandler(ws, sessionId, sessionManager.getTerminalManager()),
            'claudeCode:': setupClaudeCodeHandler(ws, sessionId),
            'geminiCode:': setupGeminiCodeHandler(ws, sessionId),
            'git:': setupGitHandler(ws, sessionId),
            'settings:': setupSettingsHandler(ws, sessionId),
            'ai:': setupAIHandler(ws, sessionId),
        };
        // Message handler
        ws.on('message', (data) => {
            try {
                const msg = JSON.parse(data.toString());
                handleMessage(ws, msg, handlers, sessionId);
            }
            catch (e) {
                console.error('[WebSocket] Parse error:', e);
                ws.send(JSON.stringify({ type: 'error', error: 'Invalid message format' }));
            }
        });
        // Close handler
        ws.on('close', () => {
            console.log(`[WebSocket] Connection closed: ${sessionId}`);
            sessionManager.removeSession(sessionId);
        });
        // Error handler
        ws.on('error', (error) => {
            console.error(`[WebSocket] Error for ${sessionId}:`, error);
            sessionManager.removeSession(sessionId);
        });
        // Send welcome message with local IP for network access
        ws.send(JSON.stringify({
            type: 'connected',
            sessionId,
            cwd,
            version: pkg.version,
            localIP: getLocalIP(),
        }));
    });
    // Start server
    server.listen(port, () => {
        console.log('='.repeat(50));
        console.log(`HStudio Web Server started`);
        console.log('='.repeat(50));
        console.log(`  URL:  http://${host}:${port}`);
        console.log(`  CWD:  ${cwd}`);
        console.log('='.repeat(50));
        console.log('');
        console.log('Press Ctrl+C to stop the server');
    });
    // Cleanup on exit
    process.on('SIGINT', () => {
        console.log('\n[Server] Shutting down...');
        sessionManager.getTerminalManager().disposeAll();
        server.close();
        process.exit(0);
    });
    process.on('SIGTERM', () => {
        console.log('\n[Server] Shutting down...');
        sessionManager.getTerminalManager().disposeAll();
        server.close();
        process.exit(0);
    });
}
// Message router
function handleMessage(ws, msg, handlers, sessionId) {
    const { type, id, requestId, method, args } = msg;
    // Find the appropriate handler
    const handlerKey = Object.keys(handlers).find(k => method?.startsWith(k));
    if (handlerKey && handlers[handlerKey]) {
        try {
            handlers[handlerKey](msg);
        }
        catch (e) {
            console.error(`[Handler] Error in ${handlerKey}:`, e);
            if (type === 'invoke') {
                ws.send(JSON.stringify({ type: 'error', id, error: String(e) }));
            }
            else if (type === 'stream') {
                ws.send(JSON.stringify({ type: 'streamError', requestId, error: String(e) }));
            }
        }
        return;
    }
    // Unknown method
    if (type === 'invoke') {
        ws.send(JSON.stringify({ type: 'error', id, error: `Unknown method: ${method}` }));
    }
    else if (type === 'stream') {
        ws.send(JSON.stringify({ type: 'streamError', requestId, error: `Unknown method: ${method}` }));
    }
}
// Start server
setupServer();
//# sourceMappingURL=index.js.map