/**
 * Live Workout State Model — Path A Companion
 *
 * This is the SINGLE SOURCE OF TRUTH for the workout session.
 * It lives on the device (Sense 2) and is mirrored to the backend.
 *
 * States: IDLE → LOADING → ACTIVE → RESTING → DONE → SYNCING → COMPLETE
 * Offline sub-states handled via pendingEvents queue.
 */

// ─── Types ───────────────────────────────────────────────────────────

/** Every set in the workout — targets come from Hevy routine, actuals from user input */
export interface WorkoutSet {
  /** 0-based index within exercise */
  index: number;
  /** Target weight in kg (from Hevy routine) */
  targetWeightKg: number | null;
  /** Target rep count or rep range (displayed as "8" or "8–12") */
  targetReps: string;
  /** Target RIR (Reps In Reserve), 0–5, null if not specified */
  targetRir: number | null;
  /** Actual weight logged by user (can differ from target) */
  actualWeightKg: number | null;
  /** Actual reps logged by user */
  actualReps: number | null;
  /** Actual RIR */
  actualRir: number | null;
  /** Whether the user skipped this set */
  skipped: boolean;
  /** ISO timestamp when set was logged */
  completedAt: string | null;
  /** Whether backend has acknowledged this set */
  serverAcked: boolean;
}

export interface Exercise {
  /** Hevy exercise_template_id */
  hevyTemplateId: string;
  /** Display name from Hevy */
  name: string;
  /** Target sets from routine */
  sets: WorkoutSet[];
  /** Notes from routine */
  notes: string | null;
  /** Exercise type: strength, cardio, etc. */
  type: 'strength' | 'cardio' | 'other';
}

/** Latest workout plan from backend */
export interface WorkoutPlan {
  routineId: string;
  routineName: string;
  exercises: Exercise[];
  fetchedAt: string;
}

// ─── State Machine ───────────────────────────────────────────────────

export type WorkoutState =
  | 'IDLE'
  | 'LOADING'
  | 'ACTIVE'
  | 'RESTING'
  | 'DONE'
  | 'SYNCING'
  | 'COMPLETE';

export interface RestingSubState {
  /** Seconds remaining */
  remainingSec: number;
  /** Configured rest duration in seconds */
  totalSec: number;
  /** Timer handle or null if not running */
  timerId: number | null;
}

/** Offline event queue entry */
export interface PendingEvent {
  id: string;
  type: 'SET_COMPLETE' | 'SKIP_SET' | 'SKIP_EXERCISE' | 'SUBSTITUTE_EXERCISE' | 'NOTES_UPDATE' | 'FINISH_WORKOUT';
  payload: Record<string, unknown>;
  createdAt: string;
  retries: number;
}

/** Root live workout state */
export interface LiveWorkoutState {
  // ── FSM ──
  state: WorkoutState;
  previousState: WorkoutState | null;

  // ── Plan ──
  plan: WorkoutPlan | null;
  planLoadError: string | null;

  // ── Progress pointers ──
  /** Index into plan.exercises */
  currentExerciseIndex: number;
  /** Index into current exercise's sets[] */
  currentSetIndex: number;

  // ── Rest timer ──
  resting: RestingSubState;

  // ── Timing ──
  workoutStartedAt: string | null;
  workoutEndedAt: string | null;
  /** Total elapsed in seconds (live clock) */
  elapsedSec: number;

  // ── Hevy sync ──
  /** Hevy workout ID after successful POST */
  hevyWorkoutId: string | null;
  syncError: string | null;
  syncRetries: number;

  // ── Offline queue ──
  pendingEvents: PendingEvent[];
  isOffline: boolean;

  // ── UI hints ──
  /** Toast message shown briefly */
  toast: string | null;
  /** Rest timer as formatted string "1:30" */
  restTimerDisplay: string;

  // ── Meta ──
  version: number;
  lastUpdatedAt: string;
}

// ─── Factory ─────────────────────────────────────────────────────────

export function createInitialState(): LiveWorkoutState {
  return {
    state: 'IDLE',
    previousState: null,
    plan: null,
    planLoadError: null,
    currentExerciseIndex: 0,
    currentSetIndex: 0,
    resting: { remainingSec: 0, totalSec: 90, timerId: null },
    workoutStartedAt: null,
    workoutEndedAt: null,
    elapsedSec: 0,
    hevyWorkoutId: null,
    syncError: null,
    syncRetries: 0,
    pendingEvents: [],
    isOffline: false,
    toast: null,
    restTimerDisplay: '0:00',
    version: 1,
    lastUpdatedAt: new Date().toISOString(),
  };
}

