/**
 * File System Handler
 *
 * Handles all file system operations for web clients.
 */
import { WebSocket } from 'ws';
import { promises as fs, watch as fsWatch } from 'fs';
import { join, dirname, basename, extname } from 'path';
import { homedir } from 'os';
import https from 'https';
import { exec } from 'child_process';
import { promisify } from 'util';
import { fileURLToPath } from 'url';
// ES module equivalent of __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const execAsync = promisify(exec);
// File watcher manager
class FileWatcherManager {
    watchers = new Map();
    fileInfo = new Map();
    startWatching(filePath, ws) {
        this.stopWatching(filePath);
        try {
            let initialContent = '';
            try {
                initialContent = fsSync.readFileSync(filePath, 'utf-8');
            }
            catch (e) {
                // File may not exist
            }
            const watcher = fsWatch(filePath, { persistent: true }, (eventType) => {
                if (eventType === 'change' || eventType === 'rename') {
                    this.handleFileChange(filePath, ws);
                }
            });
            this.watchers.set(filePath, watcher);
            this.fileInfo.set(filePath, { modified: Date.now(), ws, lastContent: initialContent });
        }
        catch (error) {
            console.error(`[FileWatcher] Failed to watch: ${filePath}`, error);
        }
    }
    stopWatching(filePath) {
        const watcher = this.watchers.get(filePath);
        if (watcher) {
            watcher.close();
            this.watchers.delete(filePath);
        }
        this.fileInfo.delete(filePath);
    }
    updateModifiedTime(filePath) {
        const info = this.fileInfo.get(filePath);
        if (info) {
            info.modified = Date.now();
            try {
                info.lastContent = fsSync.readFileSync(filePath, 'utf-8');
            }
            catch (e) {
                // Ignore
            }
        }
    }
    async handleFileChange(filePath, ws) {
        try {
            const stat = await fs.stat(filePath);
            const info = this.fileInfo.get(filePath);
            if (!info)
                return;
            // Skip if change happened within 500ms (likely our own save)
            if (Date.now() - info.modified < 500)
                return;
            const content = await fs.readFile(filePath, 'utf-8');
            if (info.lastContent === content)
                return;
            info.lastContent = content;
            info.modified = stat.mtime.getTime();
            if (ws.readyState === WebSocket.OPEN) {
                ws.send(JSON.stringify({
                    type: 'fs:fileChanged',
                    path: filePath,
                    content,
                    modified: stat.mtime.toISOString(),
                }));
            }
        }
        catch (error) {
            console.error(`[FileWatcher] Error: ${filePath}`, error);
        }
    }
    stopAll() {
        for (const watcher of this.watchers.values()) {
            watcher.close();
        }
        this.watchers.clear();
        this.fileInfo.clear();
    }
}
const fileWatcherManager = new FileWatcherManager();
// Import fs sync for initial reads
import * as fsSync from 'fs';
// File system handler
export function setupFileSystemHandler(ws, sessionId) {
    return (msg) => {
        const { type, id, method, args } = msg;
        switch (method) {
            case 'fs:watchFile':
                handleWatchFile(ws, id, args);
                break;
            case 'fs:unwatchFile':
                handleUnwatchFile(ws, id, args);
                break;
            case 'fs:readDir':
                handleReadDir(ws, id, args);
                break;
            case 'fs:readFile':
                handleReadFile(ws, id, args);
                break;
            case 'fs:writeFile':
                handleWriteFile(ws, id, args);
                break;
            case 'fs:writeFileBinary':
                handleWriteFileBinary(ws, id, args);
                break;
            case 'fs:downloadFile':
                handleDownloadFile(ws, id, args);
                break;
            case 'fs:createFile':
                handleCreateFile(ws, id, args);
                break;
            case 'fs:createDir':
                handleCreateDir(ws, id, args);
                break;
            case 'fs:rename':
                handleRename(ws, id, args);
                break;
            case 'fs:delete':
                handleDelete(ws, id, args);
                break;
            case 'fs:getHomeDir':
                ws.send(JSON.stringify({ type: 'response', id, result: homedir() }));
                break;
            case 'fs:exists':
                handleExists(ws, id, args);
                break;
            case 'fs:getFileInfo':
                handleGetFileInfo(ws, id, args);
                break;
            case 'fs:readFileBase64':
                handleReadFileBase64(ws, id, args);
                break;
            case 'fs:readFileAsBase64':
                handleReadFileAsBase64(ws, id, args);
                break;
            case 'fs:copyDir':
                handleCopyDir(ws, id, args);
                break;
            case 'fs:getTemplatePath':
                handleGetTemplatePath(ws, id, args);
                break;
            case 'fs:npmInstall':
                handleNpmInstall(ws, id, args);
                break;
            case 'fs:revealInFolder':
                handleRevealInFolder(ws, id, args);
                break;
            default:
                ws.send(JSON.stringify({ type: 'error', id, error: `Unknown fs method: ${method}` }));
        }
    };
}
async function handleWatchFile(ws, id, args) {
    const filePath = args[0];
    fileWatcherManager.startWatching(filePath, ws);
    ws.send(JSON.stringify({ type: 'response', id, result: { success: true } }));
}
async function handleUnwatchFile(ws, id, args) {
    const filePath = args[0];
    fileWatcherManager.stopWatching(filePath);
    ws.send(JSON.stringify({ type: 'response', id, result: { success: true } }));
}
async function handleReadDir(ws, id, args) {
    const dirPath = args[0];
    try {
        try {
            await fs.access(dirPath);
        }
        catch {
            ws.send(JSON.stringify({ type: 'response', id, result: [] }));
            return;
        }
        const entries = await fs.readdir(dirPath, { withFileTypes: true });
        const result = entries
            .filter(entry => !entry.name.startsWith('.'))
            .map(entry => ({
            name: entry.name,
            path: join(dirPath, entry.name),
            isDirectory: entry.isDirectory(),
            isFile: entry.isFile(),
            ext: entry.isFile() ? extname(entry.name) : ''
        }))
            .sort((a, b) => {
            if (a.isDirectory === b.isDirectory) {
                return a.name.localeCompare(b.name);
            }
            return a.isDirectory ? -1 : 1;
        });
        ws.send(JSON.stringify({ type: 'response', id, result }));
    }
    catch (error) {
        ws.send(JSON.stringify({ type: 'error', id, error: `Failed to read directory: ${error}` }));
    }
}
async function handleReadFile(ws, id, args) {
    const filePath = args[0];
    try {
        const content = await fs.readFile(filePath, 'utf-8');
        const stat = await fs.stat(filePath);
        const result = {
            content,
            path: filePath,
            name: basename(filePath),
            size: stat.size,
            modified: stat.mtime.toISOString()
        };
        ws.send(JSON.stringify({ type: 'response', id, result }));
    }
    catch (error) {
        ws.send(JSON.stringify({ type: 'error', id, error: `Failed to read file: ${error}` }));
    }
}
async function handleWriteFile(ws, id, args) {
    const filePath = args[0];
    const content = args[1];
    try {
        await fs.writeFile(filePath, content, 'utf-8');
        fileWatcherManager.updateModifiedTime(filePath);
        ws.send(JSON.stringify({ type: 'response', id, result: { success: true } }));
    }
    catch (error) {
        ws.send(JSON.stringify({ type: 'error', id, error: `Failed to write file: ${error}` }));
    }
}
async function handleWriteFileBinary(ws, id, args) {
    const filePath = args[0];
    const data = args[1];
    try {
        const dir = dirname(filePath);
        await fs.mkdir(dir, { recursive: true });
        const buffer = Buffer.from(data);
        await fs.writeFile(filePath, buffer);
        ws.send(JSON.stringify({ type: 'response', id, result: { success: true } }));
    }
    catch (error) {
        ws.send(JSON.stringify({ type: 'error', id, error: `Failed to write binary file: ${error}` }));
    }
}
async function handleDownloadFile(ws, id, args) {
    const url = args[0];
    const filePath = args[1];
    try {
        const dir = dirname(filePath);
        await fs.mkdir(dir, { recursive: true });
        await new Promise((resolve, reject) => {
            https.get(url, (response) => {
                if (response.statusCode !== 200) {
                    reject(new Error(`Download failed with status ${response.statusCode}`));
                    return;
                }
                const chunks = [];
                response.on('data', (chunk) => chunks.push(chunk));
                response.on('end', async () => {
                    try {
                        const buffer = Buffer.concat(chunks);
                        await fs.writeFile(filePath, buffer);
                        resolve();
                    }
                    catch (error) {
                        reject(error);
                    }
                });
                response.on('error', reject);
            }).on('error', reject);
        });
        ws.send(JSON.stringify({ type: 'response', id, result: { success: true } }));
    }
    catch (error) {
        ws.send(JSON.stringify({ type: 'error', id, error: `Failed to download file: ${error}` }));
    }
}
async function handleCreateFile(ws, id, args) {
    const dirPath = args[0];
    const fileName = args[1];
    try {
        const filePath = join(dirPath, fileName);
        await fs.writeFile(filePath, '', 'utf-8');
        ws.send(JSON.stringify({ type: 'response', id, result: { path: filePath, success: true } }));
    }
    catch (error) {
        ws.send(JSON.stringify({ type: 'error', id, error: `Failed to create file: ${error}` }));
    }
}
async function handleCreateDir(ws, id, args) {
    const dirPath = args[0];
    const dirName = args[1];
    try {
        const newDirPath = join(dirPath, dirName);
        await fs.mkdir(newDirPath, { recursive: true });
        ws.send(JSON.stringify({ type: 'response', id, result: { path: newDirPath, success: true } }));
    }
    catch (error) {
        ws.send(JSON.stringify({ type: 'error', id, error: `Failed to create directory: ${error}` }));
    }
}
async function handleRename(ws, id, args) {
    const oldPath = args[0];
    const newName = args[1];
    try {
        const newPath = join(dirname(oldPath), newName);
        await fs.rename(oldPath, newPath);
        ws.send(JSON.stringify({ type: 'response', id, result: { path: newPath, success: true } }));
    }
    catch (error) {
        ws.send(JSON.stringify({ type: 'error', id, error: `Failed to rename: ${error}` }));
    }
}
async function handleDelete(ws, id, args) {
    const targetPath = args[0];
    try {
        const stat = await fs.stat(targetPath);
        if (stat.isDirectory()) {
            await fs.rm(targetPath, { recursive: true });
        }
        else {
            await fs.unlink(targetPath);
        }
        ws.send(JSON.stringify({ type: 'response', id, result: { success: true } }));
    }
    catch (error) {
        ws.send(JSON.stringify({ type: 'error', id, error: `Failed to delete: ${error}` }));
    }
}
async function handleExists(ws, id, args) {
    const path = args[0];
    try {
        await fs.access(path);
        ws.send(JSON.stringify({ type: 'response', id, result: true }));
    }
    catch {
        ws.send(JSON.stringify({ type: 'response', id, result: false }));
    }
}
async function handleGetFileInfo(ws, id, args) {
    const filePath = args[0];
    try {
        const stat = await fs.stat(filePath);
        const result = {
            size: stat.size,
            isFile: stat.isFile(),
            isDirectory: stat.isDirectory(),
            created: stat.birthtime.toISOString(),
            modified: stat.mtime.toISOString(),
            accessed: stat.atime.toISOString()
        };
        ws.send(JSON.stringify({ type: 'response', id, result }));
    }
    catch (error) {
        ws.send(JSON.stringify({ type: 'response', id, result: null }));
    }
}
async function handleReadFileBase64(ws, id, args) {
    const filePath = args[0];
    try {
        const buffer = await fs.readFile(filePath);
        const ext = extname(filePath).toLowerCase();
        const mimeTypes = {
            '.png': 'image/png',
            '.jpg': 'image/jpeg',
            '.jpeg': 'image/jpeg',
            '.gif': 'image/gif',
            '.webp': 'image/webp',
            '.bmp': 'image/bmp',
            '.svg': 'image/svg+xml',
            '.pdf': 'application/pdf',
            '.txt': 'text/plain',
            '.md': 'text/markdown',
            '.json': 'application/json',
        };
        const mimeType = mimeTypes[ext] || 'application/octet-stream';
        const isText = mimeType.startsWith('text/') || mimeType === 'application/json';
        if (isText) {
            ws.send(JSON.stringify({
                type: 'response',
                id,
                result: {
                    type: 'text',
                    content: buffer.toString('utf-8'),
                    mimeType,
                    path: filePath,
                    name: basename(filePath),
                }
            }));
        }
        else {
            const base64 = buffer.toString('base64');
            const dataUrl = `data:${mimeType};base64,${base64}`;
            ws.send(JSON.stringify({
                type: 'response',
                id,
                result: {
                    type: 'base64',
                    content: dataUrl,
                    mimeType,
                    path: filePath,
                    name: basename(filePath),
                }
            }));
        }
    }
    catch (error) {
        ws.send(JSON.stringify({ type: 'error', id, error: `Failed to read file: ${error}` }));
    }
}
async function handleReadFileAsBase64(ws, id, args) {
    const filePath = args[0];
    try {
        const buffer = await fs.readFile(filePath);
        const base64 = buffer.toString('base64');
        ws.send(JSON.stringify({ type: 'response', id, result: { success: true, data: base64 } }));
    }
    catch (error) {
        ws.send(JSON.stringify({ type: 'error', id, error: `Failed to read file as base64: ${error}` }));
    }
}
async function handleCopyDir(ws, id, args) {
    const sourcePath = args[0];
    const targetPath = args[1];
    try {
        const sourceStat = await fs.stat(sourcePath);
        if (!sourceStat.isDirectory()) {
            ws.send(JSON.stringify({ type: 'error', id, error: `Source is not a directory: ${sourcePath}` }));
            return;
        }
        await fs.mkdir(targetPath, { recursive: true });
        async function copyRecursive(src, dest) {
            const entries = await fs.readdir(src, { withFileTypes: true });
            for (const entry of entries) {
                const srcEntryPath = join(src, entry.name);
                const destEntryPath = join(dest, entry.name);
                if (entry.isDirectory()) {
                    await fs.mkdir(destEntryPath, { recursive: true });
                    await copyRecursive(srcEntryPath, destEntryPath);
                }
                else {
                    await fs.copyFile(srcEntryPath, destEntryPath);
                }
            }
        }
        await copyRecursive(sourcePath, targetPath);
        ws.send(JSON.stringify({ type: 'response', id, result: { success: true, path: targetPath } }));
    }
    catch (error) {
        ws.send(JSON.stringify({ type: 'error', id, error: `Failed to copy directory: ${error}` }));
    }
}
async function handleGetTemplatePath(ws, id, args) {
    const templateType = args[0];
    try {
        // 定义可能的模板路径（按优先级排序）
        const possiblePaths = [];
        // 1. 相对于当前文件的路径（web-server 开发环境）
        possiblePaths.push(join(__dirname, '..', '..', '..', 'resources', 'templates', templateType));
        // 2. 相对于当前文件的路径（web-server 生产环境 - npm-web-dist/server/resources）
        possiblePaths.push(join(__dirname, '..', '..', 'resources', 'templates', templateType));
        // 3. 全局安装路径：hstudio-web-cli/server/resources（Windows/Linux/Mac）
        // __dirname 在全局安装后是：hstudio-web-cli/server/handlers
        possiblePaths.push(join(__dirname, '..', 'resources', 'templates', templateType));
        // 4. 相对于工作目录的路径（npm start）
        possiblePaths.push(join(process.cwd(), 'resources', 'templates', templateType));
        // 5. 相对于工作目录的上级（monorepo 结构）
        possiblePaths.push(join(process.cwd(), '..', 'resources', 'templates', templateType));
        // 6. 环境变量指定的路径
        if (process.env.HSTUDIO_TEMPLATES_PATH) {
            possiblePaths.push(join(process.env.HSTUDIO_TEMPLATES_PATH, templateType));
        }
        console.log(`[handleGetTemplatePath] Looking for template: ${templateType}`);
        console.log(`[handleGetTemplatePath] __dirname: ${__dirname}`);
        console.log(`[handleGetTemplatePath] process.cwd(): ${process.cwd()}`);
        // 尝试所有可能的路径
        let foundPath = null;
        for (const tryPath of possiblePaths) {
            console.log(`[handleGetTemplatePath] Trying: ${tryPath}`);
            try {
                await fs.access(tryPath);
                const stat = await fs.stat(tryPath);
                if (stat.isDirectory()) {
                    foundPath = tryPath;
                    console.log(`[handleGetTemplatePath] Found template at: ${tryPath}`);
                    break;
                }
            }
            catch {
                // 路径不存在，继续尝试下一个
            }
        }
        if (!foundPath) {
            // 列出尝试过的路径，帮助调试
            const triedPaths = possiblePaths.map(p => `  - ${p}`).join('\n');
            throw new Error(`Template not found: ${templateType}\n\nTried paths:\n${triedPaths}`);
        }
        ws.send(JSON.stringify({ type: 'response', id, result: { success: true, path: foundPath } }));
    }
    catch (error) {
        console.error(`[handleGetTemplatePath] Error:`, error);
        ws.send(JSON.stringify({ type: 'error', id, error: `Failed to get template path: ${error}` }));
    }
}
async function handleNpmInstall(ws, id, args) {
    const cwd = args[0];
    try {
        const { stdout, stderr } = await execAsync('npm install', { cwd, timeout: 300000 });
        ws.send(JSON.stringify({ type: 'response', id, result: { success: true, stdout, stderr } }));
    }
    catch (error) {
        ws.send(JSON.stringify({ type: 'error', id, error: `Failed to run npm install: ${error}` }));
    }
}
async function handleRevealInFolder(ws, id, args) {
    const targetPath = args[0];
    try {
        const platform = process.platform;
        let command;
        if (platform === 'darwin') {
            // macOS: use open -R to reveal in Finder
            command = `open -R "${targetPath}"`;
        }
        else if (platform === 'win32') {
            // Windows: use explorer /select
            command = `explorer /select,"${targetPath}"`;
        }
        else {
            // Linux: try xdg-open with the parent directory
            const parentDir = dirname(targetPath);
            command = `xdg-open "${parentDir}"`;
        }
        await execAsync(command);
        ws.send(JSON.stringify({ type: 'response', id, result: { success: true } }));
    }
    catch (error) {
        // Even if command fails, we consider it a success (e.g., folder opened but file not selected)
        console.log(`[revealInFolder] Command executed with result:`, error);
        ws.send(JSON.stringify({ type: 'response', id, result: { success: true } }));
    }
}
//# sourceMappingURL=file-system.js.map