import { WorkflowRequest } from '@models/WorkflowRequest';
import { User } from '@models/User';
import { ApprovalLevel } from '@models/ApprovalLevel';
import { Participant } from '@models/Participant';
import { Document } from '@models/Document';
import { Form16aSubmission } from '@models/Form16aSubmission';
import '@models/index';
import { CreateWorkflowRequest, UpdateWorkflowRequest } from '../types/workflow.types';
import { generateRequestNumber, calculateTATDays } from '@utils/helpers';
import logger, { logWorkflowEvent, logWithContext } from '@utils/logger';
import { WorkflowStatus, ParticipantType, ApprovalStatus } from '../types/common.types';
import { Op, QueryTypes, literal } from 'sequelize';
import { sequelize } from '@config/database';
import fs from 'fs';
import path from 'path';
import dayjs from 'dayjs';
import { notificationService } from './notification.service';
import { activityService } from './activity.service';
import { tatSchedulerService } from './tatScheduler.service';
import { emitToRequestRoom } from '../realtime/socket';
import { sanitizeStrict, sanitizePermissive, isHtmlEmpty } from '../utils/sanitizer';
import { canViewForm16Submission } from './form16Permission.service';

export class WorkflowService {
  /**
   * Helper method to map activity type to user-friendly action label
   */
  private getActivityAction(type: string): string {
    const actionMap: Record<string, string> = {
      'created': 'Request Created',
      'assignment': 'Assigned',
      'approval': 'Approved',
      'rejection': 'Rejected',
      'status_change': 'Status Changed',
      'comment': 'Comment Added',
      'reminder': 'Reminder Sent',
      'document_added': 'Document Added',
      'sla_warning': 'SLA Warning'
    };
    return actionMap[type] || 'Activity';
  }

  /**
   * Add a new approver to an existing workflow
   * Auto-creates user from Okta/AD if not in database
   */
  async addApprover(requestId: string, email: string, addedBy: string): Promise<any> {
    try {
      const emailLower = email.toLowerCase();

      // Find or create user from AD
      let user = await User.findOne({ where: { email: emailLower } });
      if (!user) {
        logger.info(`[Workflow] User not found in DB, syncing from AD: ${emailLower}`);
        const { UserService } = await import('./user.service');
        const userService = new UserService();
        try {
          user = await userService.ensureUserExists({ email: emailLower }) as any;
        } catch (adError: any) {
          logger.error(`[Workflow] Failed to sync user from AD: ${emailLower}`, adError);
          throw new Error(`Approver email '${email}' not found in organization directory. Please verify the email address.`);
        }
      }

      const userId = (user as any).userId;
      const userName = (user as any).displayName || (user as any).email;

      // Check if user is already a participant
      const existing = await Participant.findOne({
        where: { requestId, userId }
      });

      if (existing) {
        throw new Error('User is already a participant in this request');
      }

      // Add as approver participant
      // APPROVERS: Can approve, download documents, and need action
      const participant = await Participant.create({
        requestId,
        userId,
        userEmail: email.toLowerCase(),
        userName,
        participantType: ParticipantType.APPROVER, // Differentiates from SPECTATOR in database
        canComment: true,
        canViewDocuments: true,
        canDownloadDocuments: true, // Approvers can download
        notificationEnabled: true,
        addedBy,
        isActive: true
      } as any);

      // Get workflow details for notification
      const workflow = await WorkflowRequest.findOne({ where: { requestId } });
      const requestNumber = (workflow as any)?.requestNumber;
      const title = (workflow as any)?.title;

      // Get the user who is adding the approver
      const addedByUser = await User.findByPk(addedBy);
      const addedByName = (addedByUser as any)?.displayName || (addedByUser as any)?.email || 'User';

      // Log activity
      await activityService.log({
        requestId,
        type: 'assignment',
        user: { userId: addedBy, name: addedByName },
        timestamp: new Date().toISOString(),
        action: 'Added new approver',
        details: `${userName} (${email}) has been added as an approver by ${addedByName}`
      });

      // Send notification to new approver (in-app, email, and web push)
      // APPROVER NOTIFICATION: Uses 'assignment' type to trigger approval request email
      // This differentiates from 'spectator_added' type used for spectators
      await notificationService.sendToUsers([userId], {
        title: 'New Request Assignment',
        body: `You have been added as an approver to request ${requestNumber}: ${title}`,
        requestId,
        requestNumber,
        url: `/request/${requestNumber}`,
        type: 'assignment', //: Differentiates from 'spectator_added' - triggers approval request email
        priority: 'HIGH',
        actionRequired: true // Approvers need to take action
      });

      logger.info(`[Workflow] Added approver ${email} to request ${requestId}`);
      return participant;
    } catch (error) {
      logger.error(`[Workflow] Failed to add approver:`, error);
      throw error;
    }
  }

  /**
   * Skip an approver level (initiator can skip non-responding approver)
   */
  async skipApprover(requestId: string, levelId: string, skipReason: string, skippedBy: string): Promise<any> {
    try {
      // Get the approval level
      const level = await ApprovalLevel.findOne({ where: { levelId } });
      if (!level) {
        throw new Error('Approval level not found');
      }

      // Verify it's skippable (not already approved/rejected/skipped)
      const currentStatus = (level as any).status;
      if (currentStatus === 'APPROVED' || currentStatus === 'REJECTED' || currentStatus === 'SKIPPED') {
        throw new Error(`Cannot skip approver - level is already ${currentStatus}`);
      }

      // Get workflow to verify current level
      const workflow = await WorkflowRequest.findOne({ where: { requestId } });
      if (!workflow) {
        throw new Error('Workflow not found');
      }

      const currentLevel = (workflow as any).currentLevel;
      const levelNumber = (level as any).levelNumber;

      // Only allow skipping current level (not future levels)
      if (levelNumber > currentLevel) {
        throw new Error('Cannot skip future approval levels');
      }

      // Block skip if workflow is paused - must resume first
      if ((workflow as any).isPaused || (workflow as any).status === 'PAUSED') {
        throw new Error('Cannot skip approver while workflow is paused. Please resume the workflow first before skipping.');
      }

      // Mark as skipped
      await level.update({
        status: ApprovalStatus.SKIPPED,
        levelEndTime: new Date(),
        actionDate: new Date()
      });

      // Update additional skip fields if migration was run
      try {
        await sequelize.query(`
          UPDATE approval_levels
          SET is_skipped = TRUE,
              skipped_at = NOW(),
              skipped_by = :skippedBy,
              skip_reason = :skipReason
          WHERE level_id = :levelId
        `, {
          replacements: { levelId, skippedBy, skipReason },
          type: QueryTypes.UPDATE
        });
      } catch (err) {
        logger.warn('[Workflow] is_skipped column not available (migration not run), using status only');
      }

      // Cancel TAT jobs for skipped level
      await tatSchedulerService.cancelTatJobs(requestId, levelId);

      // Move to next level
      const nextLevelNumber = levelNumber + 1;
      const nextLevel = await ApprovalLevel.findOne({
        where: { requestId, levelNumber: nextLevelNumber }
      });

      if (nextLevel) {
        // Check if next level is paused - if so, don't activate it
        if ((nextLevel as any).isPaused || (nextLevel as any).status === 'PAUSED') {
          logger.warn(`[Workflow] Cannot activate next level ${nextLevelNumber} - level is paused`);
          throw new Error('Cannot activate next level - the next approval level is currently paused. Please resume it first.');
        }

        const now = new Date();
        await nextLevel.update({
          status: ApprovalStatus.IN_PROGRESS,
          levelStartTime: now,
          tatStartTime: now
        });

        // Schedule TAT jobs for next level
        const workflowPriority = (workflow as any)?.priority || 'STANDARD';
        await tatSchedulerService.scheduleTatJobs(
          requestId,
          (nextLevel as any).levelId,
          (nextLevel as any).approverId,
          Number((nextLevel as any).tatHours),
          now,
          workflowPriority
        );

        // Update workflow current level
        await workflow.update({ currentLevel: nextLevelNumber });

        // Notify skipped approver (triggers email)
        await notificationService.sendToUsers([(level as any).approverId], {
          title: 'Approver Skipped',
          body: `You have been skipped in request ${(workflow as any).requestNumber}. The workflow has moved to the next approver.`,
          requestId,
          requestNumber: (workflow as any).requestNumber,
          url: `/request/${(workflow as any).requestNumber}`,
          type: 'approver_skipped',
          priority: 'MEDIUM',
          metadata: {
            skipReason: skipReason,
            skippedBy: skippedBy
          }
        });

        // Notify next approver
        await notificationService.sendToUsers([(nextLevel as any).approverId], {
          title: 'Request Escalated',
          body: `Previous approver was skipped. Request ${(workflow as any).requestNumber} is now awaiting your approval.`,
          requestId,
          requestNumber: (workflow as any).requestNumber,
          url: `/request/${(workflow as any).requestNumber}`,
          type: 'assignment',
          priority: 'HIGH',
          actionRequired: true
        });
      }

      // Get user who skipped
      const skipUser = await User.findByPk(skippedBy);
      const skipUserName = (skipUser as any)?.displayName || (skipUser as any)?.email || 'User';

      // Log activity
      await activityService.log({
        requestId,
        type: 'status_change',
        user: { userId: skippedBy, name: skipUserName },
        timestamp: new Date().toISOString(),
        action: 'Approver Skipped',
        details: `Level ${levelNumber} approver (${(level as any).approverName}) was skipped by ${skipUserName}. Reason: ${skipReason || 'Not provided'}`
      });

      logger.info(`[Workflow] Skipped approver at level ${levelNumber} for request ${requestId}`);

      // Emit real-time update to all users viewing this request
      const wfForEmit = await WorkflowRequest.findByPk(requestId);
      emitToRequestRoom(requestId, 'request:updated', {
        requestId,
        requestNumber: (wfForEmit as any)?.requestNumber,
        action: 'SKIP',
        levelNumber: levelNumber,
        timestamp: new Date().toISOString()
      });

      return level;
    } catch (error) {
      logger.error(`[Workflow] Failed to skip approver:`, error);
      throw error;
    }
  }

  /**
   * Add a new approver at specific level (with level shifting)
   * Auto-creates user from Okta/AD if not in database
   */
  async addApproverAtLevel(
    requestId: string,
    email: string,
    tatHours: number,
    targetLevel: number,
    addedBy: string
  ): Promise<any> {
    try {
      const emailLower = email.toLowerCase();

      // Find or create user from AD
      let user = await User.findOne({ where: { email: emailLower } });
      if (!user) {
        logger.info(`[Workflow] User not found in DB, syncing from AD: ${emailLower}`);
        const { UserService } = await import('./user.service');
        const userService = new UserService();
        try {
          user = await userService.ensureUserExists({ email: emailLower }) as any;
        } catch (adError: any) {
          logger.error(`[Workflow] Failed to sync user from AD: ${emailLower}`, adError);
          throw new Error(`Approver email '${email}' not found in organization directory. Please verify the email address.`);
        }
      }

      const userId = (user as any).userId;
      const userName = (user as any).displayName || (user as any).email;
      const designation = (user as any).designation || (user as any).jobTitle;
      const department = (user as any).department;

      // Check if user is already a participant
      const existing = await Participant.findOne({
        where: { requestId, userId }
      });

      if (existing) {
        throw new Error('User is already a participant in this request');
      }

      // Get workflow
      const workflow = await WorkflowRequest.findOne({ where: { requestId } });
      if (!workflow) {
        throw new Error('Workflow not found');
      }

      // Get all approval levels
      const allLevels = await ApprovalLevel.findAll({
        where: { requestId },
        order: [['levelNumber', 'ASC']]
      });

      // Validate target level
      // New approver must be placed after all approved/rejected/skipped levels
      const completedLevels = allLevels.filter(l => {
        const status = (l as any).status;
        return status === 'APPROVED' || status === 'REJECTED' || status === 'SKIPPED';
      });
      const minAllowedLevel = completedLevels.length + 1;

      if (targetLevel < minAllowedLevel) {
        throw new Error(`Cannot add approver at level ${targetLevel}. Minimum allowed level is ${minAllowedLevel} (after completed levels)`);
      }

      // Shift existing levels at and after target level
      // IMPORTANT: Shift in REVERSE order to avoid unique constraint violations
      // IMPORTANT: Preserve original level names when shifting (don't overwrite them)
      // IMPORTANT: Update status of shifted levels - if they were IN_PROGRESS, set to PENDING
      // because they're no longer the current active step (new approver is being added before them)
      const levelsToShift = allLevels
        .filter(l => (l as any).levelNumber >= targetLevel)
        .sort((a, b) => (b as any).levelNumber - (a as any).levelNumber); // Sort descending

      for (const levelToShift of levelsToShift) {
        const oldLevelNumber = (levelToShift as any).levelNumber;
        const newLevelNumber = oldLevelNumber + 1;
        const existingLevelName = (levelToShift as any).levelName;
        const currentStatus = (levelToShift as any).status;

        // If the level being shifted was IN_PROGRESS or PENDING, set it to PENDING
        // because it's no longer the current active step (a new approver is being added before it)
        const newStatus = (currentStatus === ApprovalStatus.IN_PROGRESS || currentStatus === ApprovalStatus.PENDING)
          ? ApprovalStatus.PENDING
          : currentStatus; // Keep APPROVED, REJECTED, SKIPPED as-is

        // Preserve the original level name - don't overwrite it
        await levelToShift.update({
          levelNumber: newLevelNumber,
          // Keep existing levelName if it exists, otherwise use generic
          levelName: existingLevelName || `Level ${newLevelNumber}`,
          status: newStatus,
          // Clear levelStartTime and tatStartTime since this is no longer the active step
          levelStartTime: undefined,
          tatStartTime: undefined,
        } as any);
        logger.info(`[Workflow] Shifted level ${oldLevelNumber} → ${newLevelNumber}, preserved levelName: ${existingLevelName || 'N/A'}, updated status: ${currentStatus} → ${newStatus}`);
      }

      // Update total levels in workflow
      await workflow.update({ totalLevels: allLevels.length + 1 });

      // Auto-generate smart level name for newly added approver
      // Use "Additional Approver" to identify dynamically added approvers
      let levelName = `Additional Approver`;
      if (designation) {
        levelName = `Additional Approver - ${designation}`;
      } else if (department) {
        levelName = `Additional Approver - ${department}`;
      } else if (userName) {
        levelName = `Additional Approver - ${userName}`;
      }

      // Check if request is currently APPROVED - if so, we need to reactivate it
      const workflowStatus = (workflow as any).status;
      const isRequestApproved = workflowStatus === 'APPROVED' || workflowStatus === WorkflowStatus.APPROVED;

      // Determine if the new level should be IN_PROGRESS
      // If we're adding at the current level OR request was approved, the new approver becomes the active approver
      const workflowCurrentLevel = (workflow as any).currentLevel;
      const isAddingAtCurrentLevel = targetLevel === workflowCurrentLevel;
      const shouldBeActive = isAddingAtCurrentLevel || isRequestApproved;      // Create new approval level at target position
      const newLevel = await ApprovalLevel.create({
        requestId,
        levelNumber: targetLevel,
        levelName,
        approverId: userId,
        approverEmail: emailLower,
        approverName: userName,
        tatHours,
        // tatDays is auto-calculated by database as a generated column
        status: shouldBeActive ? ApprovalStatus.IN_PROGRESS : ApprovalStatus.PENDING,
        isFinalApprover: targetLevel === allLevels.length + 1,
        levelStartTime: shouldBeActive ? new Date() : null,
        tatStartTime: shouldBeActive ? new Date() : null
      } as any);

      // Only update workflow currentLevel if the new level is becoming the active one
      if (shouldBeActive) {
        // If request was APPROVED and we're adding a new approver, reactivate the request
        if (isRequestApproved) {
          // Change request status back to PENDING
          await workflow.update({
            status: WorkflowStatus.PENDING,
            currentLevel: targetLevel // Set new approver as current level
          } as any);
          logger.info(`[Workflow] Request ${requestId} status changed from APPROVED to PENDING - new approver added at level ${targetLevel}`);
        } else {
          // If we're adding at the current level, ensure currentLevel matches targetLevel
          await workflow.update({ currentLevel: targetLevel } as any);
          logger.info(`[Workflow] Updated workflow currentLevel to ${targetLevel} for active new approver`);
        }
      } else {
        logger.info(`[Workflow] New approver added as PENDING at level ${targetLevel}. Current level remains at ${workflowCurrentLevel}`);
      }

      // Update isFinalApprover for previous final approver (now it's not final anymore)
      if (allLevels.length > 0) {
        const previousFinal = allLevels.find(l => (l as any).isFinalApprover);
        if (previousFinal && targetLevel > (previousFinal as any).levelNumber) {
          await previousFinal.update({ isFinalApprover: false });
        }
      }

      // Add as participant
      await Participant.create({
        requestId,
        userId,
        userEmail: email.toLowerCase(),
        userName,
        participantType: ParticipantType.APPROVER,
        canComment: true,
        canViewDocuments: true,
        canDownloadDocuments: true,
        notificationEnabled: true,
        addedBy,
        isActive: true
      } as any);

      // Schedule TAT jobs if new approver is active (either at current level or request was approved)
      if (shouldBeActive) {
        const workflowPriority = (workflow as any)?.priority || 'STANDARD';
        await tatSchedulerService.scheduleTatJobs(
          requestId,
          (newLevel as any).levelId,
          userId,
          tatHours,
          new Date(),
          workflowPriority
        );
        logger.info(`[Workflow] TAT jobs scheduled for new approver at level ${targetLevel} (request was ${isRequestApproved ? 'APPROVED - reactivated' : 'active'})`);
      }

      // Get the user who is adding the approver
      const addedByUser = await User.findByPk(addedBy);
      const addedByName = (addedByUser as any)?.displayName || (addedByUser as any)?.email || 'User';

      // Log activity
      await activityService.log({
        requestId,
        type: 'assignment',
        user: { userId: addedBy, name: addedByName },
        timestamp: new Date().toISOString(),
        action: 'Added new approver',
        details: `${userName} (${email}) has been added as approver at Level ${targetLevel} with TAT of ${tatHours} hours by ${addedByName}`
      });

      // Send notification to new additional approver (in-app, email, and web push)
      if (shouldBeActive) {
        // If immediately active, send 'assignment' (Action Required) email
        // This will trigger the standard approval request email
        await notificationService.sendToUsers([userId], {
          title: `Action required: ${(workflow as any).requestNumber}`,
          body: `${(workflow as any).title}`,
          requestNumber: (workflow as any).requestNumber,
          requestId: (workflow as any).requestId,
          url: `/request/${(workflow as any).requestNumber}`,
          type: 'assignment',
          priority: 'HIGH',
          actionRequired: true
        });
        logger.info(`[Workflow] Sent assignment (Action Required) notification to active new approver ${email}`);
      } else {
        // If future approver, send 'participant_added' (Informational) email
        // This informs the user they were added without asking for action yet
        await notificationService.sendToUsers([userId], {
          title: `Added as additional approver: ${(workflow as any).requestNumber}`,
          body: `You have been added as a future approver for ${(workflow as any).title}`,
          requestNumber: (workflow as any).requestNumber,
          requestId: (workflow as any).requestId,
          url: `/request/${(workflow as any).requestNumber}`,
          type: 'participant_added',
          priority: 'MEDIUM',
          actionRequired: false,
          metadata: {
            addedBy: addedBy,
            role: 'Approver'
          }
        });
        logger.info(`[Workflow] Sent participant_added (Info) notification to future approver ${email}`);
      }

      logger.info(`[Workflow] Added approver ${email} at level ${targetLevel} to request ${requestId}`);
      return newLevel;
    } catch (error) {
      logger.error(`[Workflow] Failed to add approver at level:`, error);
      throw error;
    }
  }

