local SCRIPT_TITLE = 'Parameters Notes'

--[[

Synthesizer V Studio Pro Script
 
lua file name: ParametersNotesPanel.lua

Set automation parameters attached to single notes

Notice: Works only with script panel 
		introduced with Synthesizer V version >= 2.1.2

Update: 1- Creation

2026 - JF AVILES
--]]

function getClientInfo()
	return {
		name = SV:T(SCRIPT_TITLE),
		-- category = "_JFA_Panels",
		author = "JFAVILES",
		versionNumber = 1,
		minEditorVersion = 131329,
		type = "SidePanelSection"
	}
end

-- Generated by JFA TranslateScripts.lua
function getTranslations(langCode)
	return getArrayLanguageStrings()[langCode]
end

-- Generated by JFA TranslateScripts.lua
function getArrayLanguageStrings()
	return {
		["en-us"] = {
			{"Version", "Version"},
			{"author", "author"},
			{"minEditorVersion", "minEditorVersion"},
			{"Clear points", "Clear points"},
			{"Clear all points", "Clear all points"},
			{"Progressive", "Progressive"},
			{"Parameters", "Parameters"},
		},
	}
end

-- Define a class  "NotesObject"
NotesObject = {
	playBack = nil,
	playHeadPosition = nil,
	displayVersion = true,			-- display version
	displayAuthor = false,			-- display author
	parametersNotes = 0,			-- parameters notes
	parametersProgressive = 0,		-- parameters progressive
	marginTimeNotes = 1000,			-- margin time notes
	PARAMETERS_REFERENCE = {},		-- initialization
	PARAMETER_REFERENCE = "",		-- updated on parameters found
	newPointsAllRef = {},			-- new selected points for all automation parameters
	errorMessages = {},
	hostinfo = nil,
	osType = "",
	osName = "",
	hostName = "",
	languageCode = "", 
	hostVersion = "",
	hostVersionNumber = 0,
	currentSeconds = 0,
	parametersListChoice = {},
	parameterTextValue = "",
	parameterValue = "",
	parameterMinValue = 0,
	parameterMaxValue = 1,
	parameterInterval = 0.1,
	parameterDefault = 0,
	defaultParameterToDisplay = 3 -- Display first parameter 3 = Tension
}

-- Constructor method for the NotesObject class
function NotesObject:new()
    local notesObject = {}
    setmetatable(notesObject, self)
    self.__index = self
	
	self:getHostInformations()
	
	self.currentSeconds = SV:getPlayback():getPlayhead()
	
	self.controls = self:getControls()
	
	self:initializeControlsValues()
	self:setControlsCallback()
	
	self.clearParameterValue = SV:create("WidgetValue")
	self.clearAllParametersValue = SV:create("WidgetValue")
	
	self.statusTextValue = SV:create("WidgetValue")
	self.statusTextValue:setValue("")
	self.statusTextValue:setEnabled(false)

	self.pointsTextValue = SV:create("WidgetValue")
	self.pointsTextValue:setValue("")
	self.pointsTextValue:setEnabled(false)
	
	self:setParameters()
	self:getParametersHeaders()
	self:setButtonClearParametersControlCallback()
	
	self.parameterTextValue = self.PARAMETERS_REFERENCE[1].display
	self.parameterValue = self.PARAMETERS_REFERENCE[1].ref
	self.parameterMinValue = self.PARAMETERS_REFERENCE[1].min
	self.parameterMaxValue = self.PARAMETERS_REFERENCE[1].max
	self.parameterInterval = self.PARAMETERS_REFERENCE[1].interval
	self.parameterDefault = self.PARAMETERS_REFERENCE[1].default

	self.controls.parametersNotes.value:setValue(self.parametersNotes)
	self.controls.parametersProgressive.value:setValue(self.parametersProgressive)
	self.controls.parametersValue.value:setValue(self.defaultParameterToDisplay) -- Default parameter to display
	self:parameterUpdate(self.defaultParameterToDisplay)
	
	local infos = getClientInfo()

	self.infosToDisplay = ""
	if self.displayVersion then
		self.infosToDisplay = self.infosToDisplay .. SV:T("Version") .. ": " ..  infos.versionNumber
		if self.displayAuthor then
			self.infosToDisplay = self.infosToDisplay .. " - " .. SV:T("author") .. ": " .. infos.author
		end
	end
	-- self.infosToDisplay = self.infosToDisplay .. SV:T("minEditorVersion") .. ": " ..  infos.minEditorVersion
	self:addTextPanel(self.infosToDisplay)

	-- Register editor view selection callback
	self:registerEditorViewSelectionCallback()

    return self
