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

WFCatVersions.lua

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

ADOBE CONFIDENTIAL
------------------
Copyright 2020 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.

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

	-- Rx
local Rx = require 'Rx'
local Observable = Rx.Observable

	-- WF.core.utils
local KSDate = require 'KSDate'
local KSIdGenerator = require 'KSIdGenerator'
local KSStringUtils = require 'KSStringUtils'
local KSTableUtils = require 'KSTableUtils'
local WFArguments = require 'WFArguments'
local WFLogger = require 'WFLogger'
local WFPlatformInfo = require 'WFPlatformInfo'
local WFRxRunUntilDone = require 'WFRxRunUntilDone'
local WFTableMerge = require 'WFTableMerge'

	-- WF.oz_client.managed_catalog
local WFCatAssetUtils = require 'WFCatAssetUtils'
local WFCatCollaboration = require 'WFCatCollaboration'
local WFCatCopies = require 'WFCatCopies'
local WFCatDelete = require 'WFCatDelete'
local WFCatIndexUtils = require 'WFCatIndexUtils'
local WFCatOzErrors = require 'WFCatOzErrors'
local WFCatTrashUtils = require 'WFCatTrashUtils'
local WFCatUtils = require 'WFCatUtils'

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

local logger = WFLogger( 'WFCatVersions' )

local WFArguments_check = assert( WFArguments.check )

local KSTableUtils_derefOpt = assert( KSTableUtils.derefOpt )

local KSTableUtils_setNested = assert( KSTableUtils.setNested )

local WFCatAssetUtils_getLocalExternalDevelopSettingsPath = assert( WFCatAssetUtils.getLocalExternalDevelopSettingsPath )

local WFCatIndexUtils_getOrientationWidthAndHeight = assert( WFCatIndexUtils.getOrientationWidthAndHeight )

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

local OZ_MAX_VERSION_NAME_CHARS = 250
local kVersionSubtypeEdit = 'edit'
local kSpaceEditor = 'editor'

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

local function intToBool( zeroOrOne )
	return zeroOrOne ~= 0
end

--============================================================================--

-- @mark WFCatVersions namespace

local WFCatVersions = {}

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

function WFCatVersions.generateIndexSchema( indexStoreSchema )

	WFArguments_check( 'WFRxIndexStoreSchema' )

	indexStoreSchema:addTable {

		sql = [[ CREATE TABLE versions(
					localId,
					assetId,
					creationDate,
					name, 
					subtype, 
					importedByUser,
					importedByFormFactor,
					originalSha256,
					embeddedDevelopSettings,
					hasExternalizedDevelopSettings,
					externalXmpRelativePath,
					croppedWidth, 
					croppedHeight, 
					userOrientation,
					createdManually,
					spaceId,
					incomplete
				) ]],
				
		relevantTypeSubtypeTuples = {
			{ 'version', '*' },
		},

		hasOneRowPerDocument = true,
		
		map = function( docValue, emit )

			if not docValue._deleted then
			
				local localId = docValue._localId
			
				local spaceId = docValue._spaceId
			
				-- SPAM [jandrew 2020-04-20]: https://jira.corp.adobe.com/browse/LRMA-4199993
				-- When a Version documention is downloaded from Oz, we have special handling 
				-- inside of WFOzDocUtils.remapBasicOzDocument which moves the Asset section 
				-- of the document. This is the same remapping that is done for comments, favorites, 
				-- etc. If an older client downloads a Version document created by a new client,
				-- it's not remapped. So then when the older client upgrades to a Version-doc aware build,
				-- the previously synced local Versions docs still have their Asset sections under
				-- _serverProvidedMetadata. There is a similar issue for the formFactor
				
				local assetSubsection = KSTableUtils_derefOpt( docValue, 'asset' ) 
						or KSTableUtils_derefOpt( docValue, '_serverProvidedMetadata', 'asset' )
				local importedByFormFactor = KSTableUtils_derefOpt( docValue, 'importedOnDevice', 'formFactor' )
						or KSTableUtils_derefOpt( docValue, '_serverProvidedMetadata', 'importedOnDevice', 'formFactor' )
				
				local assetId = KSTableUtils_derefOpt( assetSubsection, 'id' )
				local originalSha256 = KSTableUtils_derefOpt( assetSubsection, 'sha256' )
			
				local creationDate = KSTableUtils_derefOpt( docValue, 'userCreated' )
				local versionName = KSTableUtils_derefOpt( docValue, 'name' )
				local versionType = docValue.subtype

				local importedByUser = KSTableUtils_derefOpt( docValue, '_serverProvidedMetadata', 'importedByUser', 'id' )
				
				 
				local developSection = KSTableUtils_derefOpt( docValue, 'develop' )
				
				local xmpCameraRaw = KSTableUtils_derefOpt( developSection, 'xmpCameraRaw' )
				local croppedWidth = KSTableUtils_derefOpt( developSection, 'croppedWidth' )
				local croppedHeight = KSTableUtils_derefOpt( developSection, 'croppedHeight' )
				local userOrientation = KSTableUtils_derefOpt( developSection, 'userOrientation' )
	
				local isExternalized = type( xmpCameraRaw ) == 'table'
				
				local hasXmpDevelopUrl = not not KSTableUtils_derefOpt( docValue, '_serverProvidedMetadata', 'links', '/rels/xmp/develop', 'href' ) 

				local embeddedDevelopSettings
				
				if not isExternalized then
					embeddedDevelopSettings = xmpCameraRaw
				end
				
				local externalXmpRelativePath = WFCatAssetUtils_getLocalExternalDevelopSettingsPath( docValue )
					-- This value is only present for versions with externalized develop XMP, and only applies
					-- to Android & iOS
					
				local createdManually = KSTableUtils_derefOpt( docValue, 'createdManually' ) and 1 or 0
					
				local incomplete = isExternalized and not externalXmpRelativePath and not hasXmpDevelopUrl
				incomplete = incomplete and 1 or 0
					-- If the settings have been externalized, and we have no external settings locally and
					-- Oz doesn't have them either, then the settings have yet to be uploaded.
					
				isExternalized = isExternalized and 1 or 0
					
				emit( localId, assetId, creationDate, versionName, versionType, importedByUser, importedByFormFactor,
						originalSha256, embeddedDevelopSettings, isExternalized, externalXmpRelativePath, croppedWidth, 
						croppedHeight, userOrientation, createdManually, spaceId, incomplete )
						
			end
								
		end

	}
	
	indexStoreSchema:addIndex [[CREATE INDEX versions_assetId ON versions( assetId )]]

	indexStoreSchema:addIndex [[CREATE INDEX versions_spaceId ON versions( spaceId )]]

	indexStoreSchema:addIndex [[CREATE INDEX versions_localId ON versions( localId )]]

	indexStoreSchema:addIndex [[CREATE INDEX versions_incomplete ON versions( incomplete )]]

