Məzmuna keç

Modul:Wikidata/Places

Vikipediya, azad ensiklopediya

Bu modulun sənədləşdirmə səhifəsi Modul:Wikidata/Places/doc səhifəsində yaradıla bilər

local categorizeByPlace = true; 

local WDS = require( 'Module:WikidataSelectors' );
local Flags = require( 'Module:Wikidata/Flags' );
local PlaceData = require( 'Module:Wikidata/places/config' );
local Qualifiers = require( 'Module:Wikidata/qualifiers' );

local function valuesToSet( values )
	local result = {};
	for key, value in pairs( values or {} ) do
		if type( key ) == 'number' then
			result[ value ] = true;
		elseif value then
			result[ key ] = true;
		end
	end
	return result;
end

local ignoredPlaceTypes = valuesToSet( PlaceData.ignoredPlaceTypes );
local unignoredPlaceTypes = valuesToSet( PlaceData.unignoredPlaceTypes );
local unions = valuesToSet( PlaceData.unionTypes );
local countries = valuesToSet( PlaceData.countryTypes );
local constituentCountryTypes = valuesToSet( PlaceData.constituentCountryTypes );
local showCurrentLabels = valuesToSet( PlaceData.showCurrentLabels );
local skippedPlaceTypes = valuesToSet(
	PlaceData.skippedPlaceTypes
);
local redundantParentSuffixes = PlaceData.redundantParentSuffixes or {};
local parentChainRules = {};
local adjacentParentRules = {};
local placeReplacementTargets = {};

