/**
 * Initiative Router — create, list, get, update, merge config
 */

import { TRPCError } from '@trpc/server';
import { z } from 'zod';
import type { ProcedureBuilder } from '../trpc.js';
import { requireAgentManager, requireInitiativeRepository, requireProjectRepository, requireTaskRepository, requireBranchManager, requireExecutionOrchestrator } from './_helpers.js';
import { deriveInitiativeActivity } from './initiative-activity.js';
import { buildRefinePrompt, buildConflictResolutionPrompt, buildConflictResolutionDescription } from '../../agent/prompts/index.js';
import type { PageForSerialization } from '../../agent/content-serializer.js';
import { ensureProjectClone } from '../../git/project-clones.js';

export function initiativeProcedures(publicProcedure: ProcedureBuilder) {
  return {
    createInitiative: publicProcedure
      .input(z.object({
        name: z.string().min(1),
        description: z.string().optional(),
        branch: z.string().nullable().optional(),
        projectIds: z.array(z.string().min(1)).min(1).optional(),
        executionMode: z.enum(['yolo', 'review_per_phase']).optional(),
      }))
      .mutation(async ({ ctx, input }) => {
        const repo = requireInitiativeRepository(ctx);

        if (input.projectIds && input.projectIds.length > 0) {
          const projectRepo = requireProjectRepository(ctx);
          for (const pid of input.projectIds) {
            const project = await projectRepo.findById(pid);
            if (!project) {
              throw new TRPCError({
                code: 'NOT_FOUND',
                message: `Project '${pid}' not found`,
              });
            }
          }
        }

        const initiative = await repo.create({
          name: input.name,
          status: 'active',
          ...(input.executionMode && { executionMode: input.executionMode }),
          ...(input.branch && { branch: input.branch }),
        });

        if (input.projectIds && input.projectIds.length > 0) {
          const projectRepo = requireProjectRepository(ctx);
          await projectRepo.setInitiativeProjects(initiative.id, input.projectIds);
        }

        // Create root page — seed with description as tiptap content if provided
        const descriptionText = input.description?.trim();
        let rootPage: { id: string; parentPageId: string | null; title: string; content: string | null; sortOrder: number } | null = null;
        if (ctx.pageRepository) {
          const tiptapContent = descriptionText
            ? JSON.stringify({
              type: 'doc',
              content: descriptionText.split(/\n{2,}/).map(para => ({
                type: 'paragraph',
                content: [{ type: 'text', text: para.trim() }],
              })).filter(p => p.content[0].text),
            })
            : null;

          rootPage = await ctx.pageRepository.create({
            initiativeId: initiative.id,
            parentPageId: null,
            title: input.name,
            content: tiptapContent,
            sortOrder: 0,
          });
        }

        // Auto-spawn refine agent when description is provided
        if (descriptionText && rootPage && ctx.agentManager && ctx.taskRepository) {
          try {
            const taskRepo = requireTaskRepository(ctx);
            const agentManager = requireAgentManager(ctx);

            const task = await taskRepo.create({
              initiativeId: initiative.id,
              name: `Refine: ${initiative.name}`,
              description: descriptionText,
              category: 'refine',
              status: 'in_progress',
            });

            const pages: PageForSerialization[] = [{
              id: rootPage.id,
              parentPageId: null,
              title: rootPage.title,
              content: rootPage.content,
              sortOrder: 0,
            }];

            agentManager.spawn({
              taskId: task.id,
              prompt: buildRefinePrompt(),
              mode: 'refine',
              initiativeId: initiative.id,
              inputContext: { initiative, pages },
            });
          } catch {
            // Fire-and-forget — don't fail initiative creation if agent spawn fails
          }
        }

        return initiative;
      }),

    listInitiatives: publicProcedure
      .input(z.object({
        status: z.enum(['active', 'completed', 'archived', 'pending_review']).optional(),
        projectId: z.string().min(1).optional(),
      }).optional())
      .query(async ({ ctx, input }) => {
        const repo = requireInitiativeRepository(ctx);

        let initiatives;
        if (input?.projectId) {
          const all = await repo.findByProjectId(input.projectId);
          initiatives = input.status
            ? all.filter(i => i.status === input.status)
            : all;
        } else {
          initiatives = input?.status
            ? await repo.findByStatus(input.status)
            : await repo.findAll();
        }

        // Fetch active architect agents once for all initiatives
        const ARCHITECT_MODES = ['discuss', 'plan', 'detail', 'refine'];
        const allAgents = ctx.agentManager ? await ctx.agentManager.list() : [];
        const activeArchitectAgents = allAgents
          .filter(a =>
            ARCHITECT_MODES.includes(a.mode ?? '')
            && (a.status === 'running' || a.status === 'waiting_for_input')
            && !a.userDismissedAt,
          )
          .map(a => ({ initiativeId: a.initiativeId ?? '', mode: a.mode ?? '', status: a.status }));

        if (ctx.phaseRepository) {
          const phaseRepo = ctx.phaseRepository;
          return Promise.all(initiatives.map(async (init) => {
            const phases = await phaseRepo.findByInitiativeId(init.id);
            return { ...init, activity: deriveInitiativeActivity(init, phases, activeArchitectAgents) };
          }));
        }

        return initiatives.map(init => ({
          ...init,
          activity: deriveInitiativeActivity(init, [], activeArchitectAgents),
        }));
      }),

    getInitiative: publicProcedure
      .input(z.object({ id: z.string().min(1) }))
      .query(async ({ ctx, input }) => {
        const repo = requireInitiativeRepository(ctx);
        const initiative = await repo.findById(input.id);
        if (!initiative) {
          throw new TRPCError({
            code: 'NOT_FOUND',
            message: `Initiative '${input.id}' not found`,
          });
        }

        let projects: Array<{ id: string; name: string; url: string }> = [];
        if (ctx.projectRepository) {
          const fullProjects = await ctx.projectRepository.findProjectsByInitiativeId(input.id);
          projects = fullProjects.map((p) => ({ id: p.id, name: p.name, url: p.url }));
        }

        let branchLocked = false;
        if (ctx.taskRepository) {
          const tasks = await ctx.taskRepository.findByInitiativeId(input.id);
          branchLocked = tasks.some((t) => t.status !== 'pending');
        }

        return { ...initiative, projects, branchLocked };
      }),

    updateInitiative: publicProcedure
      .input(z.object({
        id: z.string().min(1),
        name: z.string().min(1).optional(),
        status: z.enum(['active', 'completed', 'archived', 'pending_review']).optional(),
      }))
      .mutation(async ({ ctx, input }) => {
        const repo = requireInitiativeRepository(ctx);
        const { id, ...data } = input;
        return repo.update(id, data);
      }),

    deleteInitiative: publicProcedure
      .input(z.object({ id: z.string().min(1) }))
      .mutation(async ({ ctx, input }) => {
        const repo = requireInitiativeRepository(ctx);
        await repo.delete(input.id);
        return { success: true };
      }),

    updateInitiativeConfig: publicProcedure
      .input(z.object({
        initiativeId: z.string().min(1),
        executionMode: z.enum(['yolo', 'review_per_phase']).optional(),
        branch: z.string().nullable().optional(),
      }))
      .mutation(async ({ ctx, input }) => {
        const repo = requireInitiativeRepository(ctx);
        const { initiativeId, ...data } = input;

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

        // Prevent branch changes once work has started
        if (data.branch !== undefined && ctx.taskRepository) {
          const tasks = await ctx.taskRepository.findByInitiativeId(initiativeId);
          const hasStarted = tasks.some((t) => t.status !== 'pending');
          if (hasStarted) {
            throw new TRPCError({
              code: 'PRECONDITION_FAILED',
              message: 'Cannot change branch after work has started',
            });
          }
        }

        return repo.update(initiativeId, data);
      }),

    getInitiativeReviewDiff: publicProcedure
      .input(z.object({ initiativeId: z.string().min(1) }))
      .query(async ({ ctx, input }) => {
        const initiativeRepo = requireInitiativeRepository(ctx);
        const projectRepo = requireProjectRepository(ctx);
        const branchManager = requireBranchManager(ctx);

        const initiative = await initiativeRepo.findById(input.initiativeId);
        if (!initiative) {
          throw new TRPCError({ code: 'NOT_FOUND', message: `Initiative '${input.initiativeId}' not found` });
        }
        if (initiative.status !== 'pending_review') {
          throw new TRPCError({ code: 'BAD_REQUEST', message: `Initiative is not pending review (status: ${initiative.status})` });
        }
        if (!initiative.branch) {
          throw new TRPCError({ code: 'BAD_REQUEST', message: 'Initiative has no branch configured' });
        }

        const projects = await projectRepo.findProjectsByInitiativeId(input.initiativeId);
        let rawDiff = '';

        for (const project of projects) {
          const clonePath = await ensureProjectClone(project, ctx.workspaceRoot!);
          const diff = await branchManager.diffBranches(clonePath, project.defaultBranch, initiative.branch);
          if (diff) rawDiff += diff + '\n';
        }

        return {
          initiativeName: initiative.name,
          sourceBranch: initiative.branch,
          targetBranch: projects[0]?.defaultBranch ?? 'main',
          rawDiff,
        };
      }),

    getInitiativeReviewCommits: publicProcedure
      .input(z.object({ initiativeId: z.string().min(1) }))
      .query(async ({ ctx, input }) => {
        const initiativeRepo = requireInitiativeRepository(ctx);
        const projectRepo = requireProjectRepository(ctx);
        const branchManager = requireBranchManager(ctx);

        const initiative = await initiativeRepo.findById(input.initiativeId);
        if (!initiative) {
          throw new TRPCError({ code: 'NOT_FOUND', message: `Initiative '${input.initiativeId}' not found` });
        }
        if (initiative.status !== 'pending_review') {
          throw new TRPCError({ code: 'BAD_REQUEST', message: `Initiative is not pending review (status: ${initiative.status})` });
        }
        if (!initiative.branch) {
          throw new TRPCError({ code: 'BAD_REQUEST', message: 'Initiative has no branch configured' });
        }

        const projects = await projectRepo.findProjectsByInitiativeId(input.initiativeId);
        const allCommits: Array<{ hash: string; shortHash: string; message: string; author: string; date: string; filesChanged: number; insertions: number; deletions: number }> = [];

        for (const project of projects) {
          const clonePath = await ensureProjectClone(project, ctx.workspaceRoot!);
          const commits = await branchManager.listCommits(clonePath, project.defaultBranch, initiative.branch);
          allCommits.push(...commits);
        }

        return {
          commits: allCommits,
          sourceBranch: initiative.branch,
          targetBranch: projects[0]?.defaultBranch ?? 'main',
        };
      }),

    getInitiativeCommitDiff: publicProcedure
      .input(z.object({ initiativeId: z.string().min(1), commitHash: z.string().min(1) }))
      .query(async ({ ctx, input }) => {
        const projectRepo = requireProjectRepository(ctx);
        const branchManager = requireBranchManager(ctx);

        const projects = await projectRepo.findProjectsByInitiativeId(input.initiativeId);
        let rawDiff = '';

        for (const project of projects) {
          const clonePath = await ensureProjectClone(project, ctx.workspaceRoot!);
          try {
            const diff = await branchManager.diffCommit(clonePath, input.commitHash);
            if (diff) rawDiff += diff + '\n';
          } catch {
            // commit not in this project clone
          }
        }

        return { rawDiff };
      }),

    approveInitiativeReview: publicProcedure
      .input(z.object({
        initiativeId: z.string().min(1),
        strategy: z.enum(['push_branch', 'merge_and_push']),
      }))
      .mutation(async ({ ctx, input }) => {
        const orchestrator = requireExecutionOrchestrator(ctx);
        await orchestrator.approveInitiative(input.initiativeId, input.strategy);
        return { success: true };
      }),

    requestInitiativeChanges: publicProcedure
      .input(z.object({
        initiativeId: z.string().min(1),
        summary: z.string().trim().min(1),
      }))
      .mutation(async ({ ctx, input }) => {
        const orchestrator = requireExecutionOrchestrator(ctx);
        const result = await orchestrator.requestChangesOnInitiative(
          input.initiativeId,
          input.summary,
        );
        return { success: true, taskId: result.taskId };
      }),

    checkInitiativeMergeability: publicProcedure
      .input(z.object({ initiativeId: z.string().min(1) }))
      .query(async ({ ctx, input }) => {
        const initiativeRepo = requireInitiativeRepository(ctx);
        const projectRepo = requireProjectRepository(ctx);
        const branchManager = requireBranchManager(ctx);

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

        const projects = await projectRepo.findProjectsByInitiativeId(input.initiativeId);
        const allConflicts: string[] = [];
        let mergeable = true;

        for (const project of projects) {
          const clonePath = await ensureProjectClone(project, ctx.workspaceRoot!);
          const result = await branchManager.checkMergeability(clonePath, initiative.branch, project.defaultBranch);
          if (!result.mergeable) {
            mergeable = false;
            if (result.conflicts) allConflicts.push(...result.conflicts);
          }
        }

        return {
          mergeable,
          conflictFiles: allConflicts,
          targetBranch: projects[0]?.defaultBranch ?? 'main',
        };
      }),

    spawnConflictResolutionAgent: publicProcedure
      .input(z.object({
        initiativeId: z.string().min(1),
        provider: z.string().optional(),
      }))
      .mutation(async ({ ctx, input }) => {
        const agentManager = requireAgentManager(ctx);
        const initiativeRepo = requireInitiativeRepository(ctx);
        const projectRepo = requireProjectRepository(ctx);
        const taskRepo = requireTaskRepository(ctx);
        const branchManager = requireBranchManager(ctx);

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

        const projects = await projectRepo.findProjectsByInitiativeId(input.initiativeId);
        if (projects.length === 0) {
          throw new TRPCError({ code: 'BAD_REQUEST', message: 'Initiative has no linked projects' });
        }

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

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

        // Re-check mergeability to get current conflict list
        const project = projects[0];
        const clonePath = await ensureProjectClone(project, ctx.workspaceRoot!);
        const mergeCheck = await branchManager.checkMergeability(clonePath, initiative.branch, project.defaultBranch);
        if (mergeCheck.mergeable) {
          throw new TRPCError({ code: 'BAD_REQUEST', message: 'No merge conflicts detected — merge is clean' });
        }

        const conflicts = mergeCheck.conflicts ?? [];
        const targetBranch = project.defaultBranch;

        // Create task
        const task = await taskRepo.create({
          initiativeId: input.initiativeId,
          name: `Resolve conflicts: ${initiative.name}`,
          description: buildConflictResolutionDescription(initiative.branch, targetBranch, conflicts),
          category: 'merge',
          status: 'in_progress',
        });

        // Spawn agent on a unique temp branch based off the initiative branch.
        // Using initiative.branch directly as branchName would cause SimpleGitWorktreeManager.create()
        // to run `git branch -f <branch> <base>`, force-resetting the initiative branch.
        const tempBranch = `${initiative.branch}-conflict-${Date.now()}`;
        const prompt = buildConflictResolutionPrompt(initiative.branch, targetBranch, conflicts);
        return agentManager.spawn({
          name: `conflict-${Date.now()}`,
          taskId: task.id,
          prompt,
          mode: 'execute',
          provider: input.provider,
          initiativeId: input.initiativeId,
          baseBranch: initiative.branch,
          branchName: tempBranch,
        });
      }),
  };
}
