import { Action } from '@ngrx/store';
import { lwwUpdateMetaReducer } from './lww-update.meta-reducer';
import { RootState } from '../../root-state';
import { TASK_FEATURE_NAME } from '../../../features/tasks/store/task.reducer';
import { PROJECT_FEATURE_NAME } from '../../../features/project/store/project.reducer';
import { TAG_FEATURE_NAME } from '../../../features/tag/store/tag.reducer';
import { SECTION_FEATURE_NAME } from '../../../features/section/store/section.reducer';
import { Task } from '../../../features/tasks/task.model';
import { Project } from '../../../features/project/project.model';
import { Tag } from '../../../features/tag/tag.model';
import { Section } from '../../../features/section/section.model';
import { WorkContextType } from '../../../features/work-context/work-context.model';
import { TODAY_TAG } from '../../../features/tag/tag.const';
import { INBOX_PROJECT } from '../../../features/project/project.const';
import { OpLog } from '../../../core/log';
import { appDataValidators } from '../../../op-log/validation/validation-fn';
import { CONFIG_FEATURE_NAME } from '../../../features/config/store/global-config.reducer';
import { TIME_TRACKING_FEATURE_KEY } from '../../../features/time-tracking/store/time-tracking.reducer';
import { appStateFeatureKey } from '../../app-state/app-state.reducer';
import { getDbDateStr } from '../../../util/get-db-date-str';
import { SIMPLE_COUNTER_FEATURE_NAME } from '../../../features/simple-counter/store/simple-counter.reducer';
import {
  SimpleCounter,
  SimpleCounterType,
} from '../../../features/simple-counter/simple-counter.model';