for _, configuredRule in ipairs( PlaceData.parentChainRules or {} ) do
	local patternLength = #configuredRule - 1;
	local removePosition = tonumber( configuredRule[ #configuredRule ] );
	if patternLength >= 2 and removePosition and
		removePosition == math.floor( removePosition ) and
		removePosition >= 1 and removePosition <= patternLength
	then
		local pattern = {};
		for i = 1, patternLength do
			pattern[ i ] = configuredRule[ i ];
		end
		local rule = {
			pattern = pattern,
			remove = removePosition,
		};
		if patternLength == 2 then
			table.insert( adjacentParentRules, rule );
		else
			table.insert( parentChainRules, rule );
		end
	end
end

for _, replacement in ipairs( PlaceData.placeReplacements or {} ) do
	if replacement.target then
		for _, source in ipairs( replacement.sources or {} ) do
			placeReplacementTargets[ source ] = replacement.target;
		end
	end
end

local function getCanonicalEntityId( entityId )
	if not entityId then
		return entityId;
	end
	local seen = {};
	for _ = 1, 12 do
		local replacement = placeReplacementTargets[ entityId ];
		if not replacement or replacement == entityId or seen[ replacement ] then
			return entityId;
		end
		seen[ entityId ] = true;
		entityId = replacement;
	end
	return entityId;
end

local function cloneTable( source )
	local result = {};
	for key, value in pairs( source or {} ) do
		result[ key ] = value;
	end
	return result;
end

local function canonicalizeEntitySnak( snak )
	local value = snak and snak.datavalue and snak.datavalue.value;
	local entityId = value and value.id;
	if not entityId then
		return snak;
	end

	local canonicalId = getCanonicalEntityId( entityId );
	if canonicalId == entityId then
		return snak;
	end

	local result = cloneTable( snak );
	result.datavalue = cloneTable( snak.datavalue );
	result.datavalue.value = cloneTable( value );
	result.datavalue.value.id = canonicalId;
	local numericId = tonumber( mw.ustring.match( canonicalId, '^Q(%d+)$' ) );
	if numericId then
		result.datavalue.value[ 'numeric-id' ] = numericId;
	end
	return result;
end

local p = {
	config = {
		hideSameLabels = false,
		hidePartOfLabels = false,
		hideUnitsForCapitals = true,
		reverseOrder = false,
		catAmbiguousGeoChains = '[[Kateqoriya:Vikipediya:Coğrafi zənciri olan səhifələr]]',
		catLoopInGeoChains = '[[Kateqoriya:Vikipediya:Təkrarlanan coğrafi zənciri olan səhifələr]]',
		catWikibaseError = '[[Kateqoriya:Vikipediya:Vikiverilənlər istifadə edən skript xətalı səhifələr]]'
	}
};

local function min( prev, next )
	if prev == nil then
		return next;
	elseif prev > next then
		return next;
	else
		return prev;
	end
end

local function max( prev, next )
	if prev == nil then
		return next;
	elseif prev < next then
		return next;
	else
		return prev;
	end
end

local function getTimeBoundariesFromProperty( context, propertyId, ignoreUnknown )
	local dateClaims = WDS.filter( context.entity.claims, propertyId );
	if not dateClaims or #dateClaims == 0 then
		return nil;
	end

	local left = nil;
	local right = nil;
	for _, claim in pairs( dateClaims ) do
		local boundaries = claim.mainsnak and context.parseTimeBoundariesFromSnak( claim.mainsnak );
		if boundaries then
			left = min( left, boundaries[ 1 ] );
			right = max( right, boundaries[ 2 ] );
		elseif not ignoreUnknown then
			return nil;
		end
	end

	if not left or not right then return nil; end

	return { left, right };
end

local function getTimeBoundariesFromProperties( context, propertyIds )
	for _, propertyId in ipairs( propertyIds ) do
		local result = getTimeBoundariesFromProperty( context, propertyId );
		if result then
			return result;
		end
	end

	return nil;
end

local function getLifetimeBoundaries( context )
	local claims = context.entity.claims;
	local cached = context.placeLifetime;
	if cached and cached.birthClaims == claims.P569 and
		cached.deathClaims == claims.P570
	then
		return cached.boundaries;
	end
	local birth = getTimeBoundariesFromProperty( context, 'P569', true );
	local death = getTimeBoundariesFromProperty( context, 'P570', true );
	local lifetime = birth and {
		birth[ 1 ], death and death[ 1 ] or os.time() * 1000
	} or nil;
	context.placeLifetime = {
		birthClaims = claims.P569,
		deathClaims = claims.P570,
		boundaries = lifetime,
	};
	return lifetime;
end

local getTimeBoundariesFromQualifiers = Qualifiers.getTimeBoundariesFromQualifier;

local function selectTemporalEntitySnaks( context, claims, boundaries, pointInTime )
	local dated = {};
	local datedIds = {};
	local undatedPreferred = {};
	local undatedPreferredIds = {};
	local undatedNormal = {};
	local undatedNormalIds = {};

	local function insertUnique( target, ids, snak, entityId )
		if not ids[ entityId ] then
			ids[ entityId ] = true;
			snak = cloneTable( snak );
			snak.placeClaims = claims;
			table.insert( target, snak );
		end
	end

	for _, claim in pairs( claims or {} ) do
		if claim.rank ~= 'deprecated' then
			local snak = canonicalizeEntitySnak( claim.mainsnak );
			local entityId = snak and snak.snaktype == 'value' and
				snak.datavalue and snak.datavalue.value and
				snak.datavalue.value.id;

			if entityId then
				local hasStart = claim.qualifiers and
					claim.qualifiers.P580 and
					claim.qualifiers.P580[ 1 ] ~= nil;
				local hasEnd = claim.qualifiers and
					claim.qualifiers.P582 and
					claim.qualifiers.P582[ 1 ] ~= nil;

				if hasStart or hasEnd then
					local startBoundaries = getTimeBoundariesFromQualifiers(
						context, claim, 'P580'
					);
					local endBoundaries = getTimeBoundariesFromQualifiers(
						context, claim, 'P582'
					);
					local matches = boundaries ~= nil and
						( not hasStart or startBoundaries ~= nil ) and
						( not hasEnd or endBoundaries ~= nil ) and
						( startBoundaries == nil or
							startBoundaries[ pointInTime and 1 or 2 ] <= boundaries[ 1 ] ) and
						( endBoundaries == nil or
							endBoundaries[ 1 ] >= boundaries[ 2 ] );
					if matches then
						insertUnique( dated, datedIds, snak, entityId );
					end
				elseif claim.rank == 'preferred' then
					insertUnique(
						undatedPreferred,
						undatedPreferredIds,
						snak,
						entityId
					);
				else
					insertUnique(
						undatedNormal,
						undatedNormalIds,
						snak,
						entityId
					);
				end
			end
		end
	end

	if #dated > 0 then
		return dated;
	end
	if #undatedPreferred == 1 then
		return undatedPreferred;
	end
	if #undatedPreferred > 1 then
		return {};
	end
	if #undatedNormal == 1 then
		return undatedNormal;
	end
	return {};
end

local function getParentsInBoundariesSnakImpl( context, entityId, boundaries, propertyIds, selectors )
	local results = {};
	local resultEntityIds = {};
	entityId = getCanonicalEntityId( entityId );

	local function insertResult( snak )
		snak = canonicalizeEntitySnak( snak );
		local resultId = snak and snak.datavalue and
			snak.datavalue.value and snak.datavalue.value.id;
		if resultId then
			if resultEntityIds[ resultId ] then
				return;
			end
			resultEntityIds[ resultId ] = true;
		end
		table.insert( results, snak );
	end

	if not propertyIds or #propertyIds == 0 then
		return results;
	end
	
	for _, propertyId in ipairs( propertyIds ) do
		if (not string.match( propertyId, '^P%d+$' )) then error('Incorrect propertyId: ' .. propertyId); end
		local selector;
		if (selectors ~= nil) then
			selector = selectors[propertyId] or propertyId;
		else
			selector = propertyId;
		end

		local entityClaims = {};
		entityClaims[propertyId] = mw.wikibase.getAllStatements( entityId, propertyId );

		local filteredClaims = WDS.filter( entityClaims, selector .. '[rank:preferred, rank:normal]' );
		if filteredClaims then
			if propertyId == 'P131' then
				for _, snak in ipairs( selectTemporalEntitySnaks(
					context, filteredClaims, boundaries
				) ) do
					insertResult( snak );
				end
			else
				for _, claim in pairs( filteredClaims ) do
					if not boundaries or not propertyIds or #propertyIds == 0 then
						insertResult( claim.mainsnak );
					else
						local startBoundaries = getTimeBoundariesFromQualifiers( context, claim, 'P580' );
						local endBoundaries = getTimeBoundariesFromQualifiers( context, claim, 'P582' );
		
						if ( startBoundaries == nil or startBoundaries[2] <= boundaries[1] ) and
							( endBoundaries == nil or endBoundaries[1] >= boundaries[2] )
						then
							insertResult( claim.mainsnak );
						end
					end
				end
			end
		end

		if #results > 0 then
			break;
		end
	end

	return results;
end

local function getCountriesInBoundariesSnak( context, entityId, boundaries )
	entityId = getCanonicalEntityId( entityId );
	local entityClaims = {
		P17 = mw.wikibase.getAllStatements( entityId, 'P17' )
	};
	local filteredClaims = WDS.filter(
		entityClaims,
		'P17[rank:preferred, rank:normal]'
	);
	return selectTemporalEntitySnaks( context, filteredClaims, boundaries );
end

local function getParentsInBoundariesSnak( context, entityId, boundaries )
	if not entityId then error('entityId must be specified'); end
	if type(entityId) ~= 'string' then error('entityId must be string'); end
	if not boundaries then error('boundaries must be specified'); end
	if type(boundaries) ~= 'table' then error('boundaries must be table'); end
	entityId = getCanonicalEntityId( entityId );

	local results = getParentsInBoundariesSnakImpl( context, entityId, boundaries, {'P131'} )
	local isCountryResult = false;
	if not results or #results == 0 then
		results = getCountriesInBoundariesSnak( context, entityId, boundaries )
		isCountryResult = true;
	elseif #results > 1 then
		local countryResults = getCountriesInBoundariesSnak( context, entityId, boundaries )
		if countryResults and #countryResults == 1 then
			results = countryResults;
			isCountryResult = true;
		end
	end

	for r, result in pairs( results ) do
		if result.snaktype ~= 'value' then
			return nil;
		end
		local resultId = result.datavalue.value.id;
		if resultId == entityId then
			return nil;
		end
	end
	return results, isCountryResult;
end

local function isSkipTopLevel( entity )
	local isCountry = false;
	local isUnion = false;
	if entity and
		entity.claims and
		entity.claims.P31
	then
		for c, claim in pairs( entity.claims.P31 ) do
			if claim and
				claim.mainsnak and
				claim.mainsnak.datavalue and
				claim.mainsnak.datavalue.value and
				claim.mainsnak.datavalue.value.id
			then
				local typeId = claim.mainsnak.datavalue.value.id;
				isCountry = isCountry or countries[ typeId ];
				isUnion = isUnion or unions[ typeId ];
			end
		end
	end
	return isUnion and not isCountry;
end

local function isPartOfNext( prevLabel, nextLabel )
	return ( mw.ustring.len( prevLabel ) > mw.ustring.len( nextLabel ) )
		and ( mw.ustring.sub( prevLabel, mw.ustring.len( prevLabel ) - mw.ustring.len( nextLabel ) + 1 ) == nextLabel );
end

local function isIgnoredPlace( entityId, instanceOf )
	entityId = getCanonicalEntityId( entityId );
	local hasIgnoredType = false;
	instanceOf = instanceOf or mw.wikibase.getAllStatements( entityId, 'P31' ) or {};

	for _, statement in ipairs( instanceOf ) do
		local snak = statement.mainsnak;
		local typeId = snak and
			snak.datavalue and
			snak.datavalue.value and
			snak.datavalue.value.id;

		if typeId then
			if unignoredPlaceTypes[ typeId ] then
				return false;
			elseif ignoredPlaceTypes[ typeId ] then
				hasIgnoredType = true;
			end
		end
	end

	return hasIgnoredType;
end

local function matchesParentChainValue( entityId, expected )
	if type( expected ) == 'table' then
		for _, expectedId in ipairs( expected ) do
			if entityId == expectedId then
				return true;
			end
		end
		return false;
	end
	return entityId == expected;
end

local function matchesParentChainPattern( parentEntityIds, pattern, startIndex )
	for offset, expected in ipairs( pattern ) do
		if not matchesParentChainValue(
			parentEntityIds[ startIndex + offset - 1 ], expected
		) then
			return false;
		end
	end
	return true;
end

local function applyParentChainRule( parentSnaks, parentEntityIds, rule )
	local lastStart = #parentEntityIds - #rule.pattern + 1;
	if lastStart < 1 then
		return nil;
	end

	for startIndex = 1, lastStart do
		if matchesParentChainPattern( parentEntityIds, rule.pattern, startIndex ) then
			local removeIndex = startIndex + rule.remove - 1;
			table.remove( parentSnaks, removeIndex );
			table.remove( parentEntityIds, removeIndex );
			return removeIndex;
		end
	end
	return nil;
end

local function getAdjacentParentRemoval( previousEntityId, nextEntityId )
	for _, rule in ipairs( adjacentParentRules ) do
		if matchesParentChainValue( previousEntityId, rule.pattern[ 1 ] ) and
			matchesParentChainValue( nextEntityId, rule.pattern[ 2 ] )
		then
			return rule.remove;
		end
	end
	return nil;
end

local function applyParentChainRules( parentSnaks, parentEntityIds )
	local baseRemoved = false;
	for _, rule in ipairs( parentChainRules ) do
		local removeIndex = applyParentChainRule( parentSnaks, parentEntityIds, rule );
		baseRemoved = baseRemoved or removeIndex == 1;
	end
	local index = 2;
	while index <= #parentEntityIds do
		local removePosition = getAdjacentParentRemoval(
			parentEntityIds[ index - 1 ], parentEntityIds[ index ]
		);
		if removePosition then
			local removeIndex = index + removePosition - 2;
			table.remove( parentSnaks, removeIndex );
			table.remove( parentEntityIds, removeIndex );
			baseRemoved = baseRemoved or removeIndex == 1;
			index = math.max( 2, index - 1 );
		else
			index = index + 1;
		end
	end
	return baseRemoved;
end

function p.formatPlaceWithQualifiers( context, options, statement )
	local property = mw.ustring.upper( options.property );
	local skipAdministrativeUnits = options.skipAdministrativeUnits == true or
		options.skipAdministrativeUnits == 'true';

	local actualDateBoundariesProperties = nil;
	if property == 'P19' then actualDateBoundariesProperties = {'P569','P570'}; end
	if property == 'P20' then actualDateBoundariesProperties = {'P570','P569'}; end
	if property == 'P119' then actualDateBoundariesProperties = {'P570','P569'}; end
	if property == 'P159' then actualDateBoundariesProperties = {'P576'}; end

	local boundaries = nil;
	if actualDateBoundariesProperties ~= nil then
		boundaries = getTimeBoundariesFromProperties( context, actualDateBoundariesProperties );
		if (boundaries == nil) and (property == 'P159') then
			boundaries = {os.time() * 1000, os.time() * 1000};
		end
	end

	local lifetime = ( property == 'P19' or property == 'P20' or property == 'P119' ) and
		getLifetimeBoundaries( context ) or nil;
	local placeLabelCache = {};
	local placeNameInfo = {};
	local chainSnaks = {};
	local countryNameIds = {};
	local placeTypeCache = {};
	local function getPlaceTypes( entityId )
		if not placeTypeCache[ entityId ] then
			placeTypeCache[ entityId ] = mw.wikibase.getAllStatements( entityId, 'P31' ) or {};
		end
		return placeTypeCache[ entityId ];
	end
	local maxGeoChainDepth = 12;
	local countryResolutionStepsRemaining = maxGeoChainDepth * 2;

	local ignoredPlaceCache = {};
	local function isIgnoredPlaceCached( entityId )
		entityId = getCanonicalEntityId( entityId );
		if not entityId then
			return false;
		end
		local cached = ignoredPlaceCache[ entityId ];
		if cached ~= nil then
			return cached;
		end

		local ignored = isIgnoredPlace( entityId, getPlaceTypes( entityId ) );
		ignoredPlaceCache[ entityId ] = ignored;
		return ignored;
	end

	local azLabelCache = {};
	local function getAzLabel( entityId )
		entityId = getCanonicalEntityId( entityId );
		if not entityId then
			return nil;
		end
		local cached = azLabelCache[ entityId ];
		if cached == false then
			return nil;
		elseif cached ~= nil then
			return cached;
		end

		local label = mw.wikibase.getLabelByLang( entityId, 'az' );
		azLabelCache[ entityId ] = label or false;
		return label;
	end

	local countryEntityCache = {};
	local function isConstituentCountry( entityId )
		for _, claim in ipairs( getPlaceTypes( entityId ) ) do
			local snak = claim.mainsnak;
			local typeId = snak and snak.datavalue and
				snak.datavalue.value and snak.datavalue.value.id;
			if claim.rank ~= 'deprecated' and typeId and constituentCountryTypes[ typeId ] then
				return true;
			end
		end
		return false;
	end

	local function isCountryEntityCached( entityId )
		entityId = getCanonicalEntityId( entityId );
		local cached = countryEntityCache[ entityId ];
		if cached ~= nil then
			return cached;
		end

		local isCountry = false;
		for _, statement in ipairs(
			getPlaceTypes( entityId )
		) do
			local snak = statement.mainsnak;
			local typeId = snak and snak.datavalue and
				snak.datavalue.value and snak.datavalue.value.id;
			if typeId and countries[ typeId ] then
				isCountry = true;
				break;
			end
		end
		countryEntityCache[ entityId ] = isCountry;
		return isCountry;
	end

	local skippedPlaceTypeCache = {};
	local function isSkippedPlaceTypeCached( entityId )
		entityId = getCanonicalEntityId( entityId );
		local cached = skippedPlaceTypeCache[ entityId ];
		if cached ~= nil then
			return cached;
		end

		local skipped = false;
		for _, statement in ipairs(
			getPlaceTypes( entityId )
		) do
			local value = statement.mainsnak and
				statement.mainsnak.datavalue and
				statement.mainsnak.datavalue.value;
			if value and skippedPlaceTypes[ value.id ] then
				skipped = true;
				break;
			end
		end
		skippedPlaceTypeCache[ entityId ] = skipped;
		return skipped;
	end

	local historicalCountryCache = {};
	local function getPlaceLabel( entityId, keepHistorical )
		entityId = getCanonicalEntityId( entityId );
		if not entityId then return nil; end
		if showCurrentLabels[ entityId ] then return getAzLabel( entityId ); end
		if keepHistorical then countryNameIds[ entityId ] = true; end
		if lifetime then
			if historicalCountryCache[ entityId ] == nil then
				local isCountry = false;
				for _, claim in ipairs( getPlaceTypes( entityId ) ) do
					local snak = claim.mainsnak;
					local typeId = snak and snak.datavalue and
						snak.datavalue.value and snak.datavalue.value.id;
					if claim.rank ~= 'deprecated' and typeId and
						( countries[ typeId ] or unions[ typeId ] or skippedPlaceTypes[ typeId ] )
					then
						isCountry = true;
						break;
					end
				end
				historicalCountryCache[ entityId ] = isCountry;
			end
			keepHistorical = keepHistorical or countryNameIds[ entityId ] or
				historicalCountryCache[ entityId ];
		end
		local key = entityId .. ( keepHistorical and ':historical' or '' );
		if placeLabelCache[ key ] ~= nil then
			return placeLabelCache[ key ] or nil;
		end
		local label, nameInfo = getLabel(
			context, entityId, boundaries, not keepHistorical and lifetime or nil
		);
		if label and nameInfo then
			placeNameInfo[ entityId ] = placeNameInfo[ entityId ] or {};
			placeNameInfo[ entityId ][ label ] = nameInfo;
		end
		placeLabelCache[ key ] = label or false;
		return label;
	end

	local function findIgnoredPlaceFallbacks(
		startEntityId, firstUsableOnly, expectedCountryId
	)
		startEntityId = getCanonicalEntityId( startEntityId );
		local currentEntityId = startEntityId;
		local seen = { [ startEntityId ] = true };
		local fallbacks = {};

		for _ = 1, maxGeoChainDepth do
			local parentSnaks = getParentsInBoundariesSnakImpl(
				context, currentEntityId, boundaries, { 'P131' }
			);
			local isCountryFallback = false;

			if not parentSnaks or #parentSnaks == 0 then
				parentSnaks = getCountriesInBoundariesSnak(
					context, currentEntityId, boundaries
				);
				isCountryFallback = true;
			elseif #parentSnaks > 1 then
				local countrySnaks = getCountriesInBoundariesSnak(
					context, currentEntityId, boundaries
				);
				if countrySnaks and #countrySnaks == 1 then
					parentSnaks = countrySnaks;
					isCountryFallback = true;
				end
			end

			if isCountryFallback and expectedCountryId and
				#parentSnaks > 1
			then
				for _, countrySnak in ipairs( parentSnaks ) do
					if countrySnak.datavalue.value.id == expectedCountryId then
						parentSnaks = { countrySnak };
						break;
					end
				end
			end

			if not parentSnaks or #parentSnaks == 0 then
				return fallbacks;
			elseif #parentSnaks > 1 then
				return fallbacks, 'ambiguous';
			end

			local parentSnak = parentSnaks[ 1 ];
			local parentId = parentSnak and
				parentSnak.snaktype == 'value' and
				parentSnak.datavalue and
				parentSnak.datavalue.value and
				parentSnak.datavalue.value.id;

			if not parentId then
				return fallbacks;
			elseif seen[ parentId ] then
				if isCountryFallback and
					( parentId == expectedCountryId or not isConstituentCountry( parentId ) )
				then
					return fallbacks, nil, {
						snak = parentSnak,
						entityId = parentId,
						label = getPlaceLabel( parentId, true ),
					};
				end
				return fallbacks, 'loop';
			end

			seen[ parentId ] = true;

			local label = getPlaceLabel(
				parentId, isCountryFallback or parentId == expectedCountryId
			);
			if label and label ~= '' then
				if not isIgnoredPlaceCached( parentId ) then
					table.insert( fallbacks, {
						snak = parentSnak,
						entityId = parentId,
						label = label,
					} );
					if firstUsableOnly then
						return fallbacks;
					end
				end
			end

			if parentId == expectedCountryId or
				isCountryFallback and not isConstituentCountry( parentId )
			then
				return fallbacks, nil, {
					snak = parentSnak,
					entityId = parentId,
					label = label,
				};
			end

			currentEntityId = parentId;
		end

		return fallbacks;
	end

	local countryResolutionCache = {};
	local function resolveCountryFromPlace( startEntityId, startSnak )
		startEntityId = getCanonicalEntityId( startEntityId );
		local cached = countryResolutionCache[ startEntityId ];
		if cached then
			return cached.country, cached.status;
		end

		local currentEntityId = startEntityId;
		local currentSnak = canonicalizeEntitySnak( startSnak );
		local seen = {};
		local path = {};
		local country = nil;
		local status = nil;
		local completed = false;

		for _ = 1, maxGeoChainDepth do
			local currentCached = countryResolutionCache[ currentEntityId ];
			if currentCached then
				country = currentCached.country;
				status = currentCached.status;
				completed = true;
				break;
			end
			if seen[ currentEntityId ] then
				status = 'loop';
				completed = true;
				break;
			end
			if countryResolutionStepsRemaining == 0 then
				status = 'limit';
				break;
			end
			countryResolutionStepsRemaining = countryResolutionStepsRemaining - 1;
			seen[ currentEntityId ] = true;
			table.insert( path, currentEntityId );

			local countrySnaks = getCountriesInBoundariesSnak(
				context, currentEntityId, boundaries
			);
			if #countrySnaks == 1 then
				local countrySnak = countrySnaks[ 1 ];
				local countryId = countrySnak.datavalue.value.id;
				country = {
					snak = countrySnak,
					entityId = countryId,
					label = getPlaceLabel( countryId, true ),
				};
				completed = true;
				break;
			elseif #countrySnaks > 1 then
				status = 'ambiguous';
				completed = true;
				break;
			end

			local parentSnaks = getParentsInBoundariesSnakImpl(
				context, currentEntityId, boundaries, { 'P131' }
			);
			if #parentSnaks == 0 then
				if isCountryEntityCached( currentEntityId ) then
					country = {
						snak = currentSnak,
						entityId = currentEntityId,
						label = getPlaceLabel( currentEntityId, true ),
					};
				end
				completed = true;
				break;
			elseif #parentSnaks > 1 then
				status = 'ambiguous';
				completed = true;
				break;
			end

			local parentSnak = parentSnaks[ 1 ];
			local parentId = parentSnak and
				parentSnak.snaktype == 'value' and
				parentSnak.datavalue and
				parentSnak.datavalue.value and
				parentSnak.datavalue.value.id;
			if not parentId then
				completed = true;
				break;
			end
			currentSnak = parentSnak;
			currentEntityId = parentId;
		end

		local resolution = {
			country = country,
			status = status,
		};
		if completed then
			for _, pathEntityId in ipairs( path ) do
				countryResolutionCache[ pathEntityId ] = resolution;
			end
		end
		return country, status;
	end

	local entriesToLookupCategory = {};
	local mainSnak = canonicalizeEntitySnak( statement.mainsnak );
	table.insert( chainSnaks, mainSnak );
	local mainEntityId = mainSnak and
		mainSnak.datavalue and
		mainSnak.datavalue.value and
		mainSnak.datavalue.value.id;
	local mainAzLabel = mainEntityId and getAzLabel( mainEntityId );
	local hasMainAzLabel = mainAzLabel and mainAzLabel ~= '';
	local ignoredBasePlace = mainEntityId and hasMainAzLabel and
		isIgnoredPlaceCached( mainEntityId ) or false;
	local burialMainOnly = property == 'P119' and hasMainAzLabel and
		not ignoredBasePlace or false;
	local suppressBasePlace = mainEntityId and
		( not hasMainAzLabel or ignoredBasePlace ) or false;

	local circumstances = context.getSourcingCircumstances( statement );
	local result = '';
	local fallbackMaintenanceCategory = '';
	local baseResult = '';
	local baseDisplayLabel = mainAzLabel;
	if not suppressBasePlace then
		baseDisplayLabel = getPlaceLabel( mainEntityId ) or mainAzLabel;
	end
	if suppressBasePlace then
		baseResult = '';
	else
		local baseOptions = context.cloneOptions( options );
		baseOptions[ 'text' ] = baseDisplayLabel;
		baseResult = context.formatSnak( baseOptions, mainSnak, circumstances );
		if not baseResult then
			return nil;
		end
	end

	local lastDisplayedPlaceLabel = suppressBasePlace and nil or baseDisplayLabel;
	local displayedPlaceLabels = {};
	local displayedPlaceEntityIds = {};
	local displayedPlaceEntries = {};
	local baseRemovedByRule = false;
	if not suppressBasePlace then
		if baseDisplayLabel and baseDisplayLabel ~= '' then
			displayedPlaceLabels[ baseDisplayLabel ] = true;
		end
		if mainEntityId then
			displayedPlaceEntityIds[ mainEntityId ] = true;
		end
		table.insert( displayedPlaceEntries, {
			label = baseDisplayLabel,
			entityId = mainEntityId,
			snak = mainSnak,
			rendered = baseResult,
			isBase = true,
			categoryCountBefore = 0,
		} );
	end

	local function registerPlaceLabel( label, snak )
		if not label or label == '' then
			return true;
		end

		local entityId = snak and snak.datavalue and
			snak.datavalue.value and snak.datavalue.value.id;
		entityId = getCanonicalEntityId( entityId );
		if displayedPlaceLabels[ label ] or
			( entityId and displayedPlaceEntityIds[ entityId ] )
		then
			return false;
		end

		if lastDisplayedPlaceLabel then
			for _, suffix in ipairs( redundantParentSuffixes ) do
				if label == lastDisplayedPlaceLabel .. ' ' .. suffix then
					return false;
				end
			end
		end

		lastDisplayedPlaceLabel = label;
		displayedPlaceLabels[ label ] = true;
		if entityId then
			displayedPlaceEntityIds[ entityId ] = true;
		end
		return true;
	end
	
	insertFromSnak( mainSnak, entriesToLookupCategory )

	local function removeLastDisplayedPlace()
		local previous = table.remove( displayedPlaceEntries );
		if not previous then
			return false;
		end
		if previous.label then
			displayedPlaceLabels[ previous.label ] = nil;
		end
		if previous.entityId then
			displayedPlaceEntityIds[ previous.entityId ] = nil;
		end
		while #entriesToLookupCategory > previous.categoryCountBefore do
			table.remove( entriesToLookupCategory );
		end
		if previous.isBase then
			baseResult = '';
			baseRemovedByRule = true;
		else
			result = previous.resultBefore or '';
		end
		local newPrevious = displayedPlaceEntries[ #displayedPlaceEntries ];
		lastDisplayedPlaceLabel = newPrevious and newPrevious.label or nil;
		return true;
	end

	local function appendFormattedPlace( snak, label, skipAdjacentRules )
		if not snak or not label or label == '' then
			return false;
		end
		local entityId = snak.datavalue and snak.datavalue.value and
			snak.datavalue.value.id;
		entityId = getCanonicalEntityId( entityId );
		if entityId and ( isIgnoredPlaceCached( entityId ) or
			skipAdministrativeUnits and isSkippedPlaceTypeCached( entityId ) )
		then
			return false;
		end
		local parentOptions = context.cloneOptions( options );
		parentOptions[ 'text' ] = label;
		local link = context.formatSnak( parentOptions, snak );
		if link == nil then
			return false;
		end

		if displayedPlaceLabels[ label ] or
			( entityId and displayedPlaceEntityIds[ entityId ] )
		then
			return false;
		end
		while entityId and not skipAdjacentRules do
			local previous = displayedPlaceEntries[ #displayedPlaceEntries ];
			if not previous or not previous.entityId then
				break;
			end
			local removePosition = getAdjacentParentRemoval(
				previous.entityId, entityId
			);
			if removePosition == 2 then
				return false;
			elseif removePosition == 1 then
				removeLastDisplayedPlace();
			else
				break;
			end
		end

		if not registerPlaceLabel( label, snak ) then
			return false;
		end
		local resultBefore = result;
		local categoryCountBefore = #entriesToLookupCategory;
		if p.config.reverseOrder then
			result = link .. ', ' .. result;
		else
			result = result .. ', ' .. link;
		end
		insertFromSnak( snak, entriesToLookupCategory );
		table.insert( displayedPlaceEntries, {
			label = label,
			entityId = entityId,
			snak = snak,
			rendered = link,
			resultBefore = resultBefore,
			categoryCountBefore = categoryCountBefore,
		} );
		table.insert( chainSnaks, snak );
		return true;
	end

	local function appendFallbacks( fallbacks, startEntityId )
		local chain = {};
		local chainEntityIds = {};
		local startEntry = nil;
		if startEntityId then
			startEntry = {};
			table.insert( chain, startEntry );
			table.insert( chainEntityIds, getCanonicalEntityId( startEntityId ) );
		end
		for _, fallback in ipairs( fallbacks or {} ) do
			table.insert( chainSnaks, fallback.snak );
			table.insert( chain, fallback );
			table.insert( chainEntityIds, fallback.entityId );
		end
		if startEntry and applyParentChainRules( chain, chainEntityIds ) then
			removeLastDisplayedPlace();
		elseif not startEntry then
			applyParentChainRules( chain, chainEntityIds );
		end

		local appended = false;
		for _, fallback in ipairs( chain ) do
			if fallback ~= startEntry and
				appendFormattedPlace( fallback.snak, fallback.label )
			then
				appended = true;
			end
		end
		return appended;
	end

	local p131Qualifiers = {};
	local p131QualifierIds = {};
	local p17Qualifiers = {};
	local p17QualifierIds = {};
	if statement.qualifiers then
		for _, qualifier in ipairs( statement.qualifiers.P131 or {} ) do
			qualifier = canonicalizeEntitySnak( qualifier );
			local qualifierId = qualifier and qualifier.datavalue and
				qualifier.datavalue.value and qualifier.datavalue.value.id;
			if qualifierId and not p131QualifierIds[ qualifierId ] then
				p131QualifierIds[ qualifierId ] = true;
				if not burialMainOnly then table.insert( chainSnaks, qualifier ); end
				table.insert( p131Qualifiers, qualifier );
			end
		end
		for _, qualifier in ipairs( statement.qualifiers.P17 or {} ) do
			qualifier = canonicalizeEntitySnak( qualifier );
			local qualifierId = qualifier and qualifier.datavalue and
				qualifier.datavalue.value and qualifier.datavalue.value.id;
			if qualifierId and not p17QualifierIds[ qualifierId ] then
				p17QualifierIds[ qualifierId ] = true;
				if not burialMainOnly then table.insert( chainSnaks, qualifier ); end
				countryNameIds[ qualifierId ] = true;
				table.insert( p17Qualifiers, qualifier );
			end
		end
	end

	local rawP131QualifierCount = #p131Qualifiers;
	local rawDirectP131EntityId = rawP131QualifierCount == 1 and
		p131Qualifiers[ 1 ].datavalue.value.id or nil;
	local hasDirectP17Qualifier = #p17Qualifiers > 0;
	local directP17EntityId = #p17Qualifiers == 1 and
		p17Qualifiers[ 1 ].datavalue.value.id or nil;
	local isBirthOrDeathPlace = property == 'P19' or property == 'P20';
	local hasAdditionalQualifiers = false;
	if property ~= 'P119' and not burialMainOnly then
		for _, qualifier in ipairs( p131Qualifiers ) do
			local qualifierEntityId = qualifier.datavalue.value.id;
			local qualifierLabel = getPlaceLabel( qualifierEntityId );
			if qualifierLabel and qualifierLabel ~= '' and
				appendFormattedPlace( qualifier, qualifierLabel, true )
			then
				hasAdditionalQualifiers = true;
			end
		end
	end

	local function applyFallbackStatus( fallbackStatus )
		if fallbackStatus == 'loop' and p.config and p.config.catLoopInGeoChains then
			fallbackMaintenanceCategory = p.config.catLoopInGeoChains;
		elseif fallbackStatus == 'ambiguous' and p.config and p.config.catAmbiguousGeoChains then
			fallbackMaintenanceCategory = p.config.catAmbiguousGeoChains;
		end
	end

	local function appendMatchingCountryChain( startEntityId )
		if not directP17EntityId then
			return;
		end
		local fallbacks, fallbackStatus, country = findIgnoredPlaceFallbacks(
			startEntityId, false, directP17EntityId
		);
		if country and country.entityId == directP17EntityId then
			appendFallbacks( fallbacks, startEntityId );
		end
		applyFallbackStatus( fallbackStatus );
	end

	if mainSnak and
		mainSnak.datavalue and
		mainSnak.datavalue.value and
		mainSnak.datavalue.value.id
	then
		local entityId = mainSnak.datavalue.value.id;
		local parentSnaks = { mainSnak };
		local parentEntityIds = { entityId };

		if property == 'P119' then
			if not burialMainOnly then
				baseResult = '';
				local burialResolved = false;
				local fallbackStatus = nil;
				if rawP131QualifierCount == 1 then
					local qualifier = p131Qualifiers[ 1 ];
					local qualifierId = qualifier.datavalue.value.id;
					local qualifierLabel = getPlaceLabel( qualifierId );
					if qualifierLabel and qualifierLabel ~= '' and
						not isIgnoredPlaceCached( qualifierId )
					then
						burialResolved = appendFormattedPlace( qualifier, qualifierLabel );
					end
					if not burialResolved then
						local fallbacks;
						fallbacks, fallbackStatus =
							findIgnoredPlaceFallbacks( qualifierId, true );
						burialResolved = appendFallbacks( fallbacks );
					end
				else
					local fallbacks;
					fallbacks, fallbackStatus =
						findIgnoredPlaceFallbacks( entityId, true );
					burialResolved = appendFallbacks( fallbacks );
				end

				if not burialResolved then
					for _, qualifier in ipairs( p17Qualifiers ) do
						local qualifierId = qualifier.datavalue.value.id;
						local qualifierLabel = getPlaceLabel( qualifierId, true );
						if appendFormattedPlace( qualifier, qualifierLabel ) then
							burialResolved = true;
							break;
						end
					end
				end
				applyFallbackStatus( fallbackStatus );
			end
		elseif isBirthOrDeathPlace and rawP131QualifierCount > 0 and
			not hasDirectP17Qualifier
		then
			local sharedCountry = nil;
			local countriesAgree = true;
			for _, qualifier in ipairs( p131Qualifiers ) do
				local country, fallbackStatus = resolveCountryFromPlace(
					qualifier.datavalue.value.id, qualifier
				);
				applyFallbackStatus( fallbackStatus );
				if not country then
					countriesAgree = false;
				elseif sharedCountry and
					sharedCountry.entityId ~= country.entityId
				then
					countriesAgree = false;
				elseif not sharedCountry then
					sharedCountry = country;
				end
			end
			if countriesAgree and sharedCountry then
				table.insert( chainSnaks, sharedCountry.snak );
			end
			if not skipAdministrativeUnits and countriesAgree and sharedCountry and
				sharedCountry.label and sharedCountry.label ~= ''
			then
				appendFormattedPlace(
					sharedCountry.snak, sharedCountry.label, true
				);
			end
			if countriesAgree and sharedCountry and isConstituentCountry( sharedCountry.entityId ) then
				local fallbacks, fallbackStatus = findIgnoredPlaceFallbacks( sharedCountry.entityId, false );
				appendFallbacks( fallbacks );
				applyFallbackStatus( fallbackStatus );
			end
		elseif isBirthOrDeathPlace and rawP131QualifierCount == 0 and
			hasDirectP17Qualifier and suppressBasePlace
		then
			baseResult = '';
			appendMatchingCountryChain( entityId );
		elseif suppressBasePlace then
			baseResult = '';
			local fallbackStartEntityId = nil;
			if not hasDirectP17Qualifier then
				if rawP131QualifierCount == 1 then
					fallbackStartEntityId = rawDirectP131EntityId;
				elseif rawP131QualifierCount == 0 then
					fallbackStartEntityId = entityId;
				end
			end

			if fallbackStartEntityId then
				local fallbacks, fallbackStatus =
					findIgnoredPlaceFallbacks( fallbackStartEntityId, false );
				appendFallbacks( fallbacks, fallbackStartEntityId );
				applyFallbackStatus( fallbackStatus );
			end
		elseif actualDateBoundariesProperties ~= nil and
			not hasAdditionalQualifiers and
			not ( isBirthOrDeathPlace and hasDirectP17Qualifier and
				( rawP131QualifierCount > 0 or not directP17EntityId ) )
		then
			
			if boundaries then
				local entityOptions = context.cloneOptions( options );
				entityOptions['text'] = baseDisplayLabel;
				baseResult = context.formatSnak( entityOptions, mainSnak, circumstances );

				local parentId = entityId;
				local parentDepth = 0;
				local terminalEntityId = entityId;
				while parentId ~= nil and parentDepth < maxGeoChainDepth do
					parentDepth = parentDepth + 1;
					local newParentSnaks, isCountryResult =
						getParentsInBoundariesSnak( context, parentId, boundaries );
					if not newParentSnaks or #newParentSnaks == 0 then
						parentId = nil;
					elseif #newParentSnaks == 1 then
						local parentSnak = newParentSnaks[ 1 ];
						parentId = parentSnak.datavalue.value.id;
						
						local hasLoop = false
						for _, parentEntityId in pairs(parentEntityIds) do
							if parentEntityId == parentId then
								hasLoop = true
							end
						end
						
						if hasLoop then
							if p.config and p.config.catLoopInGeoChains then
								result = result .. p.config.catLoopInGeoChains;
							end
							break
						end
								
						table.insert( parentSnaks, parentSnak );
						table.insert( parentEntityIds, parentId );
						terminalEntityId = parentId;
						if isCountryResult or parentId == directP17EntityId then
							countryNameIds[ parentId ] = true;
							if parentId == directP17EntityId or not isConstituentCountry( parentId ) then
								parentId = nil;
							end
						end
					else
						parentId = nil;
						if p.config and p.config.catAmbiguousGeoChains then
							result = result .. p.config.catAmbiguousGeoChains;
						end
					end
				end

				if isBirthOrDeathPlace and rawP131QualifierCount == 0 and
					hasDirectP17Qualifier and
					terminalEntityId ~= directP17EntityId
				then
					parentSnaks = { mainSnak };
					parentEntityIds = { entityId };
				end
				for _, parentSnak in ipairs( parentSnaks ) do
					table.insert( chainSnaks, parentSnak );
				end

				local firstParentIndex = 2;
				if applyParentChainRules( parentSnaks, parentEntityIds ) then
					removeLastDisplayedPlace();
					firstParentIndex = 1;
				end
				
				do
					local capitalOf = {};
					if skipAdministrativeUnits then
						for _, placeId in ipairs( parentEntityIds ) do
							capitalOf[ placeId ] = getParentsInBoundariesSnakImpl(
								context, placeId, boundaries, { 'P1376' }
							);
						end
					end

					local checkAdministrativeType = false;
					local removeFirstParent = skipAdministrativeUnits and
						#parentSnaks == 2;
					local i = #parentSnaks;
					while i > 1 do
						if skipAdministrativeUnits and checkAdministrativeType and
							parentEntityIds[ i ]
						then
							if isSkippedPlaceTypeCached( parentEntityIds[ i ] ) then
								for _, capitalSnaks in pairs( capitalOf ) do
									for _, capitalSnak in pairs( capitalSnaks ) do
										if capitalSnak.datavalue and
											capitalSnak.datavalue.value.id == parentEntityIds[ i ]
										then
											removeFirstParent = true;
										end
									end
								end
								table.remove( parentSnaks, i );
								table.remove( parentEntityIds, i );
							end
						end
						if skipAdministrativeUnits then
							checkAdministrativeType = true;
						end

						local prevEntityId = parentEntityIds[ i - 1 ];
						local prevLabel = getPlaceLabel( prevEntityId ) or '';
						local nextEntityId = parentEntityIds[ i ];
						local nextLabel = getPlaceLabel( nextEntityId ) or '';
						if p.config and p.config.hideSameLabels == true and prevLabel == nextLabel then
							table.remove( parentSnaks, i );
							table.remove( parentEntityIds, i );
						elseif p.config and p.config.hidePartOfLabels == true and isPartOfNext( prevLabel, ' ' .. nextLabel ) then
							table.remove( parentSnaks, i - 1 );
							table.remove( parentEntityIds, i - 1 );
						elseif skipAdministrativeUnits and p.config and
							p.config.hideUnitsForCapitals == true
						then
							local capitalId = nil;
							for placeId, capitalSnaks in pairs( capitalOf ) do
								for _, capitalSnak in pairs( capitalSnaks ) do
									if capitalSnak.datavalue and
										capitalSnak.datavalue.value.id == parentEntityIds[ i ]
									then
										capitalId = placeId;
									end
								end
							end
							if capitalId or i ~= #parentSnaks then
								table.remove( parentSnaks, 1 );
								table.remove( parentEntityIds, 1 );
							end
						end
						i = i - 1;
						if skipAdministrativeUnits and i == 2 then
							table.remove( parentSnaks, i + 1 );
							table.remove( parentEntityIds, i + 1 );
						end
						if skipAdministrativeUnits and i == 1 and removeFirstParent then
							table.remove( parentSnaks, 1 );
							table.remove( parentEntityIds, 1 );
						end
					end
				end

				if isSkipTopLevel( parentEntityIds[ #parentEntityIds ] ) then
					table.remove( parentSnaks, #parentEntityIds );
					table.remove( parentEntityIds, #parentEntityIds );
				end

				if not hasAdditionalQualifiers then
					for i = firstParentIndex, #parentSnaks, 1 do
						local parentSnak = parentSnaks[ i ];
						local parentId = parentEntityIds[ i ];
						local parentLabel = getPlaceLabel( parentId );
						if parentLabel and parentLabel ~= '' then
							appendFormattedPlace( parentSnak, parentLabel );
						end
					end
				end
			elseif isBirthOrDeathPlace and rawP131QualifierCount == 0 and
				hasDirectP17Qualifier
			then
				appendMatchingCountryChain( entityId );
			end
		end
	end

	if property ~= 'P119' then
		for _, qualifier in ipairs( p17Qualifiers ) do
			local qualifierId = qualifier.datavalue.value.id;
			local qualifierLabel = getPlaceLabel( qualifierId, true );
			if qualifierLabel and qualifierLabel ~= '' then
				appendFormattedPlace( qualifier, qualifierLabel, true );
			end
		end
	end

	if lifetime then
		local candidates = {};
		local chainEnd = lifetime[ 2 ];
		for _, entry in ipairs( displayedPlaceEntries ) do
			local info = placeNameInfo[ entry.entityId ];
			info = info and info[ entry.label ];
			if info and info.currentStart then
				table.insert( candidates, { entry = entry, info = info } );
			end
		end
		if #candidates > 0 then
			local relations = {};
			local renameClaims = {};
			local dissolutionDates = {};
			local function getRenameClaims( entityId, propertyId )
				local key = entityId .. ':' .. propertyId;
				if not renameClaims[ key ] then
					renameClaims[ key ] = mw.wikibase.getAllStatements( entityId, propertyId ) or {};
				end
				return renameClaims[ key ];
			end
			local function getDissolution( entityId )
				if dissolutionDates[ entityId ] == nil then
					local ending;
					for _, claim in ipairs( getRenameClaims( entityId, 'P576' ) ) do
						local dates = claim.rank ~= 'deprecated' and claim.mainsnak and
							context.parseTimeBoundariesFromSnak( claim.mainsnak );
						if dates then ending = min( ending, dates[ 1 ] ); end
					end
					dissolutionDates[ entityId ] = ending or false;
				end
				return dissolutionDates[ entityId ] or nil;
			end
			local countryAnchor, constituentAnchor;
			for _, snak in ipairs( chainSnaks ) do
				if snak.placeClaims then table.insert( relations, snak ); end
				local entityId = snak.datavalue and snak.datavalue.value and snak.datavalue.value.id;
				if entityId then
					local ending = getDissolution( entityId );
					if ending then chainEnd = min( chainEnd, ending ); end
					local constituent = isConstituentCountry( entityId );
					if ( not countryAnchor or constituent and not constituentAnchor ) and
						( constituent or countryNameIds[ entityId ] or isCountryEntityCached( entityId ) )
					then
						countryAnchor, constituentAnchor = entityId, constituent;
					end
				end
			end
			local countryBonds = {};
			local function matchesCountryAtDate( entityId, date )
				local key = entityId .. ':' .. tostring( date );
				if countryBonds[ key ] ~= nil then return countryBonds[ key ]; end
				local seen = {};
				local current = entityId;
				local matches = false;
				for _ = 1, maxGeoChainDepth do
					if seen[ current ] then break; end
					seen[ current ] = true;
					local ending = getDissolution( current );
					if ending and date > ending then break; end
					if current == countryAnchor then matches = true; break; end
					local parents = selectTemporalEntitySnaks(
						context, getRenameClaims( current, 'P131' ), { date, date }, true
					);
					if #parents ~= 1 then
						parents = selectTemporalEntitySnaks(
							context, getRenameClaims( current, 'P17' ), { date, date }, true
						);
					end
					if #parents ~= 1 then break; end
					current = parents[ 1 ].datavalue.value.id;
				end
				countryBonds[ key ] = matches;
				return matches;
			end
			for propertyId, qualifiers in pairs( { P131 = p131Qualifiers, P17 = p17Qualifiers } ) do
				if #qualifiers > 0 and mainEntityId and not burialMainOnly then
					local parents;
					if propertyId == 'P17' then
						parents = getCountriesInBoundariesSnak( context, mainEntityId, boundaries );
					else
						parents = getParentsInBoundariesSnakImpl(
						context, mainEntityId, boundaries, { propertyId }
						);
					end
					for _, parent in ipairs( parents ) do
						local parentId = parent.datavalue.value.id;
						if ( propertyId == 'P131' and p131QualifierIds[ parentId ] or
							propertyId == 'P17' and p17QualifierIds[ parentId ] )
						then
							table.insert( relations, parent );
						end
					end
				end
			end
			local relationDates = {};
			for _, candidate in ipairs( candidates ) do
				local date = candidate.info.currentStart;
				candidate.rejected = date > chainEnd;
				for _, relation in ipairs( relations ) do
					if candidate.rejected then break; end
					local claims = relation.placeClaims;
					relationDates[ claims ] = relationDates[ claims ] or {};
					local matchingIds = relationDates[ claims ][ date ];
					if not matchingIds then
						matchingIds = {};
						for _, parent in ipairs( selectTemporalEntitySnaks(
							context, claims, { date, date }, true
						) ) do
							matchingIds[ parent.datavalue.value.id ] = true;
						end
						relationDates[ claims ][ date ] = matchingIds;
					end
					candidate.rejected = not matchingIds[ relation.datavalue.value.id ];
				end
				if candidate.rejected and countryAnchor and date <= lifetime[ 2 ] and
					matchesCountryAtDate( candidate.entry.entityId, date )
				then
					candidate.rejected = false;
				end
			end
			for _, candidate in ipairs( candidates ) do
				if candidate.rejected then
					local entry = candidate.entry;
					local labelOptions = context.cloneOptions( options );
					labelOptions.text = candidate.info.originalLabel;
					local rendered = context.formatSnak(
						labelOptions, entry.snak, entry.isBase and circumstances or nil
					);
					if rendered then
						if entry.isBase then
							baseResult = rendered;
						else
							local first, last = string.find( result, entry.rendered, 1, true );
							if first then
								result = string.sub( result, 1, first - 1 ) .. rendered .. string.sub( result, last + 1 );
							end
						end
						entry.label = candidate.info.originalLabel;
						entry.rendered = rendered;
					end
				end
			end
		end
	end

	if options[ 'thisLocationOnly' ] and not suppressBasePlace and
		not baseRemovedByRule
	then
		result = baseResult;
	elseif p.config.reverseOrder then
		if baseResult == '' then
			result = string.gsub( result, ',%s*$', '', 1 );
		end
		result = result .. baseResult;
	else
		if baseResult == '' then
			result = string.gsub( result, '^,%s*', '', 1 );
		end
		result = baseResult .. result;
	end
	local displayResult = result;
	if property == 'P119' and displayResult == '' then
		return nil;
	end
	local displayParts = {};
	for _, entry in ipairs( displayedPlaceEntries ) do
		if entry.entityId and entry.rendered and entry.rendered ~= '' then
			table.insert( displayParts, {
				entityId = entry.entityId,
				text = entry.rendered,
			} );
		end
	end
	local canonicalDirectP17EntityId = getCanonicalEntityId( directP17EntityId );
	local lastDisplayPart = displayParts[ #displayParts ];
	local displayInfo = {
		parts = displayParts,
		explicitTerminalCountry = canonicalDirectP17EntityId ~= nil and
			lastDisplayPart ~= nil and
			lastDisplayPart.entityId == canonicalDirectP17EntityId,
	};
	if options.references then
		result = result .. context.formatRefs( options, statement );
	end
	result = result .. fallbackMaintenanceCategory;

	if categorizeByPlace then
		if property == 'P19' then result = result .. getCategory( 'P1464', entriesToLookupCategory ); end
		if property == 'P20' then result = result .. getCategory( 'P1465', entriesToLookupCategory ); end
		if property == 'P119' then result = result .. getCategory( 'P1791', entriesToLookupCategory ); end
	end

	return result, displayResult, displayInfo;
end

function p.formatPlaceProperty( context, options )
	return require( 'Module:Wikidata/Places/property' ).formatPlaceProperty(
		context, options
	);
end

function insertFromSnak( snak, result )
	if not categorizeByPlace then
		return;
	end
	if snak and
		snak.datavalue and
		snak.datavalue.type == 'wikibase-entityid' and
		snak.datavalue.value and
		snak.datavalue.value[ 'entity-type' ] == 'item'
	then
		table.insert( result, snak.datavalue.value.id );
	end
end

function getCategory( propertyId, entriesToLookupCategoryFor )
	if mw.title.getCurrentTitle().namespace == 0 then
		for _, placeId in pairs( entriesToLookupCategoryFor ) do
			local claims = mw.wikibase.getBestStatements(placeId, propertyId);
	
			if claims then
				for _, claim in pairs( claims ) do
					if claim.mainsnak and
						claim.mainsnak and
						claim.mainsnak.datavalue and
						claim.mainsnak.datavalue.type == 'wikibase-entityid'
					then
						local catEntityId = claim.mainsnak.datavalue.value.id;
						local catSitelink = mw.wikibase.getSitelink(catEntityId);
						if (catSitelink) then
							return '[[' .. catSitelink .. ']]';
						end
					end
				end
			end
		end
	end
	return '';
end

local historicNamesProperties = { 'P1813', 'P1448' };

function getLabel( context, entityId, boundaries, lifetime )
	if not entityId then
		return nil;
	end
	if (type(entityId) ~= 'string') then error('incorrect type of entityId argument'); end;
	entityId = getCanonicalEntityId( entityId );
	local currentLabel = mw.wikibase.getLabelByLang( entityId, 'az' );
	if showCurrentLabels[ entityId ] or not boundaries then return currentLabel; end

	local label = nil;
	local labelEnd = nil;
	local currentStart = nil;
	local lastOldNameEnd = nil;
	local today = os.time() * 1000;
	for _, propertyId in ipairs( historicNamesProperties ) do
		local propertyLabel = nil;
		local propertyLabelEnd = nil;
		local preferredLabel = nil;
		local preferredLabelEnd = nil;
		for _, claim in ipairs( mw.wikibase.getAllStatements( entityId, propertyId ) or {} ) do
			local snak = claim.mainsnak;
			local value = snak and snak.snaktype == 'value' and
				snak.datavalue and snak.datavalue.value;
			local hasStart = claim.qualifiers and claim.qualifiers.P580 and
				claim.qualifiers.P580[ 1 ];
			local hasEnd = claim.qualifiers and claim.qualifiers.P582 and
				claim.qualifiers.P582[ 1 ];
			if claim.rank ~= 'deprecated' and type( value ) == 'table' and
				value.language == 'az' and value.text and ( hasStart or hasEnd )
			then
				local startBoundaries = getTimeBoundariesFromQualifiers( context, claim, 'P580' );
				local endBoundaries = getTimeBoundariesFromQualifiers( context, claim, 'P582' );
				if ( not hasStart or startBoundaries ) and ( not hasEnd or endBoundaries ) then
					if ( not startBoundaries or startBoundaries[ 1 ] <= boundaries[ 1 ] ) and
						( not endBoundaries or endBoundaries[ 1 ] >= boundaries[ 1 ] )
					then
						if not propertyLabel then
							propertyLabel = value.text;
							propertyLabelEnd = endBoundaries and endBoundaries[ 1 ];
						end
						if claim.rank == 'preferred' and not preferredLabel then
							preferredLabel = value.text;
							preferredLabelEnd = endBoundaries and endBoundaries[ 1 ];
						end
					end
					if lifetime and value.text == currentLabel and startBoundaries and
						startBoundaries[ 1 ] <= today and
						( not endBoundaries or endBoundaries[ 2 ] >= today )
					then
						currentStart = max( currentStart, startBoundaries[ 1 ] );
					elseif lifetime and value.text ~= currentLabel and endBoundaries and
						endBoundaries[ 1 ] <= today
					then
						lastOldNameEnd = max( lastOldNameEnd, endBoundaries[ 1 ] );
					end
				end
			end
		end
		if not label then
			label = preferredLabel or propertyLabel;
			if preferredLabel then
				labelEnd = preferredLabelEnd;
			else
				labelEnd = propertyLabelEnd;
			end
		end
		if label and not lifetime then return label, { endTime = labelEnd }; end
	end
	currentStart = currentStart or lastOldNameEnd;
	if lifetime and currentLabel and currentStart and
		lifetime[ 1 ] <= currentStart and currentStart <= lifetime[ 2 ]
	then
		return currentLabel, {
			currentStart = label and label ~= currentLabel and currentStart or nil,
			originalLabel = label or currentLabel,
			originalEnd = labelEnd,
		};
	end
	return label or currentLabel, { endTime = labelEnd };
end

p.getLabel = getLabel;

local function calculateEndDateTimestamp( context, options, statement )
	if not context then error( 'context not specified' ) end;
	if not options then error( 'options not specified' ) end;
	if not options.entity then error( 'options.entity missing' ) end;
	if not statement then error( 'statement not specified' ) end;

	if statement.qualifiers and statement.qualifiers.P582 then
		for i, qualifier in ipairs( statement.qualifiers.P582 ) do
			local parsedTime = context.parseTimeFromSnak( qualifier );
			if parsedTime then
				return parsedTime;
			end
		end
	end

	for h, propertyId in pairs( { "P570", "P577", "P576" } ) do
		local dateClaims = context.selectClaims( options, propertyId );
		if dateClaims then
			for i, statement in ipairs( dateClaims ) do
				local parsedTime = context.parseTimeFromSnak( statement.mainsnak );
				if parsedTime then
					return parsedTime;
				end
			end
		end
	end

	return os.time() * 1000;
end

local function deleteTwinAncestors( countryEntityId, propertyId, entityId )
	local badTwins
	if ( countryEntityId == 'Q174193' ) then
		badTwins = {'Q145'}
	elseif ( countryEntityId == 'Q161885' ) then
		badTwins = {'Q174193'}
	elseif ( countryEntityId == 'Q43287' ) then
		badTwins = {'Q41304', 'Q7318', 'Q2415901', 'Q183'}
	elseif ( countryEntityId == 'Q41304' ) then
		badTwins = {'Q7318', 'Q2415901', 'Q183'}
	elseif ( countryEntityId == 'Q7318' ) then
		badTwins = {'Q2415901', 'Q183'}
	elseif ( countryEntityId == 'Q2415901' ) then
		badTwins = {'Q183'}
	elseif ( countryEntityId == 'Q696908' ) then
		badTwins = {'Q207272', 'Q211274', 'Q36'}
	elseif ( countryEntityId == 'Q207272' ) then
		badTwins = {'Q211274', 'Q36'}
	elseif ( countryEntityId == 'Q211274' ) then
		badTwins = {'Q36'}
	elseif ( countryEntityId == 'Q203493' ) then
		badTwins = {'Q842794', 'Q218'}
	elseif ( countryEntityId == 'Q842794' ) then
		badTwins = {'Q218'}
	elseif ( countryEntityId == 'Q838261' ) then
		badTwins = {'Q37024'}
	else
		return true;
	end

	local listforcheck
	if propertyId == 'P1532' then
		listforcheck = mw.wikibase.getAllStatements( entityId, propertyId );
	else
		listforcheck = mw.wikibase.getBestStatements( entityId, propertyId );
	end

	for _, claim in pairs( listforcheck ) do
		if ( claim and claim.mainsnak
			and claim.mainsnak.datavalue
			and claim.mainsnak.datavalue.value
			and claim.mainsnak.datavalue.value.id ) then
			local actualId = claim.mainsnak.datavalue.value.id;
			for index, value in ipairs(badTwins) do
				if ( value == actualId ) then
            		return false;
        		end
			end
		end
	end
	return true; 
end

function p.formatCountryClaimWithFlag( context, options, statement )
	if not context then error('context not specified') end;
	if not options then error('options not specified') end;
	if not options.entity then error('options.entity is missing') end;
	if not statement then error('statement not specified') end;

	if not statement.mainsnak or
		not statement.mainsnak.datavalue or
		not statement.mainsnak.datavalue.value or
		not statement.mainsnak.datavalue.value.id
	then
		local result = context.formatStatementDefault( context, options, statement );
		if not result then
			return '';
		end
		return '<span class="country-name">' .. result .. '</span>';
	end
	
	local countryEntityId = statement.mainsnak.datavalue.value.id;
	local endDateTimestamp = calculateEndDateTimestamp( context, options, statement );
	local boundaries = getTimeBoundariesFromProperties( context, {'P570', 'P577', 'P571'} );

	if deleteTwinAncestors( countryEntityId, string.upper(options.property), options.entity.id ) then

		local countryOptions = context.cloneOptions( options );
		if not countryOptions['text'] or countryOptions['text'] == '' then
			countryOptions['text'] = getLabel( context, countryEntityId, boundaries );
		end

		local flag = Flags.getFlag( context, countryEntityId, endDateTimestamp );
		
		if context.formatStatementDefault( context, countryOptions, statement ) ~= nil then
		if flag then
			return flag .. '&nbsp;<span class="country-name">' ..
				context.formatStatementDefault( context, countryOptions, statement ) ..
				'</span>';
			end
		end

		if context.formatStatementDefault( context, countryOptions, statement ) ~= nil then
		return '<span class="country-name">' ..
			context.formatStatementDefault( context, countryOptions, statement ) ..
			'</span>';
		else
		return nil;
		end
	end
end

return p;