<?php

/**
 * Matomo - free/libre analytics platform
 *
 * @link    https://matomo.org
 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
 */

namespace Piwik\Plugins\Events;

use Piwik\Archive;
use Piwik\DataTable;
use Piwik\Metrics;
use Piwik\Piwik;

/**
 * The Events API lets you request reports about your users' Custom Events.
 *
 * Events are tracked using the Javascript Tracker trackEvent() function, or using the [Tracking HTTP API](https://developer.matomo.org/api-reference/tracking-api).
 *
 * <br/>An event is defined by an event category (Videos, Music, Games...),
 * an event action (Play, Pause, Duration, Add Playlist, Downloaded, Clicked...),
 * and an optional event name (a movie name, a song title, etc.) and an optional numeric value.
 *
 * <br/>This API exposes the following Custom Events reports: `getCategory` lists the top Event Categories,
 * `getAction` lists the top Event Actions, `getName` lists the top Event Names.
 *
 * <br/>These Events report define the following metrics: nb_uniq_visitors, nb_visits, nb_events.
 * If you define values for your events, you can expect to see the following metrics: nb_events_with_value,
 * sum_event_value, min_event_value, max_event_value, avg_event_value
 *
 * <br/>The Events.get* reports can be used with an optional `&secondaryDimension` parameter.
 * Secondary dimension is the dimension used in the sub-table of the Event report you are requesting.
 *
 * <br/>Here are the possible values of `secondaryDimension`: <ul>
 * <li>For `Events.getCategory` you can set `secondaryDimension` to `eventAction` or `eventName`.</li>
 * <li>For `Events.getAction` you can set `secondaryDimension` to `eventName` or `eventCategory`.</li>
 * <li>For `Events.getName` you can set `secondaryDimension` to `eventAction` or `eventCategory`.</li>
 * </ul>
 *
 * <br/>For example, to request all Custom Events Categories, and for each, the top Event actions,
 * you would request: `method=Events.getCategory&secondaryDimension=eventAction&flat=1`.
 * You may also omit `&flat=1` in which case, to get top Event actions for one Event category,
 * use `method=Events.getActionFromCategoryId` passing it the `&idSubtable=` of this Event category.
 *
 * @method static \Piwik\Plugins\Events\API getInstance()
 */
class API extends \Piwik\Plugin\API
{
    /**
     * @var array<string, string>
     */
    protected $defaultMappingApiToSecondaryDimension = array(
        'getCategory' => 'eventAction',
        'getAction'   => 'eventName',
        'getName'     => 'eventAction',
    );

    /**
     * @var array<string, string|array<string, string>>
     */
    protected $mappingApiToRecord = array(
        'getCategory'             =>
            array(
                'eventAction' => Archiver::EVENTS_CATEGORY_ACTION_RECORD_NAME,
                'eventName'   => Archiver::EVENTS_CATEGORY_NAME_RECORD_NAME,
            ),
        'getAction'               =>
            array(
                'eventName'     => Archiver::EVENTS_ACTION_NAME_RECORD_NAME,
                'eventCategory' => Archiver::EVENTS_ACTION_CATEGORY_RECORD_NAME,
            ),
        'getName'                 =>
            array(
                'eventAction'   => Archiver::EVENTS_NAME_ACTION_RECORD_NAME,
                'eventCategory' => Archiver::EVENTS_NAME_CATEGORY_RECORD_NAME,
            ),
        'getActionFromCategoryId' => Archiver::EVENTS_CATEGORY_ACTION_RECORD_NAME,
        'getNameFromCategoryId'   => Archiver::EVENTS_CATEGORY_NAME_RECORD_NAME,
        'getCategoryFromActionId' => Archiver::EVENTS_ACTION_CATEGORY_RECORD_NAME,
        'getNameFromActionId'     => Archiver::EVENTS_ACTION_NAME_RECORD_NAME,
        'getActionFromNameId'     => Archiver::EVENTS_NAME_ACTION_RECORD_NAME,
        'getCategoryFromNameId'   => Archiver::EVENTS_NAME_CATEGORY_RECORD_NAME,
    );

    /**
     * @ignore
     * @param string $apiMethod
     * @param string|false $secondaryDimension
     * @return string|false
     */
    public function getActionToLoadSubtables($apiMethod, $secondaryDimension = false)
    {
        $recordName = $this->getRecordNameForAction($apiMethod, $secondaryDimension);
        $apiMethod = array_search($recordName, $this->mappingApiToRecord);
        return $apiMethod;
    }

    /**
     * @ignore
     * @param string $apiMethod
     * @return string|false
     */
    public function getDefaultSecondaryDimension($apiMethod)
    {
        if (isset($this->defaultMappingApiToSecondaryDimension[$apiMethod])) {
            return $this->defaultMappingApiToSecondaryDimension[$apiMethod];
        }
        return false;
    }