  /**
   * Add a new spectator to an existing workflow
   * Auto-creates user from Okta/AD if not in database
   */
  async addSpectator(requestId: string, email: string, addedBy: string): Promise<any> {
    try {
      const emailLower = email.toLowerCase();

      // Find or create user from AD
      let user = await User.findOne({ where: { email: emailLower } });
      if (!user) {
        logger.info(`[Workflow] User not found in DB, syncing from AD: ${emailLower}`);
        const { UserService } = await import('./user.service');
        const userService = new UserService();
        try {
          user = await userService.ensureUserExists({ email: emailLower }) as any;
        } catch (adError: any) {
          logger.error(`[Workflow] Failed to sync user from AD: ${emailLower}`, adError);
          throw new Error(`Spectator email '${email}' not found in organization directory. Please verify the email address.`);
        }
      }

      const userId = (user as any).userId;
      const userName = (user as any).displayName || (user as any).email;

      // Check if user is already a participant
      const existing = await Participant.findOne({
        where: { requestId, userId }
      });

      if (existing) {
        throw new Error('User is already a participant in this request');
      }

      // Add as spectator participant
      // SPECTATORS: View-only access, no approval rights, no document downloads
      const participant = await Participant.create({
        requestId,
        userId,
        userEmail: email.toLowerCase(),
        userName,
        participantType: ParticipantType.SPECTATOR, // Differentiates from APPROVER in database
        canComment: true,
        canViewDocuments: true,
        canDownloadDocuments: false, // Spectators cannot download
        notificationEnabled: true,
        addedBy,
        isActive: true
      } as any);

      // Get workflow details for notification
      const workflow = await WorkflowRequest.findOne({ where: { requestId } });
      const requestNumber = (workflow as any)?.requestNumber;
      const title = (workflow as any)?.title;

      // Get the user who is adding the spectator
      const addedByUser = await User.findByPk(addedBy);
      const addedByName = (addedByUser as any)?.displayName || (addedByUser as any)?.email || 'User';

      // Log activity
      await activityService.log({
        requestId,
        type: 'assignment',
        user: { userId: addedBy, name: addedByName },
        timestamp: new Date().toISOString(),
        action: 'Added new spectator',
        details: `${userName} (${email}) has been added as a spectator by ${addedByName}`
      });

      // Send notification to new spectator (in-app, email, and web push)
      // SPECTATOR NOTIFICATION: Uses 'spectator_added' type to trigger spectator added email
      // This differentiates from 'assignment' type used for approvers
      await notificationService.sendToUsers([userId], {
        title: 'Added to Request',
        body: `You have been added as a spectator to request ${requestNumber}: ${title}`,
        requestId,
        requestNumber,
        url: `/request/${requestNumber}`,
        type: 'spectator_added', //: Differentiates from 'assignment' - triggers spectator added email
        priority: 'MEDIUM', // Lower priority than approvers (no action required)
        metadata: {
          addedBy: addedBy // Used in email to show who added the spectator
        }
      });

      logger.info(`[Workflow] Added spectator ${email} to request ${requestId}`);
      return participant;
    } catch (error) {
      logger.error(`[Workflow] Failed to add spectator:`, error);
      throw error;
    }
  }
  /**
   * List all workflows for ADMIN/MANAGEMENT users (organization-level)
   * Shows ALL requests in the organization, including where admin is initiator
   * Used by: "All Requests" page for admin users
   */
  async listWorkflows(page: number, limit: number, filters?: { search?: string; status?: string; priority?: string; templateType?: string; financialYear?: string; quarter?: string; department?: string; initiator?: string; approver?: string; approverType?: 'current' | 'any'; slaCompliance?: string; dateRange?: string; startDate?: string; endDate?: string }) {
    const offset = (page - 1) * limit;

    // Build where clause with filters
    const whereConditions: any[] = [];

    // Exclude drafts only
    whereConditions.push({ isDraft: false });

    // NOTE: NO initiator exclusion here - admin sees ALL requests

    // Apply status filter (pending, approved, rejected, closed, paused)
    if (filters?.status && filters.status !== 'all') {
      const statusUpper = filters.status.toUpperCase();
      if (statusUpper === 'PENDING') {
        // Pending requests (not paused)
        whereConditions.push({
          status: 'PENDING',
          isPaused: false
        });
      } else if (statusUpper === 'PAUSED') {
        // Paused requests - can filter by status or isPaused flag
        whereConditions.push({
          [Op.or]: [
            { status: 'PAUSED' },
            { isPaused: true }
          ]
        });
      } else if (statusUpper === 'CLOSED') {
        whereConditions.push({ status: 'CLOSED' });
      } else if (statusUpper === 'REJECTED') {
        whereConditions.push({ status: 'REJECTED' });
      } else if (statusUpper === 'APPROVED') {
        whereConditions.push({ status: 'APPROVED' });
      } else {
        // Fallback: use the uppercase value as-is
        whereConditions.push({ status: statusUpper });
      }
    }

    // Apply priority filter
    if (filters?.priority && filters.priority !== 'all') {
      whereConditions.push({ priority: filters.priority.toUpperCase() });
    }

    // Apply templateType filter
    if (filters?.templateType && filters.templateType !== 'all') {
      const templateTypeUpper = filters.templateType.toUpperCase();
      // For CUSTOM, also include null values (legacy requests without templateType)
      if (templateTypeUpper === 'CUSTOM') {
        whereConditions.push({
          [Op.or]: [
            { templateType: 'CUSTOM' },
            { templateType: null }
          ]
        });
      } else {
        whereConditions.push({ templateType: templateTypeUpper });
      }
    }

    // Form 16: filter by financial year and/or quarter (form16a_submissions)
    if (filters?.templateType?.toUpperCase() === 'FORM_16' && (filters.financialYear || filters.quarter)) {
      const subWhere: any = {};
      if (filters.financialYear) subWhere.financialYear = filters.financialYear;
      if (filters.quarter) subWhere.quarter = filters.quarter;
      const form16Rows = await Form16aSubmission.findAll({ where: subWhere, attributes: ['requestId'] });
      const form16RequestIds = form16Rows.map((r: any) => r.requestId);
      if (form16RequestIds.length > 0) {
        whereConditions.push({ requestId: { [Op.in]: form16RequestIds } });
      } else {
        whereConditions.push({ requestId: { [Op.in]: ['00000000-0000-0000-0000-000000000000'] } });
      }
    }

    // Apply search filter (title, description, or requestNumber)
    if (filters?.search && filters.search.trim()) {
      whereConditions.push({
        [Op.or]: [
          { title: { [Op.iLike]: `%${filters.search.trim()}%` } },
          { description: { [Op.iLike]: `%${filters.search.trim()}%` } },
          { requestNumber: { [Op.iLike]: `%${filters.search.trim()}%` } }
        ]
      });
    }

    // Apply department filter (through initiator)
    if (filters?.department && filters.department !== 'all') {
      whereConditions.push({
        '$initiator.department$': filters.department
      });
    }

    // Apply initiator filter
    if (filters?.initiator && filters.initiator !== 'all') {
      whereConditions.push({ initiatorId: filters.initiator });
    }

    // Apply approver filter (with current vs any logic)
    if (filters?.approver && filters.approver !== 'all') {
      const approverId = filters.approver;
      const approverType = filters.approverType || 'current'; // Default to 'current'

      if (approverType === 'current') {
        // Filter by current active approver only
        // Find request IDs where this approver is the current active approver
        const currentApproverLevels = await ApprovalLevel.findAll({
          where: {
            approverId: approverId,
            status: { [Op.in]: ['PENDING', 'IN_PROGRESS'] }
          },
          attributes: ['requestId', 'levelNumber'],
        });

        // Get the current level for each request to match only if this approver is at the current level
        const requestIds: string[] = [];
        for (const level of currentApproverLevels) {
          const request = await WorkflowRequest.findByPk((level as any).requestId, {
            attributes: ['requestId', 'currentLevel'],
          });
          if (request && (request as any).currentLevel === (level as any).levelNumber) {
            requestIds.push((level as any).requestId);
          }
        }

        if (requestIds.length > 0) {
          whereConditions.push({ requestId: { [Op.in]: requestIds } });
        } else {
          // No matching requests - return empty result
          whereConditions.push({ requestId: { [Op.in]: ['00000000-0000-0000-0000-000000000000'] } });
        }
      } else {
        // Filter by any approver (past or current)
        // Find all request IDs where this user is an approver at any level
        const allApproverLevels = await ApprovalLevel.findAll({
          where: { approverId: approverId },
          attributes: ['requestId'],
        });
        const approverRequestIds = allApproverLevels.map((l: any) => l.requestId);

        if (approverRequestIds.length > 0) {
          whereConditions.push({ requestId: { [Op.in]: approverRequestIds } });
        } else {
          // No matching requests - return empty result
          whereConditions.push({ requestId: { [Op.in]: ['00000000-0000-0000-0000-000000000000'] } });
        }
      }
    }

    // Apply date range filter
    if (filters?.dateRange || filters?.startDate || filters?.endDate) {
      let dateStart: Date | null = null;
      let dateEnd: Date | null = null;

      if (filters.dateRange === 'custom' && filters.startDate && filters.endDate) {
        dateStart = dayjs(filters.startDate).startOf('day').toDate();
        dateEnd = dayjs(filters.endDate).endOf('day').toDate();
      } else if (filters.startDate && filters.endDate) {
        dateStart = dayjs(filters.startDate).startOf('day').toDate();
        dateEnd = dayjs(filters.endDate).endOf('day').toDate();
      } else if (filters.dateRange) {
        const now = dayjs();
        switch (filters.dateRange) {
          case 'today':
            dateStart = now.startOf('day').toDate();
            dateEnd = now.endOf('day').toDate();
            break;
          case 'week':
            dateStart = now.startOf('week').toDate();
            dateEnd = now.endOf('week').toDate();
            break;
          case 'month':
            dateStart = now.startOf('month').toDate();
            dateEnd = now.endOf('month').toDate();
            break;
        }
      }

      if (dateStart && dateEnd) {
        whereConditions.push({
          [Op.or]: [
            { submissionDate: { [Op.between]: [dateStart, dateEnd] } },
            // Fallback to createdAt if submissionDate is null
            {
              [Op.and]: [
                { submissionDate: null },
                { createdAt: { [Op.between]: [dateStart, dateEnd] } }
              ]
            }
          ]
        });
      }
    }

    const where = whereConditions.length > 0 ? { [Op.and]: whereConditions } : {};

    // If SLA compliance filter is active, we need to:
    // 1. Fetch all matching records (or a larger batch)
    // 2. Enrich them (which calculates SLA)
    // 3. Filter by SLA compliance
    // 4. Then paginate
    if (filters?.slaCompliance && filters.slaCompliance !== 'all') {
      // Fetch a larger batch to filter by SLA (up to 1000 records)
      const { rows: allRows } = await WorkflowRequest.findAndCountAll({
        where,
        limit: 1000, // Fetch up to 1000 records for SLA filtering
        order: [['createdAt', 'DESC']],
        include: [
          { association: 'initiator', required: false, attributes: ['userId', 'email', 'displayName', 'department', 'designation'] },
        ],
      });

      // Enrich all records (calculates SLA)
      const enrichedData = await this.enrichForCards(allRows);

      // Filter by SLA compliance
      const slaFilteredData = enrichedData.filter((req: any) => {
        const slaCompliance = filters.slaCompliance || '';

        // Get SLA status from various possible locations
        const slaStatus = req.currentLevelSLA?.status ||
          req.currentApprover?.sla?.status ||
          req.sla?.status ||
          req.summary?.sla?.status;

        if (slaCompliance.toLowerCase() === 'compliant') {
          const reqStatus = (req.status || '').toString().toUpperCase();
          const isCompleted = reqStatus === 'APPROVED' || reqStatus === 'REJECTED' || reqStatus === 'CLOSED';
          if (!isCompleted) return false;
          if (!slaStatus) return true;
          return slaStatus !== 'breached' && slaStatus.toLowerCase() !== 'breached';
        }

        if (!slaStatus) {
          return slaCompliance === 'on-track' || slaCompliance === 'on_track';
        }

        const statusMap: Record<string, string> = {
          'on-track': 'on_track',
          'on_track': 'on_track',
          'approaching': 'approaching',
          'critical': 'critical',
          'breached': 'breached'
        };

        const filterStatus = statusMap[slaCompliance.toLowerCase()] || slaCompliance.toLowerCase();
        return slaStatus === filterStatus || slaStatus.toLowerCase() === filterStatus;
      });

      // Apply pagination to filtered results
      const totalFiltered = slaFilteredData.length;
      const paginatedData = slaFilteredData.slice(offset, offset + limit);

      return {
        data: paginatedData,
        pagination: {
          page,
          limit,
          total: totalFiltered,
          totalPages: Math.ceil(totalFiltered / limit) || 1,
        },
      };
    }

    // Normal pagination (no SLA filter)
    const { rows, count } = await WorkflowRequest.findAndCountAll({
      where,
      offset,
      limit,
      order: [['createdAt', 'DESC']],
      include: [
        { association: 'initiator', required: false, attributes: ['userId', 'email', 'displayName', 'department', 'designation'] },
      ],
    });
    const data = await this.enrichForCards(rows);

    return {
      data,
      pagination: {
        page,
        limit,
        total: count,
        totalPages: Math.ceil(count / limit) || 1,
      },
    };
  }

