/**
 * 文件浏览器 API 路由
 *
 * 提供文件系统浏览功能
 */
import os from 'node:os';
import { createReadStream, promises as fs } from 'node:fs';
import path from 'node:path';
import { Router } from 'express';
import multer from 'multer';
import { allowHostFileBrowser } from '../config.js';
import { validateToken } from '../middleware/auth.js';
import { createLogger } from '../utils/logger.js';
import { createWorkspaceEntry, deleteWorkspaceEntry, extractWorkspaceArchive, listWorkspaceDirectory, moveWorkspaceEntry, previewWorkspaceDocument, readWorkspaceFile, renameWorkspaceEntry, resolveWorkspaceDownloadTarget, streamWorkspaceDirectoryZip, uploadWorkspaceFiles, writeWorkspaceFile, } from '../services/workspace-files.js';
import { unwatchWorkspaceFile, watchWorkspaceFile } from '../services/workspace-watch.js';
const log = createLogger('file-browser');
export const fileBrowserRouter = Router();
export const WORKSPACE_UPLOAD_FILE_LIMIT = 2000;
const upload = multer({
    dest: path.join(os.tmpdir(), 'agentswork-runtime-bridge-uploads'),
    limits: {
        files: WORKSPACE_UPLOAD_FILE_LIMIT,
        fileSize: 512 * 1024 * 1024
    }
});
function toBoolean(value) {
    return value === true || value === 'true' || value === '1';
}
function contentDispositionFileName(fileName) {
    const fallbackName = fileName
        .replace(/[\\"\r\n]/g, '_')
        .replace(/[^\x20-\x7E]/g, '_');
    return `attachment; filename="${fallbackName}"; filename*=UTF-8''${encodeURIComponent(fileName)}`;
}
async function cleanupUploadedFiles(files = []) {
    await Promise.all(files.map((file) => fs.rm(file.path, { force: true }).catch(() => undefined)));
}
export function createWorkspaceUploadLimitResponse(error) {
    return {
        status: 400,
        body: {
            error: error.code === 'LIMIT_FILE_COUNT'
                ? `一次最多上传 ${WORKSPACE_UPLOAD_FILE_LIMIT} 个文件，请拆分文件夹后重试。`
                : error.message,
            code: error.code
        }
    };
}
export function parseWorkspaceUploadRelativePaths(body) {
    const relativePathsJson = body.relativePathsJson;
    if (typeof relativePathsJson === 'string' && relativePathsJson.trim()) {
        const parsed = JSON.parse(relativePathsJson);
        if (!Array.isArray(parsed)) {
            throw new Error('relativePathsJson must be an array');
        }
        return parsed.map((relativePath) => String(relativePath || ''));
    }
    const relativePaths = body.relativePaths;
    return Array.isArray(relativePaths)
        ? relativePaths.map((relativePath) => String(relativePath || ''))
        : typeof relativePaths === 'string'
            ? [relativePaths]
            : [];
}
function workspaceUploadMiddleware(req, res, next) {
    upload.array('files')(req, res, (error) => {
        if (!error) {
            next();
            return;
        }
        if (error instanceof multer.MulterError) {
            const response = createWorkspaceUploadLimitResponse(error);
            res.status(response.status).json(response.body);
            return;
        }
        next(error);
    });
}
async function listHostDirectories(requestPath = '') {
    const platform = process.platform;
    const isWindows = platform === 'win32';
    if (!requestPath || requestPath === '') {
        if (isWindows) {
            const drives = [];
            for (let i = 67; i <= 90; i++) { // C to Z
                const drive = String.fromCharCode(i) + ':\\';
                try {
                    await fs.access(drive);
                    drives.push({
                        name: drive,
                        path: drive,
                        isDirectory: true
                    });
                }
                catch {
                    // Drive not accessible, skip
                }
            }
            return {
                isWindows: true,
                currentPath: '',
                items: drives
            };
        }
        const entries = await fs.readdir('/', { withFileTypes: true });
        const items = entries
            .filter(entry => entry.isDirectory())
            .map(entry => ({
            name: entry.name,
            path: '/' + entry.name,
            isDirectory: true
        }));
        return {
            isWindows: false,
            currentPath: '/',
            items
        };
    }
    const normalizedPath = path.normalize(requestPath);
    let stat;
    try {
        stat = await fs.stat(normalizedPath);
    }
    catch {
        throw new Error('Path not found: ' + normalizedPath);
    }
    if (!stat.isDirectory()) {
        throw new Error('Path is not a directory: ' + normalizedPath);
    }
    const entries = await fs.readdir(normalizedPath, { withFileTypes: true });
    const items = entries
        .filter(entry => entry.isDirectory())
        .map(entry => ({
        name: entry.name,
        path: path.join(normalizedPath, entry.name),
        isDirectory: true
    }));
    return {
        isWindows,
        currentPath: normalizedPath,
        items
    };
}
/**
 * 列出目录内容
 * POST /api/file-browser/list
 */
fileBrowserRouter.post('/list', validateToken, async (req, res) => {
    try {
        if (!allowHostFileBrowser) {
            return res.status(403).json({
                error: 'Host filesystem browsing is disabled. Use /api/file-browser/workspace/list with a registered workspace, or set AWS_ALLOW_HOST_FILE_BROWSER=true for isolated local debugging.'
            });
        }
        const { path: requestPath = '' } = req.body || {};
        res.json(await listHostDirectories(String(requestPath || '')));
    }
    catch (error) {
        const err = error;
        res.status(500).json({ error: err.message });
    }
});
/**
 * 列出目录选择器内容。
 * POST /api/file-browser/directory-picker/list
 *
 * 该接口只返回目录，用于选择工作目录；不同于 /list，它不暴露文件条目。
 */
fileBrowserRouter.post('/directory-picker/list', validateToken, async (req, res) => {
    try {
        const { path: requestPath = '' } = req.body || {};
        res.json(await listHostDirectories(String(requestPath || '')));
    }
    catch (error) {
        const err = error;
        res.status(400).json({ error: err.message });
    }
});
/**
 * 列出工作区目录内容
 * POST /api/file-browser/workspace/list
 */
fileBrowserRouter.post('/workspace/list', validateToken, async (req, res) => {
    try {
        const { workspacePath, path: requestPath = '' } = req.body || {};
        const result = await listWorkspaceDirectory({
            workspacePath: String(workspacePath || ''),
            targetPath: String(requestPath || '')
        });
        res.json({
            isWindows: process.platform === 'win32',
            currentPath: result.currentPath,
            workspacePath: result.workspacePath,
            items: result.items
        });
    }
    catch (error) {
        const err = error;
        console.error('[file-browser] failed to list workspace directory:', err);
        res.status(400).json({ error: err.message });
    }
});
/**
 * 读取工作区文件内容
 * POST /api/file-browser/workspace/read
 */
fileBrowserRouter.post('/workspace/read', validateToken, async (req, res) => {
    try {
        const { workspacePath, filePath } = req.body || {};
        const result = await readWorkspaceFile({
            workspacePath: String(workspacePath || ''),
            filePath: String(filePath || '')
        });
        res.json(result);
    }
    catch (error) {
        const err = error;
        console.error('[file-browser] failed to read workspace file:', err);
        res.status(400).json({ error: err.message });
    }
});
/**
 * 预览工作区文档内容。
 * POST /api/file-browser/workspace/preview
 */
fileBrowserRouter.post('/workspace/preview', validateToken, async (req, res) => {
    try {
        const { workspacePath, filePath } = req.body || {};
        const result = await previewWorkspaceDocument({
            workspacePath: String(workspacePath || ''),
            filePath: String(filePath || '')
        });
        res.json(result);
    }
    catch (error) {
        const err = error;
        log.error('failed to preview workspace document:', err);
        res.status(400).json({ error: err.message });
    }
});
/**
 * 监听工作区文件变化，用于文档预览自动刷新。
 * POST /api/file-browser/workspace/watch
 */
fileBrowserRouter.post('/workspace/watch', validateToken, async (req, res) => {
    try {
        const { workspacePath, filePath } = req.body || {};
        res.json(await watchWorkspaceFile({
            workspacePath: String(workspacePath || ''),
            filePath: String(filePath || '')
        }));
    }
    catch (error) {
        const err = error;
        log.error('failed to watch workspace file:', err);
        res.status(400).json({ error: err.message });
    }
});
/**
 * 取消监听工作区文件变化。
 * POST /api/file-browser/workspace/unwatch
 */
fileBrowserRouter.post('/workspace/unwatch', validateToken, async (req, res) => {
    try {
        const { workspacePath, filePath } = req.body || {};
        res.json(await unwatchWorkspaceFile({
            workspacePath: String(workspacePath || ''),
            filePath: String(filePath || '')
        }));
    }
    catch (error) {
        const err = error;
        log.error('failed to unwatch workspace file:', err);
        res.status(400).json({ error: err.message });
    }
});
/**
 * 保存工作区文件内容
 * POST /api/file-browser/workspace/write
 */
fileBrowserRouter.post('/workspace/write', validateToken, async (req, res) => {
    try {
        const { workspacePath, filePath, content } = req.body || {};
        const result = await writeWorkspaceFile({
            workspacePath: String(workspacePath || ''),
            filePath: String(filePath || ''),
            content: String(content ?? '')
        });
        res.json(result);
    }
    catch (error) {
        const err = error;
        console.error('[file-browser] failed to write workspace file:', err);
        res.status(400).json({ error: err.message });
    }
});
/**
 * 在工作区目录下创建文件或目录
 * POST /api/file-browser/workspace/create
 */
fileBrowserRouter.post('/workspace/create', validateToken, async (req, res) => {
    try {
        const { workspacePath, path: requestPath = '', entryName, isDirectory } = req.body || {};
        const result = await createWorkspaceEntry({
            workspacePath: String(workspacePath || ''),
            targetPath: String(requestPath || ''),
            entryName: String(entryName || ''),
            isDirectory: Boolean(isDirectory)
        });
        res.json(result);
    }
    catch (error) {
        const err = error;
        log.error('failed to create workspace entry:', err);
        res.status(400).json({ error: err.message });
    }
});
/**
 * 重命名工作区中的文件或目录
 * POST /api/file-browser/workspace/rename
 */
fileBrowserRouter.post('/workspace/rename', validateToken, async (req, res) => {
    try {
        const { workspacePath, targetPath, newName } = req.body || {};
        const result = await renameWorkspaceEntry({
            workspacePath: String(workspacePath || ''),
            targetPath: String(targetPath || ''),
            newName: String(newName || '')
        });
        res.json(result);
    }
    catch (error) {
        const err = error;
        log.error('failed to rename workspace entry:', err);
        res.status(400).json({ error: err.message });
    }
});
/**
 * 移动工作区中的文件或目录到另一个工作区目录
 * POST /api/file-browser/workspace/move
 */
fileBrowserRouter.post('/workspace/move', validateToken, async (req, res) => {
    try {
        const { workspacePath, targetPath, destinationPath } = req.body || {};
        const result = await moveWorkspaceEntry({
            workspacePath: String(workspacePath || ''),
            targetPath: String(targetPath || ''),
            destinationPath: String(destinationPath || '')
        });
        res.json(result);
    }
    catch (error) {
        const err = error;
        log.error('failed to move workspace entry:', err);
        res.status(400).json({ error: err.message });
    }
});
/**
 * 删除工作区中的文件或目录
 * POST /api/file-browser/workspace/delete
 */
fileBrowserRouter.post('/workspace/delete', validateToken, async (req, res) => {
    try {
        const { workspacePath, targetPath } = req.body || {};
        const result = await deleteWorkspaceEntry({
            workspacePath: String(workspacePath || ''),
            targetPath: String(targetPath || '')
        });
        res.json(result);
    }
    catch (error) {
        const err = error;
        log.error('failed to delete workspace entry:', err);
        res.status(400).json({ error: err.message });
    }
});
/**
 * 上传文件到工作区指定目录，可选自动解压常见压缩包。
 * POST /api/file-browser/workspace/upload
 */
fileBrowserRouter.post('/workspace/upload', validateToken, workspaceUploadMiddleware, async (req, res) => {
    const files = (req.files || []);
    try {
        const { workspacePath, path: requestPath = '', extractArchives } = req.body || {};
        const relativePaths = parseWorkspaceUploadRelativePaths(req.body || {});
        const result = await uploadWorkspaceFiles({
            workspacePath: String(workspacePath || ''),
            targetPath: String(requestPath || ''),
            extractArchives: toBoolean(extractArchives),
            files: files.map((file, index) => ({
                originalname: file.originalname,
                relativePath: relativePaths[index],
                path: file.path,
                size: file.size
            }))
        });
        res.json(result);
    }
    catch (error) {
        const err = error;
        log.error('failed to upload workspace files:', err);
        res.status(400).json({ error: err.message });
    }
    finally {
        await cleanupUploadedFiles(files);
    }
});
/**
 * 下载工作区文件或目录；目录会实时打包为 zip。
 * POST /api/file-browser/workspace/download
 */
fileBrowserRouter.post('/workspace/download', validateToken, async (req, res) => {
    try {
        const { workspacePath, targetPath } = req.body || {};
        const target = await resolveWorkspaceDownloadTarget({
            workspacePath: String(workspacePath || ''),
            targetPath: String(targetPath || '')
        });
        res.setHeader('Content-Type', target.contentType);
        res.setHeader('Content-Disposition', contentDispositionFileName(target.fileName));
        res.setHeader('X-Workspace-Path', encodeURIComponent(target.workspacePath));
        res.setHeader('X-Workspace-Target-Path', encodeURIComponent(target.targetPath));
        if (target.isDirectory) {
            await streamWorkspaceDirectoryZip(target.resolvedTargetPath, res);
            return;
        }
        createReadStream(target.resolvedTargetPath).pipe(res);
    }
    catch (error) {
        const err = error;
        log.error('failed to download workspace entry:', err);
        if (!res.headersSent) {
            res.status(400).json({ error: err.message });
        }
        else {
            res.destroy(err);
        }
    }
});
/**
 * 解压工作区内的压缩包到指定目录。
 * POST /api/file-browser/workspace/extract
 */
fileBrowserRouter.post('/workspace/extract', validateToken, async (req, res) => {
    try {
        const { workspacePath, archivePath, outputPath = '' } = req.body || {};
        const result = await extractWorkspaceArchive({
            workspacePath: String(workspacePath || ''),
            archivePath: String(archivePath || ''),
            outputPath: String(outputPath || '')
        });
        res.json(result);
    }
    catch (error) {
        const err = error;
        log.error('failed to extract workspace archive:', err);
        res.status(400).json({ error: err.message });
    }
});
