Uncaught TypeError: history.forEach is not a function
    at MonitorRenderer.renderAll

class EventDispatcher {
    constructor() {
        this.listeners = {};
    }

    on(event, callback) {
        if (!this.listeners[event]) {
            this.listeners[event] = [];
        }
        console.log('callback' + callback);
        this.listeners[event].push(callback);
    }

    emit(event, data) {
        if (this.listeners[event]) {
            this.listeners[event].forEach(callback => callback(data));
        }
    }
}


class WebSocketService extends EventDispatcher {
    constructor(url) {
        super();
        this.url = url;
        this.socket = null;
        this.reconnectInterval = 5000;
    }

    connect() {
        this.socket = new WebSocket(this.url);

        this.socket.onopen = () => {
            this.emit('connected');
            this.send({
                p: "u"
            });
        };

        this.socket.onmessage = (event) => {
            try {
                const data = JSON.parse(event.data);
                this.emit('message', data);
            } catch (e) {
                this.emit('error', e);
            }
        };

        this.socket.onclose = () => {
            this.emit('disconnected');
            setTimeout(() => this.connect(), this.reconnectInterval);
        };

        this.socket.onerror = (error) => {
            this.emit('error', error);
        };
    }

    send(data) {
        if (this.socket && this.socket.readyState === WebSocket.OPEN) {
            this.socket.send(JSON.stringify(data));
        }
    }
}

class MonitorStore {
    constructor() {
        this.maxItems = this.getMaxItems();
        this.history = {
            'm-1': this.loadHistoryForTab('m-1'),
            'm-2': this.loadHistoryForTab('m-2'),
            'm-3': this.loadHistoryForTab('m-3'),
            'm-4': this.loadHistoryForTab('m-4'),
            'm-5': this.loadHistoryForTab('m-5')
        };
        this.subscribers = [];
    }

    getMaxItems() {
        const saved = localStorage.getItem('UF_MON');
        return saved ? parseInt(saved) : 25;
    }

    loadHistoryForTab(tabId) {
        const saved = localStorage.getItem('monitor-' + tabId);
        return saved ? JSON.parse(saved) : [];
    }

    saveHistoryForTab(tabId) {
        localStorage.setItem('monitor-' + tabId, JSON.stringify(this.history[tabId]));
    }

    addItem(item) {
        const tabId = 'm-' + item.d;
        if (!this.history[tabId]) {
            this.history[tabId] = [];
        }

        this.history[tabId].unshift(item);

        while (this.history[tabId].length > this.maxItems) {
            this.history[tabId].pop();
        }

        this.saveHistoryForTab(tabId);
        this.notify(item, tabId);
    }

    clearHistoryForTab(tabId) {
        this.history[tabId] = [];
        localStorage.removeItem('monitor-' + tabId);
        this.notify(null, tabId);
    }

    subscribe(callback) {
        this.subscribers.push(callback);
    }

    notify(item, tabId) {
        this.subscribers.forEach(cb => cb(item, tabId));
    }
}

class MonitorStore_old {
    constructor() {
        this.history = this.loadHistory();
        this.maxItems = this.getMaxItems();
        this.subscribers = [];
    }

    getMaxItems() {
        const saved = localStorage.getItem('UF_MON');
        return saved ? parseInt(saved) : 25;
    }

    loadHistory() {
        const saved = localStorage.getItem('monitor');
        return saved ? JSON.parse(saved) : [];
    }

    saveHistory() {
        localStorage.setItem('monitor', JSON.stringify(this.history));
    }

    addItem(item) {
        this.history.unshift(item);

        // Remove excess items
        while (this.history.length > this.maxItems) {
            this.history.pop();
        }

        this.saveHistory();
        this.notify(item);
    }

    subscribe(callback) {
        this.subscribers.push(callback);
    }

    notify(item) {
        this.subscribers.forEach(callback => callback(item));
    }
}

class MonitorRenderer {

    constructor(options) {
        this.container = options.container;
        this.store = options.store;
        this.templates = {
            default: this.createDefaultTemplate()
        };

        // Восстанавливаем сохранённое значение view_mon или ставим значение по умолчанию
        this.view_mon = localStorage.getItem('view_mon') || '111111111';

        this.monbuttons();
        this.updateButtonsVisual(); // Приводим кнопки в соответствие с состоянием

        AL(bID('cleard'), 'click', this.clearmonitor);
    }
    renderAll(history) {
        this.clear();
        history.forEach(item => this.render(item));
    }
    setContainer(newContainer) {
        this.container = newContainer;
        this.clear();
        this.renderAll(this.store.history);
    }

