import {now} from '../util/time_control';
import {Placement} from '../symbol/placement';
import {isSymbolStyleLayer, type SymbolStyleLayer} from './style_layer/symbol_style_layer';
import type {ITransform} from '../geo/transform_interface';
import type {StyleLayer} from './style_layer';
import type {Tile} from '../tile/tile';
import type {BucketPart} from '../symbol/placement';
import type {Terrain} from '../render/terrain';

class LayerPlacement {
    _sortAcrossTiles: boolean;
    _currentTileIndex: number;
    _currentPartIndex: number;
    _seenCrossTileIDs: {
        [k in string | number]: boolean;
    };
    _bucketParts: BucketPart[];

    constructor(styleLayer: SymbolStyleLayer) {
        this._sortAcrossTiles = styleLayer.layout.get('symbol-z-order') !== 'viewport-y' &&
            !styleLayer.layout.get('symbol-sort-key').isConstant();

        this._currentTileIndex = 0;
        this._currentPartIndex = 0;
        this._seenCrossTileIDs = {};
        this._bucketParts = [];
    }

    continuePlacement(tiles: Tile[], placement: Placement, showCollisionBoxes: boolean, styleLayer: StyleLayer, shouldPausePlacement: () => boolean) {

        const bucketParts = this._bucketParts;

        while (this._currentTileIndex < tiles.length) {
            const tile = tiles[this._currentTileIndex];
            placement.getBucketParts(bucketParts, styleLayer, tile, this._sortAcrossTiles);

            this._currentTileIndex++;
            if (shouldPausePlacement()) {
                return true;
            }
        }

        if (this._sortAcrossTiles) {
            this._sortAcrossTiles = false;
            bucketParts.sort((a, b) => (a.sortKey as any as number) - (b.sortKey as any as number));
        }

        while (this._currentPartIndex < bucketParts.length) {
            const bucketPart = bucketParts[this._currentPartIndex];
            placement.placeLayerBucketPart(bucketPart, this._seenCrossTileIDs, showCollisionBoxes);

            this._currentPartIndex++;
            if (shouldPausePlacement()) {
                return true;
            }
        }
        return false;
    }
}

export class PauseablePlacement {
    placement: Placement;
    _done: boolean;
    _currentPlacementIndex: number;
    _forceFullPlacement: boolean;
    _showCollisionBoxes: boolean;
    _inProgressLayer: LayerPlacement;

    constructor(
        transform: ITransform,
        terrain: Terrain,
        order: string[],
        forceFullPlacement: boolean,
        showCollisionBoxes: boolean,
        fadeDuration: number,
        crossSourceCollisions: boolean,
        prevPlacement?: Placement
    ) {
        this.placement = new Placement(transform, terrain, fadeDuration, crossSourceCollisions, prevPlacement);
        this._currentPlacementIndex = order.length - 1;
        this._forceFullPlacement = forceFullPlacement;
        this._showCollisionBoxes = showCollisionBoxes;
        this._done = false;
    }

    isDone() {
        return this._done;
    }

    continuePlacement(
        order: string[],
        layers: {[_: string]: StyleLayer},
        layerTiles: {[_: string]: Tile[]}
    ) {
        const startTime = now();

        const shouldPausePlacement = () => {
            return this._forceFullPlacement ? false : (now() - startTime) > 2;
        };

        while (this._currentPlacementIndex >= 0) {
            const layerId = order[this._currentPlacementIndex];
            const layer = layers[layerId];
            const placementZoom = this.placement.collisionIndex.transform.zoom;
            if (isSymbolStyleLayer(layer) &&
                layer.layout &&
                (!layer.minzoom || layer.minzoom <= placementZoom) &&
                (!layer.maxzoom || layer.maxzoom > placementZoom)) {

                if (!this._inProgressLayer) {
                    this._inProgressLayer = new LayerPlacement(layer);
                }

                const pausePlacement = this._inProgressLayer.continuePlacement(layerTiles[layer.source], this.placement, this._showCollisionBoxes, layer, shouldPausePlacement);

                if (pausePlacement) {
                    // We didn't finish placing all layers within 2ms,
                    // but we can keep rendering with a partial placement
                    // We'll resume here on the next frame
                    return;
                }

                delete this._inProgressLayer;
            }

            this._currentPlacementIndex--;
        }

        this._done = true;
    }

    commit(now: number) {
        this.placement.commit(now);
        return this.placement;
    }
}