end

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

local function maybeMakeVersionNameValid( versionName )

	if KSStringUtils.lengthOfUTF8String( versionName ) > OZ_MAX_VERSION_NAME_CHARS then
	
		logger:warnf( 'Version names are limited to %d chars, truncating: %s', OZ_MAX_VERSION_NAME_CHARS, versionName )
		versionName = KSStringUtils.UTF8Substring( versionName, 1, OZ_MAX_VERSION_NAME_CHARS )
		
	end
	
	return versionName

end

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

local function checkUserAuthorization( catalog, docId )

	return catalog:docStore():docValue( docId )
		
		:flatMap( function( docValue )
			
			if not docValue._spaceId then
			
				return Observable.just( true )
			
			else
			
				return catalog:indexStore():queryOnce( 
					{ 
						sql = [[SELECT role
									FROM space
									INNER JOIN space_user ON space_user.fullDocId == space.spaceUserId
									WHERE space.docId == ?]],
						quiesceUntilUpToDate = true,
						docValue._spaceId
					} )
					
					:map( function( rows )
					
						local role = KSTableUtils_derefOpt( rows, 1, 1 ) or ""
						
						role = WFCatCollaboration.downgradeRoleIfNotSupported( role )
						
						if role == kSpaceEditor then
							return true
						end
							
						return error( string.format( "User doesn't have authorized role for space %s, with role %s",
									docValue._spaceId, role ) )
						
					end )
			
			end
			
		end )

end

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

local function returnTrue()
	return true
end

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

local function copyExternalXmpFileAndAddVersionToDocStore( catalog, sourceDocValue, versionDocValue )

	local versionId = versionDocValue._id

	local copyOptions = {
		needToCopyOriginal = false,
		needToCopyProxy = false,
		needToCopyRenditions = false,
		needToCopyExternalDevelopSettings = true,
		needToCopyXmpSidecar = false
	}
		
	return Observable.just( copyOptions )
	
		:map( function( copyOptions )
		
			-- The following API call copies the external XMP develop settings from the
			-- existing asset document to the versions document.
					
			return WFCatCopies.calculateCopyPathsAndUpdateDocValue( catalog, sourceDocValue, versionDocValue, copyOptions )
		
		end )
		
		:flatMap( function( modifiedVersionDocValue, filesToCopy )
		
			-- On Desktop, filesToCopy should always be an empty table. WFCatCopies.calculateCopyPathsAndUpdateDocValue
			-- is platform-aware and knows that xmp settings are shared between documents on Desktop.
		
			-- If necessary, copy the external xmp to the new location.
			
			local maybeCopyExternalXmpFile = Observable.just( true )
		
			if #filesToCopy > 0 then
			
				-- Note, this block is mobile only.
				
				local xmpFileInfo = filesToCopy[ 1 ]
				
				local externalXmpPath = xmpFileInfo.srcFilePath
				local externalXmpCopyPath = xmpFileInfo.dstFilePath
				
				if externalXmpPath and externalXmpCopyPath then
					maybeCopyExternalXmpFile = WFCatCopies.copyExternalXmpFile( externalXmpPath, externalXmpCopyPath )
				end
				
			end
		
			return maybeCopyExternalXmpFile
		
				:flatMap( function()
				
					return catalog:docStore():setDocValue( modifiedVersionDocValue )
					
				end )
				
		end )
		
		:catch( function( errorMsg )
		
			logger:warnf( "Error copying version XMP: %s", errorMsg )
		
			return catalog:docStore():setDocValue( versionDocValue )
		
		end )
		
		:map( function()
		
			return versionId
			
		end )

end

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

local function truncateCroppedWidthAndHeightSettingsForVersion( versionDoc )

	-- WF-1363: Oz has reported a non trivial number of Version document uploads 
	-- that contain invalid croppedWidth/croppedHeight parameters. These values
	-- are being supplied as floats, when only integers are allowed. This results
	-- in `PayloadValidationError` errors. The following code converts the fields
	-- to integers, and deletes the field entirely as a fall back.

	-- croppedWidth
	
	local croppedWidth = KSTableUtils_derefOpt( versionDoc, "develop", "croppedWidth" )
	
	if type( croppedWidth ) == 'number' then
		croppedWidth = math.floor( croppedWidth )
	else
		croppedWidth = nil
	end
	
	KSTableUtils_setNested( versionDoc, croppedWidth, 'develop', 'croppedWidth' )

	-- croppedHeight
	
	local croppedHeight = KSTableUtils_derefOpt( versionDoc, "develop", "croppedHeight" )

	if type( croppedHeight ) == 'number' then
		croppedHeight = math.floor( croppedHeight )
	else
		croppedHeight = nil
	end
	
	KSTableUtils_setNested( versionDoc, croppedHeight, 'develop', 'croppedHeight' )
	
	return versionDoc
	
end

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

local function maybeAddOzErrorOverlay( sourceDocValue, versionDocValue )

	-- WF-1644: If the source Asset document is marked with an error preventing
	-- it's upload, then mark the Version document with the same error; there's
	-- no point in attempting to upload a Version that references an unsynced
	-- Asset document.
	
	local shouldSkipUpload, reasonToSkipUpload =
			WFCatOzErrors.shouldSkipUploadDueToError( sourceDocValue )
	
	if shouldSkipUpload then
	
		versionDocValue = WFCatOzErrors.addOverlayForOzErrorSubtype(
			versionDocValue, reasonToSkipUpload )
	
	end

end

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

--[[!
	\brief Create a new version based on the current assets' develop settings.
	
	\param catalog (WFCatalog)
	\param assetId (string|number) The local or full doc ID of an asset.
	\param createdManually (boolean) Represents if the version was explicitly created
		by the user, or if it was automatically created.
	\param versionName (optional string) A name for the version. If not provided,
		the name will be interpreted from the creation date. Cannot be an empty string.
	
	\return <code>Observable</code> Resolves to the version ID of the created Version doc
		on success, or throws on error when user is not authorized to create version.
--]]