    clear() {
        this.container.innerHTML = '';
    }
    clearmonitor() {
        const tabId = this.container.id;

        this.store.clearHistoryForTab(tabId);

        this.renderAll(this.store.history[tabId] || []);
    }

    monbuttons() {
        const buttons = bTN2('.a-f-btn');

        buttons.forEach(button => {
            button.addEventListener('click', () => {
                const i = button.getAttribute('data-i');
                let z = button.getAttribute('data-z');

                if (z == 0) {
                    z = '1';
                    button.classList.add('color-in');
                } else {
                    z = '0';
                    button.classList.remove('color-in');
                }
                button.setAttribute('data-z', z);

                let arr = this.view_mon.split('');
                if (i >= 0 && i < arr.length) {
                    arr[i] = z;
                }

                this.view_mon = arr.join('');
                localStorage.setItem('view_mon', this.view_mon);
                this.renderAll(this.store.history);
            });
        });
    }

    updateButtonsVisual() {
        const buttons = bTN2('.a-f-btn');
        buttons.forEach(button => {
            const i = button.getAttribute('data-i');
            const z = this.view_mon[i];
            button.setAttribute('data-z', z);
            if (z === '1') {
                button.classList.add('color-in');
            } else {
                button.classList.remove('color-in');
            }
        });
    }

