local vector2 = 
{
    __type = "vec2",
    __call = function(vector2, x, y) 
            return vector2:new(x, y) end
}

vector2.__index = vector2


-- vec2 cocnrtuct
function vector2:new(x, y)
    if type(x) ~= "number" or type(y) ~= "number" then
        error("Invalid input argument. Expected two numbers. [ vec2(number, number) ]")
    end

    local obj = {
        x = x or 0,
        y = y or 0
    }

    setmetatable(obj, self)
    return obj
end

-- string vec
function vector2:strvec2()
    return "(" .. self.x .. ", " .. self.y ..  ")"
end

-- round vector components
-- @param decimals -> integer
-- @return round(vec2)
-- @usage:
--      @example-1:
--              local v1 = vec2(1, 0.5)
--              print(v1:round()) -- Output: (1, 1)
--      @example-2:
--              local v1 = vec2(0.707, 0.5)
--              print(v1:round(2)) -- Output: (0.71, 0.5)
function vector2:round(decimals)
    decimals = decimals or 0
    self.x = math.floor(self.x * 10^decimals + 0.5) / 10^decimals
    self.y = math.floor(self.y * 10^decimals + 0.5) / 10^decimals
    return self
end


-- vec2 magnitude (length)
-- @return {number} - the magnitude of the vector
-- @usage:
--      @example-1:
--              local v1 = vec2(-10, 25)
--              print(v1:len()) -- Output: 26.925824035673
--      @example-2:
--              local v1 = vec2(0.32, 89)
--              print(v1:len()) -- Output: 89.
function vector2:len() 
    return math.sqrt(self.x * self.x + self.y * self.y)
end

-- angle between 2 vec2
-- @param vector {ve2}
-- @return {number} -> radians - angle between the 2 vec
function vector2:abtw(vector)
    local dot_product = self:dot(vector)
    local len_product = self:len() * vector:len()
    if len_product == 0 then
        return 0
    end
    local radians = math.acos(dot_product / len_product)
    return radians
end


-- Normalize vec2
-- @return {vector2} - The normalized vector2
-- @usage:
--      @example-1:
--              local v1 = vec2(10, 15)
--              print(v1:norm()) -- Output: (0.55470019622523, 0.83205029433784)
--      @example-2:
--              local v1 = vec2(-1, 1)
--              print(v1:norm()) -- Output: (-0.70710678118655, 0.70710678118655)
function vector2:norm()
    local length_vectors = self:len()
    return vector2:new(self.x / length_vectors, self.y / length_vectors)
end


-- project vec2
-- @param vector {vec2} 
-- @return {vec2} project
function vector2:proj(vector)
    if type(vector) == "number" then
        print("\n(( TypeError : proj(vec2) ))\nType arg proj(vec2)")
        error("Invalid input argument. Expected a vector2 object")
    end
    local dot_product = self:dot(vector)
    return vector:new(dot_product * (vector.x / vector:len()^2), dot_product * (vector.y / vector:len()^2))
end


-- exclude vector2
-- @param vector {vec2}
-- @return {vec2}
function vector2:vxld(vector)
    if type(vector) == "number" then
        print("\n(( TypeError : vxld(vec2) ))\nType arg vxld(vec2, vec2)")
        error("Invalid input argument. Expected a vector2 object\n")
    end
    return vector2:new(self.x - vector.x, self.y - vector.y)
end


-- vec2 dot product
-- @param vector {vec2} 
-- @return {number}
-- @usage:
--      @example-1:
--              local v1 = vec2(10, 15)
--              local v2 = vec2(-1, 1)
--              print(v1:dot(v2)) -- Output: 5
--      @example-2:
--              local v1 = vec2(-15, 10)
--              print(v1:dot(v1)) -- Output: (-0.83205029433784, 0.55470019622523)
function vector2:dot(vector)
    if type(vector) == "number" then
        print("\n(( TypeError : dot(vec2) ))\nType arg dot(vec2)")
        error("Invalid input argument. Expected a vector2 object")
    end
    return self.x * vector.x + self.y * vector.y
end


-- Linear interpolation for vector2
-- @param b {vector2} - The target vector2
-- @param t {number} - (0 <= t <= 1) the interpolation factor
-- @return {vector2} - The interpolated vector2
function vector2:lerp(b, t)
    if type(b) ~= "table" then
        print("\n(( TypeError : lerp(vec2) ))\nType arg lerp(vec2, number)")
        error("Invalid input argument. Expected a vector2 object\n")
    end
    if type(t) ~= "number" or t < 0 or t > 1 then
        error("Invalid input argument. Expected a number between 0 and 1 .. (0 <= t <= 1)")
    end
    return vector2:new(self.x + t * (b.x - self.x), self.y + t * (b.y - self.y));
end


