"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createTasksRouter = createTasksRouter;
const fs_1 = __importDefault(require("fs"));
const mime_1 = __importDefault(require("mime"));
const express_1 = require("express");
const multer_1 = __importDefault(require("multer"));
const validation_1 = require("../../api/rest/validation");
const index_1 = require("../../api/rest/index");
const manager_types_1 = require("../../graphs/manager-types");
const defaults_1 = require("../../lib/defaults");
const upload = (0, multer_1.default)({ storage: multer_1.default.memoryStorage(), limits: { fileSize: defaults_1.MAX_UPLOAD_SIZE } });
function createTasksRouter() {
    const router = (0, express_1.Router)({ mergeParams: true });
    function getProject(req) {
        return req.project;
    }
    // List tasks
    router.get('/', (0, validation_1.validateQuery)(validation_1.taskListSchema), (req, res, next) => {
        try {
            const p = getProject(req);
            const q = req.validatedQuery;
            const { results: tasks, total } = p.taskManager.listTasks(q);
            res.json({ results: tasks, total });
        }
        catch (err) {
            next(err);
        }
    });
    // Search tasks
    router.get('/search', (0, validation_1.validateQuery)(validation_1.taskSearchSchema), async (req, res, next) => {
        try {
            const p = getProject(req);
            const q = req.validatedQuery;
            const results = await p.taskManager.searchTasks(q.q, {
                topK: q.topK,
                minScore: q.minScore,
                searchMode: q.searchMode,
                bfsDepth: q.bfsDepth,
                maxResults: q.maxResults,
                bfsDecay: q.bfsDecay,
            });
            res.json({ results });
        }
        catch (err) {
            next(err);
        }
    });
    // Find tasks linked to an external entity
    router.get('/linked', (0, validation_1.validateQuery)(validation_1.linkedQuerySchema), (req, res, next) => {
        try {
            const p = getProject(req);
            const { targetGraph, targetNodeId, kind, projectId } = req.validatedQuery;
            const tasks = p.taskManager.findLinkedTasks(targetGraph, targetNodeId, kind, projectId ?? req.params.projectId);
            res.json({ results: tasks });
        }
        catch (err) {
            next(err);
        }
    });
    // Get task
    router.get('/:taskId', (req, res, next) => {
        try {
            const p = getProject(req);
            const task = p.taskManager.getTask(req.params.taskId);
            if (!task)
                return res.status(404).json({ error: 'Task not found' });
            const relations = p.taskManager.listRelations(req.params.taskId);
            res.json({ ...task, relations });
        }
        catch (err) {
            next(err);
        }
    });
    // Create task
    router.post('/', index_1.requireWriteAccess, (0, validation_1.validateBody)(validation_1.createTaskSchema), async (req, res, next) => {
        try {
            const p = getProject(req);
            const { title, description, status, priority, tags, dueDate, estimate, assignee, order } = req.body;
            const created = await p.mutationQueue.enqueue(async () => {
                const taskId = await p.taskManager.createTask(title, description, status, priority, tags, dueDate, estimate, assignee ?? null, order);
                return p.taskManager.getTask(taskId);
            });
            res.status(201).json(created);
        }
        catch (err) {
            next(err);
        }
    });
    // Bulk routes — must be registered before /:taskId parameterized routes
    router.post('/bulk/move', index_1.requireWriteAccess, (0, validation_1.validateBody)(validation_1.bulkMoveSchema), async (req, res, next) => {
        try {
            const p = getProject(req);
            const { taskIds, status } = req.body;
            const results = await p.mutationQueue.enqueue(async () => {
                const moved = [];
                for (const id of taskIds) {
                    if (p.taskManager.moveTask(id, status))
                        moved.push(id);
                }
                return moved;
            });
            res.json({ moved: results });
        }
        catch (err) {
            next(err);
        }
    });
    // Bulk update priority
    router.post('/bulk/priority', index_1.requireWriteAccess, (0, validation_1.validateBody)(validation_1.bulkPrioritySchema), async (req, res, next) => {
        try {
            const p = getProject(req);
            const { taskIds, priority } = req.body;
            const results = await p.mutationQueue.enqueue(async () => {
                const updated = [];
                for (const id of taskIds) {
                    if (await p.taskManager.updateTask(id, { priority }))
                        updated.push(id);
                }
                return updated;
            });
            res.json({ updated: results });
        }
        catch (err) {
            next(err);
        }
    });
    // Bulk delete tasks
    router.post('/bulk/delete', index_1.requireWriteAccess, (0, validation_1.validateBody)(validation_1.bulkDeleteSchema), async (req, res, next) => {
        try {
            const p = getProject(req);
            const { taskIds } = req.body;
            const results = await p.mutationQueue.enqueue(async () => {
                const deleted = [];
                for (const id of taskIds) {
                    if (p.taskManager.deleteTask(id))
                        deleted.push(id);
                }
                return deleted;
            });
            res.json({ deleted: results });
        }
        catch (err) {
            next(err);
        }
    });
    // Update task
    router.put('/:taskId', index_1.requireWriteAccess, (0, validation_1.validateBody)(validation_1.updateTaskSchema), async (req, res, next) => {
        try {
            const p = getProject(req);
            const taskId = req.params.taskId;
            const { version, ...patch } = req.body;
            const result = await p.mutationQueue.enqueue(async () => {
                const ok = await p.taskManager.updateTask(taskId, patch, version);
                if (!ok)
                    return null;
                return p.taskManager.getTask(taskId);
            });
            if (!result)
                return res.status(404).json({ error: 'Task not found' });
            res.json(result);
        }
        catch (err) {
            if (err instanceof manager_types_1.VersionConflictError) {
                return res.status(409).json({ error: 'version_conflict', current: err.current, expected: err.expected });
            }
            next(err);
        }
    });
    // Move task (change status) — action, so POST
    router.post('/:taskId/move', index_1.requireWriteAccess, (0, validation_1.validateBody)(validation_1.moveTaskSchema), async (req, res, next) => {
        try {
            const p = getProject(req);
            const taskId = req.params.taskId;
            const { status, version, order } = req.body;
            const result = await p.mutationQueue.enqueue(async () => {
                const ok = p.taskManager.moveTask(taskId, status, version, order);
                if (!ok)
                    return null;
                return p.taskManager.getTask(taskId);
            });
            if (!result)
                return res.status(404).json({ error: 'Task not found' });
            res.json(result);
        }
        catch (err) {
            if (err instanceof manager_types_1.VersionConflictError) {
                return res.status(409).json({ error: 'version_conflict', current: err.current, expected: err.expected });
            }
            next(err);
        }
    });
    // Reorder task (change position, optionally status)
    router.post('/:taskId/reorder', index_1.requireWriteAccess, (0, validation_1.validateBody)(validation_1.reorderTaskSchema), async (req, res, next) => {
        try {
            const p = getProject(req);
            const taskId = req.params.taskId;
            const { order, status } = req.body;
            const result = await p.mutationQueue.enqueue(async () => {
                const ok = p.taskManager.reorderTask(taskId, order, status);
                if (!ok)
                    return null;
                return p.taskManager.getTask(taskId);
            });
            if (!result)
                return res.status(404).json({ error: 'Task not found' });
            res.json(result);
        }
        catch (err) {
            next(err);
        }
    });
    // Delete task
    router.delete('/:taskId', index_1.requireWriteAccess, async (req, res, next) => {
        try {
            const p = getProject(req);
            const taskId = req.params.taskId;
            const ok = await p.mutationQueue.enqueue(async () => {
                return p.taskManager.deleteTask(taskId);
            });
            if (!ok)
                return res.status(404).json({ error: 'Task not found' });
            res.status(204).end();
        }
        catch (err) {
            next(err);
        }
    });
    // Create task link (task-to-task or cross-graph)
    router.post('/links', index_1.requireWriteAccess, (0, validation_1.validateBody)(validation_1.createTaskLinkSchema), async (req, res, next) => {
        try {
            const p = getProject(req);
            const { fromId, toId, kind, targetGraph, projectId } = req.body;
            const ok = await p.mutationQueue.enqueue(async () => {
                if (targetGraph) {
                    return p.taskManager.createCrossLink(fromId, toId, targetGraph, kind, projectId);
                }
                else {
                    return p.taskManager.linkTasks(fromId, toId, kind);
                }
            });
            if (!ok)
                return res.status(400).json({ error: 'Failed to create link' });
            res.status(201).json({ fromId, toId, kind, targetGraph: targetGraph || undefined });
        }
        catch (err) {
            next(err);
        }
    });
    // Delete task link
    router.delete('/links', index_1.requireWriteAccess, (0, validation_1.validateBody)(validation_1.createTaskLinkSchema.pick({ fromId: true, toId: true, targetGraph: true, projectId: true })), async (req, res, next) => {
        try {
            const p = getProject(req);
            const { fromId, toId, targetGraph, projectId } = req.body;
            const ok = await p.mutationQueue.enqueue(async () => {
                if (targetGraph) {
                    return p.taskManager.deleteCrossLink(fromId, toId, targetGraph, projectId);
                }
                else {
                    return p.taskManager.deleteTaskLink(fromId, toId);
                }
            });
            if (!ok)
                return res.status(404).json({ error: 'Link not found' });
            res.status(204).end();
        }
        catch (err) {
            next(err);
        }
    });
    // List relations for a task
    router.get('/:taskId/relations', (req, res, next) => {
        try {
            const p = getProject(req);
            const relations = p.taskManager.listRelations(req.params.taskId);
            res.json({ results: relations });
        }
        catch (err) {
            next(err);
        }
    });
    // -- Attachments --
    // Upload attachment
    router.post('/:taskId/attachments', index_1.requireWriteAccess, upload.single('file'), async (req, res, next) => {
        try {
            const p = getProject(req);
            const taskId = req.params.taskId;
            const file = req.file;
            if (!file)
                return res.status(400).json({ error: 'No file uploaded' });
            const filename = validation_1.attachmentFilenameSchema.parse(file.originalname);
            const meta = await p.mutationQueue.enqueue(async () => {
                return p.taskManager.addAttachment(taskId, filename, file.buffer);
            });
            if (!meta)
                return res.status(404).json({ error: 'Task not found' });
            res.status(201).json(meta);
        }
        catch (err) {
            next(err);
        }
    });
    // List attachments
    router.get('/:taskId/attachments', (req, res, next) => {
        try {
            const p = getProject(req);
            const attachments = p.taskManager.listAttachments(req.params.taskId);
            res.json({ results: attachments });
        }
        catch (err) {
            next(err);
        }
    });
    // Download attachment
    router.get('/:taskId/attachments/:filename', (req, res, next) => {
        try {
            const p = getProject(req);
            const filename = validation_1.attachmentFilenameSchema.parse(req.params.filename);
            const filePath = p.taskManager.getAttachmentPath(req.params.taskId, filename);
            if (!filePath)
                return res.status(404).json({ error: 'Attachment not found' });
            const mimeType = mime_1.default.getType(filePath) ?? 'application/octet-stream';
            res.setHeader('Content-Type', mimeType);
            res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`);
            res.setHeader('X-Content-Type-Options', 'nosniff');
            const stream = fs_1.default.createReadStream(filePath);
            stream.on('error', (err) => next(err));
            stream.pipe(res);
        }
        catch (err) {
            next(err);
        }
    });
    // Delete attachment
    router.delete('/:taskId/attachments/:filename', index_1.requireWriteAccess, async (req, res, next) => {
        try {
            const p = getProject(req);
            const taskId = req.params.taskId;
            const filename = validation_1.attachmentFilenameSchema.parse(req.params.filename);
            const ok = await p.mutationQueue.enqueue(async () => {
                return p.taskManager.removeAttachment(taskId, filename);
            });
            if (!ok)
                return res.status(404).json({ error: 'Attachment not found' });
            res.status(204).end();
        }
        catch (err) {
            next(err);
        }
    });
    return router;
}
