const std = @import("std");
const temp = @import("temp");
const zdt = @import("zdt");

const Dimensions = @import("../../Dimensions.zig");
const renderer = @import("../../renderer.zig");

pub const api = @import("../../api/root.zig");

const template = @import("template/root.zig");

const Self = @This();

allocator: std.mem.Allocator,
dimensions: Dimensions,

pub fn init(allocator: std.mem.Allocator, dimensions: Dimensions) Self {
    return .{
        .allocator = allocator,
        .dimensions = dimensions,
    };
}

pub const RenderOptions = struct {
    now: *const zdt.Datetime,
    tz: *const zdt.Timezone,
    max_items: usize,
    show_operator: bool,
};

pub fn render(self: *const Self, efa_dm_resp: *const api.efa.models.DmResponse, options: RenderOptions) ![]const u8 {
    const now_local = try options.now.tzConvert(.{ .tz = options.tz });

    var arena = std.heap.ArenaAllocator.init(self.allocator);
    defer arena.deinit();

    const departures = try arena.allocator().alloc(template.Departure, efa_dm_resp.stopEvents.len);
    var dep_i: usize = 0;
    for (efa_dm_resp.stopEvents) |dep| {
        const dp_timetable = try zdt.Datetime.fromISO8601(dep.departureTimeBaseTimetable);
        const dp_timetable_local = try dp_timetable.tzConvert(.{ .tz = options.tz });

        const dp_planned = try zdt.Datetime.fromISO8601(
            dep.departureTimeEstimated orelse dep.departureTimePlanned,
        );
        const dp_planned_local = try dp_planned.tzConvert(.{ .tz = options.tz });

        const diff_min: i32 = @intFromFloat(dp_planned_local.diff(dp_timetable_local).totalMinutes());
        const on_time: ?u32 = if (diff_min < 0) @abs(diff_min) else null;
        const delayed: ?u32 = if (diff_min > 0) @abs(diff_min) else null;

        const date: ?[]const u8 = if (dp_planned_local.dayOfYear() != now_local.dayOfYear())
            try std.fmt.allocPrint(arena.allocator(), "{d}.{d}.{d}", .{
                dp_planned_local.day,
                dp_planned_local.month,
                dp_planned_local.year,
            })
        else
            null;

        var infos: ?[]const []const u8 = null;
        if (dep.hints) |hints| {
            const arr = try arena.allocator().alloc([]const u8, hints.len + 1);
            var i: usize = 0;
            for (hints) |hint| {
                if (std.mem.eql(u8, hint.type, "Timetable"))
                    continue;

                arr[i] = hint.content;
                i += 1;
            }

            infos = arr[0..i];
        }

        departures[dep_i] = template.Departure{
            .time = try std.fmt.allocPrint(
                arena.allocator(),
                "{d}:{d:0>2}",
                .{ dp_timetable_local.hour, dp_timetable_local.minute },
            ),
            .time_unix = dp_timetable_local.toUnix(.second),
            .line = dep.transportation.name,
            .transportation_product_class = dep.transportation.product.class,
            .operator = if (dep.transportation.operator) |op| op.name else null,
            .destination = dep.transportation.destination.name,
            .platform = dep.location.properties.platform,
            .date = date,
            .on_time = on_time,
            .delayed = delayed,
            .cancelled = dep.isCancelled,
            .information = infos,
        };

        dep_i += 1;
    }

    std.mem.sort(
        template.Departure,
        departures,
        {},
        template.Departure.cmpByTimeUnix,
    );

    const context = template.Context{
        .station = efa_dm_resp.locations[0].name,
        .time = try std.fmt.allocPrint(
            arena.allocator(),
            "{d}:{d:0>2}",
            .{ now_local.hour, now_local.minute },
        ),
        .departures = departures[0..@min(departures.len, options.max_items)],
        .show_operator = options.show_operator,
    };

    const escaped_context = try context.escape(arena.allocator());

    var tmp_file = try temp.TempFile.create(arena.allocator(), .{
        .pattern = "render-departures-*.html",
    });
    defer tmp_file.deinit();
    const path = try tmp_file.parent_dir.realpathAlloc(arena.allocator(), tmp_file.basename);
    {
        const file = try tmp_file.open(.{ .mode = .write_only });
        defer file.close();
        const writer = file.writer();
        try template.render(&escaped_context, writer.any());
    }

    return try renderer.render(self.allocator, path, self.dimensions);
}
