import { Injectable } from '@nestjs/common';
import { PrismaService } from '@core/database/prisma/prisma.service';
import { AttendeeRole, MeetingStatus } from '@prisma/client';
import { MeetingAttendanceError } from '../errors/meeting-attendance.error';
import {
  getLegacySeriesIds,
  getLegacyPreferredDates,
  hasLegacy,
} from '../constants/legacy-series-mapping';

const ROLE_NAMES: Record<string, string> = {
  C_SUITE_ATTENDEE: 'C-suite attendee',
  LEVEL_1_REPORT_ATTENDEE: 'Level-1 report attendee',
  LEVEL_2_REPORT_ATTENDEE: 'Level-2 report attendee',
  MEETING_OPERATIONS: 'Meeting operations',
  REGULAR_ATTENDEE: 'Regular attendee',
  OPTIONAL_ATTENDEE: 'Optional attendee',
};

@Injectable()
export class ReportsService {
  constructor(private readonly prisma: PrismaService) {}

  async getAttendanceReport() {
    const meetings = await this.prisma.meeting.findMany({
      where: { NOT: { status: MeetingStatus.CANCELLED } },
      include: {
        requiredAttendees: {
          include: {
            user: {
              select: {
                id: true,
                displayName: true,
                email: true,
                departmentMemberships: {
                  where: { leftAt: null },
                  include: {
                    department: { select: { id: true, name: true, code: true } },
                    position: { select: { id: true, name: true, level: true } },
                  },
                },
              },
            },
          },
        },
        attendances: true,
      },
    });

    if (meetings.length === 0) {
      return {
        summary: {
          totalAttendances: 0,
          presentCount: 0,
          lateCount: 0,
          absentCount: 0,
          attendanceRate: 0,
        },
        departmentStats: [],
      };
    }

    const departmentStatsMap = new Map<string, { department: string; total: number; present: number }>();

    let totalAttendances = 0;
    let presentCount = 0;
    let lateCount = 0;
    let absentCount = 0;

    for (const meeting of meetings) {
      const attendanceMap = new Map<string, { status: string }>();
      meeting.attendances.forEach((attendance) => {
        if (attendance.userId) {
          attendanceMap.set(attendance.userId, { status: attendance.status });
        }
      });

      for (const required of meeting.requiredAttendees) {
        const department = this.getPrimaryDepartmentName(required.user) || 'Unassigned';
        const status = required.userId ? attendanceMap.get(required.userId)?.status || 'NOT_CHECKED_IN' : 'NOT_CHECKED_IN';

        totalAttendances += 1;

        if (status === 'ON_SITE' || status === 'ONLINE') {
          presentCount += 1;
        } else if (status === 'LATE') {
          lateCount += 1;
        } else {
          absentCount += 1;
        }

        if (!departmentStatsMap.has(department)) {
          departmentStatsMap.set(department, { department, total: 0, present: 0 });
        }
        const deptStat = departmentStatsMap.get(department)!;
        deptStat.total += 1;
        if (status === 'ON_SITE' || status === 'ONLINE') {
          deptStat.present += 1;
        }
      }
    }

    const departmentStats = Array.from(departmentStatsMap.values())
      .map((stat) => ({
        department: stat.department,
        total: stat.total,
        present: stat.present,
        attendanceRate: stat.total > 0 ? Math.round((stat.present / stat.total) * 100) : 0,
      }))
      .sort((a, b) => b.attendanceRate - a.attendanceRate);

    const attendanceRate = totalAttendances > 0 ? Math.round((presentCount / totalAttendances) * 100) : 0;

    return {
      summary: {
        totalAttendances,
        presentCount,
        lateCount,
        absentCount,
        attendanceRate,
      },
      departmentStats,
    };
  }

