/// A non-owning reference to either the JS event loop or the mini event loop.
pub const EventLoopHandle = union(EventLoopKind) {
    js: *JSC.EventLoop,
    mini: *MiniEventLoop,

    pub fn globalObject(this: EventLoopHandle) ?*JSC.JSGlobalObject {
        return switch (this) {
            .js => this.js.global,
            .mini => null,
        };
    }

    pub fn stdout(this: EventLoopHandle) *JSC.WebCore.Blob.Store {
        return switch (this) {
            .js => this.js.virtual_machine.rareData().stdout(),
            .mini => this.mini.stdout(),
        };
    }

    pub fn bunVM(this: EventLoopHandle) ?*VirtualMachine {
        if (this == .js) {
            return this.js.virtual_machine;
        }

        return null;
    }

    pub fn stderr(this: EventLoopHandle) *JSC.WebCore.Blob.Store {
        return switch (this) {
            .js => this.js.virtual_machine.rareData().stderr(),
            .mini => this.mini.stderr(),
        };
    }

    pub fn cast(this: EventLoopHandle, comptime tag: EventLoopKind) tag.Type() {
        return @field(this, @tagName(tag));
    }

    pub fn enter(this: EventLoopHandle) void {
        switch (this) {
            .js => this.js.enter(),
            .mini => {},
        }
    }

    pub fn exit(this: EventLoopHandle) void {
        switch (this) {
            .js => this.js.exit(),
            .mini => {},
        }
    }

    pub fn init(context: anytype) EventLoopHandle {
        const Context = @TypeOf(context);
        return switch (Context) {
            *VirtualMachine => .{ .js = context.eventLoop() },
            *JSC.EventLoop => .{ .js = context },
            *JSC.MiniEventLoop => .{ .mini = context },
            *AnyEventLoop => switch (context.*) {
                .js => .{ .js = context.js },
                .mini => .{ .mini = &context.mini },
            },
            EventLoopHandle => context,
            else => @compileError("Invalid context type for EventLoopHandle.init " ++ @typeName(Context)),
        };
    }

    pub fn filePolls(this: EventLoopHandle) *bun.Async.FilePoll.Store {
        return switch (this) {
            .js => this.js.virtual_machine.rareData().filePolls(this.js.virtual_machine),
            .mini => this.mini.filePolls(),
        };
    }

    pub fn putFilePoll(this: *EventLoopHandle, poll: *Async.FilePoll) void {
        switch (this.*) {
            .js => this.js.virtual_machine.rareData().filePolls(this.js.virtual_machine).put(poll, this.js.virtual_machine, poll.flags.contains(.was_ever_registered)),
            .mini => this.mini.filePolls().put(poll, &this.mini, poll.flags.contains(.was_ever_registered)),
        }
    }

    pub fn enqueueTaskConcurrent(this: EventLoopHandle, context: EventLoopTaskPtr) void {
        switch (this) {
            .js => {
                this.js.enqueueTaskConcurrent(context.js);
            },
            .mini => {
                this.mini.enqueueTaskConcurrent(context.mini);
            },
        }
    }

    pub fn loop(this: EventLoopHandle) *bun.uws.Loop {
        return switch (this) {
            .js => this.js.usocketsLoop(),
            .mini => this.mini.loop,
        };
    }

    pub fn pipeReadBuffer(this: EventLoopHandle) []u8 {
        return switch (this) {
            .js => this.js.pipeReadBuffer(),
            .mini => this.mini.pipeReadBuffer(),
        };
    }

    pub const platformEventLoop = loop;

    pub fn ref(this: EventLoopHandle) void {
        this.loop().ref();
    }

    pub fn unref(this: EventLoopHandle) void {
        this.loop().unref();
    }

    pub inline fn createNullDelimitedEnvMap(this: @This(), alloc: Allocator) ![:null]?[*:0]const u8 {
        return switch (this) {
            .js => this.js.virtual_machine.transpiler.env.map.createNullDelimitedEnvMap(alloc),
            .mini => this.mini.env.?.map.createNullDelimitedEnvMap(alloc),
        };
    }

    pub inline fn allocator(this: EventLoopHandle) Allocator {
        return switch (this) {
            .js => this.js.virtual_machine.allocator,
            .mini => this.mini.allocator,
        };
    }

    pub inline fn topLevelDir(this: EventLoopHandle) []const u8 {
        return switch (this) {
            .js => this.js.virtual_machine.transpiler.fs.top_level_dir,
            .mini => this.mini.top_level_dir,
        };
    }

    pub inline fn env(this: EventLoopHandle) *bun.DotEnv.Loader {
        return switch (this) {
            .js => this.js.virtual_machine.transpiler.env,
            .mini => this.mini.env.?,
        };
    }
};

pub const EventLoopTask = union(EventLoopKind) {
    js: JSC.ConcurrentTask,
    mini: JSC.AnyTaskWithExtraContext,

    pub fn init(kind: EventLoopKind) EventLoopTask {
        switch (kind) {
            .js => return .{ .js = .{} },
            .mini => return .{ .mini = .{} },
        }
    }

    pub fn fromEventLoop(loop: JSC.EventLoopHandle) EventLoopTask {
        switch (loop) {
            .js => return .{ .js = .{} },
            .mini => return .{ .mini = .{} },
        }
    }
};

pub const EventLoopTaskPtr = union {
    js: *JSC.ConcurrentTask,
    mini: *JSC.AnyTaskWithExtraContext,
};

const std = @import("std");
const bun = @import("bun");
const JSC = bun.JSC;
const Async = bun.Async;
const VirtualMachine = JSC.VirtualMachine;
const MiniEventLoop = JSC.MiniEventLoop;
const Allocator = std.mem.Allocator;
const AnyEventLoop = JSC.AnyEventLoop;
const EventLoopKind = JSC.EventLoopKind;
