const std = @import("std");
const Tensor = @import("tensor.zig").Tensor;

pub const Neuron = struct {
    weights: Tensor,
    bias: f32,

    pub fn init(allocator: std.mem.Allocator, input_size: usize) !Neuron {
        const weights = try Tensor.init(allocator, &[_]usize{input_size});

        var seed: u64 = undefined;
        try std.posix.getrandom(std.mem.asBytes(&seed));

        var prng = std.Random.DefaultPrng.init(seed);
        const rand = prng.random();

        for (weights.data) |*w| {
            w.* = rand.float(f32) * 2.0 - 1.0;
        }

        return Neuron{
            .weights = weights,
            .bias = 0.0,
        };
    }

    pub fn deinit(self: *Neuron) void {
        self.weights.deinit();
    }

    pub fn forward(self: *Neuron, inputs: Tensor) f32 {
        std.debug.assert(inputs.data.len == self.weights.data.len);

        var sum: f32 = 0.0;
        for (inputs.data, self.weights.data) |x, w| {
            sum += x * w;
        }

        const z = sum + self.bias;

        return if (z > 0) z else 0;
    }

    pub fn train(self: *Neuron, inputs: Tensor, target: f32, learning_rate: f32) f32 {
        const prediction = self.forward(inputs);

        const err = prediction - target;

        const d_relu: f32 = if (prediction > 0) 1.0 else 0.0;

        for (self.weights.data, inputs.data) |*w, x| {
            const gradient = err * d_relu * x;
            w.* -= learning_rate * gradient;
        }

        self.bias -= learning_rate * err * d_relu;

        return err * err;
    }
};
