import {useState, useEffect, useRef} from 'react'
import PropTypes from 'prop-types'
import List from '@mui/material/List'
import ListSubheader from '@mui/material/ListSubheader'
import Typography from '@mui/material/Typography'
import Box from '@mui/material/Box'
import Skeleton from '@mui/material/Skeleton'
import LinearProgress from '@mui/material/LinearProgress'
import {useColorScheme, styled} from '@mui/material/styles'
import Table from '@mui/material/Table'
import TableHead from '@mui/material/TableHead'
import TableBody from '@mui/material/TableBody'
import TableCell, {tableCellClasses} from '@mui/material/TableCell'
import TableRow from '@mui/material/TableRow'
import TableContainer from '@mui/material/TableContainer'
import Paper from '@mui/material/Paper'
import DescriptionIcon from '@mui/icons-material/Description'
import LibraryMusicIcon from '@mui/icons-material/LibraryMusic'
import {Link} from '@mui/material'

const apiUrl = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337'
const audioMetaCache = {}

const StyledTableCell = styled(TableCell)(({theme}) => ({
  [`&.${tableCellClasses.head}`]: {
    backgroundColor: theme.palette.common.black,
    color: theme.palette.common.white,
  },
  [`&.${tableCellClasses.body}`]: {
    fontSize: 14,
  },
}))

const StyledTableRow = styled(TableRow)(({theme}) => ({
  '&:nth-of-type(odd)': {
    backgroundColor: theme.palette.action.hover,
  },
  '&:last-child td, &:last-child th': {
    border: 0,
  },
}))

function formatSize(size) {
  if (size < 1000) {
    return Math.round(size) + ' Kb'
  }

  const mbSize = size / 1000
  return Math.round(mbSize) + ' Mb'
}

