import { MetricCollection } from "../../development/performance/MetricCollection.js";
import {
    MetricCollectionConsoleMonitor
} from "../../development/performance/monitor/MetricCollectionConsoleMonitor.js";
import Entity from "../../ecs/Entity.js";
import GUIElement from "../../ecs/gui/GUIElement.js";
import GUIElementSystem from "../../ecs/gui/GUIElementSystem.js";
import { EngineHarness } from "../../EngineHarness.js";
import { BehaviorComponent } from "../../intelligence/behavior/ecs/BehaviorComponent.js";
import { BehaviorSystem } from "../../intelligence/behavior/ecs/BehaviorSystem.js";
import { FluidComponent } from "./ecs/FluidComponent.js";
import { FluidEffectorsComponent } from "./ecs/FluidEffectorsComponent.js";
import { FluidSystem } from "./ecs/FluidSystem.js";
import { GlobalFluidEffector } from "./effector/GlobalFluidEffector.js";
import { ImpulseFluidEffector } from "./effector/ImpulseFluidEffector.js";
import { SliceVisualiser } from "./SliceVisualiser.js";

const harness = new EngineHarness();
const metrics = new MetricCollection();
metrics.create({ name: 'sim' });

/**
 * @param {Engine} engine
 */
function main(engine) {
    const ecd = engine.entityManager.dataset;

    const RES_X = 32, RES_Y = 32, RES_Z = 8;

    // Fluid volume entity: holds the field + its world placement. The FluidSystem
    // will step it each fixed update.
    const fluid_component = new FluidComponent();
    fluid_component.field.setResolution(RES_X, RES_Y, RES_Z);
    fluid_component.field.addScalar("dye");
    fluid_component.field.build();
    fluid_component.origin = [0, 0, 0];
    fluid_component.cell_size = 1; // world = grid for this demo

    // Seed a dye blob in the middle so the advection is visually obvious.
    const field = fluid_component.field;
    for (let z = 1; z < RES_Z - 1; z++) {
        for (let dy = -2; dy <= 2; dy++) {
            for (let dx = -2; dx <= 2; dx++) {
                field.setScalarAtIndex(0,
                    (RES_X >> 1) + dx, (RES_Y >> 1) + dy, z,
                    1);
            }
        }
    }

    new Entity().add(fluid_component).build(ecd);

    // Effector entity: separate from the fluid volume. With cell_size=1 above, world
    // coords here line up 1:1 with grid coords.
    const effectors = new FluidEffectorsComponent();

    const gravity = new GlobalFluidEffector();
    gravity.force = [0, -5, 0];
    effectors.addEffector(gravity);

    const swirl = new ImpulseFluidEffector();
    swirl.position = [RES_X >> 1, RES_Y >> 1, RES_Z >> 1];
    swirl.force = [15, 0, 0];
    swirl.radius = 3;
    effectors.addEffector(swirl);

    new Entity().add(effectors).build(ecd);

    // Debug visualisation — a per-frame behavior, separate from the fluid stepping.
    const slice_view = new SliceVisualiser();
    slice_view.size.set(RES_X * 6, RES_Y * 2);
    slice_view.scale.setScalar(2);
    slice_view.transformOrigin.set(0, 0);
    slice_view.setChannels(
        [field.velocity_x, field.velocity_y, field.velocity_z, field.getScalarData("dye")],
        field.getResolution()
    );

    new Entity()
        .add(BehaviorComponent.loop(() => {
            // FluidSystem.fixedUpdate is what advances the sim; we just observe and
            // time the draw cost here.
            const t0 = performance.now();
            slice_view.draw();
            metrics.get('sim').record(performance.now() - t0);
        }))
        .build(ecd);

    MetricCollectionConsoleMonitor.from(metrics).start();

    new Entity()
        .add(GUIElement.fromView(slice_view))
        .build(ecd);
}

harness.initialize({
    configuration(config, engine) {
        config.addSystem(new GUIElementSystem(engine.gameView, engine));
        config.addSystem(new BehaviorSystem(engine));
        config.addSystem(new FluidSystem());
    }
}).then(main);
