require 'Module:No globals'

local p = {}

local lib = require 'Module:cs:Wikidata/lib'

local ValueTypeToDefaultMethod = {
	['monolingualtext'] = 'alpha',
	['quantity'] = 'number',
	['string'] = 'alpha',
	['time'] = 'time',
	['wikibase-entityid'] = 'alpha',
}

p.Sorters = {}
setmetatable(p.Sorters, {
	__index = function(t, key)
		t[key] = require('Module:cs:Wikidata/Sorters/' .. key)
		return t[key]
	end
})

local function resolveResult(result, inverted)
	if math.abs(result) > 1 then
		return result < 0
	else
		return (result < 0) ~= (inverted or false)
	end
end

local function compareIncomplete(first, second)
	if first then
		return -2
	end
	if second then
		return 2
	end
	return 0
end

local function getDefaultMethodForSnak(snak)
	local valueType = lib.datatypeToValueType[snak.datatype]
	return ValueTypeToDefaultMethod[valueType]
end

local function getCallbackForQualifiers(method)
	return function(first, second)
		local sorter = p.Sorters[method]
		if not (sorter.mayCompareSnak(first) and sorter.mayCompareSnak(second)) then
			return error(lib.formatError('invalid-sort', method))
		end
		local first_complete = sorter.isCompleteSnak(first)
		local second_complete = sorter.isCompleteSnak(second)
		if not (first_complete and second_complete) then
			return compareIncomplete(first_complete, second_complete)
		end
		return sorter.compareSnaks(first, second)
	end
end

local function defaultCallbackForQualifiers(first, second)
	local default = getDefaultMethodForSnak(first)
	return getCallbackForQualifiers(default)(first, second)
end

local function getQualifierToCompare(statement, prop)
	local qualifiers = statement.qualifiers and statement.qualifiers[mw.ustring.upper(prop)]
	if qualifiers then
		qualifiers = mw.clone(qualifiers)
		if #qualifiers > 1 then
			table.sort(qualifiers, function(first, second)
				return resolveResult(defaultCallbackForQualifiers(first, second))
			end)
		end
		return qualifiers[1]
	end
	return nil
end

local function getCallbackForStatements(method)
	return function(first, second)
		local sorter = p.Sorters[method]
		if not (sorter.mayCompareStatement(first) and sorter.mayCompareStatement(second)) then
			return error(lib.formatError('invalid-sort', method))
		end
		local first_complete = sorter.isCompleteStatement(first)
		local second_complete = sorter.isCompleteStatement(second)
		if not (first_complete and second_complete) then
			return compareIncomplete(first_complete, second_complete)
		end
		return sorter.compareStatements(first, second)
	end
end

local function resolveMethodForStatements(method)
	if method == 'default' then
		return function(first, second)
			local default = getDefaultMethodForSnak(first.mainsnak)
			return getCallbackForStatements(default)(first, second)
		end
	elseif lib.isPropertyId(method) then
		return function(first, second)
			local first_qualifier = getQualifierToCompare(first, method)
			local second_qualifier = getQualifierToCompare(second, method)
			if not (first_qualifier and second_qualifier) then
				return compareIncomplete(not not first_qualifier, not not second_qualifier)
			end
			local default = getDefaultMethodForSnak(first_qualifier)
			return getCallbackForQualifiers(default)(first_qualifier, second_qualifier)
		end
	end
	return getCallbackForStatements(method)
end

function p.sortStatements(statements, options)
	local methods = lib.textToTable(options.sort)
	local inverted = lib.IsOptionTrue(options, 'invert')
	local cache = {}
	table.sort(statements, function(first, second)
		local result
		for _, method in ipairs(methods) do
			if not cache[method] then
				cache[method] = resolveMethodForStatements(method)
			end
			result = cache[method](first, second)
			if result ~= 0 then
				return resolveResult(result, inverted)
			end
		end
		return false
	end)
end

local function resolveMethodForQualifiers(method)
	if method == 'default' then
		return defaultCallbackForQualifiers
	end
	return getCallbackForQualifiers(method)
end

function p.sortQualifiers(qualifiers, options)
	local methods = lib.textToTable(options.sort)
	local inverted = lib.IsOptionTrue(options, 'invert')
	local cache = {}
	table.sort(qualifiers, function(first, second)
		local result
		for _, method in ipairs(methods) do
			if not cache[method] then
				cache[method] = resolveMethodForQualifiers(method)
			end
			result = cache[method](first, second)
			if result ~= 0 then
				return resolveResult(result, inverted)
			end
		end
		return false
	end)
end

return p