import { assert } from "../assert.js";
import Signal from "../events/signal/Signal.js";
import { clamp } from "../math/clamp.js";
import { computeHashFloat } from "../primitives/numbers/computeHashFloat.js";

/**
 * Wrapper around a scalar value, `Vector1` is used for consistence with {@link Vector2} and {@link Vector3}
 *
 * @author Alex Goldring
 * @copyright Company Named Limited (c) 2026
 */
export class Vector1 extends Number {
    /**
     *
     * @param {number} [x=0]
     * @constructor
     * @class
     * @property {number} x
     */
    constructor(x = 0) {
        super();

        assert.isNumber(x, 'x');
        assert.notNaN(x, 'x');

        this.x = x;

        /**
         *
         * @type {Signal<number,number>}
         */
        this.onChanged = new Signal();
    }

    /**
     * Inherited from Number class
     * @returns {number}
     */
    valueOf() {
        return this.x;
    }

    /**
     *
     * @returns {string}
     */
    toString() {
        return String(this.x);
    }

    /**
     * Currently held value
     * @returns {number}
     */
    getValue() {
        return this.x;
    }

    /**
     * Useful for sorting
     * @param {Vector1} other
     * @returns {number}
     */
    compareTo(other) {
        return this.x - other.x;
    }

    /**
     *
     * @param {number} x
     * @returns {Vector1}
     */
    set(x) {
        assert.isNumber(x, 'x');
        assert.notNaN(x, 'x');

        const oldValue = this.x;
        if (oldValue !== x) {
            this.x = x;

            if (this.onChanged.hasHandlers()) {
                this.onChanged.send2(x, oldValue);
            }
        }

        return this;
    }

    /**
     * Set value without triggering {@link #onChanged} signal
     * @param {number} x
     */
    setSilent(x) {
        assert.isNumber(x, 'x');
        assert.notNaN(x, 'x');

        this.x = x;
    }

    /**
     *
     * @return {boolean}
     */
    isZero() {
        return this.x === 0;
    }

    /**
     * Increase value by 1.
     * `value++`
     */
    increment() {
        this._add(1);
    }

    /**
     * Decrease value by 1.
     * `value--`
     */
    decrement() {
        this._add(-1);
    }

    /**
     *
     * @param {Number} v
     * @return {Vector1}
     */
    _add(v) {
        return this.set(this.x + v);
    }

    /**
     *
     * @param {Vector1|Vector2|Vector3|Vector4} other
     */
    add(other) {
        assert.isObject(other, "other");
        return this._add(other.x);
    }

    /**
     *
     * @param {Number} v
     * @return {Vector1}
     */
    _sub(v) {
        return this._add(-v);
    }

    /**
     *
     * @param {Vector1} other
     * @returns {Vector1}
     */
    sub(other) {
        return this._sub(other.x);
    }

    /**
     *
     * @param {Vector1} other
     * @returns {Vector1}
     */
    multiply(other) {
        return this.set(this.x * other.x);
    }

    /**
     *
     * @param {number} v
     */
    multiplyScalar(v) {
        this.set(this.x * v);
    }

    /**
     *
     * @param {number} low
     * @param {number} high
     * @returns {Vector1}
     */
    clamp(low, high) {
        return this.set(clamp(this.x, low, high));
    }

    /**
     * Negate sign of the value, if value is 7, it will become -7 and vice versa
     * Zero is unaffected
     */
    negate() {
        this.set(-this.x);
    }

    /**
     *
     * @param {Vector1|Vector2|Vector3|Vector4} other
     */
    copy(other) {
        this.set(other.x);
    }

    /**
     *
     * @returns {Vector1}
     */
    clone() {
        return new Vector1(this.x);
    }

    /**
     *
     * @param {Vector1} other
     * @returns {boolean}
     */
    equals(other) {
        return this.x === other.x;
    }

    /**
     * @returns {number}
     */
    hash() {
        return computeHashFloat(this.x);
    }

    /**
     *
     * @param {function(newValue:number, oldValue:number)} handler
     */
    process(handler) {
        handler(this.x, this.x);
        this.onChanged.add(handler);
    }

    toJSON() {
        return this.x;
    }

    fromJSON(val) {
        this.set(val);
    }

    /**
     *
     * @param {number[]} array
     * @param {number} offset
     */
    readFromArray(array, offset = 0) {
        this.set(array[offset]);
    }

    /**
     *
     * @param {number[]} array
     * @param {number} offset
     */
    writeToArray(array, offset = 0) {
        array[offset] = this.x;
    }

    asArray() {
        return [this.x];
    }

    /**
     *
     * @param {BinaryBuffer} buffer
     */
    toBinaryBuffer(buffer) {
        buffer.writeFloat64(this.x);
    }

    /**
     *
     * @param {BinaryBuffer} buffer
     */
    fromBinaryBuffer(buffer) {
        const x = buffer.readFloat64();

        this.set(x);
    }

    /**
     *
     * @param {Vector1} a
     * @param {Vector1} b
     * @return {number}
     */
    static compare(a, b) {
        return a.x - b.x;
    }

    // pretending to be an array

    get 0() {
        return this.x;
    }

    set 0(v) {
        this.x = v;
    }

    * [Symbol.iterator]() {

        yield this.x;

    }
}

/**
 * @readonly
 * @type {boolean}
 */
Vector1.prototype.isVector1 = true;

/**
 * @readonly
 * @type {string}
 */
Vector1.typeName = "Vector1";

/**
 * Utility singleton
 * @readonly
 * @type {Vector1}
 */
Vector1.zero = Object.freeze(new Vector1(0));

/**
 * Utility singleton
 * @readonly
 * @type {Vector1}
 */
Vector1.one = Object.freeze(new Vector1(1));


export default Vector1;
