import fs from 'fs';
import path from 'path';
import os from 'os';
import express from 'express';
import mime from 'mime-types';
import { WORKSPACES_ROOT, validateWorkspacePath } from '../shared/utils.js';
import { projectsDb } from '../modules/database/index.js';
import { expandWorkspacePath, validatePathInProject, validateFilename, getFileTree, } from '../services/filesystem.service.js';
const router = express.Router();
// ============================================================================
// FILESYSTEM BROWSE & CREATE FOLDER
// ============================================================================
// Browse filesystem endpoint for project suggestions
router.get('/browse-filesystem', async (req, res) => {
    try {
        const { path: dirPath } = req.query;
        // Default to workspaces root if no path provided
        const defaultRoot = WORKSPACES_ROOT;
        let targetPath = dirPath ? expandWorkspacePath(dirPath) : defaultRoot;
        // Resolve and normalize the path
        targetPath = path.resolve(targetPath);
        // Security check - ensure path is within allowed workspace root
        const validation = await validateWorkspacePath(targetPath);
        if (!validation.valid) {
            return res.status(403).json({ error: validation.error });
        }
        const resolvedPath = validation.resolvedPath || targetPath;
        // Security check - ensure path is accessible
        try {
            await fs.promises.access(resolvedPath);
            const stats = await fs.promises.stat(resolvedPath);
            if (!stats.isDirectory()) {
                return res.status(400).json({ error: 'Path is not a directory' });
            }
        }
        catch (err) {
            return res.status(404).json({ error: 'Directory not accessible' });
        }
        // Use getFileTree with shallow depth (only direct children)
        const fileTree = await getFileTree(resolvedPath, 1, 0, false);
        // Filter only directories and format for suggestions
        const directories = fileTree
            .filter(item => item.type === 'directory')
            .map(item => ({
            path: item.path,
            name: item.name,
            type: 'directory'
        }))
            .sort((a, b) => {
            const aHidden = a.name.startsWith('.');
            const bHidden = b.name.startsWith('.');
            if (aHidden && !bHidden)
                return 1;
            if (!aHidden && bHidden)
                return -1;
            return a.name.localeCompare(b.name);
        });
        // Add common directories if browsing home directory
        const suggestions = [];
        let resolvedWorkspaceRoot = defaultRoot;
        try {
            resolvedWorkspaceRoot = await fs.promises.realpath(defaultRoot);
        }
        catch (error) {
            // Use default root as-is if realpath fails
        }
        if (resolvedPath === resolvedWorkspaceRoot) {
            const commonDirs = ['Desktop', 'Documents', 'Projects', 'Development', 'Dev', 'Code', 'workspace'];
            const existingCommon = directories.filter(dir => commonDirs.includes(dir.name));
            const otherDirs = directories.filter(dir => !commonDirs.includes(dir.name));
            suggestions.push(...existingCommon, ...otherDirs);
        }
        else {
            suggestions.push(...directories);
        }
        res.json({
            path: resolvedPath,
            suggestions
        });
    }
    catch (error) {
        console.error('Error browsing filesystem:', error);
        res.status(500).json({ error: 'Failed to browse filesystem' });
    }
});
// Create a new folder
router.post('/create-folder', async (req, res) => {
    try {
        const { path: folderPath } = req.body;
        if (!folderPath) {
            return res.status(400).json({ error: 'Path is required' });
        }
        const expandedPath = expandWorkspacePath(folderPath);
        const resolvedInput = path.resolve(expandedPath);
        const validation = await validateWorkspacePath(resolvedInput);
        if (!validation.valid) {
            return res.status(403).json({ error: validation.error });
        }
        const targetPath = validation.resolvedPath || resolvedInput;
        const parentDir = path.dirname(targetPath);
        try {
            await fs.promises.access(parentDir);
        }
        catch (err) {
            return res.status(404).json({ error: 'Parent directory does not exist' });
        }
        try {
            await fs.promises.access(targetPath);
            return res.status(409).json({ error: 'Folder already exists' });
        }
        catch (err) {
            // Folder doesn't exist, which is what we want
        }
        try {
            await fs.promises.mkdir(targetPath, { recursive: false });
            res.json({ success: true, path: targetPath });
        }
        catch (mkdirError) {
            if (mkdirError.code === 'EEXIST') {
                return res.status(409).json({ error: 'Folder already exists' });
            }
            throw mkdirError;
        }
    }
    catch (error) {
        console.error('Error creating folder:', error);
        res.status(500).json({ error: 'Failed to create folder' });
    }
});
// ============================================================================
// FILE READ / WRITE / TREE
// ============================================================================
// Read file content endpoint
router.get('/projects/:projectId/file', async (req, res) => {
    try {
        const { projectId } = req.params;
        const { filePath } = req.query;
        if (!filePath) {
            return res.status(400).json({ error: 'Invalid file path' });
        }
        const projectRoot = await projectsDb.getProjectPathById(projectId);
        if (!projectRoot) {
            return res.status(404).json({ error: 'Project not found' });
        }
        const resolved = path.isAbsolute(filePath)
            ? path.resolve(filePath)
            : path.resolve(projectRoot, filePath);
        const normalizedRoot = path.resolve(projectRoot) + path.sep;
        if (!resolved.startsWith(normalizedRoot)) {
            return res.status(403).json({ error: 'Path must be under project root' });
        }
        const content = await fs.promises.readFile(resolved, 'utf8');
        res.json({ content, path: resolved });
    }
    catch (error) {
        console.error('Error reading file:', error);
        if (error.code === 'ENOENT') {
            res.status(404).json({ error: 'File not found' });
        }
        else if (error.code === 'EACCES') {
            res.status(403).json({ error: 'Permission denied' });
        }
        else {
            res.status(500).json({ error: error.message });
        }
    }
});
// Serve raw file bytes for previews and downloads
router.get('/projects/:projectId/files/content', async (req, res) => {
    try {
        const { projectId } = req.params;
        const { path: filePath } = req.query;
        if (!filePath) {
            return res.status(400).json({ error: 'Invalid file path' });
        }
        const projectRoot = await projectsDb.getProjectPathById(projectId);
        if (!projectRoot) {
            return res.status(404).json({ error: 'Project not found' });
        }
        const resolved = path.isAbsolute(filePath)
            ? path.resolve(filePath)
            : path.resolve(projectRoot, filePath);
        const normalizedRoot = path.resolve(projectRoot) + path.sep;
        if (!resolved.startsWith(normalizedRoot)) {
            return res.status(403).json({ error: 'Path must be under project root' });
        }
        try {
            await fs.promises.access(resolved);
        }
        catch (error) {
            return res.status(404).json({ error: 'File not found' });
        }
        const mimeType = mime.lookup(resolved) || 'application/octet-stream';
        res.setHeader('Content-Type', mimeType);
        const fileStream = fs.createReadStream(resolved);
        fileStream.pipe(res);
        fileStream.on('error', (error) => {
            console.error('Error streaming file:', error);
            if (!res.headersSent) {
                res.status(500).json({ error: 'Error reading file' });
            }
        });
    }
    catch (error) {
        console.error('Error serving binary file:', error);
        if (!res.headersSent) {
            res.status(500).json({ error: error.message });
        }
    }
});
// Save file content endpoint
router.put('/projects/:projectId/file', async (req, res) => {
    try {
        const { projectId } = req.params;
        const { filePath, content } = req.body;
        if (!filePath) {
            return res.status(400).json({ error: 'Invalid file path' });
        }
        if (content === undefined) {
            return res.status(400).json({ error: 'Content is required' });
        }
        const projectRoot = await projectsDb.getProjectPathById(projectId);
        if (!projectRoot) {
            return res.status(404).json({ error: 'Project not found' });
        }
        const resolved = path.isAbsolute(filePath)
            ? path.resolve(filePath)
            : path.resolve(projectRoot, filePath);
        const normalizedRoot = path.resolve(projectRoot) + path.sep;
        if (!resolved.startsWith(normalizedRoot)) {
            return res.status(403).json({ error: 'Path must be under project root' });
        }
        await fs.promises.writeFile(resolved, content, 'utf8');
        res.json({
            success: true,
            path: resolved,
            message: 'File saved successfully'
        });
    }
    catch (error) {
        console.error('Error saving file:', error);
        if (error.code === 'ENOENT') {
            res.status(404).json({ error: 'File or directory not found' });
        }
        else if (error.code === 'EACCES') {
            res.status(403).json({ error: 'Permission denied' });
        }
        else {
            res.status(500).json({ error: error.message });
        }
    }
});
// Get file tree for a project
router.get('/projects/:projectId/files', async (req, res) => {
    try {
        const actualPath = await projectsDb.getProjectPathById(req.params.projectId);
        if (!actualPath) {
            return res.status(404).json({ error: 'Project not found' });
        }
        try {
            await fs.promises.access(actualPath);
        }
        catch (e) {
            return res.status(404).json({ error: `Project path not found: ${actualPath}` });
        }
        const files = await getFileTree(actualPath, 10, 0, true);
        res.json(files);
    }
    catch (error) {
        console.error('[ERROR] File tree error:', error.message);
        res.status(500).json({ error: error.message });
    }
});
// ============================================================================
// FILE CREATE / RENAME / DELETE
// ============================================================================
// Create new file or directory
router.post('/projects/:projectId/files/create', async (req, res) => {
    try {
        const { projectId } = req.params;
        const { path: parentPath, type, name } = req.body;
        if (!name || !type) {
            return res.status(400).json({ error: 'Name and type are required' });
        }
        if (!['file', 'directory'].includes(type)) {
            return res.status(400).json({ error: 'Type must be "file" or "directory"' });
        }
        const nameValidation = validateFilename(name);
        if (!nameValidation.valid) {
            return res.status(400).json({ error: nameValidation.error });
        }
        const projectRoot = await projectsDb.getProjectPathById(projectId);
        if (!projectRoot) {
            return res.status(404).json({ error: 'Project not found' });
        }
        const targetDir = parentPath || '';
        const targetPath = targetDir ? path.join(targetDir, name) : name;
        const validation = validatePathInProject(projectRoot, targetPath);
        if (!validation.valid) {
            return res.status(403).json({ error: validation.error });
        }
        const resolvedPath = validation.resolved;
        try {
            await fs.promises.access(resolvedPath);
            return res.status(409).json({ error: `${type === 'file' ? 'File' : 'Directory'} already exists` });
        }
        catch {
            // Doesn't exist, which is what we want
        }
        if (type === 'directory') {
            await fs.promises.mkdir(resolvedPath, { recursive: false });
        }
        else {
            const parentDir = path.dirname(resolvedPath);
            try {
                await fs.promises.access(parentDir);
            }
            catch {
                await fs.promises.mkdir(parentDir, { recursive: true });
            }
            await fs.promises.writeFile(resolvedPath, '', 'utf8');
        }
        res.json({
            success: true,
            path: resolvedPath,
            name,
            type,
            message: `${type === 'file' ? 'File' : 'Directory'} created successfully`
        });
    }
    catch (error) {
        console.error('Error creating file/directory:', error);
        if (error.code === 'EACCES') {
            res.status(403).json({ error: 'Permission denied' });
        }
        else if (error.code === 'ENOENT') {
            res.status(404).json({ error: 'Parent directory not found' });
        }
        else {
            res.status(500).json({ error: error.message });
        }
    }
});
// Rename file or directory
router.put('/projects/:projectId/files/rename', async (req, res) => {
    try {
        const { projectId } = req.params;
        const { oldPath, newName } = req.body;
        if (!oldPath || !newName) {
            return res.status(400).json({ error: 'oldPath and newName are required' });
        }
        const nameValidation = validateFilename(newName);
        if (!nameValidation.valid) {
            return res.status(400).json({ error: nameValidation.error });
        }
        const projectRoot = await projectsDb.getProjectPathById(projectId);
        if (!projectRoot) {
            return res.status(404).json({ error: 'Project not found' });
        }
        const oldValidation = validatePathInProject(projectRoot, oldPath);
        if (!oldValidation.valid) {
            return res.status(403).json({ error: oldValidation.error });
        }
        const resolvedOldPath = oldValidation.resolved;
        try {
            await fs.promises.access(resolvedOldPath);
        }
        catch {
            return res.status(404).json({ error: 'File or directory not found' });
        }
        const parentDir = path.dirname(resolvedOldPath);
        const resolvedNewPath = path.join(parentDir, newName);
        const newValidation = validatePathInProject(projectRoot, resolvedNewPath);
        if (!newValidation.valid) {
            return res.status(403).json({ error: newValidation.error });
        }
        try {
            await fs.promises.access(resolvedNewPath);
            return res.status(409).json({ error: 'A file or directory with this name already exists' });
        }
        catch {
            // Doesn't exist, which is what we want
        }
        await fs.promises.rename(resolvedOldPath, resolvedNewPath);
        res.json({
            success: true,
            oldPath: resolvedOldPath,
            newPath: resolvedNewPath,
            newName,
            message: 'Renamed successfully'
        });
    }
    catch (error) {
        console.error('Error renaming file/directory:', error);
        if (error.code === 'EACCES') {
            res.status(403).json({ error: 'Permission denied' });
        }
        else if (error.code === 'ENOENT') {
            res.status(404).json({ error: 'File or directory not found' });
        }
        else if (error.code === 'EXDEV') {
            res.status(400).json({ error: 'Cannot move across different filesystems' });
        }
        else {
            res.status(500).json({ error: error.message });
        }
    }
});
// Delete file or directory
router.delete('/projects/:projectId/files', async (req, res) => {
    try {
        const { projectId } = req.params;
        const { path: targetPath, type } = req.body;
        if (!targetPath) {
            return res.status(400).json({ error: 'Path is required' });
        }
        const projectRoot = await projectsDb.getProjectPathById(projectId);
        if (!projectRoot) {
            return res.status(404).json({ error: 'Project not found' });
        }
        const validation = validatePathInProject(projectRoot, targetPath);
        if (!validation.valid) {
            return res.status(403).json({ error: validation.error });
        }
        const resolvedPath = validation.resolved;
        let stats;
        try {
            stats = await fs.promises.stat(resolvedPath);
        }
        catch {
            return res.status(404).json({ error: 'File or directory not found' });
        }
        if (resolvedPath === path.resolve(projectRoot)) {
            return res.status(403).json({ error: 'Cannot delete project root directory' });
        }
        if (stats.isDirectory()) {
            await fs.promises.rm(resolvedPath, { recursive: true, force: true });
        }
        else {
            await fs.promises.unlink(resolvedPath);
        }
        res.json({
            success: true,
            path: resolvedPath,
            type: stats.isDirectory() ? 'directory' : 'file',
            message: 'Deleted successfully'
        });
    }
    catch (error) {
        console.error('Error deleting file/directory:', error);
        if (error.code === 'EACCES') {
            res.status(403).json({ error: 'Permission denied' });
        }
        else if (error.code === 'ENOENT') {
            res.status(404).json({ error: 'File or directory not found' });
        }
        else if (error.code === 'ENOTEMPTY') {
            res.status(400).json({ error: 'Directory is not empty' });
        }
        else {
            res.status(500).json({ error: error.message });
        }
    }
});
// ============================================================================
// FILE UPLOAD (multer)
// ============================================================================
// Upload files to a project
router.post('/projects/:projectId/files/upload', async (req, res) => {
    const multer = (await import('multer')).default;
    const uploadMiddleware = multer({
        storage: multer.diskStorage({
            destination: (req, file, cb) => {
                cb(null, os.tmpdir());
            },
            filename: (req, file, cb) => {
                const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
                cb(null, `upload-${uniqueSuffix}`);
            }
        }),
        limits: {
            fileSize: 50 * 1024 * 1024, // 50MB limit
            files: 20 // Max 20 files at once
        }
    });
    uploadMiddleware.array('files', 20)(req, res, async (err) => {
        if (err) {
            console.error('Multer error:', err);
            if (err.code === 'LIMIT_FILE_SIZE') {
                return res.status(400).json({ error: 'File too large. Maximum size is 50MB.' });
            }
            if (err.code === 'LIMIT_FILE_COUNT') {
                return res.status(400).json({ error: 'Too many files. Maximum is 20 files.' });
            }
            return res.status(500).json({ error: err.message });
        }
        try {
            const { projectId } = req.params;
            const { targetPath, relativePaths } = req.body;
            let filePaths = [];
            if (relativePaths) {
                try {
                    filePaths = JSON.parse(relativePaths);
                }
                catch (e) {
                    // ignore parse errors
                }
            }
            if (!req.files || req.files.length === 0) {
                return res.status(400).json({ error: 'No files provided' });
            }
            const projectRoot = await projectsDb.getProjectPathById(projectId);
            if (!projectRoot) {
                return res.status(404).json({ error: 'Project not found' });
            }
            const targetDir = targetPath || '';
            let resolvedTargetDir;
            if (!targetDir || targetDir === '.' || targetDir === './') {
                resolvedTargetDir = path.resolve(projectRoot);
            }
            else {
                const validation = validatePathInProject(projectRoot, targetDir);
                if (!validation.valid) {
                    return res.status(403).json({ error: validation.error });
                }
                resolvedTargetDir = validation.resolved;
            }
            try {
                await fs.promises.access(resolvedTargetDir);
            }
            catch {
                await fs.promises.mkdir(resolvedTargetDir, { recursive: true });
            }
            const uploadedFiles = [];
            for (let i = 0; i < req.files.length; i++) {
                const file = req.files[i];
                const fileName = (filePaths && filePaths[i]) ? filePaths[i] : file.originalname;
                const destPath = path.join(resolvedTargetDir, fileName);
                const destValidation = validatePathInProject(projectRoot, destPath);
                if (!destValidation.valid) {
                    await fs.promises.unlink(file.path).catch(() => { });
                    continue;
                }
                const parentDir = path.dirname(destPath);
                try {
                    await fs.promises.access(parentDir);
                }
                catch {
                    await fs.promises.mkdir(parentDir, { recursive: true });
                }
                await fs.promises.copyFile(file.path, destPath);
                await fs.promises.unlink(file.path);
                uploadedFiles.push({
                    name: fileName,
                    path: destPath,
                    size: file.size,
                    mimeType: file.mimetype
                });
            }
            res.json({
                success: true,
                files: uploadedFiles,
                targetPath: resolvedTargetDir,
                message: `Uploaded ${uploadedFiles.length} file(s) successfully`
            });
        }
        catch (error) {
            console.error('Error uploading files:', error);
            if (req.files) {
                for (const file of req.files) {
                    await fs.promises.unlink(file.path).catch(() => { });
                }
            }
            if (error.code === 'EACCES') {
                res.status(403).json({ error: 'Permission denied' });
            }
            else {
                res.status(500).json({ error: error.message });
            }
        }
    });
});
// ============================================================================
// IMAGE UPLOAD
// ============================================================================
// Image upload endpoint
router.post('/projects/:projectId/upload-images', async (req, res) => {
    try {
        const multer = (await import('multer')).default;
        const storage = multer.diskStorage({
            destination: async (req, file, cb) => {
                const uploadDir = path.join(os.tmpdir(), 'claude-ui-uploads', String(req.user.id));
                await fs.promises.mkdir(uploadDir, { recursive: true });
                cb(null, uploadDir);
            },
            filename: (req, file, cb) => {
                const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
                const sanitizedName = file.originalname.replace(/[^a-zA-Z0-9.-]/g, '_');
                cb(null, uniqueSuffix + '-' + sanitizedName);
            }
        });
        const fileFilter = (req, file, cb) => {
            const allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'];
            if (allowedMimes.includes(file.mimetype)) {
                cb(null, true);
            }
            else {
                cb(new Error('Invalid file type. Only JPEG, PNG, GIF, WebP, and SVG are allowed.'));
            }
        };
        const upload = multer({
            storage,
            fileFilter,
            limits: {
                fileSize: 5 * 1024 * 1024, // 5MB
                files: 5
            }
        });
        upload.array('images', 5)(req, res, async (err) => {
            if (err) {
                return res.status(400).json({ error: err.message });
            }
            if (!req.files || req.files.length === 0) {
                return res.status(400).json({ error: 'No image files provided' });
            }
            try {
                const processedImages = await Promise.all(req.files.map(async (file) => {
                    const buffer = await fs.promises.readFile(file.path);
                    const base64 = buffer.toString('base64');
                    const mimeType = file.mimetype;
                    await fs.promises.unlink(file.path);
                    return {
                        name: file.originalname,
                        data: `data:${mimeType};base64,${base64}`,
                        size: file.size,
                        mimeType: mimeType
                    };
                }));
                res.json({ images: processedImages });
            }
            catch (error) {
                console.error('Error processing images:', error);
                await Promise.all(req.files.map(f => fs.promises.unlink(f.path).catch(() => { })));
                res.status(500).json({ error: 'Failed to process images' });
            }
        });
    }
    catch (error) {
        console.error('Error in image upload endpoint:', error);
        res.status(500).json({ error: 'Internal server error' });
    }
});
// ============================================================================
// ATTACHMENT UPLOAD
// ============================================================================
// File attachment upload (any file type)
router.post('/projects/:projectId/upload-attachments', async (req, res) => {
    try {
        const multer = (await import('multer')).default;
        const storage = multer.diskStorage({
            destination: async (req, file, cb) => {
                const uploadDir = path.join(os.tmpdir(), 'claude-ui-uploads', String(req.user.id));
                await fs.promises.mkdir(uploadDir, { recursive: true });
                cb(null, uploadDir);
            },
            filename: (req, file, cb) => {
                const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
                const sanitizedName = file.originalname.replace(/[^a-zA-Z0-9.-]/g, '_');
                cb(null, uniqueSuffix + '-' + sanitizedName);
            }
        });
        const upload = multer({
            storage,
            limits: {
                fileSize: 10 * 1024 * 1024, // 10MB
                files: 10
            }
        });
        upload.array('files', 10)(req, res, async (err) => {
            if (err) {
                return res.status(400).json({ error: err.message });
            }
            if (!req.files || req.files.length === 0) {
                return res.status(400).json({ error: 'No files provided' });
            }
            try {
                const processedFiles = await Promise.all(req.files.map(async (file) => {
                    const buffer = await fs.promises.readFile(file.path);
                    const base64 = buffer.toString('base64');
                    const mimeType = file.mimetype || 'application/octet-stream';
                    await fs.promises.unlink(file.path);
                    return {
                        name: file.originalname,
                        data: `data:${mimeType};base64,${base64}`,
                        size: file.size,
                        mimeType: mimeType
                    };
                }));
                res.json({ files: processedFiles });
            }
            catch (error) {
                console.error('Error processing attachments:', error);
                await Promise.all(req.files.map(f => fs.promises.unlink(f.path).catch(() => { })));
                res.status(500).json({ error: 'Failed to process attachments' });
            }
        });
    }
    catch (error) {
        console.error('Error in attachment upload endpoint:', error);
        res.status(500).json({ error: 'Internal server error' });
    }
});
export default router;
//# sourceMappingURL=project-files.js.map