"use strict";
var LocalVideoViewerModel_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.LocalVideoViewerModel = void 0;
const tslib_1 = require("tslib");
const sequelize_1 = require("sequelize");
const sequelize_typescript_1 = require("sequelize-typescript");
const activity_1 = require("../../lib/activitypub/activity");
const timeserie_1 = require("../../lib/timeserie");
const video_1 = require("../video/video");
const local_video_viewer_watch_section_1 = require("./local-video-viewer-watch-section");
let LocalVideoViewerModel = LocalVideoViewerModel_1 = class LocalVideoViewerModel extends sequelize_typescript_1.Model {
    static loadByUrl(url) {
        return this.findOne({
            where: {
                url
            }
        });
    }
    static loadFullById(id) {
        return this.findOne({
            include: [
                {
                    model: video_1.VideoModel.unscoped(),
                    required: true
                },
                {
                    model: local_video_viewer_watch_section_1.LocalVideoViewerWatchSectionModel.unscoped(),
                    required: true
                }
            ],
            where: {
                id
            }
        });
    }
    static getOverallStats(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { video, startDate, endDate } = options;
            const queryOptions = {
                type: sequelize_1.QueryTypes.SELECT,
                replacements: { videoId: video.id }
            };
            if (startDate)
                queryOptions.replacements.startDate = startDate;
            if (endDate)
                queryOptions.replacements.endDate = endDate;
            const buildWatchTimePromise = () => {
                let watchTimeDateWhere = '';
                if (startDate)
                    watchTimeDateWhere += ' AND "localVideoViewer"."startDate" >= :startDate';
                if (endDate)
                    watchTimeDateWhere += ' AND "localVideoViewer"."endDate" <= :endDate';
                const watchTimeQuery = `SELECT ` +
                    `COUNT("localVideoViewer"."id") AS "totalViewers", ` +
                    `SUM("localVideoViewer"."watchTime") AS "totalWatchTime", ` +
                    `AVG("localVideoViewer"."watchTime") AS "averageWatchTime" ` +
                    `FROM "localVideoViewer" ` +
                    `INNER JOIN "video" ON "video"."id" = "localVideoViewer"."videoId" ` +
                    `WHERE "videoId" = :videoId ${watchTimeDateWhere}`;
                return LocalVideoViewerModel_1.sequelize.query(watchTimeQuery, queryOptions);
            };
            const buildWatchPeakPromise = () => {
                let watchPeakDateWhereStart = '';
                let watchPeakDateWhereEnd = '';
                if (startDate) {
                    watchPeakDateWhereStart += ' AND "localVideoViewer"."startDate" >= :startDate';
                    watchPeakDateWhereEnd += ' AND "localVideoViewer"."endDate" >= :startDate';
                }
                if (endDate) {
                    watchPeakDateWhereStart += ' AND "localVideoViewer"."startDate" <= :endDate';
                    watchPeakDateWhereEnd += ' AND "localVideoViewer"."endDate" <= :endDate';
                }
                const beforeWatchersQuery = startDate
                    ? `SELECT COUNT(*) AS "total" FROM "localVideoViewer" WHERE "localVideoViewer"."startDate" < :startDate AND "localVideoViewer"."endDate" >= :startDate`
                    : `SELECT 0 AS "total"`;
                const watchPeakQuery = `WITH
        "beforeWatchers" AS (${beforeWatchersQuery}),
        "watchPeakValues" AS (
          SELECT "startDate" AS "dateBreakpoint", 1 AS "inc"
          FROM "localVideoViewer"
          WHERE "videoId" = :videoId ${watchPeakDateWhereStart}
          UNION ALL
          SELECT "endDate" AS "dateBreakpoint", -1 AS "inc"
          FROM "localVideoViewer"
          WHERE "videoId" = :videoId ${watchPeakDateWhereEnd}
        )
        SELECT "dateBreakpoint", "concurrent"
        FROM (
          SELECT "dateBreakpoint", SUM(SUM("inc")) OVER (ORDER BY "dateBreakpoint") + (SELECT "total" FROM "beforeWatchers") AS "concurrent"
          FROM "watchPeakValues"
          GROUP BY "dateBreakpoint"
        ) tmp
        ORDER BY "concurrent" DESC
        FETCH FIRST 1 ROW ONLY`;
                return LocalVideoViewerModel_1.sequelize.query(watchPeakQuery, queryOptions);
            };
            const buildCountriesPromise = () => {
                let countryDateWhere = '';
                if (startDate)
                    countryDateWhere += ' AND "localVideoViewer"."endDate" >= :startDate';
                if (endDate)
                    countryDateWhere += ' AND "localVideoViewer"."startDate" <= :endDate';
                const countriesQuery = `SELECT country, COUNT(country) as viewers ` +
                    `FROM "localVideoViewer" ` +
                    `WHERE "videoId" = :videoId AND country IS NOT NULL ${countryDateWhere} ` +
                    `GROUP BY country ` +
                    `ORDER BY viewers DESC`;
                return LocalVideoViewerModel_1.sequelize.query(countriesQuery, queryOptions);
            };
            const [rowsWatchTime, rowsWatchPeak, rowsCountries] = yield Promise.all([
                buildWatchTimePromise(),
                buildWatchPeakPromise(),
                buildCountriesPromise()
            ]);
            const viewersPeak = rowsWatchPeak.length !== 0
                ? parseInt(rowsWatchPeak[0].concurrent) || 0
                : 0;
            return {
                totalWatchTime: rowsWatchTime.length !== 0
                    ? Math.round(rowsWatchTime[0].totalWatchTime) || 0
                    : 0,
                averageWatchTime: rowsWatchTime.length !== 0
                    ? Math.round(rowsWatchTime[0].averageWatchTime) || 0
                    : 0,
                totalViewers: rowsWatchTime.length !== 0
                    ? Math.round(rowsWatchTime[0].totalViewers) || 0
                    : 0,
                viewersPeak,
                viewersPeakDate: rowsWatchPeak.length !== 0 && viewersPeak !== 0
                    ? rowsWatchPeak[0].dateBreakpoint || null
                    : null,
                countries: rowsCountries.map(r => ({
                    isoCode: r.country,
                    viewers: r.viewers
                }))
            };
        });
    }
    static getRetentionStats(video) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const step = Math.max(Math.round(video.duration / 100), 1);
            const query = `WITH "total" AS (SELECT COUNT(*) AS viewers FROM "localVideoViewer" WHERE "videoId" = :videoId) ` +
                `SELECT serie AS "second", ` +
                `(COUNT("localVideoViewer".id)::float / (SELECT GREATEST("total"."viewers", 1) FROM "total")) AS "retention" ` +
                `FROM generate_series(0, ${video.duration}, ${step}) serie ` +
                `LEFT JOIN "localVideoViewer" ON "localVideoViewer"."videoId" = :videoId ` +
                `AND EXISTS (` +
                `SELECT 1 FROM "localVideoViewerWatchSection" ` +
                `WHERE "localVideoViewer"."id" = "localVideoViewerWatchSection"."localVideoViewerId" ` +
                `AND serie >= "localVideoViewerWatchSection"."watchStart" ` +
                `AND serie <= "localVideoViewerWatchSection"."watchEnd"` +
                `)` +
                `GROUP BY serie ` +
                `ORDER BY serie ASC`;
            const queryOptions = {
                type: sequelize_1.QueryTypes.SELECT,
                replacements: { videoId: video.id }
            };
            const rows = yield LocalVideoViewerModel_1.sequelize.query(query, queryOptions);
            return {
                data: rows.map(r => ({
                    second: r.second,
                    retentionPercent: parseFloat(r.retention) * 100
                }))
            };
        });
    }
    static getTimeserieStats(options) {
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            const { video, metric } = options;
            const { groupInterval, startDate, endDate } = (0, timeserie_1.buildGroupByAndBoundaries)(options.startDate, options.endDate);
            const selectMetrics = {
                viewers: 'COUNT("localVideoViewer"."id")',
                aggregateWatchTime: 'SUM("localVideoViewer"."watchTime")'
            };
            const intervalWhere = {
                viewers: '"localVideoViewer"."startDate" <= "intervals"."endDate" ' +
                    'AND "localVideoViewer"."endDate" >= "intervals"."startDate"',
                aggregateWatchTime: '"localVideoViewer"."endDate" >= "intervals"."startDate" ' +
                    'AND "localVideoViewer"."endDate" <= "intervals"."endDate"'
            };
            const query = `WITH "intervals" AS (
      SELECT
        "time" AS "startDate", "time" + :groupInterval::interval as "endDate"
      FROM
        generate_series(:startDate::timestamptz, :endDate::timestamptz, :groupInterval::interval) serie("time")
    )
    SELECT "intervals"."startDate" as "date", COALESCE(${selectMetrics[metric]}, 0) AS value
    FROM
      intervals
      LEFT JOIN "localVideoViewer" ON "localVideoViewer"."videoId" = :videoId
        AND ${intervalWhere[metric]}
    GROUP BY
      "intervals"."startDate"
    ORDER BY
      "intervals"."startDate"`;
            const queryOptions = {
                type: sequelize_1.QueryTypes.SELECT,
                replacements: {
                    startDate,
                    endDate,
                    groupInterval,
                    videoId: video.id
                }
            };
            const rows = yield LocalVideoViewerModel_1.sequelize.query(query, queryOptions);
            return {
                groupInterval,
                data: rows.map(r => ({
                    date: r.date,
                    value: parseInt(r.value)
                }))
            };
        });
    }
    toActivityPubObject() {
        const location = this.country
            ? {
                location: {
                    addressCountry: this.country
                }
            }
            : {};
        return Object.assign({ id: this.url, type: 'WatchAction', duration: (0, activity_1.getActivityStreamDuration)(this.watchTime), startTime: this.startDate.toISOString(), endTime: this.endDate.toISOString(), object: this.Video.url, uuid: this.uuid, actionStatus: 'CompletedActionStatus', watchSections: this.WatchSections.map(w => ({
                startTimestamp: w.watchStart,
                endTimestamp: w.watchEnd
            })) }, location);
    }
};
tslib_1.__decorate([
    sequelize_typescript_1.CreatedAt,
    tslib_1.__metadata("design:type", Date)
], LocalVideoViewerModel.prototype, "createdAt", void 0);
tslib_1.__decorate([
    (0, sequelize_typescript_1.AllowNull)(false),
    (0, sequelize_typescript_1.Column)(sequelize_typescript_1.DataType.DATE),
    tslib_1.__metadata("design:type", Date)
], LocalVideoViewerModel.prototype, "startDate", void 0);
tslib_1.__decorate([
    (0, sequelize_typescript_1.AllowNull)(false),
    (0, sequelize_typescript_1.Column)(sequelize_typescript_1.DataType.DATE),
    tslib_1.__metadata("design:type", Date)
], LocalVideoViewerModel.prototype, "endDate", void 0);
tslib_1.__decorate([
    (0, sequelize_typescript_1.AllowNull)(false),
    sequelize_typescript_1.Column,
    tslib_1.__metadata("design:type", Number)
], LocalVideoViewerModel.prototype, "watchTime", void 0);
tslib_1.__decorate([
    (0, sequelize_typescript_1.AllowNull)(true),
    sequelize_typescript_1.Column,
    tslib_1.__metadata("design:type", String)
], LocalVideoViewerModel.prototype, "country", void 0);
tslib_1.__decorate([
    (0, sequelize_typescript_1.AllowNull)(false),
    (0, sequelize_typescript_1.Default)(sequelize_typescript_1.DataType.UUIDV4),
    (0, sequelize_typescript_1.IsUUID)(4),
    (0, sequelize_typescript_1.Column)(sequelize_typescript_1.DataType.UUID),
    tslib_1.__metadata("design:type", String)
], LocalVideoViewerModel.prototype, "uuid", void 0);
tslib_1.__decorate([
    (0, sequelize_typescript_1.AllowNull)(false),
    sequelize_typescript_1.Column,
    tslib_1.__metadata("design:type", String)
], LocalVideoViewerModel.prototype, "url", void 0);
tslib_1.__decorate([
    (0, sequelize_typescript_1.ForeignKey)(() => video_1.VideoModel),
    sequelize_typescript_1.Column,
    tslib_1.__metadata("design:type", Number)
], LocalVideoViewerModel.prototype, "videoId", void 0);
tslib_1.__decorate([
    (0, sequelize_typescript_1.BelongsTo)(() => video_1.VideoModel, {
        foreignKey: {
            allowNull: false
        },
        onDelete: 'CASCADE'
    }),
    tslib_1.__metadata("design:type", video_1.VideoModel)
], LocalVideoViewerModel.prototype, "Video", void 0);
tslib_1.__decorate([
    (0, sequelize_typescript_1.HasMany)(() => local_video_viewer_watch_section_1.LocalVideoViewerWatchSectionModel, {
        foreignKey: {
            allowNull: false
        },
        onDelete: 'cascade'
    }),
    tslib_1.__metadata("design:type", Array)
], LocalVideoViewerModel.prototype, "WatchSections", void 0);
LocalVideoViewerModel = LocalVideoViewerModel_1 = tslib_1.__decorate([
    (0, sequelize_typescript_1.Table)({
        tableName: 'localVideoViewer',
        updatedAt: false,
        indexes: [
            {
                fields: ['videoId']
            }
        ]
    })
], LocalVideoViewerModel);
exports.LocalVideoViewerModel = LocalVideoViewerModel;
//# sourceMappingURL=local-video-viewer.js.map