  private async enrichForCards(rows: WorkflowRequest[]) {
    // Batch-fetch Form 16 submission data for FORM_16 requests (incl. totalAmount, creditNoteNumber, displayStatus)
    const form16RequestIds = rows
      .filter((wf) => ((wf as any).templateType || '').toString().toUpperCase() === 'FORM_16')
      .map((wf) => (wf as any).requestId);
    const form16ByRequestId = new Map<string, any>();
    if (form16RequestIds.length > 0) {
      const { Form16CreditNote } = await import('@models/Form16CreditNote');
      const form16Rows = await Form16aSubmission.findAll({
        where: { requestId: { [Op.in]: form16RequestIds } },
        attributes: ['id', 'requestId', 'dealerCode', 'form16aNumber', 'financialYear', 'quarter', 'status', 'validationStatus', 'validationNotes', 'submittedDate', 'totalAmount'],
      });
      const submissionIds = form16Rows.map((r: any) => r.id);
      const creditNotes = submissionIds.length
        ? await Form16CreditNote.findAll({
          where: { submissionId: submissionIds },
          attributes: ['submissionId', 'creditNoteNumber'],
        })
        : [];
      const cnBySubId = new Map<number, string>();
      for (const c of creditNotes as any[]) {
        if (c.submissionId && c.creditNoteNumber) cnBySubId.set(c.submissionId, c.creditNoteNumber);
      }
      const toDisplayStatus = (hasCn: boolean, valStatus: string | null, st: string, notes: string | null): string => {
        if (hasCn) return 'Completed';
        const v = (valStatus || '').toLowerCase();
        const n = (notes || '').toLowerCase();
        if (v === 'resubmission_needed') return 'Resubmission needed';
        if (v === 'duplicate') return 'Duplicate';
        if (v === 'manually_approved') return 'Completed';
        if (v === 'failed' || st === 'failed') {
          if (n.includes('mismatch') || n.includes('26as') || n.includes('value')) return 'Balance mismatch';
          return 'Failed';
        }
        return 'Under review';
      };
      for (const row of form16Rows as any[]) {
        const hasCn = cnBySubId.has(row.id);
        const creditNoteNumber = cnBySubId.get(row.id) ?? null;
        const displayStatus = toDisplayStatus(hasCn, row.validationStatus ?? null, row.status || '', row.validationNotes ?? null);
        form16ByRequestId.set(row.requestId, {
          dealerCode: row.dealerCode,
          form16aNumber: row.form16aNumber,
          financialYear: row.financialYear,
          quarter: row.quarter,
          status: row.status,
          submittedDate: row.submittedDate,
          totalAmount: row.totalAmount != null ? Number(row.totalAmount) : null,
          creditNoteNumber,
          displayStatus,
        });
      }
    }

    const data = await Promise.all(rows.map(async (wf) => {
      const currentLevel = await ApprovalLevel.findOne({
        where: {
          requestId: (wf as any).requestId,
          status: { [Op.in]: ['PENDING', 'IN_PROGRESS', 'PAUSED'] as any }, // Include PAUSED to show SLA for paused levels
        },
        order: [['levelNumber', 'ASC']],
        include: [{ model: User, as: 'approver', attributes: ['userId', 'email', 'displayName'] }],
        // Include pause-related fields for SLA calculation
        attributes: ['levelId', 'levelNumber', 'levelName', 'approverId', 'approverEmail', 'approverName',
          'tatHours', 'tatDays', 'status', 'levelStartTime', 'tatStartTime', 'levelEndTime',
          'isPaused', 'pausedAt', 'pauseElapsedHours', 'pauseResumeDate', 'elapsedHours']
      });

      // Fetch all approval levels for this request (including pause fields for SLA calculation)
      const approvals = await ApprovalLevel.findAll({
        where: { requestId: (wf as any).requestId },
        order: [['levelNumber', 'ASC']],
        attributes: ['levelId', 'levelNumber', 'levelName', 'approverId', 'approverEmail', 'approverName', 'tatHours', 'tatDays', 'status', 'levelStartTime', 'tatStartTime', 'isPaused', 'pausedAt', 'pauseElapsedHours', 'pauseResumeDate', 'elapsedHours']
      });

      // Calculate total TAT hours from all approvals
      const totalTatHours = approvals.reduce((sum: number, a: any) => {
        return sum + Number(a.tatHours || 0);
      }, 0);

      // Calculate approved levels count
      const approvedLevelsCount = approvals.filter((a: any) => a.status === 'APPROVED').length;

      // Determine closure type for CLOSED requests
      // If ANY level was rejected, it's a "rejected" closure
      // If ALL completed levels were approved, it's an "approved" closure
      const hasRejectedLevel = approvals.some((a: any) => a.status === 'REJECTED');
      const closureType = hasRejectedLevel ? 'rejected' : 'approved';

      const priority = ((wf as any).priority || 'standard').toString().toLowerCase();

      // Calculate OVERALL request SLA based on cumulative elapsed hours from all levels
      // This correctly accounts for pause periods since each level's elapsed is pause-adjusted
      const { calculateSLAStatus, addWorkingHours, addWorkingHoursExpress } = require('@utils/tatTimeUtils');
      const submissionDate = (wf as any).submissionDate;
      const closureDate = (wf as any).closureDate;

      let overallSLA = null;

      if (submissionDate && totalTatHours > 0) {
        try {
          // Calculate total elapsed hours by summing from all levels (pause-adjusted)
          let totalElapsedHours = 0;

          for (const approval of approvals) {
            const status = ((approval as any).status || '').toString().toUpperCase();

            if (status === 'APPROVED' || status === 'REJECTED') {
              // For completed levels, use stored elapsedHours
              totalElapsedHours += Number((approval as any).elapsedHours || 0);
            } else if (status === 'SKIPPED') {
              continue;
            } else if (status === 'PENDING' || status === 'IN_PROGRESS' || status === 'PAUSED') {
              // For active/paused levels, calculate with pause handling
              const levelStartTime = (approval as any).levelStartTime || (approval as any).tatStartTime;
              const levelTatHours = Number((approval as any).tatHours || 0);

              if (levelStartTime && levelTatHours > 0) {
                const isPausedLevel = status === 'PAUSED' || (approval as any).isPaused;
                const wasResumed = !isPausedLevel &&
                  (approval as any).pauseElapsedHours !== null &&
                  (approval as any).pauseElapsedHours !== undefined &&
                  (approval as any).pauseResumeDate !== null;

                const pauseInfo = isPausedLevel ? {
                  isPaused: true,
                  pauseElapsedHours: (approval as any).pauseElapsedHours
                } : wasResumed ? {
                  isPaused: false,
                  pauseElapsedHours: Number((approval as any).pauseElapsedHours),
                  pauseResumeDate: (approval as any).pauseResumeDate
                } : undefined;

                const levelSLA = await calculateSLAStatus(levelStartTime, levelTatHours, priority, null, pauseInfo);
                totalElapsedHours += levelSLA.elapsedHours || 0;
              }
            }
          }

          // Calculate overall SLA metrics
          const totalRemainingHours = Math.max(0, totalTatHours - totalElapsedHours);
          const percentageUsed = totalTatHours > 0
            ? Math.min(100, Math.round((totalElapsedHours / totalTatHours) * 100))
            : 0;

          // Determine status
          let overallStatus: 'on_track' | 'approaching' | 'critical' | 'breached' = 'on_track';
          if (percentageUsed >= 100) overallStatus = 'breached';
          else if (percentageUsed >= 80) overallStatus = 'critical';
          else if (percentageUsed >= 60) overallStatus = 'approaching';

          // Format time display
          const formatTime = (hours: number) => {
            if (hours < 1) return `${Math.round(hours * 60)}m`;
            const wholeHours = Math.floor(hours);
            const minutes = Math.round((hours - wholeHours) * 60);
            if (minutes > 0) return `${wholeHours}h ${minutes}m`;
            return `${wholeHours}h`;
          };

          // Check if any level is paused
          const isAnyLevelPaused = approvals.some((a: any) =>
            ((a.status || '').toString().toUpperCase() === 'PAUSED' || a.isPaused === true)
          );

          // Calculate deadline
          const deadline = priority === 'express'
            ? (await addWorkingHoursExpress(submissionDate, totalTatHours)).toDate()
            : (await addWorkingHours(submissionDate, totalTatHours)).toDate();

          overallSLA = {
            elapsedHours: totalElapsedHours,
            remainingHours: totalRemainingHours,
            percentageUsed,
            status: overallStatus,
            isPaused: isAnyLevelPaused,
            deadline: deadline.toISOString(),
            elapsedText: formatTime(totalElapsedHours),
            remainingText: formatTime(totalRemainingHours)
          };
        } catch (error) {
          logger.error('[Workflow] Error calculating overall SLA:', error);
        }
      }

      // Calculate current level SLA (if there's an active level, including paused)
      let currentLevelSLA = null;
      if (currentLevel) {
        const levelStartTime = (currentLevel as any).levelStartTime || (currentLevel as any).tatStartTime;
        const levelTatHours = Number((currentLevel as any).tatHours || 0);
        // For completed levels, use the level's completion time (if available)
        // Otherwise, if request is completed, use closure_date
        const levelEndDate = (currentLevel as any).levelEndTime || closureDate || null;

        // Prepare pause info for SLA calculation
        const isPausedLevel = (currentLevel as any).status === 'PAUSED' || (currentLevel as any).isPaused;
        const wasResumed = !isPausedLevel &&
          (currentLevel as any).pauseElapsedHours !== null &&
          (currentLevel as any).pauseElapsedHours !== undefined &&
          (currentLevel as any).pauseResumeDate !== null;

        const pauseInfo = isPausedLevel ? {
          isPaused: true,
          pausedAt: (currentLevel as any).pausedAt,
          pauseElapsedHours: (currentLevel as any).pauseElapsedHours,
          pauseResumeDate: (currentLevel as any).pauseResumeDate
        } : wasResumed ? {
          isPaused: false,
          pausedAt: null,
          pauseElapsedHours: Number((currentLevel as any).pauseElapsedHours),
          pauseResumeDate: (currentLevel as any).pauseResumeDate
        } : undefined;

        if (levelStartTime && levelTatHours > 0) {
          try {
            currentLevelSLA = await calculateSLAStatus(levelStartTime, levelTatHours, priority, levelEndDate, pauseInfo);
          } catch (error) {
            logger.error('[Workflow] Error calculating current level SLA:', error);
          }
        }
      }

      return {
        requestId: (wf as any).requestId,
        requestNumber: (wf as any).requestNumber,
        title: (wf as any).title,
        description: (wf as any).description,
        status: (wf as any).status,
        priority: (wf as any).priority,
        submittedAt: (wf as any).submissionDate,
        createdAt: (wf as any).createdAt,
        closureDate: (wf as any).closureDate,
        conclusionRemark: (wf as any).conclusionRemark,
        closureType: closureType, // 'approved' or 'rejected' - indicates path to closure
        workflowType: (wf as any).workflowType || null, // 'CLAIM_MANAGEMENT', 'NON_TEMPLATIZED', etc.
        templateType: (wf as any).templateType || null, // 'CUSTOM', 'TEMPLATE', 'DEALER CLAIM'
        templateId: (wf as any).templateId || null, // Reference to workflow_templates if using admin template
        initiator: (wf as any).initiator,
        department: (wf as any).initiator?.department,
        totalLevels: (wf as any).totalLevels,
        totalTatHours: totalTatHours,
        isPaused: (wf as any).isPaused || false, // Workflow pause status
        pauseInfo: (wf as any).isPaused ? {
          isPaused: true,
          pausedAt: (wf as any).pausedAt,
          pauseReason: (wf as any).pauseReason,
          pauseResumeDate: (wf as any).pauseResumeDate,
        } : null,
        currentLevel: currentLevel ? (currentLevel as any).levelNumber : null,
        currentApprover: currentLevel ? {
          userId: (currentLevel as any).approverId,
          email: (currentLevel as any).approverEmail,
          name: (currentLevel as any).approverName,
          levelStartTime: (currentLevel as any).levelStartTime,
          tatHours: (currentLevel as any).tatHours,
          isPaused: (currentLevel as any).status === 'PAUSED' || (currentLevel as any).isPaused,
          pauseElapsedHours: (currentLevel as any).pauseElapsedHours,
          sla: currentLevelSLA, // ← Backend-calculated SLA for current level (includes pause handling)
        } : null,
        approvals: approvals.map((a: any) => ({
          levelId: a.levelId,
          levelNumber: a.levelNumber,
          levelName: a.levelName,
          approverId: a.approverId,
          approverEmail: a.approverEmail,
          approverName: a.approverName,
          tatHours: a.tatHours,
          tatDays: a.tatDays,
          status: a.status,
          levelStartTime: a.levelStartTime || a.tatStartTime
        })),
        summary: {
          approvedLevels: approvedLevelsCount,
          totalLevels: (wf as any).totalLevels,
          sla: overallSLA || {
            elapsedHours: 0,
            remainingHours: totalTatHours,
            percentageUsed: 0,
            remainingText: `${totalTatHours}h remaining`,
            isPaused: false,
            status: 'on_track'
          }
        },
        sla: overallSLA || {
          elapsedHours: 0,
          remainingHours: totalTatHours,
          percentageUsed: 0,
          remainingText: `${totalTatHours}h remaining`,
          isPaused: false,
          status: 'on_track'
        }, // ← Overall request SLA (all levels combined)
        currentLevelSLA: currentLevelSLA, // ← Also provide at root level for easy access
        // Form 16: include submission columns for All Requests / Closed Requests
        form16Submission: ((wf as any).templateType || '').toString().toUpperCase() === 'FORM_16'
          ? form16ByRequestId.get((wf as any).requestId) || null
          : null,
      };
    }));
    return data;
  }

  /**
   * List requests where user is a PARTICIPANT (not initiator) for REGULAR USERS
   * Shows only requests where user is approver or spectator, EXCLUDES initiator requests
   * Used by: "All Requests" page for regular users
   * NOTE: This is SEPARATE from listWorkflows (admin) - they don't interfere with each other
   * @deprecated Use listParticipantRequests instead for clarity
   */
  async listMyRequests(
    userId: string,
    page: number,
    limit: number,
    filters?: {
      search?: string;
      status?: string;
      priority?: string;
      department?: string;
      initiator?: string;
      approver?: string;
      approverType?: 'current' | 'any';
      slaCompliance?: string;
      dateRange?: string;
      startDate?: string;
      endDate?: string;
    }
  ) {
    const offset = (page - 1) * limit;

    // Find all request IDs where user is a participant (NOT initiator):
    // 1. As approver (in any approval level)
    // 2. As participant/spectator
    // NOTE: Exclude requests where user is initiator (those are shown in "My Requests" page)

    // Get requests where user is an approver (in any approval level)
    const approverLevels = await ApprovalLevel.findAll({
      where: { approverId: userId },
      attributes: ['requestId'],
    });
    const approverRequestIds = approverLevels.map((l: any) => l.requestId);

    // Get requests where user is a participant/spectator
    const participants = await Participant.findAll({
      where: { userId },
      attributes: ['requestId'],
    });
    const participantRequestIds = participants.map((p: any) => p.requestId);

    // Combine request IDs where user is participant (approver or spectator)
    const allRequestIds = Array.from(new Set([
      ...approverRequestIds,
      ...participantRequestIds
    ]));

    // Build where clause with filters
    const whereConditions: any[] = [];

    // ALWAYS exclude requests where user is initiator (for regular users only)
    // This ensures "All Requests" only shows participant requests, not initiator requests
    whereConditions.push({ initiatorId: { [Op.ne]: userId } });

    // Filter by request IDs where user is involved as participant (approver or spectator)
    if (allRequestIds.length > 0) {
      whereConditions.push({ requestId: { [Op.in]: allRequestIds } });
    } else {
      // No matching requests - return empty result
      whereConditions.push({ requestId: { [Op.in]: ['00000000-0000-0000-0000-000000000000'] } });
    }

    // Exclude drafts
    whereConditions.push({ isDraft: false });

    // Apply status filter (pending, approved, rejected, closed)
    // Same logic as listWorkflows but applied to participant requests only
    if (filters?.status && filters.status !== 'all') {
      const statusUpper = filters.status.toUpperCase();
      if (statusUpper === 'PENDING') {
        // Pending requests only (IN_PROGRESS is treated as PENDING)
        whereConditions.push({
          [Op.or]: [
            { status: 'PENDING' },
            { status: 'IN_PROGRESS' } // Legacy support - will be migrated to PENDING
          ]
        });
      } else if (statusUpper === 'CLOSED') {
        whereConditions.push({ status: 'CLOSED' });
      } else if (statusUpper === 'REJECTED') {
        whereConditions.push({ status: 'REJECTED' });
      } else if (statusUpper === 'APPROVED') {
        whereConditions.push({ status: 'APPROVED' });
      } else {
        // Fallback: use the uppercase value as-is
        whereConditions.push({ status: statusUpper });
      }
    }

    // Apply priority filter
    if (filters?.priority && filters.priority !== 'all') {
      whereConditions.push({ priority: filters.priority.toUpperCase() });
    }

    // Apply search filter (title, description, or requestNumber)
    if (filters?.search && filters.search.trim()) {
      whereConditions.push({
        [Op.or]: [
          { title: { [Op.iLike]: `%${filters.search.trim()}%` } },
          { description: { [Op.iLike]: `%${filters.search.trim()}%` } },
          { requestNumber: { [Op.iLike]: `%${filters.search.trim()}%` } }
        ]
      });
    }

    // Apply department filter (through initiator)
    if (filters?.department && filters.department !== 'all') {
      whereConditions.push({
        '$initiator.department$': filters.department
      });
    }

    // Apply initiator filter
    if (filters?.initiator && filters.initiator !== 'all') {
      whereConditions.push({ initiatorId: filters.initiator });
    }

    // Apply approver filter (with current vs any logic) - for listParticipantRequests
    if (filters?.approver && filters.approver !== 'all') {
      const approverId = filters.approver;
      const approverType = filters.approverType || 'current'; // Default to 'current'

      if (approverType === 'current') {
        // Filter by current active approver only
        // Find request IDs where this approver is the current active approver
        const currentApproverLevels = await ApprovalLevel.findAll({
          where: {
            approverId: approverId,
            status: { [Op.in]: ['PENDING', 'IN_PROGRESS'] }
          },
          attributes: ['requestId', 'levelNumber'],
        });

        // Get the current level for each request to match only if this approver is at the current level
        const requestIds: string[] = [];
        for (const level of currentApproverLevels) {
          const request = await WorkflowRequest.findByPk((level as any).requestId, {
            attributes: ['requestId', 'currentLevel'],
          });
          if (request && (request as any).currentLevel === (level as any).levelNumber) {
            requestIds.push((level as any).requestId);
          }
        }

        if (requestIds.length > 0) {
          whereConditions.push({ requestId: { [Op.in]: requestIds } });
        } else {
          // No matching requests - return empty result
          whereConditions.push({ requestId: { [Op.in]: ['00000000-0000-0000-0000-000000000000'] } });
        }
      } else {
        // Filter by any approver (past or current)
        // Find all request IDs where this user is an approver at any level
        const allApproverLevels = await ApprovalLevel.findAll({
          where: { approverId: approverId },
          attributes: ['requestId'],
        });
        const approverRequestIds = allApproverLevels.map((l: any) => l.requestId);

        if (approverRequestIds.length > 0) {
          whereConditions.push({ requestId: { [Op.in]: approverRequestIds } });
        } else {
          // No matching requests - return empty result
          whereConditions.push({ requestId: { [Op.in]: ['00000000-0000-0000-0000-000000000000'] } });
        }
      }
    }

    // Apply date range filter (same logic as listWorkflows)
    if (filters?.dateRange || filters?.startDate || filters?.endDate) {
      let dateStart: Date | null = null;
      let dateEnd: Date | null = null;

      if (filters.dateRange === 'custom' && filters.startDate && filters.endDate) {
        dateStart = dayjs(filters.startDate).startOf('day').toDate();
        dateEnd = dayjs(filters.endDate).endOf('day').toDate();
      } else if (filters.startDate && filters.endDate) {
        dateStart = dayjs(filters.startDate).startOf('day').toDate();
        dateEnd = dayjs(filters.endDate).endOf('day').toDate();
      } else if (filters.dateRange) {
        const now = dayjs();
        switch (filters.dateRange) {
          case 'today':
            dateStart = now.startOf('day').toDate();
            dateEnd = now.endOf('day').toDate();
            break;
          case 'week':
            dateStart = now.startOf('week').toDate();
            dateEnd = now.endOf('week').toDate();
            break;
          case 'month':
            dateStart = now.startOf('month').toDate();
            dateEnd = now.endOf('month').toDate();
            break;
        }
      }

      if (dateStart && dateEnd) {
        whereConditions.push({
          [Op.or]: [
            { submissionDate: { [Op.between]: [dateStart, dateEnd] } },
            // Fallback to createdAt if submissionDate is null
            {
              [Op.and]: [
                { submissionDate: null },
                { createdAt: { [Op.between]: [dateStart, dateEnd] } }
              ]
            }
          ]
        });
      }
    }

    const where = whereConditions.length > 0 ? { [Op.and]: whereConditions } : {};

    // If SLA compliance filter is active, fetch all, enrich, filter, then paginate
    if (filters?.slaCompliance && filters.slaCompliance !== 'all') {
      const { rows: allRows } = await WorkflowRequest.findAndCountAll({
        where,
        limit: 1000, // Fetch up to 1000 records for SLA filtering
        order: [['createdAt', 'DESC']],
        include: [
          { association: 'initiator', required: false, attributes: ['userId', 'email', 'displayName', 'department', 'designation'] },
        ],
      });

      const enrichedData = await this.enrichForCards(allRows);

      // Filter by SLA compliance
      const slaFilteredData = enrichedData.filter((req: any) => {
        const slaCompliance = filters.slaCompliance || '';
        const slaStatus = req.currentLevelSLA?.status ||
          req.currentApprover?.sla?.status ||
          req.sla?.status ||
          req.summary?.sla?.status;

        if (slaCompliance.toLowerCase() === 'compliant') {
          const reqStatus = (req.status || '').toString().toUpperCase();
          const isCompleted = reqStatus === 'APPROVED' || reqStatus === 'REJECTED' || reqStatus === 'CLOSED';
          if (!isCompleted) return false;
          if (!slaStatus) return true;
          return slaStatus !== 'breached' && slaStatus.toLowerCase() !== 'breached';
        }

        if (!slaStatus) {
          return slaCompliance === 'on-track' || slaCompliance === 'on_track';
        }

        const statusMap: Record<string, string> = {
          'on-track': 'on_track',
          'on_track': 'on_track',
          'approaching': 'approaching',
          'critical': 'critical',
          'breached': 'breached'
        };

        const filterStatus = statusMap[slaCompliance.toLowerCase()] || slaCompliance.toLowerCase();
        return slaStatus === filterStatus || slaStatus.toLowerCase() === filterStatus;
      });

      const totalFiltered = slaFilteredData.length;
      const paginatedData = slaFilteredData.slice(offset, offset + limit);

      return {
        data: paginatedData,
        pagination: {
          page,
          limit,
          total: totalFiltered,
          totalPages: Math.ceil(totalFiltered / limit) || 1
        }
      };
    }

    // Normal pagination (no SLA filter)
    const { rows, count } = await WorkflowRequest.findAndCountAll({
      where,
      offset,
      limit,
      order: [['createdAt', 'DESC']],
      include: [
        { association: 'initiator', required: false, attributes: ['userId', 'email', 'displayName', 'department', 'designation'] },
      ],
    });
    const data = await this.enrichForCards(rows);
    return { data, pagination: { page, limit, total: count, totalPages: Math.ceil(count / limit) || 1 } };
  }

