"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.NullObj = exports.ErrorObj = exports.FfiLibraryObj = void 0;
exports.resolveLibraryPath = resolveLibraryPath;
exports.loadLibrary = loadLibrary;
exports.parseFfiType = parseFfiType;
exports.ffiLoadBuiltin = ffiLoadBuiltin;
exports.ffiSymbolBuiltin = ffiSymbolBuiltin;
exports.ffiCallBuiltin = ffiCallBuiltin;
exports.isFfiAvailable = isFfiAvailable;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const values_1 = require("./values");
Object.defineProperty(exports, "ErrorObj", { enumerable: true, get: function () { return values_1.ErrorObj; } });
Object.defineProperty(exports, "NullObj", { enumerable: true, get: function () { return values_1.NullObj; } });
const runtime_1 = require("./runtime");
let koffiModule;
function tryLoadKoffi() {
    if (koffiModule !== undefined) {
        return koffiModule;
    }
    try {
        // Dynamic require so the extension can activate without native bindings.
        // eslint-disable-next-line @typescript-eslint/no-require-imports
        koffiModule = require("koffi");
        return koffiModule;
    }
    catch {
        koffiModule = null;
        return null;
    }
}
function mapType(t) {
    switch (t) {
        case "void":
            return "void";
        case "int":
        case "int32":
            return "int";
        case "uint32":
            return "uint32";
        case "int64":
            return "int64";
        case "uint64":
            return "uint64";
        case "float":
            return "float";
        case "double":
            return "double";
        case "string":
            return "str";
        case "pointer":
            return "void *";
        case "bool":
            return "bool";
        default:
            return "void";
    }
}
function nexusToNative(obj, typ) {
    switch (typ) {
        case "string":
            if (obj instanceof values_1.StringObj) {
                return obj.value;
            }
            return obj.inspect();
        case "int":
        case "int32":
        case "uint32":
        case "int64":
        case "uint64":
            if (obj instanceof values_1.IntegerObj) {
                return obj.value;
            }
            throw new Error(`expected integer for FFI arg type ${typ}`);
        case "bool":
            if (obj.type === "BOOLEAN" && "value" in obj) {
                return Boolean(obj.value);
            }
            return Boolean(obj);
        case "float":
        case "double":
            if (obj instanceof values_1.IntegerObj) {
                return obj.value;
            }
            throw new Error(`expected number for FFI arg type ${typ}`);
        case "pointer":
            return null;
        case "void":
            return undefined;
        default:
            return obj.inspect();
    }
}
function nativeToNexus(value, typ) {
    switch (typ) {
        case "void":
            return values_1.NULL_OBJ;
        case "int":
        case "int32":
        case "uint32":
        case "int64":
        case "uint64":
            return new values_1.IntegerObj(Number(value));
        case "float":
        case "double":
            return new values_1.IntegerObj(Math.trunc(Number(value)));
        case "string":
            return new values_1.StringObj(String(value ?? ""));
        case "bool":
            return value
                ? { type: "BOOLEAN", value: true, inspect: () => "true" }
                : { type: "BOOLEAN", value: false, inspect: () => "false" };
        case "pointer":
            return value == null
                ? values_1.NULL_OBJ
                : new values_1.StringObj(String(value));
        default:
            return new values_1.StringObj(String(value));
    }
}
class KoffiLibrary {
    path;
    lib;
    backend = "koffi";
    available = true;
    constructor(path, lib) {
        this.path = path;
        this.lib = lib;
    }
    define(spec) {
        const ret = mapType(spec.returnType);
        const args = spec.argTypes.map(mapType).join(", ");
        const sig = `${ret} ${spec.name}(${args})`;
        let nativeFn;
        try {
            nativeFn = this.lib.func(sig);
        }
        catch (err) {
            const message = err instanceof Error ? err.message : String(err);
            return new runtime_1.ExternFnObj(this.path, spec.name, spec.returnType, () => (0, values_1.newError)(`FFI symbol bind failed for ${spec.name}: ${message}`));
        }
        return new runtime_1.ExternFnObj(this.path, spec.name, spec.returnType, (...callArgs) => {
            if (callArgs.length !== spec.argTypes.length) {
                return (0, values_1.newError)(`FFI ${spec.name}: wrong number of arguments got=${callArgs.length} want=${spec.argTypes.length}`);
            }
            try {
                const nativeArgs = callArgs.map((a, i) => nexusToNative(a, spec.argTypes[i]));
                const result = nativeFn(...nativeArgs);
                return nativeToNexus(result, spec.returnType);
            }
            catch (err) {
                const message = err instanceof Error ? err.message : String(err);
                return (0, values_1.newError)(`FFI call ${spec.name} failed: ${message}`);
            }
        });
    }
}
class StubLibrary {
    path;
    reason;
    backend = "stub";
    available = false;
    constructor(path, reason) {
        this.path = path;
        this.reason = reason;
    }
    define(spec) {
        return new runtime_1.ExternFnObj(this.path, spec.name, spec.returnType, () => (0, values_1.newError)(`FFI unavailable (${this.reason}); cannot call ${spec.name} from ${this.path}`));
    }
}
/**
 * Resolve a library path for the current platform, trying common variants.
 */
