import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { BehaviorSubject, Observable, of, Subscription } from 'rxjs';
import { FocusModeEffects } from './focus-mode.effects';
import { provideMockStore, MockStore } from '@ngrx/store/testing';
import { FocusModeStrategyFactory } from '../focus-mode-strategies';
import { GlobalConfigService } from '../../config/global-config.service';
import { TaskService } from '../../tasks/task.service';
import { BannerService } from '../../../core/banner/banner.service';
import { MetricService } from '../../metric/metric.service';
import { FocusModeStorageService } from '../focus-mode-storage.service';
import { GlobalTrackingIntervalService } from '../../../core/global-tracking-interval/global-tracking-interval.service';
import { TakeABreakService } from '../../take-a-break/take-a-break.service';
import { NotifyService } from '../../../core/notify/notify.service';
import { IS_ANDROID_WEB_VIEW_TOKEN } from '../../../util/is-android-web-view';
import { BannerId } from '../../../core/banner/banner.model';
import { T } from '../../../t.const';
import * as actions from './focus-mode.actions';
import * as selectors from './focus-mode.selectors';
import { FocusModeMode, FocusScreen, TimerState } from '../focus-mode.model';
import { unsetCurrentTask, setCurrentTask } from '../../tasks/store/task.actions';
import { openIdleDialog } from '../../idle/store/idle.actions';
import { selectLastCurrentTask, selectTaskById } from '../../tasks/store/task.selectors';
import {
  selectFocusModeConfig,
  selectIsFocusModeEnabled,
  selectPomodoroConfig,
} from '../../config/store/global-config.reducer';
import { updateGlobalConfigSection } from '../../config/store/global-config.actions';
import { take, toArray } from 'rxjs/operators';
import { HydrationStateService } from '../../../op-log/apply/hydration-state.service';

