-- initial draft algorithm for locating the visible locals or upvalues
-- from a given line in a chunk
local function containsLine( fd, line )
	return fd.lineDefined <= line and fd.lastlineDefined >= line
end

local function visibleAtLine( fd, loc, line )
	local firstLine = fd.lines[ loc.startpc ] or fd.lineDefined
	local lastLine = fd.lines[ loc.endpc ] or fd.lastlineDefined
	
	-- strictly speaking line number is not precise enough to make
	-- this determination, but line +1 is a reasonable heuristic
	return firstLine, firstLine < line and lastLine >= line
end

local function isTopLevelChunk( fd )
	return fd.lineDefined == 0 and fd.lastlineDefined == 0
end

local function isIdentifier( s )
	return string.match( s, "^[%a_][%w_]*$" )
end

local function findVisibleNames( countParams, ignore, fd, line, results )
	assert( type( line ) == "number" and line > 0, "line must be a valid number" )
	
	results = results or {
		constants = { },
		locals = { },
	}

	local inScope = containsLine( fd, line ) or isTopLevelChunk( fd )

	local superScope = false
	for _, subf in ipairs( fd.constants.functions ) do
		local _, found = findVisibleNames( countParams, ignore, subf, line, results )
		superScope = superScope or found -- true if this is the innermost function scope
	end
	
	if inScope then
		-- go bottom up so the first found local of a given name is least shadowed.
		for i = #fd.locals, 1, -1 do
			local loc = fd.locals[ i ]
			local localLine, visible = visibleAtLine( fd, loc, line )
			if visible and not ignore[ loc.varname ] then
				local kind = superScope and "upvalue" or ( ( i <= countParams( fd ) ) and "param" or "local" )
				if not results.locals[ loc.varname ] then
					results.locals[ loc.varname ] = true
					table.insert( results, { name = loc.varname, line = localLine, kind = kind } )
				end
			end
		end
	end
	
	-- include constants even from other scopes in the chunk
	for _, const in ipairs( fd.constants.scalars ) do
		-- items in the constant pool that look like identifiers probably are
		if type( const ) == "string" and isIdentifier( const ) then
			results.constants[ const ] = true
		end
	end
	
	return results, inScope
end

return findVisibleNames
