"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const lodash_1 = require("./wrap/lodash");
const args_match_1 = require("./args-match");
const calls_1 = require("./store/calls");
const log_1 = require("./log");
const store_1 = require("./store");
const arguments_1 = require("./stringify/arguments");
const stubbings_1 = require("./store/stubbings");
const notify_after_satisfaction_1 = require("./matchers/notify-after-satisfaction");
const clone_deep_if_possible_1 = require("./clone-deep-if-possible");
const symbols_1 = require("./symbols");
exports.default = (__userDoesRehearsalInvocationHere__, config = {}) => {
    const last = calls_1.default.pop();
    ensureRehearsalOccurred(last);
    ensureCloneableIfCloneArgs(last, config);
    if (calls_1.default.wasInvoked(last.testDouble, last.args, config)) {
        notifyMatchers(last.testDouble, last.args, config);
        warnIfStubbed(last.testDouble, last.args);
    }
    else {
        log_1.default.fail(unsatisfiedErrorMessage(last.testDouble, last.args, config));
    }
};
const ensureRehearsalOccurred = (last) => {
    if (!last) {
        log_1.default.error('td.verify', `\
No test double invocation detected for \`verify()\`.

  Usage:
    verify(myTestDouble('foo'))\
`);
    }
};
function ensureCloneableIfCloneArgs(last, config) {
    if (config.cloneArgs && (0, clone_deep_if_possible_1.default)(last.args) === symbols_1.default.uncloneable) {
        return log_1.default.error('td.verify', `\
Failed to deep-clone arguments. Ensure lodash _.cloneDeep works on them
`);
    }
}
const notifyMatchers = (testDouble, expectedArgs, config) => {
    lodash_1.default.each(calls_1.default.where(testDouble, expectedArgs, config), (invocation) => {
        (0, notify_after_satisfaction_1.default)(expectedArgs, invocation.args);
    });
};
const warnIfStubbed = (testDouble, actualArgs) => {
    if (lodash_1.default.some(stubbings_1.default.for(testDouble), (stubbing) => (0, args_match_1.default)(stubbing.args, actualArgs, stubbing.config))) {
        log_1.default.warn('td.verify', `test double${stringifyName(testDouble)} was both stubbed and verified with arguments (${(0, arguments_1.default)(actualArgs)}), which is redundant and probably unnecessary.`, 'https://github.com/testdouble/testdouble.js/blob/main/docs/B-frequently-asked-questions.md#why-shouldnt-i-call-both-tdwhen-and-tdverify-for-a-single-interaction-with-a-test-double');
    }
};
const unsatisfiedErrorMessage = (testDouble, args, config) => baseSummary(testDouble, args, config) +
    matchedInvocationSummary(testDouble, args, config) +
    invocationSummary(testDouble, args, config);
const stringifyName = (testDouble) => {
    const name = store_1.default.for(testDouble).name;
    return name ? ` \`${name}\`` : '';
};
const baseSummary = (testDouble, args, config) => `\
Unsatisfied verification on test double${stringifyName(testDouble)}.

  Wanted:
    - called with \`(${(0, arguments_1.default)(args)})\`${timesMessage(config)}${ignoreMessage(config)}.\
`;
const invocationSummary = (testDouble, args, config) => {
    const calls = calls_1.default.for(testDouble);
    if (calls.length === 0) {
        return '\n\n  But there were no invocations of the test double.';
    }
    else {
        return lodash_1.default.reduce(calls, (desc, call) => desc + `\n    - called with \`(${(0, arguments_1.default)(call.args)})\`.`, '\n\n  All calls of the test double, in order were:');
    }
};
const matchedInvocationSummary = (testDouble, args, config) => {
    const calls = calls_1.default.where(testDouble, args, config);
    const expectedCalls = config.times || 0;
    if (calls.length === 0 || calls.length > expectedCalls) {
        return '';
    }
    else {
        return lodash_1.default.reduce(lodash_1.default.groupBy(calls, 'args'), (desc, callsMatchingArgs, args) => desc + `\n    - called ${pluralize(callsMatchingArgs.length, 'time')} with \`(${(0, arguments_1.default)(callsMatchingArgs[0].args)})\`.`, `\n\n  ${pluralize(calls.length, 'call')} that satisfied this verification:`);
    }
};
const pluralize = (x, msg) => `${x} ${msg}${x === 1 ? '' : 's'}`;
const timesMessage = (config) => config.times != null
    ? ` ${pluralize(config.times, 'time')}`
    : '';
const ignoreMessage = (config) => config.ignoreExtraArgs != null
    ? ', ignoring any additional arguments'
    : '';
