import { z } from 'zod'
import { TRPCError } from '@trpc/server'
import { router, protectedProcedure, adminProcedure } from '../trpc'
import { logAudit } from '@/server/utils/audit'
import { sendStyledNotificationEmail, getEmailPreviewHtml } from '@/lib/email'
import { sendBatchNotifications } from '../services/notification-sender'
import type { NotificationItem } from '../services/notification-sender'

export const messageRouter = router({
  /**
   * Send a message to recipients.
   * Resolves recipient list based on recipientType and delivers via specified channels.
   */
  send: adminProcedure
    .input(
      z.object({
        recipientType: z.enum(['USER', 'ROLE', 'ROUND_JURY', 'ROUND_APPLICANTS', 'PROGRAM_TEAM', 'PROJECT_TEAM', 'ALL']),
        recipientFilter: z.any().optional(),
        roundId: z.string().optional(),
        roundIds: z.array(z.string()).optional(),
        excludeStates: z.array(z.string()).optional(),
        subject: z.string().min(1).max(500),
        body: z.string().min(1),
        deliveryChannels: z.array(z.string()).min(1),
        scheduledAt: z.string().datetime().optional(),
        templateId: z.string().optional(),
        linkType: z.enum(['NONE', 'MESSAGES', 'LOGIN', 'INVITE']).default('MESSAGES'),
      })
    )
    .mutation(async ({ ctx, input }) => {
      // Normalize: prefer roundIds array, fall back to single roundId
      const effectiveRoundIds = input.roundIds?.length
        ? input.roundIds
        : input.roundId
          ? [input.roundId]
          : []

      // Resolve recipients based on type (union across all selected rounds)
      const recipientUserIds = await resolveRecipientsMultiRound(
        ctx.prisma,
        input.recipientType,
        input.recipientFilter,
        effectiveRoundIds,
        input.excludeStates
      )

      if (recipientUserIds.length === 0) {
        throw new TRPCError({
          code: 'BAD_REQUEST',
          message: 'No recipients found for the given criteria',
        })
      }

      const isScheduled = !!input.scheduledAt
      const now = new Date()

      // Create message
      const message = await ctx.prisma.message.create({
        data: {
          senderId: ctx.user.id,
          recipientType: input.recipientType,
          recipientFilter: input.recipientFilter ?? undefined,
          roundId: effectiveRoundIds[0] ?? null,
          metadata: effectiveRoundIds.length > 1 ? { roundIds: effectiveRoundIds } : undefined,
          templateId: input.templateId,
          subject: input.subject,
          body: input.body,
          deliveryChannels: input.deliveryChannels,
          scheduledAt: input.scheduledAt ? new Date(input.scheduledAt) : undefined,
          sentAt: isScheduled ? undefined : now,
          recipients: {
            create: recipientUserIds.flatMap((userId) =>
              input.deliveryChannels.map((channel) => ({
                userId,
                channel,
              }))
            ),
          },
        },
        include: {
          recipients: true,
        },
      })

      // If not scheduled, deliver immediately for EMAIL channel (batched to avoid overloading SMTP)
      if (!isScheduled && input.deliveryChannels.includes('EMAIL')) {
        const users = await ctx.prisma.user.findMany({
          where: { id: { in: recipientUserIds } },
          select: {
            id: true, name: true, email: true, passwordHash: true, inviteToken: true,
            teamMemberships: {
              select: { project: { select: { title: true } } },
              take: 1,
            },
          },
        })

        // Fetch round & program context for template variable substitution
        let roundName = ''
        let programName = ''
        let deadline = ''
        if (effectiveRoundIds.length > 0) {
          const rounds = await ctx.prisma.round.findMany({
            where: { id: { in: effectiveRoundIds } },
            select: {
              name: true,
              windowCloseAt: true,
              competition: {
                select: {
                  program: { select: { name: true } },
                },
              },
            },
          })
          if (rounds.length > 0) {
            roundName = rounds.map((r) => r.name).join(', ')
            programName = rounds[0].competition?.program?.name ?? ''
            // Use the earliest upcoming deadline across selected rounds
            const deadlines = rounds
              .map((r) => r.windowCloseAt)
              .filter((d): d is Date => d !== null)
              .sort((a, b) => a.getTime() - b.getTime())
            if (deadlines.length > 0) {
              deadline = deadlines[0].toLocaleDateString('en-GB', {
                day: 'numeric', month: 'long', year: 'numeric',
              })
            }
          }
        }

        /** Substitute template variables in a string for a specific user */
        function substituteVariables(
          text: string,
          user: { name: string | null; teamMemberships: { project: { title: string } }[] }
        ): string {
          return text
            .replace(/\{\{userName\}\}/g, user.name || '')
            .replace(/\{\{projectName\}\}/g, user.teamMemberships[0]?.project?.title || '')
            .replace(/\{\{roundName\}\}/g, roundName)
            .replace(/\{\{programName\}\}/g, programName)
            .replace(/\{\{deadline\}\}/g, deadline)
        }

        const baseUrl = process.env.NEXTAUTH_URL || 'https://portal.monaco-opc.com'

        function getLinkUrl(user: { id: string; passwordHash: string | null; inviteToken: string | null }): string | undefined {
          switch (input.linkType) {
            case 'NONE':
              return undefined
            case 'LOGIN':
              return `${baseUrl}/login`
            case 'INVITE':
              // Users who haven't set a password yet — provide invite/accept link if token exists
              if (user.inviteToken && !user.passwordHash) {
                return `${baseUrl}/accept-invite?token=${user.inviteToken}`
              }
              // Already activated — just link to login
              return `${baseUrl}/login`
            case 'MESSAGES':
            default:
              return `${baseUrl}/messages`
          }
        }

        const items: NotificationItem[] = users.map((user) => ({
          email: user.email,
          name: user.name || '',
          type: 'MESSAGE',
          userId: user.id,
          context: {
            name: user.name || undefined,
            title: substituteVariables(input.subject, user),
            message: substituteVariables(input.body, user),
            linkUrl: getLinkUrl(user),
          },
        }))

        // Fire-and-forget: batch send in background so the mutation returns quickly
        sendBatchNotifications(items).then((result) => {
          console.log(`[Message] Batch ${result.batchId}: ${result.sent} sent, ${result.failed} failed`)
        }).catch((err) => {
          console.error('[Message] Batch send error:', err)
        })
      }

      try {
        await logAudit({
          prisma: ctx.prisma,
          userId: ctx.user.id,
          action: 'SEND_MESSAGE',
          entityType: 'Message',
          entityId: message.id,
          detailsJson: {
            recipientType: input.recipientType,
            recipientCount: recipientUserIds.length,
            channels: input.deliveryChannels,
            scheduled: isScheduled,
          },
        })
      } catch (err) {
        console.error('[Message] Audit log failed:', err)
      }

      return {
        ...message,
        recipientCount: recipientUserIds.length,
      }
    }),

  /**
   * Get the current user's inbox (messages sent to them).
   */
  inbox: protectedProcedure
    .input(
      z.object({
        page: z.number().int().min(1).default(1),
        pageSize: z.number().int().min(1).max(100).default(20),
      }).optional()
    )
    .query(async ({ ctx, input }) => {
      const page = input?.page ?? 1
      const pageSize = input?.pageSize ?? 20
      const skip = (page - 1) * pageSize

      const [items, total] = await Promise.all([
        ctx.prisma.messageRecipient.findMany({
          where: { userId: ctx.user.id },
          include: {
            message: {
              include: {
                sender: {
                  select: { id: true, name: true, email: true },
                },
              },
            },
          },
          orderBy: { message: { createdAt: 'desc' } },
          skip,
          take: pageSize,
        }),
        ctx.prisma.messageRecipient.count({
          where: { userId: ctx.user.id },
        }),
      ])

      return {
        items,
        total,
        page,
        pageSize,
        totalPages: Math.ceil(total / pageSize),
      }
    }),

  /**
   * Mark a message as read.
   */
  markRead: protectedProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const recipient = await ctx.prisma.messageRecipient.findUnique({
        where: { id: input.id },
      })

      if (!recipient || recipient.userId !== ctx.user.id) {
        throw new TRPCError({
          code: 'NOT_FOUND',
          message: 'Message not found',
        })
      }

      return ctx.prisma.messageRecipient.update({
        where: { id: input.id },
        data: {
          isRead: true,
          readAt: new Date(),
        },
      })
    }),

  /**
   * Get messages sent by the current admin user.
   */
  sent: adminProcedure
    .input(
      z.object({
        page: z.number().int().min(1).default(1),
        pageSize: z.number().int().min(1).max(100).default(20),
      }).optional()
    )
    .query(async ({ ctx, input }) => {
      const page = input?.page ?? 1
      const pageSize = input?.pageSize ?? 20
      const skip = (page - 1) * pageSize

      const [items, total] = await Promise.all([
        ctx.prisma.message.findMany({
          where: { senderId: ctx.user.id },
          include: {
            _count: { select: { recipients: true } },
          },
          orderBy: { createdAt: 'desc' },
          skip,
          take: pageSize,
        }),
        ctx.prisma.message.count({
          where: { senderId: ctx.user.id },
        }),
      ])

      return {
        items,
        total,
        page,
        pageSize,
        totalPages: Math.ceil(total / pageSize),
      }
    }),

  /**
   * Get unread message count for the current user.
   */
  getUnreadCount: protectedProcedure.query(async ({ ctx }) => {
    const count = await ctx.prisma.messageRecipient.count({
      where: {
        userId: ctx.user.id,
        isRead: false,
      },
    })
    return { count }
  }),

  // =========================================================================
  // Template procedures
  // =========================================================================

  /**
   * List all message templates.
   */
  listTemplates: adminProcedure
    .input(
      z.object({
        category: z.string().optional(),
        activeOnly: z.boolean().default(true),
      }).optional()
    )
    .query(async ({ ctx, input }) => {
      return ctx.prisma.messageTemplate.findMany({
        where: {
          ...(input?.category ? { category: input.category } : {}),
          ...(input?.activeOnly !== false ? { isActive: true } : {}),
        },
        orderBy: { createdAt: 'desc' },
      })
    }),

  /**
   * Create a message template.
   */
  createTemplate: adminProcedure
    .input(
      z.object({
        name: z.string().min(1).max(200),
        category: z.string().min(1).max(100),
        subject: z.string().min(1).max(500),
        body: z.string().min(1),
        variables: z.any().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const template = await ctx.prisma.messageTemplate.create({
        data: {
          name: input.name,
          category: input.category,
          subject: input.subject,
          body: input.body,
          variables: input.variables ?? undefined,
          createdBy: ctx.user.id,
        },
      })

      try {
        await logAudit({
          prisma: ctx.prisma,
          userId: ctx.user.id,
          action: 'CREATE_MESSAGE_TEMPLATE',
          entityType: 'MessageTemplate',
          entityId: template.id,
          detailsJson: { name: input.name, category: input.category },
        })
      } catch (err) {
        console.error('[Message] Audit log failed:', err)
      }

      return template
    }),

  /**
   * Update a message template.
   */
  updateTemplate: adminProcedure
    .input(
      z.object({
        id: z.string(),
        name: z.string().min(1).max(200).optional(),
        category: z.string().min(1).max(100).optional(),
        subject: z.string().min(1).max(500).optional(),
        body: z.string().min(1).optional(),
        variables: z.any().optional(),
        isActive: z.boolean().optional(),
      })
    )
    .mutation(async ({ ctx, input }) => {
      const { id, ...data } = input

      const template = await ctx.prisma.messageTemplate.update({
        where: { id },
        data: {
          ...(data.name !== undefined ? { name: data.name } : {}),
          ...(data.category !== undefined ? { category: data.category } : {}),
          ...(data.subject !== undefined ? { subject: data.subject } : {}),
          ...(data.body !== undefined ? { body: data.body } : {}),
          ...(data.variables !== undefined ? { variables: data.variables } : {}),
          ...(data.isActive !== undefined ? { isActive: data.isActive } : {}),
        },
      })

      try {
        await logAudit({
          prisma: ctx.prisma,
          userId: ctx.user.id,
          action: 'UPDATE_MESSAGE_TEMPLATE',
          entityType: 'MessageTemplate',
          entityId: id,
          detailsJson: { updatedFields: Object.keys(data) },
        })
      } catch (err) {
        console.error('[Message] Audit log failed:', err)
      }

      return template
    }),

  /**
   * Soft-delete a message template (set isActive=false).
   */
  deleteTemplate: adminProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const template = await ctx.prisma.messageTemplate.update({
        where: { id: input.id },
        data: { isActive: false },
      })

      try {
        await logAudit({
          prisma: ctx.prisma,
          userId: ctx.user.id,
          action: 'DELETE_MESSAGE_TEMPLATE',
          entityType: 'MessageTemplate',
          entityId: input.id,
        })
      } catch (err) {
        console.error('[Message] Audit log failed:', err)
      }

      return template
    }),

  /**
   * Preview styled email HTML for admin compose dialog.
   */
  previewEmail: adminProcedure
    .input(z.object({ subject: z.string(), body: z.string() }))
    .query(({ input }) => {
      return { html: getEmailPreviewHtml(input.subject, input.body) }
    }),

  /**
   * Preview recipient counts for a given recipient type + filters.
   * Returns project breakdown by state for ROUND_APPLICANTS, or total user count for others.
   */
  previewRecipients: adminProcedure
    .input(z.object({
      recipientType: z.enum(['USER', 'ROLE', 'ROUND_JURY', 'ROUND_APPLICANTS', 'PROGRAM_TEAM', 'PROJECT_TEAM', 'ALL']),
      recipientFilter: z.any().optional(),
      roundId: z.string().optional(),
      roundIds: z.array(z.string()).optional(),
      excludeStates: z.array(z.string()).optional(),
    }))
    .query(async ({ ctx, input }) => {
      const effectiveRoundIds = input.roundIds?.length
        ? input.roundIds
        : input.roundId
          ? [input.roundId]
          : []

      // For ROUND_APPLICANTS, return a breakdown by project state
      if (input.recipientType === 'ROUND_APPLICANTS' && effectiveRoundIds.length > 0) {
        const projectStates = await ctx.prisma.projectRoundState.findMany({
          where: { roundId: { in: effectiveRoundIds } },
          select: {
            state: true,
            projectId: true,
            roundId: true,
            project: {
              select: {
                submittedByUserId: true,
                teamMembers: { select: { userId: true } },
              },
            },
          },
        })

        // Count projects per state
        const stateBreakdown: Record<string, number> = {}
        for (const ps of projectStates) {
          stateBreakdown[ps.state] = (stateBreakdown[ps.state] || 0) + 1
        }

        // Compute total unique users respecting exclusions
        const excludeSet = new Set(input.excludeStates ?? [])
        const includedUserIds = new Set<string>()
        for (const ps of projectStates) {
          if (excludeSet.has(ps.state)) continue
          if (ps.project.submittedByUserId) includedUserIds.add(ps.project.submittedByUserId)
          for (const tm of ps.project.teamMembers) includedUserIds.add(tm.userId)
        }

        return {
          totalProjects: projectStates.length,
          totalApplicants: includedUserIds.size,
          stateBreakdown,
        }
      }

      // For other recipient types, just count resolved users
      const userIds = await resolveRecipientsMultiRound(
        ctx.prisma,
        input.recipientType,
        input.recipientFilter,
        effectiveRoundIds,
        input.excludeStates
      )

      return {
        totalProjects: 0,
        totalApplicants: userIds.length,
        stateBreakdown: {} as Record<string, number>,
      }
    }),

  /**
   * Get detailed recipient list with names and project info.
   * Used for the expandable recipient breakdown in the compose sidebar.
   */
  listRecipientDetails: adminProcedure
    .input(z.object({
      recipientType: z.enum(['USER', 'ROLE', 'ROUND_JURY', 'ROUND_APPLICANTS', 'PROGRAM_TEAM', 'PROJECT_TEAM', 'ALL']),
      recipientFilter: z.any().optional(),
      roundId: z.string().optional(),
      roundIds: z.array(z.string()).optional(),
      excludeStates: z.array(z.string()).optional(),
    }))
    .query(async ({ ctx, input }) => {
      const effectiveRoundIds = input.roundIds?.length
        ? input.roundIds
        : input.roundId
          ? [input.roundId]
          : []

      // For ROUND_APPLICANTS, return users grouped by project (and round if multi-round)
      if (input.recipientType === 'ROUND_APPLICANTS' && effectiveRoundIds.length > 0) {
        const stateWhere: Record<string, unknown> = { roundId: { in: effectiveRoundIds } }
        if (input.excludeStates && input.excludeStates.length > 0) {
          stateWhere.state = { notIn: input.excludeStates }
        }
        const projectStates = await ctx.prisma.projectRoundState.findMany({
          where: stateWhere,
          select: {
            state: true,
            roundId: true,
            round: { select: { id: true, name: true, competition: { select: { name: true } } } },
            project: {
              select: {
                id: true,
                title: true,
                submittedBy: { select: { id: true, name: true, email: true } },
                teamMembers: {
                  select: { user: { select: { id: true, name: true, email: true } } },
                },
              },
            },
          },
        })
        return {
          type: 'projects' as const,
          projects: projectStates.map((ps) => ({
            id: ps.project.id,
            title: ps.project.title,
            state: ps.state,
            roundId: ps.roundId,
            roundName: ps.round ? `${ps.round.competition?.name ? ps.round.competition.name + ' - ' : ''}${ps.round.name}` : undefined,
            members: [
              ...(ps.project.submittedBy ? [ps.project.submittedBy] : []),
              ...ps.project.teamMembers
                .map((tm) => tm.user)
                .filter((u) => u.id !== ps.project.submittedBy?.id),
            ],
          })),
          users: [],
        }
      }

      // For ROUND_JURY, return users grouped by their assignments (and round if multi-round)
      if (input.recipientType === 'ROUND_JURY' && effectiveRoundIds.length > 0) {
        const assignments = await ctx.prisma.assignment.findMany({
          where: { roundId: { in: effectiveRoundIds } },
          select: {
            roundId: true,
            round: { select: { id: true, name: true, competition: { select: { name: true } } } },
            user: { select: { id: true, name: true, email: true } },
            project: { select: { id: true, title: true } },
          },
        })
        // Group by user
        const userMap = new Map<string, {
          user: { id: string; name: string | null; email: string };
          projects: { id: string; title: string }[];
          rounds: Set<string>;
          roundNames: string[];
        }>()
        for (const a of assignments) {
          const existing = userMap.get(a.user.id)
          const roundLabel = a.round ? `${a.round.competition?.name ? a.round.competition.name + ' - ' : ''}${a.round.name}` : a.roundId
          if (existing) {
            existing.projects.push(a.project)
            if (!existing.rounds.has(a.roundId)) {
              existing.rounds.add(a.roundId)
              existing.roundNames.push(roundLabel)
            }
          } else {
            userMap.set(a.user.id, {
              user: a.user,
              projects: [a.project],
              rounds: new Set([a.roundId]),
              roundNames: [roundLabel],
            })
          }
        }
        return {
          type: 'jurors' as const,
          projects: [],
          users: Array.from(userMap.values()).map((entry) => ({
            ...entry.user,
            projectCount: entry.projects.length,
            projectNames: entry.projects.map((p) => p.title).slice(0, 5),
            roundNames: entry.roundNames,
          })),
        }
      }

      // For all other types, just return the user list
      const userIds = await resolveRecipientsMultiRound(
        ctx.prisma,
        input.recipientType,
        input.recipientFilter,
        effectiveRoundIds,
        input.excludeStates
      )
      if (userIds.length === 0) return { type: 'users' as const, projects: [], users: [] }

      const users = await ctx.prisma.user.findMany({
        where: { id: { in: userIds } },
        select: {
          id: true,
          name: true,
          email: true,
          role: true,
          teamMemberships: {
            select: { project: { select: { id: true, title: true } } },
            take: 1,
          },
        },
      })

      return {
        type: 'users' as const,
        projects: [],
        users: users.map((u) => ({
          id: u.id,
          name: u.name,
          email: u.email,
          role: u.role,
          projectName: u.teamMemberships[0]?.project?.title ?? null,
        })),
      }
    }),

  /**
   * Send a test email to the currently logged-in admin.
   */
  sendTest: adminProcedure
    .input(z.object({ subject: z.string(), body: z.string() }))
    .mutation(async ({ ctx, input }) => {
      const userName = ctx.user.name || ''
      /** Substitute template variables with admin name + placeholder values for test emails */
      function substituteTestVariables(text: string): string {
        return text
          .replace(/\{\{userName\}\}/g, userName)
          .replace(/\{\{projectName\}\}/g, '[Project Name]')
          .replace(/\{\{roundName\}\}/g, '[Round Name]')
          .replace(/\{\{programName\}\}/g, '[Program Name]')
          .replace(/\{\{deadline\}\}/g, '[Deadline]')
      }

      await sendStyledNotificationEmail(
        ctx.user.email,
        userName,
        'MESSAGE',
        {
          title: substituteTestVariables(input.subject),
          message: substituteTestVariables(input.body),
          linkUrl: '/admin/messages',
        }
      )
      return { sent: true, to: ctx.user.email }
    }),
})