// ─── Reducer ─────────────────────────────────────────────────────────

export type WorkoutAction =
  | { type: 'LOAD_PLAN' }
  | { type: 'PLAN_LOADED'; plan: WorkoutPlan }
  | { type: 'PLAN_LOAD_ERROR'; error: string }
  | { type: 'START_WORKOUT' }
  | { type: 'LOG_SET'; weightKg: number; reps: number; rir: number }
  | { type: 'SKIP_SET' }
  | { type: 'SKIP_EXERCISE' }
  | { type: 'PREVIOUS_EXERCISE' }
  | { type: 'ADJUST_REPS'; delta: number }
  | { type: 'ADJUST_WEIGHT'; delta: number }
  | { type: 'ADJUST_RIR'; rir: number }
  | { type: 'START_REST' }
  | { type: 'TICK_REST' }
  | { type: 'SKIP_REST' }
  | { type: 'FINISH_WORKOUT' }
  | { type: 'SYNC_START' }
  | { type: 'SYNC_SUCCESS'; hevyWorkoutId: string }
  | { type: 'SYNC_ERROR'; error: string }
  | { type: 'SYNC_RETRY' }
  | { type: 'SET_OFFLINE'; isOffline: boolean }
  | { type: 'EVENT_ACKED'; eventId: string }
  | { type: 'SHOW_TOAST'; message: string }
  | { type: 'CLEAR_TOAST' }
  | { type: 'RESET' };

