import { TaskPoolService } from '../../services/task-pool/task-pool.service.js';
import { projectWorkItems } from '../../services/v3/work-item-projection.js';
import { LoggerService } from '../../services/core/logger.service.js';
const logger = LoggerService.getInstance().createComponentLogger('TasksController');
/**
 * Resolve the project record for `projectId` from storage.
 *
 * Distinguishes three cases for the caller, so each handler can map them
 * to the correct HTTP response without scattering `try/catch` ladders:
 *  - `{ kind: 'ok', project }`         — project found
 *  - `{ kind: 'not-found' }`           — projectId is unknown (→ 404)
 *  - `{ kind: 'storage-error', error }`— storage layer failed (→ 500)
 *
 * @param ctx - The ApiContext exposing the storage service
 * @param projectId - The project UUID from the URL params
 * @returns A discriminated lookup result
 */
async function resolveProject(ctx, projectId) {
    try {
        const projects = await ctx.storageService.getProjects();
        const project = projects.find((p) => p.id === projectId);
        if (!project)
            return { kind: 'not-found' };
        return { kind: 'ok', project };
    }
    catch (error) {
        return { kind: 'storage-error', error };
    }
}
/**
 * Build a graceful-degradation response for a TaskService failure.
 *
 * Returns 200 with `{ success: true, data: <empty>, warning }` so the
 * dashboard card stays alive instead of going red on a 500. The empty
 * value is supplied by the caller because shape varies per endpoint
 * (array vs status-summary object).
 *
 * Logs the underlying error at error-level so the failure is still
 * observable in backend logs and downstream alerting.
 *
 * @param res - Express response
 * @param emptyData - The shape-correct empty value for this endpoint
 * @param projectId - Project ID for log context
 * @param projectPath - Project path for log context
 * @param error - The underlying error from TaskService
 * @param contextLabel - Short label identifying which TaskService call failed
 */
function respondWithDegradation(res, emptyData, projectId, projectPath, error, contextLabel) {
    const message = error instanceof Error ? error.message : String(error);
    logger.error(`TaskService failure (${contextLabel}) — degrading to empty result`, {
        projectId,
        projectPath,
        error: message,
    });
    const warning = `Failed to load ${contextLabel} for project ${projectId}: ${message}`;
    res.json({ success: true, data: emptyData, warning });
}
/**
 * GET /api/projects/:projectId/tasks
 *
 * Lists all tasks across all milestones for a project.
 *
 * Failure mapping:
 *  - 400 if `projectId` is missing
 *  - 404 if the project does not exist
 *  - 500 if the storage layer (projects.json) cannot be read
 *  - 200 + `warning` if TaskService throws (e.g. corrupt task file,
 *    permission denied on .crewly/tasks/) — graceful degradation so
 *    dashboard cards do not go red on per-project filesystem issues.
 *    The `data` array is empty in this case.
 *
 * @throws Never (errors are mapped to HTTP status codes)
 */
export async function getAllTasks(req, res) {
    const { projectId } = req.params;
    if (!projectId) {
        res.status(400).json({ success: false, error: 'Project ID is required' });
        return;
    }
    const lookup = await resolveProject(this, projectId);
    if (lookup.kind === 'not-found') {
        res.status(404).json({ success: false, error: 'Project not found' });
        return;
    }
    if (lookup.kind === 'storage-error') {
        logger.error('Error fetching projects from storage', {
            error: lookup.error instanceof Error ? lookup.error.message : String(lookup.error),
        });
        res.status(500).json({ success: false, error: 'Failed to fetch tasks' });
        return;
    }
    const { project } = lookup;
    try {
        // V3-only as of spec 2026-05-06-task-management-v1-deprecation.md.
        // Reads from TaskPoolService and projects to the legacy InProgressTask
        // shape so the frontend Tasks tab + dashboard cards work unchanged.
        // The legacy `.crewly/tasks/*.md` filesystem reads via TaskService have
        // been retired; that store is empty post-archival.
        const pool = TaskPoolService.getInstance();
        const allItems = await pool.getAllItems();
        const projectItems = allItems.filter((wi) => {
            const meta = (wi.metadata ?? {});
            return meta.projectId === project.id || meta.projectPath === project.path;
        });
        const tasks = projectWorkItems(projectItems);
        res.json({ success: true, data: tasks });
    }
    catch (error) {
        respondWithDegradation(res, [], projectId, project.path, error, 'tasks');
    }
}
/**
 * GET /api/projects/:projectId/milestones
 *
 * Lists all milestones (with their nested tasks) for a project.
 * Same failure mapping as {@link getAllTasks}.
 */
export async function getMilestones(req, res) {
    const { projectId } = req.params;
    if (!projectId) {
        res.status(400).json({ success: false, error: 'Project ID is required' });
        return;
    }
    const lookup = await resolveProject(this, projectId);
    if (lookup.kind === 'not-found') {
        res.status(404).json({ success: false, error: 'Project not found' });
        return;
    }
    if (lookup.kind === 'storage-error') {
        logger.error('Error fetching projects from storage', {
            error: lookup.error instanceof Error ? lookup.error.message : String(lookup.error),
        });
        res.status(500).json({ success: false, error: 'Failed to fetch milestones' });
        return;
    }
    const { project } = lookup;
    try {
        // Milestones are derived from `metadata.milestone` on V3 WorkItems.
        // V1 used filesystem directories under `.crewly/tasks/<milestone>/`;
        // post-deprecation we just enumerate the distinct milestone names.
        const pool = TaskPoolService.getInstance();
        const allItems = await pool.getAllItems();
        const projectItems = allItems.filter((wi) => {
            const meta = (wi.metadata ?? {});
            return meta.projectId === project.id || meta.projectPath === project.path;
        });
        const milestoneSet = new Set();
        for (const wi of projectItems) {
            const meta = (wi.metadata ?? {});
            const m = meta.milestone ?? 'delegated';
            milestoneSet.add(m);
        }
        const milestones = Array.from(milestoneSet).map((id) => ({ id, name: id }));
        res.json({ success: true, data: milestones });
    }
    catch (error) {
        respondWithDegradation(res, [], projectId, project.path, error, 'milestones');
    }
}
/**
 * GET /api/projects/:projectId/tasks/by-status/:status
 *
 * Lists tasks for a project filtered by status.
 * Same failure mapping as {@link getAllTasks}.
 */
export async function getTasksByStatus(req, res) {
    const { projectId, status } = req.params;
    if (!projectId) {
        res.status(400).json({ success: false, error: 'Project ID is required' });
        return;
    }
    const lookup = await resolveProject(this, projectId);
    if (lookup.kind === 'not-found') {
        res.status(404).json({ success: false, error: 'Project not found' });
        return;
    }
    if (lookup.kind === 'storage-error') {
        logger.error('Error fetching projects from storage', {
            error: lookup.error instanceof Error ? lookup.error.message : String(lookup.error),
        });
        res.status(500).json({ success: false, error: 'Failed to fetch tasks by status' });
        return;
    }
    const { project } = lookup;
    try {
        const pool = TaskPoolService.getInstance();
        const allItems = await pool.getAllItems();
        const projected = projectWorkItems(allItems.filter((wi) => {
            const meta = (wi.metadata ?? {});
            return meta.projectId === project.id || meta.projectPath === project.path;
        }));
        const tasks = projected.filter((t) => t.status === status);
        res.json({ success: true, data: tasks });
    }
    catch (error) {
        respondWithDegradation(res, [], projectId, project.path, error, 'tasks by status');
    }
}
/**
 * GET /api/projects/:projectId/tasks/by-milestone/:milestoneId
 *
 * Lists tasks for a project filtered by milestone.
 * Same failure mapping as {@link getAllTasks}.
 */
export async function getTasksByMilestone(req, res) {
    const { projectId, milestoneId } = req.params;
    if (!projectId) {
        res.status(400).json({ success: false, error: 'Project ID is required' });
        return;
    }
    const lookup = await resolveProject(this, projectId);
    if (lookup.kind === 'not-found') {
        res.status(404).json({ success: false, error: 'Project not found' });
        return;
    }
    if (lookup.kind === 'storage-error') {
        logger.error('Error fetching projects from storage', {
            error: lookup.error instanceof Error ? lookup.error.message : String(lookup.error),
        });
        res.status(500).json({ success: false, error: 'Failed to fetch tasks by milestone' });
        return;
    }
    const { project } = lookup;
    try {
        const pool = TaskPoolService.getInstance();
        const allItems = await pool.getAllItems();
        const projectItems = allItems.filter((wi) => {
            const meta = (wi.metadata ?? {});
            const matchesProject = meta.projectId === project.id || meta.projectPath === project.path;
            const matchesMilestone = (meta.milestone ?? 'delegated') === milestoneId;
            return matchesProject && matchesMilestone;
        });
        const tasks = projectWorkItems(projectItems);
        res.json({ success: true, data: tasks });
    }
    catch (error) {
        respondWithDegradation(res, [], projectId, project.path, error, 'tasks by milestone');
    }
}
/**
 * GET /api/projects/:projectId/tasks-status
 *
 * Returns a per-project summary of task counts (totals by status) plus the
 * project's milestones, used by dashboard cards.
 *
 * Failure mapping mirrors {@link getAllTasks}. The graceful-degradation
 * payload uses an empty status-summary shape: `{ totals: { all: 0 }, milestones: [] }`.
 */
export async function getProjectTasksStatus(req, res) {
    const { projectId } = req.params;
    if (!projectId) {
        res.status(400).json({ success: false, error: 'Project ID is required' });
        return;
    }
    const lookup = await resolveProject(this, projectId);
    if (lookup.kind === 'not-found') {
        res.status(404).json({ success: false, error: 'Project not found' });
        return;
    }
    if (lookup.kind === 'storage-error') {
        logger.error('Error fetching projects from storage', {
            error: lookup.error instanceof Error ? lookup.error.message : String(lookup.error),
        });
        res.status(500).json({ success: false, error: 'Failed to get project task status' });
        return;
    }
    const { project } = lookup;
    try {
        const pool = TaskPoolService.getInstance();
        const allItems = await pool.getAllItems();
        const projectItems = allItems.filter((wi) => {
            const meta = (wi.metadata ?? {});
            return meta.projectId === project.id || meta.projectPath === project.path;
        });
        const projected = projectWorkItems(projectItems);
        const byStatus = projected.reduce((acc, t) => {
            acc[t.status] = (acc[t.status] || 0) + 1;
            return acc;
        }, {});
        const milestoneSet = new Set();
        for (const wi of projectItems) {
            const meta = (wi.metadata ?? {});
            milestoneSet.add(meta.milestone ?? 'delegated');
        }
        const milestones = Array.from(milestoneSet).map((id) => ({ id, name: id }));
        res.json({
            success: true,
            data: { totals: { all: projected.length, ...byStatus }, milestones },
        });
    }
    catch (error) {
        respondWithDegradation(res, { totals: { all: 0 }, milestones: [] }, projectId, project.path, error, 'project task status');
    }
}
//# sourceMappingURL=tasks.controller.js.map