describe('lwwUpdateMetaReducer', () => {
  const mockReducer = jasmine.createSpy('reducer');
  const reducer = lwwUpdateMetaReducer(mockReducer);

  const TASK_ID = 'task1';
  const PROJECT_ID = 'project1';
  const TAG_ID = 'tag1';
  const SECTION_ID = 'section1';

  const createMockTask = (overrides: Partial<Task> = {}): Task =>
    ({
      id: TASK_ID,
      title: 'Original Title',
      notes: '',
      timeEstimate: 0,
      timeSpent: 0,
      timeSpentOnDay: {},
      reminderId: null,
      plannedAt: null,
      dueDay: null,
      dueWithTime: null,
      projectId: null,
      issueId: null,
      issueProviderId: null,
      issueType: null,
      issueLastUpdated: null,
      issuePoints: null,
      issueAttachmentNr: null,
      issueWasUpdated: false,
      issueTimeTracked: null,
      issueLinkType: null,
      tagIds: [],
      parentId: null,
      subTaskIds: [],
      attachmentIds: [],
      done: false,
      doneOn: null,
      modified: 1000,
      created: 1000,
      repeatCfgId: null,
      issueData: null,
      isDone: false,
      _showSubTasksMode: 2,
      ...overrides,
    }) as Task;

  const createMockProject = (overrides: Partial<Project> = {}): Project =>
    ({
      id: PROJECT_ID,
      title: 'Original Project',
      taskIds: [],
      backlogTaskIds: [],
      noteIds: [],
      isHiddenFromMenu: false,
      isArchived: false,
      isEnableBacklog: false,
      issueIntegrationCfgs: {},
      theme: {},
      ...overrides,
    }) as Project;

  const createMockTag = (overrides: Partial<Tag> = {}): Tag =>
    ({
      id: TAG_ID,
      title: 'Original Tag',
      taskIds: [],
      color: '#ff0000',
      icon: null,
      ...overrides,
    }) as Tag;

  const createMockSection = (overrides: Partial<Section> = {}): Section => ({
    id: SECTION_ID,
    contextId: PROJECT_ID,
    contextType: WorkContextType.PROJECT,
    title: 'Original Section',
    taskIds: [],
    ...overrides,
  });

  const createMockState = (taskOverrides?: Partial<Task>[]): Partial<RootState> =>
    ({
      [TASK_FEATURE_NAME]: {
        ids: [TASK_ID],
        entities: {
          [TASK_ID]: createMockTask(taskOverrides?.[0]),
        },
        currentTaskId: null,
        selectedTaskId: null,
        taskDetailTargetPanel: null,
        isDataLoaded: true,
        lastCurrentTaskId: null,
      },
      [PROJECT_FEATURE_NAME]: {
        ids: [INBOX_PROJECT.id, PROJECT_ID],
        entities: {
          [INBOX_PROJECT.id]: createMockProject({ id: INBOX_PROJECT.id }),
          [PROJECT_ID]: createMockProject(),
        },
      },
      [TAG_FEATURE_NAME]: {
        ids: [TAG_ID],
        entities: {
          [TAG_ID]: createMockTag(),
        },
      },
      [SECTION_FEATURE_NAME]: {
        ids: [SECTION_ID],
        entities: {
          [SECTION_ID]: createMockSection(),
        },
      },
      [appStateFeatureKey]: {
        todayStr: getDbDateStr(),
        startOfNextDayDiffMs: 0,
      },
    }) as Partial<RootState>;

  beforeEach(() => {
    mockReducer.calls.reset();
    mockReducer.and.callFake((state: unknown, _action: Action) => state);
  });

  describe('non-LWW Update actions', () => {
    it('should pass through non-LWW Update actions unchanged', () => {
      const state = createMockState();
      const action = { type: '[Task] Update Task', task: { id: TASK_ID, changes: {} } };

      reducer(state, action);

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

    it('should pass through actions with similar but non-matching types', () => {
      const state = createMockState();
      const action = { type: '[TASK] Some Other Action' };

      reducer(state, action);

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

  describe('[TASK] LWW Update', () => {
    it('should update task entity with LWW winning state', () => {
      const state = createMockState();
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        title: 'LWW Winning Title',
        notes: 'Updated notes',
        meta: {
          isPersistent: true,
          entityType: 'TASK',
          entityId: TASK_ID,
          isRemote: true,
        },
      };

      reducer(state, action);

      expect(mockReducer).toHaveBeenCalled();
      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const updatedTask = updatedState[TASK_FEATURE_NAME]?.entities[TASK_ID] as Task;
      expect(updatedTask.title).toBe('LWW Winning Title');
      expect(updatedTask.notes).toBe('Updated notes');
    });

    it('should update modified timestamp', () => {
      const state = createMockState([{ modified: 1000 }]);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        title: 'Updated Title',
        modified: 1000, // Original timestamp in action
        meta: {
          isPersistent: true,
          entityType: 'TASK',
          entityId: TASK_ID,
          isRemote: true,
        },
      };

      const beforeTime = Date.now();
      reducer(state, action);
      const afterTime = Date.now();

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const updatedTask = updatedState[TASK_FEATURE_NAME]?.entities[TASK_ID] as Task;
      expect(updatedTask.modified).toBeGreaterThanOrEqual(beforeTime);
      expect(updatedTask.modified).toBeLessThanOrEqual(afterTime);
    });

    it('should recreate entity if it does not exist (LWW update won over delete)', () => {
      const state = createMockState();
      const action = {
        type: '[TASK] LWW Update',
        id: 'recreated-task',
        title: 'Recreated Task Title',
        notes: 'This task was deleted locally but update won via LWW',
        isDone: true,
        doneOn: 12345,
        meta: { isPersistent: true, entityType: 'TASK', entityId: 'recreated-task' },
      };

      spyOn(OpLog, 'log');
      reducer(state, action);

      expect(OpLog.log).toHaveBeenCalledWith(
        jasmine.stringMatching(
          /Entity TASK:recreated-task not found, recreating from LWW update/,
        ),
      );
      expect(mockReducer).toHaveBeenCalled();
      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const recreatedTask = updatedState[TASK_FEATURE_NAME]?.entities[
        'recreated-task'
      ] as Task;
      expect(recreatedTask).toBeDefined();
      expect(recreatedTask.id).toBe('recreated-task');
      expect(recreatedTask.title).toBe('Recreated Task Title');
      expect(recreatedTask.notes).toBe(
        'This task was deleted locally but update won via LWW',
      );
      expect(recreatedTask.isDone).toBe(true);
      expect(recreatedTask.doneOn).toBe(12345);
    });

    it('should add recreated entity to the ids array', () => {
      const state = createMockState();
      const action = {
        type: '[TASK] LWW Update',
        id: 'new-task-from-lww',
        title: 'New Task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: 'new-task-from-lww' },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      expect(updatedState[TASK_FEATURE_NAME]?.ids).toContain('new-task-from-lww');
    });

    // Regression for issue #7330: a partial LWW Update payload (e.g. only the
    // changed fields from _convertToLWWUpdatesIfNeeded fallback) used to create
    // a task entity with `title`, `timeSpentOnDay`, `tagIds`, `subTaskIds`
    // undefined, which then failed Typia validation and dead-ended the user
    // on the "Repair attempted but failed" dialog.
    it('should backfill required fields when recreating from a partial TASK LWW Update (#7330)', () => {
      const state = createMockState();
      const action = {
        type: '[TASK] LWW Update',
        id: 'rpt_partial_2026-04-29',
        dueDay: '2026-04-29',
        meta: {
          isPersistent: true,
          entityType: 'TASK',
          entityId: 'rpt_partial_2026-04-29',
        },
      };

      spyOn(OpLog, 'warn');
      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const recreated = updatedState[TASK_FEATURE_NAME]?.entities[
        'rpt_partial_2026-04-29'
      ] as Task;

      expect(recreated).toBeDefined();
      // Schema-required fields are backfilled from DEFAULT_TASK
      expect(recreated.title).toBe('');
      expect(recreated.timeSpentOnDay).toEqual({});
      expect(recreated.tagIds).toEqual([]);
      expect(recreated.subTaskIds).toEqual([]);
      expect(recreated.timeSpent).toBe(0);
      expect(recreated.timeEstimate).toBe(0);
      expect(recreated.isDone).toBe(false);
      expect(recreated.attachments).toEqual([]);
      // Fields the LWW Update did carry are preserved
      expect(recreated.dueDay).toBe('2026-04-29');
      // And we logged the partial-payload warning so the upstream producer
      // can be identified from logs.
      expect(OpLog.warn).toHaveBeenCalledWith(
        jasmine.stringMatching(/missing required fields/),
      );
    });

    // #7330 follow-up: DEFAULT_TASK Omits projectId (since it varies per task),
    // so the basic merge leaves projectId undefined when the LWW payload is partial.
    // TaskCopy declares projectId as required, so Typia validation still fails.
    // Backfill with INBOX_PROJECT.id (the safe default for orphan tasks).
    it('should backfill projectId to INBOX_PROJECT.id when partial TASK LWW Update lacks it (#7330)', () => {
      const state = createMockState();
      const action = {
        type: '[TASK] LWW Update',
        id: 'rpt_no_project_2026-04-29',
        dueDay: '2026-04-29',
        meta: {
          isPersistent: true,
          entityType: 'TASK',
          entityId: 'rpt_no_project_2026-04-29',
        },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const recreated = updatedState[TASK_FEATURE_NAME]?.entities[
        'rpt_no_project_2026-04-29'
      ] as Task;

      expect(recreated).toBeDefined();
      expect(recreated.projectId).toBe('INBOX_PROJECT');
    });

    it('should add recreated task to backfilled project taskIds when partial TASK LWW Update lacks projectId', () => {
      const state = createMockState();
      const action = {
        type: '[TASK] LWW Update',
        id: 'rpt_no_project_visible',
        dueDay: '2026-04-29',
        meta: {
          isPersistent: true,
          entityType: 'TASK',
          entityId: 'rpt_no_project_visible',
        },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const recreated = updatedState[TASK_FEATURE_NAME]?.entities[
        'rpt_no_project_visible'
      ] as Task;
      const inboxProject = updatedState[PROJECT_FEATURE_NAME]?.entities[
        INBOX_PROJECT.id
      ] as Project;

      expect(recreated.projectId).toBe(INBOX_PROJECT.id);
      expect(inboxProject.taskIds).toContain('rpt_no_project_visible');
    });

    // #7330 follow-up #2: a producer that emits `title: null` (rather than
    // omitting it) used to slip past the partial-keys check (=== undefined)
    // AND have `null` overwrite the DEFAULT_TASK default in the spread.
    // Both null and undefined must be treated as "missing" for the recreate
    // path so Typia validation passes downstream.
    it('should treat null required fields as missing and backfill from defaults', () => {
      const state = createMockState();
      const action = {
        type: '[TASK] LWW Update',
        id: 'partial_with_nulls',
        title: null,
        timeSpentOnDay: null,
        tagIds: null,
        subTaskIds: null,
        attachments: null,
        projectId: null,
        meta: {
          isPersistent: true,
          entityType: 'TASK',
          entityId: 'partial_with_nulls',
        },
      };

      spyOn(OpLog, 'warn');
      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const recreated = updatedState[TASK_FEATURE_NAME]?.entities[
        'partial_with_nulls'
      ] as Task;

      expect(recreated.title).toBe('');
      expect(recreated.timeSpentOnDay).toEqual({});
      expect(recreated.tagIds).toEqual([]);
      expect(recreated.subTaskIds).toEqual([]);
      expect(recreated.attachments).toEqual([]);
      expect(recreated.projectId).toBe('INBOX_PROJECT');
      expect(OpLog.warn).toHaveBeenCalledWith(
        jasmine.stringMatching(/missing required fields/),
      );
    });

    // #7330 follow-up #2: an LWW Update payload with explicit `undefined`
    // values (e.g. from the field sanitization at lines 561-563) must not
    // overwrite a backfilled default. Spread does not skip undefined, so we
    // must strip them before merging with defaults.
    it('should strip undefined-valued fields so defaults survive the merge', () => {
      const state = createMockState();
      const action = {
        type: '[TASK] LWW Update',
        id: 'partial_with_undefineds',
        title: undefined,
        meta: {
          isPersistent: true,
          entityType: 'TASK',
          entityId: 'partial_with_undefineds',
        },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const recreated = updatedState[TASK_FEATURE_NAME]?.entities[
        'partial_with_undefineds'
      ] as Task;

      expect(recreated.title).toBe('');
    });

    // #7330 follow-up #2: existing code at task-shared-lifecycle.reducer.ts:110
    // already treats INBOX_PROJECT as not-guaranteed-present. The recreate
    // path must mirror that: if INBOX_PROJECT is missing from state, fall
    // back to the first available project so the recreated task is not
    // referencing a non-existent project.
    it('should fall back to first available project when INBOX_PROJECT is missing', () => {
      const state = createMockState();
      // Replace projects: no INBOX_PROJECT, only another project
      state[PROJECT_FEATURE_NAME] = {
        ids: ['some-other-project'],
        entities: {
          // eslint-disable-next-line @typescript-eslint/naming-convention
          'some-other-project': createMockProject({ id: 'some-other-project' }),
        },
      } as never;

      const action = {
        type: '[TASK] LWW Update',
        id: 'orphan-task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: 'orphan-task' },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const recreated = updatedState[TASK_FEATURE_NAME]?.entities['orphan-task'] as Task;

      expect(recreated.projectId).toBe('some-other-project');
    });

    it('should preserve projectId when partial TASK LWW Update carries one', () => {
      const state = createMockState();
      const action = {
        type: '[TASK] LWW Update',
        id: 'partial_with_project',
        projectId: 'some-other-project',
        dueDay: '2026-04-29',
        meta: {
          isPersistent: true,
          entityType: 'TASK',
          entityId: 'partial_with_project',
        },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const recreated = updatedState[TASK_FEATURE_NAME]?.entities[
        'partial_with_project'
      ] as Task;

      expect(recreated.projectId).toBe('some-other-project');
    });

    it('should not warn when a TASK LWW Update payload is complete', () => {
      const state = createMockState();
      const completeAction = {
        type: '[TASK] LWW Update',
        id: 'complete-task',
        title: 'Complete Task',
        timeSpentOnDay: {},
        tagIds: [],
        subTaskIds: [],
        attachments: [],
        projectId: 'INBOX_PROJECT',
        meta: { isPersistent: true, entityType: 'TASK', entityId: 'complete-task' },
      };

      spyOn(OpLog, 'warn');
      reducer(state, completeAction);

      expect(OpLog.warn).not.toHaveBeenCalledWith(
        jasmine.stringMatching(/missing required fields/),
      );
    });

    it('should skip update if action has no id', () => {
      const state = createMockState();
      const action = {
        type: '[TASK] LWW Update',
        title: 'Updated Title',
        meta: { isPersistent: true, entityType: 'TASK' },
      };

      spyOn(OpLog, 'warn');
      reducer(state, action);

      expect(OpLog.warn).toHaveBeenCalledWith(
        jasmine.stringMatching(/Entity data has no id/),
      );
      expect(mockReducer).toHaveBeenCalledWith(state, action);
    });

    // #7330 integration: drive the recreate path with the worst-case partial
    // payload shape that _convertToLWWUpdatesIfNeeded emits (just `{id}` plus
    // remote-changed fields), then run the resulting TaskState through the
    // real Typia validator (`appDataValidators.task`). This is the proof the
    // user's "Repair attempted but failed" dialog cannot fire from this
    // upstream path: Typia must accept the recreated entity.
    it('produces a Typia-valid TaskState when recreating from a producer-shape partial payload (#7330)', () => {
      // Empty initial state so only the recreated entity is under test.
      // (createMockState's `createMockTask` uses `null` for several fields that
      // TaskCopy declares as `string | undefined`, so it would taint validation.)
      const emptyState: Partial<RootState> = {
        [TASK_FEATURE_NAME]: {
          ids: [],
          entities: {},
          currentTaskId: null,
          selectedTaskId: null,
          taskDetailTargetPanel: null,
          isDataLoaded: true,
          lastCurrentTaskId: null,
        },
        [PROJECT_FEATURE_NAME]: { ids: [], entities: {} },
        [TAG_FEATURE_NAME]: { ids: [], entities: {} },
        [appStateFeatureKey]: {
          todayStr: getDbDateStr(),
          startOfNextDayDiffMs: 0,
        },
      } as Partial<RootState>;

      // The producer's flat-payload fallback (conflict-resolution.service.ts
      // ~line 888) emits exactly this shape: payload becomes the action, with
      // only the remote UPDATE's changed fields populated.
      const action = {
        type: '[TASK] LWW Update',
        id: 'rpt_producer_shape_2026-04-29',
        // Only the field the remote UPDATE changed:
        dueDay: '2026-04-29',
        meta: {
          isPersistent: true,
          entityType: 'TASK',
          entityId: 'rpt_producer_shape_2026-04-29',
        },
      };

      reducer(emptyState, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const taskState = updatedState[TASK_FEATURE_NAME];

      const result = appDataValidators.task(taskState as never);
      if (!result.success) {
        // Surface the typia errors so any future regression is debuggable.
        fail(
          `TaskState failed Typia validation: ${JSON.stringify((result as any).errors)}`,
        );
      }
      expect(result.success).toBe(true);
    });

    // #7330 follow-up: the same partial-payload recreate path can fire for any
    // adapter entity type. Generalize the DEFAULT_* merge so PROJECT/TAG/etc.
    // are protected with the same defense-in-depth as TASK.
    it('should backfill required fields when recreating from a partial PROJECT LWW Update', () => {
      const state = createMockState();
      const action = {
        type: '[PROJECT] LWW Update',
        id: 'recreated-project',
        title: 'Recreated Project',
        meta: {
          isPersistent: true,
          entityType: 'PROJECT',
          entityId: 'recreated-project',
        },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const recreated = updatedState[PROJECT_FEATURE_NAME]?.entities[
        'recreated-project'
      ] as Project;

      expect(recreated).toBeDefined();
      expect(recreated.title).toBe('Recreated Project');
      // Backfilled from DEFAULT_PROJECT
      expect(recreated.taskIds).toEqual([]);
      expect(recreated.backlogTaskIds).toEqual([]);
      expect(recreated.noteIds).toEqual([]);
      expect(recreated.isHiddenFromMenu).toBe(false);
      expect(recreated.isArchived).toBe(false);
    });

    it('should backfill required fields when recreating from a partial TAG LWW Update', () => {
      const state = createMockState();
      const action = {
        type: '[TAG] LWW Update',
        id: 'recreated-tag',
        title: 'Recreated Tag',
        meta: {
          isPersistent: true,
          entityType: 'TAG',
          entityId: 'recreated-tag',
        },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const recreated = updatedState[TAG_FEATURE_NAME]?.entities['recreated-tag'] as Tag;

      expect(recreated).toBeDefined();
      expect(recreated.title).toBe('Recreated Tag');
      // Backfilled from DEFAULT_TAG
      expect(recreated.taskIds).toEqual([]);
    });
  });

  // Issue #7330 recurred on SIMPLE_COUNTER (ruckusvol's logs, both clients
  // ≥ v18.6.0): a concurrent delete-vs-update across devices recreated a
  // counter from a partial payload missing `type` — an enum typia rejects and
  // dataRepair/auto-fix had no rule for — dead-ending the user on "Repair
  // attempted but failed". SIMPLE_COUNTER was added to RECREATE_FALLBACK so the
  // generic recreate path backfills required fields.
  describe('[SIMPLE_COUNTER] LWW Update (#7330)', () => {
    const makeStateWithCounters = (
      entities: Record<string, unknown> = {},
    ): Partial<RootState> =>
      ({
        [SIMPLE_COUNTER_FEATURE_NAME]: {
          ids: Object.keys(entities),
          entities,
        },
      }) as unknown as Partial<RootState>;

    it('backfills type (and other required fields) when recreating from a partial payload', () => {
      // Counter was deleted locally; a remote UPDATE won via LWW.
      const state = makeStateWithCounters();
      const action = {
        type: '[SIMPLE_COUNTER] LWW Update',
        id: 'cnt_partial',
        // The remote UPDATE only touched the count; `type` never made it in.
        countOnDay: { [getDbDateStr()]: 4 },
        meta: {
          isPersistent: true,
          entityType: 'SIMPLE_COUNTER',
          entityId: 'cnt_partial',
        },
      };

      spyOn(OpLog, 'warn');
      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const recreated = updatedState[SIMPLE_COUNTER_FEATURE_NAME]?.entities[
        'cnt_partial'
      ] as SimpleCounter;

      expect(recreated).toBeDefined();
      // The missing enum is backfilled to the harmless ClickCounter default.
      expect(recreated.type).toBe(SimpleCounterType.ClickCounter);
      expect(recreated.isEnabled).toBe(false);
      expect(recreated.isOn).toBe(false);
      // The remote-changed field the payload carried is preserved.
      expect(recreated.countOnDay).toEqual({ [getDbDateStr()]: 4 });
      expect(OpLog.warn).toHaveBeenCalledWith(
        jasmine.stringMatching(/missing required fields/),
      );
    });

    // The proof the user's dialog can't fire from this upstream path: run the
    // recreated SimpleCounterState through the real Typia validator.
    it('produces a Typia-valid SimpleCounterState when recreating from a partial payload', () => {
      const state = makeStateWithCounters();
      const action = {
        type: '[SIMPLE_COUNTER] LWW Update',
        id: 'cnt_producer_shape',
        countOnDay: { [getDbDateStr()]: 2 },
        meta: {
          isPersistent: true,
          entityType: 'SIMPLE_COUNTER',
          entityId: 'cnt_producer_shape',
        },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const counterState = updatedState[SIMPLE_COUNTER_FEATURE_NAME];

      const result = appDataValidators.simpleCounter(counterState as never);
      if (!result.success) {
        // Surface the typia errors so any future regression is debuggable.
        fail(
          `SimpleCounterState failed Typia validation: ${JSON.stringify(
            (result as { errors?: unknown }).errors,
          )}`,
        );
      }
      expect(result.success).toBe(true);
    });
  });

  describe('[PROJECT] LWW Update', () => {
    it('should update project entity with LWW winning state', () => {
      const state = createMockState();
      const action = {
        type: '[PROJECT] LWW Update',
        id: PROJECT_ID,
        title: 'LWW Winning Project Title',
        meta: {
          isPersistent: true,
          entityType: 'PROJECT',
          entityId: PROJECT_ID,
          isRemote: true,
        },
      };

      reducer(state, action);

      expect(mockReducer).toHaveBeenCalled();
      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const updatedProject = updatedState[PROJECT_FEATURE_NAME]?.entities[
        PROJECT_ID
      ] as Project;
      expect(updatedProject.title).toBe('LWW Winning Project Title');
    });

    it('should filter orphaned taskIds from PROJECT LWW Update', () => {
      const state = createMockState();
      const action = {
        type: '[PROJECT] LWW Update',
        id: PROJECT_ID,
        title: 'Updated Project',
        taskIds: [TASK_ID, 'non-existent-task'],
        backlogTaskIds: [],
        meta: {
          isPersistent: true,
          entityType: 'PROJECT',
          entityId: PROJECT_ID,
          isRemote: true,
        },
      };

      spyOn(OpLog, 'warn');
      reducer(state, action);

      expect(mockReducer).toHaveBeenCalled();
      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const updatedProject = updatedState[PROJECT_FEATURE_NAME]?.entities[
        PROJECT_ID
      ] as Project;
      expect(updatedProject.taskIds).toEqual([TASK_ID]);
      expect(OpLog.warn).toHaveBeenCalledWith(
        jasmine.stringMatching(/Filtered orphaned taskIds from PROJECT/),
        jasmine.objectContaining({
          taskIdsRemoved: ['non-existent-task'],
        }),
      );
    });

    it('should filter orphaned backlogTaskIds from PROJECT LWW Update', () => {
      const state = createMockState();
      const action = {
        type: '[PROJECT] LWW Update',
        id: PROJECT_ID,
        title: 'Updated Project',
        taskIds: [TASK_ID],
        backlogTaskIds: [TASK_ID, 'non-existent-backlog-task'],
        meta: {
          isPersistent: true,
          entityType: 'PROJECT',
          entityId: PROJECT_ID,
          isRemote: true,
        },
      };

      spyOn(OpLog, 'warn');
      reducer(state, action);

      expect(mockReducer).toHaveBeenCalled();
      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const updatedProject = updatedState[PROJECT_FEATURE_NAME]?.entities[
        PROJECT_ID
      ] as Project;
      expect(updatedProject.backlogTaskIds).toEqual([TASK_ID]);
      // Shared helper logs a single warn per filtered payload (vs. one-per-array
      // before #7330). Removed entries are split via structured fields.
      expect(OpLog.warn).toHaveBeenCalledWith(
        jasmine.stringMatching(/Filtered orphaned.*from PROJECT LWW Update/),
        jasmine.objectContaining({
          backlogTaskIdsRemoved: ['non-existent-backlog-task'],
        }),
      );
    });

    it('should keep all taskIds when all exist in PROJECT LWW Update', () => {
      const state = createMockState();
      const action = {
        type: '[PROJECT] LWW Update',
        id: PROJECT_ID,
        title: 'Updated Project',
        taskIds: [TASK_ID],
        backlogTaskIds: [TASK_ID],
        meta: {
          isPersistent: true,
          entityType: 'PROJECT',
          entityId: PROJECT_ID,
          isRemote: true,
        },
      };

      reducer(state, action);

      expect(mockReducer).toHaveBeenCalled();
      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const updatedProject = updatedState[PROJECT_FEATURE_NAME]?.entities[
        PROJECT_ID
      ] as Project;
      expect(updatedProject.taskIds).toEqual([TASK_ID]);
      expect(updatedProject.backlogTaskIds).toEqual([TASK_ID]);
    });
  });

  describe('[TAG] LWW Update', () => {
    it('should update tag entity with LWW winning state', () => {
      const state = createMockState();
      const action = {
        type: '[TAG] LWW Update',
        id: TAG_ID,
        title: 'LWW Winning Tag Title',
        color: '#00ff00',
        meta: {
          isPersistent: true,
          entityType: 'TAG',
          entityId: TAG_ID,
          isRemote: true,
        },
      };

      reducer(state, action);

      expect(mockReducer).toHaveBeenCalled();
      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const updatedTag = updatedState[TAG_FEATURE_NAME]?.entities[TAG_ID] as Tag;
      expect(updatedTag.title).toBe('LWW Winning Tag Title');
      expect(updatedTag.color).toBe('#00ff00');
    });

    it('should filter orphaned taskIds from TAG LWW Update', () => {
      const state = createMockState();
      const action = {
        type: '[TAG] LWW Update',
        id: TAG_ID,
        title: 'Updated Tag',
        taskIds: [TASK_ID, 'non-existent-task-1', 'non-existent-task-2'],
        color: '#00ff00',
        meta: {
          isPersistent: true,
          entityType: 'TAG',
          entityId: TAG_ID,
          isRemote: true,
        },
      };

      spyOn(OpLog, 'warn');
      reducer(state, action);

      expect(mockReducer).toHaveBeenCalled();
      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const updatedTag = updatedState[TAG_FEATURE_NAME]?.entities[TAG_ID] as Tag;
      expect(updatedTag.taskIds).toEqual([TASK_ID]);
      expect(OpLog.warn).toHaveBeenCalledWith(
        jasmine.stringMatching(/Filtered orphaned.*TAG LWW Update/),
        jasmine.objectContaining({
          taskIdsRemoved: ['non-existent-task-1', 'non-existent-task-2'],
        }),
      );
    });

    it('should keep all taskIds when all exist in TAG LWW Update', () => {
      const state = createMockState();
      const action = {
        type: '[TAG] LWW Update',
        id: TAG_ID,
        title: 'Updated Tag',
        taskIds: [TASK_ID],
        color: '#00ff00',
        meta: {
          isPersistent: true,
          entityType: 'TAG',
          entityId: TAG_ID,
          isRemote: true,
        },
      };

      reducer(state, action);

      expect(mockReducer).toHaveBeenCalled();
      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const updatedTag = updatedState[TAG_FEATURE_NAME]?.entities[TAG_ID] as Tag;
      expect(updatedTag.taskIds).toEqual([TASK_ID]);
    });

    it('should filter orphaned taskIds when recreating a deleted TAG via LWW Update', () => {
      const state = createMockState();
      // LWW Update for a tag that doesn't exist locally (deleted)
      const action = {
        type: '[TAG] LWW Update',
        id: 'new-tag-from-lww',
        title: 'Recreated Tag',
        taskIds: [TASK_ID, 'deleted-task-1', 'deleted-task-2'],
        color: '#0000ff',
        meta: {
          isPersistent: true,
          entityType: 'TAG',
          entityId: 'new-tag-from-lww',
          isRemote: true,
        },
      };

      spyOn(OpLog, 'warn');
      spyOn(OpLog, 'log');
      reducer(state, action);

      expect(mockReducer).toHaveBeenCalled();
      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const recreatedTag = updatedState[TAG_FEATURE_NAME]?.entities[
        'new-tag-from-lww'
      ] as Tag;
      expect(recreatedTag).toBeDefined();
      expect(recreatedTag.taskIds).toEqual([TASK_ID]);
    });
  });

  describe('[PROJECT] LWW Update with both orphaned taskIds and backlogTaskIds', () => {
    it('should filter orphaned IDs from both taskIds and backlogTaskIds simultaneously', () => {
      const state = createMockState();
      const action = {
        type: '[PROJECT] LWW Update',
        id: PROJECT_ID,
        title: 'Updated Project',
        taskIds: [TASK_ID, 'deleted-task-1'],
        backlogTaskIds: [TASK_ID, 'deleted-task-2'],
        meta: {
          isPersistent: true,
          entityType: 'PROJECT',
          entityId: PROJECT_ID,
          isRemote: true,
        },
      };

      spyOn(OpLog, 'warn');
      reducer(state, action);

      expect(mockReducer).toHaveBeenCalled();
      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const updatedProject = updatedState[PROJECT_FEATURE_NAME]?.entities[
        PROJECT_ID
      ] as Project;
      expect(updatedProject.taskIds).toEqual([TASK_ID]);
      expect(updatedProject.backlogTaskIds).toEqual([TASK_ID]);
      // Single warn covers both arrays (one log per filtered payload after #7330).
      expect(OpLog.warn).toHaveBeenCalledTimes(1);
      expect(OpLog.warn).toHaveBeenCalledWith(
        jasmine.stringMatching(/Filtered orphaned.*from PROJECT LWW Update/),
        jasmine.objectContaining({
          taskIdsRemoved: ['deleted-task-1'],
          backlogTaskIdsRemoved: ['deleted-task-2'],
        }),
      );
    });
  });

  describe('[SECTION] LWW Update', () => {
    // Regression: SECTION was missing from ENTITY_CONFIGS so this whole code
    // path bailed out at the "Unknown entity type" warn. These tests pin the
    // registry wiring so the gap can't reappear silently.
    it('should update section entity with LWW winning state', () => {
      const state = createMockState();
      const action = {
        type: '[SECTION] LWW Update',
        id: SECTION_ID,
        title: 'LWW Winning Section Title',
        contextId: PROJECT_ID,
        contextType: WorkContextType.PROJECT,
        taskIds: [],
        meta: {
          isPersistent: true,
          entityType: 'SECTION',
          entityId: SECTION_ID,
          isRemote: true,
        },
      };

      spyOn(OpLog, 'warn');
      reducer(state, action);

      expect(OpLog.warn).not.toHaveBeenCalledWith(
        jasmine.stringMatching(/Unknown entity type: SECTION/),
      );
      expect(mockReducer).toHaveBeenCalled();
      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const updatedSection = updatedState[SECTION_FEATURE_NAME]?.entities[
        SECTION_ID
      ] as Section;
      expect(updatedSection.title).toBe('LWW Winning Section Title');
    });

    it('should recreate section if it does not exist (LWW update won over delete)', () => {
      const state = createMockState();
      const action = {
        type: '[SECTION] LWW Update',
        id: 'recreated-section',
        title: 'Recreated Section',
        contextId: PROJECT_ID,
        contextType: WorkContextType.PROJECT,
        taskIds: [],
        meta: {
          isPersistent: true,
          entityType: 'SECTION',
          entityId: 'recreated-section',
        },
      };

      spyOn(OpLog, 'log');
      spyOn(OpLog, 'warn');
      reducer(state, action);

      expect(OpLog.warn).not.toHaveBeenCalledWith(
        jasmine.stringMatching(/Unknown entity type: SECTION/),
      );
      expect(OpLog.log).toHaveBeenCalledWith(
        jasmine.stringMatching(
          /Entity SECTION:recreated-section not found, recreating from LWW update/,
        ),
      );
      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const recreated = updatedState[SECTION_FEATURE_NAME]?.entities[
        'recreated-section'
      ] as Section;
      expect(recreated).toBeDefined();
      expect(recreated.id).toBe('recreated-section');
      expect(recreated.title).toBe('Recreated Section');
      expect(recreated.contextId).toBe(PROJECT_ID);
    });
  });

  describe('unknown entity types', () => {
    it('should pass through for unknown entity types without warning', () => {
      const state = createMockState();
      const action = {
        type: '[UNKNOWN_ENTITY] LWW Update',
        id: 'unknown-1',
        title: 'Updated Title',
        meta: { isPersistent: true, entityType: 'UNKNOWN_ENTITY', entityId: 'unknown-1' },
      };

      // Unknown entity types are not in the LWW_UPDATE_ACTION_TYPES set,
      // so they silently pass through as non-LWW actions.
      reducer(state, action);

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

  describe('singleton entity LWW Updates', () => {
    const createMockStateWithSingletons = (): Partial<RootState> =>
      ({
        ...createMockState(),
        globalConfig: {
          misc: {
            isDisableAnimations: false,
            isDisableCelebration: false,
          },
          sound: { isEnabled: true },
        },
        timeTracking: {
          currentSessionTime: 0,
          isTrackingReminder: true,
        },
      }) as unknown as Partial<RootState>;

    it('should replace globalConfig state with LWW winning data', () => {
      const state = createMockStateWithSingletons();
      const action = {
        type: '[GLOBAL_CONFIG] LWW Update',
        misc: {
          isDisableAnimations: true,
          isDisableCelebration: true,
        },
        sound: { isEnabled: false },
        meta: {
          isPersistent: true,
          entityType: 'GLOBAL_CONFIG',
          isRemote: true,
        },
      };

      reducer(state, action);

      expect(mockReducer).toHaveBeenCalled();
      const updatedState = mockReducer.calls.mostRecent().args[0] as Record<
        string,
        unknown
      >;
      const globalConfig = updatedState[CONFIG_FEATURE_NAME] as Record<string, unknown>;
      expect(globalConfig).toBeDefined();
      expect(
        (globalConfig['misc'] as Record<string, unknown>)['isDisableAnimations'],
      ).toBe(true);
      expect(
        (globalConfig['misc'] as Record<string, unknown>)['isDisableCelebration'],
      ).toBe(true);
      expect((globalConfig['sound'] as Record<string, unknown>)['isEnabled']).toBe(false);
    });

    it('should fully replace state, not merge with existing', () => {
      const state = createMockStateWithSingletons();
      const action = {
        type: '[GLOBAL_CONFIG] LWW Update',
        misc: { isDisableAnimations: true },
        meta: { isPersistent: true, entityType: 'GLOBAL_CONFIG', isRemote: true },
      };

      reducer(state, action);

      expect(mockReducer).toHaveBeenCalled();
      const updatedState = mockReducer.calls.mostRecent().args[0] as Record<
        string,
        unknown
      >;
      const globalConfig = updatedState[CONFIG_FEATURE_NAME] as Record<string, unknown>;
      // 'sound' was in the original state but not in the action payload — it must be gone
      expect(globalConfig['sound']).toBeUndefined();
      // 'misc' was in the payload — it must be present
      expect(globalConfig['misc']).toBeDefined();
    });

    it('should replace timeTracking state with LWW winning data', () => {
      const state = createMockStateWithSingletons();
      const action = {
        type: '[TIME_TRACKING] LWW Update',
        currentSessionTime: 5000,
        isTrackingReminder: false,
        meta: {
          isPersistent: true,
          entityType: 'TIME_TRACKING',
          isRemote: true,
        },
      };

      reducer(state, action);

      expect(mockReducer).toHaveBeenCalled();
      const updatedState = mockReducer.calls.mostRecent().args[0] as Record<
        string,
        unknown
      >;
      const timeTracking = updatedState[TIME_TRACKING_FEATURE_KEY] as Record<
        string,
        unknown
      >;
      expect(timeTracking).toBeDefined();
      expect(timeTracking['currentSessionTime']).toBe(5000);
      expect(timeTracking['isTrackingReminder']).toBe(false);
    });

    it('should not require id field for singleton entities', () => {
      const state = createMockStateWithSingletons();
      const action = {
        type: '[GLOBAL_CONFIG] LWW Update',
        misc: { isDisableAnimations: true },
        meta: { isPersistent: true, entityType: 'GLOBAL_CONFIG', isRemote: true },
      };

      spyOn(OpLog, 'warn');
      reducer(state, action);

      expect(OpLog.warn).not.toHaveBeenCalledWith(
        jasmine.stringMatching(/Entity data has no id/),
      );
      const updatedState = mockReducer.calls.mostRecent().args[0] as Record<
        string,
        unknown
      >;
      const globalConfig = updatedState[CONFIG_FEATURE_NAME] as Record<string, unknown>;
      expect(globalConfig['misc']).toBeDefined();
    });

    it('should strip type and meta from applied singleton state', () => {
      const state = createMockStateWithSingletons();
      const action = {
        type: '[GLOBAL_CONFIG] LWW Update',
        misc: { isDisableAnimations: true },
        meta: { isPersistent: true, entityType: 'GLOBAL_CONFIG', isRemote: true },
      };

      reducer(state, action);

      expect(mockReducer).toHaveBeenCalled();
      const updatedState = mockReducer.calls.mostRecent().args[0] as Record<
        string,
        unknown
      >;
      const globalConfig = updatedState[CONFIG_FEATURE_NAME] as Record<string, unknown>;
      expect(globalConfig['type']).toBeUndefined();
      expect(globalConfig['meta']).toBeUndefined();
    });

    it('should not replace singleton state with empty data', () => {
      const state = createMockStateWithSingletons();
      // Action with only type and meta — no actual entity data
      const action = {
        type: '[GLOBAL_CONFIG] LWW Update',
        meta: { isPersistent: true, entityType: 'GLOBAL_CONFIG', isRemote: true },
      };

      spyOn(OpLog, 'warn');
      // Prevent devError from throwing (it calls alert + confirm → throws if true)
      if (!jasmine.isSpy(window.alert)) {
        spyOn(window, 'alert');
      }
      if (!jasmine.isSpy(window.confirm)) {
        spyOn(window, 'confirm').and.returnValue(false);
      } else {
        (window.confirm as jasmine.Spy).and.returnValue(false);
      }
      reducer(state, action);

      expect(OpLog.warn).toHaveBeenCalledWith(
        jasmine.stringMatching(/Empty singleton data/),
      );
      // State should be passed through unchanged
      expect(mockReducer).toHaveBeenCalledWith(state, action);
    });
  });

  describe('null/undefined state', () => {
    it('should pass through if state is undefined', () => {
      const action = { type: '[TASK] LWW Update', id: TASK_ID };

      reducer(undefined, action);

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

  describe('project.taskIds sync on task projectId change', () => {
    const PROJECT_A = 'project-a';
    const PROJECT_B = 'project-b';

    const createStateWithProjects = (
      taskProjectId: string | undefined,
      projectATaskIds: string[],
      projectBTaskIds: string[],
    ): Partial<RootState> =>
      ({
        [TASK_FEATURE_NAME]: {
          ids: [TASK_ID],
          entities: {
            [TASK_ID]: createMockTask({ projectId: taskProjectId }),
          },
          currentTaskId: null,
          selectedTaskId: null,
          taskDetailTargetPanel: null,
          isDataLoaded: true,
          lastCurrentTaskId: null,
        },
        [PROJECT_FEATURE_NAME]: {
          ids: [PROJECT_A, PROJECT_B],
          entities: {
            [PROJECT_A]: createMockProject({
              id: PROJECT_A,
              title: 'Project A',
              taskIds: projectATaskIds,
            }),
            [PROJECT_B]: createMockProject({
              id: PROJECT_B,
              title: 'Project B',
              taskIds: projectBTaskIds,
            }),
          },
        },
        [TAG_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
      }) as Partial<RootState>;

    it('should move task from old project to new project when projectId changes', () => {
      // Task is in Project A, LWW update moves it to Project B
      const state = createStateWithProjects(PROJECT_A, [TASK_ID], []);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        projectId: PROJECT_B,
        title: 'Updated Task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const projectA = updatedState[PROJECT_FEATURE_NAME]?.entities[PROJECT_A] as Project;
      const projectB = updatedState[PROJECT_FEATURE_NAME]?.entities[PROJECT_B] as Project;

      expect(projectA.taskIds).not.toContain(TASK_ID);
      expect(projectB.taskIds).toContain(TASK_ID);
    });

    it('should remove task from old project backlogTaskIds when projectId changes', () => {
      // Task is in Project A's backlog, LWW update moves it to Project B
      const state = {
        ...createStateWithProjects(PROJECT_A, [], []),
        [PROJECT_FEATURE_NAME]: {
          ids: [PROJECT_A, PROJECT_B],
          entities: {
            [PROJECT_A]: createMockProject({
              id: PROJECT_A,
              title: 'Project A',
              taskIds: [],
              backlogTaskIds: [TASK_ID],
            }),
            [PROJECT_B]: createMockProject({
              id: PROJECT_B,
              title: 'Project B',
              taskIds: [],
            }),
          },
        },
      } as Partial<RootState>;

      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        projectId: PROJECT_B,
        title: 'Updated Task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const projectA = updatedState[PROJECT_FEATURE_NAME]?.entities[PROJECT_A] as Project;
      const projectB = updatedState[PROJECT_FEATURE_NAME]?.entities[PROJECT_B] as Project;

      expect(projectA.backlogTaskIds).not.toContain(TASK_ID);
      expect(projectB.taskIds).toContain(TASK_ID);
    });

    it('should not duplicate task in project.taskIds if already present', () => {
      // Task is already in Project B's taskIds (edge case)
      const state = createStateWithProjects(PROJECT_A, [TASK_ID], [TASK_ID]);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        projectId: PROJECT_B,
        title: 'Updated Task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const projectB = updatedState[PROJECT_FEATURE_NAME]?.entities[PROJECT_B] as Project;

      // Should still only have one instance
      expect(projectB.taskIds.filter((id) => id === TASK_ID).length).toBe(1);
    });

    it('should not update project.taskIds for subtasks', () => {
      // Subtask should not be added to project.taskIds
      const state = {
        ...createStateWithProjects(PROJECT_A, [], []),
        [TASK_FEATURE_NAME]: {
          ids: [TASK_ID],
          entities: {
            [TASK_ID]: createMockTask({ projectId: PROJECT_A, parentId: 'parent-task' }),
          },
          currentTaskId: null,
          selectedTaskId: null,
          taskDetailTargetPanel: null,
          isDataLoaded: true,
          lastCurrentTaskId: null,
        },
      } as Partial<RootState>;

      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        projectId: PROJECT_B,
        parentId: 'parent-task',
        title: 'Updated Subtask',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const projectB = updatedState[PROJECT_FEATURE_NAME]?.entities[PROJECT_B] as Project;

      // Subtask should NOT be added to project.taskIds
      expect(projectB.taskIds).not.toContain(TASK_ID);
    });

    it('should handle projectId change when old project does not exist', () => {
      // Old project was deleted, new project exists
      const state = {
        [TASK_FEATURE_NAME]: {
          ids: [TASK_ID],
          entities: {
            [TASK_ID]: createMockTask({ projectId: 'deleted-project' }),
          },
          currentTaskId: null,
          selectedTaskId: null,
          taskDetailTargetPanel: null,
          isDataLoaded: true,
          lastCurrentTaskId: null,
        },
        [PROJECT_FEATURE_NAME]: {
          ids: [PROJECT_B],
          entities: {
            [PROJECT_B]: createMockProject({ id: PROJECT_B, title: 'Project B' }),
          },
        },
        [TAG_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
      } as Partial<RootState>;

      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        projectId: PROJECT_B,
        title: 'Updated Task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      // Should not throw
      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const projectB = updatedState[PROJECT_FEATURE_NAME]?.entities[PROJECT_B] as Project;

      expect(projectB.taskIds).toContain(TASK_ID);
    });

    it('should not update projects when projectId does not change', () => {
      const state = createStateWithProjects(PROJECT_A, [TASK_ID], []);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        projectId: PROJECT_A, // Same as existing
        title: 'Updated Task Title Only',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const projectA = updatedState[PROJECT_FEATURE_NAME]?.entities[PROJECT_A] as Project;
      const projectB = updatedState[PROJECT_FEATURE_NAME]?.entities[PROJECT_B] as Project;

      // Project A should still have the task
      expect(projectA.taskIds).toContain(TASK_ID);
      // Project B should still be empty
      expect(projectB.taskIds).not.toContain(TASK_ID);
    });

    it('should preserve project.taskIds when projectId is omitted from partial LWW payload', () => {
      const state = createStateWithProjects(PROJECT_A, [TASK_ID], []);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        title: 'Updated Task Title Only',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const projectA = updatedState[PROJECT_FEATURE_NAME]?.entities[PROJECT_A] as Project;

      expect(projectA.taskIds).toContain(TASK_ID);
    });

    it('should preserve backlog membership when projectId is omitted from partial LWW payload', () => {
      const state = {
        ...createStateWithProjects(PROJECT_A, [], []),
        [PROJECT_FEATURE_NAME]: {
          ids: [PROJECT_A, PROJECT_B],
          entities: {
            [PROJECT_A]: createMockProject({
              id: PROJECT_A,
              title: 'Project A',
              taskIds: [],
              backlogTaskIds: [TASK_ID],
            }),
            [PROJECT_B]: createMockProject({
              id: PROJECT_B,
              title: 'Project B',
              taskIds: [],
            }),
          },
        },
      } as Partial<RootState>;
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        title: 'Updated Backlog Task Title Only',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const projectA = updatedState[PROJECT_FEATURE_NAME]?.entities[PROJECT_A] as Project;

      expect(projectA.taskIds).not.toContain(TASK_ID);
      expect(projectA.backlogTaskIds).toContain(TASK_ID);
    });
  });

  describe('tag.taskIds sync on task tagIds change', () => {
    const TAG_A = 'tag-a';
    const TAG_B = 'tag-b';
    const TAG_C = 'tag-c';

    const createStateWithTags = (
      taskTagIds: string[],
      tagATaskIds: string[],
      tagBTaskIds: string[],
      tagCTaskIds: string[] = [],
    ): Partial<RootState> =>
      ({
        [TASK_FEATURE_NAME]: {
          ids: [TASK_ID],
          entities: {
            [TASK_ID]: createMockTask({ tagIds: taskTagIds }),
          },
          currentTaskId: null,
          selectedTaskId: null,
          taskDetailTargetPanel: null,
          isDataLoaded: true,
          lastCurrentTaskId: null,
        },
        [PROJECT_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
        [TAG_FEATURE_NAME]: {
          ids: [TAG_A, TAG_B, TAG_C],
          entities: {
            [TAG_A]: createMockTag({
              id: TAG_A,
              title: 'Tag A',
              taskIds: tagATaskIds,
            }),
            [TAG_B]: createMockTag({
              id: TAG_B,
              title: 'Tag B',
              taskIds: tagBTaskIds,
            }),
            [TAG_C]: createMockTag({
              id: TAG_C,
              title: 'Tag C',
              taskIds: tagCTaskIds,
            }),
          },
        },
      }) as Partial<RootState>;

    it('should add task to tag.taskIds when tag is added to task.tagIds', () => {
      // Task has Tag A, LWW update adds Tag B
      const state = createStateWithTags([TAG_A], [TASK_ID], []);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        tagIds: [TAG_A, TAG_B],
        title: 'Updated Task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const tagA = updatedState[TAG_FEATURE_NAME]?.entities[TAG_A] as Tag;
      const tagB = updatedState[TAG_FEATURE_NAME]?.entities[TAG_B] as Tag;

      expect(tagA.taskIds).toContain(TASK_ID);
      expect(tagB.taskIds).toContain(TASK_ID);
    });

    it('should remove task from tag.taskIds when tag is removed from task.tagIds', () => {
      // Task has Tag A and Tag B, LWW update removes Tag B
      const state = createStateWithTags([TAG_A, TAG_B], [TASK_ID], [TASK_ID]);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        tagIds: [TAG_A], // Tag B removed
        title: 'Updated Task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const tagA = updatedState[TAG_FEATURE_NAME]?.entities[TAG_A] as Tag;
      const tagB = updatedState[TAG_FEATURE_NAME]?.entities[TAG_B] as Tag;

      expect(tagA.taskIds).toContain(TASK_ID);
      expect(tagB.taskIds).not.toContain(TASK_ID);
    });

    it('should handle complete tag replacement', () => {
      // Task has Tag A, LWW update replaces with Tag B and Tag C
      const state = createStateWithTags([TAG_A], [TASK_ID], [], []);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        tagIds: [TAG_B, TAG_C], // Tag A removed, Tag B and C added
        title: 'Updated Task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const tagA = updatedState[TAG_FEATURE_NAME]?.entities[TAG_A] as Tag;
      const tagB = updatedState[TAG_FEATURE_NAME]?.entities[TAG_B] as Tag;
      const tagC = updatedState[TAG_FEATURE_NAME]?.entities[TAG_C] as Tag;

      expect(tagA.taskIds).not.toContain(TASK_ID);
      expect(tagB.taskIds).toContain(TASK_ID);
      expect(tagC.taskIds).toContain(TASK_ID);
    });

    it('should not duplicate task in tag.taskIds if already present', () => {
      // Task already in Tag B's taskIds (edge case)
      const state = createStateWithTags([TAG_A], [TASK_ID], [TASK_ID]);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        tagIds: [TAG_A, TAG_B],
        title: 'Updated Task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const tagB = updatedState[TAG_FEATURE_NAME]?.entities[TAG_B] as Tag;

      // Should still only have one instance
      expect(tagB.taskIds.filter((id) => id === TASK_ID).length).toBe(1);
    });

    it('should handle tag that does not exist gracefully', () => {
      // Task references a non-existent tag
      const state = createStateWithTags([TAG_A], [TASK_ID], []);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        tagIds: [TAG_A, 'non-existent-tag'],
        title: 'Updated Task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      // Should not throw
      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const tagA = updatedState[TAG_FEATURE_NAME]?.entities[TAG_A] as Tag;

      expect(tagA.taskIds).toContain(TASK_ID);
    });

    it('should not update tags when tagIds does not change', () => {
      const state = createStateWithTags([TAG_A, TAG_B], [TASK_ID], [TASK_ID]);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        tagIds: [TAG_A, TAG_B], // Same as existing
        title: 'Updated Task Title Only',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const tagA = updatedState[TAG_FEATURE_NAME]?.entities[TAG_A] as Tag;
      const tagB = updatedState[TAG_FEATURE_NAME]?.entities[TAG_B] as Tag;
      const tagC = updatedState[TAG_FEATURE_NAME]?.entities[TAG_C] as Tag;

      // Tags should remain unchanged
      expect(tagA.taskIds).toContain(TASK_ID);
      expect(tagB.taskIds).toContain(TASK_ID);
      expect(tagC.taskIds).not.toContain(TASK_ID);
    });

    it('should preserve tag.taskIds when tagIds is omitted from partial LWW payload', () => {
      const state = createStateWithTags([TAG_A, TAG_B], [TASK_ID], [TASK_ID]);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        title: 'Updated Task Title Only',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const tagA = updatedState[TAG_FEATURE_NAME]?.entities[TAG_A] as Tag;
      const tagB = updatedState[TAG_FEATURE_NAME]?.entities[TAG_B] as Tag;

      expect(tagA.taskIds).toContain(TASK_ID);
      expect(tagB.taskIds).toContain(TASK_ID);
    });

    it('should handle task with no previous tags getting tags', () => {
      const state = createStateWithTags([], [], []);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        tagIds: [TAG_A, TAG_B],
        title: 'Updated Task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const tagA = updatedState[TAG_FEATURE_NAME]?.entities[TAG_A] as Tag;
      const tagB = updatedState[TAG_FEATURE_NAME]?.entities[TAG_B] as Tag;

      expect(tagA.taskIds).toContain(TASK_ID);
      expect(tagB.taskIds).toContain(TASK_ID);
    });

    it('should handle task losing all tags', () => {
      const state = createStateWithTags([TAG_A, TAG_B], [TASK_ID], [TASK_ID]);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        tagIds: [], // All tags removed
        title: 'Updated Task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const tagA = updatedState[TAG_FEATURE_NAME]?.entities[TAG_A] as Tag;
      const tagB = updatedState[TAG_FEATURE_NAME]?.entities[TAG_B] as Tag;

      expect(tagA.taskIds).not.toContain(TASK_ID);
      expect(tagB.taskIds).not.toContain(TASK_ID);
    });

    it('should preserve tag.taskIds order when adding task to tag', () => {
      // Tag A already has some tasks in specific order
      const existingTasks = ['existing-1', 'existing-2', 'existing-3'];
      const state = {
        ...createStateWithTags([], [], []),
        [TAG_FEATURE_NAME]: {
          ids: [TAG_A],
          entities: {
            [TAG_A]: createMockTag({
              id: TAG_A,
              title: 'Tag A',
              taskIds: existingTasks,
            }),
          },
        },
      } as Partial<RootState>;

      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        tagIds: [TAG_A], // Adding task to TAG_A
        title: 'Updated Task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const tagA = updatedState[TAG_FEATURE_NAME]?.entities[TAG_A] as Tag;

      // Original order should be preserved, new task appended at end
      expect(tagA.taskIds).toEqual([...existingTasks, TASK_ID]);
    });

    it('should preserve tag.taskIds order when removing task from tag', () => {
      // Tag A has tasks in specific order, including TASK_ID
      const state = {
        ...createStateWithTags([TAG_A], [], []),
        [TAG_FEATURE_NAME]: {
          ids: [TAG_A],
          entities: {
            [TAG_A]: createMockTag({
              id: TAG_A,
              title: 'Tag A',
              taskIds: ['first', TASK_ID, 'last'],
            }),
          },
        },
      } as Partial<RootState>;

      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        tagIds: [], // Removing TAG_A from task
        title: 'Updated Task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const tagA = updatedState[TAG_FEATURE_NAME]?.entities[TAG_A] as Tag;

      // Order should be preserved after removal
      expect(tagA.taskIds).toEqual(['first', 'last']);
    });
  });

  describe('subtask edge cases', () => {
    const PARENT_TASK = 'parent-task';
    const SUBTASK_ID = 'subtask-1';
    const PROJECT_A = 'project-a';

    it('should handle subtask with parentId referencing deleted parent gracefully', () => {
      // Subtask references a parent that no longer exists in state
      const state = {
        [TASK_FEATURE_NAME]: {
          ids: [SUBTASK_ID],
          entities: {
            [SUBTASK_ID]: createMockTask({
              id: SUBTASK_ID,
              parentId: PARENT_TASK, // Parent no longer exists
              projectId: PROJECT_A,
            }),
          },
          currentTaskId: null,
          selectedTaskId: null,
          taskDetailTargetPanel: null,
          isDataLoaded: true,
          lastCurrentTaskId: null,
        },
        [PROJECT_FEATURE_NAME]: {
          ids: [PROJECT_A],
          entities: {
            [PROJECT_A]: createMockProject({ id: PROJECT_A, taskIds: [] }),
          },
        },
        [TAG_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
      } as Partial<RootState>;

      const action = {
        type: '[TASK] LWW Update',
        id: SUBTASK_ID,
        parentId: PARENT_TASK, // Still references deleted parent
        projectId: PROJECT_A,
        title: 'Updated Subtask',
        meta: { isPersistent: true, entityType: 'TASK', entityId: SUBTASK_ID },
      };

      // Should not throw
      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const subtask = updatedState[TASK_FEATURE_NAME]?.entities[SUBTASK_ID] as Task;
      const projectA = updatedState[PROJECT_FEATURE_NAME]?.entities[PROJECT_A] as Project;

      // Subtask should be updated
      expect(subtask.title).toBe('Updated Subtask');
      expect(subtask.parentId).toBe(PARENT_TASK);

      // Subtask should NOT be added to project.taskIds (subtasks are excluded)
      expect(projectA.taskIds).not.toContain(SUBTASK_ID);
    });

    it('should add promoted subtask to project.taskIds when parentId is removed in same project', () => {
      // Subtask loses its parent via LWW update but stays in same project.
      const state = {
        [TASK_FEATURE_NAME]: {
          ids: [SUBTASK_ID],
          entities: {
            [SUBTASK_ID]: createMockTask({
              id: SUBTASK_ID,
              parentId: PARENT_TASK,
              projectId: PROJECT_A,
            }),
          },
          currentTaskId: null,
          selectedTaskId: null,
          taskDetailTargetPanel: null,
          isDataLoaded: true,
          lastCurrentTaskId: null,
        },
        [PROJECT_FEATURE_NAME]: {
          ids: [PROJECT_A],
          entities: {
            [PROJECT_A]: createMockProject({ id: PROJECT_A, taskIds: [] }),
          },
        },
        [TAG_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
      } as Partial<RootState>;

      const action = {
        type: '[TASK] LWW Update',
        id: SUBTASK_ID,
        parentId: null, // Parent removed - becomes top-level task
        projectId: PROJECT_A, // Same project — no project change
        title: 'Now a top-level task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: SUBTASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const subtask = updatedState[TASK_FEATURE_NAME]?.entities[SUBTASK_ID] as Task;
      const projectA = updatedState[PROJECT_FEATURE_NAME]?.entities[PROJECT_A] as Project;

      // Task should now be a top-level task
      expect(subtask.parentId).toBeNull();
      expect(projectA.taskIds).toContain(SUBTASK_ID);
    });

    it('should remove task from project.taskIds when it becomes a subtask in same project', () => {
      const state = {
        [TASK_FEATURE_NAME]: {
          ids: [SUBTASK_ID, PARENT_TASK],
          entities: {
            [SUBTASK_ID]: createMockTask({
              id: SUBTASK_ID,
              parentId: undefined,
              projectId: PROJECT_A,
            }),
            [PARENT_TASK]: createMockTask({
              id: PARENT_TASK,
              parentId: undefined,
              projectId: PROJECT_A,
              subTaskIds: [],
            }),
          },
          currentTaskId: null,
          selectedTaskId: null,
          taskDetailTargetPanel: null,
          isDataLoaded: true,
          lastCurrentTaskId: null,
        },
        [PROJECT_FEATURE_NAME]: {
          ids: [PROJECT_A],
          entities: {
            [PROJECT_A]: createMockProject({ id: PROJECT_A, taskIds: [SUBTASK_ID] }),
          },
        },
        [TAG_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
      } as Partial<RootState>;

      const action = {
        type: '[TASK] LWW Update',
        id: SUBTASK_ID,
        parentId: PARENT_TASK,
        projectId: PROJECT_A,
        title: 'Now a subtask',
        meta: { isPersistent: true, entityType: 'TASK', entityId: SUBTASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const projectA = updatedState[PROJECT_FEATURE_NAME]?.entities[PROJECT_A] as Project;
      const parent = updatedState[TASK_FEATURE_NAME]?.entities[PARENT_TASK] as Task;

      expect(projectA.taskIds).not.toContain(SUBTASK_ID);
      expect(parent.subTaskIds).toContain(SUBTASK_ID);
    });

    it('should add promoted subtask to new project.taskIds when parentId is cleared and projectId changes', () => {
      // Subtask is promoted to main task AND moves to a new project via LWW update.
      // With the 'parentId' in entityData check, the task is correctly treated as
      // a main task (not subtask), so it SHOULD be added to the new project's taskIds.
      const PROJECT_B = 'project-b';
      const state = {
        [TASK_FEATURE_NAME]: {
          ids: [SUBTASK_ID],
          entities: {
            [SUBTASK_ID]: createMockTask({
              id: SUBTASK_ID,
              parentId: PARENT_TASK,
              projectId: PROJECT_A,
            }),
          },
          currentTaskId: null,
          selectedTaskId: null,
          taskDetailTargetPanel: null,
          isDataLoaded: true,
          lastCurrentTaskId: null,
        },
        [PROJECT_FEATURE_NAME]: {
          ids: [PROJECT_A, PROJECT_B],
          entities: {
            [PROJECT_A]: createMockProject({ id: PROJECT_A, taskIds: [] }),
            [PROJECT_B]: createMockProject({ id: PROJECT_B, taskIds: [] }),
          },
        },
        [TAG_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
      } as Partial<RootState>;

      const action = {
        type: '[TASK] LWW Update',
        id: SUBTASK_ID,
        parentId: null, // Promoted to main task
        projectId: PROJECT_B, // Moved to different project
        title: 'Promoted and moved task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: SUBTASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const projectB = updatedState[PROJECT_FEATURE_NAME]?.entities[PROJECT_B] as Project;

      // Task is now a main task (parentId cleared), so it should be added to new project
      expect(projectB.taskIds).toContain(SUBTASK_ID);
    });

    it('should recognize task as subtask based on existing parentId when new data has no parentId key', () => {
      // When the LWW update payload doesn't include parentId at all,
      // fall back to existingEntity.parentId to determine subtask status.
      const state = {
        [TASK_FEATURE_NAME]: {
          ids: [SUBTASK_ID],
          entities: {
            [SUBTASK_ID]: createMockTask({
              id: SUBTASK_ID,
              parentId: PARENT_TASK, // Has parent in existing state
              projectId: undefined,
            }),
          },
          currentTaskId: null,
          selectedTaskId: null,
          taskDetailTargetPanel: null,
          isDataLoaded: true,
          lastCurrentTaskId: null,
        },
        [PROJECT_FEATURE_NAME]: {
          ids: [PROJECT_A],
          entities: {
            [PROJECT_A]: createMockProject({ id: PROJECT_A, taskIds: [] }),
          },
        },
        [TAG_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
      } as Partial<RootState>;

      const action = {
        type: '[TASK] LWW Update',
        id: SUBTASK_ID,
        // Note: parentId not included in LWW update payload
        projectId: PROJECT_A,
        title: 'Subtask moved to project',
        meta: { isPersistent: true, entityType: 'TASK', entityId: SUBTASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const projectA = updatedState[PROJECT_FEATURE_NAME]?.entities[PROJECT_A] as Project;

      // parentId is not in the payload, so we fall back to existingEntity.parentId.
      // Still treated as subtask, therefore NOT added to project.taskIds
      expect(projectA.taskIds).not.toContain(SUBTASK_ID);
    });
  });

  describe('project.taskIds ordering', () => {
    const PROJECT_A = 'project-a';

    it('should preserve project.taskIds order when adding task', () => {
      const existingTasks = ['task-a', 'task-b', 'task-c'];
      const state = {
        [TASK_FEATURE_NAME]: {
          ids: [TASK_ID],
          entities: {
            [TASK_ID]: createMockTask({ projectId: undefined }), // Moving to project A
          },
          currentTaskId: null,
          selectedTaskId: null,
          taskDetailTargetPanel: null,
          isDataLoaded: true,
          lastCurrentTaskId: null,
        },
        [PROJECT_FEATURE_NAME]: {
          ids: [PROJECT_A],
          entities: {
            [PROJECT_A]: createMockProject({
              id: PROJECT_A,
              taskIds: existingTasks,
            }),
          },
        },
        [TAG_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
      } as Partial<RootState>;

      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        projectId: PROJECT_A,
        title: 'Task moving to project',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const projectA = updatedState[PROJECT_FEATURE_NAME]?.entities[PROJECT_A] as Project;

      // Original order should be preserved, new task appended at end
      expect(projectA.taskIds).toEqual([...existingTasks, TASK_ID]);
    });

    it('should preserve project.taskIds order when removing task', () => {
      const PROJECT_B = 'project-b';
      const state = {
        [TASK_FEATURE_NAME]: {
          ids: [TASK_ID],
          entities: {
            [TASK_ID]: createMockTask({ projectId: PROJECT_A }),
          },
          currentTaskId: null,
          selectedTaskId: null,
          taskDetailTargetPanel: null,
          isDataLoaded: true,
          lastCurrentTaskId: null,
        },
        [PROJECT_FEATURE_NAME]: {
          ids: [PROJECT_A, PROJECT_B],
          entities: {
            [PROJECT_A]: createMockProject({
              id: PROJECT_A,
              taskIds: ['first', TASK_ID, 'last'],
            }),
            [PROJECT_B]: createMockProject({ id: PROJECT_B, taskIds: [] }),
          },
        },
        [TAG_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
      } as Partial<RootState>;

      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        projectId: PROJECT_B, // Moving to different project
        title: 'Task moving between projects',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const projectA = updatedState[PROJECT_FEATURE_NAME]?.entities[PROJECT_A] as Project;

      // Order should be preserved after removal
      expect(projectA.taskIds).toEqual(['first', 'last']);
    });
  });

  describe('TODAY_TAG.taskIds sync on task dueDay change', () => {
    const TODAY_STR = '2025-12-31';
    const TOMORROW_STR = '2026-01-01';
    const YESTERDAY_STR = '2025-12-30';

    beforeEach(() => {
      // Mock date to control what getDbDateStr returns
      jasmine.clock().install();
      jasmine.clock().mockDate(new Date('2025-12-31T12:00:00'));
    });

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

    const createStateWithTodayTag = (
      taskDueDay: string | undefined,
      todayTagTaskIds: string[],
    ): Partial<RootState> =>
      ({
        [TASK_FEATURE_NAME]: {
          ids: [TASK_ID],
          entities: {
            [TASK_ID]: createMockTask({ dueDay: taskDueDay }),
          },
          currentTaskId: null,
          selectedTaskId: null,
          taskDetailTargetPanel: null,
          isDataLoaded: true,
          lastCurrentTaskId: null,
        },
        [PROJECT_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
        [TAG_FEATURE_NAME]: {
          ids: [TODAY_TAG.id],
          entities: {
            [TODAY_TAG.id]: createMockTag({
              id: TODAY_TAG.id,
              title: TODAY_TAG.title,
              taskIds: todayTagTaskIds,
            }),
          },
        },
        [appStateFeatureKey]: {
          todayStr: TODAY_STR,
          startOfNextDayDiffMs: 0,
        },
      }) as Partial<RootState>;

    it('should add task to TODAY_TAG.taskIds when LWW Update recreates task with dueDay = today', () => {
      // Task was deleted locally but UPDATE wins with dueDay = today
      const state = {
        [TASK_FEATURE_NAME]: {
          ids: [], // Task doesn't exist
          entities: {},
          currentTaskId: null,
          selectedTaskId: null,
          taskDetailTargetPanel: null,
          isDataLoaded: true,
          lastCurrentTaskId: null,
        },
        [PROJECT_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
        [TAG_FEATURE_NAME]: {
          ids: [TODAY_TAG.id],
          entities: {
            [TODAY_TAG.id]: createMockTag({
              id: TODAY_TAG.id,
              title: TODAY_TAG.title,
              taskIds: [],
            }),
          },
        },
        [appStateFeatureKey]: {
          todayStr: TODAY_STR,
          startOfNextDayDiffMs: 0,
        },
      } as Partial<RootState>;

      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        title: 'Recreated Task',
        dueDay: TODAY_STR,
        tagIds: [],
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      spyOn(OpLog, 'log');
      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const todayTag = updatedState[TAG_FEATURE_NAME]?.entities[TODAY_TAG.id] as Tag;

      expect(todayTag.taskIds).toContain(TASK_ID);
    });

    it('should add task to TODAY_TAG.taskIds when LWW Update changes dueDay to today', () => {
      // Task has dueDay = tomorrow, LWW update changes it to today
      const state = createStateWithTodayTag(TOMORROW_STR, []);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        dueDay: TODAY_STR,
        tagIds: [],
        title: 'Task moved to today',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const todayTag = updatedState[TAG_FEATURE_NAME]?.entities[TODAY_TAG.id] as Tag;

      expect(todayTag.taskIds).toContain(TASK_ID);
    });

    it('should remove task from TODAY_TAG.taskIds when LWW Update changes dueDay from today', () => {
      // Task has dueDay = today, LWW update changes it to tomorrow
      const state = createStateWithTodayTag(TODAY_STR, [TASK_ID]);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        dueDay: TOMORROW_STR,
        tagIds: [],
        title: 'Task moved to tomorrow',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const todayTag = updatedState[TAG_FEATURE_NAME]?.entities[TODAY_TAG.id] as Tag;

      expect(todayTag.taskIds).not.toContain(TASK_ID);
    });

    it('should not modify TODAY_TAG.taskIds when dueDay unchanged (both today)', () => {
      const state = createStateWithTodayTag(TODAY_STR, [TASK_ID, 'other-task']);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        dueDay: TODAY_STR, // Same as before
        tagIds: [],
        title: 'Updated title only',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const todayTag = updatedState[TAG_FEATURE_NAME]?.entities[TODAY_TAG.id] as Tag;

      // Should preserve order and not duplicate
      expect(todayTag.taskIds).toEqual([TASK_ID, 'other-task']);
    });

    it('should not modify TODAY_TAG.taskIds when dueDay unchanged (neither today)', () => {
      const state = createStateWithTodayTag(YESTERDAY_STR, ['other-task']);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        dueDay: TOMORROW_STR, // Neither old nor new is today
        tagIds: [],
        title: 'Updated task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const todayTag = updatedState[TAG_FEATURE_NAME]?.entities[TODAY_TAG.id] as Tag;

      expect(todayTag.taskIds).toEqual(['other-task']);
      expect(todayTag.taskIds).not.toContain(TASK_ID);
    });

    it('should handle missing TODAY_TAG gracefully', () => {
      // TODAY_TAG doesn't exist in state
      const state = {
        [TASK_FEATURE_NAME]: {
          ids: [TASK_ID],
          entities: {
            [TASK_ID]: createMockTask({ dueDay: TOMORROW_STR }),
          },
          currentTaskId: null,
          selectedTaskId: null,
          taskDetailTargetPanel: null,
          isDataLoaded: true,
          lastCurrentTaskId: null,
        },
        [PROJECT_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
        [TAG_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
        [appStateFeatureKey]: {
          todayStr: TODAY_STR,
          startOfNextDayDiffMs: 0,
        },
      } as Partial<RootState>;

      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        dueDay: TODAY_STR,
        tagIds: [],
        title: 'Task moved to today',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      // Should not throw
      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      // State should still be updated without TODAY_TAG changes
      const task = updatedState[TASK_FEATURE_NAME]?.entities[TASK_ID] as Task;
      expect(task.dueDay).toBe(TODAY_STR);
    });

    it('should not duplicate task in TODAY_TAG.taskIds if already present', () => {
      // Task already in TODAY_TAG.taskIds (edge case)
      const state = createStateWithTodayTag(TOMORROW_STR, [TASK_ID]);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        dueDay: TODAY_STR,
        tagIds: [],
        title: 'Task moved to today',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const todayTag = updatedState[TAG_FEATURE_NAME]?.entities[TODAY_TAG.id] as Tag;

      // Should still only have one instance
      expect(todayTag.taskIds.filter((id) => id === TASK_ID).length).toBe(1);
    });

    it('should preserve TODAY_TAG.taskIds order when adding task', () => {
      const existingTasks = ['existing-1', 'existing-2'];
      const state = {
        ...createStateWithTodayTag(TOMORROW_STR, existingTasks),
        [TAG_FEATURE_NAME]: {
          ids: [TODAY_TAG.id],
          entities: {
            [TODAY_TAG.id]: createMockTag({
              id: TODAY_TAG.id,
              title: TODAY_TAG.title,
              taskIds: existingTasks,
            }),
          },
        },
      } as Partial<RootState>;

      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        dueDay: TODAY_STR,
        tagIds: [],
        title: 'Task moved to today',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const todayTag = updatedState[TAG_FEATURE_NAME]?.entities[TODAY_TAG.id] as Tag;

      // Original order should be preserved, new task appended at end
      expect(todayTag.taskIds).toEqual([...existingTasks, TASK_ID]);
    });

    it('should preserve TODAY_TAG.taskIds order when removing task', () => {
      const state = {
        ...createStateWithTodayTag(TODAY_STR, ['first', TASK_ID, 'last']),
        [TAG_FEATURE_NAME]: {
          ids: [TODAY_TAG.id],
          entities: {
            [TODAY_TAG.id]: createMockTag({
              id: TODAY_TAG.id,
              title: TODAY_TAG.title,
              taskIds: ['first', TASK_ID, 'last'],
            }),
          },
        },
      } as Partial<RootState>;

      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        dueDay: TOMORROW_STR,
        tagIds: [],
        title: 'Task moved away from today',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const todayTag = updatedState[TAG_FEATURE_NAME]?.entities[TODAY_TAG.id] as Tag;

      // Order should be preserved after removal
      expect(todayTag.taskIds).toEqual(['first', 'last']);
    });

    it('should add task to TODAY_TAG.taskIds when dueDay changes from undefined to today', () => {
      const state = createStateWithTodayTag(undefined, []);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        dueDay: TODAY_STR,
        tagIds: [],
        title: 'Task scheduled for today',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const todayTag = updatedState[TAG_FEATURE_NAME]?.entities[TODAY_TAG.id] as Tag;

      expect(todayTag.taskIds).toContain(TASK_ID);
    });

    it('should remove task from TODAY_TAG.taskIds when dueDay changes from today to undefined', () => {
      const state = createStateWithTodayTag(TODAY_STR, [TASK_ID]);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        dueDay: undefined,
        tagIds: [],
        title: 'Task unscheduled',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const todayTag = updatedState[TAG_FEATURE_NAME]?.entities[TODAY_TAG.id] as Tag;

      expect(todayTag.taskIds).not.toContain(TASK_ID);
    });

    it('should preserve TODAY_TAG.taskIds when due fields are omitted from partial LWW payload', () => {
      const state = createStateWithTodayTag(TODAY_STR, [TASK_ID]);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        title: 'Updated Task Title Only',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const todayTag = updatedState[TAG_FEATURE_NAME]?.entities[TODAY_TAG.id] as Tag;

      expect(todayTag.taskIds).toContain(TASK_ID);
    });
  });

  describe('TODAY_TAG.taskIds sync on task dueWithTime change', () => {
    const TODAY_STR = '2025-12-31';
    const TODAY_TIMESTAMP = new Date(2025, 11, 31, 14, 0, 0).getTime();
    const TOMORROW_TIMESTAMP = new Date(2026, 0, 1, 14, 0, 0).getTime();

    beforeEach(() => {
      jasmine.clock().install();
      jasmine.clock().mockDate(new Date('2025-12-31T12:00:00'));
    });

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

    const createStateWithDueWithTime = (
      taskDueDay: string | undefined,
      taskDueWithTime: number | undefined,
      todayTagTaskIds: string[],
    ): Partial<RootState> =>
      ({
        [TASK_FEATURE_NAME]: {
          ids: [TASK_ID],
          entities: {
            [TASK_ID]: createMockTask({
              dueDay: taskDueDay,
              dueWithTime: taskDueWithTime,
            }),
          },
          currentTaskId: null,
          selectedTaskId: null,
          taskDetailTargetPanel: null,
          isDataLoaded: true,
          lastCurrentTaskId: null,
        },
        [PROJECT_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
        [TAG_FEATURE_NAME]: {
          ids: [TODAY_TAG.id],
          entities: {
            [TODAY_TAG.id]: createMockTag({
              id: TODAY_TAG.id,
              title: TODAY_TAG.title,
              taskIds: todayTagTaskIds,
            }),
          },
        },
        [appStateFeatureKey]: {
          todayStr: TODAY_STR,
          startOfNextDayDiffMs: 0,
        },
      }) as Partial<RootState>;

    it('should add task to TODAY_TAG when dueWithTime changes from null to today', () => {
      const state = createStateWithDueWithTime(undefined, undefined, []);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        dueWithTime: TODAY_TIMESTAMP,
        tagIds: [],
        title: 'Task scheduled for today',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const todayTag = updatedState[TAG_FEATURE_NAME]?.entities[TODAY_TAG.id] as Tag;

      expect(todayTag.taskIds).toContain(TASK_ID);
    });

    it('should remove task from TODAY_TAG when dueWithTime changes from today to tomorrow', () => {
      const state = createStateWithDueWithTime(undefined, TODAY_TIMESTAMP, [TASK_ID]);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        dueWithTime: TOMORROW_TIMESTAMP,
        tagIds: [],
        title: 'Task moved to tomorrow',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const todayTag = updatedState[TAG_FEATURE_NAME]?.entities[TODAY_TAG.id] as Tag;

      expect(todayTag.taskIds).not.toContain(TASK_ID);
    });

    it('should not modify TODAY_TAG when dueWithTime unchanged (both today)', () => {
      const state = createStateWithDueWithTime(undefined, TODAY_TIMESTAMP, [
        TASK_ID,
        'other-task',
      ]);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        dueWithTime: TODAY_TIMESTAMP,
        tagIds: [],
        title: 'Updated title only',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const todayTag = updatedState[TAG_FEATURE_NAME]?.entities[TODAY_TAG.id] as Tag;

      expect(todayTag.taskIds).toEqual([TASK_ID, 'other-task']);
    });

    it('should preserve TODAY_TAG.taskIds when dueWithTime is omitted from partial LWW payload', () => {
      const state = createStateWithDueWithTime(undefined, TODAY_TIMESTAMP, [TASK_ID]);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        title: 'Updated Task Title Only',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const todayTag = updatedState[TAG_FEATURE_NAME]?.entities[TODAY_TAG.id] as Tag;

      expect(todayTag.taskIds).toContain(TASK_ID);
    });

    it('should use dueWithTime over dueDay when both present (mutual exclusivity)', () => {
      // Task has dueDay = today, LWW update sets dueWithTime to tomorrow
      // dueWithTime should take precedence — task should be removed from today
      const state = createStateWithDueWithTime(TODAY_STR, undefined, [TASK_ID]);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        dueDay: TODAY_STR,
        dueWithTime: TOMORROW_TIMESTAMP,
        tagIds: [],
        title: 'Task with conflicting due fields',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const todayTag = updatedState[TAG_FEATURE_NAME]?.entities[TODAY_TAG.id] as Tag;

      // dueWithTime = tomorrow takes precedence, so task should NOT be in today
      // Note: isDueToday uses OR — dueDay === todayStr || dueWithTime is today
      // So with dueDay = today, the task IS still considered "due today"
      // This test documents the actual behavior of the OR logic
      expect(todayTag.taskIds).toContain(TASK_ID);
    });

    it('should add task to TODAY_TAG when dueWithTime is today even if dueDay is undefined', () => {
      const state = createStateWithDueWithTime(undefined, TOMORROW_TIMESTAMP, []);
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        dueDay: undefined,
        dueWithTime: TODAY_TIMESTAMP,
        tagIds: [],
        title: 'Task rescheduled to today',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const todayTag = updatedState[TAG_FEATURE_NAME]?.entities[TODAY_TAG.id] as Tag;

      expect(todayTag.taskIds).toContain(TASK_ID);
    });
  });

  describe('parent.subTaskIds sync on task parentId change', () => {
    const PARENT_A = 'parent-a';
    const PARENT_B = 'parent-b';
    const SUBTASK_ID = 'subtask-1';

    const createStateWithParents = (
      subtaskParentId: string | undefined,
      parentASubTaskIds: string[],
      parentBSubTaskIds: string[],
    ): Partial<RootState> =>
      ({
        [TASK_FEATURE_NAME]: {
          ids: [SUBTASK_ID, PARENT_A, PARENT_B],
          entities: {
            [SUBTASK_ID]: createMockTask({
              id: SUBTASK_ID,
              parentId: subtaskParentId,
              subTaskIds: [],
            }),
            [PARENT_A]: createMockTask({
              id: PARENT_A,
              parentId: undefined,
              subTaskIds: parentASubTaskIds,
            }),
            [PARENT_B]: createMockTask({
              id: PARENT_B,
              parentId: undefined,
              subTaskIds: parentBSubTaskIds,
            }),
          },
          currentTaskId: null,
          selectedTaskId: null,
          taskDetailTargetPanel: null,
          isDataLoaded: true,
          lastCurrentTaskId: null,
        },
        [PROJECT_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
        [TAG_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
      }) as Partial<RootState>;

    it('should move subtask from old parent to new parent when parentId changes', () => {
      // Subtask is under Parent A, LWW update moves it to Parent B
      const state = createStateWithParents(PARENT_A, [SUBTASK_ID], []);
      const action = {
        type: '[TASK] LWW Update',
        id: SUBTASK_ID,
        parentId: PARENT_B,
        title: 'Updated Subtask',
        meta: { isPersistent: true, entityType: 'TASK', entityId: SUBTASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const parentA = updatedState[TASK_FEATURE_NAME]?.entities[PARENT_A] as Task;
      const parentB = updatedState[TASK_FEATURE_NAME]?.entities[PARENT_B] as Task;

      expect(parentA.subTaskIds).not.toContain(SUBTASK_ID);
      expect(parentB.subTaskIds).toContain(SUBTASK_ID);
    });

    it('should add subtask to parent.subTaskIds when task becomes subtask', () => {
      // Task is top-level, LWW update makes it a subtask of Parent A
      const state = createStateWithParents(undefined, [], []);
      const action = {
        type: '[TASK] LWW Update',
        id: SUBTASK_ID,
        parentId: PARENT_A,
        title: 'Now a subtask',
        meta: { isPersistent: true, entityType: 'TASK', entityId: SUBTASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const parentA = updatedState[TASK_FEATURE_NAME]?.entities[PARENT_A] as Task;

      expect(parentA.subTaskIds).toContain(SUBTASK_ID);
    });

    it('should remove subtask from parent.subTaskIds when parentId is removed', () => {
      // Subtask is under Parent A, LWW update makes it a top-level task
      const state = createStateWithParents(PARENT_A, [SUBTASK_ID], []);
      const action = {
        type: '[TASK] LWW Update',
        id: SUBTASK_ID,
        parentId: null,
        title: 'Now a top-level task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: SUBTASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const parentA = updatedState[TASK_FEATURE_NAME]?.entities[PARENT_A] as Task;

      expect(parentA.subTaskIds).not.toContain(SUBTASK_ID);
    });

    it('should not duplicate subtask in parent.subTaskIds if already present', () => {
      // Subtask already in Parent B's subTaskIds (edge case)
      const state = createStateWithParents(PARENT_A, [SUBTASK_ID], [SUBTASK_ID]);
      const action = {
        type: '[TASK] LWW Update',
        id: SUBTASK_ID,
        parentId: PARENT_B,
        title: 'Updated Subtask',
        meta: { isPersistent: true, entityType: 'TASK', entityId: SUBTASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const parentB = updatedState[TASK_FEATURE_NAME]?.entities[PARENT_B] as Task;

      // Should still only have one instance
      expect(parentB.subTaskIds.filter((id) => id === SUBTASK_ID).length).toBe(1);
    });

    it('should not update parent.subTaskIds when parentId does not change', () => {
      const state = createStateWithParents(PARENT_A, [SUBTASK_ID], []);
      const action = {
        type: '[TASK] LWW Update',
        id: SUBTASK_ID,
        parentId: PARENT_A, // Same as existing
        title: 'Updated Title Only',
        meta: { isPersistent: true, entityType: 'TASK', entityId: SUBTASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const parentA = updatedState[TASK_FEATURE_NAME]?.entities[PARENT_A] as Task;
      const parentB = updatedState[TASK_FEATURE_NAME]?.entities[PARENT_B] as Task;

      // Parent A should still have the subtask
      expect(parentA.subTaskIds).toContain(SUBTASK_ID);
      // Parent B should still be empty
      expect(parentB.subTaskIds).not.toContain(SUBTASK_ID);
    });

    it('should preserve parent.subTaskIds when parentId is omitted from partial LWW payload', () => {
      const state = createStateWithParents(PARENT_A, [SUBTASK_ID], []);
      const action = {
        type: '[TASK] LWW Update',
        id: SUBTASK_ID,
        title: 'Updated Subtask Title Only',
        meta: { isPersistent: true, entityType: 'TASK', entityId: SUBTASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const parentA = updatedState[TASK_FEATURE_NAME]?.entities[PARENT_A] as Task;

      expect(parentA.subTaskIds).toContain(SUBTASK_ID);
    });

    it('should handle parentId change when old parent does not exist', () => {
      // Old parent was deleted, new parent exists
      const state = {
        [TASK_FEATURE_NAME]: {
          ids: [SUBTASK_ID, PARENT_B],
          entities: {
            [SUBTASK_ID]: createMockTask({
              id: SUBTASK_ID,
              parentId: 'deleted-parent',
              subTaskIds: [],
            }),
            [PARENT_B]: createMockTask({
              id: PARENT_B,
              parentId: undefined,
              subTaskIds: [],
            }),
          },
          currentTaskId: null,
          selectedTaskId: null,
          taskDetailTargetPanel: null,
          isDataLoaded: true,
          lastCurrentTaskId: null,
        },
        [PROJECT_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
        [TAG_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
      } as Partial<RootState>;

      const action = {
        type: '[TASK] LWW Update',
        id: SUBTASK_ID,
        parentId: PARENT_B,
        title: 'Updated Subtask',
        meta: { isPersistent: true, entityType: 'TASK', entityId: SUBTASK_ID },
      };

      // Should not throw
      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const parentB = updatedState[TASK_FEATURE_NAME]?.entities[PARENT_B] as Task;

      expect(parentB.subTaskIds).toContain(SUBTASK_ID);
    });

    it('should handle parentId change when new parent does not exist', () => {
      // New parent doesn't exist (edge case)
      const state = createStateWithParents(PARENT_A, [SUBTASK_ID], []);
      const action = {
        type: '[TASK] LWW Update',
        id: SUBTASK_ID,
        parentId: 'non-existent-parent',
        title: 'Updated Subtask',
        meta: { isPersistent: true, entityType: 'TASK', entityId: SUBTASK_ID },
      };

      // Should not throw
      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const parentA = updatedState[TASK_FEATURE_NAME]?.entities[PARENT_A] as Task;

      // Old parent should have subtask removed
      expect(parentA.subTaskIds).not.toContain(SUBTASK_ID);
    });

    it('should preserve parent.subTaskIds order when adding subtask', () => {
      const existingSubtasks = ['sub-a', 'sub-b', 'sub-c'];
      const state = {
        [TASK_FEATURE_NAME]: {
          ids: [SUBTASK_ID, PARENT_A],
          entities: {
            [SUBTASK_ID]: createMockTask({
              id: SUBTASK_ID,
              parentId: undefined,
              subTaskIds: [],
            }),
            [PARENT_A]: createMockTask({
              id: PARENT_A,
              parentId: undefined,
              subTaskIds: existingSubtasks,
            }),
          },
          currentTaskId: null,
          selectedTaskId: null,
          taskDetailTargetPanel: null,
          isDataLoaded: true,
          lastCurrentTaskId: null,
        },
        [PROJECT_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
        [TAG_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
      } as Partial<RootState>;

      const action = {
        type: '[TASK] LWW Update',
        id: SUBTASK_ID,
        parentId: PARENT_A,
        title: 'Becoming a subtask',
        meta: { isPersistent: true, entityType: 'TASK', entityId: SUBTASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const parentA = updatedState[TASK_FEATURE_NAME]?.entities[PARENT_A] as Task;

      // Original order should be preserved, new subtask appended at end
      expect(parentA.subTaskIds).toEqual([...existingSubtasks, SUBTASK_ID]);
    });

    it('should preserve parent.subTaskIds order when removing subtask', () => {
      const state = {
        [TASK_FEATURE_NAME]: {
          ids: [SUBTASK_ID, PARENT_A],
          entities: {
            [SUBTASK_ID]: createMockTask({
              id: SUBTASK_ID,
              parentId: PARENT_A,
              subTaskIds: [],
            }),
            [PARENT_A]: createMockTask({
              id: PARENT_A,
              parentId: undefined,
              subTaskIds: ['first', SUBTASK_ID, 'last'],
            }),
          },
          currentTaskId: null,
          selectedTaskId: null,
          taskDetailTargetPanel: null,
          isDataLoaded: true,
          lastCurrentTaskId: null,
        },
        [PROJECT_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
        [TAG_FEATURE_NAME]: {
          ids: [],
          entities: {},
        },
      } as Partial<RootState>;

      const action = {
        type: '[TASK] LWW Update',
        id: SUBTASK_ID,
        parentId: null, // Becoming top-level
        title: 'No longer a subtask',
        meta: { isPersistent: true, entityType: 'TASK', entityId: SUBTASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const parentA = updatedState[TASK_FEATURE_NAME]?.entities[PARENT_A] as Task;

      // Order should be preserved after removal
      expect(parentA.subTaskIds).toEqual(['first', 'last']);
    });
  });

  describe('dueDay/deadlineDay sanitization (#6908)', () => {
    it('should clear invalid dueDay string from LWW task update', () => {
      const state = createMockState();
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        dueDay: '-/-/2026',
        title: 'Updated Task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      // Prevent devError from throwing (it calls alert + confirm -> throws if true)
      if (!jasmine.isSpy(window.alert)) {
        spyOn(window, 'alert');
      }
      if (!jasmine.isSpy(window.confirm)) {
        spyOn(window, 'confirm').and.returnValue(false);
      } else {
        (window.confirm as jasmine.Spy).and.returnValue(false);
      }

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const updatedTask = updatedState[TASK_FEATURE_NAME]?.entities[TASK_ID] as Task;
      expect(updatedTask.dueDay).toBeUndefined();
    });

    it('should preserve valid dueDay string in LWW task update', () => {
      const state = createMockState();
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        dueDay: '2026-03-21',
        title: 'Updated Task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const updatedTask = updatedState[TASK_FEATURE_NAME]?.entities[TASK_ID] as Task;
      expect(updatedTask.dueDay).toBe('2026-03-21');
    });

    it('should clear invalid deadlineDay string from LWW task update', () => {
      const state = createMockState();
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        deadlineDay: '3/14/2026',
        title: 'Updated Task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      // Prevent devError from throwing (it calls alert + confirm -> throws if true)
      if (!jasmine.isSpy(window.alert)) {
        spyOn(window, 'alert');
      }
      if (!jasmine.isSpy(window.confirm)) {
        spyOn(window, 'confirm').and.returnValue(false);
      } else {
        (window.confirm as jasmine.Spy).and.returnValue(false);
      }

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const updatedTask = updatedState[TASK_FEATURE_NAME]?.entities[TASK_ID] as Task;
      expect(updatedTask.deadlineDay).toBeUndefined();
    });

    it('should pass through null dueDay without validation', () => {
      const state = createMockState();
      const action = {
        type: '[TASK] LWW Update',
        id: TASK_ID,
        dueDay: null,
        title: 'Updated Task',
        meta: { isPersistent: true, entityType: 'TASK', entityId: TASK_ID },
      };

      reducer(state, action);

      const updatedState = mockReducer.calls.mostRecent().args[0] as Partial<RootState>;
      const updatedTask = updatedState[TASK_FEATURE_NAME]?.entities[TASK_ID] as Task;
      expect(updatedTask.dueDay).toBeNull();
    });
  });
});