    /**
     * @param string $apiMethod
     * @param string|false $secondaryDimension
     * @return string
     */
    protected function getRecordNameForAction($apiMethod, $secondaryDimension = false)
    {
        if (empty($secondaryDimension)) {
            $secondaryDimension = $this->getDefaultSecondaryDimension($apiMethod);
        }
        $record = $this->mappingApiToRecord[$apiMethod];
        if (!is_array($record)) {
            return $record;
        }
        // when secondaryDimension is incorrectly set
        if (empty($record[$secondaryDimension])) {
            return key($record);
        }
        return $record[$secondaryDimension];
    }

    /**
     * @ignore
     * @param string $apiMethod
     * @return string[]|false
     */
    public function getSecondaryDimensions($apiMethod)
    {
        $records = $this->mappingApiToRecord[$apiMethod];
        if (!is_array($records)) {
            return false;
        }
        return array_keys($records);
    }

    /**
     * @param string|false $secondaryDimension
     */
    protected function checkSecondaryDimension(string $apiMethod, $secondaryDimension): void
    {
        if (empty($secondaryDimension)) {
            return;
        }

        $isSecondaryDimensionValid =
            isset($this->mappingApiToRecord[$apiMethod])
            && isset($this->mappingApiToRecord[$apiMethod][$secondaryDimension]);

        if (!$isSecondaryDimensionValid) {
            throw new \Exception(
                "Secondary dimension '$secondaryDimension' is not valid for the API $apiMethod. " .
                "Use one of: " . implode(", ", $this->getSecondaryDimensions($apiMethod) ?: [])
            );
        }
    }

    /**
     * @param int|string|int[] $idSite
     * @param 'day'|'week'|'month'|'year'|'range' $period
     * @param string|null|false $segment
     * @param int|null|false $idSubtable
     * @param string|false $secondaryDimension
     * @return DataTable|DataTable\Map
     */
    protected function getDataTable(string $name, $idSite, string $period, string $date, $segment, bool $expanded = false, $idSubtable = null, $secondaryDimension = false, bool $flat = false)
    {
        Piwik::checkUserHasViewAccess($idSite);
        $this->checkSecondaryDimension($name, $secondaryDimension);
        $recordName = $this->getRecordNameForAction($name, $secondaryDimension);

        $dataTable = Archive::createDataTableFromArchive($recordName, $idSite, $period, $date, $segment ?: '', $expanded, $flat, $idSubtable);

        $dataTable->filter(function ($dataTable) {
            $dataTable->setMetadata(DataTable::COLUMN_AGGREGATION_OPS_METADATA_NAME, [
                Metrics::INDEX_EVENT_MIN_EVENT_VALUE => 'min',
                Metrics::INDEX_EVENT_MAX_EVENT_VALUE => 'max',
            ]);
        });

        if ($flat) {
            $dataTable->filterSubtables('Piwik\Plugins\Events\DataTable\Filter\ReplaceEventNameNotSet');
        } else {
            $dataTable->filter('AddSegmentValue', array(function ($label) {
                if ($label === Archiver::EVENT_NAME_NOT_SET) {
                    return false;
                }

                return $label;
            }));
        }

        $dataTable->filter('Piwik\Plugins\Events\DataTable\Filter\ReplaceEventNameNotSet');

        return $dataTable;
    }