function WFCatVersions.createVersionForAsset( catalog, assetId, createdManually, versionName )

	WFArguments_check( "WFCatalog", "+string|number", 'boolean', '?string' )
	
	return checkUserAuthorization( catalog, assetId )
	
		:flatMap( function()
		
			logger:infof( "Creating a new version for asset %s named %s", assetId, versionName )

			local docStore = catalog:docStore()
			
			local versionId = KSIdGenerator.uuidInLowercaseHex()
			
			local baseDocValue = WFCatUtils.createBaseOverlayWithUserCreatedFields( versionId )
		
			if versionName == '' then
				versionName = nil
			end
			
			local formFactor = WFPlatformInfo.getAppDeviceClass()
			
			local versionDocValue = {
		
				type = 'version',
				subtype = 'edit',
				name = versionName,
				createdManually = createdManually,
				importedOnDevice = {
					formFactor = formFactor
				}
				
			} 
				
			versionDocValue = WFTableMerge.applyOverlay( versionDocValue, baseDocValue )
		
			return WFCatTrashUtils.getTrashOrRestoredDocFromNewId( docStore, assetId )
				-- This guard against an Android Race condition: https://jira.corp.adobe.com/browse/LRMA-4199669
				-- In case the asset was trashed quickly after editing, make sure we're creating the version
				-- against the correct trashed asset.
				
				:flatMap( function( sourceDocValue )
				
					versionDocValue.asset = {
						id = sourceDocValue._id
					}
				
					local sha256 = KSTableUtils_derefOpt( sourceDocValue, 'importSource', 'sha256' )
					
					if not sha256 then
						error( "The provided asset does not have a sha256 in the importSource." )
					end
					
					versionDocValue.asset.sha256 = sha256
					
					local developSettings = KSTableUtils_derefOpt( sourceDocValue, 'develop' )
				
					if not developSettings then
						error( "The provided asset does not have a develop payload." )
					end
					
					-- WF-1640 Nimbus is sending develop.profiles as {} in the payload
					if type( developSettings.profiles ) == 'table' and not next( developSettings.profiles ) then
						developSettings.profiles = nil
					end
				
					versionDocValue._spaceId = sourceDocValue._spaceId
				
					versionDocValue.develop = developSettings
					
					versionDocValue = truncateCroppedWidthAndHeightSettingsForVersion( versionDocValue )
					
					maybeAddOzErrorOverlay( sourceDocValue, versionDocValue )
					
					return copyExternalXmpFileAndAddVersionToDocStore( catalog, sourceDocValue, versionDocValue )
				
				end )
					
		end )
		
		:call( WFRxRunUntilDone )

end

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

--[[!
	\brief Create a new version based on an existing version.
	
	\param catalog (WFCatalog)
	\param versionId (string|number) The local or full doc ID of the version to duplicate.
	\param createdManually (boolean) Represents if the version was explicitly created
		by the user, or if it was automatically created.
	\param versionName (optional string) A name for the version. If not provided,
		the name will be interpreted from the creation date.
	
	\return <code>Observable</code> Resolves to the version ID of the created Version doc
		on success, or throws on error when user is not authorized to create version.
--]]

function WFCatVersions.duplicateVersion( catalog, sourceVersionId, createdManually, versionName )

	WFArguments_check( "WFCatalog", "+string|number", 'boolean', '?string' )
	
	return checkUserAuthorization( catalog, sourceVersionId )
	
		:flatMap( function()
	
			logger:infof( "Creating a new version based on version %s named %s", sourceVersionId, versionName )
			
			local docStore = catalog:docStore()
			
			local versionId = KSIdGenerator.uuidInLowercaseHex()
			
			local baseDocValue = WFCatUtils.createBaseOverlayWithUserCreatedFields( versionId )
			
			local formFactor = WFPlatformInfo.getAppDeviceClass()
			
			local versionDocValue = {
		
				type = 'version',
				subtype = 'edit',
				name = versionName,
				createdManually = createdManually,
				importedOnDevice = {
					formFactor = formFactor
				}
				
			} 
			
			versionDocValue = WFTableMerge.applyOverlay( versionDocValue, baseDocValue )
			
			return docStore:docValue( sourceVersionId )
			
				:flatMap( function( sourceDocValue )
				
					local assetSubsection = KSTableUtils_derefOpt( sourceDocValue, 'asset' ) 
							or KSTableUtils_derefOpt( sourceDocValue, '_serverProvidedMetadata', 'asset' )
				
					versionDocValue.asset = assetSubsection
					versionDocValue._spaceId = sourceDocValue._spaceId
					versionDocValue.develop = sourceDocValue.develop
					
					versionDocValue = truncateCroppedWidthAndHeightSettingsForVersion( versionDocValue )
					
					maybeAddOzErrorOverlay( sourceDocValue, versionDocValue )
					
					return copyExternalXmpFileAndAddVersionToDocStore( catalog, sourceDocValue, versionDocValue )
					
				end )
		
		end )

		:call( WFRxRunUntilDone )

end

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

local function applyDevelopSettingsOverridesToVersionDoc( versionDocValue, overrides )

	if overrides.croppedWidth then

		logger:infof( "Overriding cropped width for Version: %d", overrides.croppedWidth )

		KSTableUtils_setNested( 
				versionDocValue, overrides.croppedWidth, 'develop', 'croppedWidth' )

	end

	if overrides.croppedHeight then

		logger:infof( "Overriding cropped height for Version: %d", overrides.croppedHeight )

		KSTableUtils_setNested( 
				versionDocValue, overrides.croppedHeight, 'develop', 'croppedHeight' )

	end

	if overrides.userOrientation then

		logger:infof( "Overriding user orientation for Version: %d", overrides.userOrientation )

		KSTableUtils_setNested( 
				versionDocValue, overrides.userOrientation, 'develop', 'userOrientation' )

	end

	if overrides.cameraProfileHref and overrides.cameraProfileFilename then

		logger:infof( "Overriding camera profile for Version: %s, %s", 
				overrides.cameraProfileHref, overrides.cameraProfileFilename )

		KSTableUtils_setNested( 
				versionDocValue, overrides.cameraProfileHref, 'develop', 'profiles', 'camera', 'href' )

		KSTableUtils_setNested( 
				versionDocValue, overrides.cameraProfileFilename, 'develop', 'profiles', 'camera', 'filename' )

	end

	if overrides.lensProfileHref and overrides.lensProfileFilename then

		logger:infof( "Overriding lens profile for Version: %s, %s", 
				overrides.lensProfileHref, overrides.lensProfileFilename )

		KSTableUtils_setNested( 
				versionDocValue, overrides.lensProfileHref, 'develop', 'profiles', 'lens', 'href' )

		KSTableUtils_setNested( 
				versionDocValue, overrides.lensProfileFilename, 'develop', 'profiles', 'lens', 'filename' )

	end

	return versionDocValue