describe('FocusModeEffects', () => {
  let actions$: Observable<any>;
  let effects: FocusModeEffects;
  let store: MockStore;
  let strategyFactoryMock: any;
  let taskServiceMock: any;
  let globalConfigServiceMock: any;
  let metricServiceMock: any;
  let bannerServiceMock: any;
  let hydrationStateServiceMock: any;
  let notifyServiceMock: any;
  let currentTaskId$: BehaviorSubject<string | null>;

  const createMockTimer = (overrides: Partial<TimerState> = {}): TimerState => ({
    isRunning: false,
    startedAt: null,
    elapsed: 0,
    duration: 0,
    purpose: null,
    ...overrides,
  });

  const collectEmissions = <T>(
    source$: Observable<T>,
  ): { emitted: T[]; subscription: Subscription } => {
    const emitted: T[] = [];
    const subscription = source$.subscribe((action) => {
      emitted.push(action);
    });
    return { emitted, subscription };
  };

  beforeEach(() => {
    currentTaskId$ = new BehaviorSubject<string | null>(null);

    strategyFactoryMock = {
      getStrategy: jasmine.createSpy('getStrategy').and.returnValue({
        initialSessionDuration: 25 * 60 * 1000,
        shouldStartBreakAfterSession: true,
        shouldAutoStartNextSession: true,
        getBreakDuration: jasmine
          .createSpy('getBreakDuration')
          .and.returnValue({ duration: 5 * 60 * 1000, isLong: false }),
      }),
    };

    taskServiceMock = {
      currentTaskId$: currentTaskId$.asObservable(),
      currentTaskId: jasmine.createSpy('currentTaskId').and.returnValue(null),
    };

    globalConfigServiceMock = {
      sound: jasmine.createSpy('sound').and.returnValue({ volume: 75 }),
    };

    metricServiceMock = {
      logFocusSession: jasmine.createSpy('logFocusSession'),
    };

    bannerServiceMock = {
      open: jasmine.createSpy('open'),
      dismiss: jasmine.createSpy('dismiss'),
    };

    hydrationStateServiceMock = {
      isApplyingRemoteOps: jasmine
        .createSpy('isApplyingRemoteOps')
        .and.returnValue(false),
    };

    const takeABreakServiceMock = {
      otherNoBreakTIme$: new BehaviorSubject<number>(0),
    };

    notifyServiceMock = {
      notify: jasmine.createSpy('notify').and.resolveTo(undefined),
    };

    TestBed.configureTestingModule({
      providers: [
        FocusModeEffects,
        provideMockActions(() => actions$),
        provideMockStore({
          initialState: {
            focusMode: {
              timer: createMockTimer(),
              mode: FocusModeMode.Pomodoro,
              currentCycle: 1,
              lastCompletedDuration: 0,
            },
          },
          selectors: [
            { selector: selectors.selectTimer, value: createMockTimer() },
            { selector: selectors.selectMode, value: FocusModeMode.Pomodoro },
            { selector: selectors.selectCurrentCycle, value: 1 },
            { selector: selectors.selectLastSessionDuration, value: 0 },
            {
              selector: selectFocusModeConfig,
              value: {},
            },
            { selector: selectPomodoroConfig, value: { duration: 25 * 60 * 1000 } },
            { selector: selectIsFocusModeEnabled, value: true },
            { selector: selectLastCurrentTask, value: null },
          ],
        }),
        { provide: FocusModeStrategyFactory, useValue: strategyFactoryMock },
        { provide: GlobalConfigService, useValue: globalConfigServiceMock },
        { provide: TaskService, useValue: taskServiceMock },
        { provide: BannerService, useValue: bannerServiceMock },
        { provide: MetricService, useValue: metricServiceMock },
        {
          provide: FocusModeStorageService,
          useValue: { setLastCountdownDuration: jasmine.createSpy() },
        },
        { provide: HydrationStateService, useValue: hydrationStateServiceMock },
        { provide: TakeABreakService, useValue: takeABreakServiceMock },
        { provide: NotifyService, useValue: notifyServiceMock },
        { provide: IS_ANDROID_WEB_VIEW_TOKEN, useValue: false },
        {
          provide: GlobalTrackingIntervalService,
          useValue: {
            todayStr$: new BehaviorSubject<string>('2024-01-19'),
          },
        },
      ],
    });

    effects = TestBed.inject(FocusModeEffects);
    store = TestBed.inject(MockStore);
  });

  afterEach(() => {
    store.resetSelectors();
  });

  describe('syncDurationWithMode$', () => {
    it('should sync duration with mode when focusModeLoaded is dispatched', (done) => {
      actions$ = of(actions.focusModeLoaded());

      effects.syncDurationWithMode$.subscribe((action) => {
        expect(strategyFactoryMock.getStrategy).toHaveBeenCalledWith(
          FocusModeMode.Pomodoro,
        );
        expect(action).toEqual(
          actions.setFocusSessionDuration({ focusSessionDuration: 25 * 60 * 1000 }),
        );
        done();
      });
    });

    it('should sync duration with mode when setFocusModeMode is dispatched', (done) => {
      actions$ = of(actions.setFocusModeMode({ mode: FocusModeMode.Pomodoro }));

      effects.syncDurationWithMode$.subscribe((action) => {
        expect(strategyFactoryMock.getStrategy).toHaveBeenCalledWith(
          FocusModeMode.Pomodoro,
        );
        expect(action).toEqual(
          actions.setFocusSessionDuration({ focusSessionDuration: 25 * 60 * 1000 }),
        );
        done();
      });
    });

    it('should NOT sync duration on focusModeLoaded if duration is already > 0', (done) => {
      actions$ = of(actions.focusModeLoaded());
      store.overrideSelector(selectors.selectTimer, createMockTimer({ duration: 25000 }));
      store.refreshState();

      const result: any[] = [];
      effects.syncDurationWithMode$.subscribe({
        next: (action) => result.push(action),
        complete: () => {
          expect(result.length).toBe(0);
          done();
        },
      });
    });

    it('should NOT sync duration for Flowtime mode', (done) => {
      actions$ = of(actions.setFocusModeMode({ mode: FocusModeMode.Flowtime }));
      store.overrideSelector(selectors.selectMode, FocusModeMode.Flowtime);
      store.refreshState();

      const result: any[] = [];
      effects.syncDurationWithMode$.subscribe({
        next: (action) => result.push(action),
        complete: () => {
          expect(result.length).toBe(0);
          done();
        },
      });
    });
  });

  describe('syncDurationWithPomodoroConfig$', () => {
    it('should sync duration when pomodoro config changes for unstarted session', (done) => {
      actions$ = of(
        updateGlobalConfigSection({ sectionKey: 'pomodoro', sectionCfg: {} }),
      );
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ duration: 20 * 60 * 1000 }),
      );
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.overrideSelector(selectPomodoroConfig, {
        duration: 30 * 60 * 1000,
        cyclesBeforeLongerBreak: 4,
      });
      store.refreshState();

      effects.syncDurationWithPomodoroConfig$.subscribe((action) => {
        expect(action).toEqual(
          actions.setFocusSessionDuration({ focusSessionDuration: 30 * 60 * 1000 }),
        );
        done();
      });
    });

    it('should NOT sync when session has already started', (done) => {
      actions$ = of(
        updateGlobalConfigSection({ sectionKey: 'pomodoro', sectionCfg: {} }),
      );
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ duration: 20 * 60 * 1000, purpose: 'work' }),
      );
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.overrideSelector(selectPomodoroConfig, {
        duration: 30 * 60 * 1000,
        cyclesBeforeLongerBreak: 4,
      });
      store.refreshState();

      const result: any[] = [];
      effects.syncDurationWithPomodoroConfig$.subscribe({
        next: (action) => result.push(action),
        complete: () => {
          expect(result.length).toBe(0);
          done();
        },
      });
    });

    it('should NOT sync for non-Pomodoro modes', (done) => {
      actions$ = of(
        updateGlobalConfigSection({ sectionKey: 'pomodoro', sectionCfg: {} }),
      );
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ duration: 20 * 60 * 1000 }),
      );
      store.overrideSelector(selectors.selectMode, FocusModeMode.Flowtime);
      store.overrideSelector(selectPomodoroConfig, {
        duration: 30 * 60 * 1000,
        cyclesBeforeLongerBreak: 4,
      });
      store.refreshState();

      const result: any[] = [];
      effects.syncDurationWithPomodoroConfig$.subscribe({
        next: (action) => result.push(action),
        complete: () => {
          expect(result.length).toBe(0);
          done();
        },
      });
    });

    it('should NOT sync for non-pomodoro config section updates', (done) => {
      actions$ = of(updateGlobalConfigSection({ sectionKey: 'misc', sectionCfg: {} }));
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ duration: 20 * 60 * 1000 }),
      );
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.overrideSelector(selectPomodoroConfig, {
        duration: 30 * 60 * 1000,
        cyclesBeforeLongerBreak: 4,
      });
      store.refreshState();

      const result: any[] = [];
      effects.syncDurationWithPomodoroConfig$.subscribe({
        next: (action) => result.push(action),
        complete: () => {
          expect(result.length).toBe(0);
          done();
        },
      });
    });

    it('should NOT sync if duration is not divisible by 1000', (done) => {
      actions$ = of(
        updateGlobalConfigSection({ sectionKey: 'pomodoro', sectionCfg: {} }),
      );
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ duration: 20 * 60 * 1000 }),
      );
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      // 30 minutes + 500ms = not divisible by 1000
      store.overrideSelector(selectPomodoroConfig, {
        duration: 1800500,
        cyclesBeforeLongerBreak: 4,
      });
      store.refreshState();

      const result: any[] = [];
      effects.syncDurationWithPomodoroConfig$.subscribe({
        next: (action) => result.push(action),
        complete: () => {
          expect(result.length).toBe(0);
          done();
        },
      });
    });

    it('should NOT sync if duration is the same as current', (done) => {
      actions$ = of(
        updateGlobalConfigSection({ sectionKey: 'pomodoro', sectionCfg: {} }),
      );
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ duration: 25 * 60 * 1000 }),
      );
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.overrideSelector(selectPomodoroConfig, {
        duration: 25 * 60 * 1000,
        cyclesBeforeLongerBreak: 4,
      });
      store.refreshState();

      const result: any[] = [];
      effects.syncDurationWithPomodoroConfig$.subscribe({
        next: (action) => result.push(action),
        complete: () => {
          expect(result.length).toBe(0);
          done();
        },
      });
    });
  });

  describe('session completion effects (refactored)', () => {
    describe('incrementCycleOnSessionComplete$', () => {
      it('should dispatch incrementCycle for Pomodoro mode', (done) => {
        actions$ = of(actions.completeFocusSession({ isManual: true }));
        store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
        store.overrideSelector(selectors.selectCurrentCycle, 1);
        store.refreshState();

        effects.incrementCycleOnSessionComplete$.pipe(take(1)).subscribe((action) => {
          expect(action).toEqual(actions.incrementCycle());
          done();
        });
      });

      it('should NOT dispatch incrementCycle for Flowtime mode', (done) => {
        actions$ = of(actions.completeFocusSession({ isManual: true }));
        store.overrideSelector(selectors.selectMode, FocusModeMode.Flowtime);
        store.refreshState();

        const result: any[] = [];
        effects.incrementCycleOnSessionComplete$.subscribe({
          next: (action) => result.push(action),
          complete: () => {
            expect(result.length).toBe(0);
            done();
          },
        });
      });
    });

    describe('autoStartBreakOnSessionComplete$', () => {
      it('should dispatch startBreak when incrementCycle is dispatched', (done) => {
        actions$ = of(actions.incrementCycle());
        store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
        store.overrideSelector(selectors.selectCurrentCycle, 1);
        store.refreshState();

        effects.autoStartBreakOnSessionComplete$
          .pipe(toArray())
          .subscribe((actionsArr) => {
            const startBreakAction = actionsArr.find(
              (a) => a.type === actions.startBreak.type,
            ) as any;
            expect(startBreakAction).toBeDefined();
            expect(startBreakAction.duration).toBe(5 * 60 * 1000);
            expect(startBreakAction.isLongBreak).toBeFalse();
            done();
          });
      });

      it('should dispatch both unsetCurrentTask and startBreak with pausedTaskId when isPauseTrackingDuringBreak is true and task is active', (done) => {
        actions$ = of(actions.incrementCycle());
        store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
        store.overrideSelector(selectors.selectCurrentCycle, 1);
        store.overrideSelector(selectFocusModeConfig, {
          isSkipPreparation: false,
          isManualBreakStart: true,
          isPauseTrackingDuringBreak: true,
        });
        currentTaskId$.next('task-123');
        store.refreshState();

        effects.autoStartBreakOnSessionComplete$
          .pipe(toArray())
          .subscribe((actionsArr) => {
            expect(actionsArr.length).toBe(2);
            expect(actionsArr[0]).toEqual(unsetCurrentTask());
            expect(actionsArr[1]).toEqual(
              actions.startBreak({
                duration: 5 * 60 * 1000,
                isLongBreak: false,
                pausedTaskId: 'task-123',
              }),
            );
            done();
          });
      });

      it('should NOT dispatch unsetCurrentTask and should dispatch startBreak with pausedTaskId undefined when isPauseTrackingDuringBreak is false and task is active', (done) => {
        actions$ = of(actions.incrementCycle());
        store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
        store.overrideSelector(selectors.selectCurrentCycle, 1);
        store.overrideSelector(selectFocusModeConfig, {
          isSkipPreparation: false,
          isManualBreakStart: true,
          isPauseTrackingDuringBreak: false,
        });
        currentTaskId$.next('task-123');
        store.refreshState();

        effects.autoStartBreakOnSessionComplete$
          .pipe(toArray())
          .subscribe((actionsArr) => {
            expect(actionsArr.length).toBe(1);
            expect(actionsArr[0]).toEqual(
              actions.startBreak({
                duration: 5 * 60 * 1000,
                isLongBreak: false,
                pausedTaskId: undefined,
              }),
            );
            done();
          });
      });

      it('should dispatch long break after 4th session (Bug #6044)', (done) => {
        // Bug #6044 fix: Effect now listens to incrementCycle
        // When cycle=5 (after increment from 4 to 5), break should be long break
        actions$ = of(actions.incrementCycle());
        store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
        store.overrideSelector(selectors.selectCurrentCycle, 5);
        store.refreshState();

        const getBreakDurationSpy = jasmine
          .createSpy('getBreakDuration')
          .and.returnValue({ duration: 15 * 60 * 1000, isLong: true });

        strategyFactoryMock.getStrategy.and.returnValue({
          initialSessionDuration: 25 * 60 * 1000,
          shouldStartBreakAfterSession: true,
          shouldAutoStartNextSession: true,
          getBreakDuration: getBreakDurationSpy,
        });

        effects.autoStartBreakOnSessionComplete$
          .pipe(toArray())
          .subscribe((actionsArr) => {
            const startBreakAction = actionsArr.find(
              (a) => a.type === actions.startBreak.type,
            ) as any;
            // Verify getBreakDuration was called with cycle 4
            // Because we decrement cycle by 1 to get last focus session's cycle
            expect(getBreakDurationSpy).toHaveBeenCalledWith(4);
            expect(startBreakAction).toBeDefined();
            expect(startBreakAction.isLongBreak).toBeTrue();
            expect(startBreakAction.duration).toBe(15 * 60 * 1000);
            done();
          });
      });

      it('should dispatch short break after 5th session (Bug #6044)', (done) => {
        // Bug #6044 fix: Verify that session 5 (cycle 6) gets a short break, not long break
        actions$ = of(actions.incrementCycle());
        store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
        store.overrideSelector(selectors.selectCurrentCycle, 6);
        store.refreshState();

        const getBreakDurationSpy = jasmine
          .createSpy('getBreakDuration')
          .and.returnValue({ duration: 5 * 60 * 1000, isLong: false });

        strategyFactoryMock.getStrategy.and.returnValue({
          initialSessionDuration: 25 * 60 * 1000,
          shouldStartBreakAfterSession: true,
          shouldAutoStartNextSession: true,
          getBreakDuration: getBreakDurationSpy,
        });

        effects.autoStartBreakOnSessionComplete$
          .pipe(toArray())
          .subscribe((actionsArr) => {
            const startBreakAction = actionsArr.find(
              (a) => a.type === actions.startBreak.type,
            ) as any;
            // Verify getBreakDuration was called with cycle 5
            // Because we decrement cycle by 1 to get last focus session's cycle
            expect(getBreakDurationSpy).toHaveBeenCalledWith(5);
            expect(startBreakAction).toBeDefined();
            expect(startBreakAction.isLongBreak).toBeFalse();
            expect(startBreakAction.duration).toBe(5 * 60 * 1000);
            done();
          });
      });

      it('should NOT dispatch for non-Pomodoro modes', (done) => {
        actions$ = of(actions.incrementCycle());
        store.overrideSelector(selectors.selectMode, FocusModeMode.Flowtime);
        store.overrideSelector(selectors.selectCurrentCycle, 1);
        store.refreshState();

        strategyFactoryMock.getStrategy.and.returnValue({
          initialSessionDuration: 0,
          shouldStartBreakAfterSession: false,
          shouldAutoStartNextSession: false,
          getBreakDuration: () => null,
        });

        effects.autoStartBreakOnSessionComplete$
          .pipe(toArray())
          .subscribe((actionsArr) => {
            expect(actionsArr.length).toBe(0);
            done();
          });
      });
    });

    describe('stopTrackingOnSessionEnd$', () => {
      it('should dispatch setPausedTaskId and unsetCurrentTask when isManual=true AND isPauseTrackingDuringBreak=true AND currentTaskId exists (Bug #5737)', (done) => {
        currentTaskId$.next('task-123');
        actions$ = of(actions.completeFocusSession({ isManual: true }));
        store.overrideSelector(selectFocusModeConfig, {
          isPauseTrackingDuringBreak: true,
          isSkipPreparation: false,
        });
        store.refreshState();

        // Bug #5737 fix: Should dispatch both setPausedTaskId and unsetCurrentTask
        // to preserve task for resumption after break
        effects.stopTrackingOnSessionEnd$.pipe(toArray()).subscribe((actionsArr) => {
          expect(actionsArr.length).toBe(2);
          expect(actionsArr[0]).toEqual(
            actions.setPausedTaskId({ pausedTaskId: 'task-123' }),
          );
          expect(actionsArr[1]).toEqual(unsetCurrentTask());
          done();
        });
      });

      it('should NOT dispatch unsetCurrentTask when isPauseTrackingDuringBreak=false (Bug #5954)', (done) => {
        currentTaskId$.next('task-123');
        actions$ = of(actions.completeFocusSession({ isManual: true }));
        store.overrideSelector(selectFocusModeConfig, {
          isPauseTrackingDuringBreak: false,
          isSkipPreparation: false,
        });
        store.refreshState();

        effects.stopTrackingOnSessionEnd$.pipe(toArray()).subscribe((actionsArr) => {
          expect(actionsArr.length).toBe(0);
          done();
        });
      });

      it('should NOT dispatch unsetCurrentTask when isManual=true but currentTaskId is null', (done) => {
        currentTaskId$.next(null);
        actions$ = of(actions.completeFocusSession({ isManual: true }));
        store.overrideSelector(selectFocusModeConfig, {
          isPauseTrackingDuringBreak: true,
          isSkipPreparation: false,
        });
        store.refreshState();

        effects.stopTrackingOnSessionEnd$.pipe(toArray()).subscribe((actionsArr) => {
          expect(actionsArr.length).toBe(0);
          done();
        });
      });

      it('should NOT dispatch unsetCurrentTask when isManual=false in Pomodoro mode with auto-break (Bug #5996)', (done) => {
        // In Pomodoro mode with auto-break, autoStartBreakOnSessionComplete$ handles tracking pause
        currentTaskId$.next('task-123');
        actions$ = of(actions.completeFocusSession({ isManual: false }));
        store.overrideSelector(selectFocusModeConfig, {
          isPauseTrackingDuringBreak: true,
          isSkipPreparation: false,
          isManualBreakStart: false, // Break auto-starts
        });
        store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
        store.refreshState();

        // Default mock has shouldStartBreakAfterSession: true (Pomodoro)
        effects.stopTrackingOnSessionEnd$.pipe(toArray()).subscribe((actionsArr) => {
          expect(actionsArr.length).toBe(0);
          done();
        });
      });

      it('should NOT dispatch when isManual=false in Pomodoro with manual break start (Bug #6510)', (done) => {
        // Bug #6510: Tracking should continue until break actually starts
        currentTaskId$.next('task-123');
        actions$ = of(actions.completeFocusSession({ isManual: false }));
        store.overrideSelector(selectFocusModeConfig, {
          isPauseTrackingDuringBreak: true,
          isSkipPreparation: false,
          isManualBreakStart: true,
        });
        store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
        store.refreshState();

        effects.stopTrackingOnSessionEnd$.pipe(toArray()).subscribe((actionsArr) => {
          expect(actionsArr.length).toBe(0);
          done();
        });
      });

      it('should dispatch setPausedTaskId and unsetCurrentTask when isManual=false in Countdown mode (Bug #5996, #5737)', (done) => {
        // In Countdown mode, no break auto-starts, so this effect should fire
        // Bug #5737: Now also stores pausedTaskId for potential task resumption
        currentTaskId$.next('task-123');
        actions$ = of(actions.completeFocusSession({ isManual: false }));
        store.overrideSelector(selectFocusModeConfig, {
          isPauseTrackingDuringBreak: true,
          isSkipPreparation: false,
        });
        store.overrideSelector(selectors.selectMode, FocusModeMode.Countdown);
        store.refreshState();

        // Override strategy to return Countdown behavior
        strategyFactoryMock.getStrategy.and.returnValue({
          shouldStartBreakAfterSession: false,
          shouldAutoStartNextSession: false,
          getBreakDuration: () => null,
        });

        effects.stopTrackingOnSessionEnd$.pipe(toArray()).subscribe((actionsArr) => {
          expect(actionsArr.length).toBe(2);
          expect(actionsArr[0]).toEqual(
            actions.setPausedTaskId({ pausedTaskId: 'task-123' }),
          );
          expect(actionsArr[1]).toEqual(unsetCurrentTask());
          done();
        });
      });
    });

    describe('autoStartFlowtimeBreakOnSessionEnd$', () => {
      // The break must auto-start WITHOUT pausing itself. completeFocusSession is
      // dispatched with isManual:FALSE so stopTrackingOnSessionEnd$ defers (it would
      // otherwise enqueue unsetCurrentTask AFTER startBreak, making
      // syncTrackingStopToSession$ pause the just-started break). The unset is done
      // here, explicitly BEFORE startBreak, mirroring autoStartBreakOnSessionComplete$.
      it('should complete (isManual:false), unset task, then startBreak when pause-tracking is on', (done) => {
        actions$ = of(actions.endFlowtimeSession({ pausedTaskId: 'task-123' }));
        store.overrideSelector(selectors.selectMode, FocusModeMode.Flowtime);
        store.overrideSelector(
          selectors.selectTimer,
          createMockTimer({ purpose: 'work', elapsed: 1500000 }),
        );
        store.overrideSelector(selectFocusModeConfig, {
          isPauseTrackingDuringBreak: true,
        } as any);
        store.refreshState();

        strategyFactoryMock.getStrategy.and.returnValue({
          shouldStartBreakAfterSession: true,
          getBreakDuration: () => ({ duration: 300000, isLong: false }),
        });

        effects.autoStartFlowtimeBreakOnSessionEnd$
          .pipe(toArray())
          .subscribe((actionsArr) => {
            expect(actionsArr.length).toBe(3);
            expect(actionsArr[0]).toEqual(
              actions.completeFocusSession({ isManual: false }),
            );
            expect(actionsArr[1]).toEqual(unsetCurrentTask());
            expect(actionsArr[2]).toEqual(
              actions.startBreak({
                duration: 300000,
                isLongBreak: false,
                pausedTaskId: 'task-123',
              }),
            );
            done();
          });
      });

      it('should complete (isManual:false) then startBreak without unsetting when pause-tracking is off', (done) => {
        actions$ = of(actions.endFlowtimeSession({ pausedTaskId: 'task-123' }));
        store.overrideSelector(selectors.selectMode, FocusModeMode.Flowtime);
        store.overrideSelector(
          selectors.selectTimer,
          createMockTimer({ purpose: 'work', elapsed: 1500000 }),
        );
        store.overrideSelector(selectFocusModeConfig, {
          isPauseTrackingDuringBreak: false,
        } as any);
        store.refreshState();

        strategyFactoryMock.getStrategy.and.returnValue({
          shouldStartBreakAfterSession: true,
          getBreakDuration: () => ({ duration: 300000, isLong: false }),
        });

        effects.autoStartFlowtimeBreakOnSessionEnd$
          .pipe(toArray())
          .subscribe((actionsArr) => {
            expect(actionsArr.length).toBe(2);
            expect(actionsArr[0]).toEqual(
              actions.completeFocusSession({ isManual: false }),
            );
            expect(actionsArr[1]).toEqual(
              actions.startBreak({
                duration: 300000,
                isLongBreak: false,
                pausedTaskId: undefined,
              }),
            );
            done();
          });
      });

      it('should dispatch only completeFocusSession when mode is Flowtime, timer is work, but breakInfo is null', (done) => {
        actions$ = of(actions.endFlowtimeSession({ pausedTaskId: 'task-123' }));
        store.overrideSelector(selectors.selectMode, FocusModeMode.Flowtime);
        store.overrideSelector(
          selectors.selectTimer,
          createMockTimer({ purpose: 'work', elapsed: 1500000 }),
        );
        store.refreshState();

        strategyFactoryMock.getStrategy.and.returnValue({
          shouldStartBreakAfterSession: true,
          getBreakDuration: () => null,
        });

        effects.autoStartFlowtimeBreakOnSessionEnd$
          .pipe(toArray())
          .subscribe((actionsArr) => {
            expect(actionsArr.length).toBe(1);
            expect(actionsArr[0]).toEqual(
              actions.completeFocusSession({ isManual: true }),
            );
            done();
          });
      });

      it('should skip effect if mode is not Flowtime', (done) => {
        actions$ = of(actions.endFlowtimeSession({ pausedTaskId: 'task-123' }));
        store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
        store.overrideSelector(
          selectors.selectTimer,
          createMockTimer({ purpose: 'work', elapsed: 1500000 }),
        );
        store.refreshState();

        effects.autoStartFlowtimeBreakOnSessionEnd$
          .pipe(toArray())
          .subscribe((actionsArr) => {
            expect(actionsArr.length).toBe(0);
            done();
          });
      });
    });

    describe('edge cases', () => {
      it('should handle missing focusModeConfig gracefully in incrementCycleOnSessionComplete$', (done) => {
        actions$ = of(actions.completeFocusSession({ isManual: true }));
        store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
        store.overrideSelector(selectors.selectCurrentCycle, 1);
        store.overrideSelector(selectFocusModeConfig, null as any);
        store.refreshState();

        // Should still dispatch incrementCycle
        effects.incrementCycleOnSessionComplete$.pipe(take(1)).subscribe({
          next: (action) => {
            expect(action).toEqual(actions.incrementCycle());
            done();
          },
          error: (err) => {
            fail('Should not throw error: ' + err);
          },
        });
      });
    });
  });

  describe('break completion effects (refactored)', () => {
    describe('autoStartSessionOnBreakComplete$', () => {
      it('should dispatch startFocusSession when strategy.shouldAutoStartNextSession is true', (done) => {
        actions$ = of(actions.completeBreak({ pausedTaskId: null }));
        store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
        store.overrideSelector(selectFocusModeConfig, {
          isSkipPreparation: false,
          isManualBreakStart: false,
        });
        store.refreshState();

        effects.autoStartSessionOnBreakComplete$.pipe(take(1)).subscribe((action) => {
          expect(action).toEqual(
            actions.startFocusSession({
              duration: 25 * 60 * 1000,
            }),
          );
          done();
        });
      });

      it('should NOT dispatch startFocusSession when shouldAutoStartNextSession is false', (done) => {
        actions$ = of(actions.completeBreak({ pausedTaskId: null }));
        store.overrideSelector(selectors.selectMode, FocusModeMode.Countdown);
        store.refreshState();

        strategyFactoryMock.getStrategy.and.returnValue({
          initialSessionDuration: 25 * 60 * 1000,
          shouldStartBreakAfterSession: false,
          shouldAutoStartNextSession: false,
          getBreakDuration: () => null,
        });

        effects.autoStartSessionOnBreakComplete$
          .pipe(toArray())
          .subscribe((actionsArr) => {
            expect(actionsArr.length).toBe(0);
            done();
          });
      });
    });

    describe('resumeTrackingOnBreakComplete$', () => {
      it('should dispatch setCurrentTask when pausedTaskId is provided', (done) => {
        const pausedTaskId = 'test-paused-task-id';
        actions$ = of(actions.completeBreak({ pausedTaskId }));

        effects.resumeTrackingOnBreakComplete$.pipe(take(1)).subscribe((action) => {
          expect(action).toEqual(setCurrentTask({ id: pausedTaskId }));
          done();
        });
      });

      it('should NOT dispatch setCurrentTask when pausedTaskId is null', (done) => {
        actions$ = of(actions.completeBreak({ pausedTaskId: null }));

        effects.resumeTrackingOnBreakComplete$.pipe(toArray()).subscribe((actionsArr) => {
          expect(actionsArr.length).toBe(0);
          done();
        });
      });

      // Bug #6726 fix: Don't override user's task switch during break
      it('should NOT dispatch setCurrentTask when pausedTaskId exists but a different task is already being tracked', (done) => {
        const pausedTaskId = 'original-task-id';
        currentTaskId$.next('different-task-id');
        actions$ = of(actions.completeBreak({ pausedTaskId }));

        effects.resumeTrackingOnBreakComplete$.pipe(toArray()).subscribe((actionsArr) => {
          expect(actionsArr.length).toBe(0);
          done();
        });
      });
    });

    describe('combined behavior', () => {
      it('should dispatch setCurrentTask from resumeTrackingOnBreakComplete$ when pausedTaskId exists', (done) => {
        const pausedTaskId = 'test-paused-task-id';
        actions$ = of(actions.completeBreak({ pausedTaskId }));
        store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
        store.refreshState();

        // Test resumeTrackingOnBreakComplete$ independently
        effects.resumeTrackingOnBreakComplete$.pipe(take(1)).subscribe((action) => {
          expect(action).toEqual(setCurrentTask({ id: pausedTaskId }));
          done();
        });
      });
    });
  });

  describe('skipBreak$', () => {
    it('should dispatch startFocusSession when strategy.shouldAutoStartNextSession is true', (done) => {
      actions$ = of(actions.skipBreak({ pausedTaskId: null }));
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.refreshState();

      effects.skipBreak$.subscribe((action) => {
        expect(action).toEqual(
          actions.startFocusSession({
            duration: 25 * 60 * 1000,
          }),
        );
        done();
      });
    });

    it('should NOT dispatch startFocusSession when shouldAutoStartNextSession is false', (done) => {
      actions$ = of(actions.skipBreak({ pausedTaskId: null }));
      store.overrideSelector(selectors.selectMode, FocusModeMode.Countdown);
      store.refreshState();

      strategyFactoryMock.getStrategy.and.returnValue({
        initialSessionDuration: 25 * 60 * 1000,
        shouldStartBreakAfterSession: false,
        shouldAutoStartNextSession: false,
        getBreakDuration: () => null,
      });

      const result: any[] = [];
      effects.skipBreak$.subscribe({
        next: (action) => result.push(action),
        complete: () => {
          expect(result.length).toBe(0);
          done();
        },
      });
    });

    it('should dispatch setCurrentTask when pausedTaskId is provided', (done) => {
      const pausedTaskId = 'test-paused-task-id';
      actions$ = of(actions.skipBreak({ pausedTaskId }));
      store.overrideSelector(selectors.selectMode, FocusModeMode.Countdown);
      store.refreshState();

      strategyFactoryMock.getStrategy.and.returnValue({
        initialSessionDuration: 25 * 60 * 1000,
        shouldStartBreakAfterSession: false,
        shouldAutoStartNextSession: false,
        getBreakDuration: () => null,
      });

      effects.skipBreak$.pipe(take(1)).subscribe((action) => {
        expect(action).toEqual(setCurrentTask({ id: pausedTaskId }));
        done();
      });
    });

    // Bug #6726 fix: Don't override user's task switch during break
    it('should NOT dispatch setCurrentTask when pausedTaskId exists but a different task is already being tracked', (done) => {
      const pausedTaskId = 'original-task-id';
      currentTaskId$.next('different-task-id');
      actions$ = of(actions.skipBreak({ pausedTaskId }));
      store.overrideSelector(selectors.selectMode, FocusModeMode.Countdown);
      store.refreshState();

      strategyFactoryMock.getStrategy.and.returnValue({
        initialSessionDuration: 25 * 60 * 1000,
        shouldStartBreakAfterSession: false,
        shouldAutoStartNextSession: false,
        getBreakDuration: () => null,
      });

      const result: any[] = [];
      effects.skipBreak$.subscribe({
        next: (action) => result.push(action),
        complete: () => {
          expect(result.length).toBe(0);
          done();
        },
      });
    });
  });

  describe('cancelSession$', () => {
    it('should dispatch unsetCurrentTask when session is cancelled', (done) => {
      actions$ = of(actions.cancelFocusSession());

      effects.cancelSession$.subscribe((action) => {
        expect(action).toEqual(unsetCurrentTask());
        done();
      });
    });
  });

  describe('pauseOnIdle$', () => {
    it('should dispatch pauseFocusSession when openIdleDialog is dispatched', (done) => {
      actions$ = of(
        openIdleDialog({
          lastCurrentTaskId: null,
          enabledSimpleStopWatchCounters: [],
          wasFocusSessionRunning: false,
        }),
      );

      effects.pauseOnIdle$.subscribe((action) => {
        expect(action.type).toEqual(actions.pauseFocusSession.type);
        done();
      });
    });
  });

  describe('logFocusSession$', () => {
    it('should call metricService.logFocusSession with duration on completeFocusSession', () => {
      actions$ = of(actions.completeFocusSession({ isManual: false }));
      store.overrideSelector(selectors.selectLastSessionDuration, 25 * 60 * 1000);
      store.refreshState();

      effects.logFocusSession$.subscribe();

      expect(metricServiceMock.logFocusSession).toHaveBeenCalledWith(25 * 60 * 1000);
    });

    it('should NOT log when duration is 0', () => {
      actions$ = of(actions.completeFocusSession({ isManual: false }));
      store.overrideSelector(selectors.selectLastSessionDuration, 0);
      store.refreshState();

      effects.logFocusSession$.subscribe();

      expect(metricServiceMock.logFocusSession).not.toHaveBeenCalled();
    });
  });

  describe('surfaceSessionDoneOnCompletion$', () => {
    // The effect is non-dispatching (dispatch:false); it has side effects only
    // (notify + banner). Subscribing drives the tap; `complete` fires after the
    // single `of(...)` action, by which point the side effects have run.
    const run = (done: DoneFn, assertFn: () => void): void => {
      effects.surfaceSessionDoneOnCompletion$.subscribe({
        complete: () => {
          assertFn();
          done();
        },
      });
    };

    const expectExcluded = (done: DoneFn): void =>
      run(done, () => {
        expect(notifyServiceMock.notify).not.toHaveBeenCalled();
        expect(bannerServiceMock.open).not.toHaveBeenCalled();
      });

    it('should banner + notify when Countdown auto-completes with the overlay hidden and app unfocused', (done) => {
      spyOn(document, 'hasFocus').and.returnValue(false);
      actions$ = of(actions.completeFocusSession({ isManual: false }));
      store.overrideSelector(selectors.selectMode, FocusModeMode.Countdown);
      store.overrideSelector(selectors.selectLastSessionDuration, 25 * 60 * 1000);
      store.overrideSelector(selectors.selectIsOverlayShown, false);
      store.refreshState();

      run(done, () => {
        expect(notifyServiceMock.notify).toHaveBeenCalledTimes(1);
        expect(
          notifyServiceMock.notify.calls.mostRecent().args[0].translateParams,
        ).toEqual({ duration: '25m' });
        expect(bannerServiceMock.open).toHaveBeenCalledTimes(1);
        expect(bannerServiceMock.open.calls.mostRecent().args[0].id).toBe(
          BannerId.FocusModeSessionDone,
        );
      });
    });

    it('banner action opens the overlay (it does not itself start a session)', (done) => {
      spyOn(document, 'hasFocus').and.returnValue(true);
      actions$ = of(actions.completeFocusSession({ isManual: false }));
      store.overrideSelector(selectors.selectMode, FocusModeMode.Countdown);
      store.overrideSelector(selectors.selectLastSessionDuration, 25 * 60 * 1000);
      store.overrideSelector(selectors.selectIsOverlayShown, false);
      store.refreshState();
      const dispatchSpy = spyOn(store, 'dispatch');

      run(done, () => {
        const banner = bannerServiceMock.open.calls.mostRecent().args[0];
        expect(banner.action.label).toBe(T.F.FOCUS_MODE.SESSION_COMPLETED_BANNER_ACTION);
        banner.action.fn();
        expect(dispatchSpy).toHaveBeenCalledWith(actions.showFocusOverlay());
      });
    });

    it('should banner but NOT notify when the overlay is hidden and the app is focused', (done) => {
      spyOn(document, 'hasFocus').and.returnValue(true);
      actions$ = of(actions.completeFocusSession({ isManual: false }));
      store.overrideSelector(selectors.selectMode, FocusModeMode.Countdown);
      store.overrideSelector(selectors.selectLastSessionDuration, 25 * 60 * 1000);
      store.overrideSelector(selectors.selectIsOverlayShown, false);
      store.refreshState();

      run(done, () => {
        expect(notifyServiceMock.notify).not.toHaveBeenCalled();
        expect(bannerServiceMock.open).toHaveBeenCalledTimes(1);
      });
    });

    it('should NOT banner when the overlay is already shown (SessionDone is already visible)', (done) => {
      spyOn(document, 'hasFocus').and.returnValue(true);
      actions$ = of(actions.completeFocusSession({ isManual: false }));
      store.overrideSelector(selectors.selectMode, FocusModeMode.Countdown);
      store.overrideSelector(selectors.selectLastSessionDuration, 25 * 60 * 1000);
      store.overrideSelector(selectors.selectIsOverlayShown, true);
      store.refreshState();

      run(done, () => {
        expect(bannerServiceMock.open).not.toHaveBeenCalled();
        expect(notifyServiceMock.notify).not.toHaveBeenCalled();
      });
    });

    it('should notify (overlay shown, app unfocused) without a banner', (done) => {
      spyOn(document, 'hasFocus').and.returnValue(false);
      actions$ = of(actions.completeFocusSession({ isManual: false }));
      store.overrideSelector(selectors.selectMode, FocusModeMode.Countdown);
      store.overrideSelector(selectors.selectLastSessionDuration, 25 * 60 * 1000);
      store.overrideSelector(selectors.selectIsOverlayShown, true);
      store.refreshState();

      run(done, () => {
        expect(notifyServiceMock.notify).toHaveBeenCalledTimes(1);
        expect(bannerServiceMock.open).not.toHaveBeenCalled();
      });
    });

    it('should NOT fire on manual end (user-initiated, not silent)', (done) => {
      actions$ = of(actions.completeFocusSession({ isManual: true }));
      store.overrideSelector(selectors.selectMode, FocusModeMode.Countdown);
      store.refreshState();
      expectExcluded(done);
    });

    it('should NOT fire for Pomodoro (it transitions into a surfaced break, not a silent stop)', (done) => {
      actions$ = of(actions.completeFocusSession({ isManual: false }));
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.refreshState();
      expectExcluded(done);
    });

    it('should NOT fire for Flowtime (it only ever stops on explicit user action)', (done) => {
      actions$ = of(actions.completeFocusSession({ isManual: false }));
      store.overrideSelector(selectors.selectMode, FocusModeMode.Flowtime);
      store.refreshState();
      expectExcluded(done);
    });
  });

  // Self-contained module: Android needs IS_ANDROID_WEB_VIEW_TOKEN === true, which
  // must be set before FocusModeEffects is constructed, so it can't reuse the
  // shared beforeEach instance (built with the token false).
  describe('surfaceSessionDoneOnCompletion$ on Android', () => {
    let androidEffects: FocusModeEffects;
    let androidNotify: jasmine.Spy;
    let androidBannerOpen: jasmine.Spy;

    beforeEach(() => {
      TestBed.resetTestingModule();
      androidNotify = jasmine.createSpy('notify').and.resolveTo(undefined);
      androidBannerOpen = jasmine.createSpy('open');
      TestBed.configureTestingModule({
        providers: [
          FocusModeEffects,
          provideMockActions(() => actions$),
          provideMockStore({
            selectors: [
              { selector: selectors.selectMode, value: FocusModeMode.Countdown },
              {
                selector: selectors.selectLastSessionDuration,
                value: 25 * 60 * 1000,
              },
              { selector: selectors.selectIsOverlayShown, value: false },
            ],
          }),
          { provide: FocusModeStrategyFactory, useValue: { getStrategy: () => ({}) } },
          { provide: GlobalConfigService, useValue: { sound: () => ({ volume: 0 }) } },
          {
            provide: TaskService,
            useValue: { currentTaskId$: new BehaviorSubject<string | null>(null) },
          },
          {
            provide: BannerService,
            useValue: { open: androidBannerOpen, dismiss: () => {} },
          },
          { provide: MetricService, useValue: { logFocusSession: () => {} } },
          { provide: FocusModeStorageService, useValue: {} },
          {
            provide: HydrationStateService,
            useValue: { isApplyingRemoteOps: () => false },
          },
          {
            provide: TakeABreakService,
            useValue: { otherNoBreakTIme$: new BehaviorSubject<number>(0) },
          },
          { provide: NotifyService, useValue: { notify: androidNotify } },
          {
            provide: GlobalTrackingIntervalService,
            useValue: { todayStr$: new BehaviorSubject<string>('2024-01-19') },
          },
          { provide: IS_ANDROID_WEB_VIEW_TOKEN, useValue: true },
        ],
      });
      androidEffects = TestBed.inject(FocusModeEffects);
    });

    it('should banner but NOT notify on Android (native posts the completion notification)', (done) => {
      spyOn(document, 'hasFocus').and.returnValue(false);
      actions$ = of(actions.completeFocusSession({ isManual: false }));

      androidEffects.surfaceSessionDoneOnCompletion$.subscribe({
        complete: () => {
          expect(androidNotify).not.toHaveBeenCalled();
          expect(androidBannerOpen).toHaveBeenCalledTimes(1);
          done();
        },
      });
    });
  });

  describe('syncTrackingStartToSession$', () => {
    it('should dispatch startFocusSession when autoStartFocusOnPlay is on and task is selected on Main screen (overlay hidden)', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        autoStartFocusOnPlay: true,
        isSkipPreparation: false,
      });
      store.overrideSelector(selectors.selectTimer, createMockTimer());
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.overrideSelector(selectors.selectCurrentScreen, FocusScreen.Main);
      store.overrideSelector(selectors.selectIsOverlayShown, false);
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      setTimeout(() => {
        currentTaskId$.next('task-123');
      }, 10);

      effects.syncTrackingStartToSession$.pipe(take(1)).subscribe((action) => {
        expect(action).toEqual(
          actions.startFocusSession({
            duration: 25 * 60 * 1000,
          }),
        );
        done();
      });
    });

    // Bug #7384 fix preserved: when the user is inside the focus-mode overlay
    // and has opted into the preparation screen (`isShowPreparation` ON), the
    // effect must NOT auto-start the session — the user should click 'Start'
    // manually to see the rocket countdown.
    it('should NOT dispatch when overlay is shown and isShowPreparation is true (issue #7384)', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        autoStartFocusOnPlay: true,
        isSkipPreparation: false,
        isShowPreparation: true,
      });
      store.overrideSelector(selectors.selectTimer, createMockTimer());
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.overrideSelector(selectors.selectCurrentScreen, FocusScreen.Main);
      store.overrideSelector(selectors.selectIsOverlayShown, true);
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      const emitted: any[] = [];
      effects.syncTrackingStartToSession$.subscribe((action) => {
        emitted.push(action);
      });

      currentTaskId$.next('task-123');

      setTimeout(() => {
        expect(emitted).toEqual([]);
        done();
      }, 50);
    });

    it('should dispatch unPauseFocusSession when session is paused and task is selected', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      // Session is paused (purpose is 'work' but not running)
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: false, purpose: 'work' }),
      );
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.overrideSelector(selectors.selectCurrentScreen, FocusScreen.Main);
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      setTimeout(() => {
        currentTaskId$.next('task-123');
      }, 10);

      effects.syncTrackingStartToSession$.pipe(take(1)).subscribe((action) => {
        expect(action).toEqual(actions.unPauseFocusSession());
        done();
      });
    });

    it('should NOT dispatch when session is already running', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: true, purpose: 'work' }),
      );
      store.overrideSelector(selectors.selectCurrentScreen, FocusScreen.Main);
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      const { emitted, subscription } = collectEmissions(
        effects.syncTrackingStartToSession$,
      );
      currentTaskId$.next('task-123');

      setTimeout(() => {
        expect(emitted).toEqual([]);
        subscription.unsubscribe();
        done();
      }, 50);
    });

    it('should NOT dispatch when on SessionDone screen', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(selectors.selectTimer, createMockTimer());
      store.overrideSelector(selectors.selectMode, FocusModeMode.Flowtime);
      store.overrideSelector(selectors.selectCurrentScreen, FocusScreen.SessionDone);
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      const { emitted, subscription } = collectEmissions(
        effects.syncTrackingStartToSession$,
      );
      currentTaskId$.next('task-123');

      setTimeout(() => {
        expect(emitted).toEqual([]);
        subscription.unsubscribe();
        done();
      }, 50);
    });

    it('should NOT dispatch when on Break screen', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(selectors.selectTimer, createMockTimer());
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.overrideSelector(selectors.selectCurrentScreen, FocusScreen.Break);
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      const { emitted, subscription } = collectEmissions(
        effects.syncTrackingStartToSession$,
      );
      currentTaskId$.next('task-123');

      setTimeout(() => {
        expect(emitted).toEqual([]);
        subscription.unsubscribe();
        done();
      }, 50);
    });

    // Bug #6726 fix: When user starts tracking during active break, skipBreak should NOT carry stale pausedTaskId
    it('should dispatch skipBreak with pausedTaskId undefined when user starts tracking during break', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: true, purpose: 'break' }),
      );
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.overrideSelector(selectors.selectCurrentScreen, FocusScreen.Break);
      store.overrideSelector(selectors.selectPausedTaskId, 'original-task-id');
      store.overrideSelector(selectors.selectIsResumingBreak, false);
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      setTimeout(() => {
        currentTaskId$.next('new-task-id');
      }, 10);

      effects.syncTrackingStartToSession$.pipe(take(1)).subscribe((action) => {
        expect(action).toEqual(actions.skipBreak({ pausedTaskId: undefined }));
        done();
      });
    });

    it('should NOT dispatch when isFocusModeEnabled is false', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(selectors.selectTimer, createMockTimer());
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.overrideSelector(selectors.selectCurrentScreen, FocusScreen.Main);
      store.overrideSelector(selectIsFocusModeEnabled, false);
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      const { emitted, subscription } = collectEmissions(
        effects.syncTrackingStartToSession$,
      );
      currentTaskId$.next('task-123');

      setTimeout(() => {
        expect(emitted).toEqual([]);
        subscription.unsubscribe();
        done();
      }, 50);
    });

    // Regression test for #6521: config changes should NOT re-trigger session start
    it('should NOT re-dispatch when config changes while task is already selected', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        autoStartFocusOnPlay: true,
        isSkipPreparation: true,
      });
      store.overrideSelector(selectors.selectTimer, createMockTimer());
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.overrideSelector(selectors.selectCurrentScreen, FocusScreen.Main);
      store.overrideSelector(selectors.selectIsOverlayShown, false);
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      const emitted: any[] = [];
      effects.syncTrackingStartToSession$.subscribe((action) => {
        emitted.push(action);
      });

      // Select a task (should trigger once)
      currentTaskId$.next('task-123');

      setTimeout(() => {
        expect(emitted.length).toBe(1);

        // Toggle an unrelated config setting — should NOT re-trigger
        store.overrideSelector(selectFocusModeConfig, {
          autoStartFocusOnPlay: true,
          isSkipPreparation: true,
          focusModeSound: 'tick',
        });
        store.refreshState();

        setTimeout(() => {
          expect(emitted.length).toBe(1);
          done();
        }, 50);
      }, 20);
    });

    // Regression test for #6521: toggling focus mode enabled should NOT re-trigger session start
    it('should NOT re-dispatch when isFocusModeEnabled is toggled while task is selected', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        autoStartFocusOnPlay: true,
        isSkipPreparation: true,
      });
      store.overrideSelector(selectors.selectTimer, createMockTimer());
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.overrideSelector(selectors.selectCurrentScreen, FocusScreen.Main);
      store.overrideSelector(selectors.selectIsOverlayShown, false);
      store.overrideSelector(selectIsFocusModeEnabled, true);
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      const emitted: any[] = [];
      effects.syncTrackingStartToSession$.subscribe((action) => {
        emitted.push(action);
      });

      // Select a task
      currentTaskId$.next('task-123');

      setTimeout(() => {
        expect(emitted.length).toBe(1);

        // Toggle focus mode off and back on — should NOT re-trigger
        store.overrideSelector(selectIsFocusModeEnabled, false);
        store.refreshState();

        setTimeout(() => {
          store.overrideSelector(selectIsFocusModeEnabled, true);
          store.refreshState();

          setTimeout(() => {
            expect(emitted.length).toBe(1);
            done();
          }, 50);
        }, 20);
      }, 20);
    });
  });

  describe('syncTrackingStopToSession$', () => {
    it('should dispatch pauseFocusSession when tracking stops and session is running', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: true, purpose: 'work' }),
      );
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      // Simulate tracking stopping: emit task ID first, then null
      currentTaskId$.next('task-123');

      effects.syncTrackingStopToSession$.pipe(take(1)).subscribe((action) => {
        expect(action.type).toEqual(actions.pauseFocusSession.type);
        expect((action as any).pausedTaskId).toBe('task-123');
        done();
      });

      // After a short delay, stop tracking
      setTimeout(() => {
        currentTaskId$.next(null);
      }, 10);
    });

    it('should NOT dispatch when session is not running', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: false, purpose: 'work' }),
      );
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      const { emitted, subscription } = collectEmissions(
        effects.syncTrackingStopToSession$,
      );
      currentTaskId$.next('task-123');

      setTimeout(() => {
        currentTaskId$.next(null);
      }, 10);

      setTimeout(() => {
        expect(emitted).toEqual([]);
        subscription.unsubscribe();
        done();
      }, 50);
    });

    it('should dispatch pauseFocusSession during break when tracking stops (Bug #5954)', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: true, purpose: 'break' }),
      );
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      let dispatched = false;
      effects.syncTrackingStopToSession$.subscribe((action) => {
        expect(action.type).toBe('[FocusMode] Pause Session');
        expect((action as any).pausedTaskId).toBe('task-123');
        dispatched = true;
      });

      currentTaskId$.next('task-123');

      setTimeout(() => {
        currentTaskId$.next(null);
      }, 10);

      setTimeout(() => {
        expect(dispatched).toBe(true);
        done();
      }, 100);
    });

    it('should NOT dispatch when switching to different task (not stopping)', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: true, purpose: 'work' }),
      );
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      const { emitted, subscription } = collectEmissions(
        effects.syncTrackingStopToSession$,
      );
      currentTaskId$.next('task-123');

      setTimeout(() => {
        // Switch to different task, not null
        currentTaskId$.next('task-456');
      }, 10);

      setTimeout(() => {
        expect(emitted).toEqual([]);
        subscription.unsubscribe();
        done();
      }, 50);
    });

    it('should NOT dispatch when isFocusModeEnabled is false', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: true, purpose: 'work' }),
      );
      store.overrideSelector(selectIsFocusModeEnabled, false);
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      const { emitted, subscription } = collectEmissions(
        effects.syncTrackingStopToSession$,
      );
      currentTaskId$.next('task-123');

      setTimeout(() => {
        currentTaskId$.next(null);
      }, 10);

      setTimeout(() => {
        expect(emitted).toEqual([]);
        subscription.unsubscribe();
        done();
      }, 50);
    });

    it('should NOT dispatch when sync is applying remote operations (skipDuringSync)', (done) => {
      // This test verifies the fix for the app freeze bug:
      // When sync is active, the effect should be skipped to prevent
      // cascading action dispatches that overwhelm the store.
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: true, purpose: 'work' }),
      );
      store.refreshState();

      // Simulate sync being active
      hydrationStateServiceMock.isApplyingRemoteOps.and.returnValue(true);

      effects = TestBed.inject(FocusModeEffects);

      const { emitted, subscription } = collectEmissions(
        effects.syncTrackingStopToSession$,
      );
      currentTaskId$.next('task-123');

      setTimeout(() => {
        currentTaskId$.next(null);
      }, 10);

      setTimeout(() => {
        expect(emitted).toEqual([]);
        subscription.unsubscribe();
        done();
      }, 50);
    });

    it('should dispatch normally when sync completes (skipDuringSync allows)', (done) => {
      // Verify the effect works normally when not during sync
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: true, purpose: 'work' }),
      );
      store.refreshState();

      // Sync is NOT active
      hydrationStateServiceMock.isApplyingRemoteOps.and.returnValue(false);

      effects = TestBed.inject(FocusModeEffects);

      currentTaskId$.next('task-123');

      effects.syncTrackingStopToSession$.pipe(take(1)).subscribe((action) => {
        expect(action.type).toEqual(actions.pauseFocusSession.type);
        expect((action as any).pausedTaskId).toBe('task-123');
        done();
      });

      setTimeout(() => {
        currentTaskId$.next(null);
      }, 10);
    });

    it('should NOT dispatch during rapid currentTaskId changes while sync is active (freeze prevention)', (done) => {
      // This test simulates the freeze scenario: rapid task ID changes during sync
      // Without skipDuringSync, each change would trigger pauseFocusSession,
      // causing cascading effects that freeze the UI
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: true, purpose: 'work' }),
      );
      store.refreshState();

      // Sync is active
      hydrationStateServiceMock.isApplyingRemoteOps.and.returnValue(true);

      effects = TestBed.inject(FocusModeEffects);

      let emitCount = 0;
      effects.syncTrackingStopToSession$.subscribe(() => {
        emitCount++;
      });

      // Simulate rapid task ID changes during sync (like bulk operations)
      currentTaskId$.next('task-1');
      currentTaskId$.next('task-2');
      currentTaskId$.next(null);
      currentTaskId$.next('task-3');
      currentTaskId$.next(null);
      currentTaskId$.next('task-4');
      currentTaskId$.next(null);

      setTimeout(() => {
        // Effect should NOT fire at all during sync
        expect(emitCount).toBe(0);
        done();
      }, 50);
    });

    it('should resume normal behavior after sync completes', (done) => {
      // Verify that after sync ends, the effect works correctly again
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: true, purpose: 'work' }),
      );
      store.refreshState();

      // Start with sync active
      hydrationStateServiceMock.isApplyingRemoteOps.and.returnValue(true);

      effects = TestBed.inject(FocusModeEffects);

      effects.syncTrackingStopToSession$.subscribe((action) => {
        expect(action.type).toEqual(actions.pauseFocusSession.type);
        expect((action as any).pausedTaskId).toBe('task-after-sync');
        done();
      });

      // Changes during sync - should be ignored
      currentTaskId$.next('task-during-sync');

      setTimeout(() => {
        currentTaskId$.next(null);
      }, 10);

      setTimeout(() => {
        // Sync completes
        hydrationStateServiceMock.isApplyingRemoteOps.and.returnValue(false);

        // Now changes should be processed
        currentTaskId$.next('task-after-sync');

        setTimeout(() => {
          currentTaskId$.next(null);
        }, 10);
      }, 30);
    });

    it('should handle sync state toggling rapidly without crashing', (done) => {
      // Edge case: sync state changes rapidly while task ID also changes
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: true, purpose: 'work' }),
      );
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      let emitCount = 0;
      effects.syncTrackingStopToSession$.subscribe(() => {
        emitCount++;
      });

      // Rapid sync state changes with task changes
      hydrationStateServiceMock.isApplyingRemoteOps.and.returnValue(true);
      currentTaskId$.next('task-1');

      hydrationStateServiceMock.isApplyingRemoteOps.and.returnValue(false);
      currentTaskId$.next('task-2');

      hydrationStateServiceMock.isApplyingRemoteOps.and.returnValue(true);
      currentTaskId$.next(null);

      hydrationStateServiceMock.isApplyingRemoteOps.and.returnValue(false);
      currentTaskId$.next('task-3');

      setTimeout(() => {
        // Should not crash and should handle state changes gracefully
        // The exact emit count depends on timing, but it should not freeze
        expect(emitCount).toBeGreaterThanOrEqual(0);
        done();
      }, 50);
    });

    it('should correctly use pairwise after skipDuringSync filters out emissions', (done) => {
      // Verify that pairwise works correctly with skipDuringSync
      // When sync filters out emissions, pairwise should still work on remaining emissions
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: true, purpose: 'work' }),
      );
      store.refreshState();

      hydrationStateServiceMock.isApplyingRemoteOps.and.returnValue(false);

      effects = TestBed.inject(FocusModeEffects);

      const emittedActions: any[] = [];
      effects.syncTrackingStopToSession$.pipe(take(2)).subscribe((action) => {
        emittedActions.push(action);
        if (emittedActions.length === 2) {
          // Both emissions should have correct pausedTaskId from pairwise
          expect(emittedActions[0].pausedTaskId).toBe('task-A');
          expect(emittedActions[1].pausedTaskId).toBe('task-B');
          done();
        }
      });

      // First pair: task-A -> null
      currentTaskId$.next('task-A');
      setTimeout(() => {
        currentTaskId$.next(null);

        // Second pair: task-B -> null
        setTimeout(() => {
          currentTaskId$.next('task-B');
          setTimeout(() => {
            currentTaskId$.next(null);
          }, 10);
        }, 20);
      }, 10);
    });
  });

  describe('syncSessionPauseToTracking$', () => {
    it('should dispatch unsetCurrentTask when session pauses with pausedTaskId', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: false, purpose: 'work' }),
      );
      store.refreshState();
      currentTaskId$.next('task-123');

      actions$ = of(actions.pauseFocusSession({ pausedTaskId: 'task-123' }));

      effects.syncSessionPauseToTracking$.subscribe((action) => {
        expect(action.type).toEqual('[Task] UnsetCurrentTask');
        done();
      });
    });

    // Skip the redundant unsetCurrentTask when current task is already null —
    // otherwise the no-op dispatch would clobber lastCurrentTaskId in the reducer.
    it('should NOT dispatch when currentTaskId is already null', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: false, purpose: 'work' }),
      );
      store.refreshState();
      currentTaskId$.next(null);

      actions$ = of(actions.pauseFocusSession({ pausedTaskId: 'task-123' }));

      let emitted = false;
      effects.syncSessionPauseToTracking$.subscribe(() => {
        emitted = true;
      });

      setTimeout(() => {
        expect(emitted).toBe(false);
        done();
      }, 50);
    });

    it('should NOT dispatch when pausedTaskId is null', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: false, purpose: 'work' }),
      );
      store.refreshState();

      actions$ = of(actions.pauseFocusSession({ pausedTaskId: null }));

      let emitted = false;
      effects.syncSessionPauseToTracking$.subscribe(() => {
        emitted = true;
      });

      setTimeout(() => {
        expect(emitted).toBe(false);
        done();
      }, 50);
    });

    // Bug #5974 fix: Pausing break should also stop tracking to maintain sync
    it('should dispatch unsetCurrentTask when break pauses with pausedTaskId', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: false, purpose: 'break' }),
      );
      store.refreshState();
      currentTaskId$.next('task-123');

      actions$ = of(actions.pauseFocusSession({ pausedTaskId: 'task-123' }));

      effects.syncSessionPauseToTracking$.subscribe((action) => {
        expect(action.type).toEqual('[Task] UnsetCurrentTask');
        done();
      });
    });

    // Bug #5974: Additional edge case tests for break pause
    it('should NOT dispatch when break pauses but pausedTaskId is null', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: false, purpose: 'break' }),
      );
      store.refreshState();

      actions$ = of(actions.pauseFocusSession({ pausedTaskId: null }));

      let emitted = false;
      effects.syncSessionPauseToTracking$.subscribe(() => {
        emitted = true;
      });

      setTimeout(() => {
        expect(emitted).toBe(false);
        done();
      }, 50);
    });
  });

  describe('syncSessionResumeToTracking$', () => {
    it('should dispatch setCurrentTask when session resumes with pausedTaskId', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: true, purpose: 'work' }),
      );
      store.overrideSelector(selectors.selectPausedTaskId, 'task-123');
      // Mock that the task exists
      store.overrideSelector(selectTaskById as any, {
        id: 'task-123',
        title: 'Test Task',
      });
      currentTaskId$.next(null); // No current task
      store.refreshState();

      actions$ = of(actions.unPauseFocusSession());

      effects.syncSessionResumeToTracking$.subscribe((action) => {
        expect(action.type).toEqual('[Task] SetCurrentTask');
        expect((action as any).id).toBe('task-123');
        done();
      });
    });

    it('should dispatch setCurrentTask when WORK resumes even with isPauseTrackingDuringBreak true', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isPauseTrackingDuringBreak: true,
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: true, purpose: 'work' }),
      );
      store.overrideSelector(selectors.selectPausedTaskId, 'task-123');
      store.overrideSelector(selectTaskById as any, {
        id: 'task-123',
        title: 'Test Task',
      });
      currentTaskId$.next(null);
      store.refreshState();

      actions$ = of(actions.unPauseFocusSession());

      effects.syncSessionResumeToTracking$.subscribe((action) => {
        expect(action.type).toEqual('[Task] SetCurrentTask');
        expect((action as any).id).toBe('task-123');
        done();
      });
    });

    it('should NOT dispatch when no pausedTaskId', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: true, purpose: 'work' }),
      );
      store.overrideSelector(selectors.selectPausedTaskId, null);
      currentTaskId$.next(null);
      store.refreshState();

      actions$ = of(actions.unPauseFocusSession());

      let emitted = false;
      effects.syncSessionResumeToTracking$.subscribe(() => {
        emitted = true;
      });

      setTimeout(() => {
        expect(emitted).toBe(false);
        done();
      }, 50);
    });

    it('should NOT dispatch when already tracking a task', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: true, purpose: 'work' }),
      );
      store.overrideSelector(selectors.selectPausedTaskId, 'task-123');
      currentTaskId$.next('task-456'); // Already tracking a different task
      store.refreshState();

      actions$ = of(actions.unPauseFocusSession());

      let emitted = false;
      effects.syncSessionResumeToTracking$.subscribe(() => {
        emitted = true;
      });

      setTimeout(() => {
        expect(emitted).toBe(false);
        done();
      }, 50);
    });

    it('should NOT dispatch setCurrentTask when task no longer exists', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: true, purpose: 'work' }),
      );
      store.overrideSelector(selectors.selectPausedTaskId, 'deleted-task-123');
      // Mock that the task doesn't exist (use any cast for parameterized selector)
      store.overrideSelector(selectTaskById as any, undefined as any);
      currentTaskId$.next(null);
      store.refreshState();

      actions$ = of(actions.unPauseFocusSession());

      let emitted = false;
      effects.syncSessionResumeToTracking$.subscribe(() => {
        emitted = true;
      });

      setTimeout(() => {
        expect(emitted).toBe(false);
        done();
      }, 50);
    });

    it('should dispatch setCurrentTask when BREAK resumes with pausedTaskId', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: true, purpose: 'break' }),
      );
      store.overrideSelector(selectors.selectPausedTaskId, 'task-123');
      // Mock that the task exists
      store.overrideSelector(selectTaskById as any, {
        id: 'task-123',
        title: 'Test Task',
      });
      currentTaskId$.next(null); // No current task
      store.refreshState();

      actions$ = of(actions.unPauseFocusSession());

      effects.syncSessionResumeToTracking$.subscribe((action) => {
        expect(action.type).toEqual('[Task] SetCurrentTask');
        expect((action as any).id).toBe('task-123');
        done();
      });
    });

    it('should dispatch clearResumingBreakFlag (not setCurrentTask) when BREAK resumes and isPauseTrackingDuringBreak is true', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isPauseTrackingDuringBreak: true,
        isSkipPreparation: false,
      });
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({ isRunning: true, purpose: 'break' }),
      );
      store.overrideSelector(selectors.selectPausedTaskId, 'task-123');
      store.overrideSelector(selectTaskById as any, {
        id: 'task-123',
        title: 'Test Task',
      });
      currentTaskId$.next(null);
      store.refreshState();

      actions$ = of(actions.unPauseFocusSession());

      effects.syncSessionResumeToTracking$.subscribe((action) => {
        expect(action.type).toEqual(actions.clearResumingBreakFlag.type);
        done();
      });
    });
  });

  describe('syncSessionStartToTracking$', () => {
    it('should dispatch setCurrentTask when session starts with pausedTaskId and no current task', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(selectors.selectPausedTaskId, 'task-123');
      store.overrideSelector(selectLastCurrentTask, null);
      // Mock that the task exists
      store.overrideSelector(selectTaskById as any, {
        id: 'task-123',
        title: 'Test Task',
        isDone: false,
      });
      currentTaskId$.next(null);
      store.refreshState();

      actions$ = of(actions.startFocusSession({ duration: 25 * 60 * 1000 }));

      effects.syncSessionStartToTracking$.subscribe((action) => {
        expect(action.type).toEqual('[Task] SetCurrentTask');
        expect((action as any).id).toBe('task-123');
        done();
      });
    });

    it('should NOT dispatch when already tracking a task', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(selectors.selectPausedTaskId, 'task-123');
      store.overrideSelector(selectLastCurrentTask, null);
      currentTaskId$.next('task-456'); // Already tracking
      store.refreshState();

      actions$ = of(actions.startFocusSession({ duration: 25 * 60 * 1000 }));

      let emitted = false;
      effects.syncSessionStartToTracking$.subscribe(() => {
        emitted = true;
      });

      setTimeout(() => {
        expect(emitted).toBe(false);
        done();
      }, 50);
    });

    it('should NOT dispatch when no pausedTaskId and no lastCurrentTask', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(selectors.selectPausedTaskId, null);
      store.overrideSelector(selectLastCurrentTask, null);
      currentTaskId$.next(null);
      store.refreshState();

      actions$ = of(actions.startFocusSession({ duration: 25 * 60 * 1000 }));

      let emitted = false;
      effects.syncSessionStartToTracking$.subscribe(() => {
        emitted = true;
      });

      setTimeout(() => {
        expect(emitted).toBe(false);
        done();
      }, 50);
    });

    it('should dispatch showFocusOverlay when task no longer exists (Bug #5954)', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(selectors.selectPausedTaskId, 'deleted-task-123');
      store.overrideSelector(selectLastCurrentTask, null);
      // Mock that the task doesn't exist (use any cast for parameterized selector)
      store.overrideSelector(selectTaskById as any, undefined as any);
      currentTaskId$.next(null);
      store.refreshState();

      actions$ = of(actions.startFocusSession({ duration: 25 * 60 * 1000 }));

      effects.syncSessionStartToTracking$.subscribe((action) => {
        expect(action.type).toEqual('[FocusMode] Show Overlay');
        done();
      });
    });

    it('should fall back to lastCurrentTask when no pausedTaskId (Bug #5954)', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(selectors.selectPausedTaskId, null);
      store.overrideSelector(selectLastCurrentTask, {
        id: 'last-task-123',
        title: 'Last Task',
        isDone: false,
      } as any);
      // Mock that the task exists
      store.overrideSelector(selectTaskById as any, {
        id: 'last-task-123',
        title: 'Last Task',
        isDone: false,
      });
      currentTaskId$.next(null);
      store.refreshState();

      actions$ = of(actions.startFocusSession({ duration: 25 * 60 * 1000 }));

      effects.syncSessionStartToTracking$.subscribe((action) => {
        expect(action.type).toEqual('[Task] SetCurrentTask');
        expect((action as any).id).toBe('last-task-123');
        done();
      });
    });

    it('should dispatch showFocusOverlay when lastCurrentTask is done (Bug #5954)', (done) => {
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
      });
      store.overrideSelector(selectors.selectPausedTaskId, null);
      store.overrideSelector(selectLastCurrentTask, {
        id: 'done-task-123',
        title: 'Done Task',
        isDone: true,
      } as any);
      // Mock that the task exists but is done
      store.overrideSelector(selectTaskById as any, {
        id: 'done-task-123',
        title: 'Done Task',
        isDone: true,
      });
      currentTaskId$.next(null);
      store.refreshState();

      actions$ = of(actions.startFocusSession({ duration: 25 * 60 * 1000 }));

      effects.syncSessionStartToTracking$.subscribe((action) => {
        expect(action.type).toEqual('[FocusMode] Show Overlay');
        done();
      });
    });
  });

  describe('pauseTrackingDuringBreak (autoStartBreakOnSessionComplete$)', () => {
    it('should dispatch unsetCurrentTask when break starts and isPauseTrackingDuringBreak is true', (done) => {
      actions$ = of(actions.incrementCycle());
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.overrideSelector(selectors.selectCurrentCycle, 1);
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
        isPauseTrackingDuringBreak: true,
      });
      currentTaskId$.next('task-123');
      store.refreshState();

      effects.autoStartBreakOnSessionComplete$.pipe(toArray()).subscribe((actionsArr) => {
        const unsetAction = actionsArr.find((a) => a.type === '[Task] UnsetCurrentTask');
        expect(unsetAction).toBeDefined();
        done();
      });
    });

    it('should NOT dispatch unsetCurrentTask when isPauseTrackingDuringBreak is false', (done) => {
      actions$ = of(actions.incrementCycle());
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.overrideSelector(selectors.selectCurrentCycle, 1);
      store.overrideSelector(selectFocusModeConfig, {
        isSkipPreparation: false,
        isPauseTrackingDuringBreak: false,
      });
      currentTaskId$.next('task-123');
      store.refreshState();

      effects.autoStartBreakOnSessionComplete$.pipe(toArray()).subscribe((actionsArr) => {
        const unsetAction = actionsArr.find((a) => a.type === '[Task] UnsetCurrentTask');
        expect(unsetAction).toBeUndefined();
        done();
      });
    });
  });

  describe('detectSessionCompletion$', () => {
    it('should dispatch completeFocusSession when timer completes (elapsed >= duration)', (done) => {
      // Setup timer that just completed
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({
          isRunning: false,
          purpose: 'work',
          duration: 25 * 60 * 1000,
          elapsed: 25 * 60 * 1000, // Exactly at duration
        }),
      );
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.refreshState();

      // Need to recreate effects after selector override
      effects = TestBed.inject(FocusModeEffects);

      effects.detectSessionCompletion$.pipe(take(1)).subscribe((action) => {
        expect(action).toEqual(actions.completeFocusSession({ isManual: false }));
        done();
      });
    });

    it('should NOT dispatch for Flowtime mode (timer runs indefinitely)', (done) => {
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({
          isRunning: false,
          purpose: 'work',
          duration: 25 * 60 * 1000,
          elapsed: 25 * 60 * 1000,
        }),
      );
      store.overrideSelector(selectors.selectMode, FocusModeMode.Flowtime);
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      const { emitted, subscription } = collectEmissions(
        effects.detectSessionCompletion$,
      );

      setTimeout(() => {
        expect(emitted).toEqual([]);
        subscription.unsubscribe();
        done();
      }, 50);
    });

    it('should NOT dispatch when timer is still running', (done) => {
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({
          isRunning: true, // Still running
          purpose: 'work',
          duration: 25 * 60 * 1000,
          elapsed: 25 * 60 * 1000,
        }),
      );
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      const { emitted, subscription } = collectEmissions(
        effects.detectSessionCompletion$,
      );

      setTimeout(() => {
        expect(emitted).toEqual([]);
        subscription.unsubscribe();
        done();
      }, 50);
    });

    it('should NOT dispatch when elapsed < duration', (done) => {
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({
          isRunning: false,
          purpose: 'work',
          duration: 25 * 60 * 1000,
          elapsed: 20 * 60 * 1000, // Not complete yet
        }),
      );
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      const { emitted, subscription } = collectEmissions(
        effects.detectSessionCompletion$,
      );

      setTimeout(() => {
        expect(emitted).toEqual([]);
        subscription.unsubscribe();
        done();
      }, 50);
    });

    it('should NOT dispatch when purpose is break', (done) => {
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({
          isRunning: false,
          purpose: 'break', // Not a work session
          duration: 5 * 60 * 1000,
          elapsed: 5 * 60 * 1000,
        }),
      );
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      const { emitted, subscription } = collectEmissions(
        effects.detectSessionCompletion$,
      );

      setTimeout(() => {
        expect(emitted).toEqual([]);
        subscription.unsubscribe();
        done();
      }, 50);
    });

    // #7856: after a native timer-complete ends the session (createIdleTimer ->
    // purpose null), a following resume tick must NOT make this effect dispatch a
    // SECOND completeFocusSession. Guards the no-double-completion race invariant.
    it('should NOT dispatch when the session is already idle (purpose null)', (done) => {
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({
          isRunning: false,
          purpose: null,
          duration: 0,
          elapsed: 0,
        }),
      );
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      const { emitted, subscription } = collectEmissions(
        effects.detectSessionCompletion$,
      );

      setTimeout(() => {
        expect(emitted).toEqual([]);
        subscription.unsubscribe();
        done();
      }, 50);
    });

    // Overtime: when _isOvertimeEnabled is true, the tick reducer keeps the timer running,
    // so detectSessionCompletion$ should never fire. This guard handles the edge case
    // where the user pauses during overtime (isRunning becomes false).
    it('should NOT dispatch when _isOvertimeEnabled is true (pause during overtime)', (done) => {
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({
          isRunning: false,
          purpose: 'work',
          duration: 25 * 60 * 1000,
          elapsed: 26 * 60 * 1000,
        }),
      );
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.overrideSelector(selectors.selectIsOvertimeEnabled, true);
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      const { emitted, subscription } = collectEmissions(
        effects.detectSessionCompletion$,
      );

      setTimeout(() => {
        expect(emitted).toEqual([]);
        subscription.unsubscribe();
        done();
      }, 50);
    });

    // Bug #7707: dragging the timer down to 0 (or starting a non-Flowtime session
    // with duration 0) must still reach the SessionDone screen. The reducer flips
    // isRunning to false on the next tick; this effect must then dispatch
    // completeFocusSession even though duration is 0.
    it('should dispatch completeFocusSession when duration is 0 (Bug #7707)', (done) => {
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({
          isRunning: false,
          purpose: 'work',
          duration: 0,
          elapsed: 60 * 1000, // user focused for 1 min, then dragged duration to 0
        }),
      );
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.overrideSelector(selectors.selectIsOvertimeEnabled, false);
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      effects.detectSessionCompletion$.pipe(take(1)).subscribe((action) => {
        expect(action).toEqual(actions.completeFocusSession({ isManual: false }));
        done();
      });
    });

    // Bug #6206 updated: with overtime, isManualBreakStart=true causes the timer to keep
    // running (via _isOvertimeEnabled). The user completes the session manually.
    // When _isOvertimeEnabled is false (non-manual-break sessions), auto-completion still works.
    it('should dispatch completeFocusSession when timer stops with _isOvertimeEnabled=false', (done) => {
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({
          isRunning: false,
          purpose: 'work',
          duration: 25 * 60 * 1000,
          elapsed: 25 * 60 * 1000,
        }),
      );
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.overrideSelector(selectors.selectIsOvertimeEnabled, false);
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);

      effects.detectSessionCompletion$.pipe(take(1)).subscribe((action) => {
        expect(action).toEqual(actions.completeFocusSession({ isManual: false }));
        done();
      });
    });
  });

  describe('setOvertimeOnSessionStart$', () => {
    it('should enable overtime for Pomodoro with isManualBreakStart=true', (done) => {
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.overrideSelector(selectFocusModeConfig, {
        isManualBreakStart: true,
        isSkipPreparation: false,
      });
      store.refreshState();
      actions$ = of(actions.startFocusSession({ duration: 25 * 60 * 1000 }));

      effects = TestBed.inject(FocusModeEffects);

      effects.setOvertimeOnSessionStart$.pipe(take(1)).subscribe((action) => {
        expect(action).toEqual(actions.setOvertimeEnabled({ enabled: true }));
        done();
      });
    });

    it('should NOT enable overtime when isManualBreakStart is false', (done) => {
      store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
      store.overrideSelector(selectFocusModeConfig, {
        isManualBreakStart: false,
        isSkipPreparation: false,
      });
      store.refreshState();
      actions$ = of(actions.startFocusSession({ duration: 25 * 60 * 1000 }));

      effects = TestBed.inject(FocusModeEffects);

      effects.setOvertimeOnSessionStart$.pipe(take(1)).subscribe((action) => {
        expect(action).toEqual(actions.setOvertimeEnabled({ enabled: false }));
        done();
      });
    });

    it('should NOT enable overtime for Flowtime mode', (done) => {
      store.overrideSelector(selectors.selectMode, FocusModeMode.Flowtime);
      store.overrideSelector(selectFocusModeConfig, {
        isManualBreakStart: true,
        isSkipPreparation: false,
      });
      store.refreshState();
      actions$ = of(actions.startFocusSession({ duration: 0 }));

      effects = TestBed.inject(FocusModeEffects);

      effects.setOvertimeOnSessionStart$.pipe(take(1)).subscribe((action) => {
        expect(action).toEqual(actions.setOvertimeEnabled({ enabled: false }));
        done();
      });
    });

    it('should NOT enable overtime for Countdown mode', (done) => {
      store.overrideSelector(selectors.selectMode, FocusModeMode.Countdown);
      store.overrideSelector(selectFocusModeConfig, {
        isManualBreakStart: true,
        isSkipPreparation: false,
      });
      store.refreshState();
      actions$ = of(actions.startFocusSession({ duration: 25 * 60 * 1000 }));

      effects = TestBed.inject(FocusModeEffects);

      effects.setOvertimeOnSessionStart$.pipe(take(1)).subscribe((action) => {
        expect(action).toEqual(actions.setOvertimeEnabled({ enabled: false }));
        done();
      });
    });
  });

  describe('detectBreakTimeUp$', () => {
    it('should call notification when break timer completes', (done) => {
      const breakDuration = 5 * 60 * 1000;
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({
          isRunning: false,
          purpose: 'break',
          startedAt: Date.now() - breakDuration,
          duration: breakDuration,
          elapsed: breakDuration,
        }),
      );
      store.refreshState();

      // Create new effects instance and spy on _notifyUser
      effects = TestBed.inject(FocusModeEffects);
      const notifyUserSpy = spyOn(effects as any, '_notifyUser');

      effects.detectBreakTimeUp$.pipe(take(1)).subscribe(() => {
        expect(notifyUserSpy).toHaveBeenCalled();
        done();
      });
    });

    it('should NOT trigger while break timer is running (elapsed < duration)', (done) => {
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({
          isRunning: true,
          purpose: 'break',
          duration: 5 * 60 * 1000,
          elapsed: 3 * 60 * 1000, // Not complete
        }),
      );
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);
      const notifyUserSpy = spyOn(effects as any, '_notifyUser');

      setTimeout(() => {
        expect(notifyUserSpy).not.toHaveBeenCalled();
        done();
      }, 50);
    });

    // Regression: reducer drops the `duration > 0` completion guard, so a
    // break with duration=0 stops immediately. The effect must match — without
    // this, the break ends silently with no notification.
    it('should notify when a duration=0 break stops', (done) => {
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({
          isRunning: false,
          purpose: 'break',
          startedAt: Date.now() - 1000,
          duration: 0,
          elapsed: 0,
        }),
      );
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);
      const notifyUserSpy = spyOn(effects as any, '_notifyUser');

      effects.detectBreakTimeUp$.pipe(take(1)).subscribe(() => {
        expect(notifyUserSpy).toHaveBeenCalled();
        done();
      });
    });

    it('should NOT notify for an unstarted duration=0 break offer', (done) => {
      store.overrideSelector(
        selectors.selectTimer,
        createMockTimer({
          isRunning: false,
          purpose: 'break',
          startedAt: null,
          duration: 0,
          elapsed: 0,
        }),
      );
      store.refreshState();

      effects = TestBed.inject(FocusModeEffects);
      const notifyUserSpy = spyOn(effects as any, '_notifyUser');

      setTimeout(() => {
        expect(notifyUserSpy).not.toHaveBeenCalled();
        done();
      }, 50);
    });
  });

  describe('Bug #5954 Additional Edge Cases', () => {
    describe('syncSessionStartToTracking$ edge cases', () => {
      it('should prefer pausedTaskId over lastCurrentTask when both exist', (done) => {
        store.overrideSelector(selectFocusModeConfig, {
          isSkipPreparation: false,
        });
        store.overrideSelector(selectors.selectPausedTaskId, 'paused-task-456');
        store.overrideSelector(selectLastCurrentTask, {
          id: 'last-task-123',
          title: 'Last Task',
          isDone: false,
        } as any);
        // Mock that the pausedTaskId task exists
        store.overrideSelector(selectTaskById as any, {
          id: 'paused-task-456',
          title: 'Paused Task',
          isDone: false,
        });
        currentTaskId$.next(null);
        store.refreshState();

        actions$ = of(actions.startFocusSession({ duration: 25 * 60 * 1000 }));

        effects.syncSessionStartToTracking$.subscribe((action) => {
          expect(action.type).toEqual('[Task] SetCurrentTask');
          // Should use pausedTaskId, not lastCurrentTask
          expect((action as any).id).toBe('paused-task-456');
          done();
        });
      });

      it('should dispatch showFocusOverlay when lastCurrentTask no longer exists in store (Bug #5954)', (done) => {
        store.overrideSelector(selectFocusModeConfig, {
          isSkipPreparation: false,
        });
        store.overrideSelector(selectors.selectPausedTaskId, null);
        store.overrideSelector(selectLastCurrentTask, {
          id: 'deleted-task-123',
          title: 'Deleted Task',
          isDone: false,
        } as any);
        // Mock that the task no longer exists (deleted)
        store.overrideSelector(selectTaskById as any, null);
        currentTaskId$.next(null);
        store.refreshState();

        actions$ = of(actions.startFocusSession({ duration: 25 * 60 * 1000 }));

        effects.syncSessionStartToTracking$.subscribe((action) => {
          expect(action.type).toEqual('[FocusMode] Show Overlay');
          done();
        });
      });
    });

    describe('syncTrackingStopToSession$ edge cases (break handling)', () => {
      it('should NOT dispatch when break timer is paused (not running)', (done) => {
        store.overrideSelector(selectFocusModeConfig, {
          isSkipPreparation: false,
        });
        // Break is paused - timer not running
        store.overrideSelector(
          selectors.selectTimer,
          createMockTimer({ isRunning: false, purpose: 'break' }),
        );
        store.refreshState();

        effects = TestBed.inject(FocusModeEffects);

        const { emitted, subscription } = collectEmissions(
          effects.syncTrackingStopToSession$,
        );
        currentTaskId$.next('task-123');

        setTimeout(() => {
          currentTaskId$.next(null);
        }, 10);

        setTimeout(() => {
          expect(emitted).toEqual([]);
          subscription.unsubscribe();
          done();
        }, 50);
      });

      it('should handle Pomodoro mode break correctly', (done) => {
        store.overrideSelector(selectFocusModeConfig, {
          isSkipPreparation: false,
        });
        store.overrideSelector(selectors.selectMode, FocusModeMode.Pomodoro);
        store.overrideSelector(
          selectors.selectTimer,
          createMockTimer({ isRunning: true, purpose: 'break', duration: 5 * 60 * 1000 }),
        );
        store.refreshState();

        effects = TestBed.inject(FocusModeEffects);

        let dispatched = false;
        effects.syncTrackingStopToSession$.subscribe((action) => {
          expect(action.type).toBe('[FocusMode] Pause Session');
          expect((action as any).pausedTaskId).toBe('task-123');
          dispatched = true;
        });

        currentTaskId$.next('task-123');

        setTimeout(() => {
          currentTaskId$.next(null);
        }, 10);

        setTimeout(() => {
          expect(dispatched).toBe(true);
          done();
        }, 100);
      });
    });

    describe('stopTrackingOnSessionEnd$ edge cases', () => {
      it('should respect isPauseTrackingDuringBreak=true for manual session end and store pausedTaskId (Bug #5737)', (done) => {
        store.overrideSelector(selectFocusModeConfig, {
          isSkipPreparation: false,
          isPauseTrackingDuringBreak: true,
        });
        currentTaskId$.next('task-123');
        store.refreshState();

        actions$ = of(actions.completeFocusSession({ isManual: true }));

        // Bug #5737: Now dispatches both setPausedTaskId and unsetCurrentTask
        effects.stopTrackingOnSessionEnd$.pipe(toArray()).subscribe((actionsArr) => {
          expect(actionsArr.length).toBe(2);
          expect(actionsArr[0].type).toEqual('[FocusMode] Set Paused Task Id');
          expect(actionsArr[1].type).toEqual('[Task] UnsetCurrentTask');
          done();
        });
      });

      it('should NOT stop tracking on manual end when isPauseTrackingDuringBreak=false', (done) => {
        store.overrideSelector(selectFocusModeConfig, {
          isSkipPreparation: false,
          isPauseTrackingDuringBreak: false,
        });
        currentTaskId$.next('task-123');
        store.refreshState();

        actions$ = of(actions.completeFocusSession({ isManual: true }));

        effects.stopTrackingOnSessionEnd$.pipe(toArray()).subscribe((actionsArr) => {
          expect(actionsArr.length).toBe(0);
          done();
        });
      });
    });
  });
});
