--[[----------------------------------------------------------------------------

ADOBE CONFIDENTIAL
------------------
Copyright 2025 Adobe
All Rights Reserved.

NOTICE: All information contained herein is, and remains
the property of Adobe and its suppliers, if any. The intellectual
and technical concepts contained herein are proprietary to Adobe
and its suppliers and are protected by all applicable intellectual
property laws, including trade secret and copyright laws.
Dissemination of this information or reproduction of this material
is strictly forbidden unless prior written permission is obtained
from Adobe.

------------------------------------------------------------------------------]]

local KSTableUtils = require 'KSTableUtils'

--------------------------------------------------------------------------------

local KSTableUtils_derefOpt = KSTableUtils.derefOpt

--------------------------------------------------------------------------------

--[[

The following weights and logic was ported from the Best Photos algorithm,
provided by Lightroom Web:

https://git.corp.adobe.com/ponysaurus/ponysaurus/blob/master/src/shared_utils/StoryHelper.coffee

The algorithm works something like this:

	- A request to Oz is made to cluster an album. Oz groups photos by time and similarity.

	- For each photo, we calculate an importance value based on ML-data
		(aesthetics, blur, 'best frame' score). Oz calculates this for iOS and Android.
		On Web we calculate it ourselves (importance weights are down at the bottom of the file).

	- For each cluster, the most important photo is chosen and added to a list,
		ordered by importance.

	- When the user requests, say, 10 "best photos", we show them the 10 most
		important photos from that ordered list.

	- ML-data for assets can be requested from Oz somehow (for LrWeb, Oz has set
		things up to embed it automatically based on our client ID, LightroomMobileWeb1)

]]--

--------------------------------------------------------------------------------

-- Importance weights for different aspects of photo scoring

local kImportanceWeights = {

	aesthetics = {
		balancing = 0,
		blur = 0,
		content = 0,
		dof = 0,
		emphasis = 0,
		harmony = 0,
		lighting = 0,
		repetition = 0,
		rot = 0,
		score = 1.0, -- Main aesthetic score
		symmetry = 0,
		vivid = 0
	},
	
	-- Weights for different photo attributes

	flags = {
		pick = 50,
		reject = -20,
		unflagged = 0
	},
	
	-- Base importance multipliers

	aestheticScore = 0.3,
	hasEdits = 0,
	rating = {
		[ 0 ] = 0,
		[ 1 ] = 5,
		[ 2 ] = 10,
		[ 3 ] = 20,
		[ 4 ] = 40,
		[ 5 ] = 80
	},
	hasTitle = 5,
	hasCaption = 5,
	
	-- Technical quality weights

	frameScore = 10,
	faceScore = 1,
	faceScoreOutlierMin = 0.90,
	faceScoreOutlier = 2,
	blurScore = 1,
	
	-- Blur detection weights

	blur = {
		desired = 0.8,
		undesired = -0.2,
		clear = 0,
		acceptable = 0
	},
	
	-- Eye quality thresholds

	eyeQuality = {
		min_faces = 1,
		max_faces = 3,
		min_sharpness = 75,
		eye_blur_multiplier = { 0, 0.5, 1, 1.5, 1.8 },
		retina_visible_prob = 0,
		sclera_visible_prob = 0
	}

}

--------------------------------------------------------------------------------
--@mark WFCatPhotoScoring
--------------------------------------------------------------------------------

local WFCatPhotoScoring = {}

--------------------------------------------------------------------------------

--[[!

	\brief Calculates a combined aesthetic rating based on various aesthetic metrics.

	\param docValue (table)

	\return (number) A combined aesthetic rating between 0 and 1, where higher values
		indicate better aesthetic quality.

--]]

function WFCatPhotoScoring.calculateCombinedAestheticRating( docValue )

	local aesthetics = KSTableUtils_derefOpt( docValue, 'payload', 'aesthetics' )

	if not aesthetics then
		return 0
	end
	
	local total = 0

	for key, value in pairs( aesthetics ) do

		if type( value ) == 'number' and key ~= 'version' and kImportanceWeights.aesthetics[ key ] ~= 0 then
			total = total + value * kImportanceWeights.aesthetics[ key ]
		end

	end
	
	return math.max( 0, total )

end

--------------------------------------------------------------------------------

--[[!

	\brief Calculates the base importance score for a photo based on user interactions and metadata.

	\param docValue (table)

	\return (number) A base importance score that combines aesthetic quality with
		user interactions and metadata presence.

--]]

function WFCatPhotoScoring.calculateBaseImportance( docValue )

	local importance = 0
	
	-- Add aesthetic score
	importance = importance + kImportanceWeights.aestheticScore *
			WFCatPhotoScoring.calculateCombinedAestheticRating( docValue )
	
	-- Add user interaction scores
	local payload = docValue.payload or {}
	local xmp = KSTableUtils_derefOpt( payload, 'xmp' )
	
	-- Check reviews for flags
	local reviews = KSTableUtils_derefOpt( payload, 'reviews' )
	if reviews then
		for _, review in pairs( reviews ) do
			if review.flag then
				importance = importance + kImportanceWeights.flags[ review.flag ]
				break
			end
		end
	end
	
	-- Check ratings
	local ratings = KSTableUtils_derefOpt( payload, 'ratings' )
	if ratings then
		for _, rating in pairs( ratings ) do
			if rating.rating and rating.rating > 0 then
				importance = importance + kImportanceWeights.rating[ rating.rating ]
				break
			end
		end
	end
	
	-- Check title and caption
	local dc = KSTableUtils_derefOpt( xmp, 'dc' )
	if dc then
		if dc.title and dc.title ~= "" then
			importance = importance + kImportanceWeights.hasTitle
		end
		if dc.description and dc.description ~= "" then
			importance = importance + kImportanceWeights.hasCaption
		end
	end
	
	-- Check for edits ( same logic as WFCatSearchByEdited in LrD )

	local userUpdated = KSTableUtils_derefOpt( payload, 'develop', 'userUpdated' )

	if userUpdated then
		importance = importance + kImportanceWeights.hasEdits
	end

	return importance

