--[[HOTRELOAD
	run_test("test/tests/nattlua/parser.lua")
	run_test("test/performance/parser.lua")
]]
--ANALYZE
local Code = require("nattlua.code").New
local Lexer = require("nattlua.lexer.lexer").New
local Emitter = require("nattlua.emitter.emitter").New
local path_util = require("nattlua.other.path")
local math = _G.math
local math_huge = math.huge
local table_insert = _G.table.insert
local ipairs = _G.ipairs
local pcall = _G.pcall
local xpcall = _G.xpcall
local assert = _G.assert
local debug = _G.debug
local io = _G.io

--[[#local type { Token, TokenType } = import("~/nattlua/lexer/token.lua")]]

--[[#local type { ExpressionKind, StatementKind, Nodes } = import("./node.lua")]]

--[[#local type expression = Nodes.expression]]
--[[#local type statement = Nodes.statement]]
local META = require("nattlua.parser.base")()
require("nattlua.parser.expressions")(META)
require("nattlua.parser.statements")(META)
require("nattlua.parser.teal")(META)
require("nattlua.parser.lsx")(META)

function META:ParseModifiers()
	local modifiers

	while self:IsTokenType("letter") do
		local tk = self:GetToken()

		if tk.sub_type == "ref" or tk.sub_type == "literal" then
			if not modifiers then modifiers = {} end

			table.insert(modifiers, tk)
			self:Advance(1)
		else
			break
		end
	end

	return modifiers
end

function META:ParseIdentifier(expect_type--[[#: nil | boolean]], expect_modifiers--[[#: nil | boolean]])
	if not self:IsTokenType("letter") and not self:IsToken("...") then return end

	local node = self:StartNode("expression_value") -- as ValueExpression ]]
	node.is_identifier = true

	if self:IsToken("...") then
		node.value = self:ExpectToken("...")
	else
		node.value = self:ExpectTokenType("letter")

		if self:IsToken("<") then
			node.tokens["<"] = self:ExpectToken("<")
			node.attribute = self:ExpectTokenType("letter")
			node.tokens[">"] = self:ExpectToken(">")
		end
	end

	if expect_type ~= false then
		if self:IsToken(":") or expect_type then
			node.tokens[":"] = self:ExpectToken(":")

			if expect_modifiers then node.modifiers = self:ParseModifiers() end

			node.type_expression = self:ExpectTypeExpression(0)
		end
	end

	node = self:EndNode(node)
	return node
end

function META:ParseValueExpressionToken(expect_value--[[#: nil | string]])
	local node = self:StartNode("expression_value")
	node.value = expect_value and self:ExpectToken(expect_value) or self:ParseToken()
	node = self:EndNode(node)
	return node
end

local function is_end(tk)
	return tk.sub_type == "end"
end

function META:ParseStatementsUntilEnd(out--[[#: List<|any|>]])
	return self:ParseStatementsUntilCondition(is_end, out)
end

function META:ParseValueExpressionType(expect_value--[[#: TokenType]])
	local node = self:StartNode("expression_value")
	node.value = self:ExpectTokenType(expect_value)
	node = self:EndNode(node)
	return node
end

function META:ParseReturnTypeWithPotentialModifier()
	local modifiers = self:ParseModifiers()
	local node = self:ExpectTypeExpression(0)
	node.modifiers = modifiers
	return node
end

function META:ParseFunctionBody(
	node--[[#: expression.analyzer_function | expression["function"] | statement["local_function"] | statement["function"] ]]
)
	if self.TealCompat then
		if self:IsToken("<") then
			node.tokens["arguments_typesystem("] = self:ExpectToken("<")
			node.identifiers_typesystem = self:ParseMultipleValues(self.ParseIdentifier) or false
			node.tokens["arguments_typesystem)"] = self:ExpectToken(">")
		end
	end

	node.tokens["arguments("] = self:ExpectToken("(")
	node.identifiers = self:ParseMultipleValues(self.ParseIdentifier, nil, true)
	node.tokens["arguments)"] = self:ExpectToken(")", node.tokens["arguments("])

	if self:IsToken(":") then
		node.tokens["return:"] = self:ExpectToken(":")
		self:PushParserEnvironment("typesystem")
		node.return_types = self:ParseMultipleValues(self.ParseReturnTypeWithPotentialModifier, 0)
		self:PopParserEnvironment()
	end

	node.statements = self:ParseStatementsUntilEnd()
	node.tokens["end"] = self:ExpectToken("end", node.tokens["function"])
	return node
end

function META:ParseTypeFunctionBody(
	node--[[#: statement["type_function"] | expression["type_function"] | statement["type_function"] ]]
)
	if self:IsToken("!") then
		node.tokens["!"] = self:ExpectToken("!")
		node.tokens["arguments("] = self:ExpectToken("(")
		node.identifiers = self:ParseMultipleValues(self.ParseIdentifier, true)

		if self:IsToken("...") then
			table_insert(node.identifiers, self:ParseValueExpressionToken("..."))
		end

		node.tokens["arguments)"] = self:ExpectToken(")")
	else
		node.tokens["arguments("] = self:ExpectToken("<|")
		node.identifiers = self:ParseMultipleValues(self.ParseIdentifier, true, true)

		if self:IsToken("...") then
			table_insert(node.identifiers, self:ParseValueExpressionToken("..."))
		end

		node.tokens["arguments)"] = self:ExpectToken("|>", node.tokens["arguments("])

		if self:IsToken("(") then
			local lparen = self:ExpectToken("(")
			local identifiers = self:ParseMultipleValues(self.ParseIdentifier, true, true)
			local rparen = self:ExpectToken(")")
			node.identifiers_typesystem = node.identifiers or false
			node.identifiers = identifiers
			node.tokens["arguments_typesystem("] = node.tokens["arguments("]
			node.tokens["arguments_typesystem)"] = node.tokens["arguments)"]
			node.tokens["arguments("] = lparen
			node.tokens["arguments)"] = rparen
		end
	end

	if self:IsToken(":") then
		node.tokens["return:"] = self:ExpectToken(":")
		self:PushParserEnvironment("typesystem")
		node.return_types = self:ParseMultipleValues(self.ExpectTypeExpression, 0)
		self:PopParserEnvironment()
	end

	if node.identifiers_typesystem then
		node.environment = "runtime"
		self:PushParserEnvironment("runtime")
	else
		node.environment = "typesystem"
		self:PushParserEnvironment("typesystem")
	end

	local start = self:GetToken()
	node.statements = self:ParseStatementsUntilEnd()
	node.tokens["end"] = self:ExpectToken("end", start, start)
	self:PopParserEnvironment()
	return node
end

function META:ParseTypeFunctionArgument(expect_type--[[#: nil | boolean]])
	if self:IsToken(")") then return end

	if self:IsToken("...") then return end

	if expect_type or self:IsTokenType("letter") and self:IsTokenOffset(":", 1) then
		local identifier = self:ParseToken()
		local token = self:ExpectToken(":")
		local exp = self:ExpectTypeExpression(0)
		exp.tokens[":"] = token
		exp.identifier = identifier
		return exp
	end

	return self:ExpectTypeExpression(0)
end

function META:ParseAnalyzerFunctionBody(
	node--[[#: statement["analyzer_function"] | expression["analyzer_function"] | statement["local_analyzer_function"] ]],
	type_args--[[#: boolean]]
)
	self:PushParserEnvironment("runtime")
	node.tokens["arguments("] = self:ExpectToken("(")
	node.identifiers = self:ParseMultipleValues(self.ParseTypeFunctionArgument, type_args)

	if self:IsToken("...") then
		local vararg = self:StartNode("expression_value")
		vararg.value = self:ExpectToken("...")

		if self:IsToken(":") or type_args then
			vararg.tokens[":"] = self:ExpectToken(":")
			vararg.type_expression = self:ExpectTypeExpression(0)
		else
			if self:IsTokenType("letter") then
				vararg.type_expression = self:ExpectTypeExpression(0)
			end
		end

		vararg = self:EndNode(vararg)
		table_insert(node.identifiers, vararg)
	end

	node.tokens["arguments)"] = self:ExpectToken(")", node.tokens["arguments("])

	if self:IsToken(":") then
		node.tokens["return:"] = self:ExpectToken(":")
		self:PushParserEnvironment("typesystem")
		node.return_types = self:ParseMultipleValues(self.ParseTypeExpression, 0)
		self:PopParserEnvironment()
		local start = self:GetToken()
		_G.dont_hoist_import = (_G.dont_hoist_import or 0) + 1
		node.statements = self:ParseStatementsUntilEnd()
		_G.dont_hoist_import = (_G.dont_hoist_import or 0) - 1
		node.tokens["end"] = self:ExpectToken("end", start, start)
	elseif not self:IsToken(",") then
		local start = self:GetToken()
		_G.dont_hoist_import = (_G.dont_hoist_import or 0) + 1
		node.statements = self:ParseStatementsUntilEnd()
		_G.dont_hoist_import = (_G.dont_hoist_import or 0) - 1
		node.tokens["end"] = self:ExpectToken("end", start, start)
	end

	self:PopParserEnvironment()

	do
		local em = Emitter({type_annotations = false})
		em:EmitFunctionBody(node, true)
		em:EmitToken(node.tokens["end"])
		local func, code = self:CompileLuaAnalyzerDebugCode(
			"return function " .. em:Concat(),
			node,
			node.tokens["arguments)"],
			node.tokens["end"]
		)
		node.compiled_function = func()
	end

	return node
end

function META:LexString(str--[[#: string]], config--[[#: nil | any]])
	config = config or {}
	local code = Code(str, config.file_path or str:sub(1, 20))
	local lexer = Lexer(code)
	lexer.OnError = self.OnError
	local ok, tokens = xpcall(lexer.GetTokens, debug.traceback, lexer)

	if not ok then return nil, tokens end

	return tokens, code
end

function META:ParseString(str--[[#: string]], config--[[#: nil | any]])
	local tokens, code = self:LexString(str, config)

	if not tokens then return nil, code end

	local parser = self.New(tokens, code, config)
	parser.OnError = self.OnError
	local ok, node = xpcall(parser.ParseRootNode, debug.traceback, parser)

	if not ok then return nil, node end

	if self.config.on_parsed_file then
		self.config.on_parsed_file(config.file_path, {
			Tokens = tokens,
			Code = code,
			SyntaxTree = node,
		})
	end

	node.lexer_tokens = tokens
	node.parser = parser
	node.code = code
	return node
end

local function read_file(self, path)
	path = path_util.Resolve(
		path,
		self.config.root_directory,
		self.config.working_directory,
		self.config.file_path
	)

	if self.config.pre_read_file then
		local code = self.config.pre_read_file(self, path)

		if code then return code end
	end

	local f = assert(io.open(path, "rb"))
	local code = f:read("*a")
	f:close()

	if not code then
		debug.trace()
		error(path .. " is empty", 2)
	end

	if self.config.on_read_file then self.config.on_read_file(self, path, code) end

	return code
end

function META:ParseFile(path--[[#: string]], config--[[#: nil | any]])
	local ok, code = pcall(read_file, self, path)

	if not ok then return ok, code .. " path: " .. path end

	config = config or {}
	config.file_path = config.file_path or path
	config.file_name = config.file_name or "@" .. path
	return self:ParseString(code, config)
end

local imported_index = nil

function META:ParseRootNode()
	local node = self:StartNode("statement_root")
	self.RootStatement = self.config and self.config.root_statement_override or node
	local shebang
	local statements = {}
	node.statements = statements

	if self:IsTokenType("shebang") then
		shebang = self:StartNode("statement_shebang")
		shebang.tokens["shebang"] = self:ExpectTokenType("shebang")
		shebang = self:EndNode(shebang)
		node.tokens["shebang"] = shebang.tokens["shebang"]
		table_insert(statements, shebang)
	end

	if self.config.emit_environment then
		if not imported_index then
			imported_index = true
			imported_index = self:ParseString([[import("nattlua/definitions/index.nlua")]])
		end

		if imported_index and imported_index ~= true then
			self.RootStatement.imports = self.RootStatement.imports or {}

			for _, import in ipairs(imported_index.imports) do
				table_insert(self.RootStatement.imports, import)
			end

			table_insert(statements, imported_index.statements[1])
		end
	end

	self:ParseStatementsUntilCondition(nil, statements)

	if self:IsTokenType("end_of_file") then
		local eof = self:StartNode("statement_end_of_file")
		eof.tokens["end_of_file"] = self.tokens[#self.tokens]
		eof = self:EndNode(eof)
		table_insert(statements, eof)
		node.tokens["eof"] = eof.tokens["end_of_file"]
	end

	node = self:EndNode(node)
	return node
end

function META:ParseStatement()
	if self:IsTokenType("end_of_file") then return end

	local node = self:ParseDebugCodeStatement() or
		self:ParseReturnStatement() or
		self:ParseBreakStatement() or
		self:ParseContinueStatement() or
		self:ParseSemicolonStatement() or
		self:ParseGotoStatement() or
		self:ParseGotoLabelStatement() or
		self:ParseRepeatStatement() or
		self:ParseAnalyzerFunctionStatement() or
		self:ParseFunctionStatement() or
		self:ParseLocalTypeFunctionStatement() or
		self:ParseLocalFunctionStatement() or
		self:ParseLocalAnalyzerFunctionStatement() or
		self:ParseLocalTypeAssignmentStatement() or
		self:ParseLocalDestructureAssignmentStatement() or
		(
			self.TealCompat and
			self:ParseLocalTealRecord()
		)
		or
		(
			self.TealCompat and
			self:ParseLocalTealEnumStatement()
		)
		or
		self:ParseLocalAssignmentStatement() or
		self:ParseTypeAssignmentStatement() or
		self:ParseDoStatement() or
		self:ParseIfStatement() or
		self:ParseWhileStatement() or
		self:ParseNumericForStatement() or
		self:ParseGenericForStatement() or
		self:ParseDestructureAssignmentStatement() or
		self:ParseCallOrAssignmentStatement()
	return node
end

return META