end

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

local function doesDocIdAlreadyExist( docStore, docId )

	-- Check if the doc Id already exists in the doc store using docIdToLocalId.
	-- docIdToLocalId will return the local doc id for this document or nil
	-- if the full doc id does not exist within the doc store.

	return docStore:docIdToLocalId( docId )

		:map( function( maybeLocalId )

			return not not maybeLocalId

		end )

end

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

--[[!
	\brief Create a new version of a given asset using the provided INLINE XMP 
		develop settings.
	
	\param catalog (WFCatalog)
	\param assetId (string|number) The local or full doc ID of an asset.
	\param createdManually (boolean) Represents if the version was explicitly created
		by the user, or if it was automatically created.
	\param versionName (optional string) A name for the version. If not provided,
		the name will be interpreted from the creation date.
	\param developSettings (table) A table of develop settings used to create the
		version document. Any optional settings not provided will assume the values
		from the associated asset document:
		
			cameraRawXmpStr = < string >
			
			croppedWidth = < optional number >
			croppedHeight = < optional number >
			userOrientation = < optional number >
			cameraProfileHref = < optional string >
			cameraProfileFilename = < optional string >
			lensProfileHref = < optional string > 
			lensProfileFilename = < optional string >
	
			_id = < optional string > id of the version document to be created

	\return <code>Observable</code> The version ID of the created Version doc. This function will
		return an error if the developSettings do not contain the appropriate camera raw xmp
		string, if the string is too long, or if the source asset is missing a develop payload.
--]]

function WFCatVersions.createVersionForAssetWithSettings( catalog, assetId, createdManually, versionName, developSettings )

	WFArguments_check( "WFCatalog", "+string|number", 'boolean', '?string', 'table' )
	
	logger:infof( "Creating a new version for asset %s named %s", assetId, versionName )
	
	local docStore = catalog:docStore()
	
	-- Verify the presence of the inline XMP
	
	local cameraRawXmpStr = developSettings.cameraRawXmpStr
	
	if not cameraRawXmpStr then

		return Observable.throw( "cameraRawXmpStr was not provided in the develop settings." )
		
	end
	
	-- Verify the inline XMP is not over 10kb.
	
	local settingsLength = string.len( cameraRawXmpStr )

	local INLINE_XMP_CAMERA_RAW_MAX_LENGTH = 10 * 1024 --10KB

	if settingsLength > INLINE_XMP_CAMERA_RAW_MAX_LENGTH then

		return Observable.throw( "Inline XMP settings length exceeded. Please use createVersionForAssetWithExternalSettings." )

	end
	
	local doesVersionIdAlreadyExistStream = developSettings._id
			and doesDocIdAlreadyExist( docStore, developSettings._id )
			or Observable.just( false )

	return doesVersionIdAlreadyExistStream

		:flatMap( function( doesVersionIdAlreadyExist )

			if doesVersionIdAlreadyExist then
				return Observable.throw( "The supplied version document ID already exists within the doc store." )
			end

			return WFCatTrashUtils.getTrashOrRestoredDocFromNewId( docStore, assetId )

		end )

		:flatMap( function( sourceDocValue )

			-- Create the Version Document

			local versionId = developSettings._id or KSIdGenerator.uuidInLowercaseHex()

			local baseDocValue = WFCatUtils.createBaseOverlayWithUserCreatedFields( versionId )

			local formFactor = WFPlatformInfo.getAppDeviceClass()

			local versionDocValue = {

				type = 'version',
				subtype = 'edit',
				name = versionName,
				createdManually = createdManually,
				importedOnDevice = {
					formFactor = formFactor
				}

			}

			versionDocValue = WFTableMerge.applyOverlay( versionDocValue, baseDocValue )

			-- Further populate the Version document with info from the asset.
			
			versionDocValue.asset = {
				id = sourceDocValue._id
			}
		
			versionDocValue._spaceId = sourceDocValue._spaceId
		
			local sha256 = KSTableUtils_derefOpt( sourceDocValue, 'importSource', 'sha256' )
			
			if not sha256 then
				error( "The provided asset does not have sha256 in the importSource." )
			end
			
			versionDocValue.asset.sha256 = sha256
			
			-- Use the develop settings from the asset as the base settings..
			
			local assetDevelopSettings = KSTableUtils_derefOpt( sourceDocValue, 'develop' )
		
			if not assetDevelopSettings then
				error( "The provided asset does not have a develop payload." )
			end
		
			versionDocValue.develop = assetDevelopSettings
		
			-- .. Then override them with the user supplied settings. Any field not provided by the caller
			-- will maintain the value from the asset document.
			
			versionDocValue = applyDevelopSettingsOverridesToVersionDoc( versionDocValue, developSettings )
		
			versionDocValue = truncateCroppedWidthAndHeightSettingsForVersion( versionDocValue )
		
			KSTableUtils_setNested( 
					versionDocValue, developSettings.cameraRawXmpStr, 'develop', 'xmpCameraRaw' )
		
			maybeAddOzErrorOverlay( sourceDocValue, versionDocValue )
		
			return docStore:setDocValue( versionDocValue )
		
				:map( function()
		
					return versionId
					
				end )
		
		end )
		
		:call( WFRxRunUntilDone )

end

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

--[[!
	\brief Create a new version of a given asset using externalixd develop settings.
	
	\param catalog (WFCatalog)
	\param assetId (string|number) The local or full doc ID of an asset.
	\param createdManually (boolean) Represents if the version was explicitly created
		by the user, or if it was automatically created.
	\param versionName (optional string) A name for the version. If not provided,
		the name will be interpreted from the creation date.
	\param developSettings (table) A table of develop settings used to create the
		version document. Any optional settings not provided will assume the values
		from the associated asset document:
			
			cameraRawXmpFilePath = < string >
				Please note that the file path must use the appropriate semantics
				based on the current platform. On mobile, this means the path
				must be a relative one.
			cameraRawXmpSha256 = < string >
			cameraRawXmpSize = < number >
			
			croppedWidth = < optional number >
			croppedHeight = < optional number >
			userOrientation = < optional number >
			cameraProfileHref = < optional string >
			cameraProfileFilename = < optional string >
			lensProfileHref = < optional string > 
			lensProfileFilename = < optional string >
			
			_id = < optional string > id of the version document to be created
	
	\return <code>Observable</code> The version ID of the created Version doc. This function will
		return an error if the developSettings do not contain the appropriate camera raw xmp
		data,or if the source asset is missing a develop payload and/or a sha256.
--]]

