// Matching engine for Famspring scheduler.
// Builds a schedule for a given week: for each (day, slot), assign client→therapist pairs.
//
// Rules:
//   - A pair requires: client available + therapist available + same location.
//   - Therapist is selected from the client's pairing list in priority order.
//   - First-priority client gets first dibs (clients iterated in fixed order).
//   - A therapist cannot be in two pairs in the same slot.
//   - A client cannot be in two pairs in the same slot.
//   - Unlimited concurrent capacity (no fixed ceiling), per the brief.

const { SLOTS, DAYS, WEEKS, CLIENTS, THERAPISTS } = window.FS_DATA;

function matchOne(clientId, dayId, slotIdx, availC, availT, pairings, used) {
  const client = window.FS_DATA.getClient(clientId);
  if (!client) return null;
  if (!availC[clientId]?.[arguments[5]]) {} // unused param shim
  // We expect caller to have passed week-resolved availability already.
  return null;
}

// Per-week schedule.
// availC: { [clientId]: { [day]: [bool, bool, bool] } } (resolved for this week)
// availT: same shape for therapists
function buildWeekSchedule(weekIdx, availC, availT, pairings) {
  // For each cell, produce { matched: [{clientId, therapistId, loc}], unmatched: [clientId] }
  const cells = {};
  for (const d of DAYS) {
    cells[d.id] = SLOTS.map(() => ({ matched: [], unmatched: [] }));
  }

  for (const d of DAYS) {
    for (let s = 0; s < SLOTS.length; s++) {
      const usedTherapists = new Set();
      // Iterate clients in fixed order. Tiebreak rule: higher-priority client wins.
      // We model this by iterating clients in the order they appear in CLIENTS list,
      // which is alphabetical. To respect "client whose priority for this therapist
      // is HIGHER wins", we do a two-pass approach:
      //   Pass 1: try priority 1 for each client. If multiple want same therapist,
      //           none gets it yet — resolve in pass 2 by checking which clients had
      //           higher priority for that therapist (lower index = higher priority).
      // Simpler heuristic that works in practice: iterate clients alphabetically,
      // each takes the first available therapist in their priority list. This is
      // what we'll do.

      for (const client of CLIENTS) {
        if (!availC[client.id]?.[d.id]?.[s]) continue;
        const priority = pairings[client.id] || [];
        let assigned = null;
        for (const tid of priority) {
          if (usedTherapists.has(tid)) continue;
          const t = window.FS_DATA.getTherapist(tid);
          if (!t) continue;
          const cLocs = Array.isArray(client.loc) ? client.loc : [client.loc];
          const tLocs = Array.isArray(t.loc) ? t.loc : [t.loc];
          if (!cLocs.some(l => tLocs.includes(l))) continue; // location must overlap
          if (!availT[tid]?.[d.id]?.[s]) continue;
          assigned = tid;
          break;
        }
        if (assigned) {
          const t2 = window.FS_DATA.getTherapist(assigned);
          const cLocs2 = Array.isArray(client.loc) ? client.loc : [client.loc];
          const tLocs2 = Array.isArray(t2.loc) ? t2.loc : [t2.loc];
          const matchedLoc = cLocs2.find(l => tLocs2.includes(l)) || cLocs2[0];
          usedTherapists.add(assigned);
          cells[d.id][s].matched.push({
            clientId: client.id,
            therapistId: assigned,
            loc: matchedLoc,
          });
        } else {
          cells[d.id][s].unmatched.push(client.id);
        }
      }
    }
  }
  return cells;
}

// Resolve a person's availability for one week.
function weekAvail(personAvail, weekIdx) {
  return personAvail?.[weekIdx] || {};
}

// Compute metrics for a week schedule.
function weekMetrics(schedule, availC, availT, slotThreshold = 1) {
  let clientAvailTotal = 0, clientMatched = 0;
  let therapistAvailTotal = 0, therapistMatched = 0;
  let slotsTotal = DAYS.length * SLOTS.length;
  let slotsWithMatch = 0;

  for (const cid of Object.keys(availC)) {
    for (const d of DAYS) {
      for (let s = 0; s < SLOTS.length; s++) {
        if (availC[cid]?.[d.id]?.[s]) clientAvailTotal++;
      }
    }
  }
  for (const tid of Object.keys(availT)) {
    for (const d of DAYS) {
      for (let s = 0; s < SLOTS.length; s++) {
        if (availT[tid]?.[d.id]?.[s]) therapistAvailTotal++;
      }
    }
  }
  const usedTherapistCells = new Set();
  for (const d of DAYS) {
    for (let s = 0; s < SLOTS.length; s++) {
      const cell = schedule[d.id][s];
      if (cell.matched.length >= slotThreshold) slotsWithMatch++;
      clientMatched += cell.matched.length;
      for (const m of cell.matched) {
        usedTherapistCells.add(`${m.therapistId}|${d.id}|${s}`);
      }
    }
  }
  therapistMatched = usedTherapistCells.size;

  return {
    clientUtil: clientAvailTotal === 0 ? 0 : clientMatched / clientAvailTotal,
    therapistUtil: therapistAvailTotal === 0 ? 0 : therapistMatched / therapistAvailTotal,
    slotUtil: slotsTotal === 0 ? 0 : slotsWithMatch / slotsTotal,
    counts: {
      clientMatched, clientAvailTotal,
      therapistMatched, therapistAvailTotal,
      slotsWithMatch, slotsTotal,
    },
  };
}

