// Algoritmo di assegnazione persone -> stanze.
// Vincolo: ogni stanza è mono-genere (M con M, F con F).
// Strategia: 1) partiziona le stanze tra i due generi in modo che ciascun
// genere ci stia (capienza sufficiente); 2) distribuisce casualmente le
// persone nelle stanze del proprio genere, cercando di non lasciare stanze
// completamente vuote.

(function (global) {
  function shuffle(arr, rng) {
    const a = arr.slice();
    for (let i = a.length - 1; i > 0; i--) {
      const j = Math.floor(rng() * (i + 1));
      [a[i], a[j]] = [a[j], a[i]];
    }
    return a;
  }

  // Sceglie quali stanze vanno alle femmine e quali ai maschi.
  // Preferisce partizioni in cui la capienza femminile combacia esattamente
  // col numero di femmine (zero letti sprecati da quel lato => niente stanze
  // vuote lato femmine). Fallback: qualsiasi partizione valida.
  function chooseGenderPartition(rooms, nF, nM, rng) {
    const n = rooms.length;
    const total = rooms.reduce((s, r) => s + r.posti, 0);
    const valid = [];
    const exact = [];
    // esclude mask 0 e mask "tutte" così ogni genere ha almeno una stanza
    for (let mask = 1; mask < (1 << n) - 1; mask++) {
      let capF = 0;
      for (let i = 0; i < n; i++) if (mask & (1 << i)) capF += rooms[i].posti;
      const capM = total - capF;
      if (capF >= nF && capM >= nM) {
        valid.push(mask);
        if (capF === nF) exact.push(mask);
      }
    }
    const pool = exact.length ? exact : valid;
    if (!pool.length) {
      throw new Error(
        "Nessuna partizione valida: capienza insufficiente per il vincolo di genere."
      );
    }
    return pool[Math.floor(rng() * pool.length)];
  }

  // Distribuisce le persone nelle stanze del proprio genere.
  // Prima assegna 1 persona a stanza (copertura => niente stanze vuote se
  // le persone sono >= delle stanze), poi riempie i letti restanti a caso.
  function fillRooms(roomsG, peopleG, rng) {
    const rooms = shuffle(roomsG, rng);
    const people = shuffle(peopleG, rng);
    const assign = {};
    rooms.forEach((r) => (assign[r.id] = []));

    let idx = 0;
    if (people.length >= rooms.length) {
      for (const r of rooms) assign[r.id].push(people[idx++]);
    }

    const slots = [];
    for (const r of rooms) {
      const free = r.posti - assign[r.id].length;
      for (let k = 0; k < free; k++) slots.push(r.id);
    }
    const freeSlots = shuffle(slots, rng);
    while (idx < people.length) {
      const roomId = freeSlots.shift();
      assign[roomId].push(people[idx++]);
    }
    return assign;
  }

  function assign(rooms, participants, rng) {
    rng = rng || Math.random;
    const females = participants.filter((p) => p.genere === "F");
    const males = participants.filter((p) => p.genere === "M");

    const mask = chooseGenderPartition(rooms, females.length, males.length, rng);
    const fRooms = rooms.filter((r, i) => mask & (1 << i));
    const mRooms = rooms.filter((r, i) => !(mask & (1 << i)));

    const fAssign = fillRooms(fRooms, females.map((p) => p.nome), rng);
    const mAssign = fillRooms(mRooms, males.map((p) => p.nome), rng);

    const byPerson = {};
    const roomGender = {};
    fRooms.forEach((r) => (roomGender[r.id] = "F"));
    mRooms.forEach((r) => (roomGender[r.id] = "M"));
    for (const rid in fAssign) fAssign[rid].forEach((nome) => (byPerson[nome] = rid));
    for (const rid in mAssign) mAssign[rid].forEach((nome) => (byPerson[nome] = rid));

    return {
      byPerson, // nome -> roomId
      roomGender, // roomId -> "F" | "M"
      fRooms,
      mRooms,
    };
  }

  const Matching = { assign, shuffle };
  if (typeof module !== "undefined" && module.exports) module.exports = Matching;
  global.Matching = Matching;
})(typeof window !== "undefined" ? window : globalThis);
