var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _RedditAPI_instances, _RedditAPI_httpClient, _RedditAPI_mapArgsToUrl, _RedditAPI_getBaseUrl, _RedditAPI_getAuthToken, _RedditAPI_getHeaders, _RedditAPI_getBodyParams, _RedditAPI_getFetchRequest;
import { FetchRequest } from '@devvit/protos/types/devvit/plugin/http/http.js';
import { Header } from '@devvit/shared-types/Header.js';
import { isObject } from '@devvit/shared-types/isObject.js';
import { Buffer } from '../../lib/Buffer.js';
import { shouldRunAsUser } from '../utils/runAsUser.js';
import { RedditAPIMappings } from './apiMappings.js';
import { findAndThrowRedditAPIError } from './errors.js';
import { deepSnakeToCamel } from './snakeToCamel.js';
const GQL_FED_ENDPOINT = 'https://gql-fed.reddit.com';
const GQL_LEGACY_ENDPOINT = 'https://gql.reddit.com';
const camelToSnake = (str) => str.replace(/[A-Z]|(?<=[a-zA-Z])[\d]/g, (l) => `_${l.toLocaleLowerCase()}`);
export class RedditAPI {
    constructor(httpClient) {
        _RedditAPI_instances.add(this);
        _RedditAPI_httpClient.set(this, void 0);
        __classPrivateFieldSet(this, _RedditAPI_httpClient, httpClient, "f");
    }
    _decodeAndValidateResponseJSON(response, ignoreKeys = []) {
        if (response.status === 204) {
            // some endpoints return a 204 No Content for a non-op, but valid request,
            // so return an empty object to avoid a JSON parse error
            return {};
        }
        // Must wrap response.body in Buffer.from
        const body = JSON.parse(new TextDecoder().decode(Buffer.from(response.body)));
        // Throws on known error patterns.
        findAndThrowRedditAPIError(response.status, body);
        return deepSnakeToCamel(body, ignoreKeys);
    }
    _fetch(methodName, obj, metadata) {
        return __classPrivateFieldGet(this, _RedditAPI_httpClient, "f").Fetch(__classPrivateFieldGet(this, _RedditAPI_instances, "m", _RedditAPI_getFetchRequest).call(this, methodName, obj, metadata), metadata);
    }
    _gqlQueryWithQueryString(query, variables, metadata) {
        const host = metadata[Header.GQLHost]?.values[0] || GQL_FED_ENDPOINT;
        return this._gqlQuery(host, { query, variables }, metadata);
    }
    _gqlLegacyPersistedQuery(id, variables, metadata) {
        const host = metadata[Header.GQLHost]?.values[0] || GQL_LEGACY_ENDPOINT;
        return this._gqlQuery(host, { id, variables }, metadata);
    }
    _gqlFedPersistedQuery(operationName, hash, variables, metadata) {
        const extensions = {
            persistedQuery: {
                version: 1,
                sha256Hash: hash,
            },
        };
        const host = metadata[Header.GQLHost]?.values[0] || GQL_FED_ENDPOINT;
        return this._gqlQuery(host, { operationName, variables, extensions }, metadata);
    }
    _gqlQuery(endpoint, payload, metadata) {
        const authToken = metadata[Header.R2Auth]?.values[0];
        if (!authToken) {
            throw new Error('Must include auth header when calling the reddit api');
        }
        const request = FetchRequest.fromPartial({
            url: endpoint,
            data: {
                body: new TextEncoder().encode(JSON.stringify(payload)),
                headers: {
                    'Content-Type': 'application/json',
                    Authorization: `Bearer ${authToken}`,
                },
                method: 'POST',
            },
        });
        return __classPrivateFieldGet(this, _RedditAPI_httpClient, "f").Fetch(request, metadata);
    }
}
_RedditAPI_httpClient = new WeakMap(), _RedditAPI_instances = new WeakSet(), _RedditAPI_mapArgsToUrl = function _RedditAPI_mapArgsToUrl(url, obj) {
    return `${Object.entries(obj).reduce((p, c) => {
        const pattern = RegExp(`{([^}]+)?${c[0]}([^{]+)?}`);
        const value = (c[1] ?? null) !== null ? `$1${c[1]}$2` : '';
        return p.replace(pattern, value);
    }, url)}${url.indexOf('?') > 0 ? '&' : '?'}raw_json=1`;
}, _RedditAPI_getBaseUrl = function _RedditAPI_getBaseUrl(metadata) {
    const r2Host = metadata[Header.R2Host]?.values[0];
    if (r2Host) {
        // Exception for test server URLs
        if (r2Host.startsWith('http://127.0.0.1')) {
            return r2Host;
        }
        return `https://${r2Host}`;
    }
    throw new Error('unable to locate base url for reddit api');
}, _RedditAPI_getAuthToken = function _RedditAPI_getAuthToken(metadata, methodName) {
    const appName = metadata[Header.App]?.values[0];
    if (appName === undefined) {
        throw new Error('unable to derive appName from metadata');
    }
    if (shouldRunAsUser(appName, methodName)) {
        const appViewerToken = metadata[Header.AppViewerAuthToken]?.values[0];
        if (appViewerToken !== undefined) {
            return appViewerToken;
        }
        console.log('app viewer token not set');
    }
    return metadata[Header.R2Auth]?.values[0];
}, _RedditAPI_getHeaders = function _RedditAPI_getHeaders(mapping, metadata, methodName) {
    const authToken = __classPrivateFieldGet(this, _RedditAPI_instances, "m", _RedditAPI_getAuthToken).call(this, metadata, methodName);
    if (!authToken) {
        throw new Error('Must include auth header when calling the reddit api');
    }
    const headers = {};
    if (authToken) {
        headers['Authorization'] = `Bearer ${authToken}`;
    }
    switch (mapping.body) {
        case '*':
        case '*.snake':
            headers['Content-Type'] = 'application/json';
            break;
        case '*form':
        case '*form.snake':
            headers['Content-Type'] = 'application/x-www-form-urlencoded';
    }
    return headers;
}, _RedditAPI_getBodyParams = function _RedditAPI_getBodyParams(mapping, obj) {
    const { body: bodyType, enforceJson } = mapping;
    let body = '';
    const bodyObj = enforceJson ? enforceJsonOnBody(obj) : obj;
    switch (bodyType) {
        case '*': {
            body = JSON.stringify(bodyObj);
            break;
        }
        case '*.snake': {
            const tempBody = {};
            for (const key in bodyObj) {
                if (bodyObj[key] !== undefined) {
                    tempBody[camelToSnake(key)] = bodyObj[key];
                }
            }
            body = JSON.stringify(tempBody);
            break;
        }
        case '*form': {
            body = bodyToFormData(bodyObj);
            break;
        }
        case '*form.snake': {
            const tempBody = {};
            for (const key in bodyObj) {
                if (bodyObj[key] !== undefined) {
                    tempBody[camelToSnake(key)] = bodyObj[key];
                }
            }
            body = bodyToFormData(tempBody);
            break;
        }
    }
    return new TextEncoder().encode(body);
}, _RedditAPI_getFetchRequest = function _RedditAPI_getFetchRequest(methodName, obj, metadata) {
    const mapping = RedditAPIMappings[methodName];
    const url = __classPrivateFieldGet(this, _RedditAPI_instances, "m", _RedditAPI_mapArgsToUrl).call(this, `${__classPrivateFieldGet(this, _RedditAPI_instances, "m", _RedditAPI_getBaseUrl).call(this, metadata)}${mapping.path}`, obj);
    return FetchRequest.fromPartial({
        url,
        data: {
            body: __classPrivateFieldGet(this, _RedditAPI_instances, "m", _RedditAPI_getBodyParams).call(this, mapping, obj),
            headers: __classPrivateFieldGet(this, _RedditAPI_instances, "m", _RedditAPI_getHeaders).call(this, mapping, metadata, methodName),
            method: mapping.method,
        },
    });
};
// URLSearchParams isn't available in v8go,
// so this creates the formdata body
function bodyToFormData(obj) {
    // TODO: escape '&' and '=' in keys/values
    return Object.entries(obj)
        .map(([k, v]) => {
        return isObject(v)
            ? `${k}=${encodeURIComponent(JSON.stringify(v))}`
            : `${k}=${encodeURIComponent(v)}`;
    })
        .join('&');
}
// Adds the api_type=json property to a body object
function enforceJsonOnBody(obj) {
    return {
        ...obj,
        api_type: 'json',
    };
}
