-- Module to find commons galleries and categories based on wikidata entries
local getArgs = require('Module:Arguments').getArgs
local yesNo = require('Module:Yesno')
local generateWarning = require('Module:If preview')._warning
local p = {}

-- Check if string is a valid QID
-- Argument: QID to check
-- Returns: valid (bool)
local function _validQID(qid)
	return qid and mw.ustring.find
end

-- Check if string is a valid wikidata property string
-- Argument: property string to check
-- Returns: valid (bool)
local function _validProp(prop)
	return prop and mw.ustring.find
end

local function _lcfirst(s)
	return mw.ustring.lower(mw.ustring.sub)..mw.ustring.sub
end

-- Format displayed linktext
-- Arguments:
-- s = string to display
-- formatting = formatting table:
-- formatting.linktext = if defined, override s
-- formatting.lcfirst = lower case the first letter in display
-- formatting.bold = whether to bold the display
-- formatting.italic = whether to italicize the display
-- formatting.nowrap = set nowrapping
-- Returns:
-- formatted string
local function _formatResult
	local resultVal = formatting.linktext or s
	if formatting.lcfirst then
		resultVal = _lcfirst(resultVal)
	end
 local style = ""
	if formatting.italic then style = "font-style:italic; " end
	if formatting.bold then style = style.."font-weight:bold; " end
	if formatting.nowrap then style = style.."white-space:nowrap; " end
 if style ~= "" then
 	resultVal = ''
 end
	return resultVal
end

-- Get title, namespace, and QID for current page
-- Arguments:
-- qid = testing only: get title of alternative page with QID=qid
-- nsQid = whether to return the ns of the qid page or current
-- Returns:
-- title, namespace (string), qid of current page (or test page)
local function _getTitleQID
	local titleObject = mw.title.getCurrentTitle
	-- look up qid for current page (if not testing)
	local nsText = mw.ustring.gsub
	if not _validQID(qid) then
		qid = mw.wikibase.getEntityIdForCurrentPage
		return titleObject.text, nsText, qid
	end
	-- testing-only path: given a qid, determine title
	-- always use namespace from current page (to suppress tracking cat)
	qid = qid:upper
	local title = mw.wikibase.getSitelink(qid) or ""
	-- strip any namespace from sitelink
	local firstColon = mw.ustring.find
	local qidNsText = ""
	if firstColon then
		qidNsText = mw.ustring.sub
		title = mw.ustring.sub
	end
	if nsQid then
		return title, qidNsText, qid
	end
	return title, nsText, qid
end

-- Lookup Commons gallery in Wikidata
-- Arguments:
-- qid = QID of current article
-- fetch = whether to lookup Commons sitelink (bool)
-- commonsSitelink = default value for Commons sitelink
-- Returns:
-- categoryLink = name of Commons category, nil if nothing is found
-- consistent = multiple wikidata fields are examined: are they consistent?
-- commonsSitelink = commons sitelink for current article
local function _lookupGallery
	if not _validQID(qid) then
		return nil, true, nil
	end
	qid = qid:upper
	local galleryLink = nil
	local consistent = true
	-- look up commons sitelink for article, use if not category
	if fetch then
		commonsSitelink = mw.wikibase.getSitelink or commonsSitelink
	end
	if commonsSitelink and mw.ustring.sub ~= "Category:" then
		galleryLink = commonsSitelink
	end
	-- P935 is the "commons gallery" property for this article
	local P935 = mw.wikibase.getBestStatements[1]
	if P935 and P935.mainsnak.datavalue then
		local gallery = P935.mainsnak.datavalue.value
		if galleryLink and galleryLink ~= gallery then
			consistent = false
		else
			galleryLink = gallery
		end
	end
	return galleryLink, consistent, commonsSitelink
end

-- Find fallback category by looking up Commons sitelink of different page
-- Arguments:
-- qid = QID for current article
-- property = property that refers to other article whose sitelink to return
-- Returns: either category-stripped name of article, or nil
local function _lookupFallback
	if not _validQID(qid) or not _validProp(property) then
		return nil
	end
	qid = qid:upper
	property = property:upper
	-- If property exists on current article, get value (other article qid)
	local value = mw.wikibase.getBestStatements[1]
	if value and value.mainsnak.datavalue and value.mainsnak.datavalue.value.id then
		-- Look up Commons sitelink of other article
		local sitelink = mw.wikibase.getSitelink
		-- Check to see if it starts with "Category:". If so, strip it and return
		if sitelink and mw.ustring.sub == "Category:" then
			return mw.ustring.sub
		end
	end
	return nil
