source_data_SessionDataTransformer.bs

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

' Transforms session data (server, user, app, device) to content nodes
class SessionDataTransformer

  sub new()
  end sub

  ' Transform server info to JellyfinServer content node
  function transformServerInfo(serverData as object) as object
    if not isValid(serverData)
      return invalid
    end if

    try
      serverNode = CreateObject("roSGNode", "JellyfinServer")

      serverNode.id = serverData.id ?? ""
      serverNode.name = serverData.name ?? ""
      serverNode.version = serverData.version ?? ""
      serverNode.productName = serverData.productName ?? ""
      serverNode.operatingSystem = serverData.operatingSystem ?? ""

      serverNode.serverUrl = serverData.serverUrl ?? ""
      serverNode.localURL = serverData.localURL ?? ""
      serverNode.isHTTPS = serverData.isHTTPS ?? false
      serverNode.isLocalHTTPS = serverData.isLocalHTTPS ?? false

      serverNode.startupWizardCompleted = serverData.startupWizardCompleted ?? false

      ' Set loading state
      serverNode.isLoaded = true
      serverNode.isConnected = true
      serverNode.lastConnected = CreateObject("roDateTime").AsSeconds()

      #if debug
        serverNode.rawServerInfo = serverData
      #end if

      return serverNode

    catch error
      return invalid
    end try
  end function

  ' Transform user info to JellyfinUser content node (flattened structure)
  function transformUserInfo(userData as object) as object
    if not isValid(userData)
      return invalid
    end if

    try
      userNode = CreateObject("roSGNode", "JellyfinUser")

      ' User identity
      userNode.id = userData.id ?? ""
      userNode.name = userData.name ?? ""
      userNode.friendlyName = userData.friendlyName ?? ""
      userNode.authToken = userData.authToken ?? ""
      userNode.primaryImageTag = userData.primaryImageTag ?? ""
      userNode.lastRunVersion = userData.lastRunVersion ?? ""
      userNode.fontScaleFactor = userData.fontScaleFactor ?? 1.0

      ' Transform configuration to nested content node
      if isValid(userData.Configuration)
        userNode.config = m.transformUserConfiguration(userData.Configuration)
      else
        ' Create empty config node with defaults
        userNode.config = CreateObject("roSGNode", "JellyfinUserConfiguration")
      end if

      ' Transform policy to nested content node
      if isValid(userData.Policy)
        userNode.policy = m.transformUserPolicy(userData.Policy)
      else
        ' Create empty policy node with defaults
        userNode.policy = CreateObject("roSGNode", "JellyfinUserPolicy")
      end if

      ' Transform user settings (nested node)
      if isValid(userData.settings)
        userNode.settings = m.transformUserSettings(userData.settings)
      else
        ' Create empty settings node
        userNode.settings = CreateObject("roSGNode", "JellyfinUserSettings")
      end if

      ' Set loading state
      userNode.isLoaded = true
      userNode.loadedAt = CreateObject("roDateTime").AsSeconds()

      #if debug
        userNode.rawUserData = userData
      #end if

      return userNode

    catch error
      return invalid
    end try
  end function

  ' Transform user settings (from registry and API)
  function transformUserSettings(settingsData as object) as object
    settingsNode = CreateObject("roSGNode", "JellyfinUserSettings")

    if not isValid(settingsData)
      return settingsNode
    end if

    ' Playback Settings
    settingsNode.playbackBitrateMaxLimited = toBoolean(settingsData["playbackBitrateMaxLimited"])
    if settingsData.DoesExist("playbackBitrateLimit")
      settingsNode.playbackBitrateLimit = Val(settingsData["playbackBitrateLimit"])
    end if
    settingsNode.playbackCinemaMode = toBoolean(settingsData["playbackCinemaMode"])
    settingsNode.playbackDecodeMultichannelAudio = toBoolean(settingsData["playbackDecodeMultichannelAudio"])
    settingsNode.playbackSubsCustom = toBoolean(settingsData["playbackSubsCustom"])
    settingsNode.playbackResolutionMax = settingsData["playbackResolutionMax"] ?? ""
    if settingsData.DoesExist("playbackNextUpButtonSeconds")
      settingsNode.playbackNextUpButtonSeconds = Val(settingsData["playbackNextUpButtonSeconds"])
    end if
    settingsNode.playbackPlayNextEpisode = settingsData["playbackPlayNextEpisode"] ?? ""
    settingsNode.playbackPlayDefaultAudioTrack = settingsData["playbackPlayDefaultAudioTrack"] ?? ""
    settingsNode.playbackPreferredMultichannelCodec = settingsData["playbackPreferredMultichannelCodec"] ?? ""
    settingsNode.playbackSubsOnlyText = toBoolean(settingsData["playbackSubsOnlyText"])
    settingsNode.playbackMpeg2 = toBoolean(settingsData["playbackMpeg2"])
    settingsNode.playbackMpeg4 = toBoolean(settingsData["playbackMpeg4"])
    settingsNode.playbackTryDirectH264ProfileLevel = toBoolean(settingsData["playbackTryDirectH264ProfileLevel"])
    settingsNode.playbackTryDirectHevcProfileLevel = toBoolean(settingsData["playbackTryDirectHevcProfileLevel"])

    ' UI Settings
    settingsNode.uiGeneralEpisodeImages = settingsData["uiGeneralEpisodeImages"] ?? ""
    settingsNode.uiFontFallback = toBoolean(settingsData["uiFontFallback"])
    settingsNode.uiDesignHideClock = toBoolean(settingsData["uiDesignHideClock"])
    if settingsData.DoesExist("uiDetailsMaxDaysNextUp")
      settingsNode.uiDetailsMaxDaysNextUp = Val(settingsData["uiDetailsMaxDaysNextUp"])
    end if
    settingsNode.uiDetailsEnableRewatchingNextUp = toBoolean(settingsData["uiDetailsEnableRewatchingNextUp"])
    settingsNode.uiRowLayout = settingsData["uiRowLayout"] ?? ""
    settingsNode.uiHomeSplashBackground = toBoolean(settingsData["uiHomeSplashBackground"])
    settingsNode.uiHomeUseWebArrangement = toBoolean(settingsData["uiHomeUseWebArrangement"])
    settingsNode.uiDetailsHideTagline = toBoolean(settingsData["uiDetailsHideTagline"])
    settingsNode.uiMoviesShowRatings = toBoolean(settingsData["uiMoviesShowRatings"])
    settingsNode.uiTvShowsBlurUnwatched = toBoolean(settingsData["uiTvShowsBlurUnwatched"])
    settingsNode.uiTvShowsDisableCommunityRating = toBoolean(settingsData["uiTvShowsDisableCommunityRating"])
    settingsNode.uiTvShowsDisableUnwatchedCount = toBoolean(settingsData["uiTvShowsDisableUnwatchedCount"])
    settingsNode.uiTvShowsGoStraightToEpisodes = toBoolean(settingsData["uiTvShowsGoStraightToEpisodes"])

    ' Item Grid Settings
    settingsNode.itemGridShowItemCount = toBoolean(settingsData["itemGridShowItemCount"])
    settingsNode.itemGridTitles = settingsData["itemGridTitles"] ?? ""
    settingsNode.itemGridReset = toBoolean(settingsData["itemGridReset"])
    settingsNode.itemGridMovieDefaultView = settingsData["itemGridMovieDefaultView"] ?? ""

    ' Home sections
    settingsNode.homeSection0 = settingsData["homeSection0"] ?? ""
    settingsNode.homeSection1 = settingsData["homeSection1"] ?? ""
    settingsNode.homeSection2 = settingsData["homeSection2"] ?? ""
    settingsNode.homeSection3 = settingsData["homeSection3"] ?? ""
    settingsNode.homeSection4 = settingsData["homeSection4"] ?? ""
    settingsNode.homeSection5 = settingsData["homeSection5"] ?? ""
    settingsNode.homeSection6 = settingsData["homeSection6"] ?? ""

    ' Display settings
    settingsNode.displayLiveTvLanding = settingsData["displayLiveTvLanding"] ?? ""

    ' Transform display settings into nested structure
    ' Build local AA first, then assign to node field
    displaySettings = {}

    for each key in settingsData.Keys()
      if key.StartsWith("display.")
        ' Parse "display.libraryId.settingKey" → nested object
        parts = key.Split(".")
        if parts.Count() = 3 and isValidAndNotEmpty(parts[1]) and isValidAndNotEmpty(parts[2])
          libraryId = parts[1]
          settingKey = parts[2]

          ' Create library object if doesn't exist
          if not displaySettings.DoesExist(libraryId)
            displaySettings[libraryId] = {}
          end if

          ' Store setting value with proper type conversion
          rawValue = settingsData[key]

          ' Convert boolean fields from string to actual boolean
          if settingKey = "sortAscending"
            displaySettings[libraryId][settingKey] = toBoolean(rawValue)
          else
            ' Other fields stored as-is (strings, etc.)
            displaySettings[libraryId][settingKey] = rawValue
          end if
        end if
      end if
    end for

    ' Assign to content node field
    settingsNode.displaySettings = displaySettings

    ' Global settings are NOT loaded here - they come from "JellyRock" registry section
    ' and are loaded separately via user.settings.LoadGlobals()

    ' Set loading state
    settingsNode.isLoaded = true
    settingsNode.loadedAt = CreateObject("roDateTime").AsSeconds()

    ' Debug-only: Store raw settings for debugging
    #if debug
      settingsNode.rawSettings = settingsData
    #end if

    return settingsNode
  end function

  ' Transform app info to AppInfo content node
  function transformAppInfo(appData as object) as object
    if not isValid(appData)
      return invalid
    end if

    app = CreateObject("roSGNode", "AppInfo")

    app.appId = appData.id ?? ""
    app.isDev = appData.isDev ?? false
    app.version = appData.version ?? ""
    app.lastRunVersion = appData.lastRunVersion ?? ""

    return app
  end function

  ' Transform device info to DeviceInfo content node
  function transformDeviceInfo(deviceData as object) as object
    if not isValid(deviceData)
      return invalid
    end if

    device = CreateObject("roSGNode", "DeviceInfo")

    device.id = deviceData.id ?? ""
    device.name = deviceData.name ?? ""
    device.model = deviceData.model ?? ""
    device.serial = deviceData.serial ?? ""
    device.friendlyName = deviceData.friendlyName ?? ""
    device.serverDeviceName = deviceData.serverDeviceName ?? ""
    device.modelType = deviceData.modelType ?? ""

    device.videoHeight = deviceData.videoHeight ?? 1080
    device.videoWidth = deviceData.videoWidth ?? 1920
    device.maxVideoHeight = deviceData.maxVideoHeight ?? 1080
    device.maxVideoWidth = deviceData.maxVideoWidth ?? 1920
    device.canPlay4K = deviceData.canPlay4K ?? false

    if isValid(deviceData.videoCodecs)
      device.videoCodecs = deviceData.videoCodecs
    end if
    if isValid(deviceData.audioCodecs)
      device.audioCodecs = deviceData.audioCodecs
    end if

    device.memoryLevel = deviceData.memoryLevel ?? "normal"
    device.memoryTotal = deviceData.memoryTotal ?? 0
    device.modelDetails = deviceData.modelDetails ?? {}

    return device
  end function

  ' Transform user configuration to JellyfinUserConfiguration content node
  ' Configuration is SERVER-AUTHORITATIVE and should never be loaded from registry
  function transformUserConfiguration(configData as object) as object
    config = CreateObject("roSGNode", "JellyfinUserConfiguration")

    if not isValid(configData)
      return config
    end if

    ' Audio Settings
    config.audioLanguagePreference = configData.AudioLanguagePreference ?? ""
    config.playDefaultAudioTrack = configData.PlayDefaultAudioTrack ?? true
    config.rememberAudioSelections = configData.RememberAudioSelections ?? false

    ' Subtitle Settings
    config.subtitleMode = configData.SubtitleMode ?? "Default"
    config.subtitleLanguagePreference = configData.SubtitleLanguagePreference ?? ""
    config.rememberSubtitleSelections = configData.RememberSubtitleSelections ?? false

    ' Display Settings
    config.displayMissingEpisodes = configData.DisplayMissingEpisodes ?? false
    config.displayCollectionsView = configData.DisplayCollectionsView ?? false
    config.hidePlayedInLatest = configData.HidePlayedInLatest ?? true
    config.useEpisodeImagesInNextUpAndResume = configData.useEpisodeImagesInNextUpAndResume ?? false

    ' Playback Settings
    config.enableNextEpisodeAutoPlay = configData.EnableNextEpisodeAutoPlay ?? false

    ' View Organization
    config.orderedViews = configData.OrderedViews ?? []
    config.latestItemsExcludes = configData.LatestItemsExcludes ?? []
    config.myMediaExcludes = configData.MyMediaExcludes ?? []
    config.groupedFolders = configData.GroupedFolders ?? []

    ' Other Settings
    config.castReceiverId = configData.CastReceiverId ?? ""
    config.enableLocalPassword = configData.EnableLocalPassword ?? false

    #if debug
      config.rawConfigData = configData
    #end if

    return config
  end function

  ' Transform user policy to JellyfinUserPolicy content node
  ' Policy is SERVER-AUTHORITATIVE and should never be loaded from registry
  function transformUserPolicy(policyData as object) as object
    policy = CreateObject("roSGNode", "JellyfinUserPolicy")

    if not isValid(policyData)
      return policy
    end if

    ' User Status
    policy.isAdministrator = policyData.IsAdministrator ?? false
    policy.isDisabled = policyData.IsDisabled ?? false
    policy.isHidden = policyData.IsHidden ?? false

    ' Authentication Settings
    policy.authenticationProviderId = policyData.AuthenticationProviderId ?? ""
    policy.passwordResetProviderId = policyData.PasswordResetProviderId ?? ""
    policy.invalidLoginAttemptCount = policyData.InvalidLoginAttemptCount ?? 0
    policy.loginAttemptsBeforeLockout = policyData.LoginAttemptsBeforeLockout ?? - 1

    ' Media Playback Permissions
    policy.enableMediaPlayback = policyData.EnableMediaPlayback ?? true
    policy.enableAudioPlaybackTranscoding = policyData.EnableAudioPlaybackTranscoding ?? true
    policy.enableVideoPlaybackTranscoding = policyData.EnableVideoPlaybackTranscoding ?? true
    policy.enablePlaybackRemuxing = policyData.EnablePlaybackRemuxing ?? true
    policy.forceRemoteSourceTranscoding = policyData.ForceRemoteSourceTranscoding ?? false

    ' Content Access Permissions
    policy.enableAllFolders = policyData.EnableAllFolders ?? false
    policy.enabledFolders = policyData.EnabledFolders ?? []
    policy.enableContentDownloading = policyData.EnableContentDownloading ?? true
    policy.enableContentDeletion = policyData.EnableContentDeletion ?? false
    policy.enableContentDeletionFromFolders = policyData.EnableContentDeletionFromFolders ?? []

    ' Live TV Permissions
    policy.enableLiveTvAccess = policyData.EnableLiveTvAccess ?? true
    policy.enableLiveTvManagement = policyData.EnableLiveTvManagement ?? false

    ' Channel Permissions
    policy.enableAllChannels = policyData.EnableAllChannels ?? false
    policy.enabledChannels = policyData.EnabledChannels ?? []
    policy.blockedChannels = policyData.BlockedChannels ?? []

    ' Device Permissions
    policy.enableAllDevices = policyData.EnableAllDevices ?? true
    policy.enabledDevices = policyData.EnabledDevices ?? []
    policy.maxActiveSessions = policyData.MaxActiveSessions ?? 0

    ' Remote Access Permissions
    policy.enableRemoteAccess = policyData.EnableRemoteAccess ?? true
    policy.enableRemoteControlOfOtherUsers = policyData.EnableRemoteControlOfOtherUsers ?? false
    policy.enableSharedDeviceControl = policyData.EnableSharedDeviceControl ?? true
    policy.remoteClientBitrateLimit = policyData.RemoteClientBitrateLimit ?? 0

    ' Content Management Permissions
    policy.enableCollectionManagement = policyData.EnableCollectionManagement ?? false
    policy.enableSubtitleManagement = policyData.EnableSubtitleManagement ?? false
    policy.enableLyricManagement = policyData.EnableLyricManagement ?? false

    ' Sharing and Sync Permissions
    policy.enablePublicSharing = policyData.EnablePublicSharing ?? true
    policy.enableSyncTranscoding = policyData.EnableSyncTranscoding ?? true
    policy.enableMediaConversion = policyData.EnableMediaConversion ?? true

    ' User Preferences
    policy.enableUserPreferenceAccess = policyData.EnableUserPreferenceAccess ?? true

    ' Content Filtering
    policy.blockedMediaFolders = policyData.BlockedMediaFolders ?? []
    policy.blockedTags = policyData.BlockedTags ?? []
    policy.allowedTags = policyData.AllowedTags ?? []
    policy.blockUnratedItems = policyData.BlockUnratedItems ?? []

    ' Access Schedules
    policy.accessSchedules = policyData.AccessSchedules ?? []

    ' Sync Play
    policy.syncPlayAccess = policyData.SyncPlayAccess ?? "CreateAndJoinGroups"

    #if debug
      policy.rawPolicyData = policyData
    #end if

    return policy
  end function

end class