import { buildUrl, toServiceException } from "../_util.js";
import { StructuredError } from "../../error.js";
/**
 * Creates an {@link AbortSignal} that will abort after the specified timeout.
 *
 * @remarks
 * Falls back to a manual `AbortController` + `setTimeout` if `AbortSignal.timeout`
 * is not available in the runtime.
 *
 * @param ms - Timeout in milliseconds
 * @returns An abort signal that triggers after `ms` milliseconds
 *
 * @default 30000
 */
function createTimeoutSignal(ms = 30_000) {
    if (typeof AbortSignal.timeout === 'function') {
        return AbortSignal.timeout(ms);
    }
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), ms);
    controller.signal.addEventListener('abort', () => clearTimeout(timer), { once: true });
    return controller.signal;
}
/**
 * Thrown when the API returns a success HTTP status but the response body indicates failure.
 */
const WebhookResponseError = StructuredError('WebhookResponseError')();
/**
 * Client for the Agentuity Webhook service.
 *
 * Provides methods for creating and managing webhook endpoints that can receive
 * HTTP requests and forward them to configured destinations. The service supports:
 *
 * - **Webhooks**: Named endpoints with unique ingest URLs
 * - **Destinations**: URL-based targets that receive forwarded payloads
 * - **Receipts**: Records of incoming HTTP requests for auditing
 * - **Deliveries**: Records of forwarding attempts with retry support
 *
 * When an HTTP request hits a webhook's ingest URL, it is recorded as a receipt
 * and then delivered to all configured destinations. Failed deliveries can be
 * retried via {@link WebhookService.retryDelivery}.
 *
 * All methods are instrumented with OpenTelemetry spans for observability.
 *
 * @example
 * ```typescript
 * const webhooks = new WebhookService(baseUrl, adapter);
 *
 * // Create a webhook
 * const { webhook } = await webhooks.create({ name: 'GitHub Events' });
 * console.log('Ingest URL:', webhook.url);
 *
 * // Add a destination
 * await webhooks.createDestination(webhook.id, {
 *   type: 'url',
 *   config: { url: 'https://example.com/handle-github' },
 * });
 *
 * // Check delivery history
 * const { deliveries } = await webhooks.listDeliveries(webhook.id);
 * ```
 */
