// One readable line per parsed lesson, the form both snapshots compare: what a student sees // (day, time, weeks or dates, subject, kind, room, teacher, groups), not ids or source colours. const DAYS = ["", "пн", "вт", "ср", "чт", "пт", "сб", "вс"]; const weeks = (ranges) => ranges?.map(([a, b]) => (b === null ? `с ${a}` : a === b ? `${a}` : `${a}-${b}`)).join(",") || ""; export const lessonLine = (l) => [ `${l.date || DAYS[l.day]} ${l.start}${l.end ? `–${l.end}` : ""}`, (l.date ? "один раз" : l.dates?.length ? `даты ${l.dates.join(",")}` : l.weekRanges ? `нед. ${weeks(l.weekRanges)}` : "каждую неделю") + (l.variant === "upper" ? ", верхняя строка" : l.variant === "lower" ? ", нижняя строка" : ""), l.subject, l.type, // The town only when the cell names one or the day's note puts the lesson there. [l.room, l.place].filter(Boolean).join(", "), l.teacher, [...l.groups, ...l.subgroups.map((s) => `${s.group}/${s.label.replace("Подгруппа ", "п")}`)].join(",") + // "Немецкий язык" or "Французский язык": a part of the group takes one of them. (l.option ? " · на выбор" : "") + // Russian for foreign students while the others have physical education. (l.audience === "foreign" ? " · иностранцам" : l.audience === "local" ? " · кроме иностранцев" : ""), ] .map((part) => String(part ?? "").replace(/\s+/g, " ").trim()) .join(" | "); // "Занятия в Полоцке": a day of a group or subgroup in another town, not a lesson. const placeLine = (p) => [ `@ ${p.date || DAYS[p.day]}${p.from ? ` с ${p.from}` : ""}`, p.text, [...p.groups, ...p.subgroups.map((s) => `${s.group}/${s.label.replace("Подгруппа ", "п")}`)].join(","), ].join(" | "); /** Sorted lesson lines of a parsed document, with its groups and period on top. */ export const snapshotOf = (schedule) => [ `# ${schedule.period} · ${schedule.groups.join(", ")}`, ...(schedule.places || []).map(placeLine).sort(), ...schedule.lessons.map(lessonLine).sort(), ]; /** Lines only in `before` (removed) and only in `after` (added), duplicates counted. */ export function diffLines(before, after) { const count = (lines) => lines.reduce((m, x) => m.set(x, (m.get(x) || 0) + 1), new Map()); const a = count(before), b = count(after); const removed = [], added = []; for (const [line, n] of a) for (let i = b.get(line) || 0; i < n; i++) removed.push(line); for (const [line, n] of b) for (let i = a.get(line) || 0; i < n; i++) added.push(line); return { removed, added }; }