"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = require("express");
const asyncHandler_1 = require("../utils/asyncHandler");
const db_1 = require("../db");
const schema_1 = require("../db/schema");
const drizzle_orm_1 = require("drizzle-orm");
const auth_1 = require("../utils/auth");
const errorHandler_1 = require("../middleware/errorHandler");
const validation_1 = require("../utils/validation");
const zod_1 = require("zod");
const router = (0, express_1.Router)();
router.use(auth_1.authMiddleware);
function applyTaskFilters(query, userId, filters) {
    const conditions = [(0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId)];
    if (filters.categoryId) {
        conditions.push((0, drizzle_orm_1.eq)(schema_1.tasks.categoryId, filters.categoryId));
    }
    if (filters.completed !== undefined) {
        conditions.push((0, drizzle_orm_1.eq)(schema_1.tasks.completed, filters.completed));
    }
    if (filters.dueBefore) {
        conditions.push((0, drizzle_orm_1.lte)(schema_1.tasks.dueDate, filters.dueBefore));
    }
    if (filters.dueAfter) {
        conditions.push((0, drizzle_orm_1.gte)(schema_1.tasks.dueDate, filters.dueAfter));
    }
    if (filters.priority) {
        conditions.push((0, drizzle_orm_1.eq)(schema_1.tasks.priority, filters.priority));
    }
    return query.where((0, drizzle_orm_1.and)(...conditions));
}
function applyTaskSorting(query, sortBy = 'dueDate', sortOrder = 'asc') {
    const orderFn = sortOrder === 'desc' ? drizzle_orm_1.desc : drizzle_orm_1.asc;
    const columnMap = {
        dueDate: schema_1.tasks.dueDate,
        priority: schema_1.tasks.priority,
        createdAt: schema_1.tasks.createdAt,
        title: schema_1.tasks.title,
    };
    return query.orderBy(orderFn(columnMap[sortBy] || schema_1.tasks.dueDate));
}
router.get('/', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
    const filters = validation_1.taskQuerySchema.parse(req.query);
    const userId = req.user.userId;
    let query = db_1.db.select().from(schema_1.tasks);
    query = applyTaskFilters(query, userId, filters);
    query = applyTaskSorting(query, filters.sortBy, filters.sortOrder);
    const limit = filters.limit ?? 50;
    const offset = filters.offset ?? 0;
    query = query.limit(limit).offset(offset);
    const results = await query;
    // Fetch subtasks for each task
    const taskIds = results.map((t) => t.id);
    let taskSubtasks = [];
    if (taskIds.length > 0) {
        taskSubtasks = await db_1.db
            .select()
            .from(schema_1.subtasks)
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId), (0, drizzle_orm_1.inArray)(schema_1.subtasks.taskId, taskIds)))
            .orderBy((0, drizzle_orm_1.asc)(schema_1.subtasks.order));
    }
    const subtasksByTask = taskSubtasks.reduce((acc, st) => {
        if (!acc[st.taskId])
            acc[st.taskId] = [];
        acc[st.taskId].push(st);
        return acc;
    }, {});
    const tasksWithSubtasks = results.map((task) => ({
        ...task,
        subtasks: subtasksByTask[task.id] || [],
    }));
    // Get total count
    const countQuery = db_1.db.select({ count: (0, drizzle_orm_1.sql) `count(*)` }).from(schema_1.tasks);
    const countResult = await applyTaskFilters(countQuery, userId, filters);
    const total = Number(countResult[0]?.count ?? 0);
    res.json({ tasks: tasksWithSubtasks, total, limit, offset });
}));
router.get('/:id', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
    const userId = req.user.userId;
    const task = await db_1.db
        .select()
        .from(schema_1.tasks)
        .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId)))
        .limit(1);
    if (task.length === 0) {
        throw new errorHandler_1.AppError('NOT_FOUND', 'Task not found', 404);
    }
    const taskSubtasks = await db_1.db
        .select()
        .from(schema_1.subtasks)
        .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.taskId, req.params.id), (0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId)))
        .orderBy((0, drizzle_orm_1.asc)(schema_1.subtasks.order));
    res.json({ ...task[0], subtasks: taskSubtasks });
}));
router.post('/', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
    const data = validation_1.taskCreateSchema.parse(req.body);
    const userId = req.user.userId;
    const now = Date.now();
    // Verify category exists and belongs to user (optional - tasks may be uncategorized)
    if (data.categoryId) {
        const cat = await db_1.db
            .select()
            .from(schema_1.categories)
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.categories.id, data.categoryId), (0, drizzle_orm_1.eq)(schema_1.categories.userId, userId)))
            .limit(1);
        if (cat.length === 0) {
            throw new errorHandler_1.AppError('NOT_FOUND', 'Category not found', 404);
        }
    }
    const taskId = `task_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
    const newTask = {
        id: taskId,
        userId,
        categoryId: data.categoryId,
        title: data.title,
        description: data.description ?? '',
        priority: data.priority ?? 'none',
        completed: false,
        dueDate: data.dueDate ?? 0,
        dueTime: data.dueTime ?? '',
        endTime: data.endTime ?? '',
        allDay: data.allDay ?? false,
        assigneeId: data.assigneeId ?? null,
        reminder: data.reminder ?? 'none',
        reminders: data.reminders ?? '',
        createdAt: now,
        updatedAt: now,
    };
    await db_1.db.insert(schema_1.tasks).values(newTask);
    // Create subtasks if provided
    if (data.subtasks && data.subtasks.length > 0) {
        const subtaskValues = data.subtasks.map((st, index) => ({
            id: `sub_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}${index}`,
            userId,
            taskId,
            title: st.title,
            completed: false,
            order: index,
            createdAt: now,
            updatedAt: now,
        }));
        await db_1.db.insert(schema_1.subtasks).values(subtaskValues);
    }
    const createdSubtasks = await db_1.db
        .select()
        .from(schema_1.subtasks)
        .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.taskId, taskId), (0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId)))
        .orderBy((0, drizzle_orm_1.asc)(schema_1.subtasks.order));
    res.status(201).json({ ...newTask, subtasks: createdSubtasks });
}));
router.patch('/:id', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
    const data = validation_1.taskUpdateSchema.parse(req.body);
    const userId = req.user.userId;
    const existing = await db_1.db
        .select()
        .from(schema_1.tasks)
        .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId)))
        .limit(1);
    if (existing.length === 0) {
        throw new errorHandler_1.AppError('NOT_FOUND', 'Task not found', 404);
    }
    // Verify category if provided
    if (data.categoryId) {
        const cat = await db_1.db
            .select()
            .from(schema_1.categories)
            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.categories.id, data.categoryId), (0, drizzle_orm_1.eq)(schema_1.categories.userId, userId)))
            .limit(1);
        if (cat.length === 0) {
            throw new errorHandler_1.AppError('NOT_FOUND', 'Category not found', 404);
        }
    }
    // Prevent completing tasks with future due dates
    if (data.completed === true) {
        const effectiveDueDate = data.dueDate ?? existing[0].dueDate;
        if (!(0, validation_1.canCompleteTask)(effectiveDueDate)) {
            throw new errorHandler_1.AppError('VALIDATION_ERROR', 'Cannot mark a task as completed if its due date is in the future', 400);
        }
    }
    const now = Date.now();
    const updated = await db_1.db
        .update(schema_1.tasks)
        .set({ ...data, updatedAt: now })
        .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId)))
        .returning();
    const taskSubtasks = await db_1.db
        .select()
        .from(schema_1.subtasks)
        .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.subtasks.taskId, req.params.id), (0, drizzle_orm_1.eq)(schema_1.subtasks.userId, userId)))
        .orderBy((0, drizzle_orm_1.asc)(schema_1.subtasks.order));
    res.json({ ...updated[0], subtasks: taskSubtasks });
}));
router.delete('/:id', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
    const userId = req.user.userId;
    const existing = await db_1.db
        .select()
        .from(schema_1.tasks)
        .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId)))
        .limit(1);
    if (existing.length === 0) {
        throw new errorHandler_1.AppError('NOT_FOUND', 'Task not found', 404);
    }
    await db_1.db
        .delete(schema_1.tasks)
        .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, req.params.id), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId)));
    res.status(204).send();
}));
router.post('/batch', (0, asyncHandler_1.asyncHandler)(async (req, res) => {
    const { operations } = zod_1.z.object({
        operations: zod_1.z.array(zod_1.z.union([
            zod_1.z.object({ type: zod_1.z.literal('create'), data: validation_1.taskCreateSchema }),
            zod_1.z.object({ type: zod_1.z.literal('update'), id: zod_1.z.string(), data: validation_1.taskUpdateSchema }),
            zod_1.z.object({ type: zod_1.z.literal('delete'), id: zod_1.z.string() }),
        ])),
    }).parse(req.body);
    const userId = req.user.userId;
    const results = [];
    for (const op of operations) {
        try {
            if (op.type === 'create') {
                if (op.data.categoryId) {
                    const cat = await db_1.db
                        .select()
                        .from(schema_1.categories)
                        .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.categories.id, op.data.categoryId), (0, drizzle_orm_1.eq)(schema_1.categories.userId, userId)))
                        .limit(1);
                    if (cat.length === 0)
                        throw new errorHandler_1.AppError('NOT_FOUND', 'Category not found', 404);
                }
                const taskId = `task_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
                const now = Date.now();
                await db_1.db.insert(schema_1.tasks).values({
                    id: taskId,
                    userId,
                    ...op.data,
                    completed: false,
                    createdAt: now,
                    updatedAt: now,
                });
                results.push({ id: taskId, success: true });
            }
            else if (op.type === 'update') {
                if (op.data.completed === true) {
                    let effectiveDueDate;
                    if (op.data.dueDate !== undefined) {
                        effectiveDueDate = op.data.dueDate;
                    }
                    else {
                        const existingTask = await db_1.db
                            .select({ dueDate: schema_1.tasks.dueDate })
                            .from(schema_1.tasks)
                            .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, op.id), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId)))
                            .limit(1);
                        if (existingTask.length === 0)
                            throw new errorHandler_1.AppError('NOT_FOUND', 'Task not found', 404);
                        effectiveDueDate = existingTask[0].dueDate;
                    }
                    if (!(0, validation_1.canCompleteTask)(effectiveDueDate)) {
                        throw new errorHandler_1.AppError('VALIDATION_ERROR', 'Cannot mark a task as completed if its due date is in the future', 400);
                    }
                }
                await db_1.db
                    .update(schema_1.tasks)
                    .set({ ...op.data, updatedAt: Date.now() })
                    .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, op.id), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId)));
                results.push({ id: op.id, success: true });
            }
            else if (op.type === 'delete') {
                await db_1.db
                    .delete(schema_1.tasks)
                    .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.tasks.id, op.id), (0, drizzle_orm_1.eq)(schema_1.tasks.userId, userId)));
                results.push({ id: op.id, success: true });
            }
        }
        catch (error) {
            results.push({
                id: 'id' in op ? op.id : 'unknown',
                success: false,
                error: error instanceof Error ? error.message : 'Unknown error'
            });
        }
    }
    res.json({ results });
}));
exports.default = router;
//# sourceMappingURL=tasks.js.map