/**
 * Memoised document AST builder for Acurity Architect.
 * Converts a TextDocument to a Program AST using the Architect lexer + parser.
 * Results are cached by (uri, version) — unchanged documents pay no re-parse cost.
 */

import { tokenize } from "../lexer/lexer.js";
import { parse } from "./parser.js";

/** @type {Map<string, {version: number, result: {ast: object, tokens: object[]}}>} */
const astCache = new Map();
const MAX_CACHE_SIZE = 50;

/**
 * Returns the full text of a document, regardless of which TextDocument
 * implementation is in use (VS Code, PlainTextDocument, or the syntax-test shim
 * which only exposes lineAt/lineCount).
 * @param {object} document
 * @returns {string}
 */
function getDocumentText(document) {
    if (typeof document.getText === "function") {
        return document.getText();
    }
    // Fallback for minimal document shims (e.g. syntax-test runner)
    const lines = [];
    for (let i = 0; i < document.lineCount; i++) {
        lines.push(document.lineAt(i).text);
    }
    return lines.join("\n");
}

/**
 * Build (or return cached) the Program AST and token stream for a document.
 * @param {object} document - TextDocument (VS Code, PlainTextDocument, or test shim)
 * @returns {{ast: object, tokens: object[]}}
 */
function buildDocumentAst(document) {
    const cacheKey = document.uri.toString();
    const cached = astCache.get(cacheKey);
    if (cached && cached.version === document.version) return cached.result;

    const tokens = tokenize(getDocumentText(document));
    const ast = parse(tokens);
    const result = { ast, tokens };

    astCache.set(cacheKey, { version: document.version, result });
    if (astCache.size > MAX_CACHE_SIZE) {
        astCache.delete(astCache.keys().next().value);
    }
    return result;
}

/**
 * Evicts the cached AST for a specific document URI.
 * Call this when the document is closed or its include dependencies change.
 * @param {string} uriString - document.uri.toString()
 */
function invalidateDocumentAst(uriString) {
    astCache.delete(uriString);
}

export { buildDocumentAst, invalidateDocumentAst };
