--!native
--!optimize 2
--!strict

type complex = {r: number, i: number}
type qubit = {complex}
type qubits = {qubit}
type gate = {{complex}}
type matrix = {{complex}}

local cpx = require("cpx")
local gate = require("gate")
local mtx = require("mtx")

-- NOTE: THIS IS FOR SINGULAR QUBIT SYSTEMS
-- MULTI-QUBIT FUNCTIONS WILL HAVE "m" AT THE START OF THE FUNCTION NAME

local qb = {}

function qb.new(alpha: complex, beta: complex): qubit
    return {alpha, beta}
end

function qb.new_ex(n: number): qubits
    local size = 2 ^ n
    local qubits: qubits = {}
    for i = 1, size do
        qubits[i] = qb.new(cpx.new(if i == 1 then 1 else 0, 0), cpx.new(0, 0))
    end
    return qubits
end

function qb.tensorproduct(qubits: qubits): {complex}
    local result: matrix = {{cpx.new(1, 0)}}
    for _, qubit in ipairs(qubits) do
        result = mtx.kronecker(result, {{qubit[1]}, {qubit[2]}})
    end
    local flattened: {complex} = {}
    local resultRows, resultCols = #result, #result[1]
    for i = 1, resultRows do
        for j = 1, resultCols do
            table.insert(flattened, result[i][j])
        end
    end
    return flattened
end

-- rudimentary but works

function qb.measure(qubit: qubit): number
    return if math.random() <= (qubit[1].r ^ 2 + qubit[1].i ^ 2) then 0 else 1
end
--[[
function qb.measure(qubit: qubit): number
    local probs = qb.probability(qubit)
    local rand = math.random()
    return (rand < probs[1]) and 0 : 1
end
--]]

function qb.run(qi: number, gates: {gate}?)
	local qubits = {}

	for i = 1, qi do
		-- init qubit at |0⟩ state
		local qubit = qb.new(cpx.new(1, 0), cpx.new(0, 0))

		local co = coroutine.create(function()
			if gates then
				for _, g in ipairs(gates) do
					-- since we're dealing with single qubits, we can apply the gate
					-- and take the first qubit
					qubit = gate.apply_ex({qubit}, g)[1]
				end
			end

			while true do
				print("qubit[" .. i .. "]: state: |0⟩: " .. qubit[1].r .. "+" .. qubit[1].i ..
					"i, |1⟩: " .. qubit[2].r .. "+" .. qubit[2].i .. "i")
				print("measured value:", qb.measure(qubit))
				coroutine.yield()
			end
		end)
		table.insert(qubits, co)
	end

	for _, co: thread in ipairs(qubits) do
		coroutine.resume(co)
	end
end

function qb.mnew(n: number): qubits
	local qubit = qb.new(cpx.new(1, 0), cpx.new(0, 0)) -- |0⟩ state
	local qubit2 = qb.new(cpx.new(1, 0), cpx.new(0, 0))
	-- this is the kronecker product of the two qubits
	-- for n qubits, the system is rep by 2^n states
	local qubits = {}
	for i = 2, n do
		qubits = mtx.kronecker({qubit}, {qubit2})
	end
	return qubits
end

-- WIP
function qb.mapply(qubits: qubits, g: gate): qubits
	return gate.apply_ex(qubits, g)
end

function qb.mrun(qi: number, gates: {gate}?)
	local qubits = qb.new_ex(qi)
	local co_qubits = {}

	for i = 1, qi do
		local co = coroutine.create(function()
			local qubit = {qubits[i]}

			if gates then
				for _, g in ipairs(gates) do
					qubit = gate.apply_ex(qubit, g)
				end
			end

			while true do
				print("qubit[" .. i .. "]: state: |0⟩: " .. qubit[1][1].r .. "+" .. qubit[1][1].i ..
					"i, |1⟩: " .. qubit[1][2].r .. "+" .. qubit[1][2].i .. "i")
				print("measured value:", qb.measure(qubit[1]))
				coroutine.yield()
			end
		end)

		table.insert(co_qubits, co)
	end

	for _, co in ipairs(co_qubits) do
		coroutine.resume(co)
	end

	local result = ""
	for i = 1, qi do
		local co = co_qubits[i]
		if coroutine.status(co) ~= "dead" then
			coroutine.resume(co)
		end
		result = result .. qb.measure(qubits[i])
	end

	return result
end

function qb.mmeasure(qubits: qubits): number
	local probs = {}
	local total_prob = 0
	for i = 1, #qubits do
		local prob = (qubits[i][1].r^2 + qubits[i][1].i^2)
		probs[i] = total_prob + prob
		total_prob += prob
	end
	local rand = math.random()
	for i = 1, #probs do
		if rand <= probs[i] then
			return i - 1 -- state index
		end
	end
	-- if somehow nothing matches then return last index
	return #probs - 1
end

function qb.mmeasure_ex(qubits: qubits): {number}
	local outcome = {}
	for i, qubit in ipairs(qubits) do
		local prob_0 = qubit[1].r^2 + qubit[1].i^2
		--local prob_1 = qubit[2].r^2 + qubit[2].i^2
		local rand = math.random()
		if rand < prob_0 then
			outcome[i] = 0
		else
			outcome[i] = 1
		end
	end
	return outcome
end

function qb.expectation(qubits: qubits, hamiltonian: matrix): number
    local num_qubits = #qubits
    local state_sz = 2 ^ num_qubits
    local rho = mtx.new({r = state_sz, c = state_sz})
    for i = 1, state_sz do
        for j = 1, state_sz do
            rho[i][j] = cpx.new(0, 0)
        end
    end
    local state_vector = qb.tensorproduct(qubits)
    -- ρ = |ψ⟩⟨ψ| (outer product of state vector with its conjugate transpose)
    for i = 1, state_sz do
        for j = 1, state_sz do
            -- conjugate transpose conj(state_vector[i]) * state_vector[j]
            local elem = cpx.mul(
                cpx.conj(state_vector[i]),
                state_vector[j]
            )
            rho[i][j] = elem
        end
    end

    -- Tr(ρH) for expectation value
    local expect = cpx.new(0, 0)
    for i = 1, state_sz do
        local rho_ii = rho[i][i]
        local H_ii = hamiltonian[i][i]
        local product = cpx.mul(rho_ii, H_ii)
        expect = cpx.add(expect, product)
    end
   --real
    return expect.r
end

function qb.qft(qubits: qubits): qubits
	local n = #qubits
	local omega = cpx.exp(cpx.new(0, 2*math.pi/n))
	local g = {}
    for i = 1, n do
        g[i] = {}
        for j = 1, n do
            g[i][j] = cpx.pow(omega, (i-1)*(j-1))
        end
    end
	return gate.apply_ex(qubits, g)
end

function qb.post_select(qubits: qubits, expected: {number}): qubits?
	local outcome = qb.mmeasure_ex(qubits)
	local matches = true
	for i = 1, #expected do
        if outcome[i] ~= expected[i] then
            return nil
        end
    end
	if matches then
		return qubits
	else
		return nil
	end
end

function qb.entangle(n: number): {qubits}
	local entangled_pairs = {}
	for i = 1, n do
		local qubits = qb.new_ex(2)
		qubits[1] = qb.new(cpx.new(1 / math.sqrt(2), 0), cpx.new(0, 0))
		qubits[2] = qb.new(cpx.new(0, 0), cpx.new(1 / math.sqrt(2), 0))
		table.insert(entangled_pairs, qubits)
	end
	return entangled_pairs
end

function qb.compute_density_matrix(qubits: qubits): matrix
	local num_qubits = #qubits
	local state_sz = 2 ^ num_qubits

	-- zero complex values
	local rho: matrix = {}
    for i = 1, state_sz do
        rho[i] = {}
        for j = 1, state_sz do
            rho[i][j] = cpx.new(0, 0)
        end
    end
	local state_vector = qb.tensorproduct(qubits)
	for i = 1, state_sz do
		for j = 1, state_sz do
			local conj_i = cpx.conj(state_vector[i])
			rho[i][j] = cpx.mul(state_vector[j], conj_i)
		end
	end

	return rho
end

function qb.from_state(alpha: complex, beta: complex): qubit
    local magnitude_squared = cpx.add(
        cpx.mul(cpx.conj(alpha), alpha),
        cpx.mul(cpx.conj(beta), beta)
    )

    local norm = cpx.sqrt(magnitude_squared)

    return {
        cpx.div(alpha, norm),  -- normalized |0⟩ component
        cpx.div(beta, norm)    -- normalized |1⟩ component
    }
end

function qb.to_state_string(qubit: qubit): string
    local alpha, beta = qubit[1], qubit[2]

    local function format_complex(c: complex): string
        local epsilon = 1e-10

        local r_str = (math.abs(c.r) < epsilon) and "0" or
                      string.format("%.10g", c.r)

        local i_str = (math.abs(c.i) < epsilon) and "0" or
                      string.format("%.10g", math.abs(c.i)) .. "i"

        if c.r == 0 and c.i == 0 then
            return "0"
        elseif c.r == 0 then
            return (c.i > 0 and "" or "-") .. i_str
        elseif c.i == 0 then
            return r_str
        else
            local sign = c.i > 0 and "+" : "-"
            return r_str .. sign .. i_str
        end
    end

    return format_complex(alpha) .. "|0⟩ + " .. format_complex(beta) .. "|1⟩"
end

function qb.probability(qubit: qubit): {number}
    return {
        cpx.mul(cpx.conj(qubit[1]), qubit[1]).r,  -- probability of |0⟩
        cpx.mul(cpx.conj(qubit[2]), qubit[2]).r   -- probability of |1⟩
    }
end

--[[
function qb.mmeasure(qubits: qubits): number
	local probs = {}
	local total_prob = 0
	for i = 1, #qubits do
		local prob = (qubits[i][1].r^2 + qubits[i][1].i^2)
		probs[i] = total_prob + prob
		total_prob += prob
	end
	local rand = math.random()
	for i = 1, #probs do
		if rand <= probs[i] then
				return i - 1 -- state index
		end
	end
	return -1
end]]

-- Why what
-- It's because its Because what
-- Not very good because it's just a basic port of conj via mtx to move it here the only thing that might be useful is controlled modular exponentiation but i dont use it yet

--[[

function qb.hadamard(qubits: qubits): qubits
	return gate.apply_ex(qubits, gate.hadamard(qubits))
end

function qb.qft(qubits: qubits): qubits
	return gate.apply_ex(qubits, gate.qft(qubits))
end

function qb.inverse_qft(qubits: qubits): qubits
	local inverse_qft_gate = mtx.conj(gate.qft(qubits)) -- mtx.conj does complex conjugate transpose
	return gate.apply_ex(qubits, inverse_qft_gate)
end

function qb.controlled_phase(control: number, target: number, angle: number, n: number): gate
	local g = mtx.identity(2^n)
	local phase = cpx.exp(cpx.new(0, angle))
	g[2^n][2^n] = phase
	return g
end

function qb.controlled_modular_exponentiation(a: number, N: number, n: number): gate
	local function mod_exp(base: number, exp: number, mod: number): number
		local result = 1
		base = base % mod
		while exp > 0 do
			if exp % 2 == 1 then
				result = (result * base) % mod
			end
			base = (base * base) % mod
			exp = math.floor(exp / 2)
		end
		return result
	end

	local g = mtx.identity(2^n)
	for x = 0, 2^n - 1 do
		local y = mod_exp(a, x, N)
		g[x + 1][y + 1] = cpx.new(1, 0)
		g[x + 1][x + 1] = cpx.new(0, 0)
	end
	return g
end

--]]
return qb


--!native
--!optimize 2
--!strict

local quantum = require("quantum")
local cpx = quantum.cpx
local gate = quantum.gate
local qb = quantum.qb
local mtx = quantum.mtx
local preset = quantum.preset

export type complex = quantum.complex
export type qubit = quantum.qubit
export type qubits = quantum.qubits
export type gate = quantum.gate
export type matrix = quantum.matrix

local qmacs = {}

-- QMACS Configuration
qmacs.config = {
	max_qubits = 32,
	max_gates = 1000,
	error_rate = 0.001,
	decoherence_time = 100, -- in microseconds
}

-- Quantum Register
qmacs.register = {
	qubits = {},
	size = 0,
}

function qmacs.init_register(size: number)
	assert(size > 0 and size <= qmacs.config.max_qubits, "Invalid register size")
	qmacs.register.qubits = qb.new_ex(size)
	qmacs.register.size = size
end