  /**
   * List ALL requests where user is INVOLVED for REGULAR USERS - "All Requests" page
   * This is a dedicated method for regular users' "All Requests" screen
   * Shows requests where user is:
   * - Initiator (created the request)
   * - Approver (in any approval level)
   * - Participant/spectator
   * Completely separate from listWorkflows (admin) to avoid interference
   */
  async listParticipantRequests(
    userId: string,
    page: number,
    limit: number,
    filters?: {
      search?: string;
      status?: string;
      priority?: string;
      templateType?: string;
      financialYear?: string;
      quarter?: string;
      department?: string;
      initiator?: string;
      approver?: string;
      approverType?: 'current' | 'any';
      slaCompliance?: string;
      dateRange?: string;
      startDate?: string;
      endDate?: string;
    }
  ) {
    const offset = (page - 1) * limit;

    // Find all request IDs where user is INVOLVED in any capacity:
    // 1. As initiator (created the request)
    // 2. As approver (in any approval level)
    // 3. As participant/spectator

    // Get requests where user is the initiator
    const initiatorRequests = await WorkflowRequest.findAll({
      where: { initiatorId: userId, isDraft: false },
      attributes: ['requestId'],
    });
    const initiatorRequestIds = initiatorRequests.map((r: any) => r.requestId);

    // Get requests where user is an approver (in any approval level)
    const approverLevels = await ApprovalLevel.findAll({
      where: { approverId: userId },
      attributes: ['requestId'],
    });
    const approverRequestIds = approverLevels.map((l: any) => l.requestId);

    // Get requests where user is a participant/spectator
    const participants = await Participant.findAll({
      where: { userId },
      attributes: ['requestId'],
    });
    const participantRequestIds = participants.map((p: any) => p.requestId);

    // [Form 16 only] If user has Form 16 submission view permission (admin config), include all FORM_16 request IDs so they appear on All Requests.
    // Does not affect users without Form 16 access: form16RequestIds stays [] and list remains initiator + approver + spectator only.
    let form16RequestIds: string[] = [];
    const userForForm16 = await User.findByPk(userId, { attributes: ['email', 'role'] });
    if (userForForm16?.email && await canViewForm16Submission((userForForm16 as any).email, userId, (userForForm16 as any).role)) {
      const form16Workflows = await WorkflowRequest.findAll({
        where: { templateType: 'FORM_16', isDraft: false },
        attributes: ['requestId'],
      });
      form16RequestIds = form16Workflows.map((r: any) => r.requestId);
    }

    // Combine ALL request IDs where user is involved (initiator + approver + spectator + Form 16 if permitted)
    const allRequestIds = Array.from(new Set([
      ...initiatorRequestIds,
      ...approverRequestIds,
      ...participantRequestIds,
      ...form16RequestIds
    ]));

    // Build where clause with filters
    const whereConditions: any[] = [];

    // Filter by request IDs where user is involved in any capacity
    if (allRequestIds.length > 0) {
      whereConditions.push({ requestId: { [Op.in]: allRequestIds } });
    } else {
      // No matching requests - return empty result
      whereConditions.push({ requestId: { [Op.in]: ['00000000-0000-0000-0000-000000000000'] } });
    }

    // Exclude drafts
    whereConditions.push({ isDraft: false });

    // Apply status filter (pending, approved, rejected, closed)
    // Same logic as listWorkflows but applied to participant requests only
    if (filters?.status && filters.status !== 'all') {
      const statusUpper = filters.status.toUpperCase();
      if (statusUpper === 'PENDING') {
        // Pending requests only (IN_PROGRESS is treated as PENDING)
        whereConditions.push({
          [Op.or]: [
            { status: 'PENDING' },
            { status: 'IN_PROGRESS' } // Legacy support - will be migrated to PENDING
          ]
        });
      } else if (statusUpper === 'CLOSED') {
        whereConditions.push({ status: 'CLOSED' });
      } else if (statusUpper === 'REJECTED') {
        whereConditions.push({ status: 'REJECTED' });
      } else if (statusUpper === 'APPROVED') {
        whereConditions.push({ status: 'APPROVED' });
      } else {
        // Fallback: use the uppercase value as-is
        whereConditions.push({ status: statusUpper });
      }
    }

    // Apply priority filter
    if (filters?.priority && filters.priority !== 'all') {
      whereConditions.push({ priority: filters.priority.toUpperCase() });
    }

    // Apply templateType filter
    if (filters?.templateType && filters.templateType !== 'all') {
      const templateTypeUpper = filters.templateType.toUpperCase();
      // For CUSTOM, also include null values (legacy requests without templateType)
      if (templateTypeUpper === 'CUSTOM') {
        whereConditions.push({
          [Op.or]: [
            { templateType: 'CUSTOM' },
            { templateType: null }
          ]
        });
      } else {
        whereConditions.push({ templateType: templateTypeUpper });
      }
    }

    // Form 16: filter by financial year and/or quarter (form16a_submissions)
    if (filters?.templateType?.toUpperCase() === 'FORM_16' && (filters.financialYear || filters.quarter)) {
      const subWhere: any = {};
      if (filters.financialYear) subWhere.financialYear = filters.financialYear;
      if (filters.quarter) subWhere.quarter = filters.quarter;
      const form16Rows = await Form16aSubmission.findAll({ where: subWhere, attributes: ['requestId'] });
      const form16RequestIds = form16Rows.map((r: any) => r.requestId);
      if (form16RequestIds.length > 0) {
        whereConditions.push({ requestId: { [Op.in]: form16RequestIds } });
      } else {
        whereConditions.push({ requestId: { [Op.in]: ['00000000-0000-0000-0000-000000000000'] } });
      }
    }

    // Apply search filter (title, description, or requestNumber)
    if (filters?.search && filters.search.trim()) {
      whereConditions.push({
        [Op.or]: [
          { title: { [Op.iLike]: `%${filters.search.trim()}%` } },
          { description: { [Op.iLike]: `%${filters.search.trim()}%` } },
          { requestNumber: { [Op.iLike]: `%${filters.search.trim()}%` } }
        ]
      });
    }

    // Apply department filter (through initiator)
    if (filters?.department && filters.department !== 'all') {
      whereConditions.push({
        '$initiator.department$': filters.department
      });
    }

    // Apply initiator filter
    if (filters?.initiator && filters.initiator !== 'all') {
      whereConditions.push({ initiatorId: filters.initiator });
    }

    // Apply approver filter (with current vs any logic) - for listParticipantRequests
    if (filters?.approver && filters.approver !== 'all') {
      const approverId = filters.approver;
      const approverType = filters.approverType || 'current'; // Default to 'current'

      if (approverType === 'current') {
        // Filter by current active approver only
        // Find request IDs where this approver is the current active approver
        const currentApproverLevels = await ApprovalLevel.findAll({
          where: {
            approverId: approverId,
            status: { [Op.in]: ['PENDING', 'IN_PROGRESS'] }
          },
          attributes: ['requestId', 'levelNumber'],
        });

        // Get the current level for each request to match only if this approver is at the current level
        const requestIds: string[] = [];
        for (const level of currentApproverLevels) {
          const request = await WorkflowRequest.findByPk((level as any).requestId, {
            attributes: ['requestId', 'currentLevel'],
          });
          if (request && (request as any).currentLevel === (level as any).levelNumber) {
            requestIds.push((level as any).requestId);
          }
        }

        if (requestIds.length > 0) {
          whereConditions.push({ requestId: { [Op.in]: requestIds } });
        } else {
          // No matching requests - return empty result
          whereConditions.push({ requestId: { [Op.in]: ['00000000-0000-0000-0000-000000000000'] } });
        }
      } else {
        // Filter by any approver (past or current)
        // Find all request IDs where this user is an approver at any level
        const allApproverLevels = await ApprovalLevel.findAll({
          where: { approverId: approverId },
          attributes: ['requestId'],
        });
        const approverRequestIds = allApproverLevels.map((l: any) => l.requestId);

        if (approverRequestIds.length > 0) {
          whereConditions.push({ requestId: { [Op.in]: approverRequestIds } });
        } else {
          // No matching requests - return empty result
          whereConditions.push({ requestId: { [Op.in]: ['00000000-0000-0000-0000-000000000000'] } });
        }
      }
    }

    // Apply date range filter (same logic as listWorkflows)
    if (filters?.dateRange || filters?.startDate || filters?.endDate) {
      let dateStart: Date | null = null;
      let dateEnd: Date | null = null;

      if (filters.dateRange === 'custom' && filters.startDate && filters.endDate) {
        dateStart = dayjs(filters.startDate).startOf('day').toDate();
        dateEnd = dayjs(filters.endDate).endOf('day').toDate();
      } else if (filters.startDate && filters.endDate) {
        dateStart = dayjs(filters.startDate).startOf('day').toDate();
        dateEnd = dayjs(filters.endDate).endOf('day').toDate();
      } else if (filters.dateRange) {
        const now = dayjs();
        switch (filters.dateRange) {
          case 'today':
            dateStart = now.startOf('day').toDate();
            dateEnd = now.endOf('day').toDate();
            break;
          case 'week':
            dateStart = now.startOf('week').toDate();
            dateEnd = now.endOf('week').toDate();
            break;
          case 'month':
            dateStart = now.startOf('month').toDate();
            dateEnd = now.endOf('month').toDate();
            break;
        }
      }

      if (dateStart && dateEnd) {
        whereConditions.push({
          [Op.or]: [
            { submissionDate: { [Op.between]: [dateStart, dateEnd] } },
            // Fallback to createdAt if submissionDate is null
            {
              [Op.and]: [
                { submissionDate: null },
                { createdAt: { [Op.between]: [dateStart, dateEnd] } }
              ]
            }
          ]
        });
      }
    }

    const where = whereConditions.length > 0 ? { [Op.and]: whereConditions } : {};

    // If SLA compliance filter is active, fetch all, enrich, filter, then paginate
    if (filters?.slaCompliance && filters.slaCompliance !== 'all') {
      const { rows: allRows } = await WorkflowRequest.findAndCountAll({
        where,
        limit: 1000, // Fetch up to 1000 records for SLA filtering
        order: [['createdAt', 'DESC']],
        include: [
          { association: 'initiator', required: false, attributes: ['userId', 'email', 'displayName', 'department', 'designation'] },
        ],
      });

      const enrichedData = await this.enrichForCards(allRows);

      // Filter by SLA compliance
      const slaFilteredData = enrichedData.filter((req: any) => {
        const slaCompliance = filters.slaCompliance || '';
        const slaStatus = req.currentLevelSLA?.status ||
          req.currentApprover?.sla?.status ||
          req.sla?.status ||
          req.summary?.sla?.status;

        if (slaCompliance.toLowerCase() === 'compliant') {
          const reqStatus = (req.status || '').toString().toUpperCase();
          const isCompleted = reqStatus === 'APPROVED' || reqStatus === 'REJECTED' || reqStatus === 'CLOSED';
          if (!isCompleted) return false;
          if (!slaStatus) return true;
          return slaStatus !== 'breached' && slaStatus.toLowerCase() !== 'breached';
        }

        if (!slaStatus) {
          return slaCompliance === 'on-track' || slaCompliance === 'on_track';
        }

        const statusMap: Record<string, string> = {
          'on-track': 'on_track',
          'on_track': 'on_track',
          'approaching': 'approaching',
          'critical': 'critical',
          'breached': 'breached'
        };

        const filterStatus = statusMap[slaCompliance.toLowerCase()] || slaCompliance.toLowerCase();
        return slaStatus === filterStatus || slaStatus.toLowerCase() === filterStatus;
      });

      const totalFiltered = slaFilteredData.length;
      const paginatedData = slaFilteredData.slice(offset, offset + limit);

      return {
        data: paginatedData,
        pagination: {
          page,
          limit,
          total: totalFiltered,
          totalPages: Math.ceil(totalFiltered / limit) || 1
        }
      };
    }

    // Normal pagination (no SLA filter)
    const { rows, count } = await WorkflowRequest.findAndCountAll({
      where,
      offset,
      limit,
      order: [['createdAt', 'DESC']],
      include: [
        { association: 'initiator', required: false, attributes: ['userId', 'email', 'displayName', 'department', 'designation'] },
      ],
    });
    const data = await this.enrichForCards(rows);
    return { data, pagination: { page, limit, total: count, totalPages: Math.ceil(count / limit) || 1 } };
  }

  /**
   * List requests where user is the initiator (for "My Requests" page)
   */
  async listMyInitiatedRequests(
    userId: string,
    page: number,
    limit: number,
    filters?: {
      search?: string;
      status?: string;
      priority?: string;
      templateType?: string;
      department?: string;
      slaCompliance?: string;
      dateRange?: string;
      startDate?: string;
      endDate?: string;
    }
  ) {
    const offset = (page - 1) * limit;

    // Build where clause with filters - only requests where user is initiator
    const whereConditions: any[] = [{ initiatorId: userId }];

    // Exclude drafts
    // Include drafts in "My Requests" - users may keep drafts for some time
    // whereConditions.push({ isDraft: false }); // Removed to include drafts

    // Apply status filter
    if (filters?.status && filters.status !== 'all') {
      const statusUpper = filters.status.toUpperCase();
      if (statusUpper === 'PENDING') {
        whereConditions.push({
          [Op.or]: [
            { status: 'PENDING' },
            { status: 'IN_PROGRESS' }
          ]
        });
      } else if (statusUpper === 'DRAFT') {
        // Draft status - filter by isDraft flag
        whereConditions.push({ isDraft: true });
      } else {
        whereConditions.push({ status: statusUpper });
      }
    }

    // Apply priority filter
    if (filters?.priority && filters.priority !== 'all') {
      whereConditions.push({ priority: filters.priority.toUpperCase() });
    }

    // Apply templateType filter
    if (filters?.templateType && filters.templateType !== 'all') {
      const templateTypeUpper = filters.templateType.toUpperCase();
      // For CUSTOM, also include null values (legacy requests without templateType)
      if (templateTypeUpper === 'CUSTOM') {
        whereConditions.push({
          [Op.or]: [
            { templateType: 'CUSTOM' },
            { templateType: null }
          ]
        });
      } else {
        whereConditions.push({ templateType: templateTypeUpper });
      }
    }

    // Apply search filter (title, description, or requestNumber)
    if (filters?.search && filters.search.trim()) {
      whereConditions.push({
        [Op.or]: [
          { title: { [Op.iLike]: `%${filters.search.trim()}%` } },
          { description: { [Op.iLike]: `%${filters.search.trim()}%` } },
          { requestNumber: { [Op.iLike]: `%${filters.search.trim()}%` } }
        ]
      });
    }

    // Apply department filter (through initiator)
    if (filters?.department && filters.department !== 'all') {
      whereConditions.push({
        '$initiator.department$': filters.department
      });
    }

    // Apply date range filter (same logic as listWorkflows)
    if (filters?.dateRange || filters?.startDate || filters?.endDate) {
      let dateStart: Date | null = null;
      let dateEnd: Date | null = null;

      if (filters.dateRange === 'custom' && filters.startDate && filters.endDate) {
        dateStart = dayjs(filters.startDate).startOf('day').toDate();
        dateEnd = dayjs(filters.endDate).endOf('day').toDate();
      } else if (filters.startDate && filters.endDate) {
        dateStart = dayjs(filters.startDate).startOf('day').toDate();
        dateEnd = dayjs(filters.endDate).endOf('day').toDate();
      } else if (filters.dateRange) {
        const now = dayjs();
        switch (filters.dateRange) {
          case 'today':
            dateStart = now.startOf('day').toDate();
            dateEnd = now.endOf('day').toDate();
            break;
          case 'week':
            dateStart = now.startOf('week').toDate();
            dateEnd = now.endOf('week').toDate();
            break;
          case 'month':
            dateStart = now.startOf('month').toDate();
            dateEnd = now.endOf('month').toDate();
            break;
        }
      }

      if (dateStart && dateEnd) {
        whereConditions.push({
          [Op.or]: [
            { submissionDate: { [Op.between]: [dateStart, dateEnd] } },
            // Fallback to createdAt if submissionDate is null
            {
              [Op.and]: [
                { submissionDate: null },
                { createdAt: { [Op.between]: [dateStart, dateEnd] } }
              ]
            }
          ]
        });
      }
    }

    const where = whereConditions.length > 0 ? { [Op.and]: whereConditions } : {};

    // If SLA compliance filter is active, fetch all, enrich, filter, then paginate
    if (filters?.slaCompliance && filters.slaCompliance !== 'all') {
      const { rows: allRows } = await WorkflowRequest.findAndCountAll({
        where,
        limit: 1000, // Fetch up to 1000 records for SLA filtering
        order: [['createdAt', 'DESC']],
        include: [
          { association: 'initiator', required: false, attributes: ['userId', 'email', 'displayName', 'department', 'designation'] },
        ],
      });

      const enrichedData = await this.enrichForCards(allRows);

      // Filter by SLA compliance
      const slaFilteredData = enrichedData.filter((req: any) => {
        const slaCompliance = filters.slaCompliance || '';
        const slaStatus = req.currentLevelSLA?.status ||
          req.currentApprover?.sla?.status ||
          req.sla?.status ||
          req.summary?.sla?.status;

        if (slaCompliance.toLowerCase() === 'compliant') {
          const reqStatus = (req.status || '').toString().toUpperCase();
          const isCompleted = reqStatus === 'APPROVED' || reqStatus === 'REJECTED' || reqStatus === 'CLOSED';
          if (!isCompleted) return false;
          if (!slaStatus) return true;
          return slaStatus !== 'breached' && slaStatus.toLowerCase() !== 'breached';
        }

        if (!slaStatus) {
          return slaCompliance === 'on-track' || slaCompliance === 'on_track';
        }

        const statusMap: Record<string, string> = {
          'on-track': 'on_track',
          'on_track': 'on_track',
          'approaching': 'approaching',
          'critical': 'critical',
          'breached': 'breached'
        };

        const filterStatus = statusMap[slaCompliance.toLowerCase()] || slaCompliance.toLowerCase();
        return slaStatus === filterStatus || slaStatus.toLowerCase() === filterStatus;
      });

      const totalFiltered = slaFilteredData.length;
      const paginatedData = slaFilteredData.slice(offset, offset + limit);

      return {
        data: paginatedData,
        pagination: {
          page,
          limit,
          total: totalFiltered,
          totalPages: Math.ceil(totalFiltered / limit) || 1
        }
      };
    }

    // Normal pagination (no SLA filter)
    const { rows, count } = await WorkflowRequest.findAndCountAll({
      where,
      offset,
      limit,
      order: [['createdAt', 'DESC']],
      include: [
        { association: 'initiator', required: false, attributes: ['userId', 'email', 'displayName', 'department', 'designation'] },
      ],
    });
    const data = await this.enrichForCards(rows);
    return { data, pagination: { page, limit, total: count, totalPages: Math.ceil(count / limit) || 1 } };
  }

