import { api } from '../api.js';
import { escapeHtml, initMultiSelect, showAlert, hideAlert } from '../utils.js';

const NO_BUILDING = '__no_building__';
const DISPLAY_ALL = 'all';
const TARGET_CLASSROOMS = 'classrooms';
const TARGET_TEACHERS = 'teachers';
const TARGET_DEPARTMENTS = 'departments';
const ROOM_PREFIX = 'room:';
const TEACHER_PREFIX = 'teacher:';
const DEPARTMENT_PREFIX = 'department:';
const MS_PER_DAY = 24 * 60 * 60 * 1000;

const WORKLOAD_TARGETS = {
    [TARGET_CLASSROOMS]: {
        allLabel: 'Все аудитории',
        emptyLabel: 'Нет аудиторий по выбранным фильтрам',
        rowLabel: 'Аудитория',
        summaryLabel: 'Аудиторий',
        periodTitle: 'Нагрузка аудитории по неделям',
        selectError: 'Выберите аудиторию',
        prefix: ROOM_PREFIX
    },
    [TARGET_TEACHERS]: {
        allLabel: 'Все преподаватели',
        emptyLabel: 'Нет преподавателей',
        rowLabel: 'Преподаватель',
        summaryLabel: 'Преподавателей',
        periodTitle: 'Нагрузка преподавателя по неделям',
        selectError: 'Выберите преподавателя',
        prefix: TEACHER_PREFIX
    },
    [TARGET_DEPARTMENTS]: {
        allLabel: 'Все кафедры',
        emptyLabel: 'Нет кафедр',
        rowLabel: 'Кафедра',
        summaryLabel: 'Кафедр',
        periodTitle: 'Нагрузка кафедры по неделям',
        selectError: 'Выберите кафедру',
        prefix: DEPARTMENT_PREFIX
    }
};

const DAY_SHORT_LABELS = {
    1: 'Пн',
    2: 'Вт',
    3: 'Ср',
    4: 'Чт',
    5: 'Пт',
    6: 'Сб',
    7: 'Вс'
};

const DAY_FULL_LABELS = {
    1: 'Понедельник',
    2: 'Вторник',
    3: 'Среда',
    4: 'Четверг',
    5: 'Пятница',
    6: 'Суббота',
    7: 'Воскресенье'
};

const DAY_ORDER = [1, 2, 3, 4, 5, 6, 7];

const CELL_PARITY_LAYOUT = [
    { parity: 'ODD', label: 'Нечётная' },
    { parity: 'EVEN', label: 'Чётная' }
];

