/**
 * Workspace Routes — REST API for server-side file browsing.
 *
 * Exposes the FileSystemSurface's list/read/stat operations
 * so the web UI can browse a remote workspace without needing
 * the browser's File System Access API.
 */
import { PathTraversalError } from "../security/path-guard.js";
import { FileSystemSurface } from "../surfaces/filesystem.js";
import { RemoteFileSystemSurface } from "../surfaces/remote-filesystem.js";
import { uuidv7 } from "../db/uuidv7.js";
/**
 * Find the first running filesystem surface, optionally filtering by ID.
 */
function findFsSurface(registry, surfaceId) {
    if (surfaceId) {
        const s = registry.getSurface(surfaceId);
        if (s && (s instanceof FileSystemSurface || s instanceof RemoteFileSystemSurface) && s.state === "running")
            return s;
        return null;
    }
    // Find the first running filesystem surface
    for (const s of registry.listSurfaces()) {
        if ((s instanceof FileSystemSurface || s instanceof RemoteFileSystemSurface) && s.state === "running")
            return s;
    }
    return null;
}
export function registerWorkspaceRoutes(app, surfaceRegistry, sessionState, sessionService, ws) {
    // GET /api/workspace/info — returns the active workspace root + surface ID
    app.get("/api/workspace/info", async (_req, reply) => {
        const fs = findFsSurface(surfaceRegistry);
        if (!fs) {
            return reply.status(404).send({ error: "NO_WORKSPACE", message: "No filesystem surface is running" });
        }
        const snap = fs.snapshot();
        return {
            surfaceId: snap.id,
            workspaceRoot: snap.metadata?.workspaceRoot ?? null,
            state: snap.state,
        };
    });
    // POST /api/workspace/open — create a filesystem surface for the given path
    // This is called when a client picks a directory (e.g. Electron native dialog)
    // so that ALL clients on the session can browse files via the gateway REST API.
    app.post("/api/workspace/open", async (req, reply) => {
        const body = req.body;
        const workspacePath = body?.path;
        const sessionId = body?.sessionId;
        const nodeId = body?.nodeId || "gateway";
        if (!workspacePath) {
            return reply.status(400).send({ error: "VALIDATION_ERROR", message: "path is required" });
        }
        if (!sessionId) {
            return reply.status(400).send({ error: "VALIDATION_ERROR", message: "sessionId is required" });
        }
        const isRemote = ws?.isRemoteNode(nodeId) ?? false;
        if (isRemote) {
            // Remote node — verify path exists via WS proxy
            try {
                const info = await ws.proxyFsOp(nodeId, "stat", { path: workspacePath });
                if (!info.isDirectory) {
                    return reply.status(400).send({ error: "NOT_A_DIRECTORY", message: "The specified path is not a directory" });
                }
            }
            catch {
                return reply.status(400).send({ error: "PATH_NOT_FOUND", message: "The specified path does not exist on the remote node" });
            }
        }
        else {
            // Local gateway — verify the path exists on the local filesystem
            const { stat } = await import("node:fs/promises");
            try {
                const info = await stat(workspacePath);
                if (!info.isDirectory()) {
                    return reply.status(400).send({ error: "NOT_A_DIRECTORY", message: "The specified path is not a directory" });
                }
            }
            catch {
                return reply.status(400).send({ error: "PATH_NOT_FOUND", message: "The specified path does not exist" });
            }
        }
        // Stop any existing filesystem surface for this session
        const existing = surfaceRegistry.getBySession(sessionId)
            .filter((s) => (s instanceof FileSystemSurface || s instanceof RemoteFileSystemSurface) && s.state === "running");
        for (const s of existing) {
            await surfaceRegistry.stopSurface(s.snapshot().id, "replaced");
        }
        // Create a new filesystem surface (local or remote)
        const surfaceId = `filesystem-${uuidv7()}`;
        try {
            if (isRemote) {
                await surfaceRegistry.startSurface("remote-filesystem", surfaceId, {
                    sessionId,
                    workspaceRoot: workspacePath,
                    nodeId,
                });
            }
            else {
                await surfaceRegistry.startSurface("filesystem", surfaceId, {
                    sessionId,
                    workspaceRoot: workspacePath,
                });
            }
        }
        catch (err) {
            return reply.status(500).send({
                error: "SURFACE_START_FAILED",
                message: err instanceof Error ? err.message : "Failed to start filesystem surface",
            });
        }
        // Persist workspace state to session DB so late-joiners get it
        if (sessionState) {
            sessionState.set(sessionId, { "workspace.panel": { open: true, remotePath: workspacePath, surfaceId, nodeId } });
        }
        // Also persist workspacePath on the session record so CLI providers
        // can resolve it even if the filesystem surface is no longer running.
        try {
            sessionService?.update(sessionId, { workspacePath });
        }
        catch { /* best effort */ }
        return { surfaceId, workspaceRoot: workspacePath };
    });
    // GET /api/workspace/list?path=&surfaceId= — list directory entries
    app.get("/api/workspace/list", async (req, reply) => {
        const { path: dirPath, surfaceId } = req.query;
        const fs = findFsSurface(surfaceRegistry, surfaceId);
        if (!fs) {
            return reply.status(404).send({ error: "NO_WORKSPACE", message: "No filesystem surface is running" });
        }
        try {
            const snap = fs.snapshot();
            const root = snap.metadata?.workspaceRoot;
            const targetPath = dirPath || root || ".";
            const entries = await fs.list(targetPath);
            return { path: targetPath, entries };
        }
        catch (err) {
            return reply.status(400).send({
                error: "LIST_FAILED",
                message: err instanceof Error ? err.message : "Failed to list directory",
            });
        }
    });
    // GET /api/workspace/read?path=&surfaceId= — read a file
    app.get("/api/workspace/read", async (req, reply) => {
        const { path: filePath, surfaceId } = req.query;
        const fs = findFsSurface(surfaceRegistry, surfaceId);
        if (!fs) {
            return reply.status(404).send({ error: "NO_WORKSPACE", message: "No filesystem surface is running" });
        }
        if (!filePath) {
            return reply.status(400).send({ error: "VALIDATION_ERROR", message: "path query parameter is required" });
        }
        try {
            const content = await fs.read(filePath);
            const stInfo = await fs.statFile(filePath);
            return { path: filePath, content, size: stInfo.size, modified: stInfo.modified };
        }
        catch (err) {
            return reply.status(400).send({
                error: "READ_FAILED",
                message: err instanceof Error ? err.message : "Failed to read file",
            });
        }
    });
    // POST /api/workspace/write — write a file while preserving undo backups
    app.post("/api/workspace/write", async (req, reply) => {
        const body = req.body;
        const filePath = body?.path;
        const content = body?.content;
        if (!filePath) {
            return reply.status(400).send({ error: "VALIDATION_ERROR", message: "path is required" });
        }
        if (typeof content !== "string") {
            return reply.status(400).send({ error: "VALIDATION_ERROR", message: "content must be a string" });
        }
        const fs = findFsSurface(surfaceRegistry, body?.surfaceId);
        if (!fs) {
            return reply.status(404).send({ error: "NO_WORKSPACE", message: "No filesystem surface is running" });
        }
        try {
            await fs.write(filePath, content);
            return { ok: true, path: filePath };
        }
        catch (err) {
            const message = err instanceof Error ? err.message : "Failed to write file";
            const isValidationError = err instanceof PathTraversalError || (err instanceof Error && (message.includes("outside workspace root")
                || message.includes("refers to a symlink")
                || message.includes("must be relative")
                || message.includes("escapes workspace boundary")
                || message.includes("path traversal")));
            return reply.status(isValidationError ? 400 : 500).send({
                error: isValidationError ? "VALIDATION_ERROR" : "WRITE_FAILED",
                message,
            });
        }
    });
    // GET /api/workspace/stat?path=&surfaceId= — stat a file or directory
    app.get("/api/workspace/stat", async (req, reply) => {
        const { path: targetPath, surfaceId } = req.query;
        const fs = findFsSurface(surfaceRegistry, surfaceId);
        if (!fs) {
            return reply.status(404).send({ error: "NO_WORKSPACE", message: "No filesystem surface is running" });
        }
        if (!targetPath) {
            return reply.status(400).send({ error: "VALIDATION_ERROR", message: "path query parameter is required" });
        }
        try {
            const info = await fs.statFile(targetPath);
            return { path: targetPath, ...info };
        }
        catch (err) {
            return reply.status(400).send({
                error: "STAT_FAILED",
                message: err instanceof Error ? err.message : "Failed to stat path",
            });
        }
    });
    // POST /api/workspace/undo — restore a file to its pre-modification state
    app.post("/api/workspace/undo", async (req, reply) => {
        const body = req.body;
        const filePath = body?.path;
        if (!filePath) {
            return reply.status(400).send({ error: "VALIDATION_ERROR", message: "path is required" });
        }
        const fs = findFsSurface(surfaceRegistry, body?.surfaceId);
        if (!fs) {
            return reply.status(404).send({ error: "NO_WORKSPACE", message: "No filesystem surface is running" });
        }
        try {
            const restored = await fs.restore(filePath);
            if (!restored) {
                return reply.status(404).send({ error: "NO_BACKUP", message: "No backup found for this file" });
            }
            return { ok: true, path: filePath };
        }
        catch (err) {
            return reply.status(500).send({
                error: "UNDO_FAILED",
                message: err instanceof Error ? err.message : "Failed to undo file change",
            });
        }
    });
    // GET /api/workspace/backup?path= — get the original (backed-up) content of a file
    app.get("/api/workspace/backup", async (req, reply) => {
        const { path: filePath, surfaceId } = req.query;
        const fs = findFsSurface(surfaceRegistry, surfaceId);
        if (!fs) {
            return reply.status(404).send({ error: "NO_WORKSPACE", message: "No filesystem surface is running" });
        }
        if (!filePath) {
            return reply.status(400).send({ error: "VALIDATION_ERROR", message: "path query parameter is required" });
        }
        const hasBackup = fs.hasBackup(filePath);
        if (!hasBackup) {
            return reply.status(404).send({ error: "NO_BACKUP", message: "No backup found for this file" });
        }
        const backup = fs.getBackup(filePath);
        // Also read the current content
        let currentContent;
        try {
            currentContent = await fs.read(filePath);
        }
        catch {
            currentContent = "";
        }
        return {
            path: filePath,
            originalContent: backup, // null if file was newly created
            currentContent,
            hasBackup: true,
        };
    });
    // POST /api/workspace/apply-diff — apply merged file content and clear backup
    app.post("/api/workspace/apply-diff", async (req, reply) => {
        const body = req.body;
        const filePath = body?.path;
        const content = body?.content;
        if (!filePath) {
            return reply.status(400).send({ error: "VALIDATION_ERROR", message: "path is required" });
        }
        const fs = findFsSurface(surfaceRegistry, body?.surfaceId);
        if (!fs) {
            return reply.status(404).send({ error: "NO_WORKSPACE", message: "No filesystem surface is running" });
        }
        try {
            // If content is provided, write it through the surface to keep path validation
            // and backup behavior consistent with other file mutations.
            if (content !== undefined && content !== null) {
                await fs.write(filePath, content);
            }
            fs.clearBackup(filePath);
            return { ok: true, path: filePath };
        }
        catch (err) {
            const message = err instanceof Error ? err.message : "Failed to apply diff";
            const isValidationError = err instanceof PathTraversalError || (err instanceof Error && (message.includes("outside workspace root")
                || message.includes("refers to a symlink")
                || message.includes("must be relative")
                || message.includes("escapes workspace boundary")
                || message.includes("path traversal")));
            return reply.status(isValidationError ? 400 : 500).send({
                error: isValidationError ? "VALIDATION_ERROR" : "APPLY_FAILED",
                message,
            });
        }
    });
    // POST /api/workspace/undo-all — restore all modified files to pre-modification state
    app.post("/api/workspace/undo-all", async (req, reply) => {
        const body = req.body;
        const paths = body?.paths;
        if (!paths || paths.length === 0) {
            return reply.status(400).send({ error: "VALIDATION_ERROR", message: "paths array is required" });
        }
        const fs = findFsSurface(surfaceRegistry, body?.surfaceId);
        if (!fs) {
            return reply.status(404).send({ error: "NO_WORKSPACE", message: "No filesystem surface is running" });
        }
        const results = [];
        for (const p of paths) {
            try {
                const restored = await fs.restore(p);
                results.push({ path: p, restored });
            }
            catch {
                results.push({ path: p, restored: false });
            }
        }
        return { ok: true, results };
    });
}
//# sourceMappingURL=workspace.js.map