function WFCatVersions.createVersionForAssetWithExternalSettings( catalog, assetId, createdManually, versionName, developSettings )

	WFArguments_check( "WFCatalog", "+string|number", 'boolean', '?string', 'table' )
	
	logger:infof( "Creating a new version for asset %s named %s", assetId, versionName )
	
	local docStore = catalog:docStore()
	
	-- Verify all the required fields were supplied.
	
	if not developSettings.cameraRawXmpFilePath
		or not developSettings.cameraRawXmpSha256
		or not developSettings.cameraRawXmpSize then

		return Observable.throw( "Required develop XMP fields were not provided in the develop settings." )

	end
	
	local doesVersionIdAlreadyExistStream = developSettings._id
			and doesDocIdAlreadyExist( docStore, developSettings._id )
			or Observable.just( false )

	return doesVersionIdAlreadyExistStream

		:flatMap( function( doesVersionIdAlreadyExist )

			if doesVersionIdAlreadyExist then
				return Observable.throw( "The supplied version document ID already exists within the doc store." )
			end

			return WFCatTrashUtils.getTrashOrRestoredDocFromNewId( docStore, assetId )

		end )

		:flatMap( function( sourceDocValue )

			-- Create the Version Document

			local versionId = developSettings._id or KSIdGenerator.uuidInLowercaseHex()

			local baseDocValue = WFCatUtils.createBaseOverlayWithUserCreatedFields( versionId )

			local formFactor = WFPlatformInfo.getAppDeviceClass()

			local versionDocValue = {

				type = 'version',
				subtype = 'edit',
				name = versionName,
				createdManually = createdManually,
				importedOnDevice = {
					formFactor = formFactor
				}

			}

			versionDocValue = WFTableMerge.applyOverlay( versionDocValue, baseDocValue )

			-- Further populate the Version document with info from the asset.
			
			versionDocValue.asset = {
				id = sourceDocValue._id
			}
		
			versionDocValue._spaceId = sourceDocValue._spaceId
		
			local sha256 = KSTableUtils_derefOpt( sourceDocValue, 'importSource', 'sha256' )
			
			if not sha256 then
				error( "The provided asset does not have sha256 in the importSource." )
			end
			
			versionDocValue.asset.sha256 = sha256
			
			-- Use the develop settings from the asset as the base settings..
			
			local assetDevelopSettings = KSTableUtils_derefOpt( sourceDocValue, 'develop' )
		
			if not assetDevelopSettings then
				error( "The provided asset does not have a develop payload." )
			end
		
			versionDocValue.develop = assetDevelopSettings
		
			-- .. Then override them with the user supplied settings. Any field not provided by the caller
			-- will maintain the value from the asset document.
			
			versionDocValue = applyDevelopSettingsOverridesToVersionDoc( versionDocValue, developSettings )
		
			versionDocValue = truncateCroppedWidthAndHeightSettingsForVersion( versionDocValue )
		
			KSTableUtils_setNested( 
						versionDocValue, {}, 'develop', 'xmpCameraRaw' )
		
			KSTableUtils_setNested( 
						versionDocValue, developSettings.cameraRawXmpSha256, 'develop', 'xmpCameraRaw', 'sha256' )
		
			-- And finally add in the develop XMP localOnly fields.
			
			local sha256FieldName = WFPlatformInfo.isiOS and 'contentId' or 'sha256'

			local localOnly = { 
				files = {
					xmp_develop = {
						path = developSettings.cameraRawXmpFilePath,
						fileSize = developSettings.cameraRawXmpSize,
						[ sha256FieldName ] = developSettings.cameraRawXmpSha256,
					}
				}
			}

			versionDocValue._localOnly = localOnly
			
			maybeAddOzErrorOverlay( sourceDocValue, versionDocValue )
		
			return docStore:setDocValue( versionDocValue )
		
				:map( function()
		
					return versionId
					
				end )
		
		end )
		
		:call( WFRxRunUntilDone )

end

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

--[[!
	\brief Get the count of existing auto versions for an asset.
	
	\param catalog (WFCatalog)
	\param assetId (string|number) The local or full doc ID of the asset.
	
	\return <code>Observable</code> The number of auto versions for the asset.
--]]

function WFCatVersions.getAutoVersionCountForAsset( catalog, assetId )

	WFArguments_check( "WFCatalog", "+string|number" )
	
	return catalog:indexStore():query(
		{
			sql = [[ SELECT COUNT(1)
					FROM versions
					WHERE assetId = ?
						AND createdManually = 0
						AND subtype = ? ]],
			assetId, 
			kVersionSubtypeEdit
		} )
		:take( 1 )
		:map( function( result )
			return result and result[ 1 ] and result[ 1 ][ 1 ]
		end )

end

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

