/**
 * Architect Router — discuss, plan, refine, detail spawn procedures
 */

import { TRPCError } from '@trpc/server';
import { z } from 'zod';
import type { ProcedureBuilder } from '../trpc.js';
import {
  requireAgentManager,
  requireInitiativeRepository,
  requirePhaseRepository,
  requirePageRepository,
  requireTaskRepository,
} from './_helpers.js';
import {
  buildDiscussPrompt,
  buildPlanPrompt,
  buildRefinePrompt,
  buildDetailPrompt,
} from '../../agent/prompts/index.js';
import { isPlanningCategory } from '../../git/branch-naming.js';
import type { PhaseRepository } from '../../db/repositories/phase-repository.js';
import type { TaskRepository } from '../../db/repositories/task-repository.js';
import type { PageRepository } from '../../db/repositories/page-repository.js';
import type { Phase, Task } from '../../db/schema.js';
import type { PageForSerialization } from '../../agent/content-serializer.js';

export async function gatherInitiativeContext(
  phaseRepo: PhaseRepository | undefined,
  taskRepo: TaskRepository | undefined,
  pageRepo: PageRepository | undefined,
  initiativeId: string,
): Promise<{
  phases: Array<Phase & { dependsOn?: string[] }>;
  tasks: Task[];
  pages: PageForSerialization[];
}> {
  const [rawPhases, deps, initiativeTasks, pages] = await Promise.all([
    phaseRepo?.findByInitiativeId(initiativeId) ?? [],
    phaseRepo?.findDependenciesByInitiativeId(initiativeId) ?? [],
    taskRepo?.findByInitiativeId(initiativeId) ?? [],
    pageRepo?.findByInitiativeId(initiativeId) ?? [],
  ]);

  // Merge dependencies into each phase as a dependsOn array
  const depsByPhase = new Map<string, string[]>();
  for (const dep of deps) {
    const arr = depsByPhase.get(dep.phaseId) ?? [];
    arr.push(dep.dependsOnPhaseId);
    depsByPhase.set(dep.phaseId, arr);
  }
  const phases = rawPhases.map((ph) => ({
    ...ph,
    dependsOn: depsByPhase.get(ph.id) ?? [],
  }));

  // Collect tasks from all phases (some tasks only have phaseId, not initiativeId)
  const taskIds = new Set(initiativeTasks.map((t) => t.id));
  const allTasks = [...initiativeTasks];
  if (taskRepo) {
    for (const ph of rawPhases) {
      const phaseTasks = await taskRepo.findByPhaseId(ph.id);
      for (const t of phaseTasks) {
        if (!taskIds.has(t.id)) {
          taskIds.add(t.id);
          allTasks.push(t);
        }
      }
    }
  }

  // Only include implementation tasks in agent context — planning tasks are irrelevant noise
  const implementationTasks = allTasks.filter(t => !isPlanningCategory(t.category));

  return { phases, tasks: implementationTasks, pages };
}

