/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * DSL live-debugger client — `/dsl-debugging/*` endpoints, typed against
 * the **as-built** wire emitted by `DSLDebuggingRestHandler.java` on the
 * `swip-13-dsl-debugger` branch. The wire shape was rewritten upstream
 * (commits 4275f61df5 / 0e4058614c / 6f2db069a0): records now carry an
 * envelope (`startedAtMs`, `dsl`, `rule`) plus a per-execution
 * `samples[]` array, and the per-DSL stage vocabulary collapsed to five
 * unified sample types (`input | filter | function | aggregation |
 * output`).
 *
 *   POST   /dsl-debugging/session?catalog=&name=&ruleName=&clientId=
 *                                 [&granularity=]
 *                                 body (optional JSON):
 *                                 { recordCap?, retentionMillis?,
 *                                   granularity? }
 *   GET    /dsl-debugging/session/{id}
 *   POST   /dsl-debugging/session/{id}/stop
 *   GET    /dsl-debugging/sessions             — JSON object, not NDJSON
 *   GET    /dsl-debugging/status               — 5-field health snapshot
 *
 * Notable wire facts:
 * - Inputs are query params; the optional JSON body carries
 *   `recordCap` / `retentionMillis` / `granularity` overrides.
 *   `granularity` query param wins over body.
 * - The session response carries top-level `ruleKey` + `capturedAt`,
 *   per-record `startedAtMs` + `dsl` + `rule` envelope, and per-record
 *   `samples[]` whose entries discriminate via `type`.
 * - Per-record `contentHash` was removed; the verbatim `dsl` string
 *   carries hot-update awareness instead.
 * - `priorCleanup` is now a structured object `{local, peers[]}`, not a
 *   flat array.
 * - Peer install ack values are UPPERCASE: `INSTALLED | NOT_LOCAL |
 *   FAILED`.
 * - Session-start can fail with `cluster_view_split` (HTTP 421) when
 *   the cluster's view of the rule disagrees across nodes.
 */
import { RuntimeRuleApiError } from './types.js';
export const DEBUG_CATALOGS = [
    'otel-rules',
    'log-mal-rules',
    'telegraf-rules',
    'lal',
    'oal',
];
export function isDebugCatalog(value) {
    return typeof value === 'string' && DEBUG_CATALOGS.includes(value);
}
/** Mirror of OAP's `SessionLimits.MAX_RECORD_CAP` — both default and
 *  hard ceiling for `recordCap` on `POST /dsl-debugging/session`.
 *  Studio's BFF rejects requests above this with `400
 *  invalid_recordCap` before the OAP round-trip; UI inputs bound to
 *  the same value. Keep in sync with `SessionLimits.java` upstream. */
export const MAX_RECORD_CAP = 100;
/** Mirror of OAP's `SessionLimits.MAX_RETENTION_MILLIS` — 1 hour
 *  hard cap on per-session retention windows. */
export const MAX_RETENTION_MILLIS = 60 * 60 * 1000;
export const GRANULARITIES = ['block', 'statement'];
export function isGranularity(v) {
    return v === 'block' || v === 'statement';
}
export const SAMPLE_TYPES = [
    'input',
    'filter',
    'function',
    'aggregation',
    'output',
];
export class DslDebuggingClient {
    fetchImpl;
    base;
    defaultHeaders;
    timeoutMs;
    constructor(options) {
        this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
        this.base = options.adminUrl.replace(/\/$/, '');
        this.defaultHeaders = options.headers ?? {};
        this.timeoutMs = options.timeoutMs ?? 0;
    }
    /** `POST /dsl-debugging/session?catalog=&name=&ruleName=&clientId=
     *  [&granularity=]`, optional JSON body with `recordCap` /
     *  `retentionMillis` / `granularity`. The query param wins over the
     *  body for granularity (matches upstream resolution order). */
    async startSession(args) {
        const params = new URLSearchParams({
            catalog: args.catalog,
            name: args.name,
            ruleName: args.ruleName,
            clientId: args.clientId,
        });
        if (args.granularity !== undefined)
            params.set('granularity', args.granularity);
        const url = `${this.base}/dsl-debugging/session?${params.toString()}`;
        const body = {};
        if (args.recordCap !== undefined)
            body.recordCap = args.recordCap;
        if (args.retentionMillis !== undefined)
            body.retentionMillis = args.retentionMillis;
        const hasBody = Object.keys(body).length > 0;
        const init = {
            method: 'POST',
            ...(hasBody
                ? {
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify(body),
                }
                : {}),
        };
        const res = await this.send(url, init);
        if (!res.ok)
            throw await this.toError(res, url);
        return (await res.json());
    }
    /** `GET /dsl-debugging/session/{id}`. Returns `null` on `404
     *  session_not_found`; live polls return a normal envelope where
     *  the local slice may be `status: "not_local"` after a stop. */
    async getSession(id) {
        const url = `${this.base}/dsl-debugging/session/${encodeURIComponent(id)}`;
        const res = await this.send(url, { method: 'GET' });
        if (res.status === 404)
            return null;
        if (!res.ok)
            throw await this.toError(res, url);
        return (await res.json());
    }
    /** `POST /dsl-debugging/session/{id}/stop` — idempotent. */
    async stopSession(id) {
        const url = `${this.base}/dsl-debugging/session/${encodeURIComponent(id)}/stop`;
        const res = await this.send(url, { method: 'POST' });
        if (!res.ok)
            throw await this.toError(res, url);
        return (await res.json());
    }
    /** `GET /dsl-debugging/sessions` — JSON object, not NDJSON. */
    async listSessions() {
        const url = `${this.base}/dsl-debugging/sessions`;
        const res = await this.send(url, { method: 'GET' });
        if (!res.ok)
            throw await this.toError(res, url);
        return (await res.json());
    }
    /** `GET /dsl-debugging/status` — per-node 5-field health snapshot. */
    async getStatus() {
        const url = `${this.base}/dsl-debugging/status`;
        const res = await this.send(url, { method: 'GET' });
        if (!res.ok)
            throw await this.toError(res, url);
        return (await res.json());
    }
    // ── private helpers ─────────────────────────────────────────────
    async send(url, init) {
        const headers = {
            Accept: 'application/json',
            ...this.defaultHeaders,
            ...(init.headers ?? {}),
        };
        const finalInit = { ...init, headers };
        if (this.timeoutMs > 0) {
            const controller = new AbortController();
            const timer = setTimeout(() => controller.abort(), this.timeoutMs);
            try {
                return await this.fetchImpl(url, { ...finalInit, signal: controller.signal });
            }
            finally {
                clearTimeout(timer);
            }
        }
        return this.fetchImpl(url, finalInit);
    }
    async toError(res, url) {
        const text = await res.text();
        let parsed = text;
        try {
            const json = JSON.parse(text);
            // Accept either the legacy `{ applyStatus, message }` envelope
            // (runtime-rule pipeline) or the `{ status, code, message }`
            // envelope (dsl-debugging / runtime-oal). Downstream `outcomeOf`
            // helpers switch on `code` or `applyStatus`.
            if (typeof json.applyStatus === 'string' && typeof json.message === 'string') {
                parsed = json;
            }
            else if (json.status === 'error' &&
                typeof json.code === 'string' &&
                typeof json.message === 'string') {
                parsed = json;
            }
        }
        catch {
            // not JSON; keep the raw text.
        }
        return new RuntimeRuleApiError(res.status, parsed, url);
    }
}
//# sourceMappingURL=dsl-debugging.js.map