end

-- Register editor view selection callback
function NotesObject:registerEditorViewSelectionCallback()
	
	-- Register selection callback to load parameters when selection changes
	SV:getMainEditor():getSelection():registerSelectionCallback(function(selectionType, isSelected)	
		self:displaySelectedPoints()
	end)
	
	-- Register clear selection callback
	SV:getMainEditor():getSelection():registerClearCallback(function(selectionType)
		-- No group selected in editor view
		self.newPointsAllRef = {}
	end)
end

-- Display selected points
function NotesObject:displaySelectedPoints(data)
	if SV:getMainEditor():getSelection():hasSelectedContent() then
		local selection = SV:getMainEditor():getSelection()
		local selectedNotes = selection:getSelectedNotes()
		local currentGroup = SV:getMainEditor():getCurrentGroup():getTarget()
		if #selectedNotes > 0 then
			local newPointsAllRef = {}
			for _, parameter in pairs(self.PARAMETERS_REFERENCE) do
				local paramsGroup = currentGroup:getParameter(parameter.ref)
				for i = 1, #selectedNotes do
					local startNote = selectedNotes[i]:getOnset()
					local endNote = selectedNotes[i]:getEnd()
					local points = paramsGroup:getPoints(startNote, endNote)
					if #points > 0 then
						table.insert(newPointsAllRef, {ref = parameter.ref, points = points})
					end
				end
			end
			
			if #newPointsAllRef > 0 then
				local parametersPoints = ""
				for _, parameter in pairs(newPointsAllRef) do
					parametersPoints = parametersPoints .. "ref: " .. parameter.ref .. ", points: " .. #parameter.points .. "\r"
				end
				if data ~= nil then
					parametersPoints = parametersPoints .. data
				else
					parametersPoints = parametersPoints
				end
				self:setPointsValuesTextPanel(parametersPoints)
			else
				self:setPointsValuesTextPanel("")
			end
		else
			self:setPointsValuesTextPanel("")
		end
	else
		self:setPointsValuesTextPanel("")
	end
end

-- Show message dialog
function NotesObject:show(message)
	SV:showMessageBox(SV:T(SCRIPT_TITLE), message)
end

-- Get current track
function NotesObject:getCurrentTrack()
	return SV:getMainEditor():getCurrentTrack()
end

-- Get timeAxis
function NotesObject:getTimeAxis()
	return self:getProject():getTimeAxis()
end

-- Get project
function NotesObject:getProject()
	return SV:getProject()
end

-- Add log into self.logs
function NotesObject:addLogs(message)
	table.insert(self.logs, message)
end

-- Display logs into panel
function NotesObject:addLogsInPanel()
	for i = 1, #self.logs do
		self:addTextPanel(self.logs[i])
	end
end

-- Set points values text in panel
function NotesObject:setPointsValuesTextPanel(values)
	self.pointsTextValue:setValue(values)
end

-- Display message box in panel
function NotesObject:addTextPanel(message)
	local old = self.statusTextValue:getValue()
	local sepLine = "\r"
	if #old > 0 then
		message = old .. sepLine .. message
	end
	self.statusTextValue:setValue(message)
end

-- Clear display message in panel
function NotesObject:clearTextPanel()
	self.statusTextValue:setValue("")
end

-- Store error message
function NotesObject:error(message)
	table.insert(self.errorMessages, message)
end