  async listOpenForMe(userId: string, page: number, limit: number, filters?: { search?: string; status?: string; priority?: string; templateType?: string; financialYear?: string; quarter?: string }, sortBy?: string, sortOrder?: string) {
    const offset = (page - 1) * limit;
    // Find all pending/in-progress/paused approval levels across requests ordered by levelNumber
    // Include PAUSED status so paused requests where user is the current approver are shown
    const pendingLevels = await ApprovalLevel.findAll({
      where: {
        status: {
          [Op.in]: [
            ApprovalStatus.PENDING as any,
            (ApprovalStatus as any).IN_PROGRESS ?? 'IN_PROGRESS',
            ApprovalStatus.PAUSED as any,
            'PENDING',
            'IN_PROGRESS',
            'PAUSED'
          ] as any
        },
      },
      order: [['requestId', 'ASC'], ['levelNumber', 'ASC']],
      attributes: ['requestId', 'levelNumber', 'approverId'],
    });

    // For each request, pick the first (current) pending level
    const currentLevelByRequest = new Map<string, { requestId: string; levelNumber: number; approverId: string }>();
    for (const lvl of pendingLevels as any[]) {
      const rid = lvl.requestId as string;
      if (!currentLevelByRequest.has(rid)) {
        currentLevelByRequest.set(rid, {
          requestId: rid,
          levelNumber: lvl.levelNumber,
          approverId: lvl.approverId,
        });
      }
    }

    // Include requests where the current approver matches the user
    const approverRequestIds = Array.from(currentLevelByRequest.values())
      .filter(item => item.approverId === userId)
      .map(item => item.requestId);

    // Also include requests where the user is a spectator
    const spectatorParticipants = await Participant.findAll({
      where: {
        userId,
        participantType: 'SPECTATOR',
      },
      attributes: ['requestId'],
    });
    const spectatorRequestIds = spectatorParticipants.map((p: any) => p.requestId);

    // Combine both sets of request IDs (unique)
    const allRequestIds = Array.from(new Set([...approverRequestIds, ...spectatorRequestIds]));

    // Also include APPROVED requests where the user is the initiator (awaiting closure)
    const approvedAsInitiator = await WorkflowRequest.findAll({
      where: {
        initiatorId: userId,
        status: { [Op.in]: [WorkflowStatus.APPROVED as any, 'APPROVED'] as any },
      },
      attributes: ['requestId'],
    });
    const approvedInitiatorRequestIds = approvedAsInitiator.map((r: any) => r.requestId);

    // Also include PENDING/PAUSED requests where the user is the initiator (e.g. Form 16 submissions with no approval level yet)
    const pendingAsInitiator = await WorkflowRequest.findAll({
      where: {
        initiatorId: userId,
        status: { [Op.in]: [WorkflowStatus.PENDING as any, WorkflowStatus.PAUSED as any, 'PENDING', 'PAUSED'] as any },
      },
      attributes: ['requestId'],
    });
    const pendingInitiatorRequestIds = pendingAsInitiator.map((r: any) => r.requestId);

    // Combine all request IDs (approver, spectator, approved as initiator, pending as initiator)
    let allOpenRequestIds = Array.from(new Set([...allRequestIds, ...approvedInitiatorRequestIds, ...pendingInitiatorRequestIds]));

    let reForm16OpenUsed = false;
    // RE user with FORM_16 filter: show all Form 16 open requests (org-wide), not just user's
    if (filters?.templateType?.toUpperCase() === 'FORM_16') {
      const { getDealerCodeForUser } = await import('./form16.service');
      const dealerCode = await getDealerCodeForUser(userId);
      if (!dealerCode) {
        reForm16OpenUsed = true;
        const openStatuses = [
          WorkflowStatus.PENDING as any,
          WorkflowStatus.APPROVED as any,
          WorkflowStatus.PAUSED as any,
          'PENDING',
          'IN_PROGRESS',
          'APPROVED',
          'PAUSED'
        ] as any;
        const form16Where: any = { templateType: 'FORM_16', status: { [Op.in]: openStatuses } };
        const allForm16 = await WorkflowRequest.findAll({ where: form16Where, attributes: ['requestId'] });
        let reForm16Ids = allForm16.map((r: any) => r.requestId);
        if (filters.financialYear || filters.quarter) {
          const subWhere: any = {};
          if (filters.financialYear) subWhere.financialYear = filters.financialYear;
          if (filters.quarter) subWhere.quarter = filters.quarter;
          const subRows = await Form16aSubmission.findAll({ where: subWhere, attributes: ['requestId'] });
          const subIds = subRows.map((r: any) => r.requestId);
          reForm16Ids = reForm16Ids.filter((id) => subIds.includes(id));
        }
        allOpenRequestIds = reForm16Ids.length > 0 ? reForm16Ids : ['00000000-0000-0000-0000-000000000000'];
      }
    }

    // When filtering by Form 16 with FY/quarter (for dealer only), restrict to request IDs that have matching form16a_submissions
    if (!reForm16OpenUsed && filters?.templateType?.toUpperCase() === 'FORM_16' && (filters.financialYear || filters.quarter)) {
      const form16Where: any = {};
      if (filters.financialYear) form16Where.financialYear = filters.financialYear;
      if (filters.quarter) form16Where.quarter = filters.quarter;
      const form16Rows = await Form16aSubmission.findAll({ where: form16Where, attributes: ['requestId'] });
      const form16RequestIds = form16Rows.map((r: any) => r.requestId);
      if (form16RequestIds.length > 0) {
        allOpenRequestIds = allOpenRequestIds.filter((id) => form16RequestIds.includes(id));
      } else {
        allOpenRequestIds = [];
      }
    }

    // Form 16 only: for dealers, exclude Form 16 from Open Requests (they see Form 16 on All Requests only)
    const { getDealerCodeForUser } = await import('./form16.service');
    const dealerCodeOpen = await getDealerCodeForUser(userId);
    if (dealerCodeOpen && allOpenRequestIds.length > 0 && (!filters?.templateType || (filters.templateType || '').toUpperCase() !== 'FORM_16')) {
      const form16Open = await WorkflowRequest.findAll({
        where: { requestId: { [Op.in]: allOpenRequestIds }, templateType: 'FORM_16' },
        attributes: ['requestId'],
      });
      const form16OpenIds = new Set(form16Open.map((r: any) => r.requestId));
      allOpenRequestIds = allOpenRequestIds.filter((id) => !form16OpenIds.has(id));
    }

    // Form 16 only: for RE users (no dealer code), include all open Form 16 requests so they appear in Open Requests without needing to filter by Form 16
    if (!dealerCodeOpen && allOpenRequestIds.length >= 0) {
      const form16OpenStatuses = [
        WorkflowStatus.PENDING as any,
        WorkflowStatus.APPROVED as any,
        WorkflowStatus.PAUSED as any,
        'PENDING',
        'IN_PROGRESS',
        'APPROVED',
        'PAUSED'
      ] as any;
      const form16Open = await WorkflowRequest.findAll({
        where: { templateType: 'FORM_16', status: { [Op.in]: form16OpenStatuses } },
        attributes: ['requestId'],
      });
      const form16OpenIds = form16Open.map((r: any) => r.requestId);
      allOpenRequestIds = Array.from(new Set([...allOpenRequestIds, ...form16OpenIds]));
    }

    // Build base where conditions
    const baseConditions: any[] = [];

    // Add the main OR condition for request IDs
    if (allOpenRequestIds.length > 0) {
      baseConditions.push({
        requestId: { [Op.in]: allOpenRequestIds }
      });
    } else {
      // No matching requests
      baseConditions.push({
        requestId: { [Op.in]: ['00000000-0000-0000-0000-000000000000'] }
      });
    }

    // Add status condition - include PAUSED so paused requests are shown
    baseConditions.push({
      [Op.or]: [
        {
          status: {
            [Op.in]: [
              WorkflowStatus.PENDING as any,
              WorkflowStatus.APPROVED as any,
              WorkflowStatus.PAUSED as any,
              'PENDING',
              'IN_PROGRESS', // Legacy support - will be migrated to PENDING
              'APPROVED',
              'PAUSED'
            ] as any
          }
        },
        // Also include requests with isPaused = true (even if status is PENDING)
        {
          isPaused: true
        }
      ]
    });

    // Apply status filter if provided (overrides default status filter)
    if (filters?.status && filters.status !== 'all') {
      const statusUpper = filters.status.toUpperCase();
      baseConditions.pop(); // Remove default status condition

      if (statusUpper === 'PAUSED') {
        // For paused filter, include both PAUSED status and isPaused flag
        baseConditions.push({
          [Op.or]: [
            { status: 'PAUSED' },
            { isPaused: true }
          ]
        });
      } else {
        // For other statuses, filter normally but exclude paused requests
        baseConditions.push({
          [Op.and]: [
            { status: statusUpper },
            {
              [Op.or]: [
                { isPaused: { [Op.is]: null } },
                { isPaused: false }
              ]
            }
          ]
        });
      }
    }

    // Apply priority filter
    if (filters?.priority && filters.priority !== 'all') {
      baseConditions.push({ priority: filters.priority.toUpperCase() });
    }

    // Apply templateType filter
    if (filters?.templateType && filters.templateType !== 'all') {
      const templateTypeUpper = filters.templateType.toUpperCase();
      // For CUSTOM, also include null values (legacy requests without templateType)
      if (templateTypeUpper === 'CUSTOM') {
        baseConditions.push({
          [Op.or]: [
            { templateType: 'CUSTOM' },
            { templateType: null }
          ]
        });
      } else {
        baseConditions.push({ templateType: templateTypeUpper });
      }
    }

    // Apply search filter (title, description, or requestNumber)
    if (filters?.search && filters.search.trim()) {
      baseConditions.push({
        [Op.or]: [
          { title: { [Op.iLike]: `%${filters.search.trim()}%` } },
          { description: { [Op.iLike]: `%${filters.search.trim()}%` } },
          { requestNumber: { [Op.iLike]: `%${filters.search.trim()}%` } }
        ]
      });
    }

    const where = baseConditions.length > 0 ? { [Op.and]: baseConditions } : {};

    // Build order clause based on sortBy parameter
    // For computed fields (due, sla), we'll sort after enrichment
    let order: any[] = [['createdAt', 'DESC']]; // Default order
    const validSortOrder = (sortOrder?.toLowerCase() === 'asc' ? 'ASC' : 'DESC');

    if (sortBy) {
      switch (sortBy.toLowerCase()) {
        case 'created':
          order = [['createdAt', validSortOrder]];
          break;
        case 'priority':
          // Map priority values: EXPRESS = 1, STANDARD = 2 for ascending (standard first), or reverse for descending
          // For simplicity, we'll sort alphabetically: EXPRESS < STANDARD
          order = [['priority', validSortOrder], ['createdAt', 'DESC']]; // Secondary sort by createdAt
          break;
        // For 'due' and 'sla', we need to sort after enrichment (handled below)
        case 'due':
        case 'sla':
          // Keep default order - will sort after enrichment
          break;
        default:
          // Unknown sortBy, use default
          break;
      }
    }

    // For computed field sorting (due, sla), we need to fetch all matching records first,
    // enrich them, sort, then paginate. For DB fields, we can use SQL pagination.
    const needsPostEnrichmentSort = sortBy && ['due', 'sla'].includes(sortBy.toLowerCase());

    let rows: any[];
    let count: number;

    if (needsPostEnrichmentSort) {
      // Fetch all matching records (no pagination yet)
      const result = await WorkflowRequest.findAndCountAll({
        where,
        include: [
          { association: 'initiator', required: false, attributes: ['userId', 'email', 'displayName', 'department', 'designation'] },
        ],
      });

      // Enrich all records
      const allEnriched = await this.enrichForCards(result.rows);

      // Sort enriched data
      allEnriched.sort((a: any, b: any) => {
        let aValue: any, bValue: any;

        if (sortBy.toLowerCase() === 'due') {
          aValue = a.currentLevelSLA?.deadline ? new Date(a.currentLevelSLA.deadline).getTime() : Number.MAX_SAFE_INTEGER;
          bValue = b.currentLevelSLA?.deadline ? new Date(b.currentLevelSLA.deadline).getTime() : Number.MAX_SAFE_INTEGER;
        } else if (sortBy.toLowerCase() === 'sla') {
          aValue = a.currentLevelSLA?.percentageUsed || 0;
          bValue = b.currentLevelSLA?.percentageUsed || 0;
        } else {
          return 0;
        }

        if (validSortOrder === 'ASC') {
          return aValue > bValue ? 1 : -1;
        } else {
          return aValue < bValue ? 1 : -1;
        }
      });

      count = result.count;

      // Apply pagination after sorting
      const startIndex = offset;
      const endIndex = startIndex + limit;
      rows = allEnriched.slice(startIndex, endIndex);
    } else {
      // Use database sorting for simple fields (created, priority)
      const result = await WorkflowRequest.findAndCountAll({
        where,
        offset,
        limit,
        order,
        include: [
          { association: 'initiator', required: false, attributes: ['userId', 'email', 'displayName', 'department', 'designation'] },
        ],
      });
      rows = result.rows;
      count = result.count;
    }

    const data = needsPostEnrichmentSort ? rows : await this.enrichForCards(rows);
    return { data, pagination: { page, limit, total: count, totalPages: Math.ceil(count / limit) || 1 } };
  }