end

-- Find Commons category by looking in wikidata
-- Arguments:
-- qid = QID of current article
-- fetch = whether to lookup Commons sitelink (bool)
-- commonsSitelink = default value for Commons sitelink
-- Returns:
-- categoryLink = name of Commons category, nil if nothing is found
-- consistent = multiple wikidata fields are examined: are they consistent?
-- commonsSitelink = commons sitelink for current article
local function _lookupCategory
	if not _validQID(qid) then
		return nil, true, nil
	end
	qid = qid:upper
	local categoryLink = nil
	local consistent = true
	-- look up commons sitelink for article, use if starts with "Category:"
	if fetch then
		commonsSitelink = mw.wikibase.getSitelink or commonsSitelink
	end
	if commonsSitelink and mw.ustring.sub == "Category:" then
		categoryLink = mw.ustring.sub
	end
	-- P910 is the "topic's main category". Look for commons sitelink there
	local fallback = _lookupFallback
	if fallback then
		if categoryLink and categoryLink ~= fallback then
			consistent = false
			qid = nil
		else
			categoryLink = fallback
		end
	end
	-- P1754 is the "list's main category". Look for commons sitelink there
	fallback = _lookupFallback
	if fallback then
		if categoryLink and categoryLink ~= fallback then
			consistent = false
			qid = nil
		else
			categoryLink = fallback
		end
	end
 -- P373 is the "commons category" property for this article. This is
 -- a low-quality field, so should only be used as a last resort.
 if categoryLink == nil and _validQID(qid) then
	 local P373 = mw.wikibase.getBestStatements[1]
	 if P373 and P373.mainsnak.datavalue then
		 categoryLink = P373.mainsnak.datavalue.value
		 consistent = true -- P373 is never used if anything else is available
		end
	end
	return categoryLink, consistent, commonsSitelink
end

-- Does the article have a Commons gallery, and is it consistent?
-- Arguments:
-- qid = QID to lookup in wikidata (for testing only)
-- Returns:
-- filename at Commons, bool: is wikidata consistent for this article?
function p._hasGalleryConsistent(qid)
	local wp_title, wp_ns
	wp_title, wp_ns, qid = _getTitleQID(qid)
	return _lookupGallery
end

-- Does the article have a corresponding Commons gallery?
-- Arguments:
-- qid = QID to lookup in wikidata (for testing only)
-- Returns:
-- filename at Commons if so, false if not
function p._hasGallery(qid)
	local galleryLink, consistent = p._hasGalleryConsistent(qid)
	return consistent and galleryLink
end

-- Does the article have a Commons category? Is wikidata consistent for that?
-- Arguments:
-- qid = QID to lookup in wikidata (for testing only)
-- prefix = whether to add "Category:" to return string (default true)
-- Returns:
-- filename at Commons, bool: consistent
function p._hasCategoryConsistent
	if prefix == nil then
		prefix = true
	end
	local wp_title, wp_ns
	wp_title, wp_ns, qid = _getTitleQID(qid)
	local categoryLink, consistent = _lookupCategory
	if categoryLink and prefix then
		categoryLink = "Category:"..categoryLink
	end
	return categoryLink, consistent
end

-- Does the article have a corresponding Commons category?
-- Arguments:
-- qid = QID to lookup in wikidata (for testing only)
-- prefix = whether to add "Category:" to return string (default true)
-- Returns:
-- filename at Commons if so, blank if not
function p._hasCategory
	local categoryLink, consistent = p._hasCategoryConsistent
	return consistent and categoryLink
end

-- Create Commons link corresponding to current article
-- Arguments:
-- namespace = namespace in Commons ("" for galleries)
-- default = use as Commons link, don't access wikidata
-- search = string to search for
-- fallback = string to search for if wikidata fails
-- formatting = formatting parameters
-- qid = QID to lookup in wikidata (for testing only)
-- Returns:
-- formatted wikilink to Commons in specified namespace
function p._getCommons
	local nsColon
	if not namespace or namespace == "" then
		nsColon = ""
	else
		nsColon = namespace..":"
	end
	if default then
		return "".._formatResult..""
	end
	if search then
		return "".._formatResult..""
	end
	local wp_title, wp_ns
	wp_title, wp_ns, qid = _getTitleQID(qid)
	local commonsLink = nil
	local consistent = true
	if nsColon == "" then
		commonsLink, consistent = _lookupGallery
	elseif namespace:lower == "category" then
		commonsLink, consistent = _lookupCategory
	end
	-- use wikidata if consistent
	if commonsLink and consistent then
		return "".._formatResult..""
	end
	-- if not consistent, fall back to search and add to tracking cat
	-- construct default result (which searches for title)
	local searchResult = "".._formatResult..""
	if not consistent and wp_ns == "" then
		local friendlyNS
		if nsColon == "" then
			friendlyNS = "gallery"
		else
			friendlyNS = namespace:lower
		end
		searchResult = searchResult..""
	end
	return searchResult
