source_utils_displaySettings.bs

import "pkg:/source/utils/config.bs"
import "pkg:/source/utils/misc.bs"

' Deep copy displaySettings AA structure
' Creates a new AA reference with all nested library settings copied
' This ensures observer will detect changes when we reassign to the node field
function deepCopyDisplaySettings(source as object) as object
  if not isValid(source)
    return {}
  end if

  result = {}

  for each libraryId in source
    librarySettings = source[libraryId]

    ' If library settings is an AA, deep copy it
    if type(librarySettings) = "roAssociativeArray"
      result[libraryId] = {}
      for each key in librarySettings
        result[libraryId][key] = librarySettings[key]
      end for
    else
      ' Shouldn't happen, but handle it gracefully
      result[libraryId] = librarySettings
    end if
  end for

  return result
end function

' Get all display settings for a library
' Returns: AA with all settings, or empty {} if library not found
' Example: { sortField: "DateCreated", filter: "Favorites", landing: "Movies" }
function getLibraryDisplaySettings(libraryId as dynamic) as object
  if not isValid(libraryId) or libraryId = ""
    return {}
  end if

  ' Single rendezvous to get local reference
  localUser = m.global.user

  if not isValid(localUser) or not isValid(localUser.settings)
    return {}
  end if

  if not isValid(localUser.settings.displaySettings)
    return {}
  end if

  if localUser.settings.displaySettings.DoesExist(libraryId)
    return localUser.settings.displaySettings[libraryId]
  end if

  return {}
end function

' Get specific display setting for a library with fallback
' Returns: Setting value or defaultValue if not found
' The returned value will be type-cast to match the defaultValue's type
function getLibraryDisplaySetting(libraryId as dynamic, key as dynamic, defaultValue = invalid as dynamic) as dynamic
  if not isValid(libraryId) or libraryId = "" or not isValid(key) or key = ""
    return defaultValue
  end if

  librarySettings = getLibraryDisplaySettings(libraryId)

  if librarySettings.DoesExist(key)
    value = librarySettings[key]

    ' Type-cast the retrieved value to match the defaultValue's type
    ' Registry always returns strings, so we need to convert to expected type
    if isValid(defaultValue)
      defaultType = Type(defaultValue)

      if defaultType = "roBoolean" or defaultType = "Boolean"
        return toBoolean(value)
      else if defaultType = "roInteger" or defaultType = "Integer"
        ' val() converts string to Float, Int() converts Float to Integer
        return Int(val(value))
      else if defaultType = "roFloat" or defaultType = "Float"
        return val(value)
      end if
    end if

    ' Return as-is if no type conversion needed
    return value
  end if

  return defaultValue
end function

' Set library display setting (updates node + persists to registry)
sub setLibraryDisplaySetting(libraryId as dynamic, key as dynamic, value as dynamic)
  if not isValid(libraryId) or libraryId = "" or not isValid(key) or key = ""
    return
  end if

  ' Get local reference to minimize rendezvous
  localUser = m.global.user

  if not isValid(localUser) or not isValid(localUser.settings)
    return
  end if

  ' Get current displaySettings and create a DEEP COPY
  ' CRITICAL: We must create a new AA reference, not reuse the existing one
  ' Otherwise the observer won't detect changes (observers only fire on reference changes)
  oldDisplaySettings = localUser.settings.displaySettings
  if not isValid(oldDisplaySettings)
    displaySettings = {}
  else
    displaySettings = deepCopyDisplaySettings(oldDisplaySettings)
  end if

  ' Ensure library object exists
  if not displaySettings.DoesExist(libraryId)
    displaySettings[libraryId] = {}
  end if

  ' Update value in the deep copy
  displaySettings[libraryId][key] = value

  ' Reassign NEW reference to content node (triggers onChange observer which syncs to registry)
  localUser.settings.displaySettings = displaySettings
end sub

' Batch get multiple settings at once (performance optimization)
' keys: Array of setting keys ["sortField", "filter", "landing"]
' defaults: AA of default values { sortField: "SortName", filter: "All" }
' Returns: AA with all requested settings
function getLibraryDisplaySettingsBatch(libraryId as string, keys as object, defaults as object) as object
  result = {}
  librarySettings = getLibraryDisplaySettings(libraryId)

  for each key in keys
    if librarySettings.DoesExist(key)
      result[key] = librarySettings[key]
    else if defaults.DoesExist(key)
      result[key] = defaults[key]
    else
      result[key] = invalid
    end if
  end for

  return result
end function

' Delete all display settings for a library
sub deleteLibraryDisplaySettings(libraryId as dynamic)
  if not isValid(libraryId) or libraryId = ""
    return
  end if

  ' Get local reference to minimize rendezvous
  localUser = m.global.user

  if not isValid(localUser) or not isValid(localUser.settings)
    return
  end if

  ' Get current displaySettings and create a DEEP COPY
  ' CRITICAL: We must create a new AA reference for observer to detect changes
  oldDisplaySettings = localUser.settings.displaySettings
  if not isValid(oldDisplaySettings)
    return
  end if

  displaySettings = deepCopyDisplaySettings(oldDisplaySettings)

  ' Remove library from the deep copy
  if displaySettings.DoesExist(libraryId)
    displaySettings.Delete(libraryId)
  end if

  ' Reassign NEW reference to content node (triggers onChange observer which cleans up registry)
  localUser.settings.displaySettings = displaySettings
end sub