-- Set parameters
function NotesObject:setParameters()

	if #self.PARAMETERS_REFERENCE == 0 then
		table.insert(self.PARAMETERS_REFERENCE, {display = "Pitch Deviation", 	ref = "pitchDelta", min = -200, max = 200, interval = 10, default = 0 })
		table.insert(self.PARAMETERS_REFERENCE, {display = "Vibrato Envelope",	ref = "vibratoEnv", min = 0, max = 2, interval = 0.1, default = 0 })
		table.insert(self.PARAMETERS_REFERENCE, {display = "Loudness", 			ref = "Loudness", min = -12, max = 12, interval = 1, default = 0 })
		table.insert(self.PARAMETERS_REFERENCE, {display = "Tension",			ref = "Tension", min = -1, max = 1, interval = 0.1, default = 0 })
		table.insert(self.PARAMETERS_REFERENCE, {display = "Breathiness",		ref = "Breathiness", min = -1, max = 1, interval = 0.1, default = 0 })
		table.insert(self.PARAMETERS_REFERENCE, {display = "Voicing",			ref = "Voicing", min = 0, max = 1, interval = 0.1, default = 1 })
		table.insert(self.PARAMETERS_REFERENCE, {display = "Gender",			ref = "Gender", min = -1, max = 1, interval = 0.1, default = 0 })
		table.insert(self.PARAMETERS_REFERENCE, {display = "Tone Shift",		ref = "ToneShift", min = -400, max = 400, interval = 10, default = 0 })
		 -- Crash app v2.0.5
		-- table.insert(self.PARAMETERS_REFERENCE, {display = "Rap Intonation",	ref = "rapIntonation", min = -1, max = 1, interval = 0.1, default = 0 })
		table.insert(self.PARAMETERS_REFERENCE, {display = "Mouth Opening",		ref = "MouthOpening", min = -1, max = 1, interval = 0.1, default = 0 })
	end
end

-- Get parameters headers
function NotesObject:getParametersHeaders()
	self.parametersListChoice = {}
	for _, param in pairs(self.PARAMETERS_REFERENCE) do
		table.insert(self.parametersListChoice, param.display)
	end
	return self.parametersListChoice
end

-- Get controls panel
function NotesObject:getControls()

	local controls = {
		parametersNotes = {							-- Slider: parameters notes
			value = SV:create("WidgetValue"),
			defaultValue = self.parametersNotes,
			paramKey = "parametersNotes"
		},
		parametersProgressive = {					-- Slider: parameters progressive
			value = SV:create("WidgetValue"),
			defaultValue = self.parametersProgressive,
			paramKey = "parametersProgressive"
		},
		parametersValue = {
			value = SV:create("WidgetValue"),
			defaultValue = 0,
			paramKey = "parameter"
		}
	}
	return controls
end

-- Initialize widget values
function NotesObject:initializeControlsValues()
	-- Initialize widget values
	for key, control in pairs(self.controls) do
		control.value:setValue(control.defaultValue)
	end
end

-- Set controls callback
function NotesObject:setControlsCallback()
	for key, control in pairs(self.controls) do
		control.value:setValueChangeCallback(function()
				if control.paramKey == "parametersProgressive" then
					self.parametersProgressive = self.controls.parametersProgressive.value:getValue()
					self:addParameter2Note()
				end
				if control.paramKey == "parametersNotes" then
					self.parametersNotes = self.controls.parametersNotes.value:getValue()					
					self:addParameter2Note()
				end
				if control.paramKey == "parameter" then
					self:parameterUpdate(self.controls.parametersValue.value:getValue())
					SV:refreshSidePanel()
				end
			end
		)
	end
end

-- Parameter update
function NotesObject:parameterUpdate(val)
	local data = self.PARAMETERS_REFERENCE[val + 1]
	self.parameterTextValue = data.display
	self.parameterValue = data.ref
	self.parameterMinValue = data.min
	self.parameterMaxValue = data.max
	self.parameterInterval = data.interval
	self.parameterDefault = data.default
	self.controls.parametersNotes.value:setValue(self.parameterDefault)	
end

