export class Vector {
    /**
  * Creates a new Vector.
  * 
  * @param {Vector | number} x - A vector object, an object with x, y, z properties, or the x component.
  * @param {number} [y] - The y component (optional if x is a vector object).
  * @param {number} [z] - The z component (optional if x is a vector object).
  * @returns {Vector} A new Vector instance.
  */
    constructor(x = 0, y = 0, z = 0) {
        if (typeof x === 'object' && x !== null && 'x' in x && 'y' in x && 'z' in x) {
            this.x = x.x;
            this.y = x.y;
            this.z = x.z;
        } else {
            this.x = x;
            this.y = y;
            this.z = z;
        }
    }

    // Instance Methods

    add(vec) {
        this.x += vec.x;
        this.y += vec.y;
        this.z += vec.z;
        return this;
    }

    subtract(vec) {
        this.x -= vec.x;
        this.y -= vec.y;
        this.z -= vec.z;
        return this;
    }

    multiply(scalarOrVec) {
        if (scalarOrVec instanceof Vector) {
            this.x *= scalarOrVec.x;
            this.y *= scalarOrVec.y;
            this.z *= scalarOrVec.z;
        } else {
            this.x *= scalarOrVec;
            this.y *= scalarOrVec;
            this.z *= scalarOrVec;
        }
        return this;
    }

    divide(scalarOrVec) {
        if (scalarOrVec instanceof Vector) {
            if (scalarOrVec.x !== 0 && scalarOrVec.y !== 0 && scalarOrVec.z !== 0) {
                this.x /= scalarOrVec.x;
                this.y /= scalarOrVec.y;
                this.z /= scalarOrVec.z;
            } else {
                console.error('Error: Division by zero in one of the vector components');
            }
        } else if (scalarOrVec !== 0) {
            this.x /= scalarOrVec;
            this.y /= scalarOrVec;
            this.z /= scalarOrVec;
        } else {
            console.error('Error: Division by zero');
        }
        return this;
    }

    dot(vec) {
        return this.x * vec.x + this.y * vec.y + this.z * vec.z;
    }

    cross(vec) {
        const x = this.y * vec.z - this.z * vec.y;
        const y = this.z * vec.x - this.x * vec.z;
        const z = this.x * vec.y - this.y * vec.x;
        return new Vector(x, y, z);
    }

    length() {
        return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
    }

    normalize() {
        const length = this.length();
        if (length !== 0) {
            this.divide(length);
        }
        return this;
    }

    clone() {
        return new Vector(this.x, this.y, this.z);
    }

    equals(vec) {
        return this.x === vec.x && this.y === vec.y && this.z === vec.z;
    }

    angleBetween(vec) {
        return Math.acos(this.dot(vec) / (this.length() * vec.length())) * (180 / Math.PI);
    }

    projectOnto(vec) {
        const scalar = this.dot(vec) / vec.dot(vec);
        return new Vector(vec.x * scalar, vec.y * scalar, vec.z * scalar);
    }

    distanceTo(vec) {
        return Math.sqrt(
            (this.x - vec.x) ** 2 +
            (this.y - vec.y) ** 2 +
            (this.z - vec.z) ** 2
        );
    }

    toString() {
        return `Vector(${this.x}, ${this.y}, ${this.z})`;
    }

    // Static Methods

    static add(vec1, vec2) {
        return new Vector(vec1.x + vec2.x, vec1.y + vec2.y, vec1.z + vec2.z);
    }

    static subtract(vec1, vec2) {
        return new Vector(vec1.x - vec2.x, vec1.y - vec2.y, vec1.z - vec2.z);
    }

    static multiply(vec1, scalarOrVec) {
        if (scalarOrVec instanceof Vector) {
            return new Vector(vec1.x * scalarOrVec.x, vec1.y * scalarOrVec.y, vec1.z * scalarOrVec.z);
        } else {
            return new Vector(vec1.x * scalarOrVec, vec1.y * scalarOrVec, vec1.z * scalarOrVec);
        }
    }

    static divide(vec1, scalarOrVec) {
        if (scalarOrVec instanceof Vector) {
            if (scalarOrVec.x !== 0 && scalarOrVec.y !== 0 && scalarOrVec.z !== 0) {
                return new Vector(vec1.x / scalarOrVec.x, vec1.y / scalarOrVec.y, vec1.z / scalarOrVec.z);
            } else {
                //  console.error('Error: Division by zero in one of the vector components');
                return new Vector(vec1.x, vec1.y, vec1.z);
            }
        } else if (scalarOrVec !== 0) {
            return new Vector(vec1.x / scalarOrVec, vec1.y / scalarOrVec, vec1.z / scalarOrVec);
        } else {
            //  console.error('Error: Division by zero');
            return new Vector(vec1.x, vec1.y, vec1.z);
        }
    }

    static dot(vec1, vec2) {
        return vec1.x * vec2.x + vec1.y * vec2.y + vec2.z * vec2.z;
    }

    static cross(vec1, vec2) {
        const x = vec1.y * vec2.z - vec1.z * vec2.y;
        const y = vec1.z * vec2.x - vec1.x * vec2.z;
        const z = vec1.x * vec2.y - vec1.y * vec2.x;
        return new Vector(x, y, z);
    }

    static length(vec) {
        return Math.sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z);
    }

    static normalize(vec) {
        const length = Vector.length(vec);
        if (length !== 0) {
            return Vector.divide(vec, length);
        }
        return new Vector(vec.x, vec.y, vec.z);
    }

    static clone(vec) {
        return new Vector(vec.x, vec.y, vec.z);
    }

    static equals(vec1, vec2) {
        return vec1.x === vec2.x && vec1.y === vec2.y && vec1.z === vec2.z;
    }

    static angleBetween(vec1, vec2) {
        return Math.acos(Vector.dot(vec1, vec2) / (Vector.length(vec1) * Vector.length(vec2))) * (180 / Math.PI);
    }

    static projectOnto(vec1, vec2) {
        const scalar = Vector.dot(vec1, vec2) / Vector.dot(vec2, vec2);
        return new Vector(vec2.x * scalar, vec2.y * scalar, vec2.z * scalar);
    }

    static distanceBetween(vec1, vec2) {
        return Math.sqrt(
            (vec1.x - vec2.x) ** 2 +
            (vec1.y - vec2.y) ** 2 +
            (vec1.z - vec2.z) ** 2
        );
    }

    static fromArray(arr) {
        if (arr.length !== 3) {
            throw new Error('Array must have exactly three elements');
        }
        return new Vector(arr[0], arr[1], arr[2]);
    }

    static fromObject(obj) {
        if (obj.x === undefined || obj.y === undefined || obj.z === undefined) {
            throw new Error('Object must have x, y, and z properties');
        }
        return new Vector(obj.x, obj.y, obj.z);
    }

    static toObject(vec) {
        return { x: vec.x, y: vec.y, z: vec.z };
    }

    static zero() {
        return new Vector(0, 0, 0);
    }

    static one() {
        return new Vector(1, 1, 1);
    }
}
