local logger = require 'smm.utils.logger'
local config = require 'smm.playback.config'

---@class SMM_PlaybackTimer
---@field current_pos integer The current position in milliseconds
---@field update_interval integer How often the timer should update
---@field is_updating boolean Whether or not the timer should send updates.
---@field update fun(current_pos: integer|nil) Where to send the updated timestamp
---@field sync_interval integer time in ms between each sync
---@field is_syncing boolean whether we are currently syncing. Important so we don't issue multiple syncs at once.
---@field sync fun(callback: fun(sync_data: SyncData)) Method to run, provided by the caller to sync with Spotify servers
---@field last_sync_time integer When the last sync time occurred
---@field next_sync_override integer In case a sync needs to occur sooner
---@field consecutive_failures integer For tracking consecutive failures
---@field backoff_multiplier integer Multiplier for backoff in case too many 500s
---@field timer uv_timer_t The underlying timer object
local Timer = {}
Timer.__index = Timer

---@param opts table
---@return SMM_PlaybackTimer
function Timer:new(opts)
  local instance = {
    current_pos = 0,
    update_interval = config.get().timer_update_interval,
    is_updating = false,
    update = opts.update and opts.update or nil,
    sync_interval = config.get().timer_sync_interval,
    is_syncing = false,
    sync = opts.sync and opts.sync or nil,
    last_sync_time = 0,
    timer = vim.uv.new_timer(),
    next_sync_override = nil,
    consecutive_failures = 0,
    backoff_multiplier = 1,
  }

  setmetatable(instance, self)
  return instance
end

function Timer:start()
  -- We comment out all logging entries in this method specifically as they take up resources. Each '%s' or '%d' is calculated regardless of whether it is printed, which can take extra time that is not needed.
  self.timer:start(
    0,
    self.update_interval,
    vim.schedule_wrap(function()
      -- logger.debug('[TIMER] Tick - updating: %s, syncing: %s, current_pos: %d', tostring(self.is_updating), tostring(self.is_syncing), self.current_pos)

      local override_sync = false
      if self.is_updating then
        self.current_pos = self.current_pos + self.update_interval
        if self.update then
          override_sync = self.update(self.current_pos)
          -- logger.debug('[TIMER] Update called back return override_sync: %s', tostring(override_sync))
        end
      end

      if not self.is_syncing then
        local current_time = vim.loop.now()
        local time_since_sync = current_time - self.last_sync_time
        -- logger.debug('[TIMER] Time since last sync: %d ms (threshold: %d ms)', time_since_sync, self.sync_interval)
        if override_sync or (current_time - self.last_sync_time >= self.sync_interval) then
          -- logger.debug('[TIMER] Starting sync - override: %s', tostring(override_sync))
          if self.sync then
            self.is_syncing = true
            self.last_sync_time = current_time
            self.sync(function(sync_data)
              if sync_data then
                self.current_pos = sync_data.current_pos
                self.is_updating = sync_data.is_playing
                self.update(self.current_pos)
              else
                self:pause()
                self.update(nil)
              end
            end)
          end
          self.is_syncing = false
          -- logger.debug '[TIMER] Sync completed, is_syncing set to false'
        end
      end
    end)
  )
  -- logger.debug('Started timer: %s', vim.inspect(self.timer))
end

function Timer:pause()
  self.is_updating = false
end

function Timer:resume()
  self.is_updating = true
end

function Timer:reset()
  self.current_pos = 0
end

function Timer:close()
  self:pause()
  self:reset()
  self.timer:close()
  logger.debug 'Closed timer'
end

local M = {}

M.Timer = Timer

return M
