import { Entity } from '@minecraft/server'
import * as Numbers from './Numbers.js';

/**
 * The `Vector` class provides a base for vector operations
 * It supports various vector dimensions for `Vector2`, `Vector3`, and `Vector5`, 
 *
 * This class also provides static methods for vector math operations (e.g., `add`, `subtract`) that can handle 
 * vectors of different sizes, standardizing them to the largest dimension and treating missing components as zero.
 *
 */
export class Vector {
    /**
     * Standardizes two vectors to the same size by filling missing components with 0,
     * and returns them as a tuple [v1, v2].
     * @param {Vector} v1 - The first vector.
     * @param {Vector} v2 - The second vector.
     * @returns {[Vector2|Vector3|Vector5, Vector2|Vector3|Vector5]} - The two vectors adjusted to the same size.
     * @throws {Error} - If the inputs are not valid Vectors
     */
    static standardize(v1, v2) {
        if (!(v1 instanceof Vector2 || v1 instanceof Vector3 || v1 instanceof Vector5)) {
            throw new Error('Invalid vector type for v1: expected Vector2, Vector3, or Vector5');
        }

        if (!(v2 instanceof Vector2 || v2 instanceof Vector3 || v2 instanceof Vector5)) {
            throw new Error('Invalid vector type for v2: expected Vector2, Vector3, or Vector5');
        }

        const maxDimension = Math.max(
            v1 instanceof Vector5 ? 5 : v1 instanceof Vector3 ? 3 : 2,
            v2 instanceof Vector5 ? 5 : v2 instanceof Vector3 ? 3 : 2
        );

        let standardizedV1, standardizedV2;

        if (maxDimension === 2) {
            standardizedV1 = new Vector2(v1.x ?? 0, v1.y ?? 0);
            standardizedV2 = new Vector2(v2.x ?? 0, v2.y ?? 0);
        } else if (maxDimension === 3) {
            standardizedV1 = new Vector3(v1.x ?? 0, v1.y ?? 0, v1.z ?? 0);
            standardizedV2 = new Vector3(v2.x ?? 0, v2.y ?? 0, v2.z ?? 0);
        } else if (maxDimension === 5) {
            standardizedV1 = new Vector5(v1.x ?? 0, v1.y ?? 0, v1.z ?? 0, v1.rx ?? 0, v1.ry ?? 0);
            standardizedV2 = new Vector5(v2.x ?? 0, v2.y ?? 0, v2.z ?? 0, v2.rx ?? 0, v2.ry ?? 0);
        }

        return [standardizedV1, standardizedV2];
    }


    /**
     * Adds two vectors and returns the resulting vector.
     * For Vector5 instances, a Vector 3 is returned
     * @param {Vector} v1 - The first vector.
     * @param {Vector} v2 - The second vector.
     * @returns {Vector} - The resulting vector after addition.
     */
    static add(v1, v2) {
        const [vec1, vec2] = Vector.standardize(v1, v2);

        const x = vec1.x + vec2.x;
        const y = vec1.y + vec2.y;
        const z = vec1.z + vec2.z;

        if (vec1 instanceof Vector5) return new Vector3(x, y, z);
        return new vec1.constructor(x, y, z);
    }

    /**
     * Subtracts two vectors and returns the resulting vector.
     * @param {Vector} v1 - The first vector.
     * @param {Vector} v2 - The second vector.
     * @returns {Vector} - The resulting vector after subtraction.
     */
    static subtract(v1, v2) {
        const [vec1, vec2] = Vector.standardize(v1, v2);

        const x = vec1.x - vec2.x;
        const y = vec1.y - vec2.y;
        const z = vec1.z - vec2.z;

        if (vec1 instanceof Vector5) return new Vector3(x, y, z);
        return new vec1.constructor(x, y, z);
    }

    /**
     * Gets the euclidean distance between two vectors.
     * @param {Vector} v1 - first vector
     * @param {Vector} v2 - Second vector
     * @returns 
     */
    static distance(v1, v2) {
        const d = Vector.subtract(vec1, vec2);
        return Math.sqrt(d.x * d.x + d.y * d.y + d.z * d.z);
    }

    // multiple
    // times
    // equals
    // lerp
}

export class Vector2 {
    constructor(x = 0, y = 0) {
        this.x = x;
        this.y = y;
    }
}

export class Vector3 {
    constructor(x = 0, y = 0, z = 0) {
        this.x = x;
        this.y = y;
        this.z = z;
    }

    normalize() {
        const length = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
        return new Vector3(this.x / length, this.y / length, this.z / length);
    }

    static normalizeVector(vector) {
        return new Vector3(vector?.x, vector?.y, vector?.z).normalize();
    }
}

export class Vector5 {
    constructor(x = 0, y = 0, z = 0, rx = 0, ry = 0) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.rx = rx;
        this.ry = ry;
    }

    /**
     * Y is yaw, pitch is X
     * @param {Number} yaw 
     * @param {Number} pitch 
     * @returns {Vector3} - The direction vector
     */
    static rotationToDirection(yaw, pitch) {
        const radX = Numbers.degToRad(pitch);
        const radY = Numbers.degToRad(yaw);

        return new Vector3(
            Math.cos(radX) * Math.cos(radY),
            Math.sin(radX),
            Math.cos(radX) * Math.sin(radY)
        )
    }

    /**
     * Used to correct the coordinates from player.getRotation()
     * @param {Number} yaw 
     * @param {Number} pitch 
     * @returns 
     */
    static getCorrectedRotation(yaw, pitch) {
        return new Vector2(
            -pitch,
            yaw + 90
        )
    }

    /**
     * 
     * @returns {Vector3} - The direction vector
     */
    getDirectionVector() {
        return Vector5.rotationToDirection(this.ry, this.rx);
    }

    getCorrectedRotation() {
        return Vector5.getCorrectedRotation(this.ry, this.rx)
    }

    /**
     * Returns a relative position
     * @param {Vector3} direction - x: forwards, y: up, z: right
     */
    getRelativePosition(direction) {
        const forwardVector = this.getDirectionVector();

        const rightVector = {
            x: -forwardVector.z,
            y: 0,
            z: forwardVector.x
        };

        const upVector = { x: 0, y: 1, z: 0 };

        const newLocation = {
            x: this.x + (forwardVector.x * direction.x) + (rightVector.x * direction.z) + (upVector.x * direction.y),
            y: this.y + (forwardVector.y * direction.x) + (rightVector.y * direction.z) + (upVector.y * direction.y),
            z: this.z + (forwardVector.z * direction.x) + (rightVector.z * direction.z) + (upVector.z * direction.y)
        };

        return newLocation;
    }
}


