AutocivControls.StatsOverlay = class
{
    autociv_statsOverlay = Engine.GetGUIObjectByName("autociv_statsOverlay");

    preStatsDefault = {
        " Player   ": state => this.stateName(state),
        "■ ": state => this.stateStrength(state),
        "# ": state => `${state.playerNumber}`,
    };
    preStatsTeam = {
        "T ": state => state.team != -1 ? `${state.team + 1}` : "",
    };
    statsColumns = {
        " P": state => state.phase,
        " Pop": state => state.popCount,
        " Inf": state => state.classCounts_Infantry,
        " Cav": state => state.classCounts_Cavalry,
        " Sig": state => state.classCounts_Siege,
        " Chp": state => state.classCounts_Champion,
        "   Food": state => Math.round(state.resourceCounts["food"]),
        "   Wood": state => Math.round(state.resourceCounts["wood"]),
        "  Stone": state => Math.round(state.resourceCounts["stone"]),
        "  Metal": state => Math.round(state.resourceCounts["metal"]),
        " Kill": state => state.enemyUnitsKilledTotal ?? 0,
        " Loss": state => state.unitsLost ?? 0,
        "   KDr": state => {
            const kills = state.enemyUnitsKilledTotal ?? 0;
            const losses = state.unitsLost ?? 0;
            return losses ? (kills / losses).toFixed(2) : kills.toFixed(2);
        },
    };

    listTeamRepresentatives = {};
    listUndefeatedPlayerIndices = [];
    preStatsSeenBefore = {};
    stateStrengthsCached = {};
    colWidths = {};
    tickPeriod = 1;
    textFont = "mono-stroke-10";
    configKey_visible = "autociv.session.statsOverlay.visible";
    configKey_brightnessThreshold = "autociv.session.statsOverlay.brightnessThreshold";
    configKey_symbolizeRating = "autociv.session.statsOverlay.symbolizeRating";
    cachedLineHeight = null;

    constructor()
    {
        this.autociv_statsOverlay.hidden = Engine.ConfigDB_GetValue("user", this.configKey_visible) == "false";
        this.autociv_brightnessThreshold = Engine.ConfigDB_GetValue("user", this.configKey_brightnessThreshold);
        this.autociv_symbolizeRating = Engine.ConfigDB_GetValue("user", this.configKey_symbolizeRating) == "true";

        // Precompute column widths (character count for alignment)
        const allCols = { ...this.preStatsDefault, ...this.preStatsTeam, ...this.statsColumns };
        for (let name in allCols)
            this.colWidths[name] = name.length;

        // Set buffer zone to avoid text clipping
        this.autociv_statsOverlay.buffer_zone = 8;

        this.autociv_statsOverlay.onTick = this.onTick.bind(this);
        this.updatePlayerLists();
        registerPlayersFinishedHandler(this.updatePlayerLists.bind(this));
        this.update();
        registerConfigChangeHandler(this.onConfigChanges.bind(this));
    }

    getLineHeight()
    {
        if (!this.cachedLineHeight) {
            // Measure a single character to get true line height
            let size = this.autociv_statsOverlay.getPreferredTextSize("A", this.textFont);
            this.cachedLineHeight = size.height - 3.5; // No idea why but height is overestimated
        }
        return this.cachedLineHeight;
    }

    getCharWidth()
    {
        if (!this.cachedCharWidth) {
            let size = this.autociv_statsOverlay.getPreferredTextSize("A", this.textFont);
            this.cachedCharWidth = size.width || 8;
        }
        return this.cachedCharWidth;
    }

    onConfigChanges(changes)
    {
        let needsUpdate = false;
        if (changes.has(this.configKey_visible)) {
            this.autociv_statsOverlay.hidden = Engine.ConfigDB_GetValue("user", this.configKey_visible) == "false";
            needsUpdate = true;
        }
        if (changes.has(this.configKey_brightnessThreshold)) {
            this.autociv_brightnessThreshold = Engine.ConfigDB_GetValue("user", this.configKey_brightnessThreshold);
            needsUpdate = true;
        }
        if (changes.has(this.configKey_symbolizeRating)) {
            this.autociv_symbolizeRating = Engine.ConfigDB_GetValue("user", this.configKey_symbolizeRating) == "true";
            needsUpdate = true;
        }
        if (needsUpdate)
            this.update();
    }

    stateName(state)
    {
        if (state.state == "defeated")
            return `[icon="icon_defeated_autociv" displace="-2 3"]${state.name}`;
        if (state.state == "won")
            return `[icon="icon_won_autociv" displace="-2 3"]${state.name}`;
        return state.name;
    }

    stateStrength(state)
    {
        if (!this.autociv_symbolizeRating || (controlsPlayer(g_ViewedPlayer) && !g_IsNetworked))
            return "\u25A0";
        if (!this.stateStrengthsCached[state.playerNumber])
        {
            let aiDiff = 1, userRating = 1200;
            try {
                aiDiff = g_InitAttributes.settings.PlayerData[state.playerNumber].AIDiff;
                userRating = splitRatingFromNick(state.name).rating;
            } catch(e) {}
            if (userRating > 1800 || aiDiff === 5)
                this.stateStrengthsCached[state.playerNumber] = "\u25B2";
            else if (userRating > 1600 || aiDiff === 4)
                this.stateStrengthsCached[state.playerNumber] = "\u25C6";
            else if (userRating > 1400 || aiDiff === 3)
                this.stateStrengthsCached[state.playerNumber] = "\u25A0";
            else if (userRating > 1200 || aiDiff === 2)
                this.stateStrengthsCached[state.playerNumber] = "\u25AC";
            else
                this.stateStrengthsCached[state.playerNumber] = "\u25A1";
        }
        return this.stateStrengthsCached[state.playerNumber];
    }

    toggle()
    {
        this.autociv_statsOverlay.hidden = !this.autociv_statsOverlay.hidden;
        Engine.ConfigDB_CreateAndSaveValue("user", this.configKey_visible, this.autociv_statsOverlay.hidden ? "false" : "true");
    }

    onTick()
    {
        if (!this.autociv_statsOverlay.hidden && g_LastTickTime % this.tickPeriod == 0)
            this.update();
    }

    updatePlayerLists()
    {
        this.listUndefeatedPlayerIndices = [];
        this.listTeamRepresentatives = {};
        for (let i = 1; i < g_Players.length; ++i)
        {
            if (g_Players[i].state !== "defeated")
            {
                this.listUndefeatedPlayerIndices.push(i - 1);
                const group = g_Players[i].team;
                if (group != -1 && !this.listTeamRepresentatives[group])
                    this.listTeamRepresentatives[group] = i;
            }
        }
    }

    maxIndex(list)
    {
        let idx = this.listUndefeatedPlayerIndices[0] ?? 0;
        let val = list[idx];
        for (let i of this.listUndefeatedPlayerIndices)
            if (list[i] > val) { val = list[i]; idx = i; }
        return idx;
    }

    minIndex(list)
    {
        let idx = this.listUndefeatedPlayerIndices[0] ?? 0;
        let val = list[idx];
        for (let i of this.listUndefeatedPlayerIndices)
            if (list[i] < val) { val = list[i]; idx = i; }
        return idx;
    }

    playerColor(state)
    {
        return brightenedColor(g_DiplomacyColors.getPlayerColor(state.playerNumber), this.autociv_brightnessThreshold);
    }

    teamColor(state)
    {
        let rep = this.listTeamRepresentatives[state.team] || state.playerNumber;
        return brightenedColor(g_DiplomacyColors.getPlayerColor(rep), this.autociv_brightnessThreshold);
    }

    leftPad(text, width)
    {
        return text.toString().substring(0, width).padStart(width);
    }

    rightPadPreStats(text, width)
    {
        let key = `${text} ${width}`;
        if (!this.preStatsSeenBefore[key])
        {
            const iconMatch = /^(\[.*?\])(.*)/.exec(text);
            if (width > 2 && iconMatch)
                text = iconMatch[1] + splitRatingFromNick(iconMatch[2]).nick.slice(0, width - 4).padEnd(width - 3);
            else if (width > 2)
                text = splitRatingFromNick(text).nick.slice(0, width - 1).padEnd(width);
            else
                text = text.padEnd(width);
            this.preStatsSeenBefore[key] = text;
        }
        return this.preStatsSeenBefore[key];
    }

    computeSize(maxWidthPx, numLines)
    {
        if (numLines <= 0) return "0 0 0 0";
        let lineHeight = this.getLineHeight();
        let buffer = this.autociv_statsOverlay.buffer_zone;
        let requiredWidth = maxWidthPx + buffer;
        let requiredHeight = (lineHeight * numLines) + this.autociv_statsOverlay.buffer_zone;
        return `100%-${Math.ceil(requiredWidth)} 100%-${Math.ceil(requiredHeight + 200)} 100% 100%-200`;
    }

    update()
    {
        Engine.ProfileStart("AutocivControls.statsOverlay:update");

        let playerStates = Engine.GuiInterfaceCall("autociv_GetStatsOverlay")?.players;
        if (!playerStates) { Engine.ProfileStop(); return; }

        // Filter players based on visibility and diplo
        let states = [];
        for (let i = 1; i < playerStates.length; ++i) {
            let s = playerStates[i];
            s.playerNumber = i;

            if (i == g_ViewedPlayer) {
                states.push(s);
                continue;
            }

            if (g_IsObserver) {
                states.push(s);
                continue;
            }

            let isMutualAlly = g_Players[g_ViewedPlayer]?.isMutualAlly?.[i] || false;

            if (playerStates[g_ViewedPlayer].hasSharedLos && isMutualAlly) {
                states.push(s);
            }
        }
        if (states.length === 0) {
            this.autociv_statsOverlay.caption = "";
            this.autociv_statsOverlay.size = "0 0 0 0";
            Engine.ProfileStop();
            return;
        }

        const preDefKeys = Object.keys(this.preStatsDefault);
        const preTeamKeys = Object.keys(this.preStatsTeam);
        const statKeys = Object.keys(this.statsColumns);

        // Build header
        let headerParts = [
            ...preDefKeys.map(k => this.leftPad(k, this.colWidths[k])),
            ...preTeamKeys.map(k => this.leftPad(k, this.colWidths[k])),
            ...statKeys.map(k => this.leftPad(k, this.colWidths[k]))
        ];
        let headerStr = headerParts.join("");
        let header = setStringTags(headerStr, { color: "250 250 250" }) + "\n";

        // Gather numeric lists for min/max highlighting
        let statValues = {};
        for (let key of statKeys) {
            statValues[key] = states.map(s => this.statsColumns[key](s));
        }

        let lines = [];
        let maxLineWidth = 0;

        let headerPlainWidth = this.autociv_statsOverlay.getPreferredTextSize(headerStr, this.textFont).width;
        maxLineWidth = Math.max(maxLineWidth, headerPlainWidth);

        for (let idx = 0; idx < states.length; idx++) {
            let state = states[idx];
            let preDefStr = preDefKeys.map(k => this.rightPadPreStats(this.preStatsDefault[k](state), this.colWidths[k])).join("");
            let preTeamStr = preTeamKeys.map(k => this.rightPadPreStats(this.preStatsTeam[k](state), this.colWidths[k])).join("");
            let statStr = statKeys.map(key => {
                let val = statValues[key][idx];
                let text = this.leftPad(val, this.colWidths[key]);
                if (idx === this.maxIndex(statValues[key]))
                    return setStringTags(text, { color: "230 230 0" });
                if (idx === this.minIndex(statValues[key]))
                    return setStringTags(text, { color: "255 100 100" });
                return text;
            }).join("");

            let fullLineStr = preDefStr + preTeamStr + statStr;
            let fullLine = fullLineStr;
            if (state.state === "defeated")
                fullLine = setStringTags(fullLineStr, { color: "255 255 255 128" });
            else
                fullLine = setStringTags(preDefStr, { color: this.playerColor(state) }) +
                           setStringTags(preTeamStr, { color: this.teamColor(state) }) +
                           statStr;

            lines.push(fullLine);

            let lineWidth = this.autociv_statsOverlay.getPreferredTextSize(fullLineStr, this.textFont).width;
            maxLineWidth = Math.max(maxLineWidth, lineWidth);
        }

        let captionText = header + lines.join("\n");
        let numLines = states.length + 1; // +1 for header

        this.autociv_statsOverlay.caption = "";
        this.autociv_statsOverlay.size = this.computeSize(maxLineWidth, numLines);
        this.autociv_statsOverlay.caption = setStringTags(captionText, { color: "250 250 250", font: this.textFont });

        Engine.ProfileStop();
    }
};