export function reduce(state: LiveWorkoutState, action: WorkoutAction): LiveWorkoutState {
  const now = new Date().toISOString();

  switch (action.type) {
    case 'LOAD_PLAN':
      return { ...state, state: 'LOADING', previousState: state.state, planLoadError: null };

    case 'PLAN_LOADED':
      return {
        ...state,
        state: 'IDLE',
        previousState: state.state,
        plan: action.plan,
        planLoadError: null,
        lastUpdatedAt: now,
      };

    case 'PLAN_LOAD_ERROR':
      return {
        ...state,
        state: 'IDLE',
        previousState: state.state,
        planLoadError: action.error,
        lastUpdatedAt: now,
      };

    case 'START_WORKOUT':
      if (!state.plan) return state;
      return {
        ...state,
        state: 'ACTIVE',
        previousState: state.state,
        currentExerciseIndex: 0,
        currentSetIndex: 0,
        workoutStartedAt: now,
        elapsedSec: 0,
        lastUpdatedAt: now,
      };

    case 'LOG_SET': {
      if (!state.plan) return state;
      const exercises = [...state.plan.exercises];
      const exercise = { ...exercises[state.currentExerciseIndex] };
      const sets = [...exercise.sets];
      const set = { ...sets[state.currentSetIndex] };

      set.actualWeightKg = action.weightKg;
      set.actualReps = action.reps;
      set.actualRir = action.rir;
      set.completedAt = now;
      set.skipped = false;
      sets[state.currentSetIndex] = set;
      exercise.sets = sets;
      exercises[state.currentExerciseIndex] = exercise;

      const isLastSet = state.currentSetIndex >= exercise.sets.length - 1;
      const isLastExercise = state.currentExerciseIndex >= exercises.length - 1;

      if (isLastSet && isLastExercise) {
        // All done — auto-transition to DONE
        return {
          ...state,
          state: 'DONE',
          previousState: state.state,
          plan: { ...state.plan, exercises },
          workoutEndedAt: now,
          toast: '¡Workout completo! 💪',
          lastUpdatedAt: now,
        };
      }

      // Transition to RESTING
      return {
        ...state,
        state: 'RESTING',
        previousState: state.state,
        plan: { ...state.plan, exercises },
        resting: { remainingSec: state.resting.totalSec, totalSec: state.resting.totalSec, timerId: null },
        toast: `Set ${state.currentSetIndex + 1} logged!`,
        lastUpdatedAt: now,
      };
    }

    case 'SKIP_SET': {
      if (!state.plan) return state;
      const exercises = [...state.plan.exercises];
      const exercise = { ...exercises[state.currentExerciseIndex] };
      const sets = [...exercise.sets];
      sets[state.currentSetIndex] = { ...sets[state.currentSetIndex], skipped: true };
      exercise.sets = sets;
      exercises[state.currentExerciseIndex] = exercise;
      return advanceSet({ ...state, plan: { ...state.plan, exercises }, lastUpdatedAt: now });
    }

    case 'SKIP_EXERCISE': {
      if (!state.plan) return state;
      const nextIdx = state.currentExerciseIndex + 1;
      if (nextIdx >= state.plan.exercises.length) {
        return { ...state, state: 'DONE', previousState: state.state, workoutEndedAt: now, lastUpdatedAt: now };
      }
      return {
        ...state,
        state: 'RESTING',
        previousState: state.state,
        currentExerciseIndex: nextIdx,
        currentSetIndex: 0,
        resting: { remainingSec: state.resting.totalSec, totalSec: state.resting.totalSec, timerId: null },
        lastUpdatedAt: now,
      };
    }

    case 'PREVIOUS_EXERCISE': {
      if (state.currentExerciseIndex <= 0) return state;
      return {
        ...state,
        currentExerciseIndex: state.currentExerciseIndex - 1,
        currentSetIndex: 0,
        lastUpdatedAt: now,
      };
    }

    case 'ADJUST_REPS': {
      if (!state.plan) return state;
      const exercises = [...state.plan.exercises];
      const exercise = { ...exercises[state.currentExerciseIndex] };
      const sets = [...exercise.sets];
      const set = sets[state.currentSetIndex];
      const currentReps = set.actualReps ?? parseInt(set.targetReps, 10) || 0;
      sets[state.currentSetIndex] = { ...set, actualReps: Math.max(0, currentReps + action.delta) };
      exercise.sets = sets;
      exercises[state.currentExerciseIndex] = exercise;
      return { ...state, plan: { ...state.plan, exercises }, lastUpdatedAt: now };
    }

    case 'ADJUST_WEIGHT': {
      if (!state.plan) return state;
      const exercises = [...state.plan.exercises];
      const exercise = { ...exercises[state.currentExerciseIndex] };
      const sets = [...exercise.sets];
      const set = sets[state.currentSetIndex];
      const currentW = set.actualWeightKg ?? set.targetWeightKg ?? 0;
      sets[state.currentSetIndex] = { ...set, actualWeightKg: Math.max(0, currentW + action.delta) };
      exercise.sets = sets;
      exercises[state.currentExerciseIndex] = exercise;
      return { ...state, plan: { ...state.plan, exercises }, lastUpdatedAt: now };
    }

    case 'ADJUST_RIR': {
      if (!state.plan) return state;
      const exercises = [...state.plan.exercises];
      const exercise = { ...exercises[state.currentExerciseIndex] };
      const sets = [...exercise.sets];
      sets[state.currentSetIndex] = { ...sets[state.currentSetIndex], actualRir: Math.max(0, Math.min(5, action.rir)) };
      exercise.sets = sets;
      exercises[state.currentExerciseIndex] = exercise;
      return { ...state, plan: { ...state.plan, exercises }, lastUpdatedAt: now };
    }

    case 'START_REST':
      return {
        ...state,
        state: 'RESTING',
        previousState: state.state,
        resting: { ...state.resting, remainingSec: state.resting.totalSec },
        lastUpdatedAt: now,
      };

    case 'TICK_REST': {
      const newRemaining = Math.max(0, state.resting.remainingSec - 1);
      const mins = Math.floor(newRemaining / 60);
      const secs = newRemaining % 60;
      const display = `${mins}:${secs.toString().padStart(2, '0')}`;

      if (newRemaining <= 0) {
        return {
          ...state,
          state: 'ACTIVE',
          previousState: state.state,
          resting: { ...state.resting, remainingSec: 0 },
          restTimerDisplay: '0:00',
          lastUpdatedAt: now,
        };
      }

      return {
        ...state,
        resting: { ...state.resting, remainingSec: newRemaining },
        restTimerDisplay: display,
        lastUpdatedAt: now,
      };
    }

    case 'SKIP_REST':
      return {
        ...state,
        state: 'ACTIVE',
        previousState: state.state,
        resting: { ...state.resting, remainingSec: 0 },
        restTimerDisplay: '0:00',
        lastUpdatedAt: now,
      };

    case 'FINISH_WORKOUT':
      return {
        ...state,
        state: 'DONE',
        previousState: state.state,
        workoutEndedAt: now,
        toast: 'Saving workout...',
        lastUpdatedAt: now,
      };

    case 'SYNC_START':
      return {
        ...state,
        state: 'SYNCING',
        previousState: state.state,
        syncError: null,
        lastUpdatedAt: now,
      };

    case 'SYNC_SUCCESS':
      return {
        ...state,
        state: 'COMPLETE',
        previousState: state.state,
        hevyWorkoutId: action.hevyWorkoutId,
        syncError: null,
        syncRetries: 0,
        toast: `Synced! Hevy ID: ${action.hevyWorkoutId.slice(0, 8)}...`,
        lastUpdatedAt: now,
      };

    case 'SYNC_ERROR':
      return {
        ...state,
        state: 'DONE',
        previousState: state.state,
        syncError: action.error,
        syncRetries: state.syncRetries + 1,
        toast: `Sync failed. Retry? (${state.syncRetries + 1})`,
        lastUpdatedAt: now,
      };

    case 'SYNC_RETRY':
      return { ...state, state: 'SYNCING', previousState: state.state, syncError: null, lastUpdatedAt: now };

    case 'SET_OFFLINE':
      return { ...state, isOffline: action.isOffline, lastUpdatedAt: now };

    case 'EVENT_ACKED':
      return {
        ...state,
        pendingEvents: state.pendingEvents.filter((e) => e.id !== action.eventId),
        lastUpdatedAt: now,
      };

    case 'SHOW_TOAST':
      return { ...state, toast: action.message, lastUpdatedAt: now };

    case 'CLEAR_TOAST':
      return { ...state, toast: null, lastUpdatedAt: now };

    case 'RESET':
      return createInitialState();

    default:
      return state;
  }
}