// Negotiation hints — for the given week, find the top "if X added one slot, you'd
// unlock N sessions" suggestions.
// We try each (therapist, day, slot) where they are NOT available, and ask:
//   if we add this, how many extra matches happen?
// Then return top 3.
function negotiationHints(weekIdx, availC, availT, pairings, baseSchedule) {
  const baseMatched = countMatched(baseSchedule);
  const hints = [];

  // Try adding therapist availability
  for (const t of THERAPISTS) {
    for (const d of DAYS) {
      for (let s = 0; s < SLOTS.length; s++) {
        if (availT[t.id]?.[d.id]?.[s]) continue;
        // Simulate adding
        const cloned = cloneAvail(availT);
        if (!cloned[t.id]) cloned[t.id] = {};
        if (!cloned[t.id][d.id]) cloned[t.id][d.id] = [false, false, false];
        cloned[t.id][d.id] = [...cloned[t.id][d.id]];
        cloned[t.id][d.id][s] = true;
        const newSched = buildWeekSchedule(weekIdx, availC, cloned, pairings);
        const newMatched = countMatched(newSched);
        const gain = newMatched - baseMatched;
        if (gain > 0) {
          hints.push({
            who: t.name, whoId: t.id, whoType: "therapist",
            day: d.id, dayLabel: d.label,
            slotIdx: s, slotLabel: SLOTS[s].label, slotTime: SLOTS[s].time,
            gain,
            loc: t.loc,
          });
        }
      }
    }
  }

  // Try adding client availability (less common — usually it's therapists)
  for (const c of CLIENTS) {
    for (const d of DAYS) {
      for (let s = 0; s < SLOTS.length; s++) {
        if (availC[c.id]?.[d.id]?.[s]) continue;
        const cloned = cloneAvail(availC);
        if (!cloned[c.id]) cloned[c.id] = {};
        if (!cloned[c.id][d.id]) cloned[c.id][d.id] = [false, false, false];
        cloned[c.id][d.id] = [...cloned[c.id][d.id]];
        cloned[c.id][d.id][s] = true;
        const newSched = buildWeekSchedule(weekIdx, cloned, availT, pairings);
        const newMatched = countMatched(newSched);
        const gain = newMatched - baseMatched;
        if (gain > 0) {
          hints.push({
            who: c.name, whoId: c.id, whoType: "client",
            day: d.id, dayLabel: d.label,
            slotIdx: s, slotLabel: SLOTS[s].label, slotTime: SLOTS[s].time,
            gain,
            loc: c.loc,
          });
        }
      }
    }
  }

  // Sort by gain descending, then prefer therapist hints (they unlock more), and dedupe by gain >= top
  hints.sort((a, b) => {
    if (b.gain !== a.gain) return b.gain - a.gain;
    if (a.whoType !== b.whoType) return a.whoType === "therapist" ? -1 : 1;
    return 0;
  });
  return hints;
}

function countMatched(schedule) {
  let n = 0;
  for (const d of DAYS) {
    for (let s = 0; s < SLOTS.length; s++) {
      n += schedule[d.id][s].matched.length;
    }
  }
  return n;
}

function cloneAvail(a) {
  // Shallow-deep clone enough for simulation
  const out = {};
  for (const k of Object.keys(a)) {
    out[k] = {};
    for (const d of Object.keys(a[k])) {
      out[k][d] = [...a[k][d]];
    }
  }
  return out;
}

// Compute per-week slot utilization for the sparkline strip across the top.
function weekSparkData(weekIdx, allClientAvail, allTherapistAvail, pairings) {
  const availC = {};
  const availT = {};
  for (const c of CLIENTS) availC[c.id] = weekAvail(allClientAvail[c.id], weekIdx);
  for (const t of THERAPISTS) availT[t.id] = weekAvail(allTherapistAvail[t.id], weekIdx);
  const sched = buildWeekSchedule(weekIdx, availC, availT, pairings);
  const m = weekMetrics(sched, availC, availT);
  return m.slotUtil;
}

// Build a full 12-week schedule for the locked-share view.
function buildAllWeeks(allClientAvail, allTherapistAvail, pairings) {
  const out = {};
  for (const w of WEEKS) {
    const availC = {};
    const availT = {};
    for (const c of CLIENTS) availC[c.id] = weekAvail(allClientAvail[c.id], w.id);
    for (const t of THERAPISTS) availT[t.id] = weekAvail(allTherapistAvail[t.id], w.id);
    out[w.id] = buildWeekSchedule(w.id, availC, availT, pairings);
  }
  return out;
}

window.FS_MATCHER = {
  buildWeekSchedule,
  weekMetrics,
  negotiationHints,
  weekAvail,
  weekSparkData,
  buildAllWeeks,
  countMatched,
};