export function architectProcedures(publicProcedure: ProcedureBuilder) {
  return {
    spawnArchitectDiscuss: publicProcedure
      .input(z.object({
        name: z.string().min(1).optional(),
        initiativeId: z.string().min(1),
        context: z.string().optional(),
        provider: z.string().optional(),
      }))
      .mutation(async ({ ctx, input }) => {
        const agentManager = requireAgentManager(ctx);
        const initiativeRepo = requireInitiativeRepository(ctx);
        const taskRepo = requireTaskRepository(ctx);

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

        const task = await taskRepo.create({
          initiativeId: input.initiativeId,
          name: `Discuss: ${initiative.name}`,
          description: input.context ?? 'Gather context and requirements for initiative',
          category: 'discuss',
          status: 'in_progress',
        });

        const context = await gatherInitiativeContext(ctx.phaseRepository, ctx.taskRepository, ctx.pageRepository, input.initiativeId);

        const prompt = buildDiscussPrompt();

        return agentManager.spawn({
          name: input.name,
          taskId: task.id,
          prompt,
          mode: 'discuss',
          provider: input.provider,
          initiativeId: input.initiativeId,
          inputContext: {
            initiative,
            pages: context.pages.length > 0 ? context.pages : undefined,
            phases: context.phases.length > 0 ? context.phases : undefined,
            tasks: context.tasks.length > 0 ? context.tasks : undefined,
          },
        });
      }),

    spawnArchitectPlan: publicProcedure
      .input(z.object({
        name: z.string().min(1).optional(),
        initiativeId: z.string().min(1),
        contextSummary: z.string().optional(),
        provider: z.string().optional(),
      }))
      .mutation(async ({ ctx, input }) => {
        const agentManager = requireAgentManager(ctx);
        const initiativeRepo = requireInitiativeRepository(ctx);
        const taskRepo = requireTaskRepository(ctx);

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

        // Auto-dismiss stale plan agents
        const allAgents = await agentManager.list();
        const staleAgents = allAgents.filter(
          (a) =>
            a.mode === 'plan' &&
            a.initiativeId === input.initiativeId &&
            ['crashed', 'idle'].includes(a.status) &&
            !a.userDismissedAt,
        );
        for (const stale of staleAgents) {
          await agentManager.dismiss(stale.id);
        }

        // Reject if a plan agent is already active for this initiative
        const activePlanAgents = allAgents.filter(
          (a) =>
            a.mode === 'plan' &&
            a.initiativeId === input.initiativeId &&
            ['running', 'waiting_for_input'].includes(a.status),
        );
        if (activePlanAgents.length > 0) {
          throw new TRPCError({
            code: 'CONFLICT',
            message: 'A plan agent is already running for this initiative',
          });
        }

        const task = await taskRepo.create({
          initiativeId: input.initiativeId,
          name: `Plan: ${initiative.name}`,
          description: 'Plan initiative into phases',
          category: 'plan',
          status: 'in_progress',
        });

        const context = await gatherInitiativeContext(ctx.phaseRepository, ctx.taskRepository, ctx.pageRepository, input.initiativeId);

        const prompt = buildPlanPrompt();

        return agentManager.spawn({
          name: input.name,
          taskId: task.id,
          prompt,
          mode: 'plan',
          provider: input.provider,
          initiativeId: input.initiativeId,
          inputContext: {
            initiative,
            pages: context.pages.length > 0 ? context.pages : undefined,
            phases: context.phases.length > 0 ? context.phases : undefined,
            tasks: context.tasks.length > 0 ? context.tasks : undefined,
          },
        });
      }),

    spawnArchitectRefine: publicProcedure
      .input(z.object({
        name: z.string().min(1).optional(),
        initiativeId: z.string().min(1),
        instruction: z.string().optional(),
        provider: z.string().optional(),
      }))
      .mutation(async ({ ctx, input }) => {
        const agentManager = requireAgentManager(ctx);
        const initiativeRepo = requireInitiativeRepository(ctx);
        const pageRepo = requirePageRepository(ctx);
        const taskRepo = requireTaskRepository(ctx);

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

        // Bug #10: Auto-dismiss stale (crashed/idle) refine agents before checking for active ones
        const allAgents = await agentManager.list();
        const staleAgents = allAgents.filter(
          (a) =>
            a.mode === 'refine' &&
            a.initiativeId === input.initiativeId &&
            ['crashed', 'idle'].includes(a.status) &&
            !a.userDismissedAt,
        );
        for (const stale of staleAgents) {
          await agentManager.dismiss(stale.id);
        }

        // Bug #9: Prevent concurrent refine agents on the same initiative
        const activeRefineAgents = allAgents.filter(
          (a) =>
            a.mode === 'refine' &&
            a.initiativeId === input.initiativeId &&
            ['running', 'waiting_for_input'].includes(a.status),
        );
        if (activeRefineAgents.length > 0) {
          throw new TRPCError({
            code: 'CONFLICT',
            message: `A refine agent is already running for this initiative`,
          });
        }

        const pages = await pageRepo.findByInitiativeId(input.initiativeId);

        if (pages.length === 0) {
          throw new TRPCError({
            code: 'BAD_REQUEST',
            message: 'Initiative has no page content to refine',
          });
        }

        const task = await taskRepo.create({
          initiativeId: input.initiativeId,
          name: `Refine: ${initiative.name}`,
          description: input.instruction ?? 'Review and propose edits to initiative content',
          category: 'refine',
          status: 'in_progress',
        });

        const prompt = buildRefinePrompt(input.instruction);

        return agentManager.spawn({
          name: input.name,
          taskId: task.id,
          prompt,
          mode: 'refine',
          provider: input.provider,
          initiativeId: input.initiativeId,
          inputContext: { initiative, pages },
        });
      }),

    spawnArchitectDetail: publicProcedure
      .input(z.object({
        name: z.string().min(1).optional(),
        phaseId: z.string().min(1),
        taskName: z.string().min(1).optional(),
        context: z.string().optional(),
        provider: z.string().optional(),
      }))
      .mutation(async ({ ctx, input }) => {
        const agentManager = requireAgentManager(ctx);
        const phaseRepo = requirePhaseRepository(ctx);
        const taskRepo = requireTaskRepository(ctx);
        const initiativeRepo = requireInitiativeRepository(ctx);

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

        // Auto-dismiss stale detail agents for this phase
        const allAgents = await agentManager.list();
        const detailAgents = allAgents.filter(
          (a) => a.mode === 'detail' && !a.userDismissedAt,
        );

        // Look up tasks to find which phase each detail agent targets
        const activeForPhase: typeof detailAgents = [];
        const staleForPhase: typeof detailAgents = [];
        for (const agent of detailAgents) {
          if (!agent.taskId) continue;
          const agentTask = await taskRepo.findById(agent.taskId);
          if (agentTask?.phaseId !== input.phaseId) continue;

          if (['crashed', 'idle'].includes(agent.status)) {
            staleForPhase.push(agent);
          } else if (['running', 'waiting_for_input'].includes(agent.status)) {
            activeForPhase.push(agent);
          }
        }
        for (const stale of staleForPhase) {
          await agentManager.dismiss(stale.id);
        }
        if (activeForPhase.length > 0) {
          throw new TRPCError({
            code: 'CONFLICT',
            message: `A detail agent is already running for phase "${phase.name}"`,
          });
        }

        const detailTaskName = input.taskName ?? `Detail: ${phase.name}`;
        const task = await taskRepo.create({
          phaseId: phase.id,
          initiativeId: phase.initiativeId,
          name: detailTaskName,
          description: input.context ?? `Detail phase "${phase.name}" into executable tasks`,
          category: 'detail',
          status: 'in_progress',
        });

        const context = await gatherInitiativeContext(ctx.phaseRepository, ctx.taskRepository, ctx.pageRepository, phase.initiativeId);

        const prompt = buildDetailPrompt();

        return agentManager.spawn({
          name: input.name,
          taskId: task.id,
          prompt,
          mode: 'detail',
          provider: input.provider,
          initiativeId: phase.initiativeId,
          inputContext: {
            initiative,
            phase,
            task,
            pages: context.pages.length > 0 ? context.pages : undefined,
            phases: context.phases.length > 0 ? context.phases : undefined,
            tasks: context.tasks.length > 0 ? context.tasks : undefined,
          },
        });
      }),
  };
}
