/* eslint-disable @typescript-eslint/explicit-function-return-type,@typescript-eslint/naming-convention */
import { taskSharedSchedulingMetaReducer } from './task-shared-scheduling.reducer';
import { TaskSharedActions } from '../task-shared.actions';
import { RootState } from '../../root-state';
import { TASK_FEATURE_NAME } from '../../../features/tasks/store/task.reducer';
import { TAG_FEATURE_NAME } from '../../../features/tag/store/tag.reducer';
import { Task } from '../../../features/tasks/task.model';
import { Action, ActionReducer } from '@ngrx/store';
import {
  createBaseState,
  createMockTask,
  createStateWithExistingTasks,
  expectStateUpdate,
  expectTagUpdate,
  expectTaskUpdate,
} from './test-utils';
import { getDbDateStr } from '../../../util/get-db-date-str';
import { appStateFeatureKey } from '../../app-state/app-state.reducer';

describe('taskSharedSchedulingMetaReducer', () => {
  let mockReducer: jasmine.Spy;
  let metaReducer: ActionReducer<any, Action>;
  let baseState: RootState;

  // Fixed time at noon to avoid midnight boundary issues (issue #5609)
  const MOCK_TIME = new Date(2024, 5, 15, 12, 0, 0, 0);

  beforeEach(() => {
    jasmine.clock().install();
    jasmine.clock().mockDate(MOCK_TIME);
    mockReducer = jasmine.createSpy('reducer').and.callFake((state, action) => state);
    metaReducer = taskSharedSchedulingMetaReducer(mockReducer);
    baseState = createBaseState();
  });

  afterEach(() => {
    jasmine.clock().uninstall();
  });

  describe('scheduleTaskWithTime action', () => {
    const createScheduleAction = (
      taskOverrides: Partial<Task> = {},
      dueWithTime: number,
    ) =>
      TaskSharedActions.scheduleTaskWithTime({
        task: createMockTask(taskOverrides),
        dueWithTime,
        isMoveToBacklog: false,
      });

    it('should add task to Today tag when scheduled for today', () => {
      const testState = createStateWithExistingTasks(['task1'], [], ['task1'], []);
      const action = createScheduleAction({}, Date.now());

      metaReducer(testState, action);
      expectStateUpdate(
        expectTagUpdate('TODAY', { taskIds: ['task1'] }),
        action,
        mockReducer,
        testState,
      );
    });

    it('should remove task from Today tag when scheduled for different day', () => {
      const testState = createStateWithExistingTasks([], [], [], ['task1']);
      // eslint-disable-next-line no-mixed-operators
      const tomorrowTimestamp = Date.now() + 24 * 60 * 60 * 1000;
      const action = createScheduleAction({}, tomorrowTimestamp);

      metaReducer(testState, action);
      expectStateUpdate(
        expectTagUpdate('TODAY', { taskIds: [] }),
        action,
        mockReducer,
        testState,
      );
    });

    it('should not change state when task is already correctly scheduled', () => {
      const now = Date.now();
      const testState = createStateWithExistingTasks([], [], [], ['task1']);
      // Update the task to already have the correct dueWithTime (dueDay should be undefined with mutual exclusivity)
      const task1 = testState[TASK_FEATURE_NAME].entities.task1 as Task;
      testState[TASK_FEATURE_NAME].entities.task1 = {
        ...task1,
        dueWithTime: now,
        dueDay: undefined, // Mutual exclusivity: dueDay is undefined when dueWithTime is set
      } as Task;
      const action = createScheduleAction({}, now);

      metaReducer(testState, action);
      expect(mockReducer).toHaveBeenCalledWith(testState, action);
    });

    it('should set dueDay to undefined when scheduling for today (mutual exclusivity)', () => {
      const now = Date.now();
      const testState = createStateWithExistingTasks(['task1'], [], [], []);
      const action = createScheduleAction({}, now);

      metaReducer(testState, action);
      expectStateUpdate(
        expectTaskUpdate('task1', { dueWithTime: now, dueDay: undefined }),
        action,
        mockReducer,
        testState,
      );
    });

    it('should set dueDay to undefined when scheduling for a different day (mutual exclusivity)', () => {
      const testState = createStateWithExistingTasks(['task1'], [], [], ['task1']);
      // eslint-disable-next-line no-mixed-operators
      const tomorrowTimestamp = Date.now() + 24 * 60 * 60 * 1000;
      const action = createScheduleAction({}, tomorrowTimestamp);

      metaReducer(testState, action);
      expectStateUpdate(
        expectTaskUpdate('task1', {
          dueWithTime: tomorrowTimestamp,
          dueDay: undefined,
        }),
        action,
        mockReducer,
        testState,
      );
    });

    it('should clear dueDay when scheduling with dueWithTime (mutual exclusivity)', () => {
      // Mutual exclusivity pattern: setting dueWithTime clears dueDay
      // Tasks scheduled with a specific time use dueWithTime as the source of truth
      const testState = createStateWithExistingTasks(['task1'], [], [], []);
      // eslint-disable-next-line no-mixed-operators
      const tomorrowTimestamp = Date.now() + 24 * 60 * 60 * 1000;
      const action = createScheduleAction({}, tomorrowTimestamp);

      metaReducer(testState, action);
      const updatedState = mockReducer.calls.mostRecent().args[0];
      const updatedTask = updatedState[TASK_FEATURE_NAME].entities.task1;

      // Mutual exclusivity: dueDay should be undefined when dueWithTime is set
      expect(updatedTask.dueDay).toBeUndefined();
      expect(updatedTask.dueWithTime).toBe(tomorrowTimestamp);
    });
  });

  describe('unScheduleTask action', () => {
    it('should remove task from Today tag when task is in Today', () => {
      const testState = createStateWithExistingTasks([], [], [], ['task1', 'other-task']);
      const action = TaskSharedActions.unscheduleTask({ id: 'task1' });

      metaReducer(testState, action);
      expectStateUpdate(
        expectTagUpdate('TODAY', { taskIds: ['other-task'] }),
        action,
        mockReducer,
        testState,
      );
    });

    it('should not change state when task is not in Today tag', () => {
      const action = TaskSharedActions.unscheduleTask({ id: 'task1' });

      metaReducer(baseState, action);
      expect(mockReducer).toHaveBeenCalledWith(baseState, action);
    });

    it('should set dueDay when leaving task in Today list', () => {
      const testState = createStateWithExistingTasks([], [], [], ['task1']);
      testState[TASK_FEATURE_NAME].entities.task1 = createMockTask({
        id: 'task1',
        dueWithTime: Date.now(),
        dueDay: undefined,
      });
      const action = TaskSharedActions.unscheduleTask({
        id: 'task1',
        isLeaveInToday: true,
      });

      metaReducer(testState, action);
      expectStateUpdate(
        expectTaskUpdate('task1', { dueDay: getDbDateStr(), dueWithTime: undefined }),
        action,
        mockReducer,
        testState,
      );
    });
  });

  describe('dismissReminderOnly action', () => {
    it('should only clear remindAt and preserve dueDay and dueWithTime', () => {
      const now = Date.now();
      const today = getDbDateStr();
      const testState = createStateWithExistingTasks([], [], [], ['task1']);
      testState[TASK_FEATURE_NAME].entities.task1 = createMockTask({
        id: 'task1',
        dueDay: today,
        dueWithTime: now,
        remindAt: now,
      });

      const action = TaskSharedActions.dismissReminderOnly({ id: 'task1' });
      metaReducer(testState, action);

      const updatedState = mockReducer.calls.mostRecent().args[0];
      const updatedTask = updatedState[TASK_FEATURE_NAME].entities.task1;

      expect(updatedTask.remindAt).toBeUndefined();
      expect(updatedTask.dueDay).toBe(today);
      expect(updatedTask.dueWithTime).toBe(now);
    });

    it('should not remove task from TODAY tag', () => {
      const now = Date.now();
      const testState = createStateWithExistingTasks([], [], [], ['task1', 'other-task']);
      testState[TASK_FEATURE_NAME].entities.task1 = createMockTask({
        id: 'task1',
        dueWithTime: now,
        remindAt: now,
      });

      const action = TaskSharedActions.dismissReminderOnly({ id: 'task1' });
      metaReducer(testState, action);

      const updatedState = mockReducer.calls.mostRecent().args[0];
      const todayTag = updatedState[TAG_FEATURE_NAME].entities.TODAY;

      expect(todayTag.taskIds).toContain('task1');
      expect(todayTag.taskIds).toContain('other-task');
    });
  });

  describe('planTasksForToday action', () => {
    it('should add new tasks to the top of Today tag', () => {
      const testState = createStateWithExistingTasks([], [], [], ['existing-task']);
      // Add task entities that will be planned for today
      testState[TASK_FEATURE_NAME].entities['task1'] = createMockTask({ id: 'task1' });
      testState[TASK_FEATURE_NAME].entities['task2'] = createMockTask({ id: 'task2' });
      (testState[TASK_FEATURE_NAME].ids as string[]).push('task1', 'task2');
      const action = TaskSharedActions.planTasksForToday({
        taskIds: ['task1', 'task2'],
        parentTaskMap: {},
      });

      metaReducer(testState, action);
      expectStateUpdate(
        expectTagUpdate('TODAY', { taskIds: ['task1', 'task2', 'existing-task'] }),
        action,
        mockReducer,
        testState,
      );
    });

    it('should not add tasks that are already in Today tag', () => {
      const testState = createStateWithExistingTasks(
        [],
        [],
        [],
        ['task1', 'existing-task'],
      );
      // Add task2 entity (task1 and existing-task already exist from createStateWithExistingTasks)
      testState[TASK_FEATURE_NAME].entities['task2'] = createMockTask({ id: 'task2' });
      (testState[TASK_FEATURE_NAME].ids as string[]).push('task2');
      const action = TaskSharedActions.planTasksForToday({
        taskIds: ['task1', 'task2'],
        parentTaskMap: {},
      });

      metaReducer(testState, action);
      expectStateUpdate(
        expectTagUpdate('TODAY', { taskIds: ['task2', 'task1', 'existing-task'] }),
        action,
        mockReducer,
        testState,
      );
    });

    it('should handle parentTaskMap filtering', () => {
      const testState = createStateWithExistingTasks([], [], [], ['parent-task']);
      // Add task entities for subtask1 and task2
      testState[TASK_FEATURE_NAME].entities['subtask1'] = createMockTask({
        id: 'subtask1',
        parentId: 'parent-task',
      });
      testState[TASK_FEATURE_NAME].entities['task2'] = createMockTask({ id: 'task2' });
      (testState[TASK_FEATURE_NAME].ids as string[]).push('subtask1', 'task2');
      const action = TaskSharedActions.planTasksForToday({
        taskIds: ['subtask1', 'task2'],
        parentTaskMap: { subtask1: 'parent-task' },
      });

      metaReducer(testState, action);
      expectStateUpdate(
        expectTagUpdate('TODAY', { taskIds: ['task2', 'parent-task'] }),
        action,
        mockReducer,
        testState,
      );
    });

    it('should add subtask when parent is NOT in Today', () => {
      const testState = createStateWithExistingTasks([], [], [], []);
      // Create parent and subtask entities
      testState[TASK_FEATURE_NAME].entities['parent-task'] = createMockTask({
        id: 'parent-task',
      });
      testState[TASK_FEATURE_NAME].entities['subtask1'] = createMockTask({
        id: 'subtask1',
        parentId: 'parent-task',
      });
      testState[TASK_FEATURE_NAME].ids.push('parent-task', 'subtask1');

      const action = TaskSharedActions.planTasksForToday({
        taskIds: ['subtask1'],
        parentTaskMap: { subtask1: 'parent-task' },
      });

      metaReducer(testState, action);
      expectStateUpdate(
        expectTagUpdate('TODAY', { taskIds: ['subtask1'] }),
        action,
        mockReducer,
        testState,
      );
    });

    it('should NOT add subtask when parent is already in Today', () => {
      const testState = createStateWithExistingTasks([], [], [], ['parent-task']);
      // Create parent and subtask entities
      testState[TASK_FEATURE_NAME].entities['parent-task'] = createMockTask({
        id: 'parent-task',
      });
      testState[TASK_FEATURE_NAME].entities['subtask1'] = createMockTask({
        id: 'subtask1',
        parentId: 'parent-task',
      });
      testState[TASK_FEATURE_NAME].ids.push('parent-task', 'subtask1');

      const action = TaskSharedActions.planTasksForToday({
        taskIds: ['subtask1'],
        parentTaskMap: { subtask1: 'parent-task' },
      });

      metaReducer(testState, action);
      // Subtask should NOT be added, Today tag should remain unchanged (only parent-task)
      expectStateUpdate(
        expectTagUpdate('TODAY', { taskIds: ['parent-task'] }),
        action,
        mockReducer,
        testState,
      );
    });

    it('should handle multiple subtasks with different parent states', () => {
      const testState = createStateWithExistingTasks([], [], [], ['parent1']);
      // Create parent tasks and subtasks
      testState[TASK_FEATURE_NAME].entities['parent1'] = createMockTask({
        id: 'parent1',
      });
      testState[TASK_FEATURE_NAME].entities['parent2'] = createMockTask({
        id: 'parent2',
      });
      testState[TASK_FEATURE_NAME].entities['subtask1'] = createMockTask({
        id: 'subtask1',
        parentId: 'parent1',
      });
      testState[TASK_FEATURE_NAME].entities['subtask2'] = createMockTask({
        id: 'subtask2',
        parentId: 'parent2',
      });
      testState[TASK_FEATURE_NAME].ids.push('parent1', 'parent2', 'subtask1', 'subtask2');

      const action = TaskSharedActions.planTasksForToday({
        taskIds: ['subtask1', 'subtask2'],
        parentTaskMap: { subtask1: 'parent1', subtask2: 'parent2' },
      });

      metaReducer(testState, action);
      // subtask1 should NOT be added (parent1 is in Today)
      // subtask2 SHOULD be added (parent2 is not in Today)
      expectStateUpdate(
        expectTagUpdate('TODAY', { taskIds: ['subtask2', 'parent1'] }),
        action,
        mockReducer,
        testState,
      );
    });

    it('should skip non-existent task IDs (sync scenario)', () => {
      const testState = createStateWithExistingTasks([], [], [], ['existing-task']);
      // Only add task1 as entity — task2 does NOT exist in the store
      testState[TASK_FEATURE_NAME].entities['task1'] = createMockTask({ id: 'task1' });
      (testState[TASK_FEATURE_NAME].ids as string[]).push('task1');

      const action = TaskSharedActions.planTasksForToday({
        taskIds: ['task1', 'non-existent-task'],
        parentTaskMap: {},
      });

      metaReducer(testState, action);
      // non-existent-task should be silently skipped, only task1 added
      expectStateUpdate(
        expectTagUpdate('TODAY', { taskIds: ['task1', 'existing-task'] }),
        action,
        mockReducer,
        testState,
      );
    });

    it('should set dueDay on subtask when added to Today', () => {
      const testState = createStateWithExistingTasks([], [], [], []);
      testState[TASK_FEATURE_NAME].entities['subtask1'] = createMockTask({
        id: 'subtask1',
        parentId: 'parent-task',
        dueDay: undefined,
      });
      testState[TASK_FEATURE_NAME].ids.push('subtask1');

      const action = TaskSharedActions.planTasksForToday({
        taskIds: ['subtask1'],
        parentTaskMap: { subtask1: 'parent-task' },
      });

      metaReducer(testState, action);
      const updatedState = mockReducer.calls.mostRecent().args[0];
      const updatedSubtask = updatedState[TASK_FEATURE_NAME].entities.subtask1;

      expect(updatedSubtask.dueDay).toBe(getDbDateStr());
    });

    it('should use the action date when replayed after the day changed', () => {
      const actionToday = '2024-06-14';
      const replayToday = '2024-06-15';
      const testState = createStateWithExistingTasks([], [], [], []);
      testState[appStateFeatureKey] = {
        ...testState[appStateFeatureKey],
        todayStr: replayToday,
      };
      testState[TASK_FEATURE_NAME].entities.task1 = createMockTask({
        id: 'task1',
        dueDay: undefined,
      });
      testState[TASK_FEATURE_NAME].ids.push('task1');

      const action = TaskSharedActions.planTasksForToday({
        taskIds: ['task1'],
        today: actionToday,
        parentTaskMap: {},
      });

      metaReducer(testState, action);
      const updatedState = mockReducer.calls.mostRecent().args[0];
      const updatedTask = updatedState[TASK_FEATURE_NAME].entities.task1;

      expect(updatedTask.dueDay).toBe(actionToday);
      expect(updatedTask.dueDay).not.toBe(replayToday);
    });

    it('should use the action start-of-next-day offset when replayed after settings changed', () => {
      const actionToday = '2024-06-14';
      const actionOffset = 4 * 60 * 60 * 1000;
      const scheduledAt = new Date(2024, 5, 15, 2, 0, 0, 0).getTime();
      const testState = createStateWithExistingTasks([], [], [], []);
      testState[appStateFeatureKey] = {
        ...testState[appStateFeatureKey],
        todayStr: actionToday,
        startOfNextDayDiffMs: 0,
      };
      testState[TASK_FEATURE_NAME].entities.task1 = createMockTask({
        id: 'task1',
        dueDay: undefined,
        dueWithTime: scheduledAt,
      });
      testState[TASK_FEATURE_NAME].ids.push('task1');

      const action = TaskSharedActions.planTasksForToday({
        taskIds: ['task1'],
        today: actionToday,
        startOfNextDayDiffMs: actionOffset,
        parentTaskMap: {},
      });

      metaReducer(testState, action);
      const updatedState = mockReducer.calls.mostRecent().args[0];
      const updatedTask = updatedState[TASK_FEATURE_NAME].entities.task1;

      expect(updatedTask.dueWithTime).toBe(scheduledAt);
      expect(updatedTask.dueDay).toBe(actionToday);
    });

    it('should remove tasks from planner days when adding to Today', () => {
      const testState = {
        ...createStateWithExistingTasks([], [], [], []),
        planner: {
          ...createStateWithExistingTasks([], [], [], []).planner,
          days: {
            '2024-01-01': ['task1', 'task2', 'keep-task'],
            '2024-01-02': ['task2', 'other-task'],
          },
        },
      };
      // Add task entities that will be planned for today
      testState[TASK_FEATURE_NAME].entities['task1'] = createMockTask({ id: 'task1' });
      testState[TASK_FEATURE_NAME].entities['task2'] = createMockTask({ id: 'task2' });
      (testState[TASK_FEATURE_NAME].ids as string[]).push('task1', 'task2');
      const action = TaskSharedActions.planTasksForToday({
        taskIds: ['task1', 'task2'],
        parentTaskMap: {},
      });

      metaReducer(testState, action);
      const updatedState = mockReducer.calls.mostRecent().args[0];

      expect(updatedState.planner.days['2024-01-01']).toEqual(['keep-task']);
      expect(updatedState.planner.days['2024-01-02']).toEqual(['other-task']);
    });

    it('should preserve dueWithTime but clear remindAt when task is scheduled for today', () => {
      const now = Date.now();
      const testState = createStateWithExistingTasks([], [], [], []);
      // Create task with dueWithTime and remindAt for today
      testState[TASK_FEATURE_NAME].entities.task1 = createMockTask({
        id: 'task1',
        dueWithTime: now,
        remindAt: now,
      });
      testState[TASK_FEATURE_NAME].ids.push('task1');

      const action = TaskSharedActions.planTasksForToday({
        taskIds: ['task1'],
        parentTaskMap: {},
      });

      metaReducer(testState, action);
      const updatedState = mockReducer.calls.mostRecent().args[0];
      const updatedTask = updatedState[TASK_FEATURE_NAME].entities.task1;

      expect(updatedTask.dueWithTime).toBe(now);
      expect(updatedTask.remindAt).toBeUndefined();
      expect(updatedTask.dueDay).toBeDefined();
    });

    it('should clear dueWithTime when task is scheduled for a different day', () => {
      // eslint-disable-next-line no-mixed-operators
      const tomorrow = Date.now() + 24 * 60 * 60 * 1000;
      const testState = createStateWithExistingTasks([], [], [], []);
      // Create task with dueWithTime for tomorrow
      testState[TASK_FEATURE_NAME].entities.task1 = createMockTask({
        id: 'task1',
        dueWithTime: tomorrow,
      });
      testState[TASK_FEATURE_NAME].ids.push('task1');

      const action = TaskSharedActions.planTasksForToday({
        taskIds: ['task1'],
        parentTaskMap: {},
      });

      metaReducer(testState, action);
      const updatedState = mockReducer.calls.mostRecent().args[0];
      const updatedTask = updatedState[TASK_FEATURE_NAME].entities.task1;

      expect(updatedTask.dueWithTime).toBeUndefined();
      expect(updatedTask.dueDay).toBeDefined();
    });

    it('should handle tasks without dueWithTime (backward compatibility)', () => {
      const testState = createStateWithExistingTasks([], [], [], []);
      testState[TASK_FEATURE_NAME].entities.task1 = createMockTask({
        id: 'task1',
        dueWithTime: undefined,
        dueDay: undefined,
      });
      testState[TASK_FEATURE_NAME].ids.push('task1');

      const action = TaskSharedActions.planTasksForToday({
        taskIds: ['task1'],
        parentTaskMap: {},
      });

      metaReducer(testState, action);
      const updatedState = mockReducer.calls.mostRecent().args[0];
      const updatedTask = updatedState[TASK_FEATURE_NAME].entities.task1;

      expect(updatedTask.dueWithTime).toBeUndefined();
      expect(updatedTask.dueDay).toBeDefined();
    });

    it('should return unchanged state when task is already in today tag AND has dueDay set to today', () => {
      const today = getDbDateStr();
      const testState = createStateWithExistingTasks([], [], [], ['task1']);
      // Task is already in today tag AND has dueDay === today
      testState[TASK_FEATURE_NAME].entities.task1 = createMockTask({
        id: 'task1',
        dueDay: today,
        dueWithTime: undefined,
      });

      const action = TaskSharedActions.planTasksForToday({
        taskIds: ['task1'],
        parentTaskMap: {},
      });

      metaReducer(testState, action);
      // Should pass unchanged state to reducer (no modifications)
      expect(mockReducer).toHaveBeenCalledWith(testState, action);
    });

    it('should update dueDay when task is in today tag but dueDay is not set', () => {
      const testState = createStateWithExistingTasks([], [], [], ['task1']);
      // Task is in today tag but dueDay is NOT set
      testState[TASK_FEATURE_NAME].entities.task1 = createMockTask({
        id: 'task1',
        dueDay: undefined,
        dueWithTime: undefined,
      });

      const action = TaskSharedActions.planTasksForToday({
        taskIds: ['task1'],
        parentTaskMap: {},
      });

      metaReducer(testState, action);
      const updatedState = mockReducer.calls.mostRecent().args[0];
      const updatedTask = updatedState[TASK_FEATURE_NAME].entities.task1;

      // Should update dueDay even though task is already in today tag
      expect(updatedTask.dueDay).toBe(getDbDateStr());
    });

    it('should update dueDay when task is in today tag but dueDay is different', () => {
      const testState = createStateWithExistingTasks([], [], [], ['task1']);
      // Task is in today tag but dueDay is set to a different day
      testState[TASK_FEATURE_NAME].entities.task1 = createMockTask({
        id: 'task1',
        dueDay: '2024-01-01', // Different day
        dueWithTime: undefined,
      });

      const action = TaskSharedActions.planTasksForToday({
        taskIds: ['task1'],
        parentTaskMap: {},
      });

      metaReducer(testState, action);
      const updatedState = mockReducer.calls.mostRecent().args[0];
      const updatedTask = updatedState[TASK_FEATURE_NAME].entities.task1;

      // Should update dueDay to today
      expect(updatedTask.dueDay).toBe(getDbDateStr());
    });

    it('should preserve dueWithTime for multiple tasks scheduled for today', () => {
      const now = Date.now();
      const testState = createStateWithExistingTasks([], [], [], []);
      // Create multiple tasks with dueWithTime for today
      testState[TASK_FEATURE_NAME].entities.task1 = createMockTask({
        id: 'task1',
        dueWithTime: now,
      });
      testState[TASK_FEATURE_NAME].entities.task2 = createMockTask({
        id: 'task2',
        // eslint-disable-next-line no-mixed-operators
        dueWithTime: now + 60 * 60 * 1000, // 1 hour later
      });
      testState[TASK_FEATURE_NAME].ids.push('task1', 'task2');

      const action = TaskSharedActions.planTasksForToday({
        taskIds: ['task1', 'task2'],
        parentTaskMap: {},
      });

      metaReducer(testState, action);
      const updatedState = mockReducer.calls.mostRecent().args[0];
      const updatedTask1 = updatedState[TASK_FEATURE_NAME].entities.task1;
      const updatedTask2 = updatedState[TASK_FEATURE_NAME].entities.task2;

      expect(updatedTask1.dueWithTime).toBe(now);
      // eslint-disable-next-line no-mixed-operators
      expect(updatedTask2.dueWithTime).toBe(now + 60 * 60 * 1000);
    });
  });

  describe('removeTasksFromTodayTag action', () => {
    it('should remove specified tasks from Today tag', () => {
      const testState = createStateWithExistingTasks(
        [],
        [],
        [],
        ['task1', 'task2', 'keep-task'],
      );
      const action = TaskSharedActions.removeTasksFromTodayTag({
        taskIds: ['task1', 'task2'],
      });

      metaReducer(testState, action);
      expectStateUpdate(
        expectTagUpdate('TODAY', { taskIds: ['keep-task'] }),
        action,
        mockReducer,
        testState,
      );
    });

    it('should handle empty taskIds array', () => {
      const testState = createStateWithExistingTasks([], [], [], ['task1', 'task2']);
      const action = TaskSharedActions.removeTasksFromTodayTag({ taskIds: [] });

      metaReducer(testState, action);
      expectStateUpdate(
        expectTagUpdate('TODAY', { taskIds: ['task1', 'task2'] }),
        action,
        mockReducer,
        testState,
      );
    });
  });

  describe('TaskSharedActions.moveTaskInTodayTagList action', () => {
    const createMoveTaskInTodayTagListAction = (toTaskId: string, fromTaskId: string) =>
      TaskSharedActions.moveTaskInTodayTagList({
        toTaskId,
        fromTaskId,
      });

    it('should move task before target task in Today tag', () => {
      const testState = createStateWithExistingTasks(
        [],
        [],
        [],
        ['first-task', 'move-task', 'middle-task', 'target-task', 'last-task'],
      );
      const action = createMoveTaskInTodayTagListAction('target-task', 'move-task');

      metaReducer(testState, action);
      expectStateUpdate(
        expectTagUpdate('TODAY', {
          taskIds: ['first-task', 'middle-task', 'move-task', 'target-task', 'last-task'],
        }),
        action,
        mockReducer,
        testState,
      );
    });

    it('should handle moving task to beginning of list', () => {
      const testState = createStateWithExistingTasks(
        [],
        [],
        [],
        ['first-task', 'move-task', 'last-task'],
      );
      const action = createMoveTaskInTodayTagListAction('first-task', 'move-task');

      metaReducer(testState, action);
      expectStateUpdate(
        expectTagUpdate('TODAY', {
          taskIds: ['move-task', 'first-task', 'last-task'],
        }),
        action,
        mockReducer,
        testState,
      );
    });

    it('should handle moving task to end of list', () => {
      const testState = createStateWithExistingTasks(
        [],
        [],
        [],
        ['move-task', 'middle-task', 'last-task'],
      );
      const action = createMoveTaskInTodayTagListAction('last-task', 'move-task');

      metaReducer(testState, action);
      expectStateUpdate(
        expectTagUpdate('TODAY', {
          taskIds: ['middle-task', 'move-task', 'last-task'],
        }),
        action,
        mockReducer,
        testState,
      );
    });

    it('should handle single task in Today list', () => {
      const testState = createStateWithExistingTasks([], [], [], ['only-task']);
      const action = createMoveTaskInTodayTagListAction('only-task', 'only-task');

      metaReducer(testState, action);
      expectStateUpdate(
        expectTagUpdate('TODAY', {
          taskIds: ['only-task'],
        }),
        action,
        mockReducer,
        testState,
      );
    });

    it('should handle empty Today list gracefully', () => {
      const testState = createStateWithExistingTasks([], [], [], []);
      const action = createMoveTaskInTodayTagListAction('target-task', 'move-task');

      metaReducer(testState, action);
      expectStateUpdate(
        expectTagUpdate('TODAY', {
          taskIds: [],
        }),
        action,
        mockReducer,
        testState,
      );
    });
  });

  describe('with startOfNextDayDiff offset', () => {
    // Scenario: User has startOfNextDayDiffMs set to 4 hours.
    // "Today" is Feb 15, so anything before 4 AM Feb 16 is still "today" (Feb 15).
    // Anything at or after 4 AM Feb 16 is "tomorrow" (Feb 16).
    const OFFSET_MS = 4 * 3600000; // 4 hours in ms
    const TODAY_STR = '2026-02-15';

    // Feb 16, 2026, 2:00 AM local time -- still "today" with 4h offset
    const FEB_16_2AM = new Date(2026, 1, 16, 2, 0, 0, 0).getTime();
    // Feb 16, 2026, 5:00 AM local time -- "tomorrow" with 4h offset
    const FEB_16_5AM = new Date(2026, 1, 16, 5, 0, 0, 0).getTime();
    // Feb 15, 2026, 10:00 AM local time -- clearly "today"
    const FEB_15_10AM = new Date(2026, 1, 15, 10, 0, 0, 0).getTime();

    const applyOffset = (state: RootState): RootState => ({
      ...state,
      [appStateFeatureKey]: {
        ...state[appStateFeatureKey],
        todayStr: TODAY_STR,
        startOfNextDayDiffMs: OFFSET_MS,
      },
    });

    describe('scheduleTaskWithTime', () => {
      const createScheduleAction = (
        taskOverrides: Partial<Task> = {},
        dueWithTime: number,
      ) =>
        TaskSharedActions.scheduleTaskWithTime({
          task: createMockTask(taskOverrides),
          dueWithTime,
          isMoveToBacklog: false,
        });

      it('should add task to TODAY tag when scheduled at 2 AM next calendar day (within offset)', () => {
        const testState = applyOffset(
          createStateWithExistingTasks(['task1'], [], ['task1'], []),
        );
        const action = createScheduleAction({}, FEB_16_2AM);

        metaReducer(testState, action);
        expectStateUpdate(
          expectTagUpdate('TODAY', { taskIds: ['task1'] }),
          action,
          mockReducer,
          testState,
        );
      });

      it('should NOT add task to TODAY tag when scheduled at 5 AM next calendar day (beyond offset)', () => {
        const testState = applyOffset(
          createStateWithExistingTasks(['task1'], [], ['task1'], []),
        );
        const action = createScheduleAction({}, FEB_16_5AM);

        metaReducer(testState, action);
        const updatedState = mockReducer.calls.mostRecent().args[0];
        const todayTag = updatedState[TAG_FEATURE_NAME].entities.TODAY;

        expect(todayTag.taskIds).not.toContain('task1');
      });

      it('should remove task from TODAY tag when rescheduling from today to beyond offset', () => {
        const testState = applyOffset(
          createStateWithExistingTasks([], [], [], ['task1']),
        );
        const action = createScheduleAction({}, FEB_16_5AM);

        metaReducer(testState, action);
        expectStateUpdate(
          expectTagUpdate('TODAY', { taskIds: [] }),
          action,
          mockReducer,
          testState,
        );
      });

      it('should keep task in TODAY tag when rescheduling within offset window', () => {
        const testState = applyOffset(
          createStateWithExistingTasks([], [], [], ['task1']),
        );
        // Task already in today, reschedule to 2 AM (still within offset)
        const task1 = testState[TASK_FEATURE_NAME].entities.task1 as Task;
        testState[TASK_FEATURE_NAME].entities.task1 = {
          ...task1,
          dueWithTime: FEB_15_10AM,
          dueDay: undefined,
        } as Task;
        const action = createScheduleAction({}, FEB_16_2AM);

        metaReducer(testState, action);
        const updatedState = mockReducer.calls.mostRecent().args[0];
        const todayTag = updatedState[TAG_FEATURE_NAME].entities.TODAY;

        expect(todayTag.taskIds).toContain('task1');
      });

      it('should set dueDay to undefined when scheduling within offset (mutual exclusivity)', () => {
        const testState = applyOffset(
          createStateWithExistingTasks(['task1'], [], [], []),
        );
        const action = createScheduleAction({}, FEB_16_2AM);

        metaReducer(testState, action);
        expectStateUpdate(
          expectTaskUpdate('task1', { dueWithTime: FEB_16_2AM, dueDay: undefined }),
          action,
          mockReducer,
          testState,
        );
      });
    });

    describe('planTasksForToday', () => {
      it('should preserve dueWithTime when task time is within offset window', () => {
        const testState = applyOffset(createStateWithExistingTasks([], [], [], []));
        testState[TASK_FEATURE_NAME].entities.task1 = createMockTask({
          id: 'task1',
          dueWithTime: FEB_16_2AM,
        });
        testState[TASK_FEATURE_NAME].ids.push('task1');

        const action = TaskSharedActions.planTasksForToday({
          taskIds: ['task1'],
          parentTaskMap: {},
        });

        metaReducer(testState, action);
        const updatedState = mockReducer.calls.mostRecent().args[0];
        const updatedTask = updatedState[TASK_FEATURE_NAME].entities.task1;

        // 2 AM Feb 16 is still "today" with 4h offset, so dueWithTime should be preserved
        expect(updatedTask.dueWithTime).toBe(FEB_16_2AM);
        expect(updatedTask.dueDay).toBe(TODAY_STR);
      });

      it('should clear dueWithTime when task time is beyond offset window', () => {
        const testState = applyOffset(createStateWithExistingTasks([], [], [], []));
        testState[TASK_FEATURE_NAME].entities.task1 = createMockTask({
          id: 'task1',
          dueWithTime: FEB_16_5AM,
        });
        testState[TASK_FEATURE_NAME].ids.push('task1');

        const action = TaskSharedActions.planTasksForToday({
          taskIds: ['task1'],
          parentTaskMap: {},
        });

        metaReducer(testState, action);
        const updatedState = mockReducer.calls.mostRecent().args[0];
        const updatedTask = updatedState[TASK_FEATURE_NAME].entities.task1;

        // 5 AM Feb 16 is NOT "today" with 4h offset, so dueWithTime should be cleared
        expect(updatedTask.dueWithTime).toBeUndefined();
        expect(updatedTask.dueDay).toBe(TODAY_STR);
      });

      it('should set dueDay to offset-adjusted today string', () => {
        const testState = applyOffset(createStateWithExistingTasks([], [], [], []));
        testState[TASK_FEATURE_NAME].entities.task1 = createMockTask({
          id: 'task1',
          dueDay: undefined,
          dueWithTime: undefined,
        });
        testState[TASK_FEATURE_NAME].ids.push('task1');

        const action = TaskSharedActions.planTasksForToday({
          taskIds: ['task1'],
          parentTaskMap: {},
        });

        metaReducer(testState, action);
        const updatedState = mockReducer.calls.mostRecent().args[0];
        const updatedTask = updatedState[TASK_FEATURE_NAME].entities.task1;

        // dueDay should be the offset-adjusted todayStr from state, not getDbDateStr()
        expect(updatedTask.dueDay).toBe(TODAY_STR);
      });
    });

    describe('unScheduleTask', () => {
      it('should use offset-adjusted todayStr when leaving task in today', () => {
        const testState = applyOffset(
          createStateWithExistingTasks([], [], [], ['task1']),
        );
        testState[TASK_FEATURE_NAME].entities.task1 = createMockTask({
          id: 'task1',
          dueWithTime: FEB_16_2AM,
          dueDay: undefined,
        });

        const action = TaskSharedActions.unscheduleTask({
          id: 'task1',
          isLeaveInToday: true,
        });

        metaReducer(testState, action);
        expectStateUpdate(
          expectTaskUpdate('task1', {
            dueDay: TODAY_STR,
            dueWithTime: undefined,
          }),
          action,
          mockReducer,
          testState,
        );
      });
    });
  });

  describe('other actions', () => {
    it('should pass through other actions to the reducer', () => {
      const action = { type: 'SOME_OTHER_ACTION' };
      metaReducer(baseState, action);

      expect(mockReducer).toHaveBeenCalledWith(baseState, action);
    });
  });
});
