import type { ProjectFile } from "./project"; /** Snapshot history for whole-project undo/redo. T-17. */ export class ProjectHistory { private items: ProjectFile[]; private cursor = 0; private pending = false; constructor( initial: ProjectFile, private readonly limit = 100, ) { this.items = [structuredClone(initial)]; } /** Adds one undoable snapshot unless a grouped edit is active. */ push(value: ProjectFile): void { if (this.pending) { this.replaceTop(value); return; } this.add(value); } /** Starts grouping subsequent pushes into a single operation. T-17. */ begin(): void { if (this.pending) throw new Error("History group already active"); this.items = this.items.slice(0, this.cursor + 1); this.items.push(structuredClone(this.items[this.cursor]!)); this.cursor = this.items.length - 1; if (this.items.length > this.limit) { this.items.shift(); this.cursor--; } this.pending = true; } /** Commits the final grouped snapshot as one undo step. T-17. */ commit(value?: ProjectFile): void { if (!this.pending) throw new Error("No history group active"); if (value) this.items[this.cursor] = structuredClone(value); if (JSON.stringify(this.items[this.cursor]) === JSON.stringify(this.items[this.cursor - 1])) { this.items.pop(); this.cursor--; } this.pending = false; } /** Replaces the current snapshot during a grouped drag. T-17. */ replaceTop(value: ProjectFile): void { this.items[this.cursor] = structuredClone(value); } /** Moves to the previous snapshot. */ undo(): ProjectFile { if (!this.canUndo) throw new Error("Nothing to undo"); return structuredClone(this.items[--this.cursor]!); } /** Moves to the next snapshot. */ redo(): ProjectFile { if (!this.canRedo) throw new Error("Nothing to redo"); return structuredClone(this.items[++this.cursor]!); } get canUndo(): boolean { return this.cursor > 0; } get canRedo(): boolean { return this.cursor < this.items.length - 1; } /** Returns a copy of the current snapshot. */ current(): ProjectFile { return structuredClone(this.items[this.cursor]!); } private add(value: ProjectFile) { this.items = this.items.slice(0, this.cursor + 1); this.items.push(structuredClone(value)); this.cursor = this.items.length - 1; if (this.items.length > this.limit) { this.items.shift(); this.cursor--; } } }