// Copyright (C) 2023-2025  Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

const std = @import("std");

const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;

const js = @import("js/js.zig");
const log = @import("../log.zig");
const App = @import("../App.zig");
const HttpClient = @import("../http/Client.zig");

const ArenaPool = App.ArenaPool;

const IS_DEBUG = @import("builtin").mode == .Debug;

const Session = @import("Session.zig");
const Notification = @import("../Notification.zig");

// Browser is an instance of the browser.
// You can create multiple browser instances.
// A browser contains only one session.
const Browser = @This();

env: js.Env,
app: *App,
session: ?Session,
allocator: Allocator,
arena_pool: *ArenaPool,
http_client: *HttpClient,

const InitOpts = struct {
    env: js.Env.InitOpts = .{},
    http_client: *HttpClient,
};

pub fn init(app: *App, opts: InitOpts) !Browser {
    const allocator = app.allocator;

    var env = try js.Env.init(app, opts.env);
    errdefer env.deinit();

    return .{
        .app = app,
        .env = env,
        .session = null,
        .allocator = allocator,
        .arena_pool = &app.arena_pool,
        .http_client = opts.http_client,
    };
}

pub fn deinit(self: *Browser) void {
    self.closeSession();
    self.env.deinit();
}

pub fn newSession(self: *Browser, notification: *Notification) !*Session {
    self.closeSession();
    self.session = @as(Session, undefined);
    const session = &self.session.?;
    try Session.init(session, self, notification);
    return session;
}

pub fn closeSession(self: *Browser) void {
    if (self.session) |*session| {
        session.deinit();
        self.session = null;
        self.env.memoryPressureNotification(.critical);
    }
}

pub fn runMicrotasks(self: *const Browser) void {
    self.env.runMicrotasks();
}

pub fn runMacrotasks(self: *Browser) !?u64 {
    return try self.env.runMacrotasks();
}

pub fn runMessageLoop(self: *const Browser) void {
    while (self.env.pumpMessageLoop()) {
        if (comptime IS_DEBUG) {
            log.debug(.browser, "pumpMessageLoop", .{});
        }
    }
    self.env.runIdleTasks();
}