  async listClosedByMe(userId: string, page: number, limit: number, filters?: { search?: string; status?: string; priority?: string; templateType?: string; financialYear?: string; quarter?: string }, sortBy?: string, sortOrder?: string) {
    const offset = (page - 1) * limit;

    // Get requests where user participated as approver
    const levelRows = await ApprovalLevel.findAll({
      where: {
        approverId: userId,
        status: {
          [Op.in]: [
            ApprovalStatus.APPROVED as any,
            (ApprovalStatus as any).REJECTED ?? 'REJECTED',
            'APPROVED',
            'REJECTED'
          ] as any
        },
      },
      attributes: ['requestId'],
    });
    const approverRequestIds = Array.from(new Set(levelRows.map((l: any) => l.requestId)));

    // Also include requests where user is a spectator
    const spectatorParticipants = await Participant.findAll({
      where: {
        userId,
        participantType: 'SPECTATOR',
      },
      attributes: ['requestId'],
    });
    const spectatorRequestIds = spectatorParticipants.map((p: any) => p.requestId);

    // Combine both sets of request IDs (unique)
    const allRequestIds = Array.from(new Set([...approverRequestIds, ...spectatorRequestIds]));

    // Build query conditions
    const whereConditions: any[] = [];

    // 1. Requests where user was approver/spectator (show ONLY CLOSED)
    // Closed requests are the final state after approval/rejection + conclusion
    const closedStatus = [
      (WorkflowStatus as any).CLOSED ?? 'CLOSED',
      'CLOSED'
    ] as any;

    if (allRequestIds.length > 0) {
      const approverConditionParts: any[] = [
        { requestId: { [Op.in]: allRequestIds } },
        { status: { [Op.in]: closedStatus } } // Only CLOSED requests
      ];

      // Apply closure type filter (approved/rejected before closure)
      if (filters?.status && filters?.status !== 'all') {
        const filterStatus = filters.status.toLowerCase();
        if (filterStatus === 'rejected') {
          // Closed after rejection: has at least one REJECTED approval level
          approverConditionParts.push({
            [Op.and]: [
              literal(`EXISTS (
                SELECT 1 FROM approval_levels al 
                WHERE al.request_id = "WorkflowRequest"."request_id" 
                AND al.status = 'REJECTED'
              )`)
            ]
          });
        } else if (filterStatus === 'approved') {
          // Closed after approval: no REJECTED levels (all approved)
          approverConditionParts.push({
            [Op.and]: [
              literal(`NOT EXISTS (
                SELECT 1 FROM approval_levels al 
                WHERE al.request_id = "WorkflowRequest"."request_id" 
                AND al.status = 'REJECTED'
              )`)
            ]
          });
        }
      }

      // Apply priority filter
      if (filters?.priority && filters.priority !== 'all') {
        approverConditionParts.push({ priority: filters.priority.toUpperCase() });
      }

      // Apply templateType filter
      if (filters?.templateType && filters.templateType !== 'all') {
        const templateTypeUpper = filters.templateType.toUpperCase();
        // For CUSTOM, also include null values (legacy requests without templateType)
        if (templateTypeUpper === 'CUSTOM') {
          approverConditionParts.push({
            [Op.or]: [
              { templateType: 'CUSTOM' },
              { templateType: null }
            ]
          });
        } else {
          approverConditionParts.push({ templateType: templateTypeUpper });
        }
      }

      // Apply search filter (title, description, or requestNumber)
      if (filters?.search && filters.search.trim()) {
        approverConditionParts.push({
          [Op.or]: [
            { title: { [Op.iLike]: `%${filters.search.trim()}%` } },
            { description: { [Op.iLike]: `%${filters.search.trim()}%` } },
            { requestNumber: { [Op.iLike]: `%${filters.search.trim()}%` } }
          ]
        });
      }

      const approverCondition = approverConditionParts.length > 0
        ? { [Op.and]: approverConditionParts }
        : { requestId: { [Op.in]: allRequestIds } };

      whereConditions.push(approverCondition);
    }

    // 2. Requests where user is initiator (show ONLY CLOSED)
    //    CLOSED means request has been finalized with conclusion
    const initiatorStatuses = [
      (WorkflowStatus as any).CLOSED ?? 'CLOSED',
      'CLOSED'
    ] as any;

    const initiatorConditionParts: any[] = [
      { initiatorId: userId },
      { status: { [Op.in]: initiatorStatuses } } // Only CLOSED requests
    ];

    // Apply closure type filter (approved/rejected before closure)
    if (filters?.status && filters?.status !== 'all') {
      const filterStatus = filters.status.toLowerCase();
      if (filterStatus === 'rejected') {
        // Closed after rejection: has at least one REJECTED approval level
        initiatorConditionParts.push({
          [Op.and]: [
            literal(`EXISTS (
              SELECT 1 FROM approval_levels al 
              WHERE al.request_id = "WorkflowRequest"."request_id" 
              AND al.status = 'REJECTED'
            )`)
          ]
        });
      } else if (filterStatus === 'approved') {
        // Closed after approval: no REJECTED levels (all approved)
        initiatorConditionParts.push({
          [Op.and]: [
            literal(`NOT EXISTS (
              SELECT 1 FROM approval_levels al 
              WHERE al.request_id = "WorkflowRequest"."request_id" 
              AND al.status = 'REJECTED'
            )`)
          ]
        });
      }
    }

    // Apply priority filter
    if (filters?.priority && filters.priority !== 'all') {
      initiatorConditionParts.push({ priority: filters.priority.toUpperCase() });
    }

    // Apply templateType filter
    if (filters?.templateType && filters.templateType !== 'all') {
      const templateTypeUpper = filters.templateType.toUpperCase();
      // For CUSTOM, also include null values (legacy requests without templateType)
      if (templateTypeUpper === 'CUSTOM') {
        initiatorConditionParts.push({
          [Op.or]: [
            { templateType: 'CUSTOM' },
            { templateType: null }
          ]
        });
      } else {
        initiatorConditionParts.push({ templateType: templateTypeUpper });
      }
    }

    // Apply search filter (title, description, or requestNumber)
    if (filters?.search && filters.search.trim()) {
      initiatorConditionParts.push({
        [Op.or]: [
          { title: { [Op.iLike]: `%${filters.search.trim()}%` } },
          { description: { [Op.iLike]: `%${filters.search.trim()}%` } },
          { requestNumber: { [Op.iLike]: `%${filters.search.trim()}%` } }
        ]
      });
    }

    const initiatorCondition = initiatorConditionParts.length > 0
      ? { [Op.and]: initiatorConditionParts }
      : { initiatorId: userId };

    whereConditions.push(initiatorCondition);

    // When filtering by Form 16 with FY/quarter, restrict to request IDs that have matching form16a_submissions
    let where: any = whereConditions.length > 0 ? { [Op.or]: whereConditions } : {};
    let reForm16ClosedUsed = false;
    // RE user with FORM_16 filter: show all closed Form 16 requests (org-wide)
    if (filters?.templateType?.toUpperCase() === 'FORM_16') {
      const { getDealerCodeForUser } = await import('./form16.service');
      const dealerCode = await getDealerCodeForUser(userId);
      if (!dealerCode) {
        reForm16ClosedUsed = true;
        const closedStatuses = [(WorkflowStatus as any).CLOSED ?? 'CLOSED', 'CLOSED'] as any;
        where = { templateType: 'FORM_16', status: { [Op.in]: closedStatuses } };
        if (filters.financialYear || filters.quarter) {
          const form16Where: any = {};
          if (filters.financialYear) form16Where.financialYear = filters.financialYear;
          if (filters.quarter) form16Where.quarter = filters.quarter;
          const form16Rows = await Form16aSubmission.findAll({ where: form16Where, attributes: ['requestId'] });
          const form16RequestIds = form16Rows.map((r: any) => r.requestId);
          if (form16RequestIds.length > 0) {
            where = { [Op.and]: [where, { requestId: { [Op.in]: form16RequestIds } }] };
          } else {
            where = { [Op.and]: [where, { requestId: { [Op.in]: ['00000000-0000-0000-0000-000000000000'] } }] };
          }
        }
        if (filters?.status && filters?.status !== 'all') {
          const s = filters.status.toLowerCase();
          if (s === 'rejected') {
            where = { [Op.and]: [where, literal(`EXISTS (SELECT 1 FROM approval_levels al WHERE al.request_id = "WorkflowRequest"."request_id" AND al.status = 'REJECTED')`)] };
          } else if (s === 'approved') {
            where = { [Op.and]: [where, literal(`NOT EXISTS (SELECT 1 FROM approval_levels al WHERE al.request_id = "WorkflowRequest"."request_id" AND al.status = 'REJECTED')`)] };
          }
        }
        if (filters?.priority && filters?.priority !== 'all') {
          where = { [Op.and]: [where, { priority: filters.priority.toUpperCase() }] };
        }
        if (filters?.search && filters?.search?.trim()) {
          where = { [Op.and]: [where, { [Op.or]: [{ title: { [Op.iLike]: `%${filters.search.trim()}%` } }, { description: { [Op.iLike]: `%${filters.search.trim()}%` } }, { requestNumber: { [Op.iLike]: `%${filters.search.trim()}%` } }] }] };
        }
      }
    }
    if (!reForm16ClosedUsed && filters?.templateType?.toUpperCase() === 'FORM_16' && (filters.financialYear || filters.quarter)) {
      const form16Where: any = {};
      if (filters.financialYear) form16Where.financialYear = filters.financialYear;
      if (filters.quarter) form16Where.quarter = filters.quarter;
      const form16Rows = await Form16aSubmission.findAll({ where: form16Where, attributes: ['requestId'] });
      const form16RequestIds = form16Rows.map((r: any) => r.requestId);
      if (form16RequestIds.length > 0) {
        where = { [Op.and]: [where, { requestId: { [Op.in]: form16RequestIds } }] };
      } else {
        where = { requestId: { [Op.in]: ['00000000-0000-0000-0000-000000000000'] } };
      }
    }

    // Build where clause with OR conditions (already built above)

    // Build order clause based on sortBy parameter
    let order: any[] = [['createdAt', 'DESC']]; // Default order
    const validSortOrder = (sortOrder?.toLowerCase() === 'asc' ? 'ASC' : 'DESC');

    if (sortBy) {
      switch (sortBy.toLowerCase()) {
        case 'created':
          order = [['createdAt', validSortOrder]];
          break;
        case 'due':
          // Sort by closureDate or updatedAt (closed date)
          order = [['updatedAt', validSortOrder], ['createdAt', 'DESC']];
          break;
        case 'priority':
          order = [['priority', validSortOrder], ['createdAt', 'DESC']];
          break;
        default:
          // Unknown sortBy, use default
          break;
      }
    }

    // Fetch only CLOSED requests (already finalized with conclusion)
    const { rows, count } = await WorkflowRequest.findAndCountAll({
      where,
      offset,
      limit,
      order,
      include: [
        { association: 'initiator', required: false, attributes: ['userId', 'email', 'displayName', 'department', 'designation'] },
      ],
    });

    // Enrich with SLA and closure type
    const enrichedData = await this.enrichForCards(rows);

    return {
      data: enrichedData,
      pagination: {
        page,
        limit,
        total: count,
        totalPages: Math.ceil(count / limit) || 1
      }
    };
  }
  async createWorkflow(
    initiatorId: string,
    workflowData: CreateWorkflowRequest,
    requestMetadata?: { ipAddress?: string | null; userAgent?: string | null; transaction?: any }
  ): Promise<WorkflowRequest> {
    try {
      const requestNumber = await generateRequestNumber();
      const totalTatHours = workflowData.approvalLevels.reduce((sum, level) => sum + level.tatHours, 0);
      const isDraftRequested = workflowData.isDraft === true;
      const initialStatus = isDraftRequested ? WorkflowStatus.DRAFT : WorkflowStatus.PENDING;
      const now = new Date();
      const transaction = requestMetadata?.transaction;

      const sanitizedTitle = workflowData.title ? sanitizeStrict(workflowData.title) : workflowData.title;
      const sanitizedDescription = workflowData.description ? sanitizePermissive(workflowData.description) : workflowData.description;

      if (!isDraftRequested && !sanitizedTitle) {
        throw new Error('A valid title is required. Please ensure the title contains valid content.');
      }

      if (!isDraftRequested && (!sanitizedDescription || isHtmlEmpty(sanitizedDescription))) {
        throw new Error('A valid description is required. Please ensure the description contains valid content.');
      }

      const workflow = await WorkflowRequest.create({
        requestNumber,
        initiatorId,
        templateType: workflowData.templateType,
        workflowType: workflowData.workflowType || 'NON_TEMPLATIZED',
        title: sanitizedTitle,
        description: sanitizedDescription,
        priority: workflowData.priority,
        status: initialStatus,
        currentLevel: 1,
        totalLevels: workflowData.approvalLevels.length,
        totalTatHours,
        isDraft: isDraftRequested,
        isDeleted: false,
        submissionDate: isDraftRequested ? undefined : now
      }, { transaction });

      // Create approval levels if skipCreation is false
      if (!workflowData.skipCreation) {
        for (const levelData of workflowData.approvalLevels) {
          await ApprovalLevel.create({
            requestId: workflow.requestId,
            levelNumber: levelData.levelNumber,
            levelName: levelData.levelName,
            approverId: levelData.approverId,
            approverEmail: levelData.approverEmail,
            approverName: levelData.approverName,
            tatHours: levelData.tatHours,
            // tatDays is auto-calculated by database as a generated column
            status: ApprovalStatus.PENDING,
            elapsedHours: 0,
            remainingHours: levelData.tatHours,
            tatPercentageUsed: 0,
            isFinalApprover: levelData.isFinalApprover || false
          }, { transaction });
        }
      }

      // Create participants if provided and skipCreation is false
      if (workflowData.participants && !workflowData.skipCreation) {
        const participantMap = new Map<string, typeof workflowData.participants[0]>();
        const rolePriority: Record<string, number> = {
          'INITIATOR': 3,
          'APPROVER': 2,
          'SPECTATOR': 1
        };

        for (const participantData of workflowData.participants) {
          const existing = participantMap.get(participantData.userId);

          if (existing) {
            // User already exists, check if we should replace with higher priority role
            const existingPriority = rolePriority[existing.participantType] || 0;
            const newPriority = rolePriority[participantData.participantType] || 0;

            if (newPriority > existingPriority) {
              logger.info(`[Workflow] User ${participantData.userId} (${participantData.userEmail}) has multiple roles. Keeping ${participantData.participantType} over ${existing.participantType}`);
              participantMap.set(participantData.userId, participantData);
            } else {
              logger.info(`[Workflow] User ${participantData.userId} (${participantData.userEmail}) has multiple roles. Keeping ${existing.participantType} over ${participantData.participantType}`);
            }
          } else {
            participantMap.set(participantData.userId, participantData);
          }
        }

        for (const participantData of participantMap.values()) {
          await Participant.create({
            requestId: workflow.requestId,
            userId: participantData.userId,
            userEmail: participantData.userEmail,
            userName: participantData.userName,
            participantType: (participantData.participantType as unknown as ParticipantType),
            canComment: participantData.canComment ?? true,
            canViewDocuments: participantData.canViewDocuments ?? true,
            canDownloadDocuments: participantData.canDownloadDocuments ?? false,
            notificationEnabled: participantData.notificationEnabled ?? true,
            addedBy: initiatorId,
            isActive: true
          }, { transaction });
        }
      }

      logWorkflowEvent('created', workflow.requestId, {
        requestNumber,
        priority: workflowData.priority,
        userId: initiatorId,
        status: workflow.status,
      });

      // Get initiator details
      const initiator = await User.findByPk(initiatorId);
      const initiatorName = (initiator as any)?.displayName || (initiator as any)?.email || 'User';

      // Log creation activity
      activityService.log({
        requestId: (workflow as any).requestId,
        type: 'created',
        user: { userId: initiatorId, name: initiatorName },
        timestamp: new Date().toISOString(),
        action: isDraftRequested ? 'Draft request created' : 'Initial request submitted',
        details: isDraftRequested
          ? `Draft request "${workflowData.title}" created by ${initiatorName}`
          : `Initial request submitted for ${workflowData.title} by ${initiatorName}`,
        ipAddress: requestMetadata?.ipAddress || undefined,
        userAgent: requestMetadata?.userAgent || undefined
      });

      // If not a draft, initiate the workflow (approvals, notifications, etc.)
      if (!isDraftRequested) {
        return await this.internalSubmitWorkflow(workflow, now);
      }

      return workflow;
    } catch (error: any) {
      logWithContext('error', 'Failed to create workflow', {
        userId: initiatorId,
        priority: workflowData.priority,
        errorMessage: error.message,
        errorStack: error.stack,
        error: error, // Keep for full object in some loggers
      });
      throw new Error(`Failed to create workflow: ${error.message}`);
    }
  }

  // Helper to determine if identifier is UUID or requestNumber
  private isUuid(identifier: string): boolean {
    const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
    return uuidRegex.test(identifier);
  }

  // Helper to find workflow by either requestId or requestNumber
  private async findWorkflowByIdentifier(identifier: string) {
    if (this.isUuid(identifier)) {
      return await WorkflowRequest.findByPk(identifier);
    } else {
      return await WorkflowRequest.findOne({
        where: { requestNumber: identifier }
      });
    }
  }

  async getWorkflowById(requestId: string): Promise<WorkflowRequest | null> {
    try {
      const workflow = await this.findWorkflowByIdentifier(requestId);
      if (!workflow) return null;

      return await WorkflowRequest.findByPk(workflow.requestId, {
        include: [
          { association: 'initiator' },
          { association: 'approvalLevels' },
          { association: 'participants' },
          { association: 'documents' }
        ]
      });
    } catch (error) {
      logger.error(`Failed to get workflow ${requestId}:`, error);
      throw new Error('Failed to get workflow');
    }
  }

  /**
   * Check if a user has access to view a specific request.
   * User has access if they are:
   * 1. Admin/Management (has management access)
   * 2. The initiator of the request
   * 3. An approver at any level of the request
   * 4. A spectator/participant of the request
   * 
   * @param userId - The user ID to check access for
   * @param requestId - The request ID or request number
   * @returns Object with hasAccess boolean and reason string
   */
  async checkUserRequestAccess(userId: string, requestId: string): Promise<{ hasAccess: boolean; reason?: string }> {
    try {
      // First, find the workflow
      const workflowBase = await this.findWorkflowByIdentifier(requestId);
      if (!workflowBase) {
        return { hasAccess: false, reason: 'Request not found' };
      }

      const actualRequestId = (workflowBase as any).getDataValue
        ? (workflowBase as any).getDataValue('requestId')
        : (workflowBase as any).requestId;

      // Check 1: Is the user an admin/management?
      const user = await User.findByPk(userId);
      if (user && user.hasManagementAccess()) {
        return { hasAccess: true };
      }

      // Check 2: Is the user the initiator?
      const initiatorId = (workflowBase as any).initiatorId || (workflowBase as any).initiator_id;
      if (initiatorId === userId) {
        return { hasAccess: true };
      }

      // Check 3: Is the user an approver at any level?
      const isApprover = await ApprovalLevel.findOne({
        where: {
          requestId: actualRequestId,
          approverId: userId
        }
      });
      if (isApprover) {
        return { hasAccess: true };
      }

      // Check 4: Is the user a spectator/participant?
      const isParticipant = await Participant.findOne({
        where: {
          requestId: actualRequestId,
          userId: userId
        }
      });
      if (isParticipant) {
        return { hasAccess: true };
      }

      // Check 5: [Form 16 only] For FORM_16 requests, allow if user has Form 16 submission view permission (admin config).
      // Does not affect other workflow types: runs only when templateType is FORM_16.
      const templateType = (workflowBase as any).templateType ?? (workflowBase as any).template_type;
      if (templateType && String(templateType).toUpperCase() === 'FORM_16') {
        const u = user ?? await User.findByPk(userId, { attributes: ['email', 'role'] });
        if (u?.email && await canViewForm16Submission((u as any).email, userId, (u as any).role)) {
          return { hasAccess: true };
        }
      }

      // No access
      return {
        hasAccess: false,
        reason: 'You do not have permission to view this request. Access is restricted to the initiator, approvers, and spectators of this request.'
      };
    } catch (error) {
      logger.error(`Failed to check user access for request ${requestId}:`, error);
      throw new Error('Failed to verify access permissions');
    }
  }

