"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RequestRule = void 0;
const uuid_1 = require("uuid");
const types_1 = require("../../types");
const request_utils_1 = require("../../util/request-utils");
const matchers = require("../matchers");
const request_handlers_1 = require("./request-handlers");
const rule_serialization_1 = require("../rule-serialization");
class RequestRule {
    constructor(data) {
        this.requests = [];
        this.requestCount = 0;
        (0, rule_serialization_1.validateMockRuleData)(data);
        this.id = data.id || (0, uuid_1.v4)();
        this.priority = data.priority ?? types_1.RulePriority.DEFAULT;
        this.matchers = data.matchers;
        this.completionChecker = data.completionChecker;
        if ('handle' in data.handler) {
            this.handler = data.handler;
        }
        else {
            // We transform the definition into a real handler, by creating an instance of the raw handler (which is
            // a subtype of the definition with the same constructor) and copying the fields across.
            this.handler = Object.assign(Object.create(request_handlers_1.HandlerLookup[data.handler.type].prototype), data.handler);
        }
    }
    matches(request) {
        return matchers.matchesAll(request, this.matchers);
    }
    handle(req, res, options) {
        let handlerPromise = (async () => {
            return this.handler.handle(req, res, {
                emitEventCallback: options.emitEventCallback
            });
        })();
        // Requests are added to rule.requests as soon as they start being handled,
        // as promises, which resolve only when the response & request body is complete.
        if (options.record) {
            this.requests.push(Promise.race([
                // When the handler resolves, the request is completed:
                handlerPromise,
                // If the response is closed before the handler completes (due to aborts, handler
                // timeouts, whatever) then that also counts as the request being completed:
                new Promise((resolve) => res.on('close', resolve))
            ])
                .catch(() => { }) // Ignore handler errors here - we're only tracking the request
                .then(() => (0, request_utils_1.waitForCompletedRequest)(req))
                .catch(() => {
                // If for some reason the request is not completed, we still want to record it.
                // TODO: Update the body to return the data that has been received so far.
                const initiatedRequest = (0, request_utils_1.buildInitiatedRequest)(req);
                return {
                    ...initiatedRequest,
                    body: (0, request_utils_1.buildBodyReader)(Buffer.from([]), req.headers)
                };
            }));
        }
        // Even if traffic recording is disabled, the number of matched
        // requests is still tracked
        this.requestCount += 1;
        return handlerPromise;
    }
    isComplete() {
        if (this.completionChecker) {
            // If we have a specific rule, use that
            return this.completionChecker.isComplete(this.requestCount);
        }
        else if (this.requestCount === 0) {
            // Otherwise, by default we're definitely incomplete if we've seen no requests
            return false;
        }
        else {
            // And we're _maybe_ complete if we've seen at least one request. In reality, we're incomplete
            // but we should be used anyway if we're at any point we're the last matching rule for a request.
            return null;
        }
    }
    explain(withoutExactCompletion = false) {
        let explanation = `Match requests ${matchers.explainMatchers(this.matchers)}, ` +
            `and then ${this.handler.explain()}`;
        if (this.completionChecker) {
            explanation += `, ${this.completionChecker.explain(withoutExactCompletion ? undefined : this.requestCount)}.`;
        }
        else {
            explanation += '.';
        }
        return explanation;
    }
    dispose() {
        this.handler.dispose();
        this.matchers.forEach(m => m.dispose());
        if (this.completionChecker)
            this.completionChecker.dispose();
    }
}
exports.RequestRule = RequestRule;
//# sourceMappingURL=request-rule.js.map