/*
 * Copyright (c) 2025, Salesforce, Inc.
 * SPDX-License-Identifier: Apache-2
 * For full license text, see the license.txt file in the repo root or http://www.apache.org/licenses/LICENSE-2.0
 */
/**
 * Stateless HTTP client for the B2C Commerce Script Debugger API (SDAPI 2.0).
 *
 * All methods use global `fetch()` with Basic auth and the required
 * `x-dw-client-id` header. The client is stateless — callers are responsible
 * for session lifecycle (create/delete client, polling, keepalive).
 *
 * @module operations/debug/sdapi-client
 */
import { getLogger } from '../../logging/logger.js';
/**
 * Error thrown when the SDAPI returns a fault response.
 */
export class SdapiError extends Error {
    fault;
    status;
    constructor(fault, status) {
        super(`${fault.type}: ${fault.message}`);
        this.name = 'SdapiError';
        this.fault = fault;
        this.status = status;
    }
}
/**
 * Creates a new SDAPI client.
 */
export function createSdapiClient(config) {
    return new SdapiClient(config);
}
export class SdapiClient {
    baseUrl;
    headers;
    logger = getLogger();
    constructor(config) {
        this.baseUrl = `https://${config.hostname}/s/-/dw/debugger/v2_0`;
        const credentials = btoa(`${config.username}:${config.password}`);
        this.headers = {
            Authorization: `Basic ${credentials}`,
            'x-dw-client-id': config.clientId ?? 'b2c-cli',
            'Content-Type': 'application/json',
        };
    }
    // -----------------------------------------------------------------------
    // Client resource (debugger lifecycle)
    // -----------------------------------------------------------------------
    async createClient() {
        await this.request('POST', '/client', { expect204: true });
    }
    async deleteClient() {
        await this.request('DELETE', '/client', { expect204: true });
    }
    // -----------------------------------------------------------------------
    // Breakpoints
    // -----------------------------------------------------------------------
    async getBreakpoints() {
        const data = await this.request('GET', '/breakpoints');
        return data.breakpoints ?? [];
    }
    async setBreakpoints(breakpoints) {
        const data = await this.request('POST', '/breakpoints', {
            body: { breakpoints },
        });
        return data.breakpoints ?? [];
    }
    async deleteBreakpoints() {
        await this.request('DELETE', '/breakpoints', { expect204: true });
    }
    async deleteBreakpoint(id) {
        await this.request('DELETE', `/breakpoints/${id}`, { expect204: true });
    }
    // -----------------------------------------------------------------------
    // Threads
    // -----------------------------------------------------------------------
    async getThreads() {
        const data = await this.request('GET', '/threads');
        return data.script_threads ?? [];
    }
    async getThread(threadId) {
        return this.request('GET', `/threads/${threadId}`);
    }
    async resetThreads() {
        await this.request('POST', '/threads/reset', { expect204: true });
    }
    // -----------------------------------------------------------------------
    // Execution control
    // -----------------------------------------------------------------------
    async resume(threadId) {
        return this.request('POST', `/threads/${threadId}/resume`);
    }
    async stepOver(threadId) {
        return this.request('POST', `/threads/${threadId}/over`);
    }
    async stepInto(threadId) {
        return this.request('POST', `/threads/${threadId}/into`);
    }
    async stepOut(threadId) {
        return this.request('POST', `/threads/${threadId}/out`);
    }
    async stopThread(threadId) {
        await this.request('DELETE', `/threads/${threadId}/stop`, { expect204: true });
    }
    // -----------------------------------------------------------------------
    // Variables & evaluation
    // -----------------------------------------------------------------------
    async getVariables(threadId, frameIndex) {
        return this.request('GET', `/threads/${threadId}/frames/${frameIndex}/variables`);
    }
    async getMembers(threadId, frameIndex, objectPath, start, count) {
        const params = new URLSearchParams();
        if (objectPath)
            params.set('object_path', objectPath);
        if (start !== undefined)
            params.set('start', String(start));
        if (count !== undefined)
            params.set('count', String(count));
        const qs = params.toString();
        const suffix = qs ? `?${qs}` : '';
        return this.request('GET', `/threads/${threadId}/frames/${frameIndex}/members${suffix}`);
    }
    async evaluate(threadId, frameIndex, expr) {
        const params = new URLSearchParams({ expr });
        return this.request('GET', `/threads/${threadId}/frames/${frameIndex}/eval?${params.toString()}`);
    }
    // -----------------------------------------------------------------------
    // Internal
    // -----------------------------------------------------------------------
    async request(method, path, options) {
        const url = `${this.baseUrl}${path}`;
        this.logger.trace({ method, path }, 'SDAPI request');
        const response = await fetch(url, {
            method,
            headers: this.headers,
            body: options?.body ? JSON.stringify(options.body) : undefined,
        });
        if (options?.expect204) {
            if (!response.ok) {
                await this.throwSdapiError(response);
            }
            return undefined;
        }
        if (!response.ok) {
            await this.throwSdapiError(response);
        }
        return (await response.json());
    }
    async throwSdapiError(response) {
        let fault;
        try {
            const body = (await response.json());
            // SDAPI may return fault at top level or nested under a "fault" property
            const faultData = body.fault ?? body;
            fault = {
                _v: String(faultData._v ?? body._v ?? '2.0'),
                type: String(faultData.type ?? 'UnknownError'),
                message: String(faultData.message ?? `HTTP ${response.status} ${response.statusText}`),
            };
        }
        catch {
            fault = { _v: '2.0', type: 'UnknownError', message: `HTTP ${response.status} ${response.statusText}` };
        }
        throw new SdapiError(fault, response.status);
    }
}
//# sourceMappingURL=sdapi-client.js.map