// The set of workspace files open in the surface's editor group, plus which is
// active — the VS Code "open editors" model. Sibling to
// WorkspaceFileSelectionStore (which stays the single-file selection used by the
// launcher's Viewer tab); this store owns the multi-tab surface. Its active
// editor derives the same SelectedWorkspaceFile shape, so downstream diff
// correlation and tree highlight consume one selection regardless of source.
const EMPTY_SNAPSHOT = {
    editors: [],
    activeKey: null,
    activeFile: null,
    reveal: null,
};
/**
 * Stable key for an editor. Uses NUL as the separator — legal in neither an
 * entry id nor a file path — so `entryId`/`path` can never collide.
 */
export function editorKey(entryId, path) {
    return `${entryId}\u0000${path}`;
}
/**
 * NUL-prefixed namespace for virtual-document entry ids — the same
 * collision-proofing trick as {@link editorKey}: no real workspace entry id
 * can contain NUL, so a virtual document can never alias a workspace file.
 */
const VIRTUAL_ENTRY_PREFIX = "\u0000virtual:";
/**
 * Entry id for a virtual document — an editor tab whose content is not a
 * workspace file (e.g. the session's `plan.md`, an execution artifact).
 * Virtual documents share the editor group with file tabs (same open / pin /
 * close / activate semantics); only their *content rendering* differs, via the
 * surface's `virtualDocuments` seam. `kind` names the document family
 * (`"plan"` today) so future virtual documents get distinct identities.
 */
export function virtualEntryId(kind) {
    return `${VIRTUAL_ENTRY_PREFIX}${kind}`;
}
/**
 * True when an entry id names a virtual document rather than a workspace
 * entry. Used by file-only consumers (diff correlation, tree highlight,
 * reveal) to skip work that can only apply to real files.
 */
export function isVirtualEntryId(entryId) {
    return entryId.startsWith(VIRTUAL_ENTRY_PREFIX);
}
/**
 * Framework-agnostic store for the surface's open-editor group.
 *
 * Implements the `useSyncExternalStore` contract like the sibling selection and
 * conversation stores: the context/ref carries the instance, and `getSnapshot`
 * returns a snapshot object that is replaced only inside a mutation — so it is a
 * stable reference between mutations and never triggers a render loop.
 *
 * VS Code preview-tab semantics:
 * - {@link openPreview} — single click: reuse the one preview slot (or focus an
 *   already-open editor); casual browsing stays at one tab.
 * - {@link openPinned} / {@link pin} — double-click or explicit open: the tab
 *   becomes persistent.
 * - {@link close} — remove a tab, activating a neighbor.
 * - {@link activate} — focus an already-open tab.
 */
