public function calculate(\core_analytics\local\time_splitting\base $timesplitting, array &$sampleids,
            array $ranges, \core_analytics\local\target\base $target): ?array {

        $calculatedtarget = null;
        if ($this->includetarget) {
            // We first calculate the target because analysable data may still be invalid or none
            // of the analysable samples may be valid.
            $calculatedtarget = $target->calculate($sampleids, $timesplitting->get_analysable());

            // We remove samples we can not calculate their target.
            $sampleids = array_filter($sampleids, function($sampleid) use ($calculatedtarget) {
                if (is_null($calculatedtarget[$sampleid])) {
                    return false;
                }
                return true;
            });
        }

        // No need to continue calculating if the target couldn't be calculated for any sample.
        if (empty($sampleids)) {
            return null;
        }

        $dataset = $this->calculate_indicators($timesplitting, $sampleids, $ranges);

        if (empty($dataset)) {
            return null;
        }

        // Now that we have the indicators in place we can add the time range indicators (and target if provided) to each of them.
        $this->fill_dataset($timesplitting, $dataset, $calculatedtarget);

        $this->add_context_metadata($timesplitting, $dataset, $target);

        if (!PHPUNIT_TEST && CLI_SCRIPT) {
            echo PHP_EOL;
        }

        return $dataset;
    }