/**
 * Doc Persistence — Maps docIds to filesystem paths and provides debounced save/load.
 *
 * Storage layout:  {DOCS_STORAGE_DIR}/{space}/{module}/{collection}[/{itemId}].automerge
 * Example:         /data/docs/demo/notes/notebooks/abc.automerge
 */

import { resolve, dirname } from "node:path";
import { mkdir, readdir, readFile, writeFile, stat } from "node:fs/promises";
import * as Automerge from "@automerge/automerge";
import type { SyncServer } from "./sync-server";

const DOCS_DIR = process.env.DOCS_STORAGE_DIR || "/data/docs";
const SAVE_DEBOUNCE_MS = 2000;

/** Convert a docId like "demo:notes:notebooks:abc" to a filesystem path. */
export function docIdToPath(docId: string): string {
	const parts = docId.split(":");
	if (parts.length < 3) throw new Error(`Invalid docId: ${docId}`);
	// Last part becomes the filename, rest become directories
	const filename = parts.pop()! + ".automerge";
	return resolve(DOCS_DIR, ...parts, filename);
}

/** Convert a filesystem path back to a docId. */
function pathToDocId(filePath: string): string {
	const rel = filePath.slice(DOCS_DIR.length + 1); // strip leading dir + /
	const withoutExt = rel.replace(/\.automerge$/, "");
	return withoutExt.split("/").join(":");
}

// Debounce timers per docId
const saveTimers = new Map<string, ReturnType<typeof setTimeout>>();

/** Debounced save — writes Automerge binary to disk after SAVE_DEBOUNCE_MS. */
export function saveDoc(docId: string, doc: Automerge.Doc<any>): void {
	const existing = saveTimers.get(docId);
	if (existing) clearTimeout(existing);

	saveTimers.set(
		docId,
		setTimeout(async () => {
			saveTimers.delete(docId);
			try {
				const filePath = docIdToPath(docId);
				await mkdir(dirname(filePath), { recursive: true });
				const binary = Automerge.save(doc);
				await writeFile(filePath, binary);
				console.log(`[DocStore] Saved ${docId} (${binary.byteLength} bytes)`);
			} catch (e) {
				console.error(`[DocStore] Failed to save ${docId}:`, e);
			}
		}, SAVE_DEBOUNCE_MS)
	);
}

/** Recursively scan DOCS_DIR and load all .automerge files into the SyncServer. */
export async function loadAllDocs(syncServer: SyncServer): Promise<number> {
	let count = 0;
	try {
		await mkdir(DOCS_DIR, { recursive: true });
		count = await scanDir(DOCS_DIR, syncServer);
	} catch (e) {
		console.error("[DocStore] Failed to scan docs directory:", e);
	}
	console.log(`[DocStore] Loaded ${count} documents from ${DOCS_DIR}`);
	return count;
}

async function scanDir(dir: string, syncServer: SyncServer): Promise<number> {
	let count = 0;
	let entries;
	try {
		entries = await readdir(dir, { withFileTypes: true });
	} catch {
		return 0;
	}

	for (const entry of entries) {
		const fullPath = resolve(dir, entry.name);
		if (entry.isDirectory()) {
			count += await scanDir(fullPath, syncServer);
		} else if (entry.name.endsWith(".automerge")) {
			try {
				const binary = await readFile(fullPath);
				const doc = Automerge.load(new Uint8Array(binary));
				const docId = pathToDocId(fullPath);
				syncServer.setDoc(docId, doc);
				count++;
			} catch (e) {
				console.error(`[DocStore] Failed to load ${fullPath}:`, e);
			}
		}
	}

	return count;
}
