-- libmusic: list and play dfpwm music tracks from a directory.
--
-- A factory: `local createMusic = require('/apis/libmusic'); local music = createMusic();`
--
-- Non-UI logic lives here so the `music` program stays thin and the listing is
-- unit-testable with an injected `fs`. Dependencies are injectable to keep the
-- module testable outside the CC runtime (mirrors libccpm/libversion style).

local DEFAULT_DIR = '/musics';
local EXTENSION = '.dfpwm';
local CHUNK_SIZE = 16 * 1024;

local function createMusic(opts)
  opts = opts or {};
  local fsLib = opts.fs or fs;
  local peripheralLib = opts.peripheral or peripheral;
  local osLib = opts.os or os;
  local ioLib = opts.io or io;
  local dir = opts.dir or DEFAULT_DIR;

  local api = {};

  -- Returns a sorted list of `{ name = <display>, path = <full path> }` for every
  -- `.dfpwm` file in the music directory; `{}` when the directory is missing.
  function api.listTracks()
    local tracks = {};
    if not fsLib.isDir(dir) then return tracks; end
    for _, entry in ipairs(fsLib.list(dir)) do
      if entry:sub(-#EXTENSION) == EXTENSION then
        tracks[#tracks + 1] = {
          name = entry:sub(1, #entry - #EXTENSION),
          path = dir .. '/' .. entry,
        };
      end
    end
    table.sort(tracks, function(a, b) return a.name < b.name; end);
    return tracks;
  end

  -- Plays a dfpwm file through the first speaker peripheral, blocking until the
  -- whole track has been queued and drained. Errors if no speaker is attached.
  function api.play(path)
    local speaker = peripheralLib.find('speaker');
    if not speaker then
      error('no speaker attached', 0);
    end

    local dfpwm = opts.dfpwm or require('cc.audio.dfpwm');
    local decoder = dfpwm.make_decoder();

    for chunk in ioLib.lines(path, CHUNK_SIZE) do
      local buffer = decoder(chunk);
      while not speaker.playAudio(buffer) do
        osLib.pullEvent('speaker_audio_empty');
      end
    end
  end

  return api;
end

return createMusic;