// ─── Helpers ─────────────────────────────────────────────────────────

function advanceSet(state: LiveWorkoutState): LiveWorkoutState {
  if (!state.plan) return state;
  const exercise = state.plan.exercises[state.currentExerciseIndex];
  const nextSetIdx = state.currentSetIndex + 1;

  if (nextSetIdx >= exercise.sets.length) {
    // End of exercise → next exercise or DONE
    const nextExIdx = state.currentExerciseIndex + 1;
    if (nextExIdx >= state.plan.exercises.length) {
      return {
        ...state,
        state: 'DONE',
        previousState: state.state,
        workoutEndedAt: new Date().toISOString(),
      };
    }
    return {
      ...state,
      state: 'RESTING',
      previousState: state.state,
      currentExerciseIndex: nextExIdx,
      currentSetIndex: 0,
      resting: { remainingSec: state.resting.totalSec, totalSec: state.resting.totalSec, timerId: null },
    };
  }

  return {
    ...state,
    state: 'RESTING',
    previousState: state.state,
    currentSetIndex: nextSetIdx,
    resting: { remainingSec: state.resting.totalSec, totalSec: state.resting.totalSec, timerId: null },
  };
}

// ─── Hevy Payload Builder (mirrors what backend sends to Hevy API) ──

export interface HevyWorkoutPayload {
  workout: {
    title: string;
    description: string;
    start_time: string;
    end_time: string;
    is_private: boolean;
    exercises: HevyExercisePayload[];
  };
}

export interface HevyExercisePayload {
  exercise_template_id: string;
  notes?: string;
  sets: HevySetPayload[];
}

export interface HevySetPayload {
  type: 'normal' | 'warmup' | 'dropset' | 'failure';
  weight_kg: number;
  reps: number;
  rpe: number | null;
  duration_seconds?: number;
}

/** Build Hevy API payload from completed workout state */
export function buildHevyPayload(state: LiveWorkoutState): HevyWorkoutPayload | null {
  if (!state.plan || !state.workoutStartedAt || !state.workoutEndedAt) return null;

  const exercises: HevyExercisePayload[] = state.plan.exercises.map((ex) => ({
    exercise_template_id: ex.hevyTemplateId,
    notes: ex.notes ?? undefined,
    sets: ex.sets
      .filter((s) => !s.skipped && s.completedAt !== null)
      .map((s) => ({
        type: 'normal' as const,
        weight_kg: s.actualWeightKg ?? s.targetWeightKg ?? 0,
        reps: s.actualReps ?? parseInt(s.targetReps, 10) || 0,
        rpe: s.actualRir !== null ? 10 - s.actualRir : null,
      })),
  })).filter((ex) => ex.sets.length > 0);

  return {
    workout: {
      title: state.plan.routineName || 'Fitbit Workout',
      description: `Completed via Hevy Companion (Fitbit Sense 2) — ${new Date().toLocaleDateString()}`,
      start_time: state.workoutStartedAt,
      end_time: state.workoutEndedAt,
      is_private: false,
      exercises,
    },
  };
}