end

-- Returns "best" Commons link: first look for gallery, then try category
-- Arguments:
-- default = use as Commons link, don't access wikidata
-- search = string to search for
-- fallback = string to search for if wikidata lookup fails
-- formatting = formatting parameters
-- qid = QID to lookup in wikidata (for testing only)
-- Returns:
-- formatted wikilink to Commons "best" landing page
function p._getGalleryOrCategory
	if default then
		return "".._formatResult..""
	end
	if search then
		return "".._formatResult..""
	end
	local wp_title, wp_ns
	wp_title, wp_ns, qid = _getTitleQID(qid)
	local trackingCats = ""
	local galleryLink, consistent, commonsSitelink = _lookupGallery
	-- use wikidata if either sitelink or P935 exist, and they both agree
	if galleryLink and consistent then
		return "".._formatResult..""
	end
	if not consistent and wp_ns == "" then
		trackingCats = ""
	end
	-- if gallery is not good, fall back looking for category
	local categoryLink
	categoryLink, consistent = _lookupCategory
	if categoryLink and consistent then
		return "".._formatResult..""..trackingCats
	end
	if not consistent and wp_ns == "" then
		trackingCats = trackingCats..""
	end
	-- return search result looking for title as last attempt
	return "" .. _formatResult .. "" .. trackingCats
end

-- Return link(s) Commons gallery, or category, or both from wikidata
-- Arguments:
-- defaultGallery = default gallery link to use, instead of wikidata
-- defaultCategory = default category link to use, instead of wikidata
-- categoryText = if both gallery and category, text to use in category link ("category" by default)
-- oneSearch = only emit one search result
-- formatting = formatting parameters
-- qid = qid of page to lookup in wikidata (testing only)
function p._getGalleryAndCategory
	local wp_title, wp_ns
	wp_title, wp_ns, qid = _getTitleQID(qid)
	categoryText = categoryText or "category"
	local trackingCats = ""
	local galleryLink, galleryConsistent
	local commonsSitelink = nil
	if defaultGallery then
		galleryLink = defaultGallery
		galleryConsistent = true
	else
		galleryLink, galleryConsistent, commonsSitelink = _lookupGallery
	end
	local galleryGood = galleryLink and galleryConsistent
	if not galleryConsistent and wp_ns == "" then
		trackingCats = ""
	end
	local categoryLink, categoryConsistent
	if defaultCategory then
		categoryLink = defaultCategory
		categoryConsistent = true
	else
		categoryLink, categoryConsistent = _lookupCategory
	end
	local categoryGood = categoryLink and categoryConsistent
	if not categoryConsistent and wp_ns == "" then
		trackingCats = trackingCats..""
	end
	local firstLink
	-- construct default result (which searches for title)
	local searchResult = "".._formatResult..""
	if not oneSearch then
		searchResult = searchResult.." ("..categoryText..")"
	end
	local linkText = nil
	if galleryGood then
		firstLink = galleryLink
		linkText = galleryLink
	elseif categoryGood then
		firstLink = "Category:"..categoryLink
		linkText = categoryLink
	else
		return searchResult..trackingCats
	end
	local resultVal = "".._formatResult..""
	if galleryGood and categoryGood then
		resultVal = resultVal.." ("..categoryText..")"
	end
	return resultVal..trackingCats
end

-- Compare two titles with their namespaces stripped
local function titleMatch
	s1 = s1 or ""
	s2 = s2 or ""
 s1 = mw.ustring.gsub
 s2 = mw.ustring.gsub
 return s1 == s2
end