-- Add parameter for note
function NotesObject:addParameter2Note()

	if SV:getMainEditor():getSelection():hasSelectedContent() then
		local selection = SV:getMainEditor():getSelection()
		local selectedNotes = selection:getSelectedNotes()
		local currentGroup = SV:getMainEditor():getCurrentGroup():getTarget()
		if #selectedNotes > 0 then
			local diffPoint = self.controls.parametersNotes.value:getValue() + ((self.parameterDefault - self.controls.parametersNotes.value:getValue())/2)
			local paramsGroup = currentGroup:getParameter(self.parameterValue)
			for i = 1, #selectedNotes do
				local startNote = selectedNotes[i]:getOnset() + 1
				local durationNote = selectedNotes[i]:getDuration()
				local endNote = selectedNotes[i]:getEnd() -1
				
				paramsGroup:remove(startNote, endNote)					
				
				paramsGroup:add(startNote, self.parameterDefault)
				
				if self.controls.parametersProgressive.value:getValue() == 1 then
					paramsGroup:add(startNote + durationNote/2, diffPoint)
					paramsGroup:add(endNote - self.marginTimeNotes, self.controls.parametersNotes.value:getValue())
				end
				if self.controls.parametersProgressive.value:getValue() == 0 then
					paramsGroup:add(startNote + self.marginTimeNotes, self.controls.parametersNotes.value:getValue())
					paramsGroup:add(endNote - self.marginTimeNotes, self.controls.parametersNotes.value:getValue())
				end
				if self.controls.parametersProgressive.value:getValue() == -1 then
					paramsGroup:add(startNote + self.marginTimeNotes, self.controls.parametersNotes.value:getValue())
					paramsGroup:add(endNote - durationNote/2, diffPoint)
				end
				paramsGroup:add(endNote, self.parameterDefault)
			end
			self:displaySelectedPoints()
		end
	end
end

-- Clear parameter points for note
function NotesObject:clearParameter2Note(all)

	if SV:getMainEditor():getSelection():hasSelectedContent() then
		local selection = SV:getMainEditor():getSelection()
		local selectedNotes = selection:getSelectedNotes()
		local currentGroup = SV:getMainEditor():getCurrentGroup():getTarget()
		if #selectedNotes > 0 then
			if not all then
				local paramsGroup = currentGroup:getParameter(self.parameterValue)
				for i = 1, #selectedNotes do
					local startNote = selectedNotes[i]:getOnset()
					local endNote = selectedNotes[i]:getEnd()				
					paramsGroup:remove(startNote, endNote)					
				end
				self.controls.parametersNotes.value:setValue(self.parameterDefault)
				self.controls.parametersProgressive.value:setValue(0)
				self:displaySelectedPoints()
			else
	
				for _, parameter in pairs(self.PARAMETERS_REFERENCE) do
					local paramsGroup = currentGroup:getParameter(parameter.ref)
					for i = 1, #selectedNotes do
						local startNote = selectedNotes[i]:getOnset()
						local endNote = selectedNotes[i]:getEnd()				
						paramsGroup:remove(startNote, endNote)
					end
					self.controls.parametersNotes.value:setValue(parameter.default)
					self.controls.parametersProgressive.value:setValue(0)
					self:displaySelectedPoints()
				end
			end
		end
	end

end

-- Display message
function NotesObject:displayMessage(message)
	self:clearTextPanel()
	self:addTextPanel(self.infosToDisplay)
	self:addTextPanel(message)
end

-- Set button clear parameters control callback
function NotesObject:setButtonClearParametersControlCallback()

	-- Button clear parameter values
	self.clearParameterValue:setValueChangeCallback(function()
			-- self:getProject():newUndoRecord()			
			self:clearParameter2Note(false)
		end
	)
	-- Button clear all parameter values
	self.clearAllParametersValue:setValueChangeCallback(function()
			-- self:getProject():newUndoRecord()			
			self:clearParameter2Note(true)
		end
	)
end