  async getWorkflowDetails(requestId: string) {
    try {
      const workflowBase = await this.findWorkflowByIdentifier(requestId);
      if (!workflowBase) {
        logger.warn(`Workflow not found for identifier: ${requestId}`);
        return null;
      }

      // Get requestId - try both property access and getDataValue for safety
      const actualRequestId = (workflowBase as any).getDataValue
        ? (workflowBase as any).getDataValue('requestId')
        : (workflowBase as any).requestId;

      if (!actualRequestId) {
        logger.error(`Could not extract requestId from workflow. Identifier: ${requestId}, Workflow data:`, JSON.stringify(workflowBase, null, 2));
        throw new Error('Failed to extract requestId from workflow');
      }

      // Reload with associations
      const workflow = await WorkflowRequest.findByPk(actualRequestId, {
        include: [{ association: 'initiator' }]
      });
      if (!workflow) return null;

      // Compute current approver and SLA summary (same logic used in lists)
      // When paused, use the workflow's currentLevel field directly to get the paused level
      // Otherwise, find the first PENDING/IN_PROGRESS level
      const workflowCurrentLevel = (workflow as any).currentLevel;
      const isPaused = (workflow as any).isPaused || (workflow as any).status === 'PAUSED';

      let currentLevel: ApprovalLevel | null = null;

      if (isPaused && workflowCurrentLevel) {
        // When paused, get the level at the workflow's currentLevel (the paused level)
        // This ensures we show SLA for the paused approver, not the next one
        currentLevel = await ApprovalLevel.findOne({
          where: {
            requestId: actualRequestId,
            levelNumber: workflowCurrentLevel,
          },
          include: [{ model: User, as: 'approver', attributes: ['userId', 'email', 'displayName'] }]
        });
      } else {
        // When not paused, find the first active level (exclude PAUSED to avoid showing wrong level)
        currentLevel = await ApprovalLevel.findOne({
          where: {
            requestId: actualRequestId,
            status: { [Op.in]: ['PENDING', 'IN_PROGRESS'] as any },
          },
          order: [['levelNumber', 'ASC']],
          include: [{ model: User, as: 'approver', attributes: ['userId', 'email', 'displayName'] }]
        });
      }

      // Fallback: if currentLevel not found but workflow has currentLevel, use it
      if (!currentLevel && workflowCurrentLevel) {
        currentLevel = await ApprovalLevel.findOne({
          where: {
            requestId: actualRequestId,
            levelNumber: workflowCurrentLevel,
          },
          include: [{ model: User, as: 'approver', attributes: ['userId', 'email', 'displayName'] }]
        });
      }

      const totalTat = Number((workflow as any).totalTatHours || 0);
      let percent = 0;
      let remainingText = '';
      if ((workflow as any).submissionDate && totalTat > 0) {
        const startedAt = new Date((workflow as any).submissionDate);
        const now = new Date();
        const elapsedHrs = Math.max(0, (now.getTime() - startedAt.getTime()) / (1000 * 60 * 60));
        percent = Math.min(100, Math.round((elapsedHrs / totalTat) * 100));
        const remaining = Math.max(0, totalTat - elapsedHrs);
        const days = Math.floor(remaining / 24);
        const hours = Math.floor(remaining % 24);
        remainingText = days > 0 ? `${days} days ${hours} hours remaining` : `${hours} hours remaining`;
      }

      const summary = {
        requestId: (workflow as any).requestId,
        requestNumber: (workflow as any).requestNumber,
        title: (workflow as any).title,
        status: (workflow as any).status,
        priority: (workflow as any).priority,
        submittedAt: (workflow as any).submissionDate,
        totalLevels: (workflow as any).totalLevels,
        // When paused, ensure we use the paused level's number, not the next level
        currentLevel: currentLevel ? (currentLevel as any).levelNumber : (isPaused ? workflowCurrentLevel : null),
        currentApprover: currentLevel ? {
          userId: (currentLevel as any).approverId,
          email: (currentLevel as any).approverEmail,
          name: (currentLevel as any).approverName,
        } : null,
        sla: { percent, remainingText },
      };

      // Ensure actualRequestId is valid UUID (not requestNumber)
      if (!actualRequestId || typeof actualRequestId !== 'string') {
        logger.error(`Invalid requestId extracted: ${actualRequestId}, original identifier: ${requestId}`);
        throw new Error('Invalid workflow identifier');
      }

      // Verify it's a UUID format
      const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
      if (!uuidRegex.test(actualRequestId)) {
        logger.error(`Extracted requestId is not a valid UUID: ${actualRequestId}, original identifier: ${requestId}`);
        throw new Error('Invalid workflow identifier format');
      }

      // logger.info(`Fetching participants for requestId: ${actualRequestId} (original identifier: ${requestId})`);

      // Load related entities explicitly to avoid alias issues
      // Use the actual UUID requestId for all queries
      const approvals = await ApprovalLevel.findAll({
        where: { requestId: actualRequestId },
        order: [['levelNumber', 'ASC']]
      }) as any[];

      const participants = await Participant.findAll({
        where: { requestId: actualRequestId }
      }) as any[];

      // logger.info(`Found ${participants.length} participants for requestId: ${actualRequestId}`);

      const documents = await Document.findAll({
        where: {
          requestId: actualRequestId,
          isDeleted: false // Only fetch non-deleted documents
        }
      }) as any[];
      let activities: any[] = [];
      try {
        const { Activity } = require('@models/Activity');
        const rawActivities = await Activity.findAll({
          where: {
            requestId: actualRequestId,
            activityType: { [Op.ne]: 'comment' } // Exclude comment type activities
          },
          order: [['created_at', 'ASC']],
          raw: true  // Get raw data to access snake_case fields
        });

        // Transform activities to match frontend expected format
        activities = rawActivities
          .filter((act: any) => {
            const activityType = act.activity_type || act.activityType || '';
            const description = (act.activity_description || act.activityDescription || '').toLowerCase();

            // Filter out status changes to pending
            if (activityType === 'status_change' && description.includes('pending')) {
              return false;
            }

            return true;
          })
          .map((act: any) => ({
            user: act.user_name || act.userName || 'System',
            type: act.activity_type || act.activityType || 'status_change',
            action: this.getActivityAction(act.activity_type || act.activityType),
            details: act.activity_description || act.activityDescription || '',
            timestamp: act.created_at || act.createdAt,
            metadata: act.metadata
          }));
      } catch (error) {
        logger.error('Error fetching activities:', error);
        activities = activityService.get(actualRequestId);
      }

      // Fetch TAT alerts for all approval levels
      let tatAlerts: any[] = [];
      try {
        // Use raw SQL query to ensure all fields are returned
        const rawAlerts = await sequelize.query(`
          SELECT 
            alert_id,
            request_id,
            level_id,
            approver_id,
            alert_type,
            threshold_percentage,
            tat_hours_allocated,
            tat_hours_elapsed,
            tat_hours_remaining,
            level_start_time,
            alert_sent_at,
            expected_completion_time,
            alert_message,
            notification_sent,
            notification_channels,
            is_breached,
            was_completed_on_time,
            completion_time,
            metadata,
            created_at
          FROM tat_alerts
          WHERE request_id = :requestId
          ORDER BY alert_sent_at ASC
        `, {
          replacements: { requestId: actualRequestId },
          type: QueryTypes.SELECT
        });

        // Transform to frontend format
        tatAlerts = (rawAlerts as any[]).map((alert: any) => ({
          alertId: alert.alert_id,
          requestId: alert.request_id,
          levelId: alert.level_id,
          approverId: alert.approver_id,
          alertType: alert.alert_type,
          thresholdPercentage: Number(alert.threshold_percentage || 0),
          tatHoursAllocated: Number(alert.tat_hours_allocated || 0),
          tatHoursElapsed: Number(alert.tat_hours_elapsed || 0),
          tatHoursRemaining: Number(alert.tat_hours_remaining || 0),
          levelStartTime: alert.level_start_time,
          alertSentAt: alert.alert_sent_at,
          expectedCompletionTime: alert.expected_completion_time,
          alertMessage: alert.alert_message,
          notificationSent: alert.notification_sent,
          notificationChannels: alert.notification_channels || [],
          isBreached: alert.is_breached,
          wasCompletedOnTime: alert.was_completed_on_time,
          completionTime: alert.completion_time,
          metadata: alert.metadata || {}
        }));

        // logger.info(`Found ${tatAlerts.length} TAT alerts for request ${actualRequestId}`);
      } catch (error) {
        logger.error('Error fetching TAT alerts:', error);
        tatAlerts = [];
      }

      // Recalculate SLA for all approval levels with comprehensive data
      const priority = ((workflow as any)?.priority || 'standard').toString().toLowerCase();
      const { calculateSLAStatus } = require('@utils/tatTimeUtils');

      const updatedApprovals = await Promise.all(approvals.map(async (approval: any) => {
        const status = (approval.status || '').toString().toUpperCase();
        const approvalData = approval.toJSON();
        const isPausedLevel = status === 'PAUSED' || approval.isPaused;
        const approvalLevelNumber = approval.levelNumber || 0;
        const workflowCurrentLevelNumber = currentLevel ? (currentLevel as any).levelNumber : ((workflow as any).currentLevel || 1);

        // Calculate SLA ONLY for the CURRENT active level (matching currentLevel)
        // This ensures that when in step 1, only step 1 has elapsed time, others have 0
        // Include PAUSED so we show SLA for the paused approver, not the next one
        const isCurrentLevel = approvalLevelNumber === workflowCurrentLevelNumber;
        const shouldCalculateSLA = isCurrentLevel && (status === 'PENDING' || status === 'IN_PROGRESS' || status === 'PAUSED');

        if (shouldCalculateSLA) {
          const levelStartTime = approval.levelStartTime || approval.tatStartTime || approval.createdAt;
          const tatHours = Number(approval.tatHours || 0);

          if (levelStartTime && tatHours > 0) {
            try {
              // Prepare pause info for SLA calculation
              // Case 1: Level is currently paused
              // Case 2: Level was paused and resumed (pauseElapsedHours and pauseResumeDate are set)
              const wasResumed = !isPausedLevel &&
                approval.pauseElapsedHours !== null &&
                approval.pauseElapsedHours !== undefined &&
                approval.pauseResumeDate !== null;

              const pauseInfo = isPausedLevel ? {
                isPaused: true,
                pausedAt: approval.pausedAt,
                pauseElapsedHours: approval.pauseElapsedHours,
                pauseResumeDate: approval.pauseResumeDate
              } : wasResumed ? {
                // Level was paused but has been resumed
                isPaused: false,
                pausedAt: null,
                pauseElapsedHours: Number(approval.pauseElapsedHours), // Pre-pause elapsed hours
                pauseResumeDate: approval.pauseResumeDate // Actual resume timestamp
              } : undefined;

              // Get comprehensive SLA status from backend utility
              const slaData = await calculateSLAStatus(levelStartTime, tatHours, priority, null, pauseInfo);

              // Return updated approval with comprehensive SLA data
              return {
                ...approvalData,
                elapsedHours: slaData.elapsedHours,
                remainingHours: slaData.remainingHours,
                tatPercentageUsed: slaData.percentageUsed,
                sla: slaData // ← Full SLA object with deadline, isPaused, status, etc.
              };
            } catch (error) {
              logger.error(`[Workflow] Error calculating SLA for level ${approval.levelNumber}:`, error);
              // Return with fallback values if SLA calculation fails
              return {
                ...approvalData,
                sla: {
                  elapsedHours: isPausedLevel ? (approval.pauseElapsedHours || 0) : 0,
                  remainingHours: tatHours,
                  percentageUsed: 0,
                  isPaused: isPausedLevel,
                  status: 'on_track',
                  remainingText: `${tatHours}h`,
                  elapsedText: '0h'
                }
              };
            }
          }
        }

        // For waiting levels (future levels that haven't started), set elapsedHours to 0
        // This ensures that when in step 1, steps 2-8 show elapsedHours = 0
        if (approvalLevelNumber > workflowCurrentLevelNumber && status !== 'APPROVED' && status !== 'REJECTED') {
          return {
            ...approvalData,
            elapsedHours: 0,
            remainingHours: Number(approval.tatHours || 0),
            tatPercentageUsed: 0,
          };
        }

        // For completed/rejected levels, return as-is (already has final values from database)
        return approvalData;
      }));

      // Calculate overall request SLA based on cumulative elapsed hours from all levels
      // This correctly accounts for pause periods since each level's elapsedHours is pause-adjusted
      // Use submissionDate if available, otherwise fallback to createdAt for SLA calculation
      const submissionDate = (workflow as any).submissionDate || (workflow as any).createdAt;
      const totalTatHours = updatedApprovals.reduce((sum, a) => sum + Number(a.tatHours || 0), 0);
      let overallSLA = null;

      if (submissionDate && totalTatHours > 0) {
        // Calculate total elapsed hours by summing elapsed hours from all levels
        //: Only count elapsed hours from completed levels + current active level
        // Waiting levels (future steps) should contribute 0 elapsed hours
        // This ensures that when in step 1, only step 1's elapsed hours are counted
        let totalElapsedHours = 0;
        const workflowCurrentLevelNumber = currentLevel ? (currentLevel as any).levelNumber : ((workflow as any).currentLevel || 1);

        for (const approval of updatedApprovals) {
          const status = (approval.status || '').toString().toUpperCase();
          const approvalLevelNumber = approval.levelNumber || 0;

          if (status === 'APPROVED' || status === 'REJECTED') {
            // For completed levels, use the stored elapsedHours (already pause-adjusted from when level was completed)
            totalElapsedHours += Number(approval.elapsedHours || 0);
          } else if (status === 'SKIPPED') {
            // Skipped levels don't contribute to elapsed time
            continue;
          } else if (status === 'PENDING' || status === 'IN_PROGRESS' || status === 'PAUSED') {
            //: Only count elapsed hours for the CURRENT active level
            // Waiting levels (future steps) should NOT contribute elapsed hours
            // This ensures request-level elapsed time matches the current step's elapsed time
            const isCurrentLevel = approvalLevelNumber === workflowCurrentLevelNumber;
            if (isCurrentLevel) {
              // For active/paused levels, use the SLA-calculated elapsedHours (pause-adjusted)
              if (approval.sla?.elapsedHours !== undefined) {
                totalElapsedHours += Number(approval.sla.elapsedHours);
              } else {
                totalElapsedHours += Number(approval.elapsedHours || 0);
              }
            }
            // Waiting levels (approvalLevelNumber > workflowCurrentLevelNumber) contribute 0 elapsed hours
          }
          // WAITING levels haven't started yet, so no elapsed time
        }

        // Calculate overall SLA metrics based on cumulative elapsed hours
        const totalRemainingHours = Math.max(0, totalTatHours - totalElapsedHours);
        const percentageUsed = totalTatHours > 0
          ? Math.min(100, Math.round((totalElapsedHours / totalTatHours) * 100))
          : 0;

        // Determine overall status
        let overallStatus: 'on_track' | 'approaching' | 'critical' | 'breached' = 'on_track';
        if (percentageUsed >= 100) {
          overallStatus = 'breached';
        } else if (percentageUsed >= 80) {
          overallStatus = 'critical';
        } else if (percentageUsed >= 60) {
          overallStatus = 'approaching';
        }

        // Format time display (simple format - frontend will handle detailed formatting)
        const formatTime = (hours: number) => {
          if (hours < 1) return `${Math.round(hours * 60)}m`;
          const wholeHours = Math.floor(hours);
          const minutes = Math.round((hours - wholeHours) * 60);
          if (minutes > 0) return `${wholeHours}h ${minutes}m`;
          return `${wholeHours}h`;
        };

        // Check if any level is currently paused
        const isAnyLevelPaused = updatedApprovals.some(a =>
          (a.status || '').toString().toUpperCase() === 'PAUSED' || a.isPaused === true
        );

        // Calculate deadline using the original method (for deadline display only)
        const { addWorkingHours, addWorkingHoursExpress } = require('@utils/tatTimeUtils');
        const deadline = priority === 'express'
          ? (await addWorkingHoursExpress(submissionDate, totalTatHours)).toDate()
          : (await addWorkingHours(submissionDate, totalTatHours)).toDate();

        overallSLA = {
          elapsedHours: totalElapsedHours,
          remainingHours: totalRemainingHours,
          percentageUsed,
          status: overallStatus,
          isPaused: isAnyLevelPaused,
          deadline: deadline.toISOString(),
          elapsedText: formatTime(totalElapsedHours),
          remainingText: formatTime(totalRemainingHours)
        };
      }

      // Update summary to include comprehensive SLA
      const updatedSummary = {
        ...summary,
        sla: overallSLA || summary.sla
      };

      // For Form 16 requests, attach form16 submission (for detail Overview) and ensure Documents tab has the uploaded Form 16
      let form16Submission: any = null;
      let documentsForReturn = documents;
      if (((workflow as any).templateType || '').toString().toUpperCase() === 'FORM_16') {
        const form16Row = await Form16aSubmission.findOne({
          where: { requestId: actualRequestId },
          attributes: ['id', 'dealerCode', 'form16aNumber', 'financialYear', 'quarter', 'version', 'status', 'validationStatus', 'validationNotes', 'tanNumber', 'deductorName', 'tdsAmount', 'totalAmount', 'ocrExtractedData', 'submittedDate', 'documentUrl'],
        });
        if (form16Row) {
          const row = form16Row as any;
          const { Form16CreditNote } = await import('@models/Form16CreditNote');
          const creditNote = await Form16CreditNote.findOne({
            where: { submissionId: row.id },
            attributes: ['creditNoteNumber'],
          });
          const hasCreditNote = !!creditNote && !!(creditNote as any).creditNoteNumber;
          const creditNoteNumber = (creditNote as any)?.creditNoteNumber ?? null;
          const toDisplayStatus = (): string => {
            if (hasCreditNote) return 'Completed';
            const v = (row.validationStatus || '').toLowerCase();
            const notes = (row.validationNotes || '').toLowerCase();
            if (v === 'resubmission_needed') return 'Resubmission needed';
            if (v === 'duplicate') return 'Duplicate';
            if (v === 'manually_approved') return 'Completed';
            if (v === 'failed' || row.status === 'failed') {
              if (notes.includes('mismatch') || notes.includes('26as') || notes.includes('value')) return 'Balance mismatch';
              if (notes.includes('partial')) return 'Partial extracted data';
              return 'Failed';
            }
            return 'Under review';
          };
          // Ensure Form 16 uploaded file appears in Documents tab (for dealer, RE, admin). Backfill if no document row exists.
          if (row.documentUrl && documents.length === 0) {
            try {
              await Document.create({
                requestId: actualRequestId,
                uploadedBy: (workflow as any).initiatorId,
                fileName: 'form16a.pdf',
                originalFileName: 'Form 16A.pdf',
                fileType: 'application/pdf',
                fileExtension: 'pdf',
                fileSize: 0,
                filePath: row.documentUrl.slice(0, 500),
                storageUrl: row.documentUrl.length <= 500 ? row.documentUrl : undefined,
                mimeType: 'application/pdf',
                checksum: 'form16-legacy-' + actualRequestId.slice(0, 8),
                isGoogleDoc: false,
                category: 'SUPPORTING',
                version: 1,
                isDeleted: false,
                downloadCount: 0,
                uploadedAt: (workflow as any).submissionDate || (workflow as any).createdAt || new Date(),
              });
              const docsRefetched = await Document.findAll({
                where: { requestId: actualRequestId, isDeleted: false },
              }) as any[];
              documentsForReturn = docsRefetched;
            } catch (backfillErr) {
              logger.warn('[Workflow] Form 16 document backfill failed for request ' + actualRequestId, backfillErr);
            }
          }
          const ocr = row.ocrExtractedData || {};
          form16Submission = {
            dealerCode: row.dealerCode,
            form16aNumber: row.form16aNumber,
            financialYear: row.financialYear,
            quarter: row.quarter,
            version: row.version ?? 1,
            status: row.status,
            validationStatus: row.validationStatus ?? null,
            validationNotes: row.validationNotes ?? null,
            displayStatus: toDisplayStatus(),
            creditNoteNumber,
            tanNumber: row.tanNumber,
            deductorName: row.deductorName,
            tdsAmount: row.tdsAmount,
            totalAmount: row.totalAmount,
            submittedDate: row.submittedDate,
            acknowledgementNumber: ocr.acknowledgementNumber ?? ocr.acknowledgement_number,
            dateOfIssue: ocr.dateOfIssue ?? ocr.date_of_issue,
            section: ocr.section ?? ocr.sectionCode,
            amountPaid: ocr.amountPaid ?? ocr.amount_paid,
            dateOfPayment: ocr.dateOfPayment ?? ocr.date_of_payment,
            bsrCode: ocr.bsrCode ?? ocr.bsr_code,
            challanSerialNo: ocr.challanSerialNo ?? ocr.challan_serial_no,
            dateOfDeposit: ocr.dateOfDeposit ?? ocr.date_of_deposit,
            deduceeName: ocr.deduceeName ?? ocr.deducee_name,
            deduceePan: ocr.deduceePan ?? ocr.deducee_pan,
            deduceeAddress: ocr.deduceeAddress ?? ocr.deducee_address,
            ocrExtractedData: row.ocrExtractedData ?? null,
          };
          // Previous submissions for same dealer+FY+quarter only (Documents tab shows only this FY+quarter for this request)
          const currentFy = row.financialYear ?? row.financial_year;
          const currentQuarter = row.quarter;
          const currentDealerCode = row.dealerCode ?? row.dealer_code;
          const previousRows = await Form16aSubmission.findAll({
            where: {
              dealerCode: currentDealerCode,
              financialYear: currentFy,
              quarter: currentQuarter,
              requestId: { [Op.ne]: actualRequestId },
            },
            attributes: ['id', 'requestId', 'submittedDate', 'validationStatus', 'documentUrl'],
            order: [['submittedDate', 'ASC']],
            raw: true,
          }) as any[];
          // raw: true may return request_id (snake_case); support both
          const previousRequestIds = previousRows.map((r) => r.requestId ?? r.request_id).filter(Boolean) as string[];
          const previousRequestIdSet = new Set(previousRequestIds);
          const previousWorkflows = previousRequestIds.length > 0
            ? await WorkflowRequest.findAll({
              where: { requestId: { [Op.in]: previousRequestIds } },
              attributes: ['requestId', 'requestNumber'],
              raw: true,
            }) as any[]
            : [];
          const requestNumberByRequestId = new Map<string, string>();
          for (const w of previousWorkflows) {
            const wid = (w as any).requestId ?? (w as any).request_id;
            const wnum = (w as any).requestNumber ?? (w as any).request_number;
            if (wid && wnum) requestNumberByRequestId.set(wid, wnum);
          }
          const submissionIdsPrev = previousRows.map((r) => r.id);
          const creditNotesPrev = submissionIdsPrev.length > 0
            ? await Form16CreditNote.findAll({
              where: { submissionId: submissionIdsPrev },
              attributes: ['submissionId', 'creditNoteNumber', 'issueDate'],
              raw: true,
            }) as any[]
            : [];
          const cnBySubId = new Map<number, { creditNoteNumber: string; issueDate?: string }>();
          for (const c of creditNotesPrev) {
            if (c.submissionId) cnBySubId.set(c.submissionId, { creditNoteNumber: c.creditNoteNumber || '', issueDate: c.issueDate });
          }
          const form16PreviousSubmissions = previousRows.map((r) => {
            const rReqId = r.requestId ?? r.request_id;
            const reqNum = requestNumberByRequestId.get(rReqId) || rReqId;
            const cn = cnBySubId.get(r.id);
            const hasCn = !!cn && !!cn.creditNoteNumber;
            const displayStatus = hasCn ? 'Completed' : (r.validationStatus === 'duplicate' ? 'Duplicate' : (r.validationStatus || 'Under review'));
            return {
              requestId: rReqId,
              requestNumber: reqNum,
              submittedDate: r.submittedDate ?? r.submitted_date,
              displayStatus,
              creditNoteNumber: cn?.creditNoteNumber ?? null,
              creditNoteIssueDate: cn?.issueDate ?? null,
            };
          });
          let form16PreviousDocuments: any[] = [];
          if (previousRequestIds.length > 0) {
            const prevDocs = await Document.findAll({
              where: { requestId: { [Op.in]: previousRequestIds }, isDeleted: false },
              raw: true,
            }) as any[];
            const reqNumByRequestId = Object.fromEntries([...requestNumberByRequestId]);
            // Only include documents whose request belongs to same FY+quarter (defensive filter)
            form16PreviousDocuments = prevDocs
              .filter((d) => previousRequestIdSet.has(d.requestId ?? d.request_id))
              .map((d) => {
                const reqId = d.requestId ?? d.request_id;
                return {
                  ...d,
                  requestId: reqId,
                  requestNumber: reqNumByRequestId[reqId] || reqId,
                };
              });
          }
          (form16Submission as any).previousSubmissions = form16PreviousSubmissions;
          (form16Submission as any).previousDocuments = form16PreviousDocuments;
        }
      }

      // Ensure documents array contains only this request's documents (no cross-request leakage)
      const safeDocuments = Array.isArray(documentsForReturn)
        ? documentsForReturn.filter((d: any) => (d.requestId ?? d.request_id) === actualRequestId)
        : documentsForReturn;

      return { workflow, approvals: updatedApprovals, participants, documents: safeDocuments, activities, summary: updatedSummary, tatAlerts, form16Submission };
    } catch (error) {
      logger.error(`Failed to get workflow details ${requestId}:`, error);
      throw new Error('Failed to get workflow details');
    }
  }