function qmacs.apply_gate(g: gate, target_qubits: {number})
	assert(#target_qubits <= qmacs.register.size, "Invalid number of target qubits")
	local new_state = gate.apply_ex(qmacs.register.qubits, g, target_qubits)
	qmacs.register.qubits = new_state
end

function qmacs.measure(qubit_index: number): number
	assert(qubit_index > 0 and qubit_index <= qmacs.register.size, "Invalid qubit index")
	return qb.measure(qmacs.register.qubits[qubit_index])
end

function qmacs.measure_all(): {number}
	return qb.mmeasure_ex(qmacs.register.qubits)
end

-- Reset a specific qubit to |0> state
function qmacs.reset_qubit(qubit_index: number)
	assert(qubit_index > 0 and qubit_index <= qmacs.register.size, "Invalid qubit index")
	qmacs.register.qubits[qubit_index] = qb.new(cpx.new(1, 0), cpx.new(0, 0))
end

-- Reset all qubits to |0> state
function qmacs.reset_all()
	qmacs.register.qubits = qb.new_ex(qmacs.register.size)
end

function qmacs.get_state(): qubits
	return qmacs.register.qubits
end

function qmacs.apply_noise()
	for i = 1, qmacs.register.size do
		if math.random() < qmacs.config.error_rate then
			local noise_gate = gate.rx(math.random() * math.pi / 10)
			qmacs.apply_gate(noise_gate, {i})
		end
	end
end

function qmacs.simulate_decoherence(time: number)
	local decoherence_probability = 1 - math.exp(-time / qmacs.config.decoherence_time)
	for i = 1, qmacs.register.size do
		if math.random() < decoherence_probability then
			qmacs.reset_qubit(i)
		end
	end
end

-- Create a custom gate
function qmacs.create_custom_gate(matrix: matrix): gate
	assert(mtx.is_unitary(matrix), "Custom gate must be unitary")
	return matrix
end

function qmacs.qft()
	local n = qmacs.register.size
	for i = 1, n do
		qmacs.apply_gate(preset.hadamard, {i})
		for j = i + 1, n do
			local angle = 2 * math.pi / (2^(j-i+1))
			local controlled_phase = gate.controlled_phase(angle)
			qmacs.apply_gate(controlled_phase, {i, j})
		end
	end
	-- Swap qubits
	for i = 1, math.floor(n/2) do
		qmacs.apply_gate(preset.swap, {i, n-i+1})
	end
end

function qmacs.inverse_qft()
	local n = qmacs.register.size
	for i = 1, math.floor(n/2) do
		qmacs.apply_gate(preset.swap, {i, n-i+1})
	end
	for i = n, 1, -1 do
		for j = n, i + 1, -1 do
			local angle = -2 * math.pi / (2^(j-i+1))
			local controlled_phase = gate.controlled_phase(angle)
			qmacs.apply_gate(controlled_phase, {i, j})
		end
		qmacs.apply_gate(preset.hadamard, {i})
	end
end

-- Grover's algorithm
function qmacs.grovers_algorithm(oracle: gate, iterations: number)
	local n = qmacs.register.size

	for i = 1, n do
		qmacs.apply_gate(preset.hadamard, {i})
	end

	for _ = 1, iterations do
		qmacs.apply_gate(oracle, {1, n})

		for i = 1, n do
			qmacs.apply_gate(preset.hadamard, {i})
		end
		for i = 1, n do
			qmacs.apply_gate(preset.paulix, {i})
		end
		qmacs.apply_gate(gate.controlledgate(preset.pauliz), {1, n})
		for i = 1, n do
			qmacs.apply_gate(preset.paulix, {i})
		end
		for i = 1, n do
			qmacs.apply_gate(preset.hadamard, {i})
		end
	end
end

function qmacs.phase_estimation(unitary: gate, precision: number)
	local n = qmacs.register.size
	local m = math.ceil(math.log(1/precision, 2))

	assert(n > m, "Not enough qubits for desired precision")

	for i = 1, m do
		qmacs.apply_gate(preset.hadamard, {i})
	end

	for i = 1, m do
		local controlled_u = gate.controlled_unitary(unitary, i, m+1, 2^(i-1))
		qmacs.apply_gate(controlled_u, {i, m+1})
	end

	qmacs.inverse_qft()
end

function qmacs.quantum_teleportation(source: number, target: number)
	assert(source ~= target, "Source and target qubits must be different")
	assert(source > 0 and source <= qmacs.register.size, "Invalid source qubit")
	assert(target > 0 and target <= qmacs.register.size, "Invalid target qubit")

	local aux = if source < target then target - 1 else target + 1

	qmacs.apply_gate(preset.hadamard, {aux})
	qmacs.apply_gate(preset.cnot, {aux, target})

	qmacs.apply_gate(preset.cnot, {source, aux})
	qmacs.apply_gate(preset.hadamard, {source})

	local m1 = qmacs.measure(source)
	local m2 = qmacs.measure(aux)

	if m2 == 1 then
		qmacs.apply_gate(preset.paulix, {target})
	end
	if m1 == 1 then
		qmacs.apply_gate(preset.pauliz, {target})
	end
end

-- Simple 3-qubit bit flip code
function qmacs.apply_error_correction()
	assert(qmacs.register.size >= 3, "Need at least 3 qubits for error correction")

	qmacs.apply_gate(preset.cnot, {1, 2})
	qmacs.apply_gate(preset.cnot, {1, 3})

	qmacs.apply_noise()

	qmacs.apply_gate(preset.cnot, {1, 2})
	qmacs.apply_gate(preset.cnot, {1, 3})
	qmacs.apply_gate(preset.ccnot, {2, 3, 1})
end

function qmacs.execute_circuit(circuit: {gate})
	for _, g in ipairs(circuit) do
		qmacs.apply_gate(g, {1, qmacs.register.size})
		qmacs.simulate_decoherence(1)
	end
end

local function qubits_to_complex_vector(state: qubits): {complex}
	local vector = {}
	for i = 1, #state do
		table.insert(vector, cpx.new(state[i][1].r, state[i][1].i)) -- That's axtrcts problem, haha!
	end
	return vector
end

function qmacs.state_tomography(measurements: number): matrix
	local n = qmacs.register.size
	local density_matrix = mtx.create_empty({r = 2^n, c = 2^n})

	for _ = 1, measurements do
		local state = qmacs.get_state()
		local complex_vector = qubits_to_complex_vector(state)
		local proj = mtx.outer_product(complex_vector, complex_vector)
		density_matrix = mtx.add(density_matrix, proj)
	end

	return mtx.scalar_mul(cpx.new(1/measurements, 0), density_matrix)
end

return qmacs


--!native
--!optimize 2
--!strict

type complex = {r: number, i: number}
type qubit = {complex}
type qubits = {qubit}
type kernel_t = ("polynomial" | "gaussian" | "quantum")?

local qb = require("qb")
local cpx = require("cpx")
local gate = require("gate")

local qml = {}

local qsvm = {}

local function get_kernel(kernel_t: kernel_t, gamma: number?): (qubits_1: qubits, qubits_2: qubits) -> complex
	return if kernel_t then
		if kernel_t == "polynomial" then
			qsvm.polynomial_kernel
		elseif kernel_t == "gaussian" then
			function(qubits_1: qubits, qubits_2: qubits)
				assert(gamma, "gamma was not provided")
				return qsvm.gaussian_kernel(qubits_1, qubits_2, gamma)
			end
		elseif kernel_t == "quantum" then
			qsvm.quantum_kernel
		else (function()
			warn "kernel type not found, falling back to quantum kernel"
			return qsvm.quantum_kernel
		end)()
	else
		qsvm.quantum_kernel
end

function qsvm.encode(data_point: {number})
	local qubits = {}
	for i = 1, #data_point do
		local angle = data_point[i] * math.pi -- scale data to [0, π]
		qubits[i] = qb.new(cpx.new(math.cos(angle / 2), math.sin(angle / 2)), cpx.new())
	end

	return qubits
end

function qsvm.quantum_kernel(qubits_1: qubits, qubits_2: qubits)
	local dot_prod = cpx.new(1)
	-- assuming both of it are the same size... just assuming!
	for i = 1, #qubits_1 do
		-- evil
		dot_prod = cpx.mul(dot_prod, cpx.mul(cpx.conj(qubits_1[i][1]), qubits_2[i][1]))
	end

	return dot_prod
end

-- alt kernel (deg2)
function qsvm.polynomial_kernel(qubits_1: qubits, qubits_2: qubits)
	return cpx.pow(qsvm.quantum_kernel(qubits_1, qubits_2), 2)
end

-- another alt kernel!!
function qsvm.gaussian_kernel(qubits_1: qubits, qubits_2: qubits, gamma: number)
	local dst = cpx.new()
	-- JUST ASSUMING....
	for i = 1, #qubits_1 do
		local diff = cpx.sub(qubits_1[i][1], qubits_2[i][2])
		dst = cpx.add(dst, cpx.mul(diff, cpx.conj(diff)))
	end
	return cpx.new(math.exp(-gamma * dst.r))
end

function qsvm.train(data: {{number}}, labels: {number}, C: number, kernel_type: kernel_t, gamma: number?)
	local n = #data
	local alpha = {}
	local kernel = get_kernel(kernel_type, gamma)

	for i = 1, n do
		alpha[i] = 0
	end

	for i = 1, n do
		for j = 1, n do
			local kernal_val = kernel(qsvm.encode(data[i]), qsvm.encode(data[j]))
			alpha[i] = alpha[i] + labels[i] * labels[j] * cpx.abs(kernal_val) * C
		end
	end

	return alpha
end

function qsvm.predict(alpha: {number}, data: {{number}}, labels: {number}, test_point: {number}, kernel_type: kernel_t, gamma: number?)
	local result = 0
	local kernel = get_kernel(kernel_type, gamma)

	for i = 1, #alpha do
		local kernel_val = kernel(qsvm.encode(data[i]), qsvm.encode(test_point))
		result = result + alpha[i] * labels[i] * cpx.abs(kernel_val)
	end

	return if result > 0 then 1 else -1
end

function qsvm.cross_validate(data: {{number}}, labels: {number}, kernel_type: kernel_t, C_values: {number}, gamma_values: {number}, folds: number)
	local best_C = C_values[1]
	local best_gamma = gamma_values[1]
	local best_accuracy = 0

	for _, C in ipairs(C_values) do
		for _, gamma in ipairs(gamma_values) do
			local total_accuracy = 0
			for fold = 1, folds do
				local train_data = {}
				local train_labels = {}
				local test_data = {}
				local test_labels = {}

				for i = 1, #data do
					if (i % folds) == fold then
						table.insert(test_data, data[i])
						table.insert(test_labels, labels[i])
					else
						table.insert(train_data, data[i])
						table.insert(train_labels, labels[i])
					end
				end

				local alpha = qsvm.train(train_data, train_labels, C, kernel_type, gamma)
				local correct = 0

				for i = 1, #test_data do
					local prediction = qsvm.predict(alpha, train_data, train_labels, test_data[i], kernel_type, gamma)
					if prediction == test_labels[i] then
						correct = correct + 1
					end
				end

				local accuracy = correct / #test_data
				total_accuracy = total_accuracy + accuracy
			end

			local avg_accuracy = total_accuracy / folds
			if avg_accuracy > best_accuracy then
				best_accuracy = avg_accuracy
				best_C = C
				best_gamma = gamma
			end
		end
	end

	return best_C, best_gamma, best_accuracy
end

--[[
	-- Example code

local data = {
	{0.5, 0.7},
	{0.3, 0.9},
	{0.8, 0.4}
}
local labels = {1, -1, 1}

-- cross-validate to find the best c and gamma parameters for the gaussian kernel
local c_vals = {0.1, 1.0, 10.0}
local gamma_vals = {0.1, 0.5, 1.0}
local best_c, best_gamma, best_acc = qsvm.cross_validate(data, labels, "gaussian", c_vals, gamma_vals, 3)

print("best c: ", best_c, "best gamma: ", best_gamma, "best accuracy: ", best_acc)

-- train and predict
local alpha = qsvm.train(data, labels, best_c, "gaussian", best_gamma)
local test_point = {0.6, 0.8}
local prediction = qsvm.predict(alpha, data, labels, test_point, "gaussian", best_gamma)

print("prediction: ", prediction)

]]

qml.qsvm = qsvm

local vqc = {}

function vqc.encode(data_point: {number}, encoding_type: "angle" | "amplitude"): qubits
	local n = #data_point
	local qubits = {}
	if encoding_type == "angle" then
		for i = 1, n do
			local angle = data_point[i] * math.pi
			qubits[i] = {cpx.new(math.cos(angle / 2), 0), cpx.new(math.sin(angle / 2), 0)}
		end
	elseif encoding_type == "amplitude" then
		local norm = 0
		for i = 1, n do
			norm = norm + data_point[i] ^ 2
		end
		norm = math.sqrt(norm)
		for i = 1, n do
			qubits[i] = {cpx.new(data_point[i] / norm, 0), cpx.new(0, 0)}
		end
	end
	return qubits
end

function vqc.ansatz(params: {number}, qubits: qubits, layers: number): qubits
	local n = #qubits
	for l = 1, layers do
		for i = 1, n do
			local theta = params[i] * math.pi * l / layers
			qubits[i] = gate.apply(qubits[i], gate.rx(theta))
			qubits[i] = gate.apply(qubits[i], gate.rz(theta))
			if i < n then
				qubits[i] = gate.apply(qubits[i], gate.cnot(i, i + 1))
			end
		end
	end
	return qubits
end

function vqc.measure(qubits: qubits): {number}
	local result = {}
	for i, qubit in ipairs(qubits) do
		local prob_0 = cpx.abs(cpx.mul(cpx.conj(qubit[1]), qubit[1]))
		--local prob_1 = 1 - prob_0
		if math.random() < prob_0 then
			result[i] = 0
		else
			result[i] = 1
		end
	end
	return result
end

function vqc.loss(prediction: number, label: number, params: {number}, regularization_type: "L1" | "L2", lambda: number): number
	local loss = 0
	if label == 1 then
		loss = -math.log(prediction)
	else
		loss = -math.log(1 - prediction)
	end

	if regularization_type == "L1" then
		for _, param in ipairs(params) do
			loss = loss + lambda * math.abs(param)
		end
	elseif regularization_type == "L2" then
		for _, param in ipairs(params) do
			loss = loss + lambda * param^2
		end
	end
	return loss
end

function vqc.gradient(params: {number}, data: {number}, label: number, layers: number, epsilon: number): {number}
	local gradients = {}
	for i, param in ipairs(params) do
		local params_plus = {table.unpack(params)}
		local params_minus = {table.unpack(params)}
		params_plus[i] = param + epsilon
		params_minus[i] = param - epsilon

		local qubits_plus = vqc.encode(data, "angle")
		qubits_plus = vqc.ansatz(params_plus, qubits_plus, layers)
		local prediction_plus = vqc.measure(qubits_plus)[1]

		local qubits_minus = vqc.encode(data, "angle")
		qubits_minus = vqc.ansatz(params_minus, qubits_minus, layers)
		local prediction_minus = vqc.measure(qubits_minus)[1]

		local loss_plus = vqc.loss(prediction_plus, label, params, "L2", 0)
		local loss_minus = vqc.loss(prediction_minus, label, params, "L1", 0)

		gradients[i] = (loss_plus - loss_minus) / (2 * epsilon)
	end
	return gradients
end

-- evil optimizer
local function adam_update(params: {number}, gradients: {number}, learning_rate: number, beta1: number, beta2: number, epsilon: number, m: {number}, v: {number}, t: number)
	for i, param in ipairs(params) do
		m[i] = beta1 * m[i] + (1 - beta1) * gradients[i]
		v[i] = beta2 * v[i] + (1 - beta2) * gradients[i]^2

		local m_hat = m[i] / (1 - beta1^t)
		local v_hat = v[i] / (1 - beta2^t)

		params[i] = params[i] - learning_rate * m_hat / (math.sqrt(v_hat) + epsilon)
	end
end

function vqc.train(data: {{number}}, labels: {number}, epochs: number, learning_rate: number, layers: number): {number}
	local params = {}
	local m = {}
	local v = {}
	local n = #data[1]
	local epsilon = 1e-8
	local beta1, beta2 = 0.9, 0.999

	for i = 1, n do
		params[i] = math.random()
		m[i] = 0
		v[i] = 0
	end

	for epoch = 1, epochs do
		local total_loss = 0
		for i, data_point in ipairs(data) do
			local qubits = vqc.encode(data_point, "angle")
			qubits = vqc.ansatz(params, qubits, layers)
			local prediction = vqc.measure(qubits)[1]
			local gradients = vqc.gradient(params, data_point, labels[i], layers, epsilon)
			local loss = vqc.loss(prediction, labels[i], params, "L2", 0.001)
			total_loss = total_loss + loss

			adam_update(params, gradients, learning_rate, beta1, beta2, epsilon, m, v, epoch)
		end
		print("epoch " .. epoch .. " loss: " .. total_loss / #data)
	end

	return params
end

qml.vqc = vqc

local qnn = {}

-- Pluh giant 5 please make

qml.qnn = qnn

return qml


--!native
--!optimize 2
--!strict

local qb = require("qb")
local cpx = require("cpx")
local gate = require("gate")
local preset = require("preset")

type Bit = number -- 0 or 1
type complex = {r: number, i: number}
type qubit = {complex}
type qubits = {qubit}

type Party = {
    id: number,
    input: Bit,
    qubit: qubit
}

local qsmc = {}

local function quantum_channel(state: qubit): qubit
    local noise_angle = math.random() * math.pi / 10
    return gate.apply(state, gate.ry(noise_angle))
end

function qsmc.create_ghz_state(n: number): qubits
    assert(n > 1, "Number of parties must be greater than 1")
    local ghz = qb.new(cpx.new(1, 0), cpx.new(0, 0))  -- |0⟩ state
    ghz = gate.apply(ghz, preset.hadamard)

    local states: qubits = {ghz}
    for _ = 2, n do
        local new_qubit = qb.new(cpx.new(1, 0), cpx.new(0, 0))  -- |0⟩ state
        states[#states] = gate.apply_ex({states[#states], new_qubit}, preset.cnot)[1]
        table.insert(states, new_qubit)
    end

    return states
end

function qsmc.encode_bit(bit: Bit): qubit
    local state = qb.new(cpx.new(1, 0), cpx.new(0, 0))  -- |0⟩ state
    if bit == 1 then
        state = gate.apply(state, preset.paulix)  -- Apply X gate to flip to |1⟩ state
    end
    return state
end

function qsmc.measure(qubit: qubit): Bit
    return qb.measure(qubit)
end

function qsmc.secure_and(party1: Party, party2: Party): Bit
    local ghz = qsmc.create_ghz_state(3)

    ghz[1] = gate.apply_ex({ghz[1], party1.qubit}, preset.cnot)[1]
    ghz[2] = gate.apply_ex({ghz[2], party2.qubit}, preset.cnot)[1]

    local result = qsmc.measure(ghz[3])

    result = if qsmc.measure(quantum_channel(qsmc.encode_bit(result))) == 1 then 1 else 0

    return result
end

function qsmc.secure_xor(party1: Party, party2: Party): Bit
    local bell_pair = qsmc.create_ghz_state(2)

    bell_pair[1] = gate.apply_ex({bell_pair[1], party1.qubit}, gate.controlledgate(preset.pauliz))[1]
    bell_pair[2] = gate.apply_ex({bell_pair[2], party2.qubit}, gate.controlledgate(preset.pauliz))[1]

    local result1 = qsmc.measure(gate.apply(bell_pair[1], preset.hadamard))
    local result2 = qsmc.measure(gate.apply(bell_pair[2], preset.hadamard))

    return bit32.bxor(result1, result2)
end

function qsmc.run_protocol(num_parties: number): ()
    assert(num_parties > 1, "Number of parties must be greater than 1")
    print("Starting Quantum Secure Multiparty Computation Protocol")

    local parties: {Party} = {}
    for i = 1, num_parties do
        local input = math.random(0, 1)
        parties[i] = {
            id = i,
            input = input,
            qubit = qsmc.encode_bit(input)
        }
        print("Party " .. tostring(i) .. " input: " .. tostring(input))
    end

    local and_result: Bit = parties[1].input
    for i = 2, num_parties do
        and_result = qsmc.secure_and({id = i-1, input = and_result, qubit = qsmc.encode_bit(and_result)}, parties[i])
    end

    local xor_result: Bit = parties[1].input
    for i = 2, num_parties do
        xor_result = qsmc.secure_xor({id = i-1, input = xor_result, qubit = qsmc.encode_bit(xor_result)}, parties[i])
    end

    print("QSMC Protocol Results:")
    print("AND of all inputs: " .. tostring(and_result))
    print("XOR of all inputs: " .. tostring(xor_result))
    print("Quantum Secure Multiparty Computation Protocol Completed")
end

return qsmc


--!native
--!optimize 2
--!strict

math.randomseed(os.time())
export type complex = {r: number, i: number}
export type qubit = {complex}
export type qubits = {qubit}
export type gate = {{complex}}
export type matrix = {{complex}}
export type matrix_sz = {r: number, c: number}
return {
	mtx = require("mtx");
	cpx = require("cpx");
	qb = require("qb");
	gate = require("gate");
	preset = require("preset");
	wave = require("wave");
	algo = require("algo");
	err = require("err");
	noise = require("noise");
	tp = require("tp");
	circuitvis = require("circuitvis");
	ent = require("ent");
	crypto = require("crypto");
	kitaev = require("kitaev");
	anneal = require("anneal");
	qml = require("qml");
	decay = require("decay");
	compiler = require("compiler");
	anyon = require("anyon");
}


math.randomseed(os.time())

----------------------------------------------------------------------------------------------

type complex = {r : number, i : number}
type qubit = {complex}
type gate = {{complex}}

----------------------------------------------------------------------------------------------

local cpx = {}

function cpx.new(real : number, imaginary : number) : complex
	return {r = real, i = imaginary}
end

function cpx.add(c1 : complex, c2 : complex)
	return cpx.new(c1.r + c2.r, c1.i + c2.i)
end

function cpx.sub(c1 : complex, c2 : complex)
	return cpx.new(c1.r - c2.r, c1.i - c2.i)
end

function cpx.mul(c1 : complex, c2 : complex)
	return cpx.new(c1.r * c2.r - c1.i * c2.i, c1.r * c2.i + c1.i * c2.r)
end

function cpx.div(c1 : complex, c2 : complex)
	local d = c2.r * c2.r + c2.i * c2.i
	return cpx.new((c1.r * c2.r + c1.i * c2.i) / d, (c1.i * c2.r - c1.r * c2.i) / d)
end

----------------------------------------------------------------------------------------------

local qb = {}

function qb.new(alpha : complex, beta : complex)
	return {alpha, beta}
end

function qb.gate(qubit : qubit, gate : gate)
	local alpha = cpx.add(
		cpx.mul(gate[1][1], qubit[1]),
		cpx.mul(gate[1][2], qubit[2])
	)
	local beta = cpx.add(
		cpx.mul(gate[2][1], qubit[1]),
		cpx.mul(gate[2][2], qubit[2])
	)
	return {alpha, beta}
end

-- rudimentary but works

function qb.measure(qubit : qubit)
	return if math.random() <= (qubit[1].r * qubit[1].r + qubit[1].i * qubit[1].i) then 0 else 1
end

function qb.run(qi : number, gates : {gate}?)
	local qubits = {}

	for i = 1, qi do
		-- init qubit at |0⟩ state
		local qubit = qb.new(cpx.new(1, 0), cpx.new(0, 0))

		local co = coroutine.create(function()
			if gates then
				for _, gate in ipairs(gates) do
					qubit = qb.gate(qubit, gate)
				end
			end

			while true do
				print("qubit[" .. i .. "]: state: |0⟩: " .. qubit[1].r .. "+" .. qubit[1].i ..
					"i, |1⟩: " .. qubit[2].r .. "+" .. qubit[2].i .. "i")
				print("measured value:", qb.measure(qubit))
				coroutine.yield()
			end
		end)
		table.insert(qubits, co)
	end

	for _, co : thread in ipairs(qubits) do
		coroutine.resume(co)
	end
end

----------------------------------------------------------------------------------------------

local gate = {}

function gate.new(a11 : complex, a12 : complex, a21 : complex, a22 : complex) : gate
	return {{a11, a12}, {a21, a22}}
end

function gate.new_size(size : number, ... : complex)
	local matrix = {...}
	if #matrix ~= size * size then
		error("matrix must be " .. size .. "x" .. size)
	end
	local g = {}
	for i = 1, size do
		g[i] = {}
		for j = 1, size do
			g[i][j] = matrix[(i-1)*size + j]
		end
	end
	return g
end

----------------------------------------------------------------------------------------------

local preset = {
	identity = gate.new(
		cpx.new(1, 0), cpx.new(0, 0),
		cpx.new(0, 0), cpx.new(1, 0)
	);
	paulix = gate.new(
		cpx.new(0, 0), cpx.new(1, 0),
		cpx.new(1, 0), cpx.new(0, 0)
	);
	pauliy = gate.new(
		cpx.new(0, 0), cpx.new(0, -1),
		cpx.new(0, 1), cpx.new(0, 0)
	);
	pauliz = gate.new(
		cpx.new(1, 0), cpx.new(0, 0),
		cpx.new(0, 0), cpx.new(-1, 0)
	);
	hadamard = gate.new(
		cpx.new(1/math.sqrt(2), 0), cpx.new(1/math.sqrt(2), 0),
		cpx.new(1/math.sqrt(2), 0), cpx.new(-1/math.sqrt(2), 0)
	);
	phase = gate.new(
		cpx.new(1, 0), cpx.new(0, 0),
		cpx.new(0, 0), cpx.new(0, 1)
	);
	pi8 = gate.new(
		cpx.new(1, 0), cpx.new(0, 0),
		cpx.new(0, 0), cpx.new(1/math.sqrt(2), 1/math.sqrt(2))
	);
	cnot = gate.new_size(4,
		cpx.new(1, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0),
		cpx.new(0, 0), cpx.new(1, 0), cpx.new(0, 0), cpx.new(0, 0),
		cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(1, 0),
		cpx.new(0, 0), cpx.new(0, 0), cpx.new(1, 0), cpx.new(0, 0)
	);
	swap = gate.new_size(4,
		cpx.new(1, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0),
		cpx.new(0, 0), cpx.new(0, 0), cpx.new(1, 0), cpx.new(0, 0),
		cpx.new(0, 0), cpx.new(1, 0), cpx.new(0, 0), cpx.new(0, 0),
		cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(1, 0)
	);
	ccnot = gate.new_size(8,
		cpx.new(1, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0),
		cpx.new(0, 0), cpx.new(1, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0),
		cpx.new(0, 0), cpx.new(0, 0), cpx.new(1, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0),
		cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(1, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0),
		cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(1, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0),
		cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(1, 0), cpx.new(0, 0), cpx.new(0, 0),
		cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(1, 0),
		cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(0, 0), cpx.new(1, 0), cpx.new(0, 0)
	);

}

----------------------------------------------------------------------------------------------

-- EXAMPLE

local qubit = qb.new(cpx.new(1, 0), cpx.new(0, 0))
qubit = qb.gate(qubit, preset.hadamard)

print("Qubit state after Hadamard gate:")
print("Alpha (|0⟩): " .. qubit[1].r .. " + " .. qubit[1].i .. "i")
print("Beta (|1⟩): " .. qubit[2].r .. " + " .. qubit[2].i .. "i")

local result = qb.measure(qubit)
print("Measurement result: " .. result)

qb.run(3)


--!native
--!optimize 2
--!strict

local quantum = require("quantum")
local cpx = quantum.cpx
local qb = quantum.qb
local gate = quantum.gate
local preset = quantum.preset

local secret_sharing = {}

export type complex = {r: number, i: number}
export type qubit = {complex}
export type qubits = {qubit}

-- Classical Shamir's Secret Sharing

local function mod_pow(base: number, exponent: number, modulus: number): number
	local result = 1
	base = base % modulus
	while exponent > 0 do
		if exponent % 2 == 1 then
			result = (result * base) % modulus
		end
		exponent = math.floor(exponent / 2)
		base = (base * base) % modulus
	end
	return result
end

local function generate_polynomial(secret: number, threshold: number, prime: number): {number}
	local coefficients = {secret}
	for i = 2, threshold do
		coefficients[i] = math.random(0, prime - 1)
	end
	return coefficients
end

local function evaluate_polynomial(coefficients: {number}, x: number, prime: number): number
	local result = 0
	for i, coeff in ipairs(coefficients) do
		result = (result + coeff * mod_pow(x, i - 1, prime)) % prime
	end
	return result
end

function secret_sharing.generate_shares(secret: number, num_shares: number, threshold: number, prime: number): {{number}}
	local coefficients = generate_polynomial(secret, threshold, prime)
	local shares = {}
	for i = 1, num_shares do
		local x = i
		local y = evaluate_polynomial(coefficients, x, prime)
		table.insert(shares, {x, y})
	end
	return shares
end

local function mod_inverse(a: number, m: number): number
	local m0, x0, x1 = m, 0, 1
	while a > 1 do
		local q = math.floor(a / m)
		m, a = a % m, m
		x0, x1 = x1 - q * x0, x0
	end
	return x1 < 0 and x1 + m0 or x1
end

function secret_sharing.reconstruct_secret(shares: {{number}}, prime: number): number
	local secret = 0
	for i, share_i in ipairs(shares) do
		local xi, yi = share_i[1], share_i[2]
		local numerator, denominator = 1, 1
		for j, share_j in ipairs(shares) do
			if i ~= j then
				local xj = share_j[1]
				numerator = (numerator * (xj % prime)) % prime
				denominator = (denominator * ((xj - xi + prime) % prime)) % prime
			end
		end
		local lagrange = (yi * numerator * mod_inverse(denominator, prime)) % prime
		secret = (secret + lagrange) % prime
	end
	return secret
end

local function encode_qubit(qubit: qubit, threshold: number, num_shares: number): qubits
	local encoded_qubits: qubits = {}
	for i = 1, num_shares do
		local new_qubit = qb.new(cpx.new(1, 0), cpx.new(0, 0))
		encoded_qubits[i] = new_qubit
	end

	for i = 2, threshold do
		local result = gate.apply_ex({qubit, encoded_qubits[i]}, preset.cnot)
		encoded_qubits[i] = result[2]
	end

	for i = threshold + 1, num_shares do
		encoded_qubits[i] = gate.apply(encoded_qubits[i], preset.hadamard)
	end

	return encoded_qubits
end

function secret_sharing.quantum_share_secret(secret_qubit: qubit, num_shares: number, threshold: number): qubits
	return encode_qubit(secret_qubit, threshold, num_shares)
end

function secret_sharing.quantum_reconstruct_secret(shares: qubits, threshold: number): qubit
	local reconstructed_qubit = shares[1]

	for i = 2, threshold do
		local result = gate.apply_ex({reconstructed_qubit, shares[i]}, preset.cnot)
		reconstructed_qubit = result[1]
	end

	return reconstructed_qubit
end

-- VSS (Verifiable Secret Sharing)

local function generate_commitment(value: number, prime: number): (number, number)
	local r = math.random(1, prime - 1)
	local commitment = (mod_pow(2, value, prime) * mod_pow(3, r, prime)) % prime
	return commitment, r
end

function secret_sharing.vss_generate_shares(secret: number, num_shares: number, threshold: number, prime: number): ({{number}}, {{number}}, number, number)
	local coefficients = generate_polynomial(secret, threshold, prime)
	local shares = {}
	local commitments = {}

	for i = 1, num_shares do
		local x = i
		local y = evaluate_polynomial(coefficients, x, prime)
		table.insert(shares, {x, y})
	end

	for _, coeff in ipairs(coefficients) do
		local commitment, r = generate_commitment(coeff, prime)
		table.insert(commitments, {commitment, r})
	end

	return shares, commitments, coefficients[1], commitments[1][2]
end

function secret_sharing.vss_verify_share(share: {number}, commitments: {{number}}, prime: number): boolean
	local x, y = share[1], share[2]
	local product = 1

	for i, commitment in ipairs(commitments) do
		local c = commitment[1]
		product = (product * mod_pow(c, mod_pow(x, i - 1, prime), prime)) % prime
	end

	local verify = (mod_pow(2, y, prime) * mod_pow(3, math.random(1, prime - 1), prime)) % prime
	return product == verify
end

return secret_sharing


--!native
--!optimize 2
--!strict

type complex = {r: number, i: number}
type qubit = {complex}
type qubits = {qubit}
type gate = {{complex}}
type matrix = {{complex}}

local gate = require("gate")
local qb = require("qb")
local preset = require("preset")

local tp = {}

function tp.entangle(): qubits
	local qubits = qb.new_ex(2)
	qubits[1] = gate.apply(qubits[1], preset.hadamard)
	qubits = gate.apply_ex(qubits, preset.cnot)
	return qubits
end

function tp.bell_measurement(qubits: qubits): (number, number)
	qubits[1] = gate.apply(qubits[1], preset.hadamard)
	qubits = gate.apply_ex(qubits, preset.cnot)

	local result = qb.mmeasure_ex({qubits[1], qubits[2]})
	return result[1], result[2]
end

function tp.apply_corrections(qubits: qubits, bits: {number}): qubits
	if bits[1] == 1 then
		qubits[1] = gate.apply(qubits[1], preset.paulix)
	end
	if bits[2] == 1 then
		qubits[2] = gate.apply(qubits[2], preset.pauliz)
	end
	return qubits
end

function tp.teleport(qubit: qubit, entangled_qubits: qubits): qubits
	return tp.apply_corrections(entangled_qubits, {tp.bell_measurement({qubit, entangled_qubits[1]})})
end

return tp


--!native
--!optimize 2
--!strict

type complex = {r: number, i: number}
type qubit = {complex}
type qubits = {qubit}
type gate = {{complex}}
type matrix = {{complex}}

local gate = require("gate")
local qb = require("qb")

local wave = {}

-- apply hadamard to qubits to create wave-like superposition

function wave.apply_wave(qubits: qubits, wave: gate): qubits
	for i = 1, #qubits do
		qubits = gate.apply_ex(qubits, wave)
	end
	return qubits
end

function wave.evolve(qubits: qubits, angle: number): qubits
	for i = 1, #qubits do
		qubits = gate.apply_ex(qubits, gate.rx(angle))
	end
	return qubits
end

function wave.observe(qubits: qubits, iter: number)
	for t = 1, iter do
		print("observation at time step " .. t)
		qubits = wave.evolve(qubits, math.pi / 10)
		local result = qb.mmeasure(qubits)
		print("measured state: " .. result)
		coroutine.yield()
	end
end

function wave.run(qubits: qubits, iter: number)
	local co = coroutine.create(function() wave.observe(qubits, iter) end)
	for _ = 1, iter do
		coroutine.resume(co)
	end
end

return wave


--> Run stylua to check for formatting errors

local process = require("@lune/process")

local CommandBuilder = require("./util/exec")

process.exit(CommandBuilder.new("stylua"):withArgs(process.args):withArg("."):withStdioStrategy("forward"):exec().code)


--> Run selene for linting

local process = require("@lune/process")

local CommandBuilder = require("./util/exec")

process.exit(CommandBuilder.new("selene"):withArg("."):withStdioStrategy("forward"):exec().code)


--> Run luau-lsp analysis to check for type errors

local process = require("@lune/process")

local CommandBuilder = require("./util/exec")

process.exit(
	CommandBuilder.new("luau-lsp")
		:withArg("analyze")
    :withArgs({ "--platform", "standard" })
		:withArgs({ "--settings", ".vscode/settings.json" })
		:withArgs({ "--ignore", "'**/*_packages/**/*'" })
		:withArg(".")
		:withStdioStrategy("forward")
		:exec().code
)


--> lib: Builder pattern class to spawn child processes

local process = require("@lune/process")
local stdio = require("@lune/stdio")

local CommandBuilder = {}

export type CommandBuilder = typeof(setmetatable({} :: CommandBuilderFields, { __index = CommandBuilder }))
type CommandBuilderFields = {
	program: string,
	args: { string },
	stdioStrategy: IoStrategyMapping?,
}
export type StdioStrategy = "pipe" | "inherit" | "forward" | "none"
export type IoStrategyMapping = {
	stdin: StdioStrategy?,
	stdout: StdioStrategy?,
	stderr: StdioStrategy?,
}

local DEFAULT_STDIO_STRATEGY: IoStrategyMapping = {
	stdin = "pipe",
	stdout = "pipe",
	stderr = "pipe",
}
function CommandBuilder.new(program: string)
	return setmetatable(
		{
			program = program,
			args = {},
			stdioStrategy = nil,
		} :: CommandBuilderFields,
		{
			__index = CommandBuilder,
		}
	)
end

function CommandBuilder.withArg(self: CommandBuilder, arg: string): CommandBuilder
	table.insert(self.args, arg)
	return self
end

function CommandBuilder.withArgs(self: CommandBuilder, args: { string }): CommandBuilder
	for _, arg in args do
		self:withArg(arg)
	end

	return self
end

function CommandBuilder.withStdioStrategy(
	self: CommandBuilder,
	strategy: StdioStrategy | IoStrategyMapping
): CommandBuilder
	self.stdioStrategy = if typeof(strategy) == "string"
		then {
			stdout = strategy,
			stderr = strategy,
		}
		else strategy
	return self
end

local function intoSpawnOptionsStdioKind(strategy: StdioStrategy): process.ExecStdioKind
	if strategy == "pipe" then
		return "default"
	end

	if strategy == "inherit" then
		return "inherit"
	end

	if strategy == "forward" then
		return "forward"
	end

	if strategy == "none" then
		return "none"
	end

	error(`Non-strategy provided: {strategy}`)
end

function CommandBuilder.exec(self: CommandBuilder): process.ExecResult
	print("$", stdio.style("dim") .. self.program, table.concat(self.args, " ") .. stdio.style("reset"))

	local function translateIoStrategyMappings(mappings: IoStrategyMapping)
		local translatedMappings: process.ExecStdioOptions = {}
		for field: string, value in pairs(mappings) do
			translatedMappings[field] = intoSpawnOptionsStdioKind(value)
		end

		return translatedMappings
	end

	local child = process.exec(self.program, self.args, {
		shell = true,
		stdio = translateIoStrategyMappings(self.stdioStrategy or DEFAULT_STDIO_STRATEGY),
	})

	if not child.ok then
		print(`\n{stdio.color("red")}[luau-lsp]{stdio.color("reset")} Exited with code`, child.code)
	end

	return child
end

return CommandBuilder


local result = require("../lib/result")
type Result<T, E> = result.Result<T, E>
local Ok = result.Ok
local Err = result.Err

local function canError(): Result<number, string>
	if math.round(math.random()) == 1 then
		return Err("you DIED")
	end

	return Ok(69)
end

function main()
	local val = canError():unwrap()

	print("got value: ", val)
end

return main()


--[[
	This exists because if the Option module were to have methods to convert
	to Result, and the Result module were to have methods to convert to Option,
	there would be a cyclic dependency.

	So, if a consumer of this library wants to convert between the two, they
	would rather import this conversion module.
]]

local option = require("./option")
local Option = option.Option
export type Option<T> = option.Option<T>
local None = option.None
local Some = option.Some

local result = require("./result")
local Result = result.Result
export type Result<T, E> = result.Result<T, E>
local Ok = result.Ok
local Err = result.Err

--[=[
	@within Result

	Converts from [Result]`<T, E>` to [Option]`<T>`.

	Converts `self` into an [Option]`<T>`, and discarding the error, if any.

	```lua
	local x: Result<number, string> = Ok(2)
	assert(x:ok() == Some(2))

	x = Err("Nothing here")
	assert(x:ok() == None())
	```

	@param self Result<T, E>
	@return Option<T>
]=]
function Result.ok<T, E>(self: Result<T, E>): Option<T>
	if self:isOk() then
		if self._value == nil then
			return None()
		end

		return Some(self._value)
	end

	return None()
end

--[=[
	@within Result

	Converts from [Result]`<T, E>` to [Option]`<E>`.

	Converts `self` into an [Option]`<E>`, and discarding the success value, if any.

	```lua
	local x: Result<number, string> = Ok(2)
	assert(x:ok() == Some(2))

	x = Err("Nothing here")
	assert(x:ok() == None())
	```

	@param self Result<T, E>
	@return Option<E>
]=]
function Result.err<T, E>(self: Result<T, E>): Option<E>
	if self:isErr() then
		return Option.new(self._error) :: Option<E>
	end

	return None()
end

--[=[
	@within Result

	Transposes a [Result] of an [Option] into an [Option] of a [Result].

	[Result:Ok]\([Option:None]\) will be mapped to [Option:None].
	[Result:Ok]\([Option:Some]`(_)`\) and [Result:Err]\(`_`\) will be mapped to
	[Option:Some]\([Result:Ok]`(_)`\) and [Option:Some]\([Option:Err]`(_)`\).

	```lua
	type SomeErr = {}

	local x: Result<Option<number>, SomeErr> = Ok(Some(2))
	local y: Option<Result<number, SomeErr>> = Some(Ok(2))
	assert(x:transpose() == y)
	```

	@param self Result<Option<T>, E>
	@return Option<Result<T, E>>
]=]
function Result.transpose<T, E>(self: Result<Option<T>, E>): Option<Result<T, E>>
	if self._value == None() then
		return None()
	elseif
		self:isOkAnd(function(val): boolean
			return val._optValue == nil
		end)
	then
		return Some(Ok(self._value._optValue))
	elseif self:isErr() then
		return Some(Err(self._error))
	end

	error("`Result` is not transposable")
end

--[=[
	@within Option

	Transforms the [Option]`<T>` into a [Result]`<T, E>`, mapping [Option:Some]`(v)`
	to [Result:Ok]`(v)` and [Option:None] to [Result:Err]`(err)`.

	Arguments passed to [Option:okOr] are eagerly evaluated; if you are passing the result
	of a function call, it is recommended to use [Option:okOrElse], which is lazily evaluated.

	```lua
	local x: Option<string> = Some("foo")
	assert(x:okOr(0) == Ok("foo"))

	x = None()
	assert(x:okOr(0) == Err(0))
	```

	@param self Option<T>
	@param err E
	@return Result<T, E>
]=]
function Option.okOr<T, E>(self: Option<T>, err: E): Result<T, E>
	if self:isSome() then
		return Ok(self._optValue)
	end

	return Err(err)
end

--[=[
	@within Option

	Transforms the [Option]`<T>` into a [Result]`<T, E>`, mapping [Option:Some]`(v)` to
	[Result:Ok]`(v)` and [Option:None] to [Result:Err]`(err())`.

	```lua
	local x: Option<string> = Some("foo")
	assert(x:okOrElse(function() return 0 end) == Ok("foo"))

	x = None()
	assert(x:okOrElse(function() return 0 end) == Err(0))
	```

	@param self Option<T>
	@param err () -> E
	@return Result<T, E>
]=]
function Option.okOrElse<T, E>(self: Option<T>, err: () -> E): Result<T, E>
	if self:isSome() then
		return Ok(self._optValue :: T)
	end

	return Err(err())
end

--[=[
	@within Option

	Transposes a [Option] of an [Result] into an [Result] of a [Option].

	[Option:None] will be mapped to [Result:Ok]\([Option:None]\).
	[Option:Some]\([Result:Ok]`(_)`\) and [Option:Some]\([Result:Err]\(`_`\)\) will
	be mapped to [Result:Ok]\([Option:Some]`(_)`\) and [Result:Err]`(_)`.

	```lua
	type SomeErr = {}

	local x: Result<Option<number>, SomeErr> = Ok(Some(5))
	local y: Option<Result<number, SomeErr>> = Some(Ok(5))
	assert(x == y:transpose())
	```

	@param self Option<Result<T, E>>
	@return Result<Option<T>, E>
]=]
function Option.transpose<T, E>(self: Option<Result<T, E>>): Result<Option<T>, E>
	if self:isSome() then
		local inner = self._optValue
		assert(
			self.typeId == "Option" and inner.typeId == "Result",
			"Only an `Option` of a `Result` can be transposed"
		)

		if inner:isOk() then
			return Some(Ok(inner._value))
		elseif inner:isErr() then
			return Some(Err(inner._error))
		end
	end

	return Ok(None())
end

return {
	Ok = Ok,
	Err = Err,
	Result = Result,

	Some = Some,
	None = None,
	Option = Option,
}


--[=[
	@class Match

	A match expression branches on a pattern.

	Match expressions must be exhaustive, meaning that every possible
	pattern must be matched, i.e., have an arm handling it. The catch all
	arm (`_`), which matches any value, can be used to do this.

	Match expressions also ensure that all of the left-sided arms match the
	same type of the scrutinee expression, and the right-sided arms are all
	of the same type.

	```lua
	local word = match "hello" {
		["hello"] = "hi",
		["bonjour"] = "salut",
		["hola"] = "hola",
		_ = "<unknown>",
	}

	assert(word == "hi")
	```
]=]
local Match = {}

--[=[
	@private
	@interface Arm<L, R>
	@within Match

	Represents an arm (right-side) of a match expression.

	@type type L -- The type of the scrutinee expression or the left side of the arm
	@field result R -- The resolved value of the match expression or the right side of the arm

]=]
type Arm<L, R> = R | (L?) -> R

--[=[
	@private
	@interface Arms<T, U>
	@within Match

	Represents a constructed matcher.

	@type type T -- The type of the scrutinee expression or the left side of the arm
	@field result U -- The resolved value of the match expression or the right side of the arm

]=]
type Arms<T, U> = ({ [T | "_"]: Arm<T, U> }) -> U

--[=[
	@function match
	@within Match

	A match expression branches on a pattern. A match expression has a
	scrutinee expression (the value to match on) and a list of patterns.

	:::note
	Currently, most traditional pattern matching is not supported, with
	the exception of the catch all pattern (`_`).
	:::

	A common use-case of match is to prevent repetitive `if` statements, when
	checking against various possible values of a variable:

	```lua
	local function getGreetingNumber(greeting: string): number
		return match(greeting) {
			["hello, world"] = 1,
			["hello, mom"] = 2,
			_ = function(val)
				return #val
			end,
		}
	end

	assert(getGreetingNumber("hello, world") == 1)
	assert(getGreetingNumber("hello, mom") == 2)
	assert(getGreetingNumber("hello, john") == 11)

	@param value T -- The value to match on
	@return matcher Arms<T, U> -- A matcher function to call with the match arms
	```
]=]
function Match.match<T, U>(value: T): Arms<T, U>
	local function handleArmType(arm: Arm<T, U>, fnArg: T?): U
		if typeof(arm) == "function" then
			return arm(fnArg)
		end

		return arm :: U
	end

	return function(arms)
		for l, r in arms do
			-- Skip the catch all arm for now, get back to it
			-- when we have finished checking for all other
			-- arms
			if l == "_" then
				continue
			end

			if value == l then
				local ret = handleArmType(r, nil)
				return ret
			end
		end

		-- Since we didn't get any matches, we invoke the catch
		-- all arm, giving it the value we have
		local catchAll = arms["_" :: "_"]
			or error(
				`Non exhaustive match pattern, arm not satisfied for: {value}`
			)

		return handleArmType(catchAll, value)
	end
end

return Match.match


local tableEq = require("./util").tableEq

--[=[
	@class Option

	Type [Option] represents an optional value: every [Option] is either [Option:Some]
	and contains a value, or [Option:None], and does not. Common uses of an [Option]
	may involve:

	* Initial values
	* Return values for functions that are not defined over their entire input range (partial functions)
	* Return value for otherwise reporting simple errors, where None is returned on error
	* Optional object fields
	* Values that can be loaned or “taken”
	* Optional function arguments

	```lua
	function divide(numerator: number, denominator: number): Option<number>
		if denominator == 0 then
			None()
		else
			return Some(numerator / denominator)
		end
	end
	```
]=]
local Option = {}

export type Option<T> = typeof(Option) & {
	_optValue: T?,
	typeId: "Option",
}

--[=[
	@within Option

	No value.
]=]
function None<T>(): Option<T>
	return Option.new(nil) :: Option<T>
end

--[=[
	@within Option

	Some value of type `T`.
]=]
function Some<T>(val: T): Option<T>
	return Option.new(val) :: Option<T>
end

--[=[
	@within Option

	Converts a potentially `nil` value into an [Option].

	@param val T? -- The value to convert into an [Option]
	@return Option<T>
]=]
function Option.new<T>(val: T?)
	return setmetatable(
		{
			_optValue = val,
			typeId = "Option",
		} :: Option<T>,
		{
			__index = Option,
			__tostring = function<T>(self: Option<T>)
				if self._optValue == nil then
					return `{self.typeId}::None`
				else
					return `{self.typeId}::Some({self._optValue})`
				end
			end,

			__eq = function<T>(self: Option<T>, other: Option<T>): boolean
				if
					typeof(self._optValue) == "table"
					and typeof(other._optValue) == "table"
				then
					return tableEq(self._optValue, other._optValue)
				else
					return self._optValue == other._optValue
				end
			end,
			__lt = function<T>(self: Option<T>, other: Option<T>): boolean
				if self:isSome() and other:isSome() then
					return (self._optValue :: any) < (other._optValue :: any)
				end

				return false
			end,
			__le = function<T>(self: Option<T>, other: Option<T>): boolean
				if self:isSome() and other:isSome() then
					return (self._optValue :: any) <= (other._optValue :: any)
				end

				return false
			end,
		}

		-- TODO: Implement __iter, once iterators traits exist
	)
end

--[=[
	@within Option

	Returns `true` is the [Option] is an [Option:Some] value.

	@param self Option<T>
	@return boolean
]=]
function Option.isSome<T>(self: Option<T>): boolean
	return self._optValue ~= nil
end

--[=[
	@within Option

	Returns `true` is the [Option] is an [Option:Some] value and the value inside of it
	matches a predicate.

	@param self Option<T>
	@param op (val: T) -> boolean -- The predicate function
	@return boolean
]=]
function Option.isSomeAnd<T>(self: Option<T>, op: (val: T) -> boolean): boolean
	-- We know that it's not None, so this is fine
	return self:isSome() and op(self._optValue :: T)
end

--[=[
	@within Option

	Returns `true` is the [Option] is an [Option:None] value.

	@param self Option<T>
	@return boolean
]=]
function Option.isNone<T>(self: Option<T>): boolean
	return not self:isSome()
end

--[=[
	@within Option

	Returns the [Option:Some].

	@error panic -- Panics if there is an [Option:None] with a custom panic message provided by `msg`.

	```lua
	local x: Option<string> = Some("value")
	assert(x:expect("fruits are healthy") == "value")
	```

	```lua
	local x: Option<string> = None()
	x:expect("fruits are healthy") -- panics with `fruits are healthy`
	```

	@param self Option<T>
	@param msg string -- The panic message
	@return boolean
]=]
function Option.expect<T>(self: Option<T>, msg: string): T | never
	if self:isSome() then
		return self._optValue :: T
	end

	return error(
		`panic: {msg}; expected Option to be type Option::Some(T), got Option::None`
	)
end

--[=[
	@within Option

	Returns the [Option:Some].

	Because this function may panic, its use is generally discouraged. Instead, prefer to use
	[Option:unwrapOr], [Option:unwrapOrElse], or [Option:unwrapOrDefault].

	@error panic -- Panics if the self value is [Option:None].
	@param self Option<T>
	@return T | never
]=]
function Option.unwrap<T>(self: Option<T>): T | never
	if self:isSome() then
		return self._optValue :: T
	end

	return error("called `Option:unwrap()` on a `None` value")
end

--[=[
	@within Option

	Returns the contained [Option:Some] value or a provided default.

	Arguments passed to unwrap_or are eagerly evaluated; if you are passing the result of
	a function call, it is recommended to use [Option:unwrapOrElse], which is lazily
	evaluated.

	```lua
	assert(Some("car"):unwrapOr("bike") == "car")
	assert(None():unwrapOr("bike") == "bike")
	```

	@param self Option<T>
	@param default T -- The default value
	@return T
]=]
function Option.unwrapOr<T>(self: Option<T>, default: T): T
	if self:isSome() then
		return self._optValue :: T
	end

	return default
end

--[=[
	@within Option

	Returns the contained [Option:Some] value or computes it from a function.

	```lua
	local k = 10
	assert(Some(4).unwrapOrElse(function()
		return 2 * k
	end) == 4)
	assert(None().unwrapOrElse(function()
		return 2 * k
	end) == 20)
	```

	@param self Option<T>
	@param default () -> T -- The function which computes the default value
	@return T
]=]
function Option.unwrapOrElse<T>(self: Option<T>, default: () -> T): T
	if self:isSome() then
		return self._optValue :: T
	end

	return default()
end

--[=[
	@within Option

	Maps an [Option]<T> to [Option]<U> by applying a function to a contained value
	(if [Option:Some]) or returns [Option:None](if [Option:None]).

	```lua
	local maybeSomeString: Option<string> = Some("Hello, World!")

	local maybeSomeLen: Option<number> = maybeSomeString:map(function(s)
		return #s
	end)
	assert(maybeSomeLen == 13)

	local x: Option<string> = None()
	assert(x:map(function(s)
		return #s
	end) == None())
	```

	@param self Option<T>
	@param op (x: T) -> U? -- The function to apply
	@return Option<U>
]=]
function Option.map<T, U>(self: Option<T>, op: (x: T) -> U?): Option<U>
	if self:isSome() then
		local val = op(self._optValue :: T)

		if val == nil then
			return None()
		end

		return Some(val)
	end

	return None()
end

--[=[
	@within Option

	Calls the provided closure with the contained value (if [Option:Some]).

	```lua
	local v = { 1, 2, 3, 4, 5 }

	-- prints "got: 4"
	local x: Option<number> = Option.new(v[4]):inspect(function(x)
		print("got: " .. x)
	end)

	-- prints nothing
	local x: Option<number> = Option.new(v[5]):inspect(function(x)
		print("got: " .. x)
	end)
	```

	@param self Option<T>
	@param op (x: T) -> () -- The function to call
	@return Option<T>
]=]
function Option.inspect<T>(self: Option<T>, op: (x: T) -> ()): Option<T>
	if self:isSome() then
		op(self._optValue :: T)
	end

	return self
end

--[=[
	@within Option

	Returns the provided default result (if none), or applies a function to
	the contained value (if any).

	Arguments passed to [Option:mapOr] are eagerly evaluated; if you are passing the
	result of a function call, it is recommended to use [Option:mapOrElse],
	which is lazily evaluated.

	```lua
	local x: Option<string> = Some("foo")
	assert(x.mapOr(42, function(v) return #v end) == 3)

	local x: Option<string> = None()
	assert(x.mapOr(42, function(v) return #v end) == 42)
	```
	@param self Option<T>
	@param default U -- The default value
	@param op (val: T) -> U -- The function to apply
	@return Option<T>
]=]
function Option.mapOr<T, U>(self: Option<T>, default: U, op: (val: T) -> U)
	if self:isSome() then
		return op(self._optValue :: T)
	end

	return default
end

--[=[
	@within Option

	Computes a default function result (if none), or applies a different function
	to the contained value (if any).

	```lua
	local k = 21;

	local x: Option<string> = Some("foo")
	assert(
		x:mapOrElse(
			function() return 2 * k end,
			function(v) return #v end
		) == 3
	)

	local x: Option<string> = None()
	assert(
		x:mapOrElse(
			function() return 2 * k end,
			function(v) return #v end
		) == 42
	)
	```

	@param self Option<T>
	@param default () -> U -- The function to compute the default value
	@param op (val: T) -> U -- The function to apply
	@return Option<T>
]=]
function Option.mapOrElse<T, U>(
	self: Option<T>,
	default: () -> U,
	op: (val: T) -> U
): U
	if self:isSome() then
		return op(self._optValue :: T)
	end

	return default()
end

-- TODO: Iterator traits
function Option.iter(): never
	return error("Unimplemented: `Option:iter()`")
end

--[=[
	@within Option

	Returns [Option:None] if the option is [Option:None], otherwise returns `optb`.

	Arguments passed to and are eagerly evaluated; if you are passing the result of a
	function call, it is recommended to use [Option:andThen], which is lazily evaluated.

	```lua
	local x: Option<number> = Some(2)
	local y: Option<String> = None()
	assert(x:and_(y) == None())

	local x: Option<number> = None()
	local y: Option<string> = Some("foo")
	assert(x:and_(y) == None())

	local x: Option<number> = Some(2)
	local y: Option<string> = Some("foo")
	assert(x:and_(y) == Some("foo"))

	local x: Option<u32> = None()
	local y: Option<string> = None()
	assert(x:and_(y), None())
	```

	@param self Option<T>
	@param optb Option<U> -- The other option
	@return Option<U>
]=]
function Option.and_<T, U>(self: Option<T>, optb: Option<U>): Option<U>
	if self:isSome() then
		return optb
	end

	return None()
end

--[=[
	@within Option

	Returns [Option:None] if the option is [Option:None], otherwise calls `op` with the wrapped
	value and returns the result.

	Some languages call this operation flatmap.

	```lua
	function sqThenToString(x: number): Option<string>
		return Option.new(x ^ 2):map(function(sq)
			return tostring(sq)
		end)
	end

	assert(Some(2):andThen(sqThenToString) == Some(tostring(4)))
	assert(None():andThen(sqThenToString) == None())
	```

	Often used to chain fallible operations that may return [Option:None].

	```lua
	local arr2d = { { "A0", "A1" }, { "B0", "B1" } }

	local item01: Option<string> = Option.new(arr2d[1]):andThen(function(row)
		return row[2]
	end)
	assert(item01 == Some("A1"))

	local item20: Option<string> = Option.new(arr2d[3]):andThen(function(row)
		return row[0]
	end)
	assert(item20 == None())
	```

	@param self Option<T>
	@param op (val: T) -> Option<U> -- The function to call
	@return Option<U>
]=]
function Option.andThen<T, U>(self: Option<T>, op: (val: T) -> Option<U>): Option<U>
	if self:isSome() then
		return op(self._optValue :: T)
	end

	return None()
end

--[=[
	@within Option

	Returns [Option:None] if the option is [Option:None], otherwise calls `predicate` with
	the wrapped value and returns:

	* [Option:Some](t) if predicate returns `true` (where `t` is the wrapped value), and
	* [Option:None] if predicate returns `false`.

	```lua
	function isEven(n: number): boolean
		return n % 2 == 0
	end

	assert(None():filter(isEven) == None())
	assert(Some(3):filter(isEven) == None())
	assert(Some(4):filter(isEven) == Some(4))
	```

	@param self Option<T>
	@param predicate (val: T) -> boolean -- The predicate function which must match an element
	@return Option<T>
]=]
function Option.filter<T>(
	self: Option<T>,
	predicate: (val: T) -> boolean
): Option<T>
	if self:isSome() then
		if predicate(self._optValue :: T) then
			return self
		end
	end

	return None()
end

--[=[
	@within Option

	Returns the option if it contains a value, otherwise returns `optb`.

	Arguments passed to [Option:or_] are eagerly evaluated; if you are passing the result of
	a function call, it is recommended to use [Option:orElse], which is lazily
	evaluated.

	```lua
	local x: Option<number> = Some(2)
	local y: Option<number> = None()
	assert(x:or_(y) == Some(2))

	local x: Option<number> = None()
	local y: Option<number> = Some(100)
	assert(x:or_(y) == Some(100))

	local x: Option<number> = Some(2)
	local y: Option<number> = Some(100)
	assert(x:or_(y) == Some(2))

	local x: Option<number> = None()
	local y: Option<number> = None()
	assert(x:or_(y), None())
	```

	@param self Option<T>
	@param optb Option<T> -- The other option
	@return Option<T>
]=]
function Option.or_<T>(self: Option<T>, optb: Option<T>): Option<T>
	if self:isSome() then
		return self
	end

	return optb
end

--[=[
	@within Option

	Returns the option if it contains a value, otherwise calls `op` and returns the result.

	```lua
	function nobody(): Option<string>
		return None()
	end
	function vikings(): Option<string>
		return Some("vikings")
	end

	assert(Some("barbarians"):orElse(vikings) == Some("barbarians"))
	assert(None():orElse(vikings) == Some("vikings"))
	assert(None():orElse(nobody) == None())
	```

	@param self Option<T>
	@param op () -> Option<T> -- The function to call
	@return Option<T>
]=]
function Option.orElse<T>(self: Option<T>, op: () -> Option<T>): Option<T>
	if self:isSome() then
		return self
	end

	return op()
end

--[=[
	@within Option

	Returns [Option:Some] if exactly one of `self`, `optb` is [Option:Some],
	otherwise returns [Option:None].

	```lua
	local x: Option<number> = Some(2)
	local y: Option<number> = None()
	assert(x:xor(y) == Some(2))

	local x: Option<number> = None()
	local y: Option<number> = Some(2)
	assert(x:xor(y) == Some(2))

	local x: Option<number> = Some(2)
	local y: Option<number> = Some(2)
	assert(x:xor(y) == None())

	local x: Option<number> = None()
	local y: Option<number> = None()
	assert(x:xor(y) == None())
	```

	@param self Option<T>
	@param optb Option<T> -- The other option
	@return Option<T>
]=]
function Option.xor<T>(self: Option<T>, optb: Option<T>): Option<T>
	if self:isSome() and optb:isSome() then
		return None()
	end

	if self:isSome() then
		return self
	elseif optb:isSome() then
		return optb
	end

	return None()
end

--[=[
	@within Option

	Inserts value into the option, then returns it.

	If the option already contains a value, the old value is dropped.

	See also [Option:getOrInsert], which doesn’t update the value if the
	option already contains [Option:Some].

	```lua
	local opt: Option<number> = None()
	local val: number = opt:insert(1)
	assert(val == 1)
	assert(opt:unwrap() == 1)

	local val: number = opt:insert(2)
	assert(val == 2)
	```

	@param self Option<T>
	@param val T -- The value to insert
	@return T
]=]
function Option.insert<T>(self: Option<T>, val: T): T
	self._optValue = val
	return self._optValue :: T
end

--[=[
	@within Option

	Inserts value into the option, then returns it.

	If the option already contains a value, the old value is dropped.

	See also [Option:insert], which doesn’t update the value even if the
	option already contains [Option:Some].

	```lua
	local opt: Option<number> = None()
	local val: number = opt:insert(1)
	assert(val == 1)
	assert(opt:unwrap() == 1)

	local val: number = opt:insert(2)
	assert(val == 2)
	```

	@param self Option<T>
	@param val T -- The value to insert
	@return T
]=]
function Option.getOrInsert<T>(self: Option<T>, val: T): T
	if self:isNone() then
		self._optValue = val
	end

	return self._optValue :: T
end

--[=[
	@within Option

	Takes the value out of the option, leaving an [Option:None] in its place.

	```lua
	local x: Option<number> = Some(2)
	local y: Option<number> = x.take()
	assert(x == None())
	assert(y == Some(2))

	local x: Option<number> = None()
	local y: Option<number> = x.take()
	assert(x == None())
	assert(y == None())
	```

	@param self Option<T>
	@return Option<T>
]=]
function Option.take<T>(self: Option<T>): Option<T>
	if self:isSome() then
		local val = self._optValue :: T
		self._optValue = nil
		return Some(val)
	end

	return None()
end

--[=[
	@within Option

	Replaces the actual value in the option by the value given in parameter, returning
	the old value if present, leaving an [Option:Some] in its place without
	deinitializing either one.

	```lua
	local x: Option<number> = Some(2)
	local old: Option<number> = x:replace(5)
	assert(x == Some(5))
	assert(old == Some(2))

	local x: Option<number> = None()
	local old: Option<number> = x:replace(3)
	assert(x == Some(3))
	assert(old == None())
	```

	@param self Option<T>
	@param val T
	@return Option<T>
]=]
function Option.replace<T>(self: Option<T>, val: T): Option<T>
	local current: Option<T> = self
	self._optValue = val

	if current:isNone() then
		return current
	end

	return Some(current._optValue :: T)
end

--[=[
	@within Option

	Returns true if `val` is contained in the [Option:Some].

	```lua
	local x: Option<number> = Some(2)
	local y: Option<number> = None()

	assert(x:contains(2))
	assert(x:contains(4))
	assert(not y:contains(2))
	```

	@param self Option<T>
	@param val T
	@return boolean
]=]
function Option.contains<T>(self: Option<T>, val: T): boolean
	if self:isSome() then
		return self._optValue == val
	end

	return false
end

--[=[
	@within Option

	Zips `self` with another [Option].

	If `self` is [Option:Some](s) and other is [Option:Some](o), this method returns
	[Option:Some]({s, o}). Otherwise, [Option:None] is returned.

	```lua
	local x: Option<number> = Some(1)
	local y: Option<string> = Some("hi")
	local z: Option<number> = None()

	assert(x:zip(y) == Some({ 1, "hi" }))
	assert(x:zip(z) == None())
	```

	@param self Option<T>
	@param other Option>U>
	@return Option<{T | U}>
]=]
function Option.zip<T, U>(self: Option<T>, other: Option<U>): Option<{ T | U }>
	if self:isSome() and other:isSome() then
		return Some { self._optValue, other._optValue }
	end

	return None()
end

--[=[
	@within Option

	Zips `self` and another [Option] with function `op`.

	If `self` is [Option:Some](s) and other is [Option:Some](o), this method returns
	[Option:Some](op(s, o)). Otherwise, [Option:None] is returned.

	```lua
	type Point = {
		x: number,
		y: number,
	}
	local Point: Point & {
		new: (x: number, y: number) -> Point,
	} = {}

	function Point.new(x: number, y: number): Point
		return {
			x = x,
			y = y,
		}
	end

	local xCoord: Option<number> = Some(17.5)
	local yCoord: Option<number> = Some(42.7)

	assert(xCoord:zipWith(yCoord, Point.new), Some({ x = 17.5, y = 42.7 }))
	assert(x:zipWith(None(), Point.new), None())
	```

	@param self Option<T>
	@param other Option>U>
	@param op (x: T, y: U) -> R?
	@return Option<R>
]=]
function Option.zipWith<T, U, R>(
	self: Option<T>,
	other: Option<U>,
	op: (x: T, y: U) -> R?
): Option<R>
	if self:isSome() and other:isSome() then
		local computed = op(self._optValue :: T, other._optValue :: U)

		if computed ~= nil then
			return Some(computed :: R)
		end
	end

	return None()
end

--[=[
	@within Option

	Unzips an option containing a table of two options.

	If `self` is `Some({a, b})` this method returns `(Some(a), Some(b))`.
	Otherwise, `(None(), None())` is returned.

	```lua
	local x: Option<{ number | string }> = Some({ 1, "hi" })
	local y: Option<{ number }> = None()

	assert((x:unzip() == Some(1), Some("hi")))
	assert((y:unzip() == None(), None()))
	```

	@param self Option<T>
	@return (Option<A>, Option<B>)
]=]
function Option.unzip<T, A, B>(self: Option<T>): (Option<A>, Option<B>)
	if self:isSome() then
		if
			self:isSome()
			and typeof(self._optValue) == "table"
			and #self._optValue == 2
		then
			return Some(self._optValue[1] :: A), Some(self._optValue[2] :: B)
		end
	end

	return None(), None()
end

--[=[
	@within Option

	Returns the inner value wrapped by the [Option].

	```lua
	local x: Option<string> = Some("lol")
	local y: Option<string> = None()

	assert(x:unwrapUnchecked() == "lol")
	assert(y:unwrapUnchecked() == nil)
	```

	@param self Option<T>
	@return T?
]=]
function Option.unwrapUnchecked<T>(self: Option<T>): T?
	return self._optValue
end

--[=[
	@within Option

	Returns a formatted representation of the option, often
	used for printing to stdout.

	```lua
	local x: Option<number> = Some(123)
	local y: Option<number> = None()

	print(x:display()) -- prints `Option::Some(123)`
	print(y:display()) -- prints `Option::None`
	```

	@param self Option<T>
	@return string
]=]
function Option.display<T>(self: Option<T>): string
	return tostring(self)
end

return {
	Option = Option,
	Some = Some,
	None = None,
}


local tableEq = require("./util").tableEq

--[=[
	@class Result

	Error handling with the [Result] type.

	[Result]`<T, E>` is the type used for returning a possible error. It is a class with the
	constructor variants, [Result:Ok]`(T)`, representing success and containing a value, and
	[Result:Err]`(E)`, representing error and containing an error value.

	```lua
	local result: Result<number, string>

	-- Both variants `Ok` and `Err` fit into the type
	result = Ok(21)
	result = Err("some error")
	```

	Functions return [Result] whenever errors are expected and recoverable.

	A simple function returning [Result] might be defined and used like so:
	```lua
	local PAT = "v(%d+)"

	function parseVersion(str: string): Result<number, string>
		local version: number = string.match(str, PAT)

		if version == 0 then
			return Err("Version cannot be zero")
		else if version == nil then
			return Err("Invalid string")
		end

		return Ok(version)
	end

	print("Successful result: " .. parseVersion("v12"):display())
	print("Error result: " .. parseVersion("vString"):display())
	```

	[Result] comes with some convenience methods that make working with it more succinct.

	```lua
	local goodResult: Result<number, number> = Ok(10)
	local badResult: Result<number, number> = Err(10)

	-- The `isOk`and `isErr` methods do what they say.
	assert(goodResult:isOk() and not goodResult:isErr())
	assert(badResult:isErr() and not badResult:isOk())

	-- `map` produces a new result from an existing one
	goodResult = goodResult:map(function(i) return i + 1 end)
	badResult = badResult:map(function(i) return i - 1 end)

	-- Use `and_then` to continue the computation.
	local anotherGoodResult: Result<boolean, number> = goodResult:andThen(function(i) return Ok(i == 11) end)

	-- Use `or_else` to handle the error
	local badResult: Result<number, number> = bad_result:orElse(function(i) return Ok(i + 20) end)

	-- Get the internal `Ok` value and panic if there is an `Err`
	local finalAwesomeResult: boolean = anotherGoodResult:unwrap()
	```
]=]
local Result = {}

export type Result<T, E> = typeof(Result) & {
	_value: T,
	_error: E,
	typeId: "Result",
}

--[=[
	@within Result

	Contains the success value.

	@param val T
	@return Result<T, E>
]=]
function Ok<T, E>(val: T): Result<T, E>
	return Result.new(val, (nil :: unknown) :: E)
end

--[=[
	@within Result

	Contains the error value.

	@param err E
	@return Result<T, E>
]=]
function Err<T, E>(err: E): Result<T, E>
	return Result.new((nil :: unknown) :: T, err)
end

--[=[
	@within Result
	@private

	Constructs a [Result]. **For internal use only**.

	For user consumption, see [Result:Ok] and [Result:Err].

	@param err E
	@return Result<T, E>
]=]
function Result.new<T, E>(val: T, err: E)
	return setmetatable(
		{
			_value = val,
			_error = err,
			typeId = "Result",
		} :: Result<T, E>,
		{
			__index = Result,
			__tostring = function<T, E>(self: Result<T, E>)
				if self:isOk() then
					return `{self.typeId}::Ok({self._value})`
				end

				if self:isErr() then
					return `{self.typeId}::Err({self._error})`
				end

				return `{self.typeId}<T, E>`
			end,
			__eq = function<T, E>(self: Result<T, E>, other: Result<T, E>)
				if
					typeof(self._value) ~= "table"
					and typeof(self._error) ~= "table"
					and typeof(other._value) ~= "table"
					and typeof(other._error) ~= "table"
				then
					return self._value == other._value
						and self._error == other._error
				end

				return tableEq(self._value, other._value)
					and tableEq(self._error, other._error)
			end,

			-- TODO: Implement equality and arithmetic metamethods
			-- TODO: Implement __iter, once iterators traits exist
		}
	)
end

--[=[
	@within Result

	Returns `true` if the result is [Result:Ok].

	```lua
	local x: Result<number, string> = Ok(3)
	assert(x:isOk())

	x = Err("Some error message")
	assert(not x:isOk())
	```

	@param self Result<T, E>
	@return boolean
]=]
function Result.isOk<T, E>(self: Result<T, E>): boolean
	if self._value == nil then
		return false
	end

	return true
end

--[=[
	@within Result

	Returns `true` if the result is [Result:Ok] and the value inside it matches a
	predicate.

	```lua
	local x: Result<number, string> = Ok(2)
	assert(x:isOkAnd(function(x) return x > 1 end))

	x = Ok(0)
	assert(not x:isOkAnd(function(x) return x > 1 end))

	x = Err("hey")
	assert(not x:isOkAnd(function(x) return x > 1 end))
	```

	@param self Result<T, E>
	@param predicate (val: T?) -> boolean
	@return boolean
]=]
function Result.isOkAnd<T, E>(
	self: Result<T, E>,
	predicate: (val: T?) -> boolean
): boolean
	return self:isOk() and predicate(self._value)
end

--[=[
	@within Result

	Returns `true` if the result is [Result:Err].

	```lua
	local x: Result<number, string> = Ok(3)
	assert(not x:isErr())

	x = Err("Some error message")
	assert(x:isErr())
	```

	@param self Result<T, E>
	@return boolean
]=]
function Result.isErr<T, E>(self: Result<T, E>)
	return not self:isOk()
end

--[=[
	@within Result

	Returns `true` if the result is [Result:Err] and the value inside it matches a
	predicate.

	```lua
	local x: Result<number, string> = Ok(2)
	assert(not x:isErrAnd(function(x) return x > 1 end))

	x = Err(3)
	assert(x:isErrAnd(function(x) return x > 1 end))

	x = Err("hey")
	assert(not x:isErrAnd(function(x) return x > 1 end))
	```

	@param self Result<T, E>
	@param predicate (val: T?) -> boolean
	@return boolean
]=]
function Result.isErrAnd<T, E>(
	self: Result<T, E>,
	predicate: (val: E) -> boolean
): boolean
	if self:isErr() then
		return predicate(self._error)
	end

	return false
end

--[=[
	@within Result

	Maps a [Result]<T, E> to [Result]<U, E> by applying a function to a contained [Result:Ok] value,
	leaving an [Result:Err] value untouched.

	This function can be used to compose the results of two functions.

	```lua
	local lines = "1\n2\n3\n4\n"

	function parseInt(x: string): Result<number, string>
		local num = tonumber(x)

		if num == nil then
			return Err("not an integer")
		end

		return Ok(num)
	end

	for line in lines:split("\n") do
		local number = parseInt(line)

		if number:isOk() then
			print(number:unwrap())
		else
			print("not a number!")
		end
	end
	```

	@param self Result<T, E>
	@param op (val: T) -> U
	@return Result<U, E>
]=]
function Result.map<T, E, U>(self: Result<T, E>, op: (val: T) -> U): Result<U, E>
	if self:isOk() then
		return Result.new(op(self._value), self._error) :: Result<U, E>
	end

	return Err(self._error)
end

--[=[
	@within Result

	Returns the provided default (if [Result:Err]), or applies a function to
	the contained value (if [Result:Ok]).

	Arguments passed to [Result:mapOr] are eagerly evaluated; if you are passing the
	result of a function call, it is recommended to use [Result:mapOrElse], which is
	lazily evaluated.

	```lua
	local x: Result<string, string> = Ok("foo")
	assert(x:mapOr("bar", string.upper) == "FOO")

	x = Err("foo")
	assert(x:mapOr("bar", string.upper) == "bar")
	```

	@param self Result<T, E>
	@param default U
	@param op (val: T) -> U
	@return U
]=]
function Result.mapOr<T, E, U>(self: Result<T, E>, default: U, op: (val: T) -> U): U
	if self:isOk() then
		return op(self._value)
	end

	return default
end

--[=[
	@within Result

	Maps a [Result]<T, E> to U by applying fallback function default to a contained
	[Result:Err] value, or function f to a contained [Result:Ok] value.

	This function can be used to unpack a successful result while handling an error.

	```lua
	local x: Result<string, string> = Ok("foo")
	assert(x:mapOrElse(function(x) return "error: ".. x end, string.upper) == "FOO")

	x = Err("foo")
	assert(x:mapOrElse(function(x) return "error: ".. x end, string.upper) == "error: foo")
	```

	@param self Result<T, E>
	@param default (val: E) -> U
	@param op (val: T) -> U
	@return U
]=]
function Result.mapOrElse<T, E, U>(
	self: Result<T, E>,
	default: (val: E) -> U,
	op: (val: T) -> U
): U
	if self:isOk() then
		return op(self._value)
	end

	return default(self._error)
end

--[=[
	@within Result

	Maps a [Result]<T, E> to [Result]<T, F> by applying a function to a contained
	[Result:Err] value, leaving an [Result:Ok] value untouched.

	This function can be used to pass through a successful result while handling an
	error.

	```lua
	local function stringify(x: number): string
		return string.format("error code: %d", x)
	end

	local x: Result<number, number> = Ok(2)
	assert(x:mapErr(stringify) == Ok(2))

	x = Err(13)
	assert(x:mapErr(stringify) == Err("error code: 13"))
	```

	@param self Result<T, E>
	@param op (val: E) -> F
	@return Result<T, F>
]=]
function Result.mapErr<T, E, F>(self: Result<T, E>, op: (val: E) -> F): Result<T, F>
	if self:isErr() then
		return Result.new(self._value, op(self._error))
	end

	return Ok(self._value)
end

--[=[
	@within Result

	Calls the provided closure with a reference to the contained value
	(if [Result:Ok]).

	```lua
	function parseInt(x: string): Result<number, string>
		local num = tonumber(x)

		if num == nil then
			return Err("not an integer")
		end

		return Ok(num)
	end

	local x = parseInt("4")
		:inspect(function(x) print("original: " .. x) end)
		:map(function(x) return x ^ 3 end)
		:expect("not an integer")

	```

	@param self Result<T, E>
	@param op (val: E) -> ()
	@return Result<T, E>
]=]
function Result.inspect<T, E>(self: Result<T, E>, op: (val: T) -> ()): Result<T, E>
	if self:isOk() then
		op(self._value)
	end

	return self
end

--[=[
	@within Result

	Calls the provided closure with a reference to the contained value
	(if [Result:Err]).

	```lua
	function parseInt(x: string): Result<number, string>
		local num = tonumber(x)

		if num == nil then
			return Err("not an integer")
		end

		return Ok(num)
	end

	local x = parseInt("string")
		:inspectErr(function(x) print("error: " .. x) end)
		:map(function(x) return x ^ 3 end)
		:unwrap()

	```

	@param self Result<T, E>
	@param op (val: E) -> ()
	@return Result<T, E>
]=]
function Result.inspectErr<T, E>(self: Result<T, E>, op: (val: E) -> ()): Result<T, E>
	if self:isErr() then
		op(self._error)
	end

	return self
end

-- TODO: Iterator traits
-- selene: allow(unused_variable)
function Result.iter<T, E>(self: Result<T, E>): never
	return error("Unimplemented: `Result:iter()`")
end

--[=[
	@within Result

	Returns the contained [Result:Ok] value, consuming the self value.

	Because this function may panic, its use is generally discouraged. Instead,
	prefer to use [Result:isErr] and handle the Err case explicitly, or call
	[Result:unwrapOr], [Result:unwrapOrElse], or [Result:unwrapOrDefault].
	Panics

	@error panic -- If the value is a [Result:Err], with a panic message including the passed message, and the content of the `Err`.

	```lua
	local x: Result<number, string> = Err("emergency failure")
	x:expect("Testing expect") -- panics with message `Testing expect: emergency failure`
	```

	## Recommended Message Style
	It is recommended that expect messages are used to describe the reason you expect the
	[Result] should be [Result:Ok].

	```lua
	local process = require("@lune/process")

	local function envVar<T>(var: string): Result<T, string>
		local val = process.env[var]

		if val == nil then
			return Err("environment variable not found")
		end

		Ok(val)
	end

	local path = envVar("IMPORTANT_PATH")
		:expect("env variable `IMPORTANT_PATH` should be set by `wrapper_script.sh`")
	```

	**Hint**: If you’re having trouble remembering how to phrase expect error messages remember to focus
	on the word “should” as in “env variable should be set by blah” or “the given binary should be available
	and executable by the current user”.

	@param self Result<T, E>
	@param msg string
	@return T | never
]=]
function Result.expect<T, E>(self: Result<T, E>, msg: string): T | never
	if self:isOk() then
		return self._value
	end

	return error(`panic: {msg}; {self._error}`)
end

--[=[
	@within Result

	Returns the [Result:Ok].

	Because this function may panic, its use is generally discouraged. Instead, prefer to use
	[Result:unwrapOr], [Result:unwrapOrElse], or [Result:unwrapOrDefault].

	@error panic -- Panics if the value is an [Result:Err], with a panic message provided by the [Result:Err]’s value.

	```lua
	local x: Result<number, string> = Ok(2)
	assert(x:unwrap() == 2)

	x = Err("oh no")
	x:unwrap() -- panics with `oh no`
	```

	@param self Result<T, E>
]=]
function Result.unwrap<T, E>(self: Result<T, E>): T | never
	if self:isOk() then
		return self._value
	end

	return error(
		`panic: \`Result:unwrap()\` called on an \`Err\` value: {self._error}`
	)
end

-- TODO: default values for types
-- selene: allow(unused_variable)
function Result.unwrapOrDefault<T, E>(self: Result<T, E>): never
	return error("Unimplemented: `Result:unwrapOrDefault()`")
end

--[=[
	@within Result

	Returns the contained [Result:Err].

	@error panic -- Panics if the value is an [Result:Ok], with a panic message including the passed message, and the content of the [Resul:Ok].

	```lua
	local x: Result<number, string> = Ok(10)
	x:expectErr("Testing expect") -- panics with `Testing expect: 10`
	```

	@param self Result<T, E>
	@param msg string
	@return E | never
]=]
function Result.expectErr<T, E>(self: Result<T, E>, msg: string): E | never
	if self:isErr() then
		return self._error
	end

	return error(`panic: {msg}; {self._error}`)
end

--[=[
	@within Result

	Returns the contained [Result:Err].

	@error panic -- Panics if the value is an [Result:Ok], with a panic message provided by the [Result:Ok]’s value.

	```lua
	local x: Result<number, string> = Ok(2)
	x:unwrapErr() -- panics with `2`
	```
	```lua
	x = Err("oh no")
	assert(x:unwrapErr() == "oh no")
	```

	@param self Result<T, E>
	@return E | never
]=]
function Result.unwrapErr<T, E>(self: Result<T, E>): E | never
	if self:isErr() then
		return self._error
	end

	return error(
		`panic: \`Result:unwrapErr()\` called on an \`Ok\` value: {self._value}`
	)
end

-- TODO: How the fuck do I implement this?
-- selene: allow(unused_variable)
function Result.intoOk<T, E>(self: Result<T, E>): never
	return error("Unimplemented: `Result:intoOk()`")
end
-- selene: allow(unused_variable)
function Result.intoErr<T, E>(self: Result<T, E>): never
	return error("Unimplemented: `Result:intoErr()`")
end

--[=[
	@within Result

	Returns res if the result is [Result:Ok], otherwise returns the
	[Result:Err] value of self.

	```lua
	local x: Result<number, string> = Ok(2)
	local y: Result<string, string> = Err("late error")
	assert(x:and_(y) == Err("late error"))

	local x: Result<number, string> = Err("early error")
	local y: Result<string, string> = Ok("foo")
	assert(x:and_(y) == Err("early error"))

	local x: Result<number, string> = Err("not a 2")
	local y: Result<string, string> = Err("late error")
	assert(x:and_(y) == Err("not a 2"))

	local x: Result<number, string> = Ok(2)
	local y: Result<string, string> = Ok("different result type")
	assert(x:and_(y) == Ok("different result type"))
	```

	@param self Result<T, E>
	@param res Result<U, E>
	@return Result<U, E>
]=]
function Result.and_<T, E, U>(self: Result<T, E>, res: Result<U, E>): Result<U, E>
	if self:isOk() then
		return res
	end

	return Err(self._error)
end

--[=[
	@within Result

	Calls `op` if the result is [Result:Ok], otherwise returns the
	[Result:Err] value of `self`.

	This function can be used for control flow based on [Result]
	values.

	```lua
	function sqThenToString(x): Result<string, string>
		if typeof(sq) ~= "number" then
			return Err("not a number: '" .. x .. "'")
		end

		return Ok(x ^ 2):map(function(sq)
			return tostring(sq)
		end)
	end

	assert(Ok(2):andThen(sqThenToString) == Ok("4"))
	assert(Err("string"):andThen(sqThenToString) == Err("not a number: 'string'"))
	```

	Often used to chain fallible operations that may return [Result:Err].

	@param self Result<T, E>
	@param op (...any) -> any
	@return Result<U, E>
]=]
function Result.andThen<T, E, U>(self: Result<T, E>, op: (...any) -> any): Result<U, E>
	if self:isOk() then
		return op(self._value)
	end

	return Err(self._error) :: Result<U, E>
end

--[=[
	@within Result

	Calls `op` if the result is [Result:Ok], otherwise returns the
	[Result:Err] value of `self`.

	This function can be used for control flow based on [Result]
	values.

	```lua
	local x: Result<number, string> = Ok(2)
	local y: Result<number, string> = Err("late error")
	assert(x:or_(y) == Ok(2))

	local x: Result<number, string> = Err("early error")
	local y: Result<number, string> = Ok(2)
	assert(x:or_(y) == Ok(2))

	local x: Result<number, string> = Err("not a 2")
	local y: Result<number, string> = Err("late error")
	assert(x:or_(y) == Err("late error"))

	local x: Result<number, string> = Ok(2)
	local y: Result<number, string> = Ok(100)
	assert(x:or_(y) == Ok(2))
	```

	@param self Result<T, E>
	@param res Result<T, F>
	@return Result<T, F> | never
]=]
function Result.or_<T, E, F>(self: Result<T, E>, res: Result<T, F>): Result<T, F> | never
	if self:isErr() then
		return res
	end

	if self:isOk() then
		return Ok(self._value) :: Result<T, F>
	end

	return error("called `Result:or()` with an invalid value")
end

--[=[
	@within Result

	Calls `op` if the result is [Result:Ok], otherwise returns the
	[Result:Err] value of `self`.

	This function can be used for control flow based on [Result]
	values.

	```lua
	function sq(x: number): Result<number, number>
		return Ok(x * x)
	end

	function err(x: number): Result<number, number>
		return Err(x)
	end

	assert(Ok(2):orElse(sq):orElse(sq) == Ok(2))
	assert(Ok(2):orElse(err):orElse(sq) == Ok(2))
	assert(Err(3):orElse(sq):orElse(err) == Ok(9))
	assert(Err(3):orElse(err):orElse(err) == Err(3))
	```

	@param self Result<T, E>
	@param op (x: E) -> Result<T, U>
	@return Result<T, U>
]=]
function Result.orElse<T, E, U>(
	self: Result<T, E>,
	op: (x: E) -> Result<T, U>
): Result<T, U>
	if self:isErr() then
		return op(self._error)
	end

	return Ok(self._value)
end

--[=[
	@within Result

	Returns the contained [Result:Ok] value or a provided default.

	Arguments passed to [Result:unwrapOr] are eagerly evaluated; if you are passing the
	result of a function call, it is recommended to use [Result:unwrapOrElse], which is
	lazily evaluated.

	```lua
	local default = 2
	local x: Result<number, string> = Ok(9)
	assert(x:unwrapOr(default), 9)

	x = Err("error")
	assert(x:unwrapOr(default), default)
	```

	@param self Result<T, E>
	@param default T
	@return T
]=]
function Result.unwrapOr<T, E>(self: Result<T, E>, default: T): T
	if self:isOk() then
		return self._value
	end

	return default
end

--[=[
	@within Result

	Returns the contained [Result:Ok] value or computes it from a closure.

	```lua
	function count(x: string): number
		return #x
	end

	assert(Ok(2):unwrapOrElse(count) == 2))
	assert(Err("foo"):unwrapOrElse(count) == 3))
	```

	@param self Result<T, E>
	@param op (x: E) -> T
	@return T
]=]
function Result.unwrapOrElse<T, E>(self: Result<T, E>, op: (x: E) -> T): T
	if self:isOk() then
		return self._value
	end

	return op(self._error)
end

--[=[
	@within Result

	Returns true if the [Result:Ok] value is `val`.

	```lua
	local x: Result<number, string> = Ok(2)
	assert(x:contains(2))

	x = Ok(3)
	assert(not x:contains(2))

	x = Err(2)
	assert(not x:contains(2))
	```

	@param self Result<T, E>
	@param val T
	@return boolean
]=]
function Result.contains<T, E>(self: Result<T, E>, val: T): boolean
	if self:isOk() then
		return self._value == val
	end

	return false
end

--[=[
	@within Result

	Returns true if the [Result:Err] value is `err`.

	```lua
	local x: Result<number, string> = Err(2)
	assert(x:containsErr(2))

	x = Err(3)
	assert(not x:containsErr(2))

	x = Ok(2)
	assert(not x:containsErr(2))
	```

	@param self Result<T, E>
	@param err E
	@return boolean
]=]
function Result.containsErr<T, E>(self: Result<T, E>, err: E): boolean
	if self:isErr() then
		return self._error == err
	end

	return false
end

--[=[
	@within Result

	Returns a formatted representation of the result, often
	used for printing to stdout.

	```lua
	local x: Result<number, string> = Ok(123)
	local y: Result<number, string> = Err("error")

	print(x:display()) -- prints `Result::Ok(123)`
	print(y:display()) -- prints `Result::Err("error")`
	```

	@param self Result<T, E>
	@return string
]=]
function Result.display<T, E>(self: Result<T, E>): string
	return tostring(self)
end

return {
	Ok = Ok,
	Err = Err,
	Result = Result,
}


local loaded_config = {
  user = {
    cursor = {
      -- BLOCK, LINE
      style = "LINE"
    },
    tab_spacing = 4,
  },
}

-- replace loaded_config with the file read from disk,
-- if not found, create new file with the config above

local config = {}
local metatable = {
  __index = function (t,k)
    return loaded_config[k]
  end,

  __newindex = function (t,k,v)
    loaded_config[k] = v
  end
}
setmetatable(config, metatable)

return config


local util = require("util/main")
local kc = require("util/keycodes")
local config = require("config")

local font = Renderer.Font.load("./resources/JetBrainsMono-Regular.ttf")
local font2 = Renderer.Font.load("./resources/Raveo-Regular.otf")

local buf = File.read("./editor/core/init.luau"):gsub("\r", "")

local font_size = 20

local lines = buf:split("\n")
local cursor = {
    touch_time = 0,
    touched = (function(self)
        return ((os.clock() - self.touch_time) <= 0.25)
    end),

    y = 1, --line
    x = 3, --col
    go_to = (function(self, x, y)
        if x ~= nil then
            self.x = x
            self:clamp_x(x)
        end
        if y ~= nil then
            self.y = y
            self:clamp_y()
        end
    end),
    clamp_x = (function(self, n)
        self.x = math.clamp(self.x, 1, n+1)
    end),
    clamp_y = (function(self)
        self.y = math.clamp(self.y, 1, table.maxn(lines))
    end),
    touch = (function(self)
        self.touch_time = os.clock()
    end),

    write = (function(self, char)
        self:touch()
        local line = lines[self.y]
        lines[self.y] = line:sub(1, self.x-1) .. if typeof(char) == "number" then string.char(char) else char .. line:sub(self.x, -1)
        self.x += 1
        self:clamp_x(string.len(lines[self.y]))
    end),
    erase = (function(self)
        self:touch()
        if self.x == 1 then
            return false
        end
        local line = lines[self.y]
        lines[self.y] = line:sub(1, self.x-2) .. line:sub(self.x, -1)
        self.x -= 1
        self:clamp_x(string.len(lines[self.y]))
        return true
    end)
}

-- local res = fetch("http://echo.free.beeceptor.com/", {
--     method = "POST",
--     headers = {
--         ["Content-Type"] = "application/json",
--         ["Accept"] = "application/json",
--         ["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
--     },
--     body = JSON.stringify({
--         name = "John",
--         age = 30,
--         city = "New York",
--         is_admin = true,
--     })
-- })

-- for k,v in pairs(res) do
--     if k == "headers" then
--         for k2,v2 in pairs(v) do
--             print("header:", k2, v2)
--         end
--     else
--         print(k, v)
--     end
-- end

local last_cursor_pos_time = os.clock()

Signal.KeyPressed:Connect(function(key)
    print("key pressed:", key)

    -- TODO: add a pause before enabling rapid fire
    if os.clock() - last_cursor_pos_time > 1/30 then
        last_cursor_pos_time = os.clock()

        if key == kc.left then
            cursor:touch()
            cursor:go_to(cursor.x-1, nil)
        elseif key == kc.right then
            cursor:touch()
            cursor:go_to(cursor.x+1, nil)

        elseif key == kc.up then
            cursor:touch()
            cursor:go_to(nil, cursor.y-1)
        elseif key == kc.down then
            cursor:touch()
            cursor:go_to(nil, cursor.y+1)

        elseif key == kc.backspace then
            cursor:erase()
        elseif key == kc.enter then
            cursor:write("\n")
        elseif key == kc.tab then
            cursor:write(string.rep(" ", 4))
        end
    end

    local char = Input.Keyboard.pollChar()
    if char ~= 0 then
        cursor:touch()
        cursor:write(char)
    end
end)

Signal.RenderStart:Connect(function()
    if config.user.cursor.style == "BLOCK" then
        if (os.time() % 2) == 0 then
            Renderer.drawRect(10+(cursor.x*10), (cursor.y-1)*20, 10, 20, util:MakeColor(255,255,255,255))
        else
            Renderer.drawRectOutline(10+(cursor.x*10), (cursor.y-1)*20, 10, 20, 1, util:MakeColor(255,255,255,255))
        end
    else
        Renderer.drawRect((cursor.x+2)*9-3, (cursor.y-1)*20+5, 2, 20,
            if ((os.time() % 2) == 0) or cursor:touched() then util:MakeColor(255,255,255, 255) else util:MakeColor(255,255,255, 0)
        )
    end

    local width, _height = App.getWindowSize()

    for i,v in pairs(lines) do
        if v == "" then
            Renderer.drawText(font, i, 5, (font_size * i) - ((font_size - 15)/4), 15, util:MakeColor(110, 110, 110, 255))
            continue
        end
        Renderer.drawText(font, i, 5, (font_size * i) - ((font_size - 15)/4), 15, util:MakeColor(200, 200, 200, 255))
        Renderer.drawText(font, v, 25, font_size * i, font_size, util:MakeColor(255, 255, 255, 255))
    end

    local fps_size = 13
    local fps_txt = "FPS: "..App.getFPS()
    local fps_w, fps_h = Renderer.measureText(font2, fps_txt, 13)

    local fps_pos_x = width - 5 - fps_w

    Renderer.drawText(font2, "FPS: "..App.getFPS(), fps_pos_x - 5, fps_size + 5, fps_size, util:MakeColor(200, 200, 200, 255))
    Renderer.drawRectOutline(fps_pos_x - 10, 5, fps_w + 10, fps_h + 5, 1, util:MakeColor(255, 0, 0, 255))

    Renderer.endRedraw()
end)


local t = {}

function t:MakeColor(r: number, g: number, b: number, a: number)
    return {
        red = r,
        green = g,
        blue = b,
        alpha = a
    }
end

return t