export default function FilesList({files}) {
  const {mode} = useColorScheme()
  const [audioMeta, setAudioMeta] = useState(audioMetaCache)
  const [downloading, setDownloading] = useState({})
  const controllersRef = useRef({})

  const musicFiles = files.filter(file => file.mime.startsWith('audio'))
  const pdfFiles = files.filter(file => file.mime === 'application/pdf')

  const sortedMusicFiles = musicFiles.sort((a, b) => {
    const extensionOrder = {
      '.flac': 0,
      '.ogg': 1,
      '.aac': 2,
      '.mp3': 3
    }
    return extensionOrder[a.ext.toLowerCase()] - extensionOrder[b.ext.toLowerCase()]
  })

  useEffect(() => {
    const audioFiles = files.filter(f => f.mime.startsWith('audio'))
    if (audioFiles.length === 0) {
      return
    }

    let cancelled = false

    async function fetchAllMeta() {
      const mm = await import('music-metadata-browser') // eslint-disable-line node/no-unsupported-features/es-syntax
      const results = {}
      await Promise.all(
        audioFiles.map(async file => {
          if (file.id in audioMetaCache) {
            results[file.id] = audioMetaCache[file.id]
            return
          }

          try {
            const response = await fetch(`${apiUrl}${file.url}`, {
              headers: {Range: 'bytes=0-32767'},
            })
            const buffer = await response.arrayBuffer()
            const meta = await mm.parseBuffer(new Uint8Array(buffer), {mimeType: file.mime, skipCovers: true})
            audioMetaCache[file.id] = meta.format
            results[file.id] = meta.format
          } catch {
            audioMetaCache[file.id] = null
            results[file.id] = null
          }
        })
      )
      if (!cancelled) {
        setAudioMeta(results)
      }
    }

    fetchAllMeta()
    return () => {
      cancelled = true
    }
  }, [files])

  const losslessFormats = new Set(['.flac', '.wav', '.aiff', '.alac'])

  const qualityBadge = ext => {
    const isHaute = losslessFormats.has(ext.toLowerCase())
    return (
      <Typography variant='caption' sx={{
        backgroundColor: isHaute ? '#07332f' : '#393940',
        color: isHaute ? '#21feec' : '#fff',
        borderRadius: '0.66rem',
        border: mode === 'dark' ? `1px solid ${isHaute ? '#21feec' : '#fff'}` : 'none',
        padding: '0.15rem 0.5rem',
        fontWeight: 'bold',
        letterSpacing: '0.05rem',
        textTransform: 'uppercase',
      }}
      >{isHaute ? 'Haute' : 'Faible'}</Typography>
    )
  }

  const renderMeta = file => {
    const format = file.ext.replace('.', '').toUpperCase()

    if (!(file.id in audioMeta)) {
      return <Skeleton variant='text' width={80} />
    }

    const meta = audioMeta[file.id]
    const sampleRate = meta?.sampleRate ? `${meta.sampleRate / 1000} kHz` : null
    const bitDepth = meta?.bitsPerSample ? `${meta.bitsPerSample} bits` : null
    const bitrate = meta?.bitrate ? `${Math.round(meta.bitrate / 1000)} kbps` : null
    const details = [sampleRate, bitDepth ?? bitrate].filter(Boolean).join(' · ')

    return (
      <Box display='flex' flexDirection='column' gap={0.5}>
        <Box display='flex' alignItems='center' gap={1}>
          {qualityBadge(file.ext)}
          <Typography variant='caption' sx={{fontWeight: 'bold', ml: 1}}>{format}</Typography>
        </Box>
        {details && <Typography variant='caption' sx={{color: 'text.secondary'}}>{details}</Typography>}
      </Box>
    )
  }

  useEffect(() => () => {
    for (const c of Object.values(controllersRef.current)) {
      c.abort()
    }
  }, [])

  const handleClick = async (e, url, fileName, fileId) => {
    e.stopPropagation()
    if (fileId in downloading) {
      return
    }

    const controller = new AbortController()
    controllersRef.current[fileId] = controller

    setDownloading(prev => ({...prev, [fileId]: 0}))
    try {
      const response = await fetch(url, {signal: controller.signal})
      const contentLength = Number(response.headers.get('content-length'))
      const reader = response.body.getReader()
      const chunks = []
      let received = 0

      for (;;) {
        // eslint-disable-next-line no-await-in-loop -- chaque chunk dépend du précédent, la lecture est intrinsèquement séquentielle
        const {done, value} = await reader.read()
        if (done) {
          break
        }

        chunks.push(value)
        received += value.length
        if (contentLength) {
          setDownloading(prev => ({...prev, [fileId]: Math.round(received / contentLength * 100)}))
        }
      }

      const blob = new Blob(chunks, {type: response.headers.get('content-type')})
      const blobUrl = URL.createObjectURL(blob)
      const a = document.createElement('a')
      a.href = blobUrl
      a.download = fileName
      a.click()
      URL.revokeObjectURL(blobUrl)
    } catch (error) {
      if (error.name !== 'AbortError') {
        throw error
      }
    } finally {
      delete controllersRef.current[fileId]
      setDownloading(prev => {
        const next = {...prev}
        delete next[fileId]
        return next
      })
    }
  }

  const handleCancel = (e, fileId) => {
    e.stopPropagation()
    controllersRef.current[fileId]?.abort()
  }

  return (
    <>
      {musicFiles.length > 0 && (
        <List
          sx={{width: '100%', maxWidth: 800}}
          component='nav'
          aria-labelledby='nested-list-subheader'
        >
          <ListSubheader disableSticky sx={{bgcolor: 'background.paper'}}>
            <Box paddingBlock={1} display='flex' justifyContent='center'>
              <LibraryMusicIcon />
              <Typography gutterBottom marginLeft={1} variant='button'>Musiques</Typography>
            </Box>
          </ListSubheader>
          <TableContainer component={Paper}>
            <Table size='small' aria-label='Musiques'>
              <TableHead>
                <TableRow>
                  <StyledTableCell align='center'>FORMAT</StyledTableCell>
                  <StyledTableCell />
                </TableRow>
              </TableHead>
              <TableBody>
                {sortedMusicFiles.map(file => (
                  <StyledTableRow key={file.id}>
                    <StyledTableCell>
                      {renderMeta(file)}
                    </StyledTableCell>
                    <StyledTableCell align='left'>
                      <Link
                        href='#'
                        underline='hover'
                        sx={{fontWeight: 'bold', pointerEvents: file.id in downloading ? 'none' : 'auto'}}
                        aria-label='download'
                        onClick={e => handleClick(e, `${apiUrl}${file.url}`, file.name, file.id)}
                      >
                        {file.name}
                      </Link>
                      <small style={{marginLeft: 3}}>({formatSize(file.size)})</small>
                      {file.id in downloading && (
                        <Box sx={{mt: 0.5}}>
                          <LinearProgress
                            variant={downloading[file.id] > 0 ? 'determinate' : 'indeterminate'}
                            value={downloading[file.id]}
                          />
                          <Box display='flex' justifyContent='space-between' alignItems='center'>
                            <Typography variant='caption' sx={{color: 'text.secondary'}}>
                              {downloading[file.id]} %
                            </Typography>
                            <Typography
                              variant='caption'
                              sx={{color: 'error.main', cursor: 'pointer'}}
                              onClick={e => handleCancel(e, file.id)}
                            >
                              Annuler
                            </Typography>
                          </Box>
                        </Box>
                      )}
                    </StyledTableCell>
                  </StyledTableRow>
                ))}
              </TableBody>
            </Table>
          </TableContainer>
        </List>
      )}

      {pdfFiles.length > 0 && (
        <List
          sx={{width: '100%', maxWidth: 800}}
          component='nav'
          aria-labelledby='nested-list-subheader'
        >
          <ListSubheader disableSticky sx={{marginTop: 2, bgcolor: 'background.paper'}}>
            <Box paddingBlock={1} display='flex' justifyContent='center' alignSelf='center'>
              <DescriptionIcon />
              <Typography gutterBottom marginLeft={1} variant='button'>Paroles</Typography>
            </Box>
          </ListSubheader>
          <TableContainer component={Paper}>
            <Table size='small' aria-label='Paroles'>
              <TableHead>
                <TableRow>
                  <StyledTableCell>LANGUE</StyledTableCell>
                  <StyledTableCell />
                </TableRow>
              </TableHead>
              <TableBody>
                {pdfFiles.map(file => (
                  <StyledTableRow key={file.id}>
                    <StyledTableCell>
                      <strong>
                        {file.caption}
                      </strong>
                    </StyledTableCell>
                    <StyledTableCell align='left'>
                      <Link
                        href='#'
                        underline='hover'
                        sx={{fontWeight: 'bold'}}
                        aria-label='download'
                        onClick={e => handleClick(e, `${apiUrl}${file.url}`, file.name)}
                      >
                        {file.name}
                      </Link>
                      <small style={{marginLeft: 3}}>({formatSize(file.size)})</small>
                    </StyledTableCell>
                  </StyledTableRow>
                ))}
              </TableBody>
            </Table>
          </TableContainer>
        </List>
      )}
    </>
  )
}

FilesList.propTypes = {
  files: PropTypes.array.isRequired
}