    /**
     * Returns event metrics grouped by event category.
     *
     * @param int|string|int[] $idSite Website ID(s) to query.
     *                                 - Single site ID (e.g. 1)
     *                                 - Multiple site IDs (e.g. [1, 4, 5])
     *                                 - Comma-separated list ("1,4,5") or "all"
     * @param 'day'|'week'|'month'|'year'|'range' $period The period to process, processes data for the period
     *                                                    containing the specified date.
     * @param string $date The date or date range to process.
     *                     'YYYY-MM-DD', magic keywords (today, yesterday, lastWeek, lastMonth, lastYear),
     *                     or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD', lastX, previousX).
     * @param string|null|false $segment Custom segment to filter the report.
     *                                   Example: "referrerName==example.com"
     *                                   Supports AND (;) and OR (,) operators.
     * @param bool $expanded Whether subtables should be expanded in the response.
     * @param 'eventAction'|'eventName'|false $secondaryDimension Optional secondary dimension for subtable rows.
     * @param bool $flat Whether subtable rows should be flattened into a single table.
     * @return DataTable|DataTable\Map Event category metrics.
     */
    public function getCategory($idSite, string $period, string $date, $segment = false, bool $expanded = false, $secondaryDimension = false, bool $flat = false)
    {
        return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, $expanded, false, $secondaryDimension, $flat);
    }

    /**
     * Returns event metrics grouped by event action.
     *
     * @param int|string|int[] $idSite Website ID(s) to query.
     *                                 - Single site ID (e.g. 1)
     *                                 - Multiple site IDs (e.g. [1, 4, 5])
     *                                 - Comma-separated list ("1,4,5") or "all"
     * @param 'day'|'week'|'month'|'year'|'range' $period The period to process, processes data for the period
     *                                                    containing the specified date.
     * @param string $date The date or date range to process.
     *                     'YYYY-MM-DD', magic keywords (today, yesterday, lastWeek, lastMonth, lastYear),
     *                     or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD', lastX, previousX).
     * @param string|null|false $segment Custom segment to filter the report.
     *                                   Example: "referrerName==example.com"
     *                                   Supports AND (;) and OR (,) operators.
     * @param bool $expanded Whether subtables should be expanded in the response.
     * @param 'eventName'|'eventCategory'|false $secondaryDimension Optional secondary dimension for subtable rows.
     * @param bool $flat Whether subtable rows should be flattened into a single table.
     * @return DataTable|DataTable\Map Event action metrics.
     */
    public function getAction($idSite, string $period, string $date, $segment = false, bool $expanded = false, $secondaryDimension = false, bool $flat = false)
    {
        return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, $expanded, false, $secondaryDimension, $flat);
    }

    /**
     * Returns event metrics grouped by event name.
     *
     * @param int|string|int[] $idSite Website ID(s) to query.
     *                                 - Single site ID (e.g. 1)
     *                                 - Multiple site IDs (e.g. [1, 4, 5])
     *                                 - Comma-separated list ("1,4,5") or "all"
     * @param 'day'|'week'|'month'|'year'|'range' $period The period to process, processes data for the period
     *                                                    containing the specified date.
     * @param string $date The date or date range to process.
     *                     'YYYY-MM-DD', magic keywords (today, yesterday, lastWeek, lastMonth, lastYear),
     *                     or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD', lastX, previousX).
     * @param string|null|false $segment Custom segment to filter the report.
     *                                   Example: "referrerName==example.com"
     *                                   Supports AND (;) and OR (,) operators.
     * @param bool $expanded Whether subtables should be expanded in the response.
     * @param 'eventAction'|'eventCategory'|false $secondaryDimension Optional secondary dimension for subtable rows.
     * @param bool $flat Whether subtable rows should be flattened into a single table.
     * @return DataTable|DataTable\Map Event name metrics.
     */
    public function getName($idSite, string $period, string $date, $segment = false, bool $expanded = false, $secondaryDimension = false, bool $flat = false)
    {
        return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, $expanded, false, $secondaryDimension, $flat);
    }

    /**
     * Returns event actions for one event category row.
     *
     * @param int|string|int[] $idSite Website ID(s) to query.
     *                                 - Single site ID (e.g. 1)
     *                                 - Multiple site IDs (e.g. [1, 4, 5])
     *                                 - Comma-separated list ("1,4,5") or "all"
     * @param 'day'|'week'|'month'|'year'|'range' $period The period to process, processes data for the period
     *                                                    containing the specified date.
     * @param string $date The date or date range to process.
     *                     'YYYY-MM-DD', magic keywords (today, yesterday, lastWeek, lastMonth, lastYear),
     *                     or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD', lastX, previousX).
     * @param int $idSubtable Subtable ID for the event category row to expand.
     * @param string|null|false $segment Custom segment to filter the report.
     *                                   Example: "referrerName==example.com"
     *                                   Supports AND (;) and OR (,) operators.
     * @return DataTable|DataTable\Map Event action metrics for the selected category.
     */
    public function getActionFromCategoryId($idSite, string $period, string $date, $idSubtable, $segment = false)
    {
        return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, false, $idSubtable);
    }

    /**
     * Returns event names for one event category row.
     *
     * @param int|string|int[] $idSite Website ID(s) to query.
     *                                 - Single site ID (e.g. 1)
     *                                 - Multiple site IDs (e.g. [1, 4, 5])
     *                                 - Comma-separated list ("1,4,5") or "all"
     * @param 'day'|'week'|'month'|'year'|'range' $period The period to process, processes data for the period
     *                                                    containing the specified date.
     * @param string $date The date or date range to process.
     *                     'YYYY-MM-DD', magic keywords (today, yesterday, lastWeek, lastMonth, lastYear),
     *                     or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD', lastX, previousX).
     * @param int $idSubtable Subtable ID for the event category row to expand.
     * @param string|null|false $segment Custom segment to filter the report.
     *                                   Example: "referrerName==example.com"
     *                                   Supports AND (;) and OR (,) operators.
     * @return DataTable|DataTable\Map Event name metrics for the selected category.
     */
    public function getNameFromCategoryId($idSite, string $period, string $date, $idSubtable, $segment = false)
    {
        return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, false, $idSubtable);
    }

    /**
     * Returns event categories for one event action row.
     *
     * @param int|string|int[] $idSite Website ID(s) to query.
     *                                 - Single site ID (e.g. 1)
     *                                 - Multiple site IDs (e.g. [1, 4, 5])
     *                                 - Comma-separated list ("1,4,5") or "all"
     * @param 'day'|'week'|'month'|'year'|'range' $period The period to process, processes data for the period
     *                                                    containing the specified date.
     * @param string $date The date or date range to process.
     *                     'YYYY-MM-DD', magic keywords (today, yesterday, lastWeek, lastMonth, lastYear),
     *                     or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD', lastX, previousX).
     * @param int $idSubtable Subtable ID for the event action row to expand.
     * @param string|null|false $segment Custom segment to filter the report.
     *                                   Example: "referrerName==example.com"
     *                                   Supports AND (;) and OR (,) operators.
     * @return DataTable|DataTable\Map Event category metrics for the selected action.
     */
    public function getCategoryFromActionId($idSite, string $period, string $date, $idSubtable, $segment = false)
    {
        return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, false, $idSubtable);
    }

    /**
     * Returns event names for one event action row.
     *
     * @param int|string|int[] $idSite Website ID(s) to query.
     *                                 - Single site ID (e.g. 1)
     *                                 - Multiple site IDs (e.g. [1, 4, 5])
     *                                 - Comma-separated list ("1,4,5") or "all"
     * @param 'day'|'week'|'month'|'year'|'range' $period The period to process, processes data for the period
     *                                                    containing the specified date.
     * @param string $date The date or date range to process.
     *                     'YYYY-MM-DD', magic keywords (today, yesterday, lastWeek, lastMonth, lastYear),
     *                     or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD', lastX, previousX).
     * @param int $idSubtable Subtable ID for the event action row to expand.
     * @param string|null|false $segment Custom segment to filter the report.
     *                                   Example: "referrerName==example.com"
     *                                   Supports AND (;) and OR (,) operators.
     * @return DataTable|DataTable\Map Event name metrics for the selected action.
     */
    public function getNameFromActionId($idSite, string $period, string $date, $idSubtable, $segment = false)
    {
        return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, false, $idSubtable);
    }

    /**
     * Returns event actions for one event name row.
     *
     * @param int|string|int[] $idSite Website ID(s) to query.
     *                                 - Single site ID (e.g. 1)
     *                                 - Multiple site IDs (e.g. [1, 4, 5])
     *                                 - Comma-separated list ("1,4,5") or "all"
     * @param 'day'|'week'|'month'|'year'|'range' $period The period to process, processes data for the period
     *                                                    containing the specified date.
     * @param string $date The date or date range to process.
     *                     'YYYY-MM-DD', magic keywords (today, yesterday, lastWeek, lastMonth, lastYear),
     *                     or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD', lastX, previousX).
     * @param int $idSubtable Subtable ID for the event name row to expand.
     * @param string|null|false $segment Custom segment to filter the report.
     *                                   Example: "referrerName==example.com"
     *                                   Supports AND (;) and OR (,) operators.
     * @return DataTable|DataTable\Map Event action metrics for the selected name.
     */
    public function getActionFromNameId($idSite, string $period, string $date, $idSubtable, $segment = false)
    {
        return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, false, $idSubtable);
    }

    /**
     * Returns event categories for one event name row.
     *
     * @param int|string|int[] $idSite Website ID(s) to query.
     *                                 - Single site ID (e.g. 1)
     *                                 - Multiple site IDs (e.g. [1, 4, 5])
     *                                 - Comma-separated list ("1,4,5") or "all"
     * @param 'day'|'week'|'month'|'year'|'range' $period The period to process, processes data for the period
     *                                                    containing the specified date.
     * @param string $date The date or date range to process.
     *                     'YYYY-MM-DD', magic keywords (today, yesterday, lastWeek, lastMonth, lastYear),
     *                     or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD', lastX, previousX).
     * @param int $idSubtable Subtable ID for the event name row to expand.
     * @param string|null|false $segment Custom segment to filter the report.
     *                                   Example: "referrerName==example.com"
     *                                   Supports AND (;) and OR (,) operators.
     * @return DataTable|DataTable\Map Event category metrics for the selected name.
     */
    public function getCategoryFromNameId($idSite, string $period, string $date, $idSubtable, $segment = false)
    {
        return $this->getDataTable(__FUNCTION__, $idSite, $period, $date, $segment, false, $idSubtable);
    }
}
