//
// Copyright (c) Double Symmetry GmbH
// Commercial use requires a license. See https://rntp.dev/pricing
//

enum AudioPlayerError: Error {
  case invalidIndex
  case invalidSourceUrl(String)
}

class QueueManager {
  private(set) var items: [AudioItem] = []
  private(set) var currentIndex: Int = -1

  // MARK: - Shuffle

  private(set) var isShuffled: Bool = false
  private var shuffleOrder: [Int] = []
  private var shufflePosition: Int = -1

  var current: AudioItem? {
    guard currentIndex >= 0 && currentIndex < items.count else { return nil }
    return items[currentIndex]
  }

  // MARK: - Replace

  func replaceCurrentItem(with item: AudioItem) {
    if items.isEmpty {
      items.append(item)
      currentIndex = 0
    } else if currentIndex >= 0 && currentIndex < items.count {
      items[currentIndex] = item
    } else {
      items.append(item)
      currentIndex = items.count - 1
    }
  }

  func replaceItem(at index: Int, with item: AudioItem) throws {
    guard index >= 0 && index < items.count else {
      throw AudioPlayerError.invalidIndex
    }
    items[index] = item
  }

  // MARK: - Add

  func add(_ newItems: [AudioItem]) {
    items.append(contentsOf: newItems)
    if isShuffled {
      let newIndices = Array((items.count - newItems.count)..<items.count)
      shuffleOrder.append(contentsOf: newIndices.shuffled())
    }
  }

  func add(_ newItems: [AudioItem], at index: Int) throws {
    guard index >= 0 && index <= items.count else {
      throw AudioPlayerError.invalidIndex
    }
    items.insert(contentsOf: newItems, at: index)
    if currentIndex >= 0 && index <= currentIndex {
      currentIndex += newItems.count
    }
    if isShuffled {
      rebuildShuffleOrder()
    }
  }

  // MARK: - Navigate

  @discardableResult
  func next(wrap: Bool) -> Bool {
    if isShuffled {
      return nextShuffled(wrap: wrap)
    }
    if currentIndex + 1 < items.count {
      currentIndex += 1
      return true
    } else if wrap && !items.isEmpty {
      currentIndex = 0
      return true
    }
    return false
  }

  @discardableResult
  func previous(wrap: Bool) -> Bool {
    if isShuffled {
      return previousShuffled(wrap: wrap)
    }
    if currentIndex > 0 {
      currentIndex -= 1
      return true
    } else if wrap && !items.isEmpty {
      currentIndex = items.count - 1
      return true
    }
    return false
  }

  func jump(to index: Int) throws {
    guard index >= 0 && index < items.count else {
      throw AudioPlayerError.invalidIndex
    }
    currentIndex = index
    if isShuffled {
      shufflePosition = shuffleOrder.firstIndex(of: index) ?? shufflePosition
    }
  }

  // MARK: - Remove

  func removeItem(at index: Int) throws {
    guard index >= 0 && index < items.count else {
      throw AudioPlayerError.invalidIndex
    }

    let wasCurrentIndex = index == currentIndex
    items.remove(at: index)

    if items.isEmpty {
      currentIndex = -1
    } else if index < currentIndex {
      currentIndex -= 1
    } else if wasCurrentIndex && currentIndex >= items.count {
      currentIndex = items.count - 1
    }

    if isShuffled {
      rebuildShuffleOrder()
    }
  }

  // MARK: - Clear

  func clear() {
    items.removeAll()
    currentIndex = -1
    disableShuffle()
  }

  // MARK: - Shuffle Control

  func setShuffle(enabled: Bool) {
    if enabled {
      enableShuffle()
    } else {
      disableShuffle()
    }
  }

  private func enableShuffle() {
    guard !items.isEmpty else { return }
    isShuffled = true
    var indices = Array(0..<items.count)
    indices.removeAll { $0 == currentIndex }
    indices.shuffle()
    if currentIndex >= 0 {
      indices.insert(currentIndex, at: 0)
      shufflePosition = 0
    }
    shuffleOrder = indices
  }

  private func disableShuffle() {
    isShuffled = false
    shuffleOrder = []
    shufflePosition = -1
  }

  private func rebuildShuffleOrder() {
    guard isShuffled else { return }
    var indices = Array(0..<items.count)
    indices.removeAll { $0 == currentIndex }
    indices.shuffle()
    if currentIndex >= 0 {
      indices.insert(currentIndex, at: 0)
      shufflePosition = 0
    }
    shuffleOrder = indices
  }

  private func nextShuffled(wrap: Bool) -> Bool {
    guard !shuffleOrder.isEmpty else { return false }
    if shufflePosition + 1 < shuffleOrder.count {
      shufflePosition += 1
      currentIndex = shuffleOrder[shufflePosition]
      return true
    } else if wrap {
      shufflePosition = 0
      currentIndex = shuffleOrder[0]
      return true
    }
    return false
  }

  private func previousShuffled(wrap: Bool) -> Bool {
    guard !shuffleOrder.isEmpty else { return false }
    if shufflePosition > 0 {
      shufflePosition -= 1
      currentIndex = shuffleOrder[shufflePosition]
      return true
    } else if wrap {
      shufflePosition = shuffleOrder.count - 1
      currentIndex = shuffleOrder[shufflePosition]
      return true
    }
    return false
  }
}
