const Path = require('path')
const Logger = require('../Logger')
const prober = require('./prober')

const ImageCodecs = ['mjpeg', 'jpeg', 'png']

function getDefaultAudioStream(audioStreams) {
  if (audioStreams.length === 1) return audioStreams[0]
  var defaultStream = audioStreams.find(a => a.is_default)
  if (!defaultStream) return audioStreams[0]
  return defaultStream
}

async function scan(path) {
  Logger.debug(`Scanning path "${path}"`)
  var probeData = await prober(path)
  if (!probeData || !probeData.audio_streams || !probeData.audio_streams.length) {
    return {
      error: 'Invalid audio file'
    }
  }
  if (!probeData.duration || !probeData.size) {
    return {
      error: 'Invalid duration or size'
    }
  }
  var audioStream = getDefaultAudioStream(probeData.audio_streams)

  const finalData = {
    format: probeData.format,
    duration: probeData.duration,
    size: probeData.size,
    bit_rate: audioStream.bit_rate || probeData.bit_rate,
    codec: audioStream.codec,
    time_base: audioStream.time_base,
    language: audioStream.language,
    channel_layout: audioStream.channel_layout,
    channels: audioStream.channels,
    sample_rate: audioStream.sample_rate,
    chapters: probeData.chapters || []
  }

  var hasCoverArt = probeData.video_stream ? ImageCodecs.includes(probeData.video_stream.codec) : false
  if (hasCoverArt) {
    finalData.embedded_cover_art = probeData.video_stream.codec
  }

  for (const key in probeData) {
    if (probeData[key] && key.startsWith('file_tag')) {
      finalData[key] = probeData[key]
    }
  }

  if (finalData.file_tag_track) {
    var track = finalData.file_tag_track
    var trackParts = track.split('/').map(part => Number(part))
    if (trackParts.length > 0) {
      finalData.trackNumber = trackParts[0]
    }
    if (trackParts.length > 1) {
      finalData.trackTotal = trackParts[1]
    }
  }

  return finalData
}
module.exports.scan = scan


function isNumber(val) {
  return !isNaN(val) && val !== null
}

function getTrackNumberFromMeta(scanData) {
  return !isNaN(scanData.trackNumber) && scanData.trackNumber !== null ? Math.trunc(Number(scanData.trackNumber)) : null
}

function getTrackNumberFromFilename(title, author, series, publishYear, filename) {
  var partbasename = Path.basename(filename, Path.extname(filename))

  // Remove title, author, series, and publishYear from filename if there
  if (title) partbasename = partbasename.replace(title, '')
  if (author) partbasename = partbasename.replace(author, '')
  if (series) partbasename = partbasename.replace(series, '')
  if (publishYear) partbasename = partbasename.replace(publishYear)

  // Remove eg. "disc 1" from path
  partbasename = partbasename.replace(/ disc \d\d? /i, '')

  var numbersinpath = partbasename.match(/\d{1,4}/g)
  if (!numbersinpath) return null

  var number = numbersinpath.length ? parseInt(numbersinpath[0]) : null
  return number
}

