/**
 * Cloudflare API operations factory.
 *
 * This module is imported as `import * as API from "../client/api.ts"` by
 * generated service files so that `API.make()`, `API.OperationMethod`, etc.
 * are all accessible as namespace members.
 */
import * as Effect from "effect/Effect";
import * as Schema from "effect/Schema";
import * as Stream from "effect/Stream";
import { makeAPI, } from "@distilled.cloud/core/client";
import { paginateCursor, paginateSingle, paginateWithDefaults, } from "@distilled.cloud/core/pagination";
import { getPath } from "@distilled.cloud/core/traits";
import { CloudflareHttpError, HTTP_STATUS_MAP, InternalServerError, TooManyRequests, UnknownCloudflareError, } from "../errors.js";
import { Credentials, formatHeaders } from "../credentials.js";
import { getErrorMatchers } from "../traits.js";
// ============================================================================
// Global Cloudflare error codes
// ============================================================================
/**
 * Cloudflare error codes that map to global/default errors regardless of operation.
 * These are infrastructure-level errors that can occur on any endpoint.
 */
const GLOBAL_ERROR_CODE_MAP = {
    // "Please wait and consider throttling your request speed"
    971: (message) => new TooManyRequests({ message }),
};
/**
 * Create an appropriate error for an HTTP status code.
 *
 * For status codes in HTTP_STATUS_MAP (400, 401, 500, 502, 503, 504, etc.),
 * returns the properly categorized error (with retry categories for 5xx).
 * For unmapped 5xx codes (e.g., Cloudflare-specific 520-530), returns a
 * CloudflareHttpError so the status is preserved.
 */
function httpStatusError(status, body) {
    const ErrorClass = HTTP_STATUS_MAP[status];
    const message = body ?? String(status);
    if (ErrorClass) {
        return new ErrorClass({ message });
    }
    // For unmapped 5xx codes (e.g., Cloudflare-specific 520-530), use
    // InternalServerError so they get ServerError + Retryable categories
    if (status >= 500) {
        return new InternalServerError({ message });
    }
    return new CloudflareHttpError({
        status,
        statusText: String(status),
        body,
        message,
    });
}
// ============================================================================
// Error matching
// ============================================================================
/**
 * Extract the _tag literal value from a TaggedError schema AST.
 */
function extractTagFromAst(ast) {
    if (ast.encoding && ast.encoding.length > 0) {
        return extractTagFromAst(ast.encoding[0].to);
    }
    if (ast._tag === "Objects") {
        const tagProp = ast.propertySignatures.find((p) => p.name === "_tag");
        if (tagProp &&
            tagProp.type._tag === "Literal" &&
            typeof tagProp.type.literal === "string") {
            return tagProp.type.literal;
        }
    }
    return undefined;
}
/**
 * Check if an error matches an expression-based matcher.
 */
function matchesExpression(matcher, code, status, message) {
    if (matcher.code === undefined && matcher.status === undefined)
        return false;
    if (matcher.code !== undefined && matcher.code !== code)
        return false;
    if (matcher.status !== undefined && matcher.status !== status)
        return false;
    if (matcher.message !== undefined) {
        if (typeof matcher.message === "string") {
            if (matcher.message !== message)
                return false;
        }
        else if (matcher.message.includes !== undefined) {
            if (!message.includes(matcher.message.includes))
                return false;
        }
    }
    return true;
}
/**
 * Calculate specificity score for a matcher.
 */
function matcherSpecificity(matcher) {
    let score = 0;
    if (matcher.code !== undefined)
        score += 1;
    if (matcher.status !== undefined)
        score += 1;
    if (matcher.message !== undefined)
        score += 1;
    return score;
}
/**
 * Find matching error schema using annotations from the schema AST.
 */
function findMatchingError(errorSchemas, code, status, message) {
    let bestMatch;
    let bestScore = 0;
    for (const [name, schema] of errorSchemas) {
        const ast = schema.ast;
        const matchers = getErrorMatchers(ast);
        if (!matchers || matchers.length === 0)
            continue;
        for (const matcher of matchers) {
            if (matchesExpression(matcher, code, status, message)) {
                const score = matcherSpecificity(matcher);
                if (score > bestScore) {
                    bestScore = score;
                    bestMatch = { schema, tag: name };
                }
            }
        }
    }
    return bestMatch;
}
/**
 * Match a Cloudflare API error response using per-operation error schemas.
 */