local function getVersionsStreamForAsset( catalog, assetId, includeFakeImportStateVersion, manualVersionsOnly, autoVersionsOnly )

	WFArguments_check( "WFCatalog", "+string|number", '?boolean', '?boolean', '?boolean' )
			
	-- The type and importedByUser fields are included to allow clients
	-- to filter this data.
	
	local fullDocIdStream = Observable.just( assetId )
	
	if type( assetId ) == 'number' then
		fullDocIdStream = catalog:docStore():localIdToDocId( assetId )
	end
	
	local versionsQuery = function( docId )
		
		local manualStatment = ""
		
		if manualVersionsOnly then
			manualStatment = "AND createdManually = 1"
		elseif autoVersionsOnly then
			manualStatment = "AND createdManually = 0"
		end
		
		return catalog:indexStore():query(
			{
				sql = string.format( 
						[[ SELECT versions.docId, creationDate, name, subtype, 
								importedByUser, feedback_user.fullname, importedByFormFactor, 
								embeddedDevelopSettings, externalXmpRelativePath, 
								croppedWidth, croppedHeight, userOrientation,
								createdManually, hasExternalizedDevelopSettings
							FROM versions
								LEFT OUTER JOIN feedback_user ON versions.importedByUser == feedback_user.userId
							WHERE assetId = ?
								AND subtype = ?
								AND incomplete = 0 
								%s
							ORDER BY creationDate ASC ]], 
						manualStatment ),
				calm = 1,
				docId, 
				kVersionSubtypeEdit
			} )
	
	end 
	
	return fullDocIdStream
	
		:flatMap( function( docId )
		
			return Observable.combineLatest( 
					versionsQuery( docId ), 
					catalog:docStore():docValue( docId ) )
			
		end )
		
		:map( function( versionRows, assetDocValue )
		
			local versions = {}
		
			if includeFakeImportStateVersion then
			
				-- Create version Zero. This represents the asset in it's original, imported state.
				
				local originalImportDate = KSTableUtils_derefOpt( assetDocValue, 'importSource', 'importTimestamp' )
				
				local originalOrientation, originalWidth, originalHeight 
						= WFCatIndexUtils_getOrientationWidthAndHeight( assetDocValue )
				
				local originalImportedByUser 
						= KSTableUtils_derefOpt( assetDocValue, '_serverProvidedMetadata', 'importedByUser', 'id' )
				
				local versionZero = {
					date = originalImportDate,
					type = 'edit',
					author = originalImportedByUser,
					createdManually = false,
					croppedWidth = originalWidth,
					croppedHeight = originalHeight,
					userOrientation = originalOrientation,
					isFakeImportStateVersion = true
				}
				
				versions[ #versions + 1 ] = versionZero
			
			end
		
			local catalogId = catalog:ozCatalogId()
			
			for _, row in ipairs( versionRows ) do
			
				-- SPAM [jandrew 2021-05-24]: LRMA-4203003: this prevents the get*VersionsByAssetId APIs
				-- from returning incomplete version documents caused by permission issues. In most cases,
				-- clients will not call these APIs unless the user's current role is an editor, or
				-- if they're the owner of the asset. However, if the associated models are left open
				-- during a role downgrade from editor -> contributor/viewer, the API can fire with 
				-- versions that are technically incomplete. To detect this, we look for the creation
				-- date field. This field is always included in complete version documents, but is omitted
				-- from version documents sent to viewers and contributors. We look at this specific field
				-- instead of role field in the spaceUser document because, after a role downgrade, we may
				-- receive the updated version documents BEFORE we receive the role update in the spaceUser
				-- document.
				
				local creationDate = row[ 2 ]
			
				if creationDate then
				
					local externalXmpRelativePath = row[ 9 ]
				
					local externalizedDevelopSettingsPath 
							= WFCatAssetUtils.absolutePathToDevelopSettings( catalogId, externalXmpRelativePath )
				
					local version = {
						id = row[ 1 ],
						date = creationDate,
						name = row[ 3 ],
						type = row[ 4 ],
						author = row[ 5 ],
						authorFullname = row[ 6 ],
						authorFormFactor = row[ 7 ],
						embeddedDevelopSettings = row[ 8 ],
						hasExternalizedDevelopSettings = intToBool( row[ 14 ] ),
						externalizedDevelopSettingsPath = externalizedDevelopSettingsPath,
						croppedWidth = row[ 10 ],
						croppedHeight = row[ 11 ],
						userOrientation = row[ 12 ],
						createdManually = intToBool( row[ 13 ] )
					}
				
					versions[ #versions + 1 ] = version
				
				end
				
			end
			
			return versions
			
		end )

end

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

--[[!
	\brief Fetch All Versions of a particular asset, sorted chronologically.
		(Newest first) 
	
	\param catalog (WFCatalog)
	\param assetId (string|number) The local or full doc ID of an asset.
	\param includeFakeImportStateVersion (optional boolean) If true, a fake version
		representing the original/reset state of the photo upon import is included
		in the response.
		
	\return <code>Observable</code> An observable stream that fires with a table
		containing all versions of the specified asset. Example:
		
		{
			{
				id = <version ID>,
				date = <creation date>,
				name = <version name>,
				type = <"version" or eventually "remix">
				author = <author full name>
				authorFormFactor = <"Tablet" or "Phone" or "Computer" or ...>
				embeddedDevelopSettings = < xmp settings string >
					-- This is only populated for assets with internalized develop settings.
				externalizedDevelopSettingsPath = <path to the externalized develop settings>
					-- If the settings are externalized but have not been downloaded,
					-- this path will be nil.
				croppedWidth = < number >
				croppedHeight = < number >
				userOrientation = < number >
				createdManually = < boolean >
			},
			...
		}
		
--]]

function WFCatVersions.getAllVersionsStreamForAsset( catalog, assetId, includeFakeImportStateVersion )

	logger:infof( "Fetching all versions for asset %s", assetId )

	return getVersionsStreamForAsset( catalog, assetId, includeFakeImportStateVersion )

end 

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

--[[!
	\brief Fetch manual Versions of a particular asset, sorted chronologically.
		(Newest first) 
	
	\param catalog (WFCatalog)
	\param assetId (string|number) The local or full doc ID of an asset.
	\param includeFakeImportStateVersion (optional boolean) If true, a fake version
		representing the original/reset state of the photo upon import is included
		in the response.
		
	\return <code>Observable</code> An observable stream that fires with a table
		containing all versions of the specified asset. 
		Example please see WFCatVersions.getAllVersionsStreamForAsset
		
--]]

function WFCatVersions.getManualVersionsStreamForAsset( catalog, assetId, includeFakeImportStateVersion )

	logger:infof( "Fetching manual versions for asset %s", assetId )

	return getVersionsStreamForAsset( catalog, assetId, includeFakeImportStateVersion, true, false )

end

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

--[[!
	\brief Fetch auto Versions of a particular asset, sorted chronologically.
		(Newest first) 
	
	\param catalog (WFCatalog)
	\param assetId (string|number) The local or full doc ID of an asset.
	\param includeFakeImportStateVersion (optional boolean) If true, a fake version
		representing the original/reset state of the photo upon import is included
		in the response.
		
	\return <code>Observable</code> An observable stream that fires with a table
		containing all versions of the specified asset. 
		Example please see WFCatVersions.getAllVersionsStreamForAsset
		
--]]

function WFCatVersions.getAutoVersionsStreamForAsset( catalog, assetId, includeFakeImportStateVersion )

	logger:infof( "Fetching auto versions for asset %s", assetId )

	return getVersionsStreamForAsset( catalog, assetId, includeFakeImportStateVersion, false, true )
	
end 

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

--[[!
	\brief Rename a Version
	
	\param catalog (WFCatalog)
	\param versionId (string|number) The local or full doc ID of a Version.
	\param name (string) The new name of the Version.
	
	\return <code>Observable</code> Resolves to true once the version
		has been renamed successfully, or throws on error when user 
		is not authorized to create version.
--]]

function WFCatVersions.renameVersion( catalog, versionId, versionName )

	WFArguments_check( "WFCatalog", "+string|number", "string" )

	return checkUserAuthorization( catalog, versionId )
	
		:flatMap( function()

			versionName = maybeMakeVersionNameValid( versionName )
			
			local docStore = catalog:docStore()
		
			local baseOverlay = WFCatUtils.createBaseOverlayWithUserUpdatedField( versionId )
				
			baseOverlay.name = versionName
			baseOverlay.createdManually = true
				-- If an automatic version is renamed later by the end user, this should set
				-- to true to indicate that it's of interest to the end user.
				
			return docStore:applyOverlay( baseOverlay )
				
				:map( returnTrue )
		
		end )

end

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

--[[!
	\brief Delete a Version
	
	\param catalog (WFCatalog)
	\param versionId (string|number) The local or full doc ID of a Version.
		
	\return <code>Observable</code> Resolvese to true once the Version
		has been deleted successfully, or throws on error when user 
		is not authorized to create version.
--]]

function WFCatVersions.deleteVersion( catalog, versionId )

	WFArguments_check( "WFCatalog", "+string|number" )

	return checkUserAuthorization( catalog, versionId )
	
		:flatMap( function()
			
			return catalog:docStore():deleteDoc( versionId )
				:map( returnTrue )
		
		end )
	
end

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

local function deleteVersionsForAsset( catalog, assetId, manualVersionsOnly, autoVersionsOnly )

	WFArguments_check( "WFCatalog", "+string|number", "?boolean", "?boolean" )
	
	return checkUserAuthorization( catalog, assetId )
	
		:flatMap( function()
	
			local fullDocIdStream = Observable.just( assetId )
			
			if type( assetId ) == 'number' then
				fullDocIdStream = catalog:docStore():localIdToDocId( assetId )
			end
			
			return fullDocIdStream
			
				:flatMap( function( docId )
				
					local manualStatment = ""
				
					if manualVersionsOnly then
						manualStatment = "AND createdManually = 1"
					elseif autoVersionsOnly then
						manualStatment = "AND createdManually = 0"
					end
					
					local sql = string.format( 
									[[ SELECT docId, externalXmpRelativePath
										FROM versions
										WHERE assetId == ? 
											AND subtype == ? 
											%s ]],
								manualStatment )
				
					return catalog:indexStore():queryOnce(
						{
							sql = sql,
							quiesceUntilUpToDate = true,
							docId, 
							kVersionSubtypeEdit
						} )
				
				end )
				
				:flatMap( function( versionRows )
				
					return WFCatDelete.deleteDocsFromQueryResult( catalog:docStore(), versionRows )
					
				end )
				
				:map( returnTrue )
				
		end )

end

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

--[[!
	\brief Delete all Versions of a particular Asset.
	
	\param catalog (WFCatalog)
	\param assetId (string|number) The local or full doc ID of an asset.
		
	\return <code>Observable</code> Resolves to true once all Versions of the
		asset have been deleted successfully, or throws on error when user 
		is not authorized to create version.
--]]

function WFCatVersions.deleteAllVersionsForAsset( catalog, assetId )

	WFArguments_check( "WFCatalog", "+string|number" )
	
	return deleteVersionsForAsset( catalog, assetId )

end

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

--[[!
	\brief Delete all manual Versions of a particular Asset.
	
	\param catalog (WFCatalog)
	\param assetId (string|number) The local or full doc ID of an asset.
		
	\return <code>Observable</code> Resolves to true once all manual Versions of the
		asset have been deleted successfully, or throws on error when user 
		is not authorized to create version.
--]]

function WFCatVersions.deleteManualVersionsForAsset( catalog, assetId )

	WFArguments_check( "WFCatalog", "+string|number" )
	
	return deleteVersionsForAsset( catalog, assetId, true, false )

end

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

--[[!
	\brief Delete all auto Versions of a particular Asset.
	
	\param catalog (WFCatalog)
	\param assetId (string|number) The local or full doc ID of an asset.
		
	\return <code>Observable</code> Resolves to true once all auto Versions of the
		asset have been deleted successfully, or throws on error when user 
		is not authorized to create version.
--]]

function WFCatVersions.deleteAutoVersionsForAsset( catalog, assetId )

	WFArguments_check( "WFCatalog", "+string|number" )
	
	return deleteVersionsForAsset( catalog, assetId, false, true )

end

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

--[[!
	\brief Compare the provided settings against the settings within an asset
		or version document.
	
	\param catalog (WFCatalog)
	\param docId (string|number) The local or full doc ID of an asset or version
		to compare against.
	\param developSettings (table) A table containing the following develop settings:
		xmpCameraRaw (string)
		croppedHeight (number)
		croppedWidth (number)
		userOrientation (number)
		lensHref (string)
		cameraHref (string)
	
	\return <code>Observable</code> Resolves to true if the settings match, else
		returns false.
--]]

function WFCatVersions.compareCurrentSettings( catalog, docId, developSettings )

	WFArguments_check( "WFCatalog", "+string|number", "table" )

	return catalog:docStore():docValue( docId )
	
		:pluck( 'develop' )
	
		:map( function( docDevelopSettings )
	
			local docXmpCameraRaw = KSTableUtils_derefOpt( docDevelopSettings, 'xmpCameraRaw' )
			
			-- TODO [jandrew 2020-01-16]:  How should xmpCameraRaw settings be passed in? Always as strings?
			-- What about externalized deveop settings?
			
			if type( docXmpCameraRaw ) == 'table' and type( developSettings.xmpCameraRaw ) == 'table' then
				
				-- If both settings are tables, deep compare.
				
				if not KSTableUtils.deepEquals( docXmpCameraRaw, developSettings.xmpCameraRaw ) then
				
					return false
					
				end
			
			elseif type( docXmpCameraRaw ) == 'table' or type( developSettings.xmpCameraRaw ) == 'table' then
			
				-- If only one setting is in the table format, they don't match.
			
				return false
			
			elseif docXmpCameraRaw ~= developSettings.xmpCameraRaw then
			
				return false
				
			end
			
			local docCroppedHeight = KSTableUtils_derefOpt( docDevelopSettings, 'croppedHeight' )
			
			if docCroppedHeight ~= developSettings.croppedHeight then
				return false
			end
			
			local docCroppedWidth = KSTableUtils_derefOpt( docDevelopSettings, 'croppedWidth' )
			
			if docCroppedWidth ~= developSettings.croppedWidth then
				return false
			end
			
			local docUserOrientation = KSTableUtils_derefOpt( docDevelopSettings, 'userOrientation' )
			
			if docUserOrientation ~= developSettings.userOrientation then
				return false
			end
			
			local docLensHref = KSTableUtils_derefOpt( docDevelopSettings, 'profiles', 'lens', 'href' )
			
			if docLensHref ~= developSettings.lensHref then
				return false
			end
			
			local docCameraHref = KSTableUtils_derefOpt( docDevelopSettings, 'profiles', 'camera', 'href' )
	
			if docCameraHref ~= developSettings.cameraHref then
				return false
			end
	
			return true
	
		end )
	
end

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

--[[!
	\brief Download externalized xmp for a version asset.
	
	\param catalog (WFCatalog)
	\param docId (string|number) The local or full doc ID of the version for
		which to download the externalized xmp.
	\param saveFunc (function) A function that takes three arguments: 'catalog',
		a KSData instance containing the externalized xmp, and the version's
		docValue. This function should return the path of the saved xmp, the
		file size of the saved file, and the sha256 or contentId of the 
		externalized xmp on success, or throw on failure.
	
	\return <code>Observable</code> Resolves to the path of the saved externalized
		xmp on success, or throws on error.
--]]

function WFCatVersions.downloadExternalizedXmpForVersion( catalog, docId, saveFunc )

	WFArguments_check( "WFCatalog", "+string|number", "function" )

	return catalog:docStore():docValue( docId )
	
		:flatMapLatest( function( docValue )
			
			local href = KSTableUtils_derefOpt( docValue, "_serverProvidedMetadata", "links", "/rels/xmp/develop", "href" )
			
			if not href then
			
				return Observable.throw( "No externalized xmp href" )
				
			else
			
				local url = string.format( "%s%s", catalog:ozUser():serverBaseUrl(), href )
				
				return catalog:ozUser():get( url )
				
					:map( function( ... )
						return docValue, ...
					end )
					
			end
			
		end )
		
		:flatMapLatest( function( docValue, response, headers )

			if type( headers ) == "table" and headers.status_code == "200" then
			
				return saveFunc( catalog, response, docValue )
				
					:flatMapLatest( function( externalizedXmpPath, fileSize, xmpSha256OrContentId )
					
						if type( externalizedXmpPath ) == "string" then
							
							local sha256FieldName = WFPlatformInfo.isiOS and 'contentId' or 'sha256'

							local localOnly = { 
								files = {
									xmp_develop = {
										path = externalizedXmpPath,
										fileSize = fileSize,
										[ sha256FieldName ] = xmpSha256OrContentId,
									}
								}
							}
							
							 return catalog:docStore():applyOverlay {
									_id = docId,
									_localOnly = localOnly
								}
								
								:map( function()
									return externalizedXmpPath
								end )
							
						else
							return Observable.throw( "saveFunc returned non-string path" )
						end
						
					end )
					
			elseif type( headers ) == "table" then
			
				return Observable.throw(
					string.format "version xmp download failed with status code %s",
					headers.status_code )

			else

				return Observable.throw(
					string.format( "unexpected response to externalized xmp download attempt for %s", docId ) )

			end
			
			-- Just in case....
			return Observable.throw(
				string.format(
					"externalized xmp download attempt resulted in a response of type %s and headers of type %s",
					type( response ), type( headers ) ) )
					
		end )


end

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

--[[!
	\brief Get analytics for the versions in a user catalog.
	
	\param catalog (WFCatalog)
		
	\return <code>Observable</code> Resolves to a table of Versions metrics.
--]]

function WFCatVersions.getVersionsAnalytics( catalog )

	WFArguments_check( "WFCatalog" )
	
	return catalog:indexStore():queryOnce(
		{
			sql = [[ SELECT createdManually
						FROM versions
						WHERE subtype = ? ]],
			quiesceUntilUpToDate = true,
			kVersionSubtypeEdit
		} )
	
		:map( function( rows )
		
			local implicitVersionsCount = 0
			local manualVersionsCount = 0
			
			for _, row in ipairs( rows ) do
			
				local createdManually = row[ 1 ]
			
				if createdManually == 1 then
					manualVersionsCount = manualVersionsCount + 1
				else
					implicitVersionsCount = implicitVersionsCount + 1
				end
			
			end
			
			return {
				implicitVersionsCount = implicitVersionsCount,
				manualVersionsCount = manualVersionsCount
			}
			
		end )

end

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

--[[!
	\brief Deletes the previous auto-version for a given asset,
		if it belongs to the current user and it falls within
		a specified time window. If the latest auto-version
		is from another user, then the previous auto-version
		of the current user wont be deleted to preserve history.
		
	\param catalog (WFCatalog)
	\param assetId (string|number) The local or full doc ID of an asset.
	\param withinSeconds (number) The time window in seconds.
	
	\return <code>Observable</code> Resolves to a table of Versions metrics.
--]]

function WFCatVersions.maybeDeleteTempAutoVersion( catalog, assetId, withinSeconds )

	WFArguments_check( "WFCatalog" )
	
	return catalog:indexStore():queryOnce(
		{
			sql = [[ SELECT docId, creationDate, importedByUser
						FROM versions 
						WHERE assetId == ? 
							AND createdManually = 0
							AND subtype == ?
						ORDER BY creationDate DESC
						LIMIT 1]],
			quiesceUntilUpToDate = true,
			assetId, 
			kVersionSubtypeEdit
		} )
	
		:flatMap( function( rows )
		
			-- Delete the auto-version only if the latest auto-version
			-- is from the current user( importedByUser is NULL )
			-- and the creationDate is within the given time
			
			if rows and rows[ 1 ] then
				
				local docId = rows [ 1 ][ 1 ]
				local creationDate = rows [ 1 ][ 2 ] 
				local importedByUser = rows [ 1 ][ 3 ]
				
				local now = KSDate.timeInIso8601Format()
				local timeDiffInSecs = KSDate.differenceInSeconds( creationDate, now )
				
				if timeDiffInSecs < withinSeconds and not importedByUser then
				
					return WFCatDelete.deleteDocs( catalog:docStore(), { docId } )
						:map( function()
							return docId
						end )
					
				end
			end
			
			return Observable.just( nil )
			
		end )

end

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

return WFCatVersions