async function scanAudioFiles(audiobook, newAudioFiles) {
  if (!newAudioFiles || !newAudioFiles.length) {
    Logger.error('[AudioFileScanner] Scan Audio Files no new files', audiobook.title)
    return
  }

  Logger.debug('[AudioFileScanner] Scanning audio files')

  var tracks = []
  var numDuplicateTracks = 0
  var numInvalidTracks = 0

  for (let i = 0; i < newAudioFiles.length; i++) {
    var audioFile = newAudioFiles[i]
    var scanData = await scan(audioFile.fullPath)
    if (!scanData || scanData.error) {
      Logger.error('[AudioFileScanner] Scan failed for', audioFile.path)
      continue;
    }

    var trackNumFromMeta = getTrackNumberFromMeta(scanData)
    var book = audiobook.book || {}

    var trackNumFromFilename = getTrackNumberFromFilename(book.title, book.author, book.series, book.publishYear, audioFile.filename)

    var audioFileObj = {
      ino: audioFile.ino,
      filename: audioFile.filename,
      path: audioFile.path,
      fullPath: audioFile.fullPath,
      ext: audioFile.ext,
      ...scanData,
      trackNumFromMeta,
      trackNumFromFilename
    }
    var audioFile = audiobook.addAudioFile(audioFileObj)

    var trackNumber = 1
    if (newAudioFiles.length > 1) {
      trackNumber = isNumber(trackNumFromMeta) ? trackNumFromMeta : trackNumFromFilename
      if (trackNumber === null) {
        Logger.debug('[AudioFileScanner] Invalid track number for', audioFile.filename)
        audioFile.invalid = true
        audioFile.error = 'Failed to get track number'
        numInvalidTracks++
        continue;
      }
    }

    if (tracks.find(t => t.index === trackNumber)) {
      // Logger.debug('[AudioFileScanner] Duplicate track number for', audioFile.filename)
      audioFile.invalid = true
      audioFile.error = 'Duplicate track number'
      numDuplicateTracks++
      continue;
    }

    audioFile.index = trackNumber
    tracks.push(audioFile)
  }

  if (!tracks.length) {
    Logger.warn('[AudioFileScanner] No Tracks for audiobook', audiobook.id)
    return
  }

  if (numDuplicateTracks > 0) {
    Logger.warn(`[AudioFileScanner] ${numDuplicateTracks} Duplicate tracks for "${audiobook.title}"`)
  }
  if (numInvalidTracks > 0) {
    Logger.error(`[AudioFileScanner] ${numDuplicateTracks} Invalid tracks for "${audiobook.title}"`)
  }

  tracks.sort((a, b) => a.index - b.index)
  audiobook.audioFiles.sort((a, b) => {
    var aNum = isNumber(a.trackNumFromMeta) ? a.trackNumFromMeta : isNumber(a.trackNumFromFilename) ? a.trackNumFromFilename : 0
    var bNum = isNumber(b.trackNumFromMeta) ? b.trackNumFromMeta : isNumber(b.trackNumFromFilename) ? b.trackNumFromFilename : 0
    return aNum - bNum
  })

  // If first index is 0, increment all by 1
  if (tracks[0].index === 0) {
    tracks = tracks.map(t => {
      t.index += 1
      return t
    })
  }

  var hasTracksAlready = audiobook.tracks.length
  tracks.forEach((track) => {
    audiobook.addTrack(track)
  })
  if (hasTracksAlready) {
    audiobook.tracks.sort((a, b) => a.index - b.index)
  }
}
module.exports.scanAudioFiles = scanAudioFiles


async function rescanAudioFiles(audiobook) {
  var audioFiles = audiobook.audioFiles
  var updates = 0

  for (let i = 0; i < audioFiles.length; i++) {
    var audioFile = audioFiles[i]
    var scanData = await scan(audioFile.fullPath)
    if (!scanData || scanData.error) {
      Logger.error('[AudioFileScanner] Scan failed for', audioFile.path)
      // audiobook.invalidAudioFiles.push(parts[i])
      continue;
    }
    var hasUpdates = audioFile.updateMetadata(scanData)
    if (hasUpdates) {
      // Sync audio track with audio file
      var matchingAudioTrack = audiobook.tracks.find(t => t.ino === audioFile.ino)
      if (matchingAudioTrack) {
        matchingAudioTrack.syncMetadata(audioFile)
      } else if (!audioFile.exclude) { // If audio file is not excluded then it should have an audio track

        // Fallback to checking path
        matchingAudioTrack = audiobook.tracks.find(t => t.path === audioFile.path)
        if (matchingAudioTrack) {
          Logger.error(`[AudioFileScanner] Audio File mismatch ino with audio track "${audioFile.filename}"`)
          matchingAudioTrack.ino = audioFile.ino
          matchingAudioTrack.syncMetadata(audioFile)
        } else {
          Logger.error(`[AudioFileScanner] Audio File has no matching Track ${audioFile.filename} for "${audiobook.title}"`)

          // Exclude audio file to prevent further errors
          // audioFile.exclude = true
        }
      }
      updates++
    }
  }

  return updates
}
module.exports.rescanAudioFiles = rescanAudioFiles

async function scanTrackNumbers(audiobook) {
  var tracks = audiobook.tracks || []
  var scannedTrackNumData = []
  for (let i = 0; i < tracks.length; i++) {
    var track = tracks[i]
    var scanData = await scan(track.fullPath)

    var trackNumFromMeta = getTrackNumberFromMeta(scanData)
    var book = audiobook.book || {}
    var trackNumFromFilename = getTrackNumberFromFilename(book.title, book.author, book.series, book.publishYear, track.filename)
    Logger.info(`[AudioFileScanner] Track # for "${track.filename}", Metadata: "${trackNumFromMeta}", Filename: "${trackNumFromFilename}", Current: "${track.index}"`)
    scannedTrackNumData.push({
      filename: track.filename,
      currentTrackNum: track.index,
      trackNumFromFilename,
      trackNumFromMeta,
      scanDataTrackNum: scanData.file_tag_track
    })
  }
  return scannedTrackNumData
}
module.exports.scanTrackNumbers = scanTrackNumbers