const matchError = (status, errorBody, errors) => {
    // Handle non-JSON error responses (e.g., HTML from malformed URLs, 520 pages)
    const isNonJsonError = typeof errorBody === "object" &&
        errorBody !== null &&
        "_nonJsonError" in errorBody;
    if (isNonJsonError) {
        const message = String(errorBody.body);
        // For 5xx errors, return a properly categorized error so retries work
        if (status >= 500) {
            return Effect.fail(httpStatusError(status, message));
        }
        return Effect.fail(new CloudflareHttpError({
            status,
            statusText: String(status),
            body: message,
            message,
        }));
    }
    // Parse the Cloudflare error envelope
    const envelope = typeof errorBody === "object" && errorBody !== null ? errorBody : undefined;
    const cfErrors = envelope && "errors" in envelope && Array.isArray(envelope.errors)
        ? envelope.errors
        : undefined;
    const firstError = cfErrors?.[0];
    // Cloudflare sometimes omits the code field entirely (e.g., webhook errors).
    // Treat missing code as 0 (the default) so matchers with { code: 0 } can match.
    const errorCode = firstError && typeof firstError.code === "number"
        ? firstError.code
        : firstError
            ? 0
            : undefined;
    const errorMessage = firstError?.message ?? String(status);
    // Check if this is a valid Cloudflare envelope (has success field)
    const isEnvelope = envelope && "success" in envelope && envelope.success === false;
    if (!isEnvelope) {
        // Not a Cloudflare envelope — HTTP-level error
        // For 5xx errors, return a properly categorized error so retries work
        const bodyStr = typeof errorBody === "string" ? errorBody : JSON.stringify(errorBody);
        if (status >= 500) {
            return Effect.fail(httpStatusError(status, bodyStr));
        }
        return Effect.fail(new CloudflareHttpError({
            status,
            statusText: String(status),
            body: bodyStr,
            message: bodyStr,
        }));
    }
    // Build error schema map from the per-operation errors
    if (errors && errors.length > 0) {
        const errorSchemas = new Map();
        for (const errorSchema of errors) {
            const identifier = extractTagFromAst(errorSchema.ast);
            if (identifier) {
                errorSchemas.set(identifier, errorSchema);
            }
        }
        const matched = findMatchingError(errorSchemas, errorCode, status, errorMessage);
        if (matched) {
            // Decode using the schema - properly instantiates TaggedError classes
            const errorData = {
                _tag: matched.tag,
                code: errorCode ?? 0,
                message: errorMessage,
            };
            return Schema.decodeUnknownEffect(matched.schema)(errorData).pipe(Effect.flatMap((decoded) => Effect.fail(decoded)), Effect.catchIf((e) => typeof e === "object" &&
                e !== null &&
                "_tag" in e &&
                e._tag === "SchemaError", () => Effect.fail(new UnknownCloudflareError({
                code: errorCode,
                message: errorMessage,
            }))));
        }
    }
    // Check global error codes before falling through to unknown
    if (errorCode !== undefined && errorCode in GLOBAL_ERROR_CODE_MAP) {
        return Effect.fail(GLOBAL_ERROR_CODE_MAP[errorCode](errorMessage));
    }
    // No match — return unknown Cloudflare error
    return Effect.fail(new UnknownCloudflareError({
        code: errorCode,
        message: errorMessage,
    }));
};
/**
 * Wrap schema decode failures as CloudflareHttpError to match the original's behavior.
 * The original project maps all decode errors to CloudflareHttpError (not a separate parse error type).
 */
class CloudflareDecodeError extends CloudflareHttpError {
    constructor(props) {
        const message = typeof props.body === "string" ? props.body : JSON.stringify(props.body);
        super({
            status: 200,
            statusText: "Schema decode failed",
            body: message,
            message,
        });
    }
}
const ASSET_UPLOAD_PATH = "/accounts/{account_id}/workers/assets/upload";
export const transformCloudflareRequestParts = ({ pathTemplate, parts, }) => {
    if (pathTemplate !== ASSET_UPLOAD_PATH) {
        return parts;
    }
    const authorization = parts.headers.Authorization;
    if (!authorization || authorization.startsWith("Bearer ")) {
        return parts;
    }
    return {
        ...parts,
        headers: {
            ...parts.headers,
            Authorization: `Bearer ${authorization}`,
        },
    };
};
const _API = makeAPI({
    credentials: Credentials,
    getBaseUrl: (creds) => creds.apiBaseUrl,
    getAuthHeaders: formatHeaders,
    matchError,
    ParseError: CloudflareDecodeError,
    transformRequestParts: ({ pathTemplate, parts }) => transformCloudflareRequestParts({ pathTemplate, parts }),
});
const paginatePageByItems = (operation, input, pagination) => {
    const inputToken = pagination.inputToken;
    if (!inputToken) {
        return Stream.die(new Error("Cloudflare page pagination requires an inputToken"));
    }
    const startPage = typeof input[inputToken] === "number" ? input[inputToken] : 1;
    return Stream.unfold({ page: startPage, done: false }, (state) => Effect.gen(function* () {
        if (state.done)
            return undefined;
        const requestPayload = { ...input, [inputToken]: state.page };
        const response = yield* operation(requestPayload);
        const items = pagination.items !== undefined
            ? getPath(response, pagination.items)
            : undefined;
        return [
            response,
            {
                page: state.page + 1,
                done: (items ?? []).length === 0,
            },
        ];
    }));
};
const cloudflarePaginate = (operation, input, pagination) => {
    switch (pagination.mode) {
        case "single":
            return paginateSingle(operation, input, pagination);
        case "page":
            return paginatePageByItems(operation, input, pagination);
        case "cursor":
            return paginateCursor(operation, input, pagination);
        case "token":
        default:
            return paginateWithDefaults(operation, input, pagination);
    }
};
export const make = _API.make;
export const makePaginated = ((configFn, paginateFn) => _API.makePaginated(configFn, paginateFn ?? cloudflarePaginate));
//# sourceMappingURL=api.js.map