-- Dist btw 2 vec2
-- @param vector {vector2}
-- @return {number} - distance between the 2 vect2
function vector2:dist(vector)
    if type(vector) ~= "table" then
        print("\n(( TypeError : dist(vec2) ))\nType arg dist(vec2)")
        error("Invalid input argument. Expected a vec2 object or a table with two numbers.")
    end
    local dx = self.x - vector.x
    local dy = self.y - vector.y
    local result = vec2(dx, dy)
    return result:len()
end

-- cross product for vec2 (in 3D space)
-- @param {vec2} v - The other vec2
-- @return {number} -> float || integer
-- @usage
--     local v1 = vec2(10, 15)
--     local v2 = vec2(15, 10)
--     print(v1:cross(v2)) -- Output: -125
function vector2:cross(v)
    if type(v) == "number" then
        print("\n(( TypeError : cross ))\nType arg cross(vec2)")
        error("Invalid input argument. Expected a vec2 object.\n")
    end
    return self.x * v.y - self.y * v.x
end

-- rotate vec2
-- @param angle {number}
-- @param axis {string} - axis rotate around (x, y, or z)
-- @param convert2deg {bool} .. if true => deg convert to rad 
-- @return {vector2} - rotated vector2
function vector2:rot(angle, axis, convert2deg)
    if convert2deg == true then 
        angle = math.rad(angle) 
    end

    if type(axis) == "string" then
        if axis == "x" then 
            local x_new = self.x
            local y_new = self.y * math.cos(angle) - self.y * math.sin(angle)
            self.y = y_new
        elseif axis == "y" then
            local x_new = self.x * math.cos(angle) + self.x * math.sin(angle)
            local y_new = self.y
            self.x = x_new
        elseif axis == "z" then
            local x_new = self.x * math.cos(angle) - self.y * math.sin(angle)
            local y_new = self.x * math.sin(angle) + self.y * math.cos(angle)
            self.x, self.y = x_new, y_new
        end
    elseif axis == nil then
        local x_new = self.x * math.cos(angle) - self.y * math.sin(angle)
        local y_new = self.x * math.sin(angle) + self.y * math.cos(angle)
        self.x, self.y = x_new, y_new
    end
    return self:round(15)
end

-- add vec 
function vector2.__add(value_1, value_2)
    if type(value_1) == "number" then 
        if value_1 == 0 then 
            return vector2:new(value_2.x, value_2.y)
        else 
            return vector2:new(value_2.x + value_1, value_2.y + value_1)
        end
    else
        if type(value_2) == "number" then
            if value_2 == 0 then
                return vector2:new(value_1.x, value_1.y)
            else
                return vector2:new(value_1.x + value_2, value_1.y + value_2)
            end
        else
            return vector2:new(value_1.x + value_2.x, value_1.y + value_2.y)
        end
    end
end


-- sub vec 
function vector2.__sub(value_1, value_2)
    if type(value_1) == "number" then 
        if value_1 == 0 then 
            return vector2:new(value_2.x, value_2.y)
        else 
            return vector2:new(value_2.x - value_1, value_2.y - value_1)
        end
    else
        if type(value_2) == "number" then
            if value_2 == 0 then
                return vector2:new(value_1.x, value_1.y)
            else
                return vector2:new(value_1.x - value_2, value_1.y - value_2)
            end
        else
            return vector2:new(value_1.x - value_2.x, value_1.y - value_2.y)
        end
    end
end



-- mul vec
function vector2.__mul(value_1, value_2)
    if type(value_1) == "number" then
        return vector2:new(value_2.x * value_1, value_2.y * value_1)
    else
        if type(value_2) == "number" then
            return vector2:new(value_1.x * value_2, value_1.y * value_2)
        else
            return vector2:new(value_1.x * value_2.x, value_1.y * value_2.y)
        end
    end
end

-- pow vec
function vector2.__pow(value_1, value_2)
    if type(value_1) == "number" then
        return vector2:new(value_1.x ^ value_2, value_2.y ^ value_1)
    else
        if type(value_2) == "number" then
            return vector2:new(value_1.x ^ value_2, value_1.y ^ value_2)
        else
            return vector2:new(value_1.x ^ value_2.x, value_1.y ^ value_2.y)
        end
    end
end



-- div vec
function vector2.__div(value_1, value_2)
    if type(value_1) == "number" then
        return vector2:new(value_2.x / value_1, value_2.y / value_1)
    else
        if type(value_2) == "number" then
            return vector2:new(value_1.x / value_2, value_1.y / value_2)
        else
            return vector2:new(value_1.x / value_2.x, value_1.y / value_2.y)
        end
    end
end


-- eq vec
function vector2.__eq(a, b)
    return a.x == b.x and a.y == b.y
end


-- eq vec
function vector2.__eq(a, b)
    return a.x == b.x and a.y == b.y
end

function vector2.__tostring(vcrt)
    return vcrt:strvec2()
end

return setmetatable(vector2, vector2)

















