import { describe, it, expect } from 'vitest';
import { parse } from './parser';
import { buildSymbolTable } from './symbols';

function build(src: string) {
	const r = parse(src);
	return { ...r, table: buildSymbolTable(r) };
}

describe('symbols — declarations and usages', () => {
	it('captures let declarations', () => {
		const { table } = build('let x = 1; x + 1;');
		const x = table.allDecls.find((d) => d.name === 'x')!;
		expect(x.kind).toBe('let');
		expect(table.usages.find((u) => u.name === 'x')!.resolved).toBe(x);
	});

	it('records fn signatures for completion detail', () => {
		const { table } = build('fn process(order, user) { order }');
		const fn = table.allDecls.find((d) => d.name === 'process')!;
		expect(fn.kind).toBe('fn');
		expect(fn.signature).toBe('process(order, user)');
	});

	it('hoists fn declarations: calls above the decl resolve', () => {
		const { table } = build('greet("world"); fn greet(s) { s }');
		const u = table.usages.find((u) => u.name === 'greet')!;
		expect(u.resolved?.kind).toBe('fn');
	});

	it('function bodies do not see outer locals', () => {
		const { table } = build(`
			let outer = 1;
			fn f() { outer }
		`);
		const outerUse = table.usages.find((u) => u.name === 'outer')!;
		expect(outerUse.resolved).toBeNull();
	});

	it('function bodies do see outer fn declarations', () => {
		const { table } = build(`
			fn helper() { 1 }
			fn caller() { helper() }
		`);
		const helperUse = table.usages.find((u) => u.name === 'helper' && u.range.start > 30)!;
		expect(helperUse.resolved?.kind).toBe('fn');
	});

	it('captures function parameters in their body scope', () => {
		const { table } = build('fn f(a, b) { a + b }');
		const a = table.allDecls.find((d) => d.name === 'a')!;
		expect(a.kind).toBe('param');
		const useOfA = table.usages.find((u) => u.name === 'a')!;
		expect(useOfA.resolved).toBe(a);
	});

	it('captures for-loop binders', () => {
		const { table } = build('for item in [1, 2, 3] { item }');
		const item = table.allDecls.find((d) => d.name === 'item')!;
		expect(item.kind).toBe('for');
	});

	it('respects forward-declaration: cannot use a let before its decl', () => {
		const { table } = build('x; let x = 1;');
		const earlyUse = table.usages.find((u) => u.name === 'x' && u.range.start < 5)!;
		expect(earlyUse.resolved).toBeNull();
	});
});

describe('symbols — object-literal field maps', () => {
	it('records fields of an object-map literal initializer', () => {
		const { table } = build('let order = #{ id: 1, total: 5 };');
		const order = table.allDecls.find((d) => d.name === 'order')!;
		expect(order.objectFields).toEqual(['id', 'total']);
	});

	it('objectFieldsOf returns the set after the declaration', () => {
		const src = 'let order = #{ id: 1 }; order.id';
		const { table } = build(src);
		const afterDecl = src.indexOf('order.id') + 'order.'.length;
		expect(table.objectFieldsOf('order', afterDecl)).toEqual(['id']);
	});
});

describe('symbols — completion + navigation helpers', () => {
	it('scopeCompletions surfaces in-scope locals and hoisted fns', () => {
		const src = `
			let outer = 1;
			fn process(order) {
				order
			}
		`;
		const { table } = build(src);
		const insideFn = src.indexOf('order\n');
		const names = table.scopeCompletions(insideFn).map((d) => d.name);
		expect(names).toContain('order');
		expect(names).toContain('process');
		// outer is not visible from inside `fn process`.
		expect(names).not.toContain('outer');
	});

	it('declOfUsageAt resolves a usage to its declaration', () => {
		const src = 'fn process(o) { o } process(1)';
		const { table } = build(src);
		const callPos = src.lastIndexOf('process');
		const d = table.declOfUsageAt(callPos)!;
		expect(d.name).toBe('process');
		expect(d.kind).toBe('fn');
	});

	it('usagesOf collects declaration + every reference', () => {
		const src = 'fn process(o) { o } process(1); process(2);';
		const { table } = build(src);
		const fn = table.allDecls.find((d) => d.name === 'process')!;
		const all = table.usagesOf(fn);
		// 1 decl name + 2 call sites = 3 ranges
		expect(all).toHaveLength(3);
	});
});
