import * as fs from "fs";
import * as path from "path";
import * as vscode from "../__mocks__/vscode.js";
import { provideDefinition } from "../../vscode-extension/src/definitionProvider.js";

import { fileURLToPath } from "url";
import { dirname } from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

function createMockDocument(filePath) {
    const text = fs.readFileSync(filePath, "utf8");
    const lines = text.split(/\r?\n/);

    return {
        uri: vscode.Uri.file(filePath),
        fileName: filePath,
        lineCount: lines.length,
        lineAt: (lineIndex) => ({ text: lines[lineIndex] ?? "" }),
        getWordRangeAtPosition: (position) => {
            const lineText = lines[position.line] ?? "";
            if (!lineText) {
                return null;
            }

            let start = position.character;
            let end = position.character;

            while (start > 0 && /[A-Za-z0-9_]/.test(lineText[start - 1])) {
                start -= 1;
            }
            while (end < lineText.length && /[A-Za-z0-9_]/.test(lineText[end])) {
                end += 1;
            }

            if (start === end) {
                return null;
            }

            return {
                start: new vscode.Position(position.line, start),
                end: new vscode.Position(position.line, end)
            };
        },
        getText: (range) => {
            if (!range) {
                return text;
            }

            if (range.start.line !== range.end.line) {
                return "";
            }

            const lineText = lines[range.start.line] ?? "";
            return lineText.slice(range.start.character, range.end.character);
        }
    };
}

describe("DefinitionProvider", () => {
    it("resolves function definitions through circular INCLUDEONCE chains without hanging", async () => {
        const fixtureDir = path.join(__dirname, "fixtures", "included-files");
        const rootPath = path.join(fixtureDir, "circular-include-safe.TXT");
        const rootDoc = createMockDocument(rootPath);

        const originalOpenTextDocument = vscode.workspace.openTextDocument;
        vscode.workspace.openTextDocument = async (uri) => createMockDocument(uri.fsPath);

        try {
            let targetLineIndex = -1;
            for (let i = 0; i < rootDoc.lineCount; i += 1) {
                if (rootDoc.lineAt(i).text.includes("safeCircularInclude2Function(")) {
                    targetLineIndex = i;
                    break;
                }
            }

            expect(targetLineIndex).toBeGreaterThanOrEqual(0);

            const targetLine = rootDoc.lineAt(targetLineIndex).text;
            const wordIndex = targetLine.indexOf("safeCircularInclude2Function");
            const position = new vscode.Position(targetLineIndex, wordIndex + 2);

            let timeoutId;
            const timeoutPromise = new Promise((_, reject) => {
                timeoutId = setTimeout(() => reject(new Error("Definition lookup timed out")), 1500);
            });

            const location = await Promise.race([provideDefinition(rootDoc, position), timeoutPromise]);
            clearTimeout(timeoutId);

            expect(location).toBeTruthy();
            expect(path.basename(location.uri.fsPath)).toBe("circular-include-safe.INCLUDE2.TXT");
        } finally {
            vscode.workspace.openTextDocument = originalOpenTextDocument;
        }
    });

    it("resolves definitions repeatedly through circular INCLUDE chains without stalling", async () => {
        const fixtureDir = path.join(__dirname, "fixtures", "included-files");
        const rootPath = path.join(fixtureDir, "circular-include-unsafe.INCLUDE2.TXT");
        const rootDoc = createMockDocument(rootPath);

        const originalOpenTextDocument = vscode.workspace.openTextDocument;
        vscode.workspace.openTextDocument = async (uri) => createMockDocument(uri.fsPath);

        try {
            let targetLineIndex = -1;
            for (let i = 0; i < rootDoc.lineCount; i += 1) {
                if (rootDoc.lineAt(i).text.includes("unsafeCircularInclude2Function")) {
                    targetLineIndex = i;
                    break;
                }
            }

            expect(targetLineIndex).toBeGreaterThanOrEqual(0);

            const targetLine = rootDoc.lineAt(targetLineIndex).text;
            const wordIndex = targetLine.indexOf("unsafeCircularInclude2Function");
            const position = new vscode.Position(targetLineIndex, wordIndex + 2);

            for (let attempt = 0; attempt < 8; attempt += 1) {
                let timeoutId;
                const timeoutPromise = new Promise((_, reject) => {
                    timeoutId = setTimeout(() => reject(new Error("Definition lookup timed out")), 1000);
                });

                const location = await Promise.race([provideDefinition(rootDoc, position), timeoutPromise]);
                clearTimeout(timeoutId);

                expect(location).toBeTruthy();
                expect(path.basename(location.uri.fsPath)).toBe("circular-include-unsafe.INCLUDE2.TXT");
            }
        } finally {
            vscode.workspace.openTextDocument = originalOpenTextDocument;
        }
    });
});
