local check_state = require("luacheck.check_state")
local core_utils = require("luacheck.core_utils")
local parse_inline_options = require("luacheck.stages.parse_inline_options")
local parser = require("luacheck.parser")
local stages = require("luacheck.stages")
local utils = require("luacheck.utils")
local inline_option_fields = utils.array_to_set(parse_inline_options.inline_option_fields)

local function validate_fields(tables, per_code_fields)
	for _, t in ipairs(tables) do
		local fields_set

		if per_code_fields then
			if not t.code then error("Warning has no code", 0) end

			local warning_info = stages.warnings[t.code]

			if not warning_info then error("Unknown issue code " .. t.code, 0) end

			fields_set = warning_info.fields_set
		else
			fields_set = inline_option_fields
		end

		for field in pairs(t) do
			if not fields_set[field] then
				error(
					"Unknown field " .. field .. " in " .. (
							per_code_fields and
							"issue with code " .. t.code or
							"inline option table"
						),
					0
				)
			end
		end
	end
end

--- Checks source.
-- Returns a table with results, with the following fields:
--    `events`: array of issues and inline option events (options, push, or pop).
--    `per_line_options`: map from line numbers to arrays of inline option events.
--    `line_lengths`: map from line numbers to line lengths.
--    `line_endings`: map from line numbers to "comment", "string", or `nil` base on
--                    whether the line ending is within a token.
-- If `events` array contains a syntax error, the other fields are empty tables.
local function check(source)
	local chstate = check_state.new(source)
	local ok, error_wrapper = utils.try(stages.run, chstate)
	local warnings, inline_options, line_lengths, line_endings

	if ok then
		warnings = chstate.warnings
		core_utils.sort_by_location(warnings)
		inline_options = chstate.inline_options
		line_lengths = chstate.line_lengths
		line_endings = chstate.line_endings
	else
		local err = error_wrapper.err

		if not utils.is_instance(err, parser.SyntaxError) then
			error(error_wrapper, 0)
		end

		local syntax_error = {
			code = "011",
			line = err.line,
			column = chstate:offset_to_column(err.line, err.offset),
			end_column = chstate:offset_to_column(err.line, err.end_offset),
			msg = err.msg,
		}

		if err.prev_line then
			syntax_error.prev_line = err.prev_line
			syntax_error.prev_column = chstate:offset_to_column(err.prev_line, err.prev_offset)
			syntax_error.prev_end_column = chstate:offset_to_column(err.prev_line, err.prev_end_offset)
		end

		warnings = {syntax_error}
		inline_options = {}
		line_lengths = {}
		line_endings = {}
	end

	validate_fields(warnings, true)
	validate_fields(inline_options)
	return {
		warnings = warnings,
		inline_options = inline_options,
		line_lengths = line_lengths,
		line_endings = line_endings,
	}
end

return check