export class WebhookService {
    #adapter;
    #baseUrl;
    /**
     * Creates a new WebhookService instance.
     *
     * @param baseUrl - The base URL of the webhook API
     * @param adapter - The HTTP fetch adapter used for making API requests
     */
    constructor(baseUrl, adapter) {
        this.#adapter = adapter;
        this.#baseUrl = baseUrl;
    }
    /**
     * Unwrap a potentially nested response envelope. Some API responses wrap the data
     * in an additional `{ data: T }` layer; this method normalizes both shapes.
     *
     * @param raw - The raw response data to unwrap
     * @returns The unwrapped data of type `T`
     */
    #unwrap(raw) {
        if (raw !== null && typeof raw === 'object' && 'data' in raw) {
            return raw.data;
        }
        return raw;
    }
    /**
     * Create a new webhook endpoint.
     *
     * @param params - The webhook creation parameters including name and optional description
     * @returns The newly created webhook with its unique ingest URL
     * @throws {@link ServiceException} if the API request fails
     *
     * @example
     * ```typescript
     * const { webhook } = await webhooks.create({
     *   name: 'Stripe Events',
     *   description: 'Receives payment webhooks from Stripe',
     * });
     * console.log('Ingest URL:', webhook.url);
     * ```
     */
    async create(params) {
        const url = buildUrl(this.#baseUrl, '/webhook/create');
        const signal = createTimeoutSignal();
        const res = await this.#adapter.invoke(url, {
            method: 'POST',
            signal,
            body: JSON.stringify(params),
            contentType: 'application/json',
            telemetry: {
                name: 'agentuity.webhook.create',
                attributes: {
                    name: params.name,
                },
            },
        });
        if (res.ok) {
            if (res.data.success) {
                return { webhook: res.data.data };
            }
            throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
        }
        throw await toServiceException('POST', url, res.response);
    }
    /**
     * List all webhooks with optional pagination.
     *
     * @param params - Optional pagination parameters
     * @returns Paginated list of webhooks with total count
     * @throws {@link ServiceException} if the API request fails
     *
     * @example
     * ```typescript
     * const { webhooks, total } = await webhooks.list({ limit: 10, offset: 0 });
     * console.log(`Showing ${webhooks.length} of ${total} webhooks`);
     * for (const wh of webhooks) {
     *   console.log(`${wh.name}: ${wh.url}`);
     * }
     * ```
     */
    async list(params) {
        const qs = new URLSearchParams();
        if (params?.limit !== undefined) {
            qs.set('limit', String(params.limit));
        }
        if (params?.offset !== undefined) {
            qs.set('offset', String(params.offset));
        }
        const path = qs.toString() ? `/webhook/list?${qs.toString()}` : '/webhook/list';
        const url = buildUrl(this.#baseUrl, path);
        const signal = createTimeoutSignal();
        const res = await this.#adapter.invoke(url, {
            method: 'GET',
            signal,
            telemetry: {
                name: 'agentuity.webhook.list',
                attributes: {
                    limit: String(params?.limit ?? ''),
                    offset: String(params?.offset ?? ''),
                },
            },
        });
        if (res.ok) {
            if (res.data.success) {
                const unwrapped = this.#unwrap(res.data.data);
                if (Array.isArray(unwrapped)) {
                    return { webhooks: unwrapped, total: unwrapped.length };
                }
                const arr = Array.isArray(unwrapped.data) ? unwrapped.data : [];
                return { webhooks: arr, total: unwrapped.total ?? arr.length };
            }
            throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
        }
        throw await toServiceException('GET', url, res.response);
    }
    /**
     * Get a webhook by its ID, including its configured destinations.
     *
     * @param webhookId - The unique webhook identifier
     * @returns The webhook and its list of destinations
     * @throws {@link ServiceException} if the API request fails
     *
     * @example
     * ```typescript
     * const { webhook, destinations } = await webhooks.get('wh_abc123');
     * console.log(`${webhook.name} has ${destinations.length} destination(s)`);
     * ```
     */
    async get(webhookId) {
        const url = buildUrl(this.#baseUrl, `/webhook/get/${encodeURIComponent(webhookId)}`);
        const signal = createTimeoutSignal();
        const res = await this.#adapter.invoke(url, {
            method: 'GET',
            signal,
            telemetry: {
                name: 'agentuity.webhook.get',
                attributes: {
                    webhookId,
                },
            },
        });
        if (res.ok) {
            if (res.data.success) {
                const { destinations } = await this.listDestinations(webhookId);
                return { webhook: res.data.data, destinations };
            }
            throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
        }
        throw await toServiceException('GET', url, res.response);
    }
    /**
     * Update a webhook's name and/or description.
     *
     * @param webhookId - The unique webhook identifier
     * @param params - Fields to update; only provided fields are changed
     * @returns The updated webhook
     * @throws {@link ServiceException} if the API request fails
     *
     * @example
     * ```typescript
     * const { webhook } = await webhooks.update('wh_abc123', {
     *   name: 'GitHub Events (Production)',
     *   description: 'Production webhook for GitHub push events',
     * });
     * console.log('Updated:', webhook.name);
     * ```
     */
    async update(webhookId, params) {
        const url = buildUrl(this.#baseUrl, `/webhook/update/${encodeURIComponent(webhookId)}`);
        const signal = createTimeoutSignal();
        const res = await this.#adapter.invoke(url, {
            method: 'PUT',
            signal,
            body: JSON.stringify(params),
            contentType: 'application/json',
            telemetry: {
                name: 'agentuity.webhook.update',
                attributes: {
                    webhookId,
                },
            },
        });
        if (res.ok) {
            if (res.data.success) {
                return { webhook: res.data.data };
            }
            throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
        }
        throw await toServiceException('PUT', url, res.response);
    }
    /**
     * Delete a webhook and all its associated destinations, receipts, and deliveries.
     *
     * @param webhookId - The unique webhook identifier
     * @throws {@link ServiceException} if the API request fails
     *
     * @example
     * ```typescript
     * await webhooks.delete('wh_abc123');
     * console.log('Webhook deleted');
     * ```
     */
    async delete(webhookId) {
        const url = buildUrl(this.#baseUrl, `/webhook/delete/${encodeURIComponent(webhookId)}`);
        const signal = createTimeoutSignal();
        const res = await this.#adapter.invoke(url, {
            method: 'DELETE',
            signal,
            telemetry: {
                name: 'agentuity.webhook.delete',
                attributes: {
                    webhookId,
                },
            },
        });
        if (res.ok) {
            if (res.data?.success !== false) {
                return;
            }
            throw new WebhookResponseError({
                status: res.response.status,
                message: res.data?.message ?? 'Delete failed',
            });
        }
        throw await toServiceException('DELETE', url, res.response);
    }
    /**
     * Create a new destination for a webhook. When the webhook receives a request,
     * the payload will be forwarded to this destination.
     *
     * @param webhookId - The ID of the parent webhook
     * @param params - Destination configuration including type and type-specific config
     * @returns The newly created destination
     * @throws {@link ServiceException} if the API request fails
     *
     * @example
     * ```typescript
     * const { destination } = await webhooks.createDestination('wh_abc123', {
     *   type: 'url',
     *   config: {
     *     url: 'https://example.com/webhook-handler',
     *     headers: { 'X-Custom-Header': 'my-value' },
     *   },
     * });
     * console.log('Destination created:', destination.id);
     * ```
     */
    async createDestination(webhookId, params) {
        const url = buildUrl(this.#baseUrl, `/webhook/destination-create/${encodeURIComponent(webhookId)}`);
        const signal = createTimeoutSignal();
        const res = await this.#adapter.invoke(url, {
            method: 'POST',
            signal,
            body: JSON.stringify(params),
            contentType: 'application/json',
            telemetry: {
                name: 'agentuity.webhook.createDestination',
                attributes: {
                    webhookId,
                    type: params.type,
                },
            },
        });
        if (res.ok) {
            if (res.data.success) {
                return { destination: res.data.data };
            }
            throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
        }
        throw await toServiceException('POST', url, res.response);
    }
    /**
     * List all destinations configured for a webhook.
     *
     * @param webhookId - The ID of the webhook
     * @returns List of destinations
     * @throws {@link ServiceException} if the API request fails
     *
     * @example
     * ```typescript
     * const { destinations } = await webhooks.listDestinations('wh_abc123');
     * for (const dest of destinations) {
     *   console.log(`${dest.type}: ${JSON.stringify(dest.config)}`);
     * }
     * ```
     */
    async listDestinations(webhookId) {
        const url = buildUrl(this.#baseUrl, `/webhook/destination-list/${encodeURIComponent(webhookId)}`);
        const signal = createTimeoutSignal();
        const res = await this.#adapter.invoke(url, {
            method: 'GET',
            signal,
            telemetry: {
                name: 'agentuity.webhook.listDestinations',
                attributes: {
                    webhookId,
                },
            },
        });
        if (res.ok) {
            if (res.data.success) {
                return { destinations: Array.isArray(res.data.data) ? res.data.data : [] };
            }
            throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
        }
        throw await toServiceException('GET', url, res.response);
    }
    /**
     * Delete a destination from a webhook.
     *
     * @param webhookId - The ID of the parent webhook
     * @param destinationId - The ID of the destination to delete
     * @throws {@link ServiceException} if the API request fails
     *
     * @example
     * ```typescript
     * await webhooks.deleteDestination('wh_abc123', 'dest_xyz789');
     * console.log('Destination deleted');
     * ```
     */
    async deleteDestination(webhookId, destinationId) {
        const url = buildUrl(this.#baseUrl, `/webhook/destination-delete/${encodeURIComponent(webhookId)}/${encodeURIComponent(destinationId)}`);
        const signal = createTimeoutSignal();
        const res = await this.#adapter.invoke(url, {
            method: 'DELETE',
            signal,
            telemetry: {
                name: 'agentuity.webhook.deleteDestination',
                attributes: {
                    webhookId,
                    destinationId,
                },
            },
        });
        if (res.ok) {
            if (res.data?.success !== false) {
                return;
            }
            throw new WebhookResponseError({
                status: res.response.status,
                message: res.data?.message ?? 'Delete destination failed',
            });
        }
        throw await toServiceException('DELETE', url, res.response);
    }
    /**
     * List receipts (records of incoming HTTP requests) for a webhook.
     *
     * @param webhookId - The ID of the webhook
     * @param params - Optional pagination parameters
     * @returns List of receipt records
     * @throws {@link ServiceException} if the API request fails
     *
     * @example
     * ```typescript
     * const { receipts } = await webhooks.listReceipts('wh_abc123', {
     *   limit: 50,
     * });
     * for (const receipt of receipts) {
     *   console.log(`${receipt.date}: ${JSON.stringify(receipt.headers)}`);
     * }
     * ```
     */
    async listReceipts(webhookId, params) {
        const qs = new URLSearchParams();
        if (params?.limit !== undefined) {
            qs.set('limit', String(params.limit));
        }
        if (params?.offset !== undefined) {
            qs.set('offset', String(params.offset));
        }
        const basePath = `/webhook/receipt-list/${encodeURIComponent(webhookId)}`;
        const path = qs.toString() ? `${basePath}?${qs.toString()}` : basePath;
        const url = buildUrl(this.#baseUrl, path);
        const signal = createTimeoutSignal();
        const res = await this.#adapter.invoke(url, {
            method: 'GET',
            signal,
            telemetry: {
                name: 'agentuity.webhook.listReceipts',
                attributes: {
                    webhookId,
                    limit: String(params?.limit ?? ''),
                    offset: String(params?.offset ?? ''),
                },
            },
        });
        if (res.ok) {
            if (res.data.success) {
                return { receipts: Array.isArray(res.data.data) ? res.data.data : [] };
            }
            throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
        }
        throw await toServiceException('GET', url, res.response);
    }
    /**
     * Get a single receipt by its ID, including the full payload.
     *
     * @param webhookId - The ID of the webhook
     * @param receiptId - The unique receipt identifier
     * @returns The receipt record with headers and payload
     * @throws {@link ServiceException} if the API request fails
     *
     * @example
     * ```typescript
     * const receipt = await webhooks.getReceipt('wh_abc123', 'rcpt_def456');
     * console.log('Payload:', receipt.payload);
     * console.log('Headers:', receipt.headers);
     * ```
     */
    async getReceipt(webhookId, receiptId) {
        const url = buildUrl(this.#baseUrl, `/webhook/receipt-get/${encodeURIComponent(webhookId)}/${encodeURIComponent(receiptId)}`);
        const signal = createTimeoutSignal();
        const res = await this.#adapter.invoke(url, {
            method: 'GET',
            signal,
            telemetry: {
                name: 'agentuity.webhook.getReceipt',
                attributes: {
                    webhookId,
                    receiptId,
                },
            },
        });
        if (res.ok) {
            if (res.data.success) {
                return res.data.data;
            }
            throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
        }
        throw await toServiceException('GET', url, res.response);
    }
    /**
     * List delivery attempts for a webhook with optional pagination.
     *
     * @param webhookId - The ID of the webhook
     * @param params - Optional pagination parameters
     * @returns List of delivery records showing forwarding status
     * @throws {@link ServiceException} if the API request fails
     *
     * @example
     * ```typescript
     * const { deliveries } = await webhooks.listDeliveries('wh_abc123');
     * for (const d of deliveries) {
     *   console.log(`${d.status} → dest ${d.webhook_destination_id} (retries: ${d.retries})`);
     * }
     * ```
     */
    async listDeliveries(webhookId, params) {
        const qs = new URLSearchParams();
        if (params?.limit !== undefined) {
            qs.set('limit', String(params.limit));
        }
        if (params?.offset !== undefined) {
            qs.set('offset', String(params.offset));
        }
        const basePath = `/webhook/delivery-list/${encodeURIComponent(webhookId)}`;
        const path = qs.toString() ? `${basePath}?${qs.toString()}` : basePath;
        const url = buildUrl(this.#baseUrl, path);
        const signal = createTimeoutSignal();
        const res = await this.#adapter.invoke(url, {
            method: 'GET',
            signal,
            telemetry: {
                name: 'agentuity.webhook.listDeliveries',
                attributes: {
                    webhookId,
                    limit: String(params?.limit ?? ''),
                    offset: String(params?.offset ?? ''),
                },
            },
        });
        if (res.ok) {
            if (res.data.success) {
                return { deliveries: Array.isArray(res.data.data) ? res.data.data : [] };
            }
            throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
        }
        throw await toServiceException('GET', url, res.response);
    }
    /**
     * Retry a failed delivery attempt. Only deliveries with `'failed'` status can be retried.
     *
     * @param webhookId - The ID of the parent webhook
     * @param deliveryId - The ID of the failed delivery to retry
     * @throws {@link ServiceException} if the API request fails
     *
     * @example
     * ```typescript
     * // Find and retry failed deliveries
     * const { deliveries } = await webhooks.listDeliveries('wh_abc123');
     * for (const d of deliveries) {
     *   if (d.status === 'failed') {
     *     await webhooks.retryDelivery('wh_abc123', d.id);
     *     console.log('Retrying delivery:', d.id);
     *   }
     * }
     * ```
     */
    async retryDelivery(webhookId, deliveryId) {
        const url = buildUrl(this.#baseUrl, `/webhook/delivery-retry/${encodeURIComponent(webhookId)}/${encodeURIComponent(deliveryId)}`);
        const signal = createTimeoutSignal();
        const res = await this.#adapter.invoke(url, {
            method: 'POST',
            signal,
            telemetry: {
                name: 'agentuity.webhook.retryDelivery',
                attributes: {
                    webhookId,
                    deliveryId,
                },
            },
        });
        if (res.ok) {
            if (res.data?.success !== false) {
                return;
            }
            throw new WebhookResponseError({
                status: res.response.status,
                message: res.data?.message ?? 'Retry delivery failed',
            });
        }
        throw await toServiceException('POST', url, res.response);
    }
    /**
     * Get org-level webhook analytics summary.
     *
     * Returns total received, delivered, and failed counts for all webhooks
     * in the org within the specified time period.
     *
     * @param options - Analytics options (start, end, granularity)
     * @returns Org-level webhook analytics with period and summary
     * @throws {@link ServiceException} if the API request fails
     */
    async getOrgAnalytics(options) {
        const params = new URLSearchParams();
        if (options?.start)
            params.set('start', options.start);
        if (options?.end)
            params.set('end', options.end);
        if (options?.granularity)
            params.set('granularity', options.granularity);
        const query = params.toString();
        const path = query ? `/webhook/analytics/org?${query}` : '/webhook/analytics/org';
        const url = buildUrl(this.#baseUrl, path);
        const signal = createTimeoutSignal();
        const res = await this.#adapter.invoke(url, {
            method: 'GET',
            signal,
            telemetry: { name: 'agentuity.webhook.analytics.org' },
        });
        if (res.ok) {
            if (res.data.success) {
                const unwrapped = this.#unwrap(res.data.data);
                return unwrapped.analytics;
            }
            throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
        }
        throw await toServiceException('GET', url, res.response);
    }
    /**
     * Get org-level webhook time series data.
     *
     * Returns time-bucketed received, delivered, and failed counts for all webhooks
     * in the org within the specified time period.
     *
     * @param options - Analytics options (start, end, granularity)
     * @returns Webhook time series data with period and series array
     * @throws {@link ServiceException} if the API request fails
     */
    async getOrgTimeSeries(options) {
        const params = new URLSearchParams();
        if (options?.start)
            params.set('start', options.start);
        if (options?.end)
            params.set('end', options.end);
        if (options?.granularity)
            params.set('granularity', options.granularity);
        const query = params.toString();
        const path = query
            ? `/webhook/analytics/org/timeseries?${query}`
            : '/webhook/analytics/org/timeseries';
        const url = buildUrl(this.#baseUrl, path);
        const signal = createTimeoutSignal();
        const res = await this.#adapter.invoke(url, {
            method: 'GET',
            signal,
            telemetry: { name: 'agentuity.webhook.analytics.org.timeseries' },
        });
        if (res.ok) {
            if (res.data.success) {
                const unwrapped = this.#unwrap(res.data.data);
                return unwrapped.timeseries;
            }
            throw new WebhookResponseError({ status: res.response.status, message: res.data.message });
        }
        throw await toServiceException('GET', url, res.response);
    }
}
//# sourceMappingURL=service.js.map