-- Display error messages
function NotesObject:displayErrors()
	local result = ""
	if #self.errorMessages > 0 then
		for _, m in pairs(self.errorMessages) do
			result = result .. m .. "\r"
		end
	end
	return result
end

-- Get host informations
function NotesObject:getHostInformations()
	self.hostinfo = SV:getHostInfo()
	self.osType = self.hostinfo.osType  -- "macOS", "Linux", "Unknown", "Windows"
	self.osName = self.hostinfo.osName
	self.hostName = self.hostinfo.hostName
	self.languageCode = self.hostinfo.languageCode
	self.hostVersion = self.hostinfo.hostVersion
	self.hostVersionNumber = self.hostinfo.hostVersionNumber
end

-- Get object properties (debug only)
function NotesObject:getObjectProperties(obj, level)
	local result = ""
	local level = level or 0
	local maxLevel = 3
	level = level + 1
	
	for k, v in pairs(obj) do
		if obj[k] ~= nil then
			result = result .. "(level: " .. level .. ") " .. k .. "=" .. tostring(v) .. "\r"
			if type(v) == "table" then
				-- result = result .. ", size:" .. #v .. ": "
				if level < maxLevel then
					result = result .. self:getObjectProperties(v, level) .. "\r"
				else
					result = result .. "\r"
				end
			end
		end
	end
	return result
end

-- Get string format from seconds
function NotesObject:secondsToClock(timestamp)
	return string.format("%02d:%06.3f", 
	  --math.floor(timestamp/3600), 
	  math.floor(timestamp/60)%60, 
	  timestamp%60):gsub("%.",",")
end

-- Get section
function NotesObject:getSection()
		
	local clearPoints = 
			{
				type = "Container",
				columns = {
					{
						type = "Button",
						text = SV:T("Clear points"),
						width = 0.5,
						value = self.clearParameterValue
					}, 
					{
						type = "Button",
						text = SV:T("Clear all points"),
						width = 0.5,
						value = self.clearAllParametersValue
					}, 
				}
			}
		
	local parametersNotesChoice = {
				type = "Container",
				columns = {
				  {
					type = "Slider",
					text = self.parameterTextValue,
					format = "%1.2f",
					minValue = self.parameterMinValue,
					maxValue = self.parameterMaxValue,
					interval = self.parameterInterval,
					value = self.controls.parametersNotes.value,
					width = 1
				  }
				}
			}
	local parametersProgressiveChoice = {
				type = "Container",
				columns = {
				  {
					type = "Slider",
					text = SV:T("Progressive"),
					format = "%1.2f",
					minValue = -1,
					maxValue = 1,
					interval = 1,
					value = self.controls.parametersProgressive.value,
					width = 1
				  }
				}
			}
			
	local comboBoxParameters = {
			type = "Container",
			columns = {
				{
					type = "ComboBox",
					text = SV:T("Parameters"),
					value = self.controls.parametersValue.value,
					choices = self.parametersListChoice,
					width = 1.0
				}
			}
		}
	
	-- Define CheckBox & button & textarea
	local section = {
		title = SV:T(SCRIPT_TITLE),
		rows = {
			comboBoxParameters,
			parametersNotesChoice,
			parametersProgressiveChoice,
			clearPoints,
			{
				type = "Container",
				columns = {
					{
						type = "TextArea",
						value = self.pointsTextValue,
						height = 50,
						width = 1.0,
						readOnly = false
					}
				}
			},
			{
				type = "Container",
				columns = {
					{
						type = "TextArea",
						value = self.statusTextValue,
						height = 40,
						width = 1.0,
						readOnly = true
					}
				}
			}
		}
	}
	return section
end

-- Get panel section state
function NotesObject:getPanelSectionState()

	local errors = self:displayErrors()
	if #errors > 0 then
		self:addTextPanel(errors)
	end
	
	-- Get section data
	local section = self:getSection()

	return section
end

-- Initialize main internal object	
local notesObject = NotesObject:new()

-- Get panel section state called by Synthesizer V internal system
function getSidePanelSectionState()
	local section = notesObject:getPanelSectionState()

	return section
end