local galleryTrackingCats = {
	commons_link_on_wikidata = '',
	commons_link_defined_as_pagename = '',
	commons_link_locally_defined = '',
	commons_link_from_wikidata = '',
	commons_link_is_pagename = '',
	inconsistent = ''

local categoryTrackingCats = {
		commons_link_on_wikidata = '',
		commons_link_defined_as_pagename = '',
		commons_link_locally_defined = '',
		commons_link_from_wikidata = '',
		commons_link_is_pagename = '',
		inconsistent = ''

local function selectTrackingCat
	if not consistent then
		return trackingCats.inconsistent
	end
	if default then
	-- construct warning message
		if default == wikidata then
			return trackingCats.commons_link_on_wikidata
		end
		local warning = ""
		if wikidata then
			warning = generateWarning({
					"Commons link does not match Wikidata – please check"
		end
		if titleMatch then
			return trackingCats.commons_link_defined_as_pagename .. warning
		end
		return trackingCats.commons_link_locally_defined .. warning
	end
	if wikidata then
		return trackingCats.commons_link_from_wikidata
	end
	return trackingCats.commons_link_is_pagename
end

-- Figure out tracking categories and editor warnings
-- Arguments:
-- default = Commons link argument passed to template
-- fetchGallery = whether to fetch a gallery from Wikidata
-- fetchCategory = whether to fetch a category from Wikidata
-- qid = force a qid for testing
-- Returns:
-- tracking category and possible user warning
--
-- Note: the logic for the tracking is quite different than the logic
-- for generating Commons links (above). Thus, it is separated into another
-- function for code clarity and maintainability. This should not seriously 
-- affect performance: server time is dominated by fetching wikidata entities,
-- and those entities should be cached and shared between the Commons generating
-- code and this tracking code.
function p._tracking
	local title, wp_ns, wp_qid = _getTitleQID
	if wp_ns ~= "" then
		title = wp_ns..":"..title
	end
	-- only track if test or namespace=article or namespace=category
	if not (qid or wp_ns == "" or wp_ns == "Category") then
		return ""
	end
	
	-- determine title and namespace of wikidata and wp article
	local wikidata = nil
	local consistent = nil
	-- Tracking code works for all 4 cases of states of fetchGallery/Category
	-- fetchGallery takes precedence
	if fetchGallery then
		wikidata, consistent = p._hasGalleryConsistent(qid)
		if default or not fetchCategory or (consistent and wikidata) then
			return selectTrackingCat
		end
	end
 if fetchCategory then
		local cat_wikidata, cat_consistent = p._hasCategoryConsistent
		if not fetchGallery or (cat_consistent and cat_wikidata) then
			return selectTrackingCat
		end
		return selectTrackingCat
 end
	return "" -- nothing fetched, nothing tracked
end

local function _createFormatting(args)
	local formatting = {}
	formatting.linktext = args.linktext
	formatting.lcfirst = yesNo(args.lcfirst)
	formatting.bold = yesNo(args.bold)
	formatting.italic = yesNo(args.italic)
	formatting.nowrap = yesNo(args.nowrap)
	return formatting
end

-- Testing-only entry point for _getTitleQID
function p.getTitleQID(frame)
	local args = getArgs
	local text, ns, qid = _getTitleQID
	return text..","..ns..","..(qid or "nil")
end

-- Testing-only entry point for _lookupFallback
function p.lookupFallback(frame)
	local args = getArgs
	local fallback = _lookupFallback
	return fallback or "nil"
end

-- Find the Commons gallery page associated with article
function p.getGallery(frame)
	local args = getArgs
	return p._getCommons("",args[1],args.search,args.fallback,_createFormatting(args),args.qid)
end

-- Find the Commons category page associated with article
function p.getCategory(frame)
	local args = getArgs
	local retval = p._getCommons("Category", args[1], 
		args.search, args.fallback, _createFormatting(args), args.qid
	)
	if args.tracking then
		local default = nil
		if args[1] then
			default = "Category:"..args[1]
		end
		retval = retval..p._tracking
	end
	return retval
end

function p.getGalleryOrCategory(frame)
	local args = getArgs
	local retval = p._getGalleryOrCategory(
		args[1], args.search, args.fallback, _createFormatting(args), args.qid
	)
	if args.tracking then
		retval = retval..p._tracking
	end
	return retval
end

function p.hasGallery(frame)
	local args = getArgs
	return p._hasGallery(args.qid) or ""
end

function p.hasCategory(frame)
	local args = getArgs
	return p._hasCategory(args.qid) or ""
end

function p.hasGalleryOrCategory(frame)
	local args = getArgs
	return p._hasGallery(args.qid) or p._hasCategory(args.qid) or ""
end

function p.getGalleryAndCategory(frame)
	local args = getArgs
	return p._getGalleryAndCategory(args[1], args[2], 
		args.categoryText, args.oneSearch, _createFormatting(args), args.qid)
end

function p.tracking(frame)
	local args = getArgs
	return p._tracking
end