import { Debug } from '../../core/debug.js';

/**
 * @import { GSplatInfo } from './gsplat-info.js'
 */ class GSplatWorldState {
    constructor(device, version, splats){
        /**
     * The version of the world state.
     *
     * @type {number}
     */ this.version = 0;
        /**
     * Whether the sort parameters have been set on the sorter.
     *
     * @type {boolean}
     */ this.sortParametersSet = false;
        /**
     * Whether the world state has been sorted before.
     *
     * @type {boolean}
     */ this.sortedBefore = false;
        /**
     * An array of all splats managed by this world state.
     *
     * @type {GSplatInfo[]}
     */ this.splats = [];
        /**
     * The texture size of work buffer.
     *
     * @type {number}
     */ this.textureSize = 0;
        /**
     * Total number of pixels actually used in the texture (excluding unused regions).
     * This is the count that should be sent to the sort worker and renderer.
     *
     * @type {number}
     */ this.totalUsedPixels = 0;
        this.version = version;
        this.splats = splats;
        this.estimateTextureSize(this.splats, device.maxTextureSize);
        this.assignLines(this.splats, this.textureSize);
    }
    /**
     * Estimates the square texture size that can store all splats, using binary search to find the
     * smallest size that fits.
     *
     * @param {GSplatInfo[]} splats - The splats to allocate space for.
     * @param {number} maxSize - Max texture size (width and height).
     * @returns {boolean} - True if the texture size was found.
     */ estimateTextureSize(splats, maxSize) {
        const fits = (size)=>{
            let rows = 0;
            for (const splat of splats){
                rows += Math.ceil(splat.activeSplats / size);
                if (rows > size) return false;
            }
            return true;
        };
        let low = 1;
        let high = maxSize;
        let bestSize = null;
        while(low <= high){
            const mid = Math.floor((low + high) / 2);
            if (fits(mid)) {
                bestSize = mid;
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        if (bestSize === null) {
            this.textureSize = 0;
            Debug.error('estimateTextureSize: failed to find a valid texture size');
            return false;
        }
        this.textureSize = bestSize;
        return true;
    }
    destroy() {
        this.splats.forEach((splat)=>splat.destroy());
        this.splats.length = 0;
    }
    /**
     * Assigns lines to each splat based on the texture size.
     *
     * @param {GSplatInfo[]} splats - The splats to assign lines to.
     * @param {number} size - The texture size.
     */ assignLines(splats, size) {
        if (splats.length === 0) {
            this.totalUsedPixels = 0;
            return;
        }
        let start = 0;
        for (const splat of splats){
            const activeSplats = splat.activeSplats;
            const numLines = Math.ceil(activeSplats / size);
            splat.setLines(start, numLines, size, activeSplats);
            start += numLines;
        }
        // Calculate total used pixels (lines used × texture width)
        this.totalUsedPixels = start * size;
    }
}

export { GSplatWorldState };
