/**
 * Local DWN discovery — discovers a running `@enbox/dwn-server` instance
 * so the agent can route traffic to it.
 *
 * Discovery channels (tried in order):
 * 1. **In-memory cache** — serves a recent positive or negative result.
 * 2. **Discovery file** (`~/.enbox/dwn.json`) — written by `electrobun-dwn`
 *    on startup. Fast filesystem read, no network. Available for CLI and
 *    native apps; skipped in browsers.
 * 3. **Injected endpoint** — in browsers, the `dwn://connect` redirect
 *    flow delivers the endpoint, which is injected via
 *    {@link LocalDwnDiscovery.setCachedEndpoint | setCachedEndpoint()}.
 *
 * @see https://github.com/enboxorg/enbox/issues/677
 * @module
 */
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
/** The `server` field returned by `GET /info` on `@enbox/dwn-server`. */
export const localDwnServerName = '@enbox/dwn-server';
/** Strips a trailing slash from a URL so endpoint comparisons are consistent. */
export function normalizeBaseUrl(url) {
    return url.endsWith('/') ? url.slice(0, -1) : url;
}
/**
 * Discovers a running local DWN server.
 *
 * Results are cached for {@link _cacheTtlMs} milliseconds (default 10 s) to
 * avoid repeated I/O on hot paths such as sync.
 *
 * @example Discovery with file-based channel (CLI / native)
 * ```ts
 * import { DwnDiscoveryFile } from './dwn-discovery-file.js';
 *
 * const discoveryFile = new DwnDiscoveryFile();
 * const discovery = new LocalDwnDiscovery(rpcClient, 10_000, discoveryFile);
 * const endpoint = await discovery.getEndpoint();
 * ```
 *
 * @example Browser: inject cached endpoint from `dwn://connect` redirect
 * ```ts
 * const discovery = new LocalDwnDiscovery(rpcClient);
 * discovery.setCachedEndpoint('http://127.0.0.1:55557');
 * ```
 */
export class LocalDwnDiscovery {
    constructor(_rpcClient, _cacheTtlMs = 10000, _discoveryFile) {
        this._rpcClient = _rpcClient;
        this._cacheTtlMs = _cacheTtlMs;
        this._discoveryFile = _discoveryFile;
        this._cacheExpiry = 0;
    }
    /**
     * Returns the base URL of a local DWN server, or `undefined` if none
     * is discoverable.
     *
     * The discovery order is:
     * 1. In-memory cache (if not expired).
     * 2. `~/.enbox/dwn.json` discovery file (if a {@link DwnDiscoveryFile}
     *    was provided). The endpoint from the file is validated via
     *    `GET /info` to ensure the server is still running.
     *
     * If neither channel finds an endpoint, the result (`undefined`) is
     * cached to avoid repeated discovery file reads on hot paths.
     *
     * In browser environments (where no discovery file is available), the
     * endpoint must be injected externally via
     * {@link setCachedEndpoint | setCachedEndpoint()} — typically after a
     * `dwn://connect` redirect delivers the endpoint in the URL fragment.
     */
    getEndpoint() {
        return __awaiter(this, void 0, void 0, function* () {
            const now = Date.now();
            if (now < this._cacheExpiry) {
                return this._cachedEndpoint;
            }
            // File-based discovery (CLI / native — skipped when no file is configured).
            const fileEndpoint = yield this._tryDiscoveryFile();
            if (fileEndpoint !== undefined) {
                this._setCacheEntry(fileEndpoint, now);
                return fileEndpoint;
            }
            // No endpoint found. Cache the negative result to avoid repeated
            // discovery file reads within the TTL window.
            this._setCacheEntry(undefined, now);
            return undefined;
        });
    }
    /**
     * Inject a cached endpoint (e.g. from a `dwn://connect` browser redirect
     * or from `localStorage`). The endpoint is validated via `GET /info` before
     * caching.
     *
     * @returns `true` if the endpoint was validated and cached, `false` otherwise.
     */
    setCachedEndpoint(endpoint) {
        return __awaiter(this, void 0, void 0, function* () {
            const normalized = normalizeBaseUrl(endpoint);
            const valid = yield this._validateEndpoint(normalized);
            if (valid) {
                this._setCacheEntry(normalized, Date.now());
            }
            return valid;
        });
    }
    /**
     * Clear the in-memory cache, forcing the next {@link getEndpoint} call
     * to perform a fresh discovery.
     */
    clearCache() {
        this._cachedEndpoint = undefined;
        this._cacheExpiry = 0;
    }
    // ─── Private ──────────────────────────────────────────────────
    /**
     * Try the `~/.enbox/dwn.json` discovery file. Returns the endpoint if
     * the file exists, is valid, and the endpoint passes `GET /info`
     * validation. Returns `undefined` otherwise.
     */
    _tryDiscoveryFile() {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this._discoveryFile) {
                return undefined;
            }
            try {
                const record = yield this._discoveryFile.read();
                if (!record) {
                    return undefined;
                }
                // Validate that the server is actually alive and is ours.
                const valid = yield this._validateEndpoint(record.endpoint);
                return valid ? record.endpoint : undefined;
            }
            catch (_a) {
                return undefined;
            }
        });
    }
    /**
     * Call `GET /info` on the endpoint and check that
     * `serverInfo.server === '@enbox/dwn-server'`.
     */
    _validateEndpoint(endpoint) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                const serverInfo = yield this._rpcClient.getServerInfo(endpoint);
                return serverInfo.server === localDwnServerName;
            }
            catch (_a) {
                return false;
            }
        });
    }
    /** Update the in-memory cache entry. */
    _setCacheEntry(endpoint, now) {
        this._cachedEndpoint = endpoint;
        this._cacheExpiry = now + this._cacheTtlMs;
    }
}
//# sourceMappingURL=local-dwn.js.map