const selecteddiscipline = window.FREIGABE;

const csrf_token = window.CSDR_TOKEN;

let ws;
let firstConnect = true;
let wsOpen = false;
const RETRY_DELAY = 5000;

const WSaccesstype = "rankLive";
const WSaccess = window.FREIGABE;
const WSaccessPermission = "R";

const urlAjaxNewWSToken = '/intern/scripts/ajax-create-ws-token.php';

async function fetchNewWSToken(accesstype, access, accessPermission) {
    try {
        const response = await fetch(urlAjaxNewWSToken, {
            method: "POST",
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: new URLSearchParams({ 
                csrf_token,
                accesstype, 
                access, 
                accessPermission 
            })
        });

        if (response.status === 403) {
            console.warn("Please Re-Autenithicate. Reloading page...");
            location.reload(); 
            return null;
        }

        if (!response.ok) return null;

        const data = await response.json();
        return data.success ? data.token : null;
    } catch (error) {
        console.error("Token fetch failed:", error);
        return null;
    }
}

async function startWebSocket() {
    console.log("Attempting WebSocket connection...");

    let token;
    if (firstConnect) {
        token = window.WS_ACCESS_TOKEN;
    } else {
        token = await fetchNewWSToken(WSaccesstype, WSaccess, WSaccessPermission);
    }

    if (!token) {
        console.error("No valid token available. Retrying...");
        scheduleRetry();
        return;
    }

    try {
        ws = new WebSocket(`wss://${window.location.hostname}/ws/?access=token&token=${token}`);
    } catch (err) {
        console.error("Malformed WebSocket URL", err);
        scheduleRetry();
        return;
    }

    ws.onopen = () => {
        console.log("WebSocket connected!");
        if (!firstConnect) {
            displayMsg(1, "Live Syncronisation wieder verfügbar");
        }
        firstConnect = true;
        wsOpen = true;
    };

    ws.onerror = (event) => {
        console.error("WebSocket error observed." + JSON.stringify(event));
    };

    ws.onclose = (event) => {

        if (firstConnect) {
            displayMsg(0, "Live Syncronisation verloren");
        }
        firstConnect = false;
        wsOpen = false;

        scheduleRetry();
    };
}

function scheduleRetry() {
    console.log(`Retrying in ${RETRY_DELAY}ms...`);
    setTimeout(startWebSocket, RETRY_DELAY);
}

// Start the initial connection attempt safely
startWebSocket();

let rangNotenArray = JSON.parse(window.RANG_NOTEN_ARRAY) ?? [];

const rangNotenId = window.RANG_NOTE_ID ?? 0;

const rangSort = window.RANG_SORT ?? '';
const isLive = parseInt(window.LIVE ?? 0);

ws.addEventListener("message", async function(event) {
  let msgOBJ;

  try {
    msgOBJ = JSON.parse(event.data);
  } catch (error) {
    return;
  }

  // Ensure it's an UPDATE type (matches your sendToGroup logic)
  if (msgOBJ?.type === "UPDATE") {
    const data = msgOBJ.payload;
    
    // Check access rights
    if (data.discipline === selecteddiscipline.toLowerCase() || selecteddiscipline === 'A') {
        const noten = data.rankLive;

        const programmId = $(`tr[data-person-id="${data.personId}"]`).closest('tbody').attr('data-programm-id') ?? null;

        if (!isLive && noten[0][1][rangNotenId] !== undefined && rangNotenArray[programmId][data.personId] !== undefined) {
            rangNotenArray[programmId][data.personId] = noten[0][1][rangNotenId];
            applyRanks(rankProgramm(rangNotenArray, programmId, rangSort), true);
        }

        for (const [keyG, noteGroup] of Object.entries(noten)) {

            for (const [run, runGroup] of Object.entries(noteGroup)) {

                for (const [key, value] of Object.entries(runGroup)) {

                    const $elements = $(`.changebleValue[data-field-type-id="${key}"][data-geraet-id="${keyG}"][data-person-id="${data.personId}"][data-run="${run}"]`);
                    $elements.each(function() {
                        const $el = $(this);

                        const isNullable = ($el.attr('data-noten-nullable') ?? 'false') === 'true';
                        const floatValZero = parseFloat(value.replace(",", ".").replace("'", "")) === 0.0;

                        if (isNullable && floatValZero || value === '') {
                            $el.text('-').addClass('emtyPlaceholder');
                        } else {
                            $el.text(value).removeClass('emtyPlaceholder');
                        }
                        
                        $el.removeClass('updated');
                        $el.addClass('updated');

                        updateDependentVisibility($el);
                        
                        setTimeout(() => {
                            $el.removeClass('updated');
                        }, 2000);
                    });
                }
            }
        }
    }
  } else if (msgOBJ?.type === "UPDATE_RANKLIVE_C_SUBABT") {
    if (!(await displayConfirm('Eine neue Gruppe ist aktiv. Fenster aktualisieren?'))) return;
    location.reload();
  }
});

function rankProgramm(dataObj, programmId, order = 'DESC') {
    if (!dataObj[programmId]) {
        console.error(`ID ${programmId} not found in data.`);
        return [];
    }

    const values = dataObj[programmId];

    // 1. Transform and Sort
    const sorted = Object.entries(values)
        .map(([id, val]) => ({ id, val }))
        .sort((a, b) => {
            // 1. Handle null values first (Always push them to the bottom)
            if (a.val === null && b.val === null) return 0;
            if (a.val === null) return 1;  // Move 'a' to a higher index (down)
            if (b.val === null) return -1; // Move 'b' to a higher index (down)

            // 2. Normal sorting logic for valid numbers
            return order.toUpperCase() === 'ASC' 
                ? a.val - b.val 
                : b.val - a.val;
        });

    let lastVal = null;
    let lastRank = 0;

    let sortedMap = [];

    sortedMap[programmId] = sorted.map((item, index) => {
        if (item.val !== lastVal) {
            lastRank = index + 1;
        }
        
        lastVal = item.val;
        
        return {
            rang: lastRank,
            ...item
        };
    });

    return sortedMap;
}

function rankAll(dataObj, order = 'DESC') {
    return Object.fromEntries(
        Object.entries(dataObj).map(([groupKey, values]) => {
            const sorted = Object.entries(values)
                .map(([id, val]) => ({ id, val }))
                .sort((a, b) => {
                    // 1. Handle null values first (Always push them to the bottom)
                    if (a.val === null && b.val === null) return 0;
                    if (a.val === null) return 1;  // Move 'a' to a higher index (down)
                    if (b.val === null) return -1; // Move 'b' to a higher index (down)

                    // 2. Normal sorting logic for valid numbers
                    return order.toUpperCase() === 'ASC' 
                        ? a.val - b.val 
                        : b.val - a.val;
                });

            let lastVal = null;
            let lastRank = 0;

            const ranked = sorted.map((item, index) => {
                if (item.val !== lastVal) {
                    lastRank = index + 1;
                }
                lastVal = item.val;
                
                return {
                    rang: lastRank,
                    ...item
                };
            });

            return [groupKey, ranked];
        })
    );
}

function applyRanks(array, sort = false) {
    for (const [key, entries] of Object.entries(array)) {
        const $tbody = $(`tbody[data-programm-id="${key}"]`);
        
        if ($tbody.length === 0) continue; 

        for (const [skey, entry] of Object.entries(entries)) {
            const $row = $tbody.find(`tr[data-person-id="${entry.id}"]`);

            if ($row.length === 0) continue; 

            $row.attr('data-sort-rank', entry.rang);

            const $cell = $row.find('.rangField');

            const points = parseFloat(rangNotenArray[key][entry.id]);

            const text = !isNaN(points) ? entry.rang : '';
            
            $cell.text(text);
        }

        if (sort) {
            const rows = $tbody.find('tr').get();

            rows.sort((a, b) => {
                const rankA = parseFloat($(a).attr('data-sort-rank')) || 999;
                const rankB = parseFloat($(b).attr('data-sort-rank')) || 999;
                return rankA - rankB;
            });

            // Re-append the sorted rows back into the tbody
            $.each(rows, (index, row) => {
                $tbody.append(row);
            });
        }
    }
}

applyRanks(rankAll(rangNotenArray, rangSort), true);

function updateDependentVisibility($changedEl) {
    const uid = $changedEl.attr('data-uid');
    if (uid === undefined || uid === null) return;

    const $row = $changedEl.closest('tr');

    if ($row.length !== 1) return;

    const isVisible = $changedEl.hasClass('emtyPlaceholder');
    const doesExist = $changedEl.hasClass('nonExistentEl');

    const $linkedEl = $row.find(`.singleValueSpan[data-linked-el-uid="${uid}"]`)

    $linkedEl.toggleClass('emtyPlaceholder', isVisible).toggleClass('nonExistentEl', doesExist);
}

function initConditionalVisibility() {
    const $table = $('table.customDisplayEditorTable');

    const $rows = $table.find('tr');

    $rows.each((ind, el) => {
        const $row = $(el);
        $row.find('.singleValueSpan[data-linked-el-uid]').each(function() {
            const linkedUid = $(this).data('linked-el-uid');
            
            const $linkedEl = $row.find(`.singleValueSpan[data-uid="${linkedUid}"]`);
            
            if ($linkedEl.length === 1) {
                updateDependentVisibility($linkedEl);
            }
        });
    })
    
}

initConditionalVisibility();
initConditionalVisibility();

const $parent = $('.allAbtContainer');
const $children = $parent.children('.shiftedGeraetTable');

$children.sort((a, b) => {
    const idA = parseFloat($(a).data('geraet-index'));
    const idB = parseFloat($(b).data('geraet-index'));
    
    return idA - idB;
});

$parent.append($children);