const fs = require('node:fs/promises');
const path = require('node:path');
const { randomUUID } = require('node:crypto');

class Sinks {
  constructor() { this.items = new Map(); }
  async open(destination, size) {
    if (!Number.isSafeInteger(size) || size < 0) throw new Error('Taille invalide.');
    const id = randomUUID();
    const temporary = path.join(path.dirname(destination), `.${path.basename(destination)}.part-${id}`);
    const handle = await fs.open(temporary, 'wx', 0o600);
    this.items.set(id, { handle, temporary, destination, size, written: 0, queued: 0, pending: Promise.resolve(), closing: false });
    return { id, name: path.basename(destination) };
  }
  write(id, data) {
    const item = this.items.get(id);
    if (!item || item.closing) return Promise.reject(new Error('Téléchargement fermé.'));
    if (!(data instanceof Uint8Array) || data.byteLength === 0 || data.byteLength > 65536)
      return Promise.reject(new Error('Bloc invalide (maximum 64 Kio).'));
    if (item.written + data.byteLength > item.size) return Promise.reject(new Error('Taille annoncée dépassée.'));
    if (item.queued + data.byteLength > 4 * 1024 * 1024) return Promise.reject(new Error('Attendez la fin des écritures disque.'));
    const bytes = Buffer.from(data);
    item.written += bytes.length;
    item.queued += bytes.length;
    item.pending = item.pending.then(async () => {
      let offset = 0;
      while (offset < bytes.length) {
        const { bytesWritten } = await item.handle.write(bytes, offset, bytes.length - offset, null);
        if (!bytesWritten) throw new Error('Écriture disque interrompue.');
        offset += bytesWritten;
      }
    }).finally(() => { item.queued -= bytes.length; });
    return item.pending;
  }
  async close(id) {
    const item = this.items.get(id);
    if (!item || item.closing) throw new Error('Téléchargement fermé.');
    item.closing = true;
    try {
      await item.pending;
      if (item.written !== item.size) throw new Error('Téléchargement incomplet.');
      await item.handle.sync();
      await item.handle.close();
      await fs.rename(item.temporary, item.destination);
      this.items.delete(id);
      return { name: path.basename(item.destination), size: item.written };
    } catch (error) {
      await this.cleanup(id);
      throw error;
    }
  }
  async cleanup(id) {
    const item = this.items.get(id);
    if (!item) return;
    item.closing = true;
    await item.pending.catch(() => {});
    await item.handle.close().catch(() => {});
    await fs.unlink(item.temporary).catch(error => { if (error.code !== 'ENOENT') throw error; });
    this.items.delete(id);
  }
  async cleanupAll() { await Promise.all([...this.items.keys()].map(id => this.cleanup(id))); }
}
module.exports = { Sinks };
