import * as vscode from "vscode";
import type { SfClient } from "./sf-client.js";

/**
 * Patterns that identify the start of a named function, class, or method
 * declaration in common languages. Each entry captures the symbol name in
 * capture group 1.
 */
const SYMBOL_PATTERNS: { languages: string[]; regex: RegExp }[] = [
	{
		// TypeScript / JavaScript: function foo(...) | async function foo(...)
		languages: [
			"typescript",
			"typescriptreact",
			"javascript",
			"javascriptreact",
		],
		regex: /^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*[(<]/,
	},
	{
		// TypeScript / JavaScript: class Foo
		languages: [
			"typescript",
			"typescriptreact",
			"javascript",
			"javascriptreact",
		],
		regex: /^\s*(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/,
	},
	{
		// TypeScript / JavaScript: method declarations inside a class
		//   foo(...) { | async foo(...) { | private foo(...): T {
		languages: [
			"typescript",
			"typescriptreact",
			"javascript",
			"javascriptreact",
		],
		regex:
			/^\s*(?:(?:public|private|protected|static|async|readonly)\s+)*(\w+)\s*\(/,
	},
	{
		// Python: def foo( | async def foo(
		languages: ["python"],
		regex: /^\s*(?:async\s+)?def\s+(\w+)\s*\(/,
	},
	{
		// Python: class Foo
		languages: ["python"],
		regex: /^\s*class\s+(\w+)/,
	},
	{
		// Go: func foo( | func (r Receiver) foo(
		languages: ["go"],
		regex: /^\s*func\s+(?:\([^)]+\)\s+)?(\w+)\s*\(/,
	},
	{
		// Rust: fn foo( | pub fn foo( | async fn foo(
		languages: ["rust"],
		regex: /^\s*(?:pub(?:\([^)]+\))?\s+)?(?:async\s+)?fn\s+(\w+)\s*[(<]/,
	},
];

/**
 * CodeLensProvider that adds an "Ask SF" lens above named function and class
 * declarations. Clicking the lens sends a brief explanation request to the SF
 * agent for that specific symbol.
 */
export class SfCodeLensProvider
	implements vscode.CodeLensProvider, vscode.Disposable
{
	private readonly _onDidChangeCodeLenses = new vscode.EventEmitter<void>();
	readonly onDidChangeCodeLenses = this._onDidChangeCodeLenses.event;

	private disposables: vscode.Disposable[] = [];

	constructor(readonly client: SfClient) {
		this.disposables.push(
			this._onDidChangeCodeLenses,
			client.onConnectionChange(() => this._onDidChangeCodeLenses.fire()),
			vscode.workspace.onDidChangeConfiguration((e) => {
				if (e.affectsConfiguration("sf.codeLens")) {
					this._onDidChangeCodeLenses.fire();
				}
			}),
		);
	}

	provideCodeLenses(
		document: vscode.TextDocument,
		_token: vscode.CancellationToken,
	): vscode.CodeLens[] {
		const lenses: vscode.CodeLens[] = [];

		if (
			!vscode.workspace.getConfiguration("sf").get<boolean>("codeLens", true)
		) {
			return lenses;
		}
		const langId = document.languageId;
		const patterns = SYMBOL_PATTERNS.filter((p) =>
			p.languages.includes(langId),
		);

		if (patterns.length === 0) {
			return lenses;
		}

		const fileName =
			document.fileName.split(/[\\/]/).pop() ?? document.fileName;
		const seen = new Set<number>();

		for (let i = 0; i < document.lineCount; i++) {
			const text = document.lineAt(i).text;

			for (const { regex } of patterns) {
				const match = regex.exec(text);
				if (match && match[1] && !seen.has(i)) {
					seen.add(i);
					const symbolName = match[1];
					const range = new vscode.Range(i, 0, i, text.length);
					const args = [symbolName, fileName, i + 1];

					lenses.push(
						new vscode.CodeLens(range, {
							title: "$(hubot) Ask SF",
							tooltip: `Ask SF to explain ${symbolName}`,
							command: "sf.askAboutSymbol",
							arguments: args,
						}),
						new vscode.CodeLens(range, {
							title: "$(pencil) Refactor",
							tooltip: `Refactor ${symbolName}`,
							command: "sf.refactorSymbol",
							arguments: args,
						}),
						new vscode.CodeLens(range, {
							title: "$(bug) Find Bugs",
							tooltip: `Review ${symbolName} for bugs`,
							command: "sf.findBugsSymbol",
							arguments: args,
						}),
						new vscode.CodeLens(range, {
							title: "$(beaker) Tests",
							tooltip: `Generate tests for ${symbolName}`,
							command: "sf.generateTestsSymbol",
							arguments: args,
						}),
					);
				}
			}
		}

		return lenses;
	}

	dispose(): void {
		for (const d of this.disposables) {
			d.dispose();
		}
	}
}