  async listSeriesOptions() {
    const list = await this.prisma.meetingSeries.findMany({
      where: { isActive: true },
      select: { id: true, title: true, startDate: true, endDate: true },
      orderBy: { createdAt: 'desc' },
    });
    return list.map((s) => ({
      id: s.id,
      title: s.title,
      startDate: s.startDate?.toISOString() ?? null,
      endDate: s.endDate?.toISOString() ?? null,
      hasLegacy: hasLegacy(s.id),
    }));
  }

  async getSeriesReport(query: {
    seriesId?: string;
    startDate?: string;
    endDate?: string;
    includeLegacy?: boolean;
  }) {
    if (!query.seriesId) {
      throw new MeetingAttendanceError(400, 'seriesId is required');
    }

    const series = (await this.prisma.meetingSeries.findUnique({
      where: { id: query.seriesId },
      select: {
        id: true,
        title: true,
        startDate: true,
        meetings: true,
        city: true,
        enforceCheckinMode: true,
      } as any,
    })) as any;

    if (!series) {
      throw new MeetingAttendanceError(404, 'Series not found');
    }

    const includeLegacy = query.includeLegacy !== false;
    const legacyIds = includeLegacy ? getLegacySeriesIds(query.seriesId) : [];
    const seriesIds = legacyIds.length > 0 ? [query.seriesId, ...legacyIds] : [query.seriesId];

    // 合并视图下以旧系列为准的日期（例如 FFAI 3/16），排除这些日期的新系列 meeting
    const legacyPreferredDates =
      legacyIds.length > 0 ? getLegacyPreferredDates(query.seriesId) : [];
    const preferLegacyExclusions = legacyPreferredDates.map((dateStr) => {
      const dayStart = new Date(`${dateStr}T00:00:00.000Z`);
      const dayEnd = new Date(dayStart.getTime() + 24 * 60 * 60 * 1000);
      return {
        NOT: {
          seriesId: query.seriesId,
          startTime: { gte: dayStart.toISOString(), lt: dayEnd.toISOString() },
        },
      };
    });

    const whereConditions: any = {
      seriesId: { in: seriesIds },
      AND: [{ NOT: { status: MeetingStatus.CANCELLED } }, ...preferLegacyExclusions],
    };

    if (query.startDate) {
      const startDateTime = new Date(`${query.startDate}T00:00:00.000`);
      whereConditions.startTime = {
        ...(whereConditions.startTime ?? {}),
        gte: startDateTime.toISOString(),
      };
    }

    if (query.endDate) {
      const endDateTime = new Date(`${query.endDate}T23:59:59.999`);
      whereConditions.startTime = {
        ...(whereConditions.startTime ?? {}),
        lte: endDateTime.toISOString(),
      };
    }

    const meetings = (await this.prisma.meeting.findMany({
      where: whereConditions,
      include: {
        requiredAttendees: {
          include: {
            user: {
              select: {
                id: true,
                displayName: true,
                email: true,
                workCity: true,
                departmentMemberships: {
                  where: { leftAt: null },
                  include: {
                    department: { select: { id: true, name: true, code: true } },
                    position: { select: { id: true, name: true, level: true } },
                  },
                },
              } as any,
            },
          },
        },
        attendances: {
          include: {
            user: {
              select: {
                id: true,
                displayName: true,
                email: true,
                departmentMemberships: {
                  where: { leftAt: null },
                  include: {
                    department: { select: { id: true, name: true, code: true } },
                    position: { select: { id: true, name: true, level: true } },
                  },
                },
              },
            },
          },
        },
      },
      orderBy: { startTime: 'asc' },
    })) as any[];

    // v1.2 预取系列级参会人签到方式覆盖
    const seriesPreferences = await (this.prisma as any).meetingSeriesAttendeePreference.findMany({
      where: { seriesId: query.seriesId },
      select: { userId: true, defaultCheckinMode: true },
    });
    const seriesPrefMap = new Map<string, 'ON_SITE' | 'ONLINE'>(
      seriesPreferences.map((p: any) => [p.userId, p.defaultCheckinMode]),
    );

    // v1.2 系列级 adjustmentCount 累计（会议级 checkinMode 非空的场次数）
    const adjustmentCountMap = new Map<string, { count: number; meetingIds: string[] }>();

    if (meetings.length === 0) {
      return {
        series: {
          id: series.id,
          title: series.title,
          meetings: [],
          startDate: series.startDate?.toISOString() ?? null,
          effectiveStartTime: series.startDate?.toISOString() ?? null,
        },
        meetingRange: {
          startDate: query.startDate || null,
          endDate: query.endDate || null,
          actualCount: 0,
          firstMeeting: null,
          lastMeeting: null,
        },
        overallStats: { totalMeetings: 0, totalRequired: 0, totalAttended: 0, attendanceRate: 0, optionalTotal: 0, optionalAttended: 0 },
        statusDistribution: [
          { status: 'ON_SITE', name: 'On Site', count: 0, color: '#10B981' },
          { status: 'ONLINE', name: 'Online', count: 0, color: '#10B981' },
          { status: 'LATE', name: 'Late', count: 0, color: '#F59E0B' },
          { status: 'ABSENT', name: 'Absent', count: 0, color: '#EF4444' },
          { status: 'PTO', name: 'PTO', count: 0, color: '#06B6D4' },
          { status: 'BUSINESS_CONFLICT', name: 'Business Conflict', count: 0, color: '#06B6D4' },
          { status: 'NOT_CHECKED_IN', name: 'Not Checked In', count: 0, color: '#6B7280' },
        ],
        roleStats: [],
        personalStats: [],
        departmentStats: [],
        lowAttendanceRanking: [],
        hasLegacy: hasLegacy(query.seriesId),
        legacyIncluded: legacyIds as string[],
      };
    }

    const roleStatsMap = new Map<AttendeeRole, { role: AttendeeRole; roleName: string; total: number; attended: number }>();

    const personalStatsMap = new Map<
      string,
      {
        userId: string;
        userName: string;
        userEmail: string;
        userDepartment: string | null;
        role: AttendeeRole;
        totalMeetings: number;
        attended: number;
        statusCounts: {
          ON_SITE: number;
          ONLINE: number;
          LATE: number;
          ABSENT: number;
          PTO: number;
          BUSINESS_CONFLICT: number;
          NOT_CHECKED_IN: number;
        };
      }
    >();

    const departmentStatsMap = new Map<string, { department: string; total: number; attended: number }>();
    const statusStatsMap = new Map<string, number>();

    // 应到/实到口径：仅统计 REGULAR_ATTENDEE。OPTIONAL_ATTENDEE 单独计数，作为小标展示。
    // v1.3 之前 optional 一起算进总数，导致出勤率被稀释。
    let totalRequired = 0;
    let totalAttended = 0;
    let optionalTotal = 0;
    let optionalAttended = 0;

    for (const meeting of meetings as any[]) {
      const attendanceMap = new Map<string, { status: string }>();
      (meeting.attendances as any[]).forEach((attendance: any) => {
        if (attendance.userId) {
          attendanceMap.set(attendance.userId, { status: attendance.status });
        }
      });

      for (const required of meeting.requiredAttendees) {
        if (!required.userId || !required.user) continue;

        const userId = required.userId;
        const user = required.user;
        const role = required.role as AttendeeRole;
        const isOptional = role === AttendeeRole.OPTIONAL_ATTENDEE;
        const department = this.getPrimaryDepartmentName(user) || 'Unassigned';

        const attendance = attendanceMap.get(userId);
        const status = attendance?.status || 'NOT_CHECKED_IN';
        const isAttended = ['ON_SITE', 'ONLINE', 'LATE'].includes(status);

        if (isOptional) {
          optionalTotal += 1;
          if (isAttended) optionalAttended += 1;
        } else {
          totalRequired += 1;
          if (isAttended) totalAttended += 1;
          statusStatsMap.set(status, (statusStatsMap.get(status) || 0) + 1);
        }

        if (!roleStatsMap.has(role)) {
          roleStatsMap.set(role, { role, roleName: ROLE_NAMES[role] || role, total: 0, attended: 0 });
        }
        const roleStat = roleStatsMap.get(role)!;
        roleStat.total += 1;
        if (isAttended) roleStat.attended += 1;

        if (!personalStatsMap.has(userId)) {
          personalStatsMap.set(userId, {
            userId,
            userName: user.displayName,
            userEmail: user.email,
            userDepartment: this.getPrimaryDepartmentName(user),
            role,
            totalMeetings: 0,
            attended: 0,
            workCity: (user as any).workCity ?? null,
            statusCounts: {
              ON_SITE: 0,
              ONLINE: 0,
              LATE: 0,
              ABSENT: 0,
              PTO: 0,
              BUSINESS_CONFLICT: 0,
              NOT_CHECKED_IN: 0,
            },
          } as any);
        }
        const personalStat = personalStatsMap.get(userId)!;
        personalStat.totalMeetings += 1;
        if (isAttended) personalStat.attended += 1;

        if (status === 'ON_SITE') personalStat.statusCounts.ON_SITE += 1;
        else if (status === 'ONLINE') personalStat.statusCounts.ONLINE += 1;
        else if (status === 'LATE') personalStat.statusCounts.LATE += 1;
        else if (status === 'ABSENT') personalStat.statusCounts.ABSENT += 1;
        else if (status === 'PTO') personalStat.statusCounts.PTO += 1;
        else if (status === 'BUSINESS_CONFLICT') personalStat.statusCounts.BUSINESS_CONFLICT += 1;
        else if (status === 'NOT_CHECKED_IN') personalStat.statusCounts.NOT_CHECKED_IN += 1;

        // v1.2 累计 adjustmentCount：会议级 checkinMode 非空的场次
        if ((required as any).checkinMode) {
          const acc = adjustmentCountMap.get(userId) ?? { count: 0, meetingIds: [] };
          acc.count += 1;
          acc.meetingIds.push(meeting.id);
          adjustmentCountMap.set(userId, acc);
        }

        // 部门口径只统计 REGULAR_ATTENDEE，跟 totalRequired 保持一致
        if (!isOptional) {
          if (!departmentStatsMap.has(department)) {
            departmentStatsMap.set(department, { department, total: 0, attended: 0 });
          }
          const deptStat = departmentStatsMap.get(department)!;
          deptStat.total += 1;
          if (isAttended) deptStat.attended += 1;
        }
      }
    }

    const roleStats = Array.from(roleStatsMap.values())
      .map((stat) => ({
        role: stat.role,
        roleName: stat.roleName,
        total: stat.total,
        attended: stat.attended,
        attendanceRate: stat.total > 0 ? Math.round((stat.attended / stat.total) * 100) : 0,
      }))
      .sort((a, b) => b.attendanceRate - a.attendanceRate);

    const personalStats = Array.from(personalStatsMap.values())
      .map((stat) => {
        // v1.2 系列层级 allowedMode：seriesPref > cityDerive
        const seriesPref = seriesPrefMap.get(stat.userId);
        let allowedMode: 'ON_SITE' | 'ONLINE' | null;
        let allowedModeSource: 'SERIES_PREFERENCE' | 'CITY_DERIVED';
        if (seriesPref) {
          allowedMode = seriesPref;
          allowedModeSource = 'SERIES_PREFERENCE';
        } else {
          const userCity = (stat as any).workCity?.trim() || null;
          const seriesCity = series.city?.trim() || null;
          allowedMode = !userCity ? null : userCity === seriesCity ? 'ON_SITE' : 'ONLINE';
          allowedModeSource = 'CITY_DERIVED';
        }
        const adj = adjustmentCountMap.get(stat.userId);

        return {
          user: {
            id: stat.userId,
            displayName: stat.userName,
            email: stat.userEmail,
            department: stat.userDepartment,
          },
          role: stat.role,
          totalMeetings: stat.totalMeetings,
          attended: stat.attended,
          attendanceRate:
            stat.totalMeetings > 0 ? Math.round((stat.attended / stat.totalMeetings) * 100) : 0,
          statusCounts: stat.statusCounts,
          allowedMode,
          allowedModeSource,
          adjustmentCount: adj?.count ?? 0,
          adjustmentMeetingIds: adj?.meetingIds ?? [],
        };
      })
      .sort((a, b) => a.attendanceRate - b.attendanceRate);

    const departmentStats = Array.from(departmentStatsMap.values())
      .map((stat) => ({
        department: stat.department,
        total: stat.total,
        attended: stat.attended,
        attendanceRate: stat.total > 0 ? Math.round((stat.attended / stat.total) * 100) : 0,
      }))
      .sort((a, b) => b.attendanceRate - a.attendanceRate);

    // v1.3 lowAttendanceRanking 仅排 REGULAR_ATTENDEE：OPTIONAL_ATTENDEE 不计入应到/出勤率
    // 排在「低出勤率」榜单会误伤——他们本来就不要求来
    const rankableStats = personalStats.filter((stat) => stat.role !== AttendeeRole.OPTIONAL_ATTENDEE);
    let lowAttendanceRanking: Array<(typeof personalStats)[0] & { rank: number }> = [];
    if (rankableStats.length > 0) {
      const fifthFromLastIndex = Math.max(0, rankableStats.length - 5);
      const thresholdRate =
        rankableStats.length >= 5
          ? rankableStats[fifthFromLastIndex].attendanceRate
          : rankableStats[rankableStats.length - 1].attendanceRate;

      lowAttendanceRanking = rankableStats
        .filter((stat) => stat.attendanceRate < thresholdRate)
        .slice(0, 20)
        .map((stat, index) => ({
          rank: index + 1,
          user: stat.user,
          role: stat.role,
          totalMeetings: stat.totalMeetings,
          attended: stat.attended,
          attendanceRate: stat.attendanceRate,
          statusCounts: stat.statusCounts,
          // v1.2 新字段透传（低出勤率排名和个人详情的列保持一致）
          allowedMode: stat.allowedMode,
          allowedModeSource: stat.allowedModeSource,
          adjustmentCount: stat.adjustmentCount,
          adjustmentMeetingIds: stat.adjustmentMeetingIds,
        }));
    }

    const overallAttendanceRate = totalRequired > 0 ? Math.round((totalAttended / totalRequired) * 100) : 0;
    const statusDistribution = [
      { status: 'ON_SITE', name: 'On Site', count: statusStatsMap.get('ON_SITE') || 0, color: '#10B981' },
      { status: 'ONLINE', name: 'Online', count: statusStatsMap.get('ONLINE') || 0, color: '#10B981' },
      { status: 'LATE', name: 'Late', count: statusStatsMap.get('LATE') || 0, color: '#F59E0B' },
      { status: 'ABSENT', name: 'Absent', count: statusStatsMap.get('ABSENT') || 0, color: '#EF4444' },
      { status: 'PTO', name: 'PTO', count: statusStatsMap.get('PTO') || 0, color: '#06B6D4' },
      { status: 'BUSINESS_CONFLICT', name: 'Business Conflict', count: statusStatsMap.get('BUSINESS_CONFLICT') || 0, color: '#06B6D4' },
      { status: 'NOT_CHECKED_IN', name: 'Not Checked In', count: statusStatsMap.get('NOT_CHECKED_IN') || 0, color: '#6B7280' },
    ];

    return {
      series: {
        id: series.id,
        title: series.title,
        meetings: series.meetings,
        startDate: series.startDate?.toISOString() ?? null,
        effectiveStartTime:
          meetings[0]?.startTime?.toISOString() ?? series.startDate?.toISOString() ?? null,
      },
      meetingRange: {
        startDate: query.startDate || null,
        endDate: query.endDate || null,
        actualCount: meetings.length,
        firstMeeting:
          meetings.length > 0
            ? { title: meetings[0].title, startTime: meetings[0].startTime.toISOString() }
            : null,
        lastMeeting:
          meetings.length > 0
            ? { title: meetings[meetings.length - 1].title, startTime: meetings[meetings.length - 1].startTime.toISOString() }
            : null,
      },
      overallStats: {
        totalMeetings: meetings.length,
        totalRequired,
        totalAttended,
        attendanceRate: overallAttendanceRate,
        optionalTotal,
        optionalAttended,
      },
      // v1.2 系列级签到方式校验状态
      enforceCheckinMode: !!series.enforceCheckinMode,
      city: series.city ?? null,
      statusDistribution,
      roleStats,
      personalStats,
      departmentStats,
      lowAttendanceRanking,
      hasLegacy: hasLegacy(query.seriesId),
      legacyIncluded: legacyIds as string[],
    };
  }

