/**
 * Lightweight HTTP client for talking to a running Markus API server.
 * Used by all CLI commands except `start`, `init`, and `db:init`.
 */
export class ApiError extends Error {
    status;
    body;
    constructor(status, body) {
        const msg = typeof body === 'object' && body !== null && 'error' in body
            ? body.error
            : JSON.stringify(body);
        super(`API ${status}: ${msg}`);
        this.status = status;
        this.body = body;
        this.name = 'ApiError';
    }
}
export class ApiClient {
    baseUrl;
    headers;
    constructor(opts) {
        this.baseUrl = opts.server.replace(/\/+$/, '');
        this.headers = { 'Content-Type': 'application/json' };
        if (opts.apiKey) {
            this.headers['Authorization'] = `Bearer ${opts.apiKey}`;
        }
    }
    async get(path, query) {
        const url = this.buildUrl(path, query);
        return this.request(url, { method: 'GET' });
    }
    async post(path, body) {
        return this.request(this.buildUrl(path), {
            method: 'POST',
            body: body !== undefined ? JSON.stringify(body) : undefined,
        });
    }
    async put(path, body) {
        return this.request(this.buildUrl(path), {
            method: 'PUT',
            body: body !== undefined ? JSON.stringify(body) : undefined,
        });
    }
    async patch(path, body) {
        return this.request(this.buildUrl(path), {
            method: 'PATCH',
            body: body !== undefined ? JSON.stringify(body) : undefined,
        });
    }
    async delete(path, query) {
        return this.request(this.buildUrl(path, query), { method: 'DELETE' });
    }
    buildUrl(path, query) {
        const url = new URL(`/api${path}`, this.baseUrl);
        if (query) {
            for (const [k, v] of Object.entries(query)) {
                if (v !== undefined)
                    url.searchParams.set(k, v);
            }
        }
        return url.toString();
    }
    async request(url, init) {
        const headers = { ...this.headers };
        // Always send Content-Type even for empty-body POST (e.g., /tasks/:id/approve)
        // The server rejects requests without Content-Type with HTTP 415.
        // We explicitly set it to 'application/json' for consistency.
        if (init.body === undefined || init.body === null) {
            // Ensure Content-Type is present even for no-body requests
        }
        let res;
        try {
            res = await fetch(url, { ...init, headers });
        }
        catch (err) {
            throw new Error(`Cannot connect to Markus server at ${this.baseUrl}. Is it running? (${err})`);
        }
        if (!res.ok) {
            let body;
            try {
                body = await res.json();
            }
            catch {
                body = await res.text();
            }
            throw new ApiError(res.status, body);
        }
        const text = await res.text();
        if (!text)
            return undefined;
        return JSON.parse(text);
    }
}
/** Resolve ApiClient from commander global opts */
export function createClient(opts) {
    const server = opts.server || process.env['MARKUS_API_URL'] || 'http://localhost:8056';
    const apiKey = opts.apiKey || process.env['MARKUS_API_KEY'];
    return new ApiClient({ server, apiKey });
}
//# sourceMappingURL=api-client.js.map