const std = @import("std");
const Allocator = std.mem.Allocator;
const interp_mod = @import("interp.zig");
const Value = interp_mod.Value;
const Interpreter = interp_mod.Interpreter;

// ── BuildConfig ─────────────────────────────────────────────────────────
// Mutable build configuration accumulated by #run blocks via #compiler methods.

pub const BuildConfig = struct {
    link_flags: std.ArrayList([]const u8) = .empty,
    frameworks: std.ArrayList([]const u8) = .empty,
    output_path: ?[]const u8 = null,
    wasm_shell_path: ?[]const u8 = null,

    pub fn deinit(self: *BuildConfig, alloc: Allocator) void {
        self.link_flags.deinit(alloc);
        self.frameworks.deinit(alloc);
    }
};

// ── Hook system ─────────────────────────────────────────────────────────

pub const HookError = error{
    CannotEvalComptime,
    TypeError,
};

/// Hook function signature. Receives the interpreter (for heap/string access),
/// resolved argument values, and the mutable build config.
pub const HookFn = *const fn (
    interp: *const Interpreter,
    args: []const Value,
    bc: *BuildConfig,
    alloc: Allocator,
) HookError!Value;

pub const Registry = struct {
    hooks: std.StringHashMap(HookFn),

    pub fn init(alloc: Allocator) Registry {
        return .{ .hooks = std.StringHashMap(HookFn).init(alloc) };
    }

    pub fn deinit(self: *Registry) void {
        self.hooks.deinit();
    }

    pub fn get(self: *const Registry, name: []const u8) ?HookFn {
        return self.hooks.get(name);
    }

    /// Register all built-in compiler hooks.
    pub fn registerDefaults(self: *Registry) void {
        self.hooks.put("build_options", &hookBuildOptions) catch {};
        self.hooks.put("BuildOptions.add_link_flag", &hookAddLinkFlag) catch {};
        self.hooks.put("BuildOptions.add_framework", &hookAddFramework) catch {};
        self.hooks.put("BuildOptions.set_output_path", &hookSetOutputPath) catch {};
        self.hooks.put("BuildOptions.set_wasm_shell", &hookSetWasmShell) catch {};
    }
};

// ── build_options() hook ────────────────────────────────────────────────

fn hookBuildOptions(
    _: *const Interpreter,
    _: []const Value,
    _: *BuildConfig,
    _: Allocator,
) HookError!Value {
    // build_options() returns a sentinel value; the real work happens
    // when methods like add_link_flag/set_output_path are called on it.
    return .void_val;
}

// ── BuildOptions hooks ──────────────────────────────────────────────────

fn hookAddLinkFlag(
    interp: *const Interpreter,
    args: []const Value,
    bc: *BuildConfig,
    alloc: Allocator,
) HookError!Value {
    // args: [self (BuildOptions value), flag_string]
    if (args.len < 2) return .void_val;
    const str_val = args[1];
    if (str_val.asString(interp)) |s| {
        bc.link_flags.append(alloc, alloc.dupe(u8, s) catch return error.CannotEvalComptime) catch return error.CannotEvalComptime;
    }
    return .void_val;
}

fn hookAddFramework(
    interp: *const Interpreter,
    args: []const Value,
    bc: *BuildConfig,
    alloc: Allocator,
) HookError!Value {
    // args: [self (BuildOptions value), framework_name]
    if (args.len < 2) return .void_val;
    const str_val = args[1];
    if (str_val.asString(interp)) |s| {
        bc.frameworks.append(alloc, alloc.dupe(u8, s) catch return error.CannotEvalComptime) catch return error.CannotEvalComptime;
    }
    return .void_val;
}

fn hookSetOutputPath(
    interp: *const Interpreter,
    args: []const Value,
    bc: *BuildConfig,
    alloc: Allocator,
) HookError!Value {
    // args: [self (BuildOptions value), path_string]
    if (args.len < 2) return .void_val;
    const str_val = args[1];
    if (str_val.asString(interp)) |s| {
        bc.output_path = alloc.dupe(u8, s) catch return error.CannotEvalComptime;
    }
    return .void_val;
}

fn hookSetWasmShell(
    interp: *const Interpreter,
    args: []const Value,
    bc: *BuildConfig,
    alloc: Allocator,
) HookError!Value {
    // args: [self (BuildOptions value), path_string]
    if (args.len < 2) return .void_val;
    const str_val = args[1];
    if (str_val.asString(interp)) |s| {
        bc.wasm_shell_path = alloc.dupe(u8, s) catch return error.CannotEvalComptime;
    }
    return .void_val;
}