  async getSingleMeetingReport(query: { meetingId?: string }) {
    if (!query.meetingId) {
      throw new MeetingAttendanceError(400, 'meetingId is required');
    }

    const meeting = (await this.prisma.meeting.findUnique({
      where: { id: query.meetingId },
      include: {
        requiredAttendees: {
          include: {
            user: {
              select: {
                id: true,
                displayName: true,
                email: true,
                workCity: true,
                departmentMemberships: {
                  where: { leftAt: null },
                  include: {
                    department: { select: { id: true, name: true, code: true } },
                    position: { select: { id: true, name: true, level: true } },
                  },
                },
              } as any,
            },
          },
        },
        attendances: {
          include: {
            user: {
              select: {
                id: true,
                displayName: true,
                email: true,
                departmentMemberships: {
                  where: { leftAt: null },
                  include: {
                    department: { select: { id: true, name: true, code: true } },
                    position: { select: { id: true, name: true, level: true } },
                  },
                },
              },
            },
          },
        },
      },
    })) as any;

    if (!meeting) {
      throw new MeetingAttendanceError(404, 'Meeting not found');
    }

    // v1.2 预取系列级参会人签到方式覆盖（当 meeting 属于某系列）
    const seriesPrefMapSingle = new Map<string, 'ON_SITE' | 'ONLINE'>();
    if (meeting.seriesId) {
      const rows = await (this.prisma as any).meetingSeriesAttendeePreference.findMany({
        where: { seriesId: meeting.seriesId },
        select: { userId: true, defaultCheckinMode: true },
      });
      for (const r of rows) seriesPrefMapSingle.set(r.userId, r.defaultCheckinMode);
    }

    const attendanceMap = new Map<string, { status: string; checkinTime: string | null }>();
    (meeting.attendances as any[]).forEach((attendance: any) => {
      if (attendance.userId) {
        attendanceMap.set(attendance.userId, {
          status: attendance.status,
          checkinTime: attendance.checkinTime?.toISOString() || null,
        });
      }
    });

    const roleStatsMap = new Map<AttendeeRole, { role: AttendeeRole; roleName: string; total: number; attended: number }>();
    const personalStatsMap = new Map<
      string,
      {
        userId: string;
        userName: string;
        userEmail: string;
        userDepartment: string | null;
        role: AttendeeRole;
        attended: number;
        actualStatus: string;
        statusCounts: {
          ON_SITE: number;
          ONLINE: number;
          LATE: number;
          ABSENT: number;
          PTO: number;
          BUSINESS_CONFLICT: number;
          NOT_CHECKED_IN: number;
        };
      }
    >();

    const departmentStatsMap = new Map<string, { department: string; total: number; attended: number }>();
    const statusStatsMap = new Map<string, number>();

    // 应到/实到口径：仅统计 REGULAR_ATTENDEE。OPTIONAL_ATTENDEE 单独计数，作为小标展示。
    let totalRequired = 0;
    let totalAttended = 0;
    let optionalTotal = 0;
    let optionalAttended = 0;

    for (const required of meeting.requiredAttendees) {
      if (!required.userId || !required.user) continue;

      const userId = required.userId;
      const user = required.user;
      const role = required.role as AttendeeRole;
      const isOptional = role === AttendeeRole.OPTIONAL_ATTENDEE;
      const department = this.getPrimaryDepartmentName(user) || 'Unassigned';

      const attendance = attendanceMap.get(userId);
      const status = attendance?.status || 'NOT_CHECKED_IN';
      const isAttended = ['ON_SITE', 'ONLINE', 'LATE'].includes(status);

      if (isOptional) {
        optionalTotal += 1;
        if (isAttended) optionalAttended += 1;
      } else {
        totalRequired += 1;
        if (isAttended) totalAttended += 1;
        statusStatsMap.set(status, (statusStatsMap.get(status) || 0) + 1);
      }

      if (!roleStatsMap.has(role)) {
        roleStatsMap.set(role, { role, roleName: ROLE_NAMES[role] || role, total: 0, attended: 0 });
      }
      const roleStat = roleStatsMap.get(role)!;
      roleStat.total += 1;
      if (isAttended) roleStat.attended += 1;

      if (!personalStatsMap.has(userId)) {
        personalStatsMap.set(userId, {
          userId,
          userName: user.displayName,
          userEmail: user.email,
          userDepartment: this.getPrimaryDepartmentName(user),
          role,
          attended: 0,
          actualStatus: status,
          workCity: (user as any).workCity ?? null,
          checkinModeOverride: (required as any).checkinMode ?? null,
          statusCounts: {
            ON_SITE: 0,
            ONLINE: 0,
            LATE: 0,
            ABSENT: 0,
            PTO: 0,
            BUSINESS_CONFLICT: 0,
            NOT_CHECKED_IN: 0,
          },
        } as any);
      }
      const personalStat = personalStatsMap.get(userId)!;
      personalStat.actualStatus = status;
      if (isAttended) personalStat.attended += 1;

      if (status === 'ON_SITE') personalStat.statusCounts.ON_SITE += 1;
      else if (status === 'ONLINE') personalStat.statusCounts.ONLINE += 1;
      else if (status === 'LATE') personalStat.statusCounts.LATE += 1;
      else if (status === 'ABSENT') personalStat.statusCounts.ABSENT += 1;
      else if (status === 'PTO') personalStat.statusCounts.PTO += 1;
      else if (status === 'BUSINESS_CONFLICT') personalStat.statusCounts.BUSINESS_CONFLICT += 1;
      else if (status === 'NOT_CHECKED_IN') personalStat.statusCounts.NOT_CHECKED_IN += 1;

      // 部门口径只统计 REGULAR_ATTENDEE，跟 totalRequired 保持一致
      if (!isOptional) {
        if (!departmentStatsMap.has(department)) {
          departmentStatsMap.set(department, { department, total: 0, attended: 0 });
        }
        const deptStat = departmentStatsMap.get(department)!;
        deptStat.total += 1;
        if (isAttended) deptStat.attended += 1;
      }
    }

    const roleStats = Array.from(roleStatsMap.values())
      .map((stat) => ({
        role: stat.role,
        roleName: stat.roleName,
        total: stat.total,
        attended: stat.attended,
        attendanceRate: stat.total > 0 ? Math.round((stat.attended / stat.total) * 100) : 0,
      }))
      .sort((a, b) => b.attendanceRate - a.attendanceRate);

    const personalStats = Array.from(personalStatsMap.values()).map((stat: any) => {
      // v1.2 三层 fallback：会议级 > 系列级 > 城市派生
      const override: 'ON_SITE' | 'ONLINE' | null = stat.checkinModeOverride;
      const seriesPref = seriesPrefMapSingle.get(stat.userId);
      const userCity = (stat.workCity || '').trim() || null;
      const meetingCity = meeting.city?.trim() || null;
      const defaultMode: 'ON_SITE' | 'ONLINE' | null = seriesPref
        ? seriesPref
        : !userCity
          ? null
          : userCity === meetingCity
            ? 'ON_SITE'
            : 'ONLINE';

      let allowedMode: 'ON_SITE' | 'ONLINE' | null;
      let allowedModeSource: 'MEETING_OVERRIDE' | 'SERIES_PREFERENCE' | 'CITY_DERIVED';
      if (override) {
        allowedMode = override;
        allowedModeSource = 'MEETING_OVERRIDE';
      } else if (seriesPref) {
        allowedMode = seriesPref;
        allowedModeSource = 'SERIES_PREFERENCE';
      } else {
        allowedMode = defaultMode;
        allowedModeSource = 'CITY_DERIVED';
      }

      return {
        user: {
          id: stat.userId,
          displayName: stat.userName,
          email: stat.userEmail,
          department: stat.userDepartment,
        },
        role: stat.role,
        attended: stat.attended,
        actualStatus: stat.actualStatus,
        statusCounts: stat.statusCounts,
        allowedMode,
        allowedModeSource,
        isOverridden: !!override,
        defaultMode: override ? defaultMode : allowedMode,
      };
    });

    const departmentStats = Array.from(departmentStatsMap.values())
      .map((stat) => ({
        department: stat.department,
        total: stat.total,
        attended: stat.attended,
        attendanceRate: stat.total > 0 ? Math.round((stat.attended / stat.total) * 100) : 0,
      }))
      .sort((a, b) => b.attendanceRate - a.attendanceRate);

    const statusDistribution = [
      { status: 'ON_SITE', name: 'On Site', count: statusStatsMap.get('ON_SITE') || 0, color: '#10B981' },
      { status: 'ONLINE', name: 'Online', count: statusStatsMap.get('ONLINE') || 0, color: '#10B981' },
      { status: 'LATE', name: 'Late', count: statusStatsMap.get('LATE') || 0, color: '#F59E0B' },
      { status: 'ABSENT', name: 'Absent', count: statusStatsMap.get('ABSENT') || 0, color: '#EF4444' },
      { status: 'PTO', name: 'PTO', count: statusStatsMap.get('PTO') || 0, color: '#06B6D4' },
      { status: 'BUSINESS_CONFLICT', name: 'Business Conflict', count: statusStatsMap.get('BUSINESS_CONFLICT') || 0, color: '#06B6D4' },
      { status: 'NOT_CHECKED_IN', name: 'Not Checked In', count: statusStatsMap.get('NOT_CHECKED_IN') || 0, color: '#6B7280' },
    ];

    return {
      meeting: {
        id: meeting.id,
        title: meeting.title,
        startTime: meeting.startTime.toISOString(),
        endTime: meeting.endTime.toISOString(),
        location: meeting.location,
        status: meeting.status,
      },
      overallStats: {
        totalRequired,
        totalAttended,
        attendanceRate: totalRequired > 0 ? Math.round((totalAttended / totalRequired) * 100) : 0,
        optionalTotal,
        optionalAttended,
      },
      // v1.2 会议级签到方式校验状态
      enforceCheckinMode: !!(meeting as any).enforceCheckinMode,
      city: (meeting as any).city ?? null,
      statusDistribution,
      roleStats,
      personalStats,
      departmentStats,
    };
  }

  private getPrimaryDepartmentName(user?: {
    departmentMemberships?: Array<{
      isPrimary?: boolean;
      department?: { name?: string | null } | null;
    }>;
  }) {
    if (!user?.departmentMemberships?.length) {
      return null;
    }
    const primary = user.departmentMemberships.find((membership) => membership.isPrimary) || user.departmentMemberships[0];
    return primary?.department?.name || null;
  }
}
