export type WorkItem = {
  clientId: string;
  kind: "static_post" | "video";
  sequence: number;
  priority: "high" | "medium" | "low";
};

export type Assignment = WorkItem & { assigneeId: string; date: string };

/**
 * Spreads a department's tasks across working days so daily totals differ by
 * at most one. Extra slots are distributed through the month instead of being
 * clustered at the beginning, and different clients are rotated per day.
 */
export function balanceTasksAcrossDays<T extends { client: string }>(items: T[], workingDays: number[]): Array<T & { day: number }> {
  if (!workingDays.length) throw new Error("At least one working day is required");
  if (!items.length) return [];

  const queues = new Map<string, T[]>();
  items.forEach((item) => queues.set(item.client, [...(queues.get(item.client) ?? []), item]));
  const clientTotals = new Map([...queues].map(([client, queue]) => [client, queue.length]));
  const clientAssigned = new Map<string, number>();
  const base = Math.floor(items.length / workingDays.length);
  const remainder = items.length % workingDays.length;
  const result: Array<T & { day: number }> = [];

  workingDays.forEach((day, dayIndex) => {
    const capacity = base + (Math.floor((dayIndex + 1) * remainder / workingDays.length) - Math.floor(dayIndex * remainder / workingDays.length));
    const usedToday = new Set<string>();
    for (let slot = 0; slot < capacity; slot++) {
      const available = [...queues.entries()].filter(([, queue]) => queue.length > 0);
      const fresh = available.filter(([client]) => !usedToday.has(client));
      const candidates = fresh.length ? fresh : available;
      const nextPosition = result.length + 1;
      candidates.sort((a, b) => {
        const deficitA = nextPosition * (clientTotals.get(a[0]) ?? 0) / items.length - (clientAssigned.get(a[0]) ?? 0);
        const deficitB = nextPosition * (clientTotals.get(b[0]) ?? 0) / items.length - (clientAssigned.get(b[0]) ?? 0);
        return deficitB - deficitA || b[1].length - a[1].length || a[0].localeCompare(b[0]);
      });
      const [client, queue] = candidates[0];
      const item = queue.shift()!;
      usedToday.add(client);
      clientAssigned.set(client, (clientAssigned.get(client) ?? 0) + 1);
      result.push({ ...item, day });
    }
  });

  return result;
}

/**
 * Deterministic least-load scheduler.
 * High priority work is considered first, but every client is round-robined
 * and penalized when it appeared on the same day, preventing daily clusters.
 */
export function distributeWork(
  items: WorkItem[],
  workingDates: string[],
  eligibleAssignees: Record<WorkItem["kind"], string[]>,
): Assignment[] {
  if (!workingDates.length) throw new Error("At least one working day is required");
  const rank = { high: 0, medium: 1, low: 2 };
  const ordered = [...items].sort((a, b) =>
    rank[a.priority] - rank[b.priority] ||
    a.sequence - b.sequence ||
    a.clientId.localeCompare(b.clientId),
  );
  const dailyLoad = new Map<string, number>();
  const employeeLoad = new Map<string, number>();
  const clientDayLoad = new Map<string, number>();

  return ordered.map((item, index) => {
    const people = eligibleAssignees[item.kind];
    if (!people?.length) throw new Error(`No eligible assignee for ${item.kind}`);
    const assigneeId = [...people].sort((a, b) => (employeeLoad.get(a) ?? 0) - (employeeLoad.get(b) ?? 0) || a.localeCompare(b))[0];
    const candidates = workingDates.map((date, dateIndex) => {
      const base = dailyLoad.get(`${assigneeId}:${date}`) ?? 0;
      const repeat = clientDayLoad.get(`${item.clientId}:${date}`) ?? 0;
      const spread = Math.abs(dateIndex - (index % workingDates.length)) / workingDates.length;
      return { date, score: base * 100 + repeat * 35 + spread };
    }).sort((a, b) => a.score - b.score || a.date.localeCompare(b.date));
    const date = candidates[0].date;
    dailyLoad.set(`${assigneeId}:${date}`, (dailyLoad.get(`${assigneeId}:${date}`) ?? 0) + 1);
    employeeLoad.set(assigneeId, (employeeLoad.get(assigneeId) ?? 0) + 1);
    clientDayLoad.set(`${item.clientId}:${date}`, (clientDayLoad.get(`${item.clientId}:${date}`) ?? 0) + 1);
    return { ...item, assigneeId, date };
  });
}
