import type { PlayerState, QueueItemRef, Track } from './playerStoreTypes';
/** Hard cap on undo/redo depth — keeps memory bounded for very long sessions. */
export const QUEUE_UNDO_MAX = 32;

export type QueueUndoSnapshot = {
  /** Thin queue refs (thin-state phase 4) — not hydrated `Track[]`, so 32
   *  snapshots of a 50k queue cost refs, not 32×50k full tracks. Rebuilt to a
   *  display `Track[]` through the resolver on restore. */
  queueItems: QueueItemRef[];
  queueIndex: number;
  /** Kept full — one resolved playing track, restored to the engine on undo. */
  currentTrack: Track | null;
  /** Seconds — captured with the snapshot (older entries may omit). */
  currentTime?: number;
  progress?: number;
  isPlaying?: boolean;
  /** Main queue panel list `scrollTop` when the snapshot was taken. */
  queueListScrollTop?: number;
  /** Canonical playback-server identity at snapshot time. Restore uses this
   *  for any ref it has to prepend (e.g. a still-playing track absent from the
   *  snapshot's queue) so a mid-restore server switch can't bind the prepended
   *  ref to the wrong server (B1/H3). Older in-memory entries may omit it;
   *  callers fall back to the live `queueServerId` in that case. */
  queueServerId?: string | null;
};

const queueUndoStack: QueueUndoSnapshot[] = [];
const queueRedoStack: QueueUndoSnapshot[] = [];

/** Test-only: clears module-scoped undo/redo stacks so each test starts clean. */
export function _resetQueueUndoStacksForTest(): void {
  queueUndoStack.length = 0;
  queueRedoStack.length = 0;
}

/** QueuePanel registers a reader so undo snapshots capture list scroll position. */
let queueListScrollTopReader: (() => number | undefined) | null = null;

export function registerQueueListScrollTopReader(reader: (() => number | undefined) | null): void {
  queueListScrollTopReader = reader;
}

function readQueueListScrollTopForUndo(): number | undefined {
  return queueListScrollTopReader?.() ?? undefined;
}

/** Set in applyQueueHistorySnapshot; QueuePanel consumes in useLayoutEffect after commit. */
let pendingQueueListScrollTop: number | undefined;

export function setPendingQueueListScrollTop(value: number): void {
  pendingQueueListScrollTop = value;
}

export function consumePendingQueueListScrollTop(): number | undefined {
  const v = pendingQueueListScrollTop;
  pendingQueueListScrollTop = undefined;
  return v;
}

export function queueUndoSnapshotFromState(s: PlayerState): QueueUndoSnapshot {
  const scrollTop = readQueueListScrollTopForUndo();
  return {
    // Thin refs straight off the canonical list — 32 snapshots cost refs, not
    // 32×50k full tracks (the undo "hidden multiplier" the thin-state plan kills).
    queueItems: [...s.queueItems],
    queueIndex: s.queueIndex,
    currentTrack: s.currentTrack ? { ...s.currentTrack } : null,
    currentTime: s.currentTime,
    progress: s.progress,
    isPlaying: s.isPlaying,
    queueServerId: s.queueServerId,
    ...(scrollTop !== undefined ? { queueListScrollTop: scrollTop } : {}),
  };
}

/**
 * Snapshot the current state onto the undo stack and clear the redo stack —
 * standard "new action invalidates redo history" behaviour. Called from every
 * mutating queue action.
 */
export function pushQueueUndoFromGetter(get: () => PlayerState): void {
  queueRedoStack.length = 0;
  queueUndoStack.push(queueUndoSnapshotFromState(get()));
  while (queueUndoStack.length > QUEUE_UNDO_MAX) queueUndoStack.shift();
}

export function popQueueUndoSnapshot(): QueueUndoSnapshot | undefined {
  return queueUndoStack.pop();
}

export function popQueueRedoSnapshot(): QueueUndoSnapshot | undefined {
  return queueRedoStack.pop();
}

/** Push a prebuilt snapshot onto the redo stack — used after an undo step. */
export function pushQueueRedoSnapshot(snap: QueueUndoSnapshot): void {
  queueRedoStack.push(snap);
  while (queueRedoStack.length > QUEUE_UNDO_MAX) queueRedoStack.shift();
}

/** Push a prebuilt snapshot onto the undo stack — used after a redo step. */
export function pushQueueUndoSnapshot(snap: QueueUndoSnapshot): void {
  queueUndoStack.push(snap);
  while (queueUndoStack.length > QUEUE_UNDO_MAX) queueUndoStack.shift();
}