export class WorkspaceEditorsStore {
    _snapshot = EMPTY_SNAPSHOT;
    _listeners = new Set();
    /** Monotonic reveal token — bumped on every open-with-line (see `openPreview`). */
    _revealSeq = 0;
    // -- Mutations -----------------------------------------------------------
    /**
     * Single-click open: reuse the preview slot, or focus if already open.
     *
     * When `options.line` is set, this also records a jump-to-line reveal for the
     * opened editor. The reveal nonce is bumped **every** time — even when the
     * file is already open and active — so a second search hit in the same file
     * re-scrolls the already-mounted viewer. An open without a `line` clears any
     * pending reveal.
     */
    openPreview(entryId, path, options) {
        const key = editorKey(entryId, path);
        const { editors } = this._snapshot;
        const reveal = this._makeReveal(key, options);
        if (editors.some((e) => editorKey(e.entryId, e.path) === key)) {
            // Already open — focus it (preserving pinned/preview state) and apply the
            // reveal (or clear it). Reuse the same editors array ref so a reveal-only
            // update doesn't churn the tab list.
            this._commit(editors, key, reveal);
            return;
        }
        const next = { entryId, path, preview: true };
        const previewIndex = editors.findIndex((e) => e.preview);
        const editorsNext = previewIndex >= 0
            ? editors.map((e, i) => (i === previewIndex ? next : e))
            : [...editors, next];
        this._commit(editorsNext, key, reveal);
    }
    /** Explicit open (double-click / open-to-keep): a persistent tab. */
    openPinned(entryId, path) {
        const key = editorKey(entryId, path);
        const { editors } = this._snapshot;
        const index = editors.findIndex((e) => editorKey(e.entryId, e.path) === key);
        if (index >= 0) {
            if (editors[index].preview) {
                this._commit(editors.map((e, i) => (i === index ? { ...e, preview: false } : e)), key);
            }
            else {
                this._setActive(key);
            }
            return;
        }
        this._commit([...editors, { entryId, path, preview: false }], key);
    }
    /** Pin an open editor (clear its preview flag), keeping it active. */
    pin(entryId, path) {
        const key = editorKey(entryId, path);
        const { editors } = this._snapshot;
        const index = editors.findIndex((e) => editorKey(e.entryId, e.path) === key);
        if (index < 0 || !editors[index].preview)
            return;
        this._commit(editors.map((e, i) => (i === index ? { ...e, preview: false } : e)), key);
    }
    /** Focus an already-open editor. No-op if it is not open. */
    activate(entryId, path) {
        const key = editorKey(entryId, path);
        if (!this._snapshot.editors.some((e) => editorKey(e.entryId, e.path) === key)) {
            return;
        }
        this._setActive(key);
    }
    /**
     * Close an editor. When the closed tab was active, the neighbor to its right
     * (or, failing that, its left) becomes active — matching editor conventions.
     */
    close(entryId, path) {
        const key = editorKey(entryId, path);
        const { editors, activeKey } = this._snapshot;
        const index = editors.findIndex((e) => editorKey(e.entryId, e.path) === key);
        if (index < 0)
            return;
        const editorsNext = editors.filter((_, i) => i !== index);
        let nextActiveKey = activeKey;
        if (activeKey === key) {
            const neighbor = editorsNext[index] ?? editorsNext[index - 1] ?? null;
            nextActiveKey = neighbor
                ? editorKey(neighbor.entryId, neighbor.path)
                : null;
        }
        this._commit(editorsNext, nextActiveKey);
    }
    /** Close every editor. */
    closeAll() {
        if (this._snapshot.editors.length === 0)
            return;
        this._snapshot = EMPTY_SNAPSHOT;
        this._notify();
    }
    // -- useSyncExternalStore contract ---------------------------------------
    subscribe = (listener) => {
        this._listeners.add(listener);
        return () => {
            this._listeners.delete(listener);
        };
    };
    /** Returns the current snapshot (stable ref between mutations). */
    getSnapshot = () => this._snapshot;
    // -- Internal ------------------------------------------------------------
    _setActive(key) {
        if (this._snapshot.activeKey === key)
            return;
        this._commit(this._snapshot.editors, key);
    }
    /** Build a reveal for `key` from open options, or `null` when no line given. */
    _makeReveal(key, options) {
        const line = options?.line;
        if (line == null)
            return null;
        return { key, line, nonce: ++this._revealSeq };
    }
    /**
     * Replace the snapshot. `reveal` defaults to `null`, so any mutation that does
     * not explicitly carry one clears a pending reveal (invariant: returning to a
     * tab must not re-scroll to a stale line). The `activeFile` object reference
     * is preserved when the active editor is unchanged, so a reveal-only update
     * doesn't churn downstream memoization (diff correlation, tree highlight).
     */
    _commit(editors, activeKey, reveal = null) {
        const active = activeKey != null
            ? editors.find((e) => editorKey(e.entryId, e.path) === activeKey) ?? null
            : null;
        const resolvedActiveKey = active ? activeKey : null;
        const prev = this._snapshot;
        const activeFile = active && prev.activeKey === resolvedActiveKey && prev.activeFile
            ? prev.activeFile
            : active
                ? { entryId: active.entryId, path: active.path }
                : null;
        this._snapshot = {
            editors,
            activeKey: resolvedActiveKey,
            activeFile,
            reveal,
        };
        this._notify();
    }
    _notify() {
        for (const listener of this._listeners) {
            listener();
        }
    }
}
//# sourceMappingURL=workspace-editors-store.js.map