import { createReducer, on } from '@ngrx/store';
import * as a from './focus-mode.actions';
import {
  FocusMainUIState,
  FocusModeMode,
  FocusModeState,
  FocusScreen,
  TimerState,
  FOCUS_MODE_DEFAULTS,
} from '../focus-mode.model';
import { LS } from '../../../core/persistence/storage-keys.const';

export const FOCUS_MODE_FEATURE_KEY = 'focusMode';

const focusModeModeFromLS = localStorage.getItem(LS.FOCUS_MODE_MODE);

const createIdleTimer = (): TimerState => ({
  isRunning: false,
  startedAt: null,
  elapsed: 0,
  duration: 0,
  purpose: null,
});

export const initialState: FocusModeState = {
  timer: createIdleTimer(),
  currentScreen: FocusScreen.Main,
  mainState: FocusMainUIState.Preparation,
  isOverlayShown: false,
  mode: Object.values(FocusModeMode).includes(focusModeModeFromLS as any)
    ? (focusModeModeFromLS as FocusModeMode)
    : FocusModeMode.Countdown,
  currentCycle: 1,
  lastCompletedDuration: 0,
  pausedTaskId: null,
  _isResumingBreak: false,
  _isOvertimeEnabled: false,
};

const createWorkTimer = (duration: number): TimerState => ({
  isRunning: true,
  startedAt: Date.now(),
  elapsed: 0,
  duration,
  purpose: 'work',
});

const createBreakTimer = (duration: number, isLong = false): TimerState => ({
  isRunning: true,
  startedAt: Date.now(),
  elapsed: 0,
  duration,
  purpose: 'break',
  isLongBreak: isLong,
});

const updateTimer = (timer: TimerState): TimerState => {
  if (!timer.isRunning || !timer.startedAt) {
    return timer;
  }

  const now = Date.now();
  const elapsed = now - timer.startedAt;
  return { ...timer, elapsed };
};

export const focusModeReducer = createReducer(
  initialState,

  // Mode changes
  on(a.setFocusModeMode, (state, { mode }) => ({
    ...state,
    mode,
    timer:
      mode === FocusModeMode.Flowtime
        ? {
            ...state.timer,
            duration: 0,
          }
        : state.timer,
    _isOvertimeEnabled:
      mode === FocusModeMode.Flowtime ? false : state._isOvertimeEnabled,
  })),

  // Overlay control
  on(a.showFocusOverlay, (state) => ({
    ...state,
    isOverlayShown: true,
  })),

  on(a.hideFocusOverlay, (state) => ({
    ...state,
    isOverlayShown: false,
  })),

  // Screen navigation
  on(a.selectFocusTask, (state) => ({
    ...state,
    currentScreen: FocusScreen.Main,
    mainState: FocusMainUIState.Preparation,
  })),

  on(a.selectFocusDuration, (state) => ({
    ...state,
    currentScreen: FocusScreen.Main,
    mainState: FocusMainUIState.Preparation,
  })),

  on(a.startFocusPreparation, (state) => ({
    ...state,
    currentScreen: FocusScreen.Main,
    mainState: FocusMainUIState.Countdown,
  })),

  on(a.navigateToMainScreen, (state) => ({
    ...state,
    currentScreen: FocusScreen.Main,
    mainState: FocusMainUIState.Preparation,
  })),

  on(a.startFocusSession, (state, { duration }) => {
    // important to use 0 for flowtime
    const timer = createWorkTimer(duration ?? FOCUS_MODE_DEFAULTS.SESSION_DURATION);
    return {
      ...state,
      timer,
      currentScreen: FocusScreen.Main,
      mainState: FocusMainUIState.InProgress,
    };
  }),

  on(a.pauseFocusSession, (state, { pausedTaskId }) => {
    // Allow pausing both work sessions and breaks
    if (state.timer.purpose === null) return state;
    const timer = updateTimer(state.timer);

    return {
      ...state,
      timer: {
        ...timer,
        isRunning: false,
      },
      // Store paused task ID if provided (for sync feature)
      pausedTaskId: pausedTaskId ?? state.pausedTaskId,
    };
  }),

  on(a.unPauseFocusSession, (state) => {
    // Allow resuming both work sessions and breaks
    if (state.timer.purpose === null) return state;

    return {
      ...state,
      timer: {
        ...state.timer,
        isRunning: true,
        startedAt: Date.now() - state.timer.elapsed,
      },
      // Restore InProgress state (fixes UI showing preparation instead of active session)
      mainState: FocusMainUIState.InProgress,
      currentScreen:
        state.timer.purpose === 'work' ? FocusScreen.Main : FocusScreen.Break,
      // Set flag ONLY when resuming a break (not work sessions)
      _isResumingBreak: state.timer.purpose === 'break',
    };
  }),

  on(a.clearResumingBreakFlag, (state) => ({
    ...state,
    _isResumingBreak: false,
  })),

  on(a.completeFocusSession, (state, { completedDuration }) => {
    const duration = completedDuration ?? state.timer.elapsed;

    return {
      ...state,
      timer: createIdleTimer(),
      currentScreen: FocusScreen.SessionDone,
      mainState: FocusMainUIState.Preparation,
      lastCompletedDuration: duration,
      _isOvertimeEnabled: false,
    };
  }),

  on(a.cancelFocusSession, (state) => ({
    ...state,
    timer: createIdleTimer(),
    currentScreen: FocusScreen.Main,
    mainState: FocusMainUIState.Preparation,
    isOverlayShown: false,
    pausedTaskId: null,
    _isOvertimeEnabled: false,
  })),

  // Flowtime end-of-session: store elapsed duration and pause the timer. The
  // autoStartFlowtimeBreakOnSessionEnd$ effect then dispatches
  // completeFocusSession + startBreak (or just completeFocusSession when no
  // break is due). lastCompletedDuration is kept populated so logFocusSession$
  // can log the session via completeFocusSession.
  on(a.endFlowtimeSession, (state, { pausedTaskId }) => {
    if (state.timer.purpose !== 'work') return state;
    return {
      ...state,
      timer: { ...state.timer, isRunning: false },
      pausedTaskId: pausedTaskId ?? state.pausedTaskId,
      lastCompletedDuration: state.timer.elapsed,
    };
  }),

  on(a.setOvertimeEnabled, (state, { enabled }) => ({
    ...state,
    _isOvertimeEnabled: enabled,
  })),

  // Break handling
  on(a.startBreak, (state, { duration, isLongBreak, pausedTaskId }) => {
    const timer = createBreakTimer(
      duration || FOCUS_MODE_DEFAULTS.SHORT_BREAK_DURATION,
      isLongBreak || false,
    );

    return {
      ...state,
      timer,
      currentScreen: FocusScreen.Break,
      mainState: FocusMainUIState.Preparation,
      pausedTaskId: pausedTaskId ?? state.pausedTaskId,
    };
  }),

  on(a.skipBreak, a.completeBreak, (state) => ({
    ...state,
    timer: createIdleTimer(),
    currentScreen: FocusScreen.Main,
    mainState: FocusMainUIState.Preparation,
    pausedTaskId: null,
  })),

  // Timer updates - much simpler!
  on(a.tick, (state) => {
    if (!state.timer.isRunning || !state.timer.purpose) {
      return state;
    }

    const updatedTimer = updateTimer(state.timer);

    // Check if timer completed - mark for completion but let effects handle the flow.
    if (updatedTimer.elapsed >= updatedTimer.duration) {
      if (updatedTimer.purpose === 'work') {
        // Flowtime work sessions have no fixed duration and never auto-complete via tick.
        if (state.mode === FocusModeMode.Flowtime) {
          return { ...state, timer: updatedTimer };
        }
        // When overtime is enabled, keep the timer running past duration
        if (state._isOvertimeEnabled) {
          return { ...state, timer: updatedTimer };
        }
        // Work session completed - stop timer and mark duration, but don't change screen yet
        return {
          ...state,
          timer: { ...updatedTimer, isRunning: false }, // Stop the timer but preserve state
          lastCompletedDuration: updatedTimer.elapsed,
        };
      } else if (updatedTimer.purpose === 'break') {
        // Break completed - stop timer but stay on break screen for user confirmation
        return {
          ...state,
          timer: { ...updatedTimer, isRunning: false },
        };
      }
    }

    // Just update the timer
    return {
      ...state,
      timer: updatedTimer,
    };
  }),

  // Duration setting - don't set duration for Flowtime sessions
  on(a.setFocusSessionDuration, (state, { focusSessionDuration }) => {
    // Prevent setting duration for Flowtime mode to ensure it runs forever
    if (state.mode === FocusModeMode.Flowtime) {
      return state;
    }
    return {
      ...state,
      timer: {
        ...state.timer,
        duration: focusSessionDuration,
      },
    };
  }),

  // Cycle management
  on(a.incrementCycle, (state) => ({
    ...state,
    currentCycle: state.currentCycle + 1,
  })),

  on(a.resetCycles, (state) => ({
    ...state,
    currentCycle: 1,
  })),

  // Store pausedTaskId without pausing the session
  on(a.setPausedTaskId, (state, { pausedTaskId }) => ({
    ...state,
    pausedTaskId,
  })),

  // Adjust remaining time by modifying goal duration (work sessions only)
  on(a.adjustRemainingTime, (state, { amountMs }) => {
    if (state.timer.purpose !== 'work') {
      return state;
    }

    if (state.mode === FocusModeMode.Flowtime) {
      return state;
    }

    const currentDuration = state.timer.duration;
    if (typeof currentDuration !== 'number') {
      return state;
    }

    const updatedDuration = Math.max(0, currentDuration + amountMs);

    if (updatedDuration === currentDuration) {
      return state;
    }

    return {
      ...state,
      timer: {
        ...state.timer,
        duration: updatedDuration,
      },
    };
  }),

  // Re-adopt a focus session that kept running in the native Android
  // foreground service after the app was swiped from recents (#7855).
  // The native service is the source of truth: `startedAt` is reconstructed as
  // an absolute timestamp so the existing tick reducer keeps counting from the
  // correct point. For Flowtime (duration 0) `remainingMs` carries elapsed.
  on(
    a.restoreFocusSessionFromNative,
    (state, { durationMs, remainingMs, isBreak, isPaused }) => {
      const purpose: TimerState['purpose'] = isBreak ? 'break' : 'work';
      const isFlowtime = durationMs <= 0;
      const elapsed = isFlowtime
        ? Math.max(0, remainingMs)
        : Math.max(0, durationMs - remainingMs);
      const timer: TimerState = {
        isRunning: !isPaused,
        startedAt: Date.now() - elapsed,
        elapsed,
        duration: isFlowtime ? 0 : durationMs,
        purpose,
        // The native service only tracks `isBreak`, not long-vs-short, so a
        // recovered break renders as a short break (cosmetic: notification
        // title). Not round-tripped by design.
        ...(isBreak ? { isLongBreak: false } : {}),
      };

      // Derive `mode` from the native duration rather than trusting the
      // localStorage-seeded `state.mode`. A duration-0 session is Flowtime;
      // without forcing this, a stale `state.mode` would make the tick reducer
      // auto-complete the (durationless) session on its first tick. For a
      // fixed-duration session, only guard against the inverse desync — never
      // leave it as Flowtime (which would never auto-complete) — while
      // preserving the Pomodoro-vs-Countdown distinction localStorage holds.
      const mode = isFlowtime
        ? FocusModeMode.Flowtime
        : state.mode === FocusModeMode.Flowtime
          ? FocusModeMode.Countdown
          : state.mode;

      return {
        ...state,
        timer,
        mode,
        // `isOverlayShown` is intentionally left untouched (not forced true):
        // recovery should be non-intrusive — the header focus button shows the
        // running timer; we don't pop the full-screen overlay on app reopen.
        currentScreen: isBreak ? FocusScreen.Break : FocusScreen.Main,
        mainState: FocusMainUIState.InProgress,
      };
    },
  ),
);

// For backward compatibility, export the old State interface name
export type State = FocusModeState;
