/**
 * Task Router — CRUD, approval, listing by parent/initiative/phase
 */

import { TRPCError } from '@trpc/server';
import { z } from 'zod';
import type { ProcedureBuilder } from '../trpc.js';
import {
  requireTaskRepository,
  requireInitiativeRepository,
  requirePhaseRepository,
  requireDispatchManager,
  requireChangeSetRepository,
} from './_helpers.js';

export function taskProcedures(publicProcedure: ProcedureBuilder) {
  return {
    listTasks: publicProcedure
      .input(z.object({ parentTaskId: z.string().min(1) }))
      .query(async ({ ctx, input }) => {
        const taskRepository = requireTaskRepository(ctx);
        return taskRepository.findByParentTaskId(input.parentTaskId);
      }),

    getTask: publicProcedure
      .input(z.object({ id: z.string().min(1) }))
      .query(async ({ ctx, input }) => {
        const taskRepository = requireTaskRepository(ctx);
        const task = await taskRepository.findById(input.id);
        if (!task) {
          throw new TRPCError({
            code: 'NOT_FOUND',
            message: `Task '${input.id}' not found`,
          });
        }
        return task;
      }),

    updateTaskStatus: publicProcedure
      .input(z.object({
        id: z.string().min(1),
        status: z.enum(['pending', 'in_progress', 'completed', 'blocked']),
      }))
      .mutation(async ({ ctx, input }) => {
        const taskRepository = requireTaskRepository(ctx);
        const existing = await taskRepository.findById(input.id);
        if (!existing) {
          throw new TRPCError({
            code: 'NOT_FOUND',
            message: `Task '${input.id}' not found`,
          });
        }

        // Route through dispatchManager when completing — emits task:completed
        // event so the orchestrator can check phase completion and merge branches
        if (input.status === 'completed' && ctx.dispatchManager) {
          await ctx.dispatchManager.completeTask(input.id);
          return (await taskRepository.findById(input.id))!;
        }

        return taskRepository.update(input.id, { status: input.status });
      }),

    createInitiativeTask: publicProcedure
      .input(z.object({
        initiativeId: z.string().min(1),
        name: z.string().min(1),
        description: z.string().optional(),
        category: z.enum(['execute', 'research', 'discuss', 'plan', 'detail', 'refine', 'verify', 'merge', 'review']).optional(),
        type: z.enum(['auto']).optional(),
      }))
      .mutation(async ({ ctx, input }) => {
        const taskRepository = requireTaskRepository(ctx);
        const initiativeRepo = requireInitiativeRepository(ctx);

        const initiative = await initiativeRepo.findById(input.initiativeId);
        if (!initiative) {
          throw new TRPCError({
            code: 'NOT_FOUND',
            message: `Initiative '${input.initiativeId}' not found`,
          });
        }

        return taskRepository.create({
          initiativeId: input.initiativeId,
          name: input.name,
          description: input.description ?? null,
          category: input.category ?? 'execute',
          type: input.type ?? 'auto',
          status: 'pending',
        });
      }),

    createPhaseTask: publicProcedure
      .input(z.object({
        phaseId: z.string().min(1),
        name: z.string().min(1),
        description: z.string().optional(),
        category: z.enum(['execute', 'research', 'discuss', 'plan', 'detail', 'refine', 'verify', 'merge', 'review']).optional(),
        type: z.enum(['auto']).optional(),
      }))
      .mutation(async ({ ctx, input }) => {
        const taskRepository = requireTaskRepository(ctx);
        const phaseRepo = requirePhaseRepository(ctx);

        const phase = await phaseRepo.findById(input.phaseId);
        if (!phase) {
          throw new TRPCError({
            code: 'NOT_FOUND',
            message: `Phase '${input.phaseId}' not found`,
          });
        }

        return taskRepository.create({
          phaseId: input.phaseId,
          name: input.name,
          description: input.description ?? null,
          category: input.category ?? 'execute',
          type: input.type ?? 'auto',
          status: 'pending',
        });
      }),

    listInitiativeTasks: publicProcedure
      .input(z.object({ initiativeId: z.string().min(1) }))
      .query(async ({ ctx, input }) => {
        const taskRepository = requireTaskRepository(ctx);
        const tasks = await taskRepository.findByInitiativeId(input.initiativeId);
        return tasks;
      }),

    listPhaseTasks: publicProcedure
      .input(z.object({ phaseId: z.string().min(1) }))
      .query(async ({ ctx, input }) => {
        const taskRepository = requireTaskRepository(ctx);
        const tasks = await taskRepository.findByPhaseId(input.phaseId);
        return tasks.filter((t) => t.category !== 'detail');
      }),

    updateTask: publicProcedure
      .input(z.object({
        id: z.string().min(1),
        name: z.string().min(1).optional(),
        description: z.string().nullable().optional(),
      }))
      .mutation(async ({ ctx, input }) => {
        const taskRepository = requireTaskRepository(ctx);
        const existing = await taskRepository.findById(input.id);
        if (!existing) {
          throw new TRPCError({
            code: 'NOT_FOUND',
            message: `Task '${input.id}' not found`,
          });
        }
        const { id, ...data } = input;
        return taskRepository.update(id, data);
      }),

    deleteTask: publicProcedure
      .input(z.object({ id: z.string().min(1) }))
      .mutation(async ({ ctx, input }) => {
        const taskRepository = requireTaskRepository(ctx);
        await taskRepository.delete(input.id);

        // Reconcile any applied changesets that created this task.
        // If all created tasks in a changeset are now deleted, mark it reverted.
        if (ctx.changeSetRepository) {
          try {
            const csRepo = requireChangeSetRepository(ctx);
            const affectedChangeSets = await csRepo.findAppliedByCreatedEntity('task', input.id);
            for (const cs of affectedChangeSets) {
              const createdTaskIds = cs.entries
                .filter(e => e.entityType === 'task' && e.action === 'create')
                .map(e => e.entityId);
              const survivingTasks = await Promise.all(
                createdTaskIds.map(id => taskRepository.findById(id)),
              );
              if (survivingTasks.every(t => t === null)) {
                await csRepo.markReverted(cs.id);
              }
            }
          } catch {
            // Best-effort reconciliation — don't fail the delete
          }
        }

        return { success: true };
      }),

    listPhaseTaskDependencies: publicProcedure
      .input(z.object({ phaseId: z.string().min(1) }))
      .query(async ({ ctx, input }) => {
        const taskRepo = requireTaskRepository(ctx);
        const tasks = await taskRepo.findByPhaseId(input.phaseId);
        const edges: Array<{ taskId: string; dependsOn: string[] }> = [];
        for (const t of tasks) {
          const deps = await taskRepo.getDependencies(t.id);
          if (deps.length > 0) edges.push({ taskId: t.id, dependsOn: deps });
        }
        return edges;
      }),

    listInitiativeTaskDependencies: publicProcedure
      .input(z.object({ initiativeId: z.string().min(1) }))
      .query(async ({ ctx, input }) => {
        const taskRepo = requireTaskRepository(ctx);
        const tasks = await taskRepo.findByInitiativeId(input.initiativeId);
        const edges: Array<{ taskId: string; dependsOn: string[] }> = [];
        for (const t of tasks) {
          const deps = await taskRepo.getDependencies(t.id);
          if (deps.length > 0) edges.push({ taskId: t.id, dependsOn: deps });
        }
        return edges;
      }),

  };
}
