// ==========================================
// Switchboard Core — SDK: REST Client
// ==========================================
// Internal HTTP engine for the new SDK.
// All domain clients and auth module use this.
//
// Factory: createRestClient(getToken, onRefresh, on401Failure)
// ==========================================

const BASE = window.__BASE__ || '';

/**
 * Build a normalized Error from a failed fetch response.
 * Detects proxy interception (HTML instead of JSON).
 */
async function _buildError(resp, method, path) {
    const ct = (resp.headers.get('content-type') || '').toLowerCase();

    // Proxy interception — response is HTML, not JSON
    if (ct.includes('text/html')) {
        let title = 'unknown';
        try {
            const html = await resp.text();
            const m = html.match(/<title[^>]*>([^<]+)<\/title>/i);
            if (m) title = m[1].trim();
        } catch (_) { /* */ }
        const err = new Error(`Proxy blocked ${method} ${path}: "${title}"`);
        err.status = resp.status;
        err.proxyBlocked = true;
        err.proxyTitle = title;
        return err;
    }

    // Standard JSON error
    const data = await resp.json().catch(() => ({}));
    const err = new Error(data.error || `HTTP ${resp.status}`);
    err.status = resp.status;
    if (data.code) err.code = data.code;
    if (data.details) err.details = data.details;
    return err;
}

/**
 * Create a REST client.
 *
 * @param {() => string|null} getToken — returns current access token
 * @param {() => Promise<boolean>} onRefresh — attempt token refresh, return success
 * @param {() => void} on401Failure — called when refresh fails (session dead)
 * @returns {object} client
 */
export function createRestClient(getToken, onRefresh, on401Failure) {

    async function _request(path, method, body, opts) {
        const _opts = opts || {};
        const headers = { 'Content-Type': 'application/json' };
        const token = getToken();
        if (token) headers['Authorization'] = `Bearer ${token}`;

        const fetchOpts = { method, headers };
        if (body !== undefined) fetchOpts.body = JSON.stringify(body);
        if (_opts.signal) fetchOpts.signal = _opts.signal;

        let resp = await fetch(BASE + path, fetchOpts);

        // 401 — try refresh once
        if (resp.status === 401) {
            const refreshed = await onRefresh();
            if (refreshed) {
                // Retry with new token
                const newToken = getToken();
                if (newToken) headers['Authorization'] = `Bearer ${newToken}`;
                const retryOpts = { method, headers };
                if (body !== undefined) retryOpts.body = JSON.stringify(body);
                if (_opts.signal) retryOpts.signal = _opts.signal;
                resp = await fetch(BASE + path, retryOpts);
            } else {
                on401Failure();
                throw await _buildError(resp, method, path);
            }
        }

        if (!resp.ok) throw await _buildError(resp, method, path);

        // 204 No Content
        if (resp.status === 204) return null;

        return _unwrap(resp);
    }

    /**
     * Unwrap response envelope.
     * List endpoints return { data: [...], page, per_page, total }.
     * Single-object endpoints return the object directly.
     */
    async function _unwrap(resp) {
        const text = await resp.text();
        if (!text) return null;
        const json = JSON.parse(text);

        // List envelope: { data: Array } with no sibling keys → unwrap.
        // If siblings exist (total, page, etc.) return full object so
        // callers can access metadata alongside the list.
        if (json && Array.isArray(json.data) && Object.keys(json).length === 1) {
            return json.data;
        }
        return json;
    }

    // ── Public API ──────────────────────────────

    const client = {
        get(path, opts) {
            return _request(path, 'GET', undefined, opts);
        },

        post(path, body, opts) {
            return _request(path, 'POST', body, opts);
        },

        put(path, body, opts) {
            return _request(path, 'PUT', body, opts);
        },

        patch(path, body, opts) {
            return _request(path, 'PATCH', body, opts);
        },

        del(path, opts) {
            return _request(path, 'DELETE', undefined, opts);
        },

        /**
         * Streaming POST — returns raw Response for SSE consumption.
         * No envelope unwrapping.
         */
        async stream(path, body, signal) {
            const headers = { 'Content-Type': 'application/json' };
            const token = getToken();
            if (token) headers['Authorization'] = `Bearer ${token}`;

            let resp = await fetch(BASE + path, {
                method: 'POST', headers,
                body: JSON.stringify(body),
                signal,
            });

            if (resp.status === 401) {
                const refreshed = await onRefresh();
                if (refreshed) {
                    const newToken = getToken();
                    if (newToken) headers['Authorization'] = `Bearer ${newToken}`;
                    resp = await fetch(BASE + path, {
                        method: 'POST', headers,
                        body: JSON.stringify(body),
                        signal,
                    });
                } else {
                    on401Failure();
                    throw await _buildError(resp, 'POST', path);
                }
            }

            if (!resp.ok) throw await _buildError(resp, 'POST', path);
            return resp;
        },

        /**
         * Multipart file upload.
         */
        async upload(path, file, opts) {
            const _opts = opts || {};
            const headers = {};
            const token = getToken();
            if (token) headers['Authorization'] = `Bearer ${token}`;
            // Don't set Content-Type — browser sets multipart boundary

            const form = new FormData();
            form.append('file', file);
            if (_opts.fields) {
                for (const [k, v] of Object.entries(_opts.fields)) {
                    form.append(k, v);
                }
            }

            let resp = await fetch(BASE + path, {
                method: 'POST', headers, body: form,
                signal: _opts.signal,
            });

            if (resp.status === 401) {
                const refreshed = await onRefresh();
                if (refreshed) {
                    const newToken = getToken();
                    if (newToken) headers['Authorization'] = `Bearer ${newToken}`;
                    resp = await fetch(BASE + path, {
                        method: 'POST', headers, body: form,
                        signal: _opts.signal,
                    });
                } else {
                    on401Failure();
                    throw await _buildError(resp, 'POST', path);
                }
            }

            if (!resp.ok) throw await _buildError(resp, 'POST', path);
            if (resp.status === 204) return null;
            return _unwrap(resp);
        },
    };

    return client;
}