end

--------------------------------------------------------------------------------

--[[!

	\brief Calculates the importance score based on blur detection results.

	\param docValue (table)

	\return (number) A blur importance score that rewards desired blur and
		penalizes undesired blur.

--]]

function WFCatPhotoScoring.calculateBlurImportance( docValue )

	local blurScores = KSTableUtils_derefOpt( docValue, 'payload', 'blur' )
		-- SPAM [jandrew 2025-04-09]: The 'blur' key is not normally in the payload
		-- for sync clients. The POC yjsy I created for the Organize Assistant pretends
		-- to be LrW in order to access certain fields. I wanted to keep this
		-- logic in place, in case sync clients eventually get access.

	if not blurScores then
		return 0
	end

	local importance = 0
	
	for key, weight in pairs( kImportanceWeights.blur ) do

		if type( weight ) == 'number' and blurScores[ key ] then
			importance = importance + blurScores[ key ] * weight
		end

	end
	
	return importance

end

--------------------------------------------------------------------------------

--[[!

	\brief Calculates the importance score based on eye quality metrics.

	\param docValue (table)

	\return (number) An eye quality importance score that rewards sharp eyes
		and visible eye features.

--]]

function WFCatPhotoScoring.calculateEyeQualityImportance( docValue )

	local eyeScores = KSTableUtils_derefOpt( docValue, 'payload', 'eye_quality' )
		-- SPAM [jandrew 2025-04-09]: The 'eye_quality' key is not normally in the payload
		-- for sync clients. The POC that I created for the Organize Assistant pretends
		-- to be LrW in order to access certain fields. I wanted to keep this
		-- logic in place, in case sync clients eventually get access.

	if not eyeScores or eyeScores.eye_sharpness < kImportanceWeights.eyeQuality.min_sharpness then
		return 0
	end
	
	local importance = 0
	
	importance = importance + eyeScores.eye_sharpness * WFCatPhotoScoring.getEyeBlurMultiplier( docValue )
	
	for _, key in ipairs( { 'retina_visible_prob', 'sclera_visible_prob' } ) do

		if eyeScores[ key ] then
			importance = importance + eyeScores[ key ] * kImportanceWeights.eyeQuality[ key ]
		end

	end
	
	return importance

end

--------------------------------------------------------------------------------

--[[!

	\brief Determines the eye blur multiplier based on the number of faces in the photo.

	\param docValue (table)

	\return (number) A multiplier value based on the number of faces:
		- Returns 0 if the number of faces is outside the acceptable range
		- Otherwise returns a predefined multiplier value

--]]

function WFCatPhotoScoring.getEyeBlurMultiplier( docValue )

	local xmp = KSTableUtils_derefOpt( docValue, 'payload', 'xmp' )
	local regions = KSTableUtils_derefOpt( xmp, 'mwg-rs', 'Regions', 'RegionList' )

	local numFaces = 0
	if regions then
		for _, region in pairs( regions ) do
			if region.Type == "Face" then
				numFaces = numFaces + 1
			end
		end
	end

	if numFaces < kImportanceWeights.eyeQuality.min_faces or 
	   numFaces > kImportanceWeights.eyeQuality.max_faces then

		return 0

	end
	
	-- Use default multiplier for now
	return kImportanceWeights.eyeQuality.eye_blur_multiplier[ 3 ]

end

--------------------------------------------------------------------------------

--[[!

	\brief Calculates the overall importance score for a photo based on all available metrics.

	\param docValue (table)

	\return (number) A comprehensive importance score that combines:
		- Base importance (user interactions and metadata)
		- Technical quality (frame and face scores)
		- Blur detection
		- Eye quality assessment

--]]

function WFCatPhotoScoring.calculateImportance( docValue )

	local importance = 0

	-- Add base importance factors

	importance = importance + WFCatPhotoScoring.calculateBaseImportance( docValue )

	-- Add technical quality scores

	local payload = docValue.payload or {}

	if payload.bestFrame then

		local frame = KSTableUtils_derefOpt( payload, 'bestFrame', 'frame' ) or 0

		importance = importance + frame * kImportanceWeights.frameScore

		local faceImportance = KSTableUtils_derefOpt( payload, 'bestFrame', 'face' ) or 0

		if faceImportance >= kImportanceWeights.faceScoreOutlierMin then
			faceImportance = faceImportance * kImportanceWeights.faceScoreOutlier
		else
			faceImportance = faceImportance * kImportanceWeights.faceScore
		end

		importance = importance + faceImportance

	end
	
	-- Add blur detection score

	importance = importance + WFCatPhotoScoring.calculateBlurImportance( docValue )

	-- Add eye quality score

	importance = importance + WFCatPhotoScoring.calculateEyeQualityImportance( docValue )

	return importance

end

--------------------------------------------------------------------------------

return WFCatPhotoScoring
