const std = @import("std");

pub const Entry = struct {
    job_name: []const u8,
    commit_id: []const u8,
    queued_at: i64,
};

fn historyDir(allocator: std.mem.Allocator) ![]const u8 {
    const home = std.posix.getenv("HOME") orelse return error.NoHomeDir;
    return std.fmt.allocPrint(allocator, "{s}/.ml", .{home});
}

fn historyPath(allocator: std.mem.Allocator) ![]const u8 {
    const dir = try historyDir(allocator);
    defer allocator.free(dir);
    return std.fmt.allocPrint(allocator, "{s}/history.log", .{dir});
}

pub fn record(allocator: std.mem.Allocator, job_name: []const u8, commit_id: []const u8) !void {
    const dir = try historyDir(allocator);
    defer allocator.free(dir);
    std.fs.makeDirAbsolute(dir) catch |err| {
        if (err != error.PathAlreadyExists) return err;
    };

    const path = try historyPath(allocator);
    defer allocator.free(path);

    var file = std.fs.openFileAbsolute(path, .{ .mode = .read_write }) catch |err| switch (err) {
        error.FileNotFound => try std.fs.createFileAbsolute(path, .{}),
        else => return err,
    };
    defer file.close();

    // Append at end of file
    try file.seekFromEnd(0);

    const ts = std.time.timestamp();

    // Format one line into a temporary buffer
    const line = try std.fmt.allocPrint(
        allocator,
        "{d}\t{s}\t{s}\n",
        .{ ts, job_name, commit_id },
    );
    defer allocator.free(line);

    try file.writeAll(line);
}

pub fn loadEntries(allocator: std.mem.Allocator) ![]Entry {
    const path = historyPath(allocator) catch |err| switch (err) {
        error.NoHomeDir => return error.NoHomeDir,
        else => return err,
    };
    defer allocator.free(path);

    const file = std.fs.openFileAbsolute(path, .{}) catch |err| switch (err) {
        error.FileNotFound => return &.{},
        else => return err,
    };
    defer file.close();

    const contents = try file.readToEndAlloc(allocator, 1024 * 1024);
    defer allocator.free(contents);

    var entries = std.ArrayListUnmanaged(Entry){};
    defer entries.deinit(allocator);

    var it = std.mem.splitScalar(u8, contents, '\n');
    while (it.next()) |line_full| {
        const line = std.mem.trim(u8, line_full, " \t\r");
        if (line.len == 0) continue;

        var parts = std.mem.splitScalar(u8, line, '\t');
        const ts_str = parts.next() orelse continue;
        const job = parts.next() orelse continue;
        const commit = parts.next() orelse continue;

        const ts = std.fmt.parseInt(i64, ts_str, 10) catch continue;
        const job_dup = try allocator.dupe(u8, job);
        const commit_dup = try allocator.dupe(u8, commit);

        try entries.append(allocator, Entry{
            .job_name = job_dup,
            .commit_id = commit_dup,
            .queued_at = ts,
        });
    }

    return try entries.toOwnedSlice(allocator);
}

pub fn freeEntries(allocator: std.mem.Allocator, entries: []Entry) void {
    for (entries) |entry| {
        allocator.free(entry.job_name);
        allocator.free(entry.commit_id);
    }
    allocator.free(entries);
}
