import { assert } from "../../../../core/assert.js";

/**
 * Shifts `buffer` contents in place by `(shift_x, shift_y, shift_z)` grid cells.
 *
 * Direction convention: the cell at position `(i, j, k)` after the shift contains the
 * value previously at `(i + shift_x, j + shift_y, k + shift_z)`. Equivalent semantics:
 * the grid moved by `(shift_x, shift_y, shift_z)` cells in world space and the fluid
 * data (which is anchored to world space, not to the moving grid) follows.
 *
 * Cells whose source would have been outside the grid are NOT touched here — they
 * still contain whatever stale data the underlying axis copy left behind. Patch them
 * separately via {@link v3_grid_patch_edges_uniform}, which knows the same
 * `(shift_x, shift_y, shift_z)` and writes a Neumann zero-gradient extrapolation into
 * the exposed cells.
 *
 * If `|shift|` on any axis is `>= res` on that axis, no valid source data remains
 * after the shift. The buffer is zero-filled and the patcher will leave it that way.
 *
 * Each axis is shifted independently via `TypedArray.copyWithin`, which handles
 * overlapping source/destination correctly (`memmove`-like semantics).
 *
 * @param {Float32Array|Uint8Array} buffer
 * @param {number} res_x
 * @param {number} res_y
 * @param {number} res_z
 * @param {number} shift_x  integer
 * @param {number} shift_y  integer
 * @param {number} shift_z  integer
 */
export function v3_grid_shift_in_place(buffer, res_x, res_y, res_z, shift_x, shift_y, shift_z) {
    assert.isInteger(shift_x, "shift_x");
    assert.isInteger(shift_y, "shift_y");
    assert.isInteger(shift_z, "shift_z");

    if (shift_x === 0 && shift_y === 0 && shift_z === 0) {
        return;
    }

    const cell_count = res_x * res_y * res_z;
    assert.greaterThanOrEqual(buffer.length, cell_count, "buffer covers grid");

    // Any axis shift exceeding the grid leaves no valid source data on that axis,
    // and therefore on the whole buffer (every cell's source maps outside).
    if (Math.abs(shift_x) >= res_x || Math.abs(shift_y) >= res_y || Math.abs(shift_z) >= res_z) {
        for (let i = 0; i < cell_count; i++) {
            buffer[i] = 0;
        }
        return;
    }

    const slice = res_x * res_y;

    if (shift_x !== 0) {
        shift_axis_x(buffer, res_x, res_y, res_z, shift_x, slice);
    }
    if (shift_y !== 0) {
        shift_axis_y(buffer, res_x, res_y, res_z, shift_y, slice);
    }
    if (shift_z !== 0) {
        shift_axis_z(buffer, res_x, res_y, res_z, shift_z, slice);
    }
}

function shift_axis_x(buf, rx, ry, rz, shift, slice) {
    const n = Math.abs(shift);
    for (let z = 0; z < rz; z++) {
        const z_off = z * slice;
        for (let y = 0; y < ry; y++) {
            const row = z_off + y * rx;
            if (shift > 0) {
                // dst x in [0, rx - n) ← src x in [n, rx).
                buf.copyWithin(row, row + n, row + rx);
            } else {
                // dst x in [n, rx) ← src x in [0, rx - n).
                buf.copyWithin(row + n, row, row + rx - n);
            }
        }
    }
}

function shift_axis_y(buf, rx, ry, rz, shift, slice) {
    const n = Math.abs(shift);
    const row_size = rx;
    for (let z = 0; z < rz; z++) {
        const z_off = z * slice;
        if (shift > 0) {
            // dst y in [0, ry - n) ← src y in [n, ry).
            buf.copyWithin(z_off, z_off + n * row_size, z_off + ry * row_size);
        } else {
            // dst y in [n, ry) ← src y in [0, ry - n).
            buf.copyWithin(z_off + n * row_size, z_off, z_off + (ry - n) * row_size);
        }
    }
}

function shift_axis_z(buf, rx, ry, rz, shift, slice) {
    const n = Math.abs(shift);
    const total = rx * ry * rz;
    if (shift > 0) {
        // dst z in [0, rz - n) ← src z in [n, rz).
        buf.copyWithin(0, n * slice, total);
    } else {
        // dst z in [n, rz) ← src z in [0, rz - n).
        buf.copyWithin(n * slice, 0, total - n * slice);
    }
}
