import { system } from "@minecraft/server";
import { Vector } from "../../lib/Vector";
import { CellVerifier } from "./CellVerifier";
import { GridBuffers } from "./GridBuffers";
import { PriorityPass } from "./PriorityPass";
import { RefreshRate } from "./RefreshRate";
import { DebugBoxSweepObserver, SilentSweepObserver } from "./SweepObserver";
import { VerificationRun } from "./VerificationRun";
import { VerificationSweep } from "./VerificationSweep";
import { InstanceVerifierSettings, StandaloneVerifierSettings } from "./VerifierSettings";

export class StructureVerifier {
    #instance;
    #settings;
    #buffers = new GridBuffers();
    #priorityPass = new PriorityPass();
    #run;
    #loop;
    #isPassPending = false;

    static forInstance(instance) {
        return new StructureVerifier(instance, new InstanceVerifierSettings(instance));
    }

    static standalone(instance, options) {
        return new StructureVerifier(instance, new StandaloneVerifierSettings(options));
    }

    constructor(instance, settings) {
        this.#instance = instance;
        this.#settings = settings;
    }

    isEnabled() {
        return this.#settings.isEnabled();
    }

    getCompletedGrid() {
        return this.#buffers.completed();
    }

    refresh(isPriority = false) {
        this.#stopLoop();
        this.#cancelRun();
        if (isPriority)
            this.#priorityPass.arm();
        if (this.#instance.isEnabled())
            this.#startLoop();
    }

    async verifyStructure(shouldRender = false) {
        if (!this.isEnabled())
            return void 0;
        const bounds = this.#instance.getActiveBounds();
        const volume = Vector.volume(bounds.min, bounds.max);
        if (volume <= 0)
            return this.#buffers.completed();
        this.#cancelRun();
        const run = this.#beginRun(bounds, volume, shouldRender);
        this.#finishRun(run, await run.start());
        return this.#buffers.completed();
    }

    patchCell(location) {
        const completed = this.#buffers.completed();
        if (!completed)
            return;
        const cell = this.#tryVerifyCell(location);
        if (!cell)
            return;
        completed.setCell(location, cell.verificationLevel, cell.flags);
        const filling = this.#buffers.filling()
        filling?.setCell(location, cell.verificationLevel, cell.flags);
    }

    #tryVerifyCell(location) {
        try {
            return this.#newCellVerifier().verify(location);
        } catch {
            return void 0;
        }
    }

    #startLoop() {
        this.#isPassPending = true;
        this.#loop = system.runInterval(() => {
            if (this.#isPassPending)
                this.verifyStructure();
        });
    }

    #stopLoop() {
        if (!this.#loop)
            return;
        system.clearRun(this.#loop);
        this.#loop = void 0;
    }

    #beginRun(bounds, volume, shouldRender) {
        this.#isPassPending = false;
        this.#run = new VerificationRun(
            this.#newSweep(bounds, shouldRender),
            this.#blocksPerTick(volume)
        );
        return this.#run;
    }

    #blocksPerTick(volume) {
        if (this.#priorityPass.isArmed())
            return RefreshRate.priorityBlocksPerTick();
        return this.#settings.blocksPerTick(volume);
    }

    #finishRun(run, didComplete) {
        if (this.#run === run)
            this.#run = void 0;
        if (!didComplete)
            return;
        this.#priorityPass.disarm();
        this.#buffers.commit();
        this.#isPassPending = true;
    }

    #cancelRun() {
        this.#run?.cancel();
    }

    #newSweep(bounds, shouldRender) {
        return new VerificationSweep({
            bounds,
            grid: this.#buffers.beginPass(bounds),
            origin: this.#instance.toGlobalCoords({ x: 0, y: 0, z: 0 }),
            cellVerifier: this.#newCellVerifier(),
            observer: this.#newObserver(shouldRender)
        });
    }

    #newCellVerifier() {
        return new CellVerifier(this.#instance, this.#settings.showsBlockPreview());
    }

    #newObserver(shouldRender) {
        if (!shouldRender)
            return new SilentSweepObserver();
        return new DebugBoxSweepObserver(this.#instance, this.#settings.particleLifetimeSeconds());
    }
}