export async function initAuditoriumWorkload() {
    const dateInput = document.getElementById('workload-date');
    const refreshButton = document.getElementById('workload-refresh');
    const targetSelect = document.getElementById('workload-target-select');
    const displaySelect = document.getElementById('workload-display-select');
    const headerRow = document.getElementById('workload-header-row');
    const tbody = document.getElementById('workload-tbody');
    const summary = document.getElementById('workload-summary');
    const overviewBlock = document.getElementById('workload-overview-block');
    const roomBlock = document.getElementById('room-workload-block');
    const roomSummary = document.getElementById('room-workload-summary');
    const roomTables = document.getElementById('room-workload-tables');
    const overviewFilters = Array.from(document.querySelectorAll('.workload-overview-filter'));
    const dateLabel = dateInput?.closest('.form-group')?.querySelector('label');
    const minCapacityInput = document.getElementById('workload-min-capacity');

    let classrooms = [];
    let teachers = [];
    let departments = [];
    let timeSlots = [];
    let equipments = [];
    let groups = [];
    let semesters = [];
    let lessonsByEntitySlot = new Map();
    let failedGroupLoads = 0;
    let currentTarget = TARGET_CLASSROOMS;
    let currentView = DISPLAY_ALL;
    let selectedRoomId = null;
    let selectedEntityId = null;

    setDefaultDate();
    initFilters();
    bindEvents();
    await loadInitialData();

    function setDefaultDate() {
        if (!dateInput || dateInput.value) return;
        const today = new Date();
        dateInput.value = formatIsoDate(today);
    }

    function initFilters() {
        initMultiSelect('building-box', 'building-menu', 'building-text', 'building-checkboxes');
        initMultiSelect('equipment-box', 'equipment-menu', 'equipment-text', 'equipment-checkboxes');
    }

    function bindEvents() {
        refreshButton?.addEventListener('click', loadCurrentView);
        dateInput?.addEventListener('change', loadCurrentView);
        targetSelect?.addEventListener('change', () => {
            currentTarget = targetSelect.value || TARGET_CLASSROOMS;
            currentView = DISPLAY_ALL;
            selectedRoomId = null;
            selectedEntityId = null;
            populateDisplayOptions();
            applyDisplaySelection();
            syncDisplayMode();
            loadCurrentView();
        });
        displaySelect?.addEventListener('change', () => {
            applyDisplaySelection();
            syncDisplayMode();
            loadCurrentView();
        });

        [
            ['building-checkboxes', 'building-text', 'Все корпуса'],
            ['equipment-checkboxes', 'equipment-text', 'Любое оборудование']
        ].forEach(([containerId, textId, emptyText]) => {
            const container = document.getElementById(containerId);
            container?.addEventListener('change', () => {
                updateFilterText(containerId, textId, emptyText);
                if (currentView === DISPLAY_ALL) {
                    renderGrid();
                }
            });
        });

        minCapacityInput?.addEventListener('input', () => {
            if (currentView === DISPLAY_ALL) {
                renderGrid();
            }
        });
    }

    async function loadInitialData() {
        setTableLoading('Загрузка справочников и расписания...');
        try {
            const [loadedClassrooms, loadedEquipments, loadedGroups, academicYears, loadedTeachers, loadedDepartments] = await Promise.all([
                api.get('/api/classrooms'),
                api.get('/api/equipments'),
                api.get('/api/groups'),
                api.get('/api/admin/calendar/years').catch(() => []),
                api.get('/api/users/teachers'),
                api.get('/api/departments')
            ]);
            classrooms = [...loadedClassrooms].sort((a, b) => naturalCompare(a.name, b.name));
            teachers = [...loadedTeachers].sort((a, b) => naturalCompare(userName(a), userName(b)));
            departments = [...loadedDepartments].sort((a, b) => naturalCompare(departmentName(a), departmentName(b)));
            equipments = loadedEquipments;
            groups = loadedGroups;
            semesters = (academicYears || []).flatMap(year => year.semesters || []);
            currentTarget = targetSelect?.value || TARGET_CLASSROOMS;
            populateFilters();
            populateDisplayOptions();
            applyDisplaySelection();
            syncDisplayMode();
            await loadCurrentView();
        } catch (error) {
            const message = error.message || 'Ошибка загрузки данных загруженности';
            setTableError(message);
            setRoomError(message);
        }
    }

    async function loadCurrentView() {
        applyDisplaySelection();
        syncDisplayMode();
        try {
            if (currentView === DISPLAY_ALL) {
                await loadScheduleForSelectedDate();
            } else {
                await loadSelectedEntityWorkload();
            }
        } catch (error) {
            const message = error.message || 'Ошибка загрузки загруженности';
            if (currentView === DISPLAY_ALL) {
                setTableError(message);
            } else {
                setRoomError(message);
            }
        }
    }

    async function loadScheduleForSelectedDate() {
        const date = dateInput?.value;
        if (!date) {
            showAlert('workload-alert', 'Выберите дату', 'error');
            return;
        }

        hideAlert('workload-alert');
        failedGroupLoads = 0;
        setTableLoading('Загрузка расписания на выбранную дату...');
        timeSlots = await api.get(`/api/admin/time-slots/effective?date=${encodeURIComponent(date)}`);

        if (!groups.length) {
            lessonsByEntitySlot = new Map();
            renderGrid();
            showAlert('workload-alert', 'Группы не созданы, загруженность пуста', 'error');
            return;
        }

        const responses = await Promise.allSettled(
            groups.map(group => api.get(`/api/schedule?${scheduleParams(group, date, date)}`))
        );
        failedGroupLoads = responses.filter(response => response.status === 'rejected').length;
        const lessons = responses
            .filter(response => response.status === 'fulfilled')
            .flatMap(response => response.value || []);

        lessonsByEntitySlot = groupLessonsByEntitySlot(lessons);
        renderGrid();

        if (failedGroupLoads > 0) {
            showAlert('workload-alert', `Часть групп не загрузилась: ${failedGroupLoads}. Показаны доступные данные.`, 'error');
        }
    }

    async function loadSelectedEntityWorkload() {
        const date = dateInput?.value;
        const entity = selectedEntity();
        const target = targetConfig();
        if (!date) {
            showAlert('workload-alert', 'Выберите дату', 'error');
            setRoomError('Выберите дату для расчёта двухнедельного периода');
            return;
        }
        if (!entity) {
            showAlert('workload-alert', target.selectError, 'error');
            setRoomError(`${target.selectError} в поле «Отображение»`);
            return;
        }

        hideAlert('workload-alert');
        setRoomLoading('Загрузка расписания на две недели...');
        const range = twoWeekRange(date);
        const dates = datesBetween(range.startDate, range.endDate);
        const [slotLoad, lessonLoad] = await Promise.all([
            loadSlotsForDates(dates),
            loadEntityLessons(range, entity.id)
        ]);

        renderEntityWorkload(entity, range, dates, slotLoad.slotsByDate, lessonLoad.lessons, lessonLoad.lessonsByDateSlot, {
            failedSlotLoads: slotLoad.failedLoads,
            failedGroupLoads: lessonLoad.failedLoads,
            parityLessons: lessonLoad.parityLessons
        });

        const warnings = [];
        if (lessonLoad.failedLoads > 0) warnings.push(`групп не загрузилось: ${lessonLoad.failedLoads}`);
        if (slotLoad.failedLoads > 0) warnings.push(`сеток времени не загрузилось: ${slotLoad.failedLoads}`);
        if (warnings.length) {
            showAlert('workload-alert', `Часть данных не загрузилась (${warnings.join(', ')}). Показаны доступные данные.`, 'error');
        }
    }

    async function loadSlotsForDates(dates) {
        const responses = await Promise.allSettled(
            dates.map(date => api.get(`/api/admin/time-slots/effective?date=${encodeURIComponent(date)}`))
        );
        const slotsByDate = new Map();
        responses.forEach((response, index) => {
            const slots = response.status === 'fulfilled' ? response.value || [] : [];
            slotsByDate.set(dates[index], [...slots].sort(compareSlots));
        });
        return {
            slotsByDate,
            failedLoads: responses.filter(response => response.status === 'rejected').length
        };
    }

    async function loadEntityLessons(range, entityId) {
        if (!groups.length) {
            return {
                lessons: [],
                lessonsByDateSlot: new Map(),
                parityLessons: [],
                failedLoads: 0
            };
        }

        const responses = await Promise.allSettled(
            groups.map(group => api.get(`/api/schedule?${scheduleParams(group, range.startDate, range.endDate)}`))
        );
        const parityLessons = responses
            .filter(response => response.status === 'fulfilled')
            .flatMap(response => response.value || []);
        const lessons = uniqueLessons(parityLessons)
            .filter(lesson => entityIdsForLesson(lesson).some(id => sameId(id, entityId)));
        const lessonsByDateSlot = new Map();
        lessons.forEach(lesson => {
            const slotKey = lessonSlotKey(lesson);
            if (!lesson.date || slotKey == null) return;
            const key = dateSlotKey(lesson.date, slotKey);
            if (!lessonsByDateSlot.has(key)) lessonsByDateSlot.set(key, []);
            lessonsByDateSlot.get(key).push(lesson);
        });
        lessonsByDateSlot.forEach(items => {
            items.sort((a, b) => naturalCompare(a.subjectName, b.subjectName));
        });

        return {
            lessons,
            lessonsByDateSlot,
            parityLessons,
            failedLoads: responses.filter(response => response.status === 'rejected').length
        };
    }

    function scheduleParams(group, startDate, endDate) {
        return new URLSearchParams({
            groupId: String(group.id),
            startDate,
            endDate
        }).toString();
    }

    function populateFilters() {
        renderCheckboxes(
            'building-checkboxes',
            buildingOptions(),
            item => item.value,
            item => item.label
        );
        renderCheckboxes(
            'equipment-checkboxes',
            equipmentOptions(),
            item => item.id,
            item => item.name
        );

        updateFilterText('building-checkboxes', 'building-text', 'Все корпуса');
        updateFilterText('equipment-checkboxes', 'equipment-text', 'Любое оборудование');
    }

    function populateDisplayOptions() {
        if (!displaySelect) return;
        const selectedValue = displaySelect.value;
        const target = targetConfig();
        displaySelect.innerHTML = '';
        displaySelect.append(new Option(target.allLabel, DISPLAY_ALL));
        allEntities().forEach(entity => {
            displaySelect.append(new Option(entityOptionLabel(entity), `${target.prefix}${entity.id}`));
        });
        if (selectedValue && Array.from(displaySelect.options).some(option => option.value === selectedValue)) {
            displaySelect.value = selectedValue;
        }
    }

    function applyDisplaySelection() {
        const value = displaySelect?.value || DISPLAY_ALL;
        selectedRoomId = null;
        selectedEntityId = null;
        const target = targetConfig();
        if (value.startsWith(target.prefix)) {
            currentView = 'detail';
            selectedEntityId = value.slice(target.prefix.length);
            if (currentTarget === TARGET_CLASSROOMS) {
                selectedRoomId = selectedEntityId;
            }
            return;
        }
        currentView = DISPLAY_ALL;
    }

    function syncDisplayMode() {
        const roomMode = currentView !== DISPLAY_ALL;
        overviewFilters.forEach(filter => {
            filter.hidden = roomMode || currentTarget !== TARGET_CLASSROOMS;
        });
        if (overviewBlock) overviewBlock.hidden = roomMode;
        if (roomBlock) roomBlock.hidden = !roomMode;
        if (dateLabel) {
            dateLabel.textContent = roomMode ? 'Дата в периоде' : 'Дата';
        }
    }

    function selectedEntity() {
        const id = selectedEntityId || selectedRoomId;
        if (!id) return null;
        return allEntities().find(entity => sameId(entity.id, id)) || null;
    }

    function targetConfig() {
        return WORKLOAD_TARGETS[currentTarget] || WORKLOAD_TARGETS[TARGET_CLASSROOMS];
    }

    function allEntities() {
        if (currentTarget === TARGET_TEACHERS) return teachers;
        if (currentTarget === TARGET_DEPARTMENTS) return departments;
        return classrooms;
    }

    function visibleEntities() {
        const source = currentTarget === TARGET_CLASSROOMS ? filteredClassrooms() : allEntities();
        if (!selectedEntityId) return source;
        return source.filter(entity => sameId(entity.id, selectedEntityId));
    }

    function entityOptionLabel(entity) {
        if (currentTarget === TARGET_TEACHERS) {
            const details = [entity.jobTitle, entity.departmentName].filter(Boolean).join(', ');
            return details ? `${userName(entity)} · ${details}` : userName(entity);
        }
        if (currentTarget === TARGET_DEPARTMENTS) {
            const code = entity.departmentCode ? `код ${entity.departmentCode}` : '';
            return code ? `${departmentName(entity)} · ${code}` : departmentName(entity);
        }
        const details = [entity.building, entity.floor ? `${entity.floor} этаж` : null].filter(Boolean).join(', ');
        return details ? `${entity.name} · ${details}` : entity.name;
    }

    function buildingOptions() {
        const values = new Map();
        classrooms.forEach(room => {
            const key = buildingKey(room);
            values.set(key, key === NO_BUILDING ? 'Корпус не указан' : room.building);
        });
        return Array.from(values.entries())
            .map(([value, label]) => ({ value, label }))
            .sort((a, b) => naturalCompare(a.label, b.label));
    }

    function equipmentOptions() {
        const source = equipments.length
            ? equipments
            : classrooms.flatMap(room => room.equipments || []);
        const values = new Map();
        source.forEach(item => {
            if (item?.id && item?.name) values.set(String(item.id), item.name);
        });
        return Array.from(values.entries())
            .map(([id, name]) => ({ id, name }))
            .sort((a, b) => naturalCompare(a.name, b.name));
    }

    function renderCheckboxes(containerId, items, valueFn, labelFn) {
        const container = document.getElementById(containerId);
        if (!container) return;
        if (!items.length) {
            container.innerHTML = '<div class="workload-filter-empty">Нет данных</div>';
            return;
        }
        container.innerHTML = items.map(item => `
            <label class="checkbox-item">
                <input type="checkbox" value="${escapeHtml(valueFn(item))}">
                <span class="checkmark"></span>
                <span class="checkbox-label">${escapeHtml(labelFn(item))}</span>
            </label>
        `).join('');
    }

    function uniqueLessons(lessons) {
        const unique = new Map();
        lessons.forEach(lesson => {
            if (!lesson.timeSlotId) return;
            const key = [
                lesson.date,
                lesson.scheduleRuleSlotId || lesson.scheduleRuleId || '',
                lesson.classroomId,
                lesson.timeSlotId,
                lesson.subjectId || '',
                lesson.teacherId || ''
            ].join(':');
            const existing = unique.get(key);
            if (existing) {
                existing.groupNames = mergeNames(existing.groupNames, lesson.groupNames);
                existing.groupIds = mergeIds(existing.groupIds, lesson.groupIds);
                existing.subgroupNames = mergeNames(existing.subgroupNames, lesson.subgroupNames);
                existing.subgroupIds = mergeIds(existing.subgroupIds, lesson.subgroupIds);
                return;
            }
            unique.set(key, {
                ...lesson,
                groupNames: [...(lesson.groupNames || [])],
                groupIds: [...(lesson.groupIds || [])],
                subgroupNames: [...(lesson.subgroupNames || [])],
                subgroupIds: [...(lesson.subgroupIds || [])]
            });
        });
        return Array.from(unique.values());
    }

    function groupLessonsByEntitySlot(lessons) {
        const grouped = new Map();
        uniqueLessons(lessons).forEach(lesson => {
            entityIdsForLesson(lesson).forEach(entityId => {
                const key = entitySlotKey(entityId, lesson.timeSlotId);
                if (!grouped.has(key)) grouped.set(key, []);
                grouped.get(key).push(lesson);
            });
        });
        grouped.forEach(items => {
            items.sort((a, b) => naturalCompare(a.subjectName, b.subjectName));
        });
        return grouped;
    }

    function entityIdsForLesson(lesson) {
        if (currentTarget === TARGET_TEACHERS) {
            return lesson.teacherId ? [lesson.teacherId] : [];
        }
        if (currentTarget === TARGET_DEPARTMENTS) {
            return departmentIdsForLesson(lesson);
        }
        return lesson.classroomId ? [lesson.classroomId] : [];
    }

    function departmentIdsForLesson(lesson) {
        const departmentId = teacherDepartmentId(lesson.teacherId);
        return departmentId == null ? [] : [departmentId];
    }

    function renderGrid() {
        const rows = visibleEntities();
        const target = targetConfig();
        renderHeader();
        tbody.innerHTML = '';

        if (!rows.length) {
            tbody.innerHTML = `<tr><td colspan="${timeSlots.length + 1}" class="loading-row">${escapeHtml(target.emptyLabel)}</td></tr>`;
            renderSummary(0, 0, 0);
            return;
        }

        rows.forEach(entity => {
            const tr = document.createElement('tr');
            const entityCell = document.createElement('td');
            entityCell.className = `axis-cell${currentTarget === TARGET_CLASSROOMS && entity.isAvailable === false ? ' room-unavailable' : ''}`;
            entityCell.innerHTML = entityAxisHtml(entity);
            tr.appendChild(entityCell);

            timeSlots.forEach(slot => {
                const td = document.createElement('td');
                const lessons = lessonsByEntitySlot.get(entitySlotKey(entity.id, slot.id)) || [];
                td.className = lessons.length ? 'workload-busy-cell' : 'workload-free-cell';
                td.innerHTML = lessons.length
                    ? lessons.map(renderLessonCard).join('')
                    : '<span class="free-slot">Свободно</span>';
                tr.appendChild(td);
            });

            tbody.appendChild(tr);
        });

        const occupiedCells = countOccupiedCells(rows);
        renderSummary(rows.length, occupiedCells, rows.length * timeSlots.length);
    }

    function renderHeader() {
        const target = targetConfig();
        headerRow.innerHTML = `
            <th class="top-left-cell">
                <span class="top-label">Время</span>
                <span class="bottom-label">${escapeHtml(target.rowLabel)}</span>
            </th>
        `;

        timeSlots.forEach(slot => {
            const th = document.createElement('th');
            th.innerHTML = `
                <strong>${escapeHtml(slot.orderNumber || '')} пара</strong>
                <span>${escapeHtml(timeSlotLabel(slot))}</span>
                ${slot.scopeApplyMode === 'DEFAULT' ? '' : `<small>${escapeHtml(scopeShort(slot))}</small>`}
            `;
            headerRow.appendChild(th);
        });
    }

    function entityAxisHtml(entity) {
        if (currentTarget === TARGET_TEACHERS) {
            return `
                <strong>${escapeHtml(userName(entity))}</strong>
                <span>${escapeHtml(entity.jobTitle || 'Должность не указана')}</span>
                ${entity.departmentName ? `<small>${escapeHtml(entity.departmentName)}</small>` : ''}
            `;
        }
        if (currentTarget === TARGET_DEPARTMENTS) {
            return `
                <strong>${escapeHtml(departmentName(entity))}</strong>
                <span>${entity.departmentCode ? `Код: ${escapeHtml(entity.departmentCode)}` : 'Код не указан'}</span>
            `;
        }
        return `
            <strong>${escapeHtml(entity.name)}</strong>
            <span>${entity.capacity ? `${escapeHtml(entity.capacity)} мест` : 'Вместимость не указана'}</span>
            ${entity.building ? `<small>${escapeHtml(entity.building)}${entity.floor ? `, ${escapeHtml(entity.floor)} этаж` : ''}</small>` : ''}
        `;
    }

    function entityName(entity) {
        if (currentTarget === TARGET_TEACHERS) return userName(entity);
        if (currentTarget === TARGET_DEPARTMENTS) return departmentName(entity);
        return entity?.name || 'Аудитория без названия';
    }

    function entitySummaryDetails(entity) {
        if (currentTarget === TARGET_TEACHERS) {
            return [entity.jobTitle, entity.departmentName].filter(Boolean).join(', ');
        }
        if (currentTarget === TARGET_DEPARTMENTS) {
            return entity.departmentCode ? `код ${entity.departmentCode}` : '';
        }
        return [
            entity.capacity ? `${entity.capacity} мест` : null,
            entity.building || null,
            entity.floor ? `${entity.floor} этаж` : null
        ].filter(Boolean).join(', ');
    }

    function renderEntityWorkload(entity, range, dates, slotsByDate, lessons, lessonsByDateSlot, failures) {
        const slotRows = buildSlotRows(dates, slotsByDate, lessons);
        const dateGrid = buildDateGridByDay(dates, failures.parityLessons || lessons);
        renderRoomSummary(entity, range, dates, slotsByDate, lessonsByDateSlot, failures, dateGrid.unknownParityDates);

        if (!slotRows.length) {
            roomTables.innerHTML = '<div class="loading-row room-workload-state">За выбранный период нет временных слотов</div>';
            return;
        }

        roomTables.innerHTML = renderRoomCombinedWeekTable(dateGrid.datesByDay, slotRows, slotsByDate, lessonsByDateSlot);
    }

    function renderRoomSummary(entity, range, dates, slotsByDate, lessonsByDateSlot, failures, unknownParityDates) {
        if (!roomSummary) return;
        const totalCells = countAvailableRoomCells(dates, slotsByDate, lessonsByDateSlot);
        const occupiedCells = countOccupiedRoomCells(lessonsByDateSlot);
        const percent = totalCells ? Math.round((occupiedCells / totalCells) * 100) : 0;
        const details = entitySummaryDetails(entity);
        const warnings = [];
        if (failures.failedGroupLoads) warnings.push(`ошибок групп: ${failures.failedGroupLoads}`);
        if (failures.failedSlotLoads) warnings.push(`ошибок сеток: ${failures.failedSlotLoads}`);
        if (unknownParityDates) warnings.push('часть дат вне семестра');

        roomSummary.innerHTML = `
            <div class="room-summary-main">
                <strong>${escapeHtml(entityName(entity))}</strong>
                ${details ? `<span>${escapeHtml(details)}</span>` : ''}
            </div>
            <span class="room-summary-chip">${escapeHtml(formatDate(range.startDate))} - ${escapeHtml(formatDate(range.endDate))}</span>
            <span class="room-summary-chip">Занято: ${occupiedCells} из ${totalCells} (${percent}%)</span>
            ${warnings.length ? `<span class="room-summary-warning">${escapeHtml(warnings.join(', '))}</span>` : ''}
        `;
    }

    function renderRoomCombinedWeekTable(datesByDay, slotRows, slotsByDate, lessonsByDateSlot) {
        const dayHeaders = DAY_ORDER.map(day => `
            <th class="room-day-cell">
                <strong>${escapeHtml(dayShort(day))}</strong>
                <span>${escapeHtml(DAY_FULL_LABELS[day] || '')}</span>
                <div class="room-day-parity-dates">
                    ${CELL_PARITY_LAYOUT.map(({ parity, label }) => renderDayParityDate(datesByDay[day]?.[parity], label)).join('')}
                </div>
            </th>
        `).join('');
        const rows = slotRows.map(row => `
            <tr>
                <td class="room-time-cell">
                    <strong>${escapeHtml(row.orderLabel)}</strong>
                    <span>${escapeHtml(row.timeLabel)}</span>
                </td>
                ${DAY_ORDER.map(day => renderRoomCombinedSlotCell(datesByDay[day] || {}, row, slotsByDate, lessonsByDateSlot)).join('')}
            </tr>
        `).join('');

        return `
            <section class="room-week-panel">
                <div class="room-week-heading">
                    <h3>${escapeHtml(targetConfig().periodTitle)}</h3>
                    <span>${escapeHtml(roomWeekCaption(datesByDay))}</span>
                </div>
                <div class="workload-grid-container room-grid-container">
                    <table class="workload-table room-week-table">
                        <thead>
                            <tr>
                                <th class="room-time-heading">Время</th>
                                ${dayHeaders}
                            </tr>
                        </thead>
                        <tbody>
                            ${rows}
                        </tbody>
                    </table>
                </div>
            </section>
        `;
    }

    function renderDayParityDate(date, label) {
        return `<span>${escapeHtml(label)}: ${date ? escapeHtml(formatDateShort(date)) : '-'}</span>`;
    }

    function renderRoomCombinedSlotCell(datesByParity, row, slotsByDate, lessonsByDateSlot) {
        const states = CELL_PARITY_LAYOUT.map(({ parity, label }) => ({
            parity,
            label,
            state: roomSlotState(datesByParity[parity], row, slotsByDate, lessonsByDateSlot)
        }));

        if (roomSlotStatesEqual(states[0].state, states[1].state)) {
            return `<td class="room-combined-cell room-single-cell">${renderRoomUnifiedSlot(states[0].state)}</td>`;
        }

        const halves = states.map(({ parity, label, state }) =>
            renderRoomSlotHalf(state, parity, label)
        ).join('');
        return `<td class="room-combined-cell">${halves}</td>`;
    }

    function roomSlotState(date, row, slotsByDate, lessonsByDateSlot) {
        if (!date) {
            return { kind: 'empty', lessons: [] };
        }

        const key = dateSlotKey(date, row.key);
        const lessons = lessonsByDateSlot.get(key) || [];
        const slotExists = hasSlotForDate(date, row.key, slotsByDate) || lessons.length > 0;
        if (lessons.length) {
            return { kind: 'busy', lessons };
        }
        if (!slotExists) {
            return { kind: 'missing', lessons: [] };
        }
        return { kind: 'free', lessons: [] };
    }

    function renderRoomUnifiedSlot(state) {
        if (state.kind === 'busy') {
            return `<div class="room-week-unified room-week-unified-busy">${state.lessons.map(renderLessonCard).join('')}</div>`;
        }
        if (state.kind === 'missing') {
            return '<div class="room-week-unified room-week-unified-missing"><span class="slot-unavailable">Нет слота</span></div>';
        }
        if (state.kind === 'empty') {
            return '<div class="room-week-unified room-week-unified-empty"><span class="slot-unavailable">Нет даты</span></div>';
        }
        return '<div class="room-week-unified room-week-unified-free"><span class="free-slot">Свободно</span></div>';
    }

    function renderRoomSlotHalf(state, parity, label) {
        if (state.kind === 'busy') {
            return `
                <div class="room-week-half room-week-half-${parity.toLowerCase()} room-week-half-busy">
                    <span class="room-half-marker">${escapeHtml(label)}</span>
                    ${state.lessons.map(renderLessonCard).join('')}
                </div>
            `;
        }
        if (state.kind === 'missing') {
            return `
                <div class="room-week-half room-week-half-${parity.toLowerCase()} room-week-half-missing">
                    <span class="room-half-marker">${escapeHtml(label)}</span>
                    <span class="slot-unavailable">Нет слота</span>
                </div>
            `;
        }
        if (state.kind === 'empty') {
            return `
                <div class="room-week-half room-week-half-${parity.toLowerCase()} room-week-half-empty">
                    <span class="room-half-marker">${escapeHtml(label)}</span>
                    <span class="slot-unavailable">Нет даты</span>
                </div>
            `;
        }
        return `
            <div class="room-week-half room-week-half-${parity.toLowerCase()} room-week-half-free">
                <span class="room-half-marker">${escapeHtml(label)}</span>
                <span class="free-slot">Свободно</span>
            </div>
        `;
    }

    function roomSlotStatesEqual(first, second) {
        if (first.kind !== second.kind) return false;
        if (first.kind !== 'busy') return true;
        return lessonSetSignature(first.lessons) === lessonSetSignature(second.lessons);
    }

    function lessonSetSignature(lessons) {
        return lessons
            .map(lessonContentSignature)
            .sort((a, b) => naturalCompare(a, b))
            .join('|');
    }

    function lessonContentSignature(lesson) {
        return [
            lesson.scheduleRuleSlotId || lesson.scheduleRuleId || '',
            lesson.subjectId || lesson.subjectName || '',
            lesson.teacherId || lesson.teacherName || '',
            lesson.lessonTypeId || lesson.lessonTypeName || '',
            lesson.lessonFormat || '',
            valueListSignature(lesson.groupIds, lesson.groupNames),
            valueListSignature(lesson.subgroupIds, lesson.subgroupNames)
        ].join(':');
    }

    function valueListSignature(primary = [], fallback = []) {
        const primaryList = Array.isArray(primary) ? primary : [];
        const fallbackList = Array.isArray(fallback) ? fallback : [];
        const source = primaryList.length ? primaryList : fallbackList;
        return [...source]
            .map(value => String(value))
            .sort((a, b) => naturalCompare(a, b))
            .join(',');
    }

    function buildSlotRows(dates, slotsByDate, lessons) {
        const rows = new Map();
        dates.forEach(date => {
            (slotsByDate.get(date) || []).forEach(slot => {
                const key = slotOrderKey(slot);
                if (key == null) return;
                putSlotRow(rows, key, slot.orderNumber, slot.startTime, slot.endTime);
            });
        });
        lessons.forEach(lesson => {
            const key = lessonSlotKey(lesson);
            if (key == null) return;
            putSlotRow(rows, key, lesson.timeSlotOrder, lesson.startTime, lesson.endTime);
        });
        return Array.from(rows.values()).sort(compareSlotRows);
    }

    function putSlotRow(rows, key, orderNumber, startTime, endTime) {
        const existing = rows.get(key);
        const next = {
            key,
            orderNumber,
            startTime,
            endTime,
            orderLabel: orderNumber ? `${orderNumber} пара` : 'Пара',
            timeLabel: startTime || endTime ? `${trimTime(startTime)}-${trimTime(endTime)}` : 'Время не указано'
        };
        if (!existing || existing.timeLabel === 'Время не указано') {
            rows.set(key, next);
        }
    }

    function buildDateGridByDay(dates, lessons) {
        const datesByDay = {};
        let unknownParityDates = 0;
        dates.forEach((date, index) => {
            let parity = parityForDate(date, lessons);
            if (!parity) {
                unknownParityDates += 1;
                parity = index < 7 ? 'ODD' : 'EVEN';
            }
            const day = isoDay(date);
            if (!datesByDay[day]) datesByDay[day] = {};
            datesByDay[day][parity] = date;
        });
        return { datesByDay, unknownParityDates };
    }

    function parityForDate(date, lessons) {
        const lessonParity = lessons.find(lesson => lesson.date === date && ['ODD', 'EVEN'].includes(lesson.parity))?.parity;
        if (lessonParity) return lessonParity;
        const weekNumber = weekNumberForDate(date);
        if (!weekNumber) return null;
        return weekNumber % 2 === 0 ? 'EVEN' : 'ODD';
    }

    function weekNumberForDate(date) {
        const semester = semesters.find(item => item.startDate <= date && item.endDate >= date);
        if (!semester) return null;
        const days = Math.floor((parseIsoDate(date) - parseIsoDate(semester.startDate)) / MS_PER_DAY);
        return Math.floor(days / 7) + 1;
    }

    function uniqueWeekNumbers(dates) {
        return Array.from(new Set(dates.map(weekNumberForDate).filter(Boolean)))
            .sort((a, b) => a - b);
    }

    function roomWeekCaption(datesByDay) {
        return CELL_PARITY_LAYOUT
            .map(({ parity, label }) => {
                const dates = DAY_ORDER.map(day => datesByDay[day]?.[parity]).filter(Boolean);
                const weeks = uniqueWeekNumbers(dates);
                const weekText = weeks.length ? `учебная неделя ${weeks.join(', ')}` : 'неделя вне семестра';
                const positionText = parity === 'ODD' ? 'сверху' : 'снизу';
                return `${label} ${positionText}, ${weekText}`;
            })
            .join('; ');
    }

    function hasSlotForDate(date, key, slotsByDate) {
        return (slotsByDate.get(date) || []).some(slot => slotOrderKey(slot) === key);
    }

    function renderLessonCard(lesson) {
        const subgroupText = (lesson.subgroupNames || []).join(', ');
        return `
            <div class="lesson-card">
                <div class="lesson-subject">${escapeHtml(lesson.subjectName || 'Без дисциплины')}</div>
                <div class="lesson-group">${escapeHtml((lesson.groupNames || []).join(', ') || 'Группа не указана')}</div>
                ${subgroupText ? `<div class="lesson-subgroup">${escapeHtml(subgroupText)}</div>` : ''}
                <div class="lesson-teacher">${escapeHtml(lesson.teacherName || 'Преподаватель не указан')}</div>
                <div class="lesson-meta">${escapeHtml([lesson.lessonTypeName, lesson.lessonFormat].filter(Boolean).join(' · '))}</div>
            </div>
        `;
    }

    function filteredClassrooms() {
        const selectedBuildings = selectedValues('building-checkboxes');
        const minCapacity = parseInt(minCapacityInput?.value || '0', 10) || 0;
        const selectedEquipment = selectedValues('equipment-checkboxes');

        return classrooms.filter(room => {
            const buildingMatches = !selectedBuildings.length || selectedBuildings.includes(buildingKey(room));
            const capacityMatches = !minCapacity || Number(room.capacity || 0) >= minCapacity;
            const equipmentMatches = !selectedEquipment.length || selectedEquipment.every(id =>
                (room.equipments || []).some(item => String(item.id) === String(id))
            );
            return buildingMatches && capacityMatches && equipmentMatches;
        });
    }

    function selectedValues(containerId) {
        const container = document.getElementById(containerId);
        if (!container) return [];
        return Array.from(container.querySelectorAll('input[type="checkbox"]:checked'))
            .map(input => input.value);
    }

    function updateFilterText(containerId, textId, emptyText) {
        const container = document.getElementById(containerId);
        const text = document.getElementById(textId);
        if (!container || !text) return;
        const checked = Array.from(container.querySelectorAll('input[type="checkbox"]:checked'));
        if (!checked.length) {
            text.textContent = emptyText;
        } else if (checked.length === 1) {
            text.textContent = checked[0].closest('.checkbox-item')?.textContent.trim() || emptyText;
        } else {
            text.textContent = `Выбрано: ${checked.length}`;
        }
    }

    function setTableLoading(message) {
        renderHeader();
        tbody.innerHTML = `<tr><td colspan="${Math.max(timeSlots.length, 1) + 1}" class="loading-row">${escapeHtml(message)}</td></tr>`;
        if (summary) summary.textContent = '';
    }

    function setTableError(message) {
        renderHeader();
        tbody.innerHTML = `<tr><td colspan="${Math.max(timeSlots.length, 1) + 1}" class="loading-row">${escapeHtml(message)}</td></tr>`;
        showAlert('workload-alert', message, 'error');
    }

    function setRoomLoading(message) {
        if (roomSummary) roomSummary.textContent = '';
        if (roomTables) roomTables.innerHTML = `<div class="loading-row room-workload-state">${escapeHtml(message)}</div>`;
    }

    function setRoomError(message) {
        if (roomSummary) roomSummary.textContent = '';
        if (roomTables) roomTables.innerHTML = `<div class="loading-row room-workload-state">${escapeHtml(message)}</div>`;
        showAlert('workload-alert', message, 'error');
    }

    function renderSummary(entityCount, occupiedCells, totalCells) {
        if (!summary) return;
        const percent = totalCells ? Math.round((occupiedCells / totalCells) * 100) : 0;
        const date = dateInput?.value ? formatDate(dateInput.value) : '-';
        const day = dateInput?.value ? dayShort(selectedIsoDay()) : '-';
        const failed = failedGroupLoads ? `, ошибок загрузки групп: ${failedGroupLoads}` : '';
        const selected = selectedEntityId ? ', выбран один объект' : '';
        summary.textContent = `Дата: ${date}, ${day}. ${targetConfig().summaryLabel}: ${entityCount}${selected}. Занятых слотов: ${occupiedCells} из ${totalCells} (${percent}%)${failed}.`;
    }

    function countOccupiedCells(entities) {
        return entities.reduce((count, entity) => count + timeSlots.filter(slot =>
            (lessonsByEntitySlot.get(entitySlotKey(entity.id, slot.id)) || []).length > 0
        ).length, 0);
    }

    function countAvailableRoomCells(dates, slotsByDate, lessonsByDateSlot) {
        return dates.reduce((count, date) => {
            const keys = new Set((slotsByDate.get(date) || []).map(slotOrderKey).filter(key => key != null));
            lessonsByDateSlot.forEach((items, key) => {
                if (items.length && key.startsWith(`${date}:`)) {
                    keys.add(key.slice(date.length + 1));
                }
            });
            return count + keys.size;
        }, 0);
    }

    function countOccupiedRoomCells(lessonsByDateSlot) {
        return Array.from(lessonsByDateSlot.values()).filter(items => items.length > 0).length;
    }

    function entitySlotKey(entityId, timeSlotId) {
        return `${entityId}:${timeSlotId}`;
    }

    function dateSlotKey(date, slotKey) {
        return `${date}:${slotKey}`;
    }

    function slotOrderKey(slot) {
        const value = slot?.orderNumber ?? slot?.timeSlotOrder ?? slot?.id;
        return value == null ? null : String(value);
    }

    function lessonSlotKey(lesson) {
        const value = lesson?.timeSlotOrder ?? lesson?.timeSlotId;
        return value == null ? null : String(value);
    }

    function buildingKey(room) {
        return room.building ? String(room.building) : NO_BUILDING;
    }

    function teacherDepartmentId(teacherId) {
        const teacher = teachers.find(item => sameId(item.id, teacherId));
        if (!teacher) return null;
        if (teacher.departmentId != null) return teacher.departmentId;
        if (!teacher.departmentName) return null;
        return departments.find(department => departmentName(department) === teacher.departmentName)?.id ?? null;
    }

    function userName(user) {
        return user?.fullName || user?.username || 'Преподаватель без имени';
    }

    function departmentName(department) {
        return department?.departmentName || department?.name || 'Кафедра без названия';
    }

    function mergeNames(first = [], second = []) {
        return Array.from(new Set([...first, ...second].filter(Boolean))).sort((a, b) => naturalCompare(a, b));
    }

    function mergeIds(first = [], second = []) {
        return Array.from(new Set([...first, ...second].filter(id => id != null)));
    }

    function timeSlotLabel(slot) {
        return `${trimTime(slot.startTime)}-${trimTime(slot.endTime)}`;
    }

    function selectedIsoDay() {
        if (!dateInput?.value) return null;
        return isoDay(dateInput.value);
    }

    function scopeShort(slot) {
        if (slot.scopeApplyMode === 'WEEKDAY') {
            return dayShort(slot.scopeDayOfWeek);
        }
        return 'Ручн.';
    }

    function dayShort(dayOfWeek) {
        return DAY_SHORT_LABELS[Number(dayOfWeek)] || '-';
    }

    function isoDay(value) {
        const day = parseIsoDate(value).getDay();
        return day === 0 ? 7 : day;
    }

    function twoWeekRange(value) {
        const selected = parseIsoDate(value);
        const start = addDays(selected, 1 - isoDay(value));
        const end = addDays(start, 13);
        return {
            startDate: formatIsoDate(start),
            endDate: formatIsoDate(end)
        };
    }

    function datesBetween(startDate, endDate) {
        const dates = [];
        for (let date = parseIsoDate(startDate); date <= parseIsoDate(endDate); date = addDays(date, 1)) {
            dates.push(formatIsoDate(date));
        }
        return dates;
    }

    function parseIsoDate(value) {
        const [year, month, day] = String(value).split('-').map(Number);
        return new Date(year, month - 1, day);
    }

    function addDays(date, days) {
        const next = new Date(date);
        next.setDate(next.getDate() + days);
        return next;
    }

    function trimTime(value) {
        return value ? String(value).slice(0, 5) : '';
    }

    function formatIsoDate(date) {
        return [
            date.getFullYear(),
            String(date.getMonth() + 1).padStart(2, '0'),
            String(date.getDate()).padStart(2, '0')
        ].join('-');
    }

    function formatDate(value) {
        const [year, month, day] = String(value).split('-');
        return `${day}.${month}.${year}`;
    }

    function formatDateShort(value) {
        const [, month, day] = String(value).split('-');
        return `${day}.${month}`;
    }

    function compareSlots(a, b) {
        return Number(a.orderNumber || 0) - Number(b.orderNumber || 0)
            || naturalCompare(a.startTime, b.startTime);
    }

    function compareSlotRows(a, b) {
        const first = Number(a.orderNumber || a.key);
        const second = Number(b.orderNumber || b.key);
        if (!Number.isNaN(first) && !Number.isNaN(second) && first !== second) {
            return first - second;
        }
        return naturalCompare(a.startTime, b.startTime)
            || naturalCompare(a.key, b.key);
    }

    function sameId(first, second) {
        return String(first) === String(second);
    }

    function naturalCompare(a, b) {
        return String(a || '').localeCompare(String(b || ''), 'ru', { numeric: true, sensitivity: 'base' });
    }
}