function resolveLibraryPath(libPath) {
    if (path.isAbsolute(libPath) && fs.existsSync(libPath)) {
        return libPath;
    }
    if (fs.existsSync(libPath)) {
        return path.resolve(libPath);
    }
    const candidates = [libPath];
    if (process.platform === "win32") {
        if (!libPath.endsWith(".dll")) {
            candidates.push(`${libPath}.dll`);
        }
    }
    else if (process.platform === "darwin") {
        if (!libPath.includes(".dylib") && !libPath.includes(".so")) {
            candidates.push(`lib${libPath}.dylib`, `${libPath}.dylib`);
        }
    }
    else {
        if (!libPath.includes(".so")) {
            candidates.push(`lib${libPath}.so`, `${libPath}.so`);
        }
    }
    for (const c of candidates) {
        if (fs.existsSync(c)) {
            return path.resolve(c);
        }
    }
    return libPath;
}
/**
 * Load a native dynamic library for FFI. Falls back to a safe stub backend
 * when koffi is unavailable or the library cannot be opened — the API remains
 * fully usable and returns clear ErrorObj values on call.
 */
function loadLibrary(libPath) {
    const resolved = resolveLibraryPath(libPath);
    const koffi = tryLoadKoffi();
    if (!koffi) {
        return new StubLibrary(resolved, "koffi native module not loaded in this host");
    }
    try {
        const lib = koffi.load(resolved);
        return new KoffiLibrary(resolved, lib);
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        return new StubLibrary(resolved, message);
    }
}
/** Parse a type name from Nexus extern declarations. */
function parseFfiType(name) {
    const n = name.toLowerCase();
    switch (n) {
        case "void":
        case "nil":
        case "null":
            return "void";
        case "int":
        case "i32":
        case "int32":
            return "int";
        case "u32":
        case "uint32":
            return "uint32";
        case "i64":
        case "int64":
            return "int64";
        case "u64":
        case "uint64":
            return "uint64";
        case "float":
        case "f32":
            return "float";
        case "double":
        case "f64":
            return "double";
        case "string":
        case "str":
            return "string";
        case "bool":
        case "boolean":
            return "bool";
        case "ptr":
        case "pointer":
            return "pointer";
        default:
            return "int";
    }
}
function ffiLoadBuiltin(libPath) {
    if (!(libPath instanceof values_1.StringObj)) {
        return (0, values_1.newError)("ffi_load: library path must be a string");
    }
    const loaded = loadLibrary(libPath.value);
    return new FfiLibraryObj(loaded);
}
class FfiLibraryObj {
    library;
    type = "FFI_LIBRARY";
    constructor(library) {
        this.library = library;
    }
    inspect() {
        return `ffi_library(${JSON.stringify(this.library.path)}, backend=${this.library.backend})`;
    }
}
exports.FfiLibraryObj = FfiLibraryObj;
function ffiSymbolBuiltin(libObj, nameObj, retObj, ...argTypeObjs) {
    if (!(libObj instanceof FfiLibraryObj)) {
        return (0, values_1.newError)("ffi_symbol: first argument must be an ffi library");
    }
    if (!(nameObj instanceof values_1.StringObj)) {
        return (0, values_1.newError)("ffi_symbol: symbol name must be a string");
    }
    if (!(retObj instanceof values_1.StringObj)) {
        return (0, values_1.newError)("ffi_symbol: return type must be a string");
    }
    const argTypes = argTypeObjs.map((a) => {
        if (!(a instanceof values_1.StringObj)) {
            throw new Error("ffi_symbol: arg types must be strings");
        }
        return parseFfiType(a.value);
    });
    try {
        return libObj.library.define({
            name: nameObj.value,
            returnType: parseFfiType(retObj.value),
            argTypes,
        });
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        return (0, values_1.newError)(`ffi_symbol failed: ${message}`);
    }
}
function ffiCallBuiltin(fnObj, ...args) {
    if (!(fnObj instanceof runtime_1.ExternFnObj)) {
        return (0, values_1.newError)("ffi_call: first argument must be an extern fn");
    }
    return fnObj.call(...args);
}
function isFfiAvailable() {
    return tryLoadKoffi() !== null;
}
//# sourceMappingURL=ffi.js.map