"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.detectRuntime = detectRuntime;
exports.isVersionAtLeast = isVersionAtLeast;
exports.checkSupport = checkSupport;
exports.assertSupported = assertSupported;
// deno-coverage-ignore-file
// deno-lint-ignore-file no-explicit-any
const dntShim = __importStar(require("../../_dnt.shims.js"));
/** Detect the current JS runtime and version (if available). */
function detectRuntime() {
    const info = { runtime: "unknown", version: null, details: {} };
    const deno = dntShim.dntGlobalThis.Deno;
    const proc = dntShim.dntGlobalThis.process;
    const bun = dntShim.dntGlobalThis.Bun;
    // Heuristic: real Deno exposes build info alongside a deno version.
    const isRealDeno = (d) => !!(d && d.version?.deno && d.build?.os && d.build?.arch);
    // Cloudflare Workers (workerd)
    const ua = (typeof globalThis.navigator === "object" &&
        typeof globalThis.navigator?.userAgent === "string")
        ? globalThis.navigator.userAgent
        : "";
    if (/Cloudflare-Workers/i.test(ua)) {
        info.runtime = "cloudflare";
        info.version = null;
        info.details = {
            userAgent: ua,
            miniflare: !!dntShim.dntGlobalThis.MINIFLARE || /Miniflare/i.test(ua),
            hasWebSocketPair: typeof dntShim.dntGlobalThis.WebSocketPair !== "undefined",
            hasCaches: typeof dntShim.dntGlobalThis.caches !== "undefined",
        };
        return info;
    }
    if (typeof dntShim.dntGlobalThis.process === "undefined" &&
        typeof dntShim.dntGlobalThis.Deno === "undefined" &&
        typeof dntShim.dntGlobalThis.Bun === "undefined" &&
        typeof dntShim.dntGlobalThis.WebSocketPair !== "undefined" &&
        typeof dntShim.dntGlobalThis.caches !== "undefined") {
        info.runtime = "cloudflare";
        info.version = null;
        info.details = { userAgent: ua || null, heuristic: true };
        return info;
    }
    // Bun
    if (typeof bun === "object" && typeof bun?.version === "string") {
        info.runtime = "bun";
        info.version = bun.version;
        info.details = {
            bunRevision: bun.revision ?? null,
            nodeCompat: (typeof proc === "object") && !!proc?.versions?.node,
        };
        return info;
    }
    // Prefer Node when process.versions.node exists but process.versions.deno does not.
    const hasNode = typeof proc === "object" && !!proc?.versions?.node;
    const nodeDominant = hasNode && !proc?.versions?.deno;
    if (nodeDominant) {
        info.runtime = "node";
        info.version = String(proc.versions.node);
        info.details = {
            v8: proc.versions.v8,
            arch: proc.arch,
            platform: proc.platform,
            denoShimDetected: typeof deno === "object",
        };
        return info;
    }
    // Prefer "real Deno"; otherwise Deno compat in Deno will still identify as Deno.
    const hasDeno = typeof deno === "object" && !!deno?.version?.deno;
    if (hasDeno && (!hasNode || isRealDeno(deno) || proc?.versions?.deno)) {
        info.runtime = "deno";
        info.version = String(deno.version.deno);
        info.details = {
            v8: deno.version.v8,
            typescript: deno.version.typescript,
            os: deno.build?.os,
            arch: deno.build?.arch,
            nodeCompat: !!proc?.versions?.deno,
        };
        return info;
    }
    // Fallback Node
    if (hasNode) {
        info.runtime = "node";
        info.version = String(proc.versions.node);
        info.details = {
            v8: proc.versions.v8,
            arch: proc.arch,
            platform: proc.platform,
        };
        return info;
    }
    return info;
}
/** Minimal semver parser (major.minor.patch only; pre-release makes it "lower"). */
function parseSemver(v) {
    if (v == null)
        return null;
    let s = String(v).trim();
    if (s.startsWith("v"))
        s = s.slice(1);
    const core = s.split("+")[0];
    const [nums, pre = ""] = core.split("-");
    const [maj = "0", min = "0", pat = "0"] = nums.split(".");
    const toInt = (x) => {
        const n = parseInt(String(x), 10);
        return Number.isFinite(n) ? n : 0;
    };
    return { maj: toInt(maj), min: toInt(min), pat: toInt(pat), pre };
}
/** Compare two semvers: returns -1, 0, 1 */
function cmpSemver(a, b) {
    const A = parseSemver(a), B = parseSemver(b);
    if (!A || !B)
        return 0;
    if (A.maj !== B.maj)
        return A.maj < B.maj ? -1 : 1;
    if (A.min !== B.min)
        return A.min < B.min ? -1 : 1;
    if (A.pat !== B.pat)
        return A.pat < B.pat ? -1 : 1;
    // Treat pre-release as lower than stable
    const aPre = A.pre && A.pre.length > 0;
    const bPre = B.pre && B.pre.length > 0;
    if (aPre !== bPre)
        return aPre ? -1 : 1;
    if (aPre && bPre)
        return A.pre < B.pre ? -1 : (A.pre > B.pre ? 1 : 0);
    return 0;
}
/** Check current >= minimum (major.minor.patch). */
function isVersionAtLeast(current, minimum) {
    return cmpSemver(current, minimum) >= 0;
}
/**
 * Check support against your policy.
 */
function checkSupport(policy = {}) {
    const info = detectRuntime();
    const fail = (reason) => ({
        ok: false,
        info,
        reason,
    });
    const pass = () => ({ ok: true, info });
    const hasGlobalPath = (path) => {
        const parts = path.split(".");
        let cur = dntShim.dntGlobalThis;
        for (const p of parts) {
            if (cur == null || !(p in cur))
                return false;
            cur = cur[p];
        }
        return true;
    };
    if (info.runtime === "node" && policy.node) {
        if (!info.version || !isVersionAtLeast(info.version, policy.node)) {
            return fail(`Requires Node >= ${policy.node}, found ${info.version ?? "unknown"}.`);
        }
        return pass();
    }
    if (info.runtime === "deno" && policy.deno) {
        if (!info.version || !isVersionAtLeast(info.version, policy.deno)) {
            return fail(`Requires Deno >= ${policy.deno}, found ${info.version ?? "unknown"}.`);
        }
        return pass();
    }
    if (info.runtime === "bun" && policy.bun) {
        if (!info.version || !isVersionAtLeast(info.version, policy.bun)) {
            return fail(`Requires Bun >= ${policy.bun}, found ${info.version ?? "unknown"}.`);
        }
        return pass();
    }
    if (info.runtime === "cloudflare") {
        const feats = policy.cloudflare?.requireFeatures ?? [];
        for (const f of feats) {
            if (!hasGlobalPath(f)) {
                return fail(`Cloudflare Workers missing required feature: ${f}`);
            }
        }
        return pass();
    }
    return fail("Unsupported runtime.");
}
/** Throw an Error if current runtime/version is unsupported. */
function assertSupported(policy) {
    const res = checkSupport(policy);
    if (!res.ok) {
        const { runtime, version } = res.info;
        throw new Error(`Unsupported environment: ${res.reason} (runtime=${runtime}, version=${version})`);
    }
}
