import { assert } from "../assert.js";
import Signal from "../events/signal/Signal.js";

/**
 * Wrapper around a boolean value that signals change via {@link onChanged} signal.
 *
 * @example
 *  const v = new ObservedBoolean(false);
 *
 *  v.getValue(); // false
 *
 *  v.set(true);
 *
 *  v.getValue(); // true
 *
 * @author Alex Goldring
 * @copyright Company Named Limited (c) 2026
 */
class ObservedBoolean extends Boolean {

    /**
     * Signals current value and previously held value. Only fires when the value actually changes.
     * @readonly
     * @type {Signal<boolean,boolean>}
     */
    onChanged = new Signal();

    /**
     *
     * @param {boolean} [value=false]
     * @constructor
     */
    constructor(value = false) {
        super();

        assert.isBoolean(value, 'value');

        /**
         *
         * @type {boolean}
         * @private
         */
        this.__value = value;
    }

    /**
     *
     * @returns {boolean}
     */
    valueOf() {
        return this.__value;
    }

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

    /**
     *
     * @param {boolean} value
     * @returns {ObservedBoolean}
     */
    set(value) {
        assert.isBoolean(value, 'value');

        const oldValue = this.__value;
        if (oldValue !== value) {
            this.__value = value;

            this.onChanged.send2(value, oldValue);
        }

        return this;
    }

    /**
     * Equivalent to {@link set} with `true` argument.
     *
     */
    setTrue() {
        this.set(true);
    }

    /**
     * Equivalent to {@link set} with `false` argument.
     *
     */
    setFalse() {
        this.set(false);
    }

    /**
     *
     * @param {ObservedBoolean} other
     */
    copy(other) {
        this.set(other.getValue());
    }

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

    /**
     *
     * @return {number}
     */
    hash() {
        return this.__value ? 1 : 0;
    }

    /**
     *
     * @param {function(boolean,boolean)} f
     * @param {*} [thisArg]
     * @returns {this}
     */
    process(f, thisArg) {

        this.onChanged.add(f, thisArg);

        const v = this.__value;

        f.call(thisArg, v, v);

        return this;
    }

    /**
     *
     * @returns {boolean}
     */
    getValue() {
        return this.__value;
    }

    /**
     * Flip value.
     * If the value is `true` - it becomes false, if it was `false` it becomes `true`
     */
    invert() {
        this.set(!this.__value);
    }

    toJSON() {
        return this.__value;
    }

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

    /**
     *
     * @param {BinaryBuffer} buffer
     */
    toBinaryBuffer(buffer) {
        buffer.writeUint8(this.__value ? 1 : 0);
    }

    /**
     *
     * @param {BinaryBuffer} buffer
     */
    fromBinaryBuffer(buffer) {
        const v = buffer.readUint8() !== 0;
        this.set(v);
    }
}

/**
 * @readonly
 * @type {ObservedBoolean}
 */
ObservedBoolean.FALSE = Object.freeze(new ObservedBoolean(false));

/**
 * @readonly
 * @type {ObservedBoolean}
 */
ObservedBoolean.TRUE = Object.freeze(new ObservedBoolean(true));

/**
 * @readonly
 * @type {boolean}
 */
ObservedBoolean.prototype.isObservedBoolean = true;

/**
 * @deprecated
 * @type {function(): number}
 */
ObservedBoolean.prototype.hashCode = ObservedBoolean.prototype.hash;

export default ObservedBoolean;