// =============================================================================
// Helper: Resolve recipient user IDs based on recipientType (multi-round)
// =============================================================================

type PrismaClient = Parameters<Parameters<typeof adminProcedure.mutation>[0]>[0]['ctx']['prisma']

async function resolveRecipientsMultiRound(
  prisma: PrismaClient,
  recipientType: string,
  recipientFilter: unknown,
  roundIds: string[],
  excludeStates?: string[]
): Promise<string[]> {
  // For round-based types, union across all selected rounds
  if ((recipientType === 'ROUND_JURY' || recipientType === 'ROUND_APPLICANTS') && roundIds.length > 0) {
    const allUserIds = new Set<string>()
    for (const rid of roundIds) {
      const ids = await resolveRecipients(prisma, recipientType, recipientFilter, rid, excludeStates)
      for (const id of ids) allUserIds.add(id)
    }
    return [...allUserIds]
  }
  // For non-round types, delegate to single-round resolver
  return resolveRecipients(prisma, recipientType, recipientFilter, roundIds[0], excludeStates)
}

async function resolveRecipients(
  prisma: PrismaClient,
  recipientType: string,
  recipientFilter: unknown,
  roundId?: string,
  excludeStates?: string[]
): Promise<string[]> {
  const filter = recipientFilter as Record<string, unknown> | undefined

  switch (recipientType) {
    case 'USER': {
      const userId = filter?.userId as string
      if (!userId) return []
      const user = await prisma.user.findUnique({
        where: { id: userId },
        select: { id: true },
      })
      return user ? [user.id] : []
    }

    case 'ROLE': {
      const role = filter?.role as string
      if (!role) return []
      const users = await prisma.user.findMany({
        where: { role: role as any, status: 'ACTIVE' },
        select: { id: true },
      })
      return users.map((u) => u.id)
    }

    case 'ROUND_JURY': {
      const targetRoundId = roundId || (filter?.roundId as string)
      if (!targetRoundId) return []
      const assignments = await prisma.assignment.findMany({
        where: { roundId: targetRoundId },
        select: { userId: true },
        distinct: ['userId'],
      })
      return assignments.map((a) => a.userId)
    }

    case 'ROUND_APPLICANTS': {
      const targetRoundId = roundId || (filter?.roundId as string)
      if (!targetRoundId) return []
      // Get all projects in this round, optionally excluding certain states
      const stateWhere: Record<string, unknown> = { roundId: targetRoundId }
      if (excludeStates && excludeStates.length > 0) {
        stateWhere.state = { notIn: excludeStates }
      }
      const projectStates = await prisma.projectRoundState.findMany({
        where: stateWhere,
        select: { projectId: true },
      })
      const projectIds = projectStates.map((ps) => ps.projectId)
      if (projectIds.length === 0) return []
      // Get team members + submittedByUserId
      const [teamMembers, projects] = await Promise.all([
        prisma.teamMember.findMany({
          where: { projectId: { in: projectIds } },
          select: { userId: true },
        }),
        prisma.project.findMany({
          where: { id: { in: projectIds } },
          select: { submittedByUserId: true },
        }),
      ])
      const userIds = new Set<string>()
      for (const tm of teamMembers) userIds.add(tm.userId)
      for (const p of projects) {
        if (p.submittedByUserId) userIds.add(p.submittedByUserId)
      }
      return [...userIds]
    }

    case 'PROGRAM_TEAM': {
      const programId = filter?.programId as string
      if (!programId) return []
      const projects = await prisma.project.findMany({
        where: { programId },
        select: { submittedByUserId: true },
      })
      const ids = new Set(projects.map((p) => p.submittedByUserId).filter(Boolean) as string[])
      return [...ids]
    }

    case 'PROJECT_TEAM': {
      const projectId = filter?.projectId as string
      if (!projectId) return []
      const [teamMembers, project] = await Promise.all([
        prisma.teamMember.findMany({
          where: { projectId },
          select: { userId: true },
        }),
        prisma.project.findUnique({
          where: { id: projectId },
          select: { submittedByUserId: true },
        }),
      ])
      const ids = new Set<string>()
      for (const tm of teamMembers) ids.add(tm.userId)
      if (project?.submittedByUserId) ids.add(project.submittedByUserId)
      return [...ids]
    }

    case 'ALL': {
      const users = await prisma.user.findMany({
        where: { status: 'ACTIVE' },
        select: { id: true },
      })
      return users.map((u) => u.id)
    }

    default:
      return []
  }
}