  async updateWorkflow(requestId: string, updateData: UpdateWorkflowRequest): Promise<WorkflowRequest | null> {
    try {
      // Sanitize title and description if provided
      if (updateData.title) {
        updateData.title = sanitizeStrict(updateData.title);
      }
      if (updateData.description) {
        updateData.description = sanitizePermissive(updateData.description);
      }
      if (updateData.conclusionRemark) {
        updateData.conclusionRemark = sanitizePermissive(updateData.conclusionRemark);
      }

      const workflow = await this.findWorkflowByIdentifier(requestId);
      if (!workflow) return null;

      const actualRequestId = (workflow as any).getDataValue
        ? (workflow as any).getDataValue('requestId')
        : (workflow as any).requestId;

      // Only allow full updates (approval levels, participants) for DRAFT workflows
      const isDraft = (workflow as any).status === WorkflowStatus.DRAFT || (workflow as any).isDraft;

      // Determine if this is a transition from draft to submitted
      const isTransitioningToSubmitted = updateData.isDraft === false && (workflow as any).isDraft;

      // Update basic workflow fields
      const basicUpdate: any = {};
      if (updateData.title) basicUpdate.title = updateData.title;
      if (updateData.description) basicUpdate.description = updateData.description;
      if (updateData.priority) basicUpdate.priority = updateData.priority;
      if (updateData.status) basicUpdate.status = updateData.status;
      if (updateData.conclusionRemark !== undefined) basicUpdate.conclusionRemark = updateData.conclusionRemark;
      if (updateData.isDraft !== undefined) basicUpdate.isDraft = updateData.isDraft;

      // If transitioning, ensure status and submissionDate are set
      if (isTransitioningToSubmitted) {
        basicUpdate.status = WorkflowStatus.PENDING;
        basicUpdate.submissionDate = new Date();
      }

      await workflow.update(basicUpdate);

      // Update approval levels if provided (only for drafts)
      if (isDraft && updateData.approvalLevels && Array.isArray(updateData.approvalLevels)) {
        // Delete all existing approval levels for this draft
        await ApprovalLevel.destroy({ where: { requestId: actualRequestId } });

        // Create new approval levels
        const totalTatHours = updateData.approvalLevels.reduce((sum, level) => sum + level.tatHours, 0);

        for (const levelData of updateData.approvalLevels) {
          await ApprovalLevel.create({
            requestId: actualRequestId,
            levelNumber: levelData.levelNumber,
            levelName: levelData.levelName || `Level ${levelData.levelNumber}`,
            approverId: levelData.approverId,
            approverEmail: levelData.approverEmail,
            approverName: levelData.approverName,
            tatHours: levelData.tatHours,
            // tatDays is auto-calculated by database as a generated column
            status: ApprovalStatus.PENDING,
            elapsedHours: 0,
            remainingHours: levelData.tatHours,
            tatPercentageUsed: 0,
            isFinalApprover: levelData.isFinalApprover || false
          });
        }

        // Update workflow totals
        await workflow.update({
          totalLevels: updateData.approvalLevels.length,
          totalTatHours,
          currentLevel: 1
        });

        logger.info(`Updated ${updateData.approvalLevels.length} approval levels for workflow ${actualRequestId}`);
      }

      // Update participants if provided (only for drafts)
      // IMPORTANT: Skip if participants array is empty - this means "don't update participants"
      // Frontend sends empty array when it expects backend to auto-generate, but we should preserve existing participants
      if (isDraft && updateData.participants && Array.isArray(updateData.participants) && updateData.participants.length > 0) {
        // Get existing participants
        const existingParticipants = await Participant.findAll({
          where: { requestId: actualRequestId }
        });

        // Create a map of existing participants by userId
        const existingMap = new Map(existingParticipants.map((p: any) => [
          (p as any).userId,
          p
        ]));

        // Create a set of new participant userIds
        const newUserIds = new Set(updateData.participants.map(p => p.userId));

        // Delete participants that are no longer in the new list (except INITIATOR)
        for (const existing of existingParticipants) {
          const userId = (existing as any).userId;
          const participantType = (existing as any).participantType;

          // Never delete INITIATOR
          if (participantType === 'INITIATOR') continue;

          // Delete if not in new list
          if (!newUserIds.has(userId)) {
            await existing.destroy();
            logger.info(`Deleted participant ${userId} from workflow ${actualRequestId}`);
          }
        }

        // Add or update participants from the new list
        for (const participantData of updateData.participants) {
          const existing = existingMap.get(participantData.userId);

          if (existing) {
            // Update existing participant
            await existing.update({
              userEmail: participantData.userEmail,
              userName: participantData.userName,
              participantType: participantData.participantType as any,
              canComment: participantData.canComment ?? true,
              canViewDocuments: participantData.canViewDocuments ?? true,
              canDownloadDocuments: participantData.canDownloadDocuments ?? false,
              notificationEnabled: participantData.notificationEnabled ?? true,
              isActive: true
            });
          } else {
            // Create new participant
            await Participant.create({
              requestId: actualRequestId,
              userId: participantData.userId,
              userEmail: participantData.userEmail,
              userName: participantData.userName,
              participantType: participantData.participantType as any,
              canComment: participantData.canComment ?? true,
              canViewDocuments: participantData.canViewDocuments ?? true,
              canDownloadDocuments: participantData.canDownloadDocuments ?? false,
              notificationEnabled: participantData.notificationEnabled ?? true,
              addedBy: (workflow as any).initiatorId,
              isActive: true
            });
            logger.info(`Added new participant ${participantData.userId} to workflow ${actualRequestId}`);
          }
        }

        logger.info(`Synced ${updateData.participants.length} participants for workflow ${actualRequestId}`);
      } else if (isDraft && updateData.participants && Array.isArray(updateData.participants) && updateData.participants.length === 0) {
        // Empty array means "preserve existing participants" - don't delete them
        logger.info(`[Workflow] Empty participants array provided for draft ${actualRequestId} - preserving existing participants`);
      }

      // Delete documents if requested (only for drafts)
      if (isDraft && updateData.deleteDocumentIds && updateData.deleteDocumentIds.length > 0) {
        logger.info(`Attempting to delete ${updateData.deleteDocumentIds.length} documents for workflow ${actualRequestId}. Document IDs:`, updateData.deleteDocumentIds);

        // First get documents with file paths before deleting
        const documentsToDelete = await Document.findAll({
          where: { requestId: actualRequestId, documentId: { [Op.in]: updateData.deleteDocumentIds } },
          attributes: ['documentId', 'originalFileName', 'filePath', 'isDeleted']
        });
        logger.info(`Found ${documentsToDelete.length} documents matching delete IDs. Existing:`, documentsToDelete.map((d: any) => ({ id: d.documentId, name: d.originalFileName, filePath: d.filePath, isDeleted: d.isDeleted })));

        // Delete physical files from filesystem
        for (const doc of documentsToDelete) {
          const filePath = (doc as any).filePath;
          if (filePath && fs.existsSync(filePath)) {
            try {
              fs.unlinkSync(filePath);
              logger.info(`Deleted physical file: ${filePath} for document ${(doc as any).documentId}`);
            } catch (error) {
              logger.error(`Failed to delete physical file ${filePath}:`, error);
              // Continue with soft-delete even if file deletion fails
            }
          } else if (filePath) {
            logger.warn(`File path does not exist, skipping file deletion: ${filePath}`);
          }
        }

        // Mark documents as deleted in database
        const deleteResult = await Document.update(
          { isDeleted: true },
          { where: { requestId: actualRequestId, documentId: { [Op.in]: updateData.deleteDocumentIds } } }
        );
        logger.info(`Marked ${deleteResult[0]} documents as deleted in database (out of ${updateData.deleteDocumentIds.length} requested)`);
      }

      // If transitioning, call the internal submission logic (notifications, TAT, etc.)
      if (isTransitioningToSubmitted) {
        logger.info(`[Workflow] Transitioning draft ${actualRequestId} to submitted state`);
        return await this.internalSubmitWorkflow(workflow, (workflow as any).submissionDate);
      }

      // Reload the workflow instance to get latest data
      const refreshed = await WorkflowRequest.findByPk(actualRequestId);
      return refreshed;
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      const errorStack = error instanceof Error ? error.stack : undefined;
      logger.error(`Failed to update workflow ${requestId}:`, {
        error: errorMessage,
        stack: errorStack,
        requestId,
        updateData: JSON.stringify(updateData, null, 2),
      });
      // Preserve original error message for better debugging
      throw new Error(`Failed to update workflow: ${errorMessage}`);
    }
  }

  async submitWorkflow(requestId: string): Promise<WorkflowRequest | null> {
    try {
      const workflow = await this.findWorkflowByIdentifier(requestId);
      if (!workflow) return null;

      const now = new Date();
      return await this.internalSubmitWorkflow(workflow, now);
    } catch (error) {
      logger.error(`Failed to submit workflow ${requestId}:`, error);
      throw new Error('Failed to submit workflow');
    }
  }

  /**
   * Internal method to handle workflow submission logic (status update, notifications, TAT scheduling)
   * Centralized here to support both direct creation-submission and draft-to-submission flows.
   */
  private async internalSubmitWorkflow(workflow: WorkflowRequest, now: Date): Promise<WorkflowRequest> {
    // Get the actual requestId (UUID) - handle both UUID and requestNumber cases
    const actualRequestId = (workflow as any).getDataValue
      ? (workflow as any).getDataValue('requestId')
      : (workflow as any).requestId;

    const updated = await workflow.update({
      status: WorkflowStatus.PENDING,
      isDraft: false,
      submissionDate: now
    });

    // Get initiator details for activity logging
    const initiatorId = (updated as any).initiatorId;
    const initiator = initiatorId ? await User.findByPk(initiatorId) : null;
    const initiatorName = (initiator as any)?.displayName || (initiator as any)?.email || 'User';
    const workflowTitle = (updated as any).title || 'Request';
    const requestNumber = (updated as any).requestNumber;

    // Check if this was a previously saved draft (has activity history before submission)
    // or a direct submission (createWorkflow + submitWorkflow in same flow)
    const { Activity } = require('@models/Activity');
    const existingActivities = await Activity.count({
      where: { requestId: actualRequestId }
    });

    // Only log "Request submitted" if this is a draft being submitted (has prior activities)
    // For direct submissions, createWorkflow already logs "Initial request submitted"
    if (existingActivities > 1) {
      // This is a saved draft being submitted later
      activityService.log({
        requestId: actualRequestId,
        type: 'submitted',
        user: initiatorId ? { userId: initiatorId, name: initiatorName } : undefined,
        timestamp: new Date().toISOString(),
        action: 'Draft submitted',
        details: `Draft request "${workflowTitle}" submitted for approval by ${initiatorName}`
      });
    } else {
      // Direct submission - just update the status, createWorkflow already logged the activity
      activityService.log({
        requestId: actualRequestId,
        type: 'submitted',
        user: initiatorId ? { userId: initiatorId, name: initiatorName } : undefined,
        timestamp: new Date().toISOString(),
        action: 'Request submitted',
        details: `Request "${workflowTitle}" submitted for approval`
      });
    }

    const current = await ApprovalLevel.findOne({
      where: { requestId: actualRequestId, levelNumber: (updated as any).currentLevel || 1 }
    });
    if (current) {
      // Set the first level's start time and schedule TAT jobs
      await current.update({
        levelStartTime: now,
        tatStartTime: now,
        status: ApprovalStatus.IN_PROGRESS
      });

      // Log assignment activity for the first approver (similar to createWorkflow)
      activityService.log({
        requestId: actualRequestId,
        type: 'assignment',
        user: initiatorId ? { userId: initiatorId, name: initiatorName } : undefined,
        timestamp: new Date().toISOString(),
        action: 'Assigned to approver',
        details: `Request assigned to ${(current as any).approverName || (current as any).approverEmail || 'approver'} for review`
      });

      // Schedule TAT notification jobs for the first level
      try {
        const workflowPriority = (updated as any).priority || 'STANDARD';
        await tatSchedulerService.scheduleTatJobs(
          actualRequestId,
          (current as any).levelId,
          (current as any).approverId,
          Number((current as any).tatHours),
          now,
          workflowPriority  // Pass workflow priority (EXPRESS = 24/7, STANDARD = working hours)
        );
        logger.info(`[Workflow] TAT jobs scheduled for first level of request ${requestNumber} (Priority: ${workflowPriority})`);
      } catch (tatError) {
        logger.error(`[Workflow] Failed to schedule TAT jobs:`, tatError);
        // Don't fail the submission if TAT scheduling fails
      }

      // Send notifications when workflow is submitted (not when created as draft)
      // Send notification to INITIATOR confirming submission
      await notificationService.sendToUsers([initiatorId], {
        title: 'Request Submitted Successfully',
        body: `Your request "${workflowTitle}" has been submitted and is now with the first approver.`,
        requestNumber: requestNumber,
        requestId: actualRequestId,
        url: `/request/${requestNumber}`,
        type: 'request_submitted',
        priority: 'MEDIUM'
      });

      // Send notification to FIRST APPROVER for assignment
      const firstLevelName = (current as any).levelName || '';
      let assignmentTitle = 'New Request Assigned';
      let assignmentBody = `${workflowTitle}`;

      if (firstLevelName.toLowerCase().includes('proposal')) {
        assignmentTitle = 'Proposal Required';
        assignmentBody = `A proposal is required for request ${requestNumber}: ${workflowTitle}`;
      } else if (firstLevelName.toLowerCase().includes('completion') || firstLevelName.toLowerCase().includes('documents')) {
        assignmentTitle = 'Completion Documents Required';
        assignmentBody = `Completion documents are required for request ${requestNumber}: ${workflowTitle}`;
      }

      await notificationService.sendToUsers([(current as any).approverId], {
        title: assignmentTitle,
        body: assignmentBody,
        requestNumber: requestNumber,
        requestId: actualRequestId,
        url: `/request/${requestNumber}`,
        type: 'assignment',
        priority: 'HIGH',
        actionRequired: true
      });
      logger.info(`[Workflow] Sent assignment (Action Required) notification to active first approver ${(current as any).approverEmail}`);
    }

    // Send notifications to SPECTATORS (in-app, email, and web push)
    // Moved outside the if(current) block to ensure spectators are always notified on submission
    try {
      logger.info(`[Workflow] Querying spectators for request ${requestNumber} (requestId: ${actualRequestId})`);
      const spectators = await Participant.findAll({
        where: {
          requestId: actualRequestId, // Use the actual UUID requestId
          participantType: ParticipantType.SPECTATOR,
          isActive: true,
          notificationEnabled: true
        },
        attributes: ['userId', 'userEmail', 'userName']
      });

      logger.info(`[Workflow] Found ${spectators.length} active spectators for request ${requestNumber}`);

      if (spectators.length > 0) {
        const spectatorUserIds = spectators.map((s: any) => s.userId);
        logger.info(`[Workflow] Sending notifications to ${spectatorUserIds.length} spectators: ${spectatorUserIds.join(', ')}`);

        await notificationService.sendToUsers(spectatorUserIds, {
          title: 'Added to Request',
          body: `You have been added as a spectator to request ${requestNumber}: ${workflowTitle}`,
          requestNumber: requestNumber,
          requestId: actualRequestId,
          url: `/request/${requestNumber}`,
          type: 'spectator_added',
          priority: 'MEDIUM'
        });
        logger.info(`[Workflow] Successfully sent notifications to ${spectators.length} spectators for request ${requestNumber}`);
      } else {
        logger.info(`[Workflow] No active spectators found for request ${requestNumber} (requestId: ${actualRequestId})`);
      }
    } catch (spectatorError) {
      logger.error(`[Workflow] Failed to send spectator notifications for request ${requestNumber} (requestId: ${actualRequestId}):`, spectatorError);
      // Don't fail the submission if spectator notifications fail
    }

    return updated;
  }
}