    // Возвращает функцию для рендеринга стандартного элемента
    createDefaultTemplate() {

        return (data) => {
            console.log(data.d + '  ' + this.container.id);

            if (data.d === 5 && this.container.id == 'm-5') {

                let tr = '<pre>' + JSON.stringify(data.m, null, 2) + '</pre>';

                return tr;
            }
            if (data.d === 4 && this.container.id == 'm-4') {

                let tr = '<pre>' + JSON.stringify(data.m, null, 2) + '</pre>';

                return tr;
            }
            if (data.d === 3 && this.container.id == 'm-3') {

                let tr = '<pre>' + JSON.stringify(data.m, null, 2) + '</pre>';

                return tr;
            }
            if (data.d === 2 && this.container.id == 'm-2') {

                let tr = '<pre>' + JSON.stringify(data.m, null, 2) + '</pre>';

                return tr;
            }
            if (data.d == 1 && this.container.id == 'm-1') {

                let tr = '';

                let d = data.m;

                let ft = d.ft;

                let status = '';
                let sicon = '';
                let render = 0;
                let wss41 = 'ws-s4-11';
                if (ft[6] === '1') {
                    if (this.view_mon[6] === '1') {
                        render = 1;
                    }
                    sicon = 'fa-road-lock color-negative';
                    status = 'Блок';
                    wss41 = 'ws-s4-12';
                }
                if (ft[2] === '1') {
                    if (this.view_mon[2] === '1') {
                        render = 1;
                    }
                    sicon = 'fa-check color-positive';
                    status = 'Разрешен';

                }
                if (ft[3] === '1') {
                    if (this.view_mon[3] === '1') {
                        render = 1;
                    }
                    sicon = 'fa-car color-positive';
                    status = 'Постоянный';

                }
                if (ft[4] === '1') {
                    if (this.view_mon[4] === '1') {
                        render = 1;
                    }
                    sicon = 'fa-clock-rotate-left color-positive';
                    status = 'Временный';
                }
                if (ft[5] === '1') {
                    if (this.view_mon[5] === '1') {
                        render = 1;
                    }
                    if (data.p === 'pin') {
                        sicon = 'fa-credit-card color-positive';
                    } else {
                        sicon = 'fa-taxi color-positive';
                    }
                    status = 'Гость';
                }
                if (ft[1] === '1') {
                    if (this.view_mon[1] === '1') {
                        render = 1;
                    }
                    sicon = 'fa-lock color-negative';
                    status = 'Запрещен';
                    wss41 = 'ws-s4-12';
                }
                if (ft[0] === '1') {
                    if (this.view_mon[0] === '1') {
                        render = 1;
                    }
                    if (data.p === 'pin') {
                        sicon = 'fa-credit-card color-negative';
                    } else if (data.p === 'code') {
                        sicon = 'fa-barcode color-negative';
                    } else {
                        sicon = 'fa-ban color-negative';
                    }

                    status = 'Нет в базе';
                    wss41 = 'ws-s4-12';
                }
                if (render == 1) {
                    render = 0;
                    if (ft[7] === '1') {
                        if (this.view_mon[7] === '1') {
                            render = 1;
                        }
                    }
                    if (ft[8] === '1') {
                        if (this.view_mon[8] === '1') {
                            render = 1;
                        }
                    }
                    if (render == 1) {

                        let n = {
                            1: 'fa-arrow-down color-positive',
                            2: 'fa-arrow-up color-negative'
                        };

                        tr = ' \
                        <table class="ws-s0"><tr> \
                        <td class="ws-s1"> \
                            <span class="fa-solid ' + sicon + ' pull-left"></span><div class="text-block"> \
                                <div> \
                                    <b>' + status + '</b> \
                                </div> \
                                <div class="time"> \
                                    ' + d.tm + ' \
                                </div> \
                            </div> \
                        </td> \
                        ';

                        tr += '<td class="ws-s2"> \
                            <div class="text-block"> \
                                <b>' + d.terr + '</b> \
                            </div> \
                            <div class="time2"><span class="fa-solid ' + n[d.in_out] + ' pull-left"></span>' + d.contr + ' </div> \
                        </td>';

                        let link10 = '';
                        let link11 = '';
                        let link12 = '';
                        let link13 = '';
                        let fio = '';

                        if (data.p === 'auto') {
                            if (d.car.id == 0) {
                                link10 = '<span class="ws-s3-1">' + d.car.name + '</span>';
                            } else {
                                link10 = '<span class="ws-s3-1"><a href="/car/get/' + d.car.id + '" target="_new" >' + d.car.name + '</a></span>';
                            }

                            if (d.car.marka) {
                                link12 = d.car.marka;
                            }
                            if (d.car.comment) {
                                link13 = '<span class="fa-solid fa-comment"></span> ' + d.car.comment;
                            }
                            if (d.fio.id) {
                                if (d.fio.id != 0) {
                                    fio = '<b><a href="/card/get/' + d.fio.id + '" data-transition="window">' + d.fio.name + '</a></b>';
                                } else {
                                    fio = '<b>' + d.fio.name + '</b>';
                                }
                            }
                        }
                        if (data.p === 'pin') {
                            if (d.car.id == 0) {
                                link10 = '<span class="ws-s3-1">' + d.pin + '</span>';

                            } else {

                                link10 = '<span class="ws-s3-1"><a href="/car/get/' + d.car.id + '" data-transition="window">' + d.car.name + '</a></span>';

                                link13 = '<span class="fa-solid fa-credit-card color-positive"></span> ' + d.pin;

                            }

                            if (d.car.marka) {
                                link12 = d.car.marka;
                            }


                            if (d.fio.id) {
                                if (d.fio.id != 0) {
                                    fio = '<b><a href="/card/get/' + d.fio.id + '" target="_new">' + d.fio.name + '</a></b>';
                                } else {
                                    fio = '<b>' + d.fio.name + '</b>';
                                }
                            }
                        }
                        if (data.p === 'code') {
                            if (d.car.id == 0) {
                                link10 = '<span class="ws-s3-1">' + d.code + '</span>';

                            } else {

                                link10 = '<span class="ws-s3-1"><a href="/car/get/' + d.car.id + '" data-transition="window">' + d.car.name + '</a></span>';

                                link13 = '<span class="fa-solid fa-barcode color-positive"></span> ' + d.code;

                            }

                            if (d.car.marka) {
                                link12 = d.car.marka;
                            }


                            if (d.fio.id) {
                                if (d.fio.id != 0) {
                                    fio = '<b><a href="/card/get/' + d.fio.id + '" target="_new">' + d.fio.name + '</a></b>';
                                } else {
                                    fio = '<b>' + d.fio.name + '</b>';
                                }
                            }
                        }

                        tr += '<td class="ws-s3"> \
                            ' + link10 + ' \
                            <span class="ws-s3-2"> \
                            ' + link12 + ' \
                            </span> \
                    </td>';

                        tr += '<td class="ws-s4"> \
                             <div class="text-block"> \
                                <span class="' + wss41 + ' pull-left">' + d.car.access + '</span> ' + fio + ' <b class="ws-s4-2">' + d.company.podr + '</b> <b>' + d.company.name + '</b> \
                            </div> \
                            <div class="ws-s4-3"> \
                            ' + link13 + ' \
                            </div> \
                        </td>';

                        tr += '</tr></table>';
                    }
                }
                return tr;
            }
            return '';
        };
    }
    action_access(data) {
        let d = data.m;
        let ft = d.ft;
        // блок
        if (ft[6] === '1') {
            if (this.view_mon[6] === '1') {
                bID('sound-3').play();
            }
        }
        if (ft[1] === '1') {
            if (this.view_mon[1] === '1') {
                bID('sound-2').play();
            }

        }
        if (ft[0] === '1') {
            if (this.view_mon[0] === '1') {
                bID('sound-1').play();
            }
        }
    }
    createDOMElement(html) {
        const tr = document.createElement('li');
        tr.classList.add('table-view-cell');
        tr.innerHTML = html;
        return tr;
    }
    render(data, mode = 1) {
        const template = this.templates.default;
        const tr = template(data);
        if (tr !== '') {
            const element = this.createDOMElement(tr);

            if (mode == 2) {
                this.container.prepend(element);
                this.action_access(data);
            } else {
                this.container.append(element);
            }
        }
    }

}

class MonitorApp {
    constructor() {
        this.wsService = new WebSocketService('wss://' + window.location.hostname + '/ws');
        this.store = new MonitorStore();

        // Создаем рендерер (передаем container по умолчанию, например m-1)
        this.renderer = new MonitorRenderer({
            container: document.getElementById('m-1'),
            store: this.store
        });

        this.activeViewId = 'd-1'; // какая вкладка сейчас активна
        this.containerMap = {
            'd-1': 'm-1',
            'd-2': 'm-2',
            'd-3': 'm-3',
            'd-4': 'm-4',
            'd-5': 'm-5'
        };

        this.initialize();
    }


    initialize() {

        // Подписка на данные сокета - всегда обновляем store
        this.wsService.on('message', (data) => this.handleMessage(data));

        // Подписка на изменение store
        // ПОМЕЩАЕМ сюда логику:
        // Добавлять элементы в store, но рендерить новые элементы только если текущая вкладка == 'disp-m' (или та, которая нужна)
        /*
        this.store.subscribe((item) => {
            // если контейнер видим
            if (this.activeViewId && this.containerMap[this.activeViewId]) {
                const currentContainerId = this.containerMap[this.activeViewId];

                if (this.renderer.container.id === currentContainerId) {
                    // рисуем новый элемент в текущий контейнер
                    this.renderer.render(item, 2);
                }
            }
        });
        */
        this.store.subscribe((item, containerId) => {
            const currentContainerId = this.containerMap[this.activeViewId];

            // Проверяем, что сообщение относится к активной вкладке
            if (currentContainerId === containerId) {
                this.renderer.render(item, 2);
            }
        });


        // при старте отрисовываем все в текущий контейнер
        this.renderActiveView();


        // Запуск websocket
        this.wsService.connect();
    }

    handleMessage(data) {
        this.store.addItem(data);
    }

    // Меняем активную вкладку
    switchView(viewId) {
        // обновляем активную
        this.activeViewId = viewId;

        // меняем контейнер в рендерере
        const containerId = this.containerMap[viewId];
        if (!containerId) return;

        const container = document.getElementById(containerId);
        if (!container) return;

        this.renderer.setContainer(container);

        // отрисовываем всю историю полностью
        this.renderActiveView();
    }

    renderActiveView() {
        this.renderer.clear();
        const containerId = this.containerMap[this.activeViewId];
        const historyForTab = this.store.history[containerId] || [];
        this.renderer.renderAll(historyForTab);
    }
    /*
    renderActiveView() {
        this.renderer.clear();
        this.renderer.renderAll(this.store.history);
    }
        */
}

function disp_fullscreen() {
    const elem = document.documentElement;
    if (elem.requestFullscreen) {
        elem.requestFullscreen();
    } else if (elem.mozRequestFullScreen) {
        elem.mozRequestFullScreen();
    } else if (elem.webkitRequestFullscreen) {
        elem.webkitRequestFullscreen();
    } else if (elem.msRequestFullscreen) {
        elem.msRequestFullscreen();
    }
}

let monitorApp = null;

function onTabChanged(activeViewId) {
    console.log('activeViewId ' + activeViewId);
    if (monitorApp) {
        monitorApp.switchView(activeViewId);
    }
}

function disp_list() {

    AL(bID('fullscreen'), 'click', disp_fullscreen);
    input_select_1('find-number-input', '/car/get/', input_select_1_id, 1, 'car/find');

    monitorApp = new MonitorApp();

    /*
    

        this.containerMap.forEach(callback => callback(item))
        registerTabChangedListener(function(activeViewId) {
            if (activeViewId === 'd-1') {
                // Код для первой страницы/вкладок
                console.log('Активна вкладка disp-m');
            }
        });
        */
}