<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Local helpers for mod_modernvideoplayer.
 *
 * @package    mod_modernvideoplayer
 * @copyright  2025 Adebare Showemmo | adebareshowemimo@gmail.com | support@agunfoninteractivity.com | www.agunfoninteractivity.com
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

/**
 * Return the module instance using either cm id or instance id.
 *
 * @param int $id course module id
 * @param int $n instance id
 * @return array
 */
function modernvideoplayer_get_course_module_and_instance(int $id = 0, int $n = 0): array {
    global $DB;

    if ($n) {
        $modernvideoplayer = $DB->get_record('modernvideoplayer', ['id' => $n], '*', MUST_EXIST);
        $cm = get_coursemodule_from_instance(
            'modernvideoplayer',
            $modernvideoplayer->id,
            $modernvideoplayer->course,
            false,
            MUST_EXIST
        );
    } else {
        $cm = get_coursemodule_from_id('modernvideoplayer', $id, 0, false, MUST_EXIST);
        $modernvideoplayer = $DB->get_record('modernvideoplayer', ['id' => $cm->instance], '*', MUST_EXIST);
    }

    $course = $DB->get_record('course', ['id' => $cm->course], '*', MUST_EXIST);
    return [$course, $cm, $modernvideoplayer];
}

/**
 * Get admin defaults as a plain array.
 *
 * @return array
 */
function modernvideoplayer_get_defaults(): array {
    $config = get_config('modernvideoplayer');

    $autoplay = isset($config->defaultautoplay) ? (string) $config->defaultautoplay : 'unmuted';
    if (!in_array($autoplay, ['off', 'muted', 'unmuted'], true)) {
        $autoplay = 'unmuted';
    }

    $defaultcaptionlang = isset($config->defaultcaptionlang) ? (string) $config->defaultcaptionlang : 'en';
    if (!preg_match('/^[a-zA-Z]{2,3}(-[a-zA-Z]{2,4})?$/', $defaultcaptionlang)) {
        $defaultcaptionlang = 'en';
    }

    return [
        'requiredpercent' => isset($config->defaultrequiredpercent) ? (float) $config->defaultrequiredpercent : 95.0,
        'heartbeatinterval' => isset($config->defaultheartbeatinterval) ? (int) $config->defaultheartbeatinterval : 15,
        'graceseconds' => isset($config->defaultgraceseconds) ? (int) $config->defaultgraceseconds : 3,
        'allowplaybackspeed' => isset($config->defaultallowplaybackspeed) ? (int) $config->defaultallowplaybackspeed : 1,
        'maxplaybackspeed' => isset($config->defaultmaxplaybackspeed) ? (float) $config->defaultmaxplaybackspeed : 1.25,
        'allowresume' => isset($config->defaultresumeenabled) ? (int) $config->defaultresumeenabled : 1,
        'allownextactivityoverlay' => isset($config->defaultallownextactivityoverlay)
            ? (int) $config->defaultallownextactivityoverlay : 1,
        'nextactivitytarget' => 'auto_next',
        'nextactivitymanualcmid' => 0,
        'allowfullscreen' => isset($config->defaultfullscreenenabled) ? (int) $config->defaultfullscreenenabled : 1,
        'autoplay' => $autoplay,
        'allowcaptions' => isset($config->defaultallowcaptions) ? (int) $config->defaultallowcaptions : 1,
        'defaultcaptionlang' => $defaultcaptionlang,
        'showprimarynav' => isset($config->defaultshowprimarynav) ? (int) $config->defaultshowprimarynav : 1,
        'showsecondarynav' => isset($config->defaultshowsecondarynav) ? (int) $config->defaultshowsecondarynav : 1,
        'showcourseindex' => isset($config->defaultshowcourseindex) ? (int) $config->defaultshowcourseindex : 1,
        'showrightblocks' => isset($config->defaultshowrightblocks) ? (int) $config->defaultshowrightblocks : 1,
        'titleposition' => isset($config->defaulttitleposition) ? (string) $config->defaulttitleposition : 'left',
        'showcontroltext' => isset($config->defaultshowcontroltext) ? (int) $config->defaultshowcontroltext : 1,
        'showsuspiciousflags' => isset($config->defaultsuspiciouslogging) ? (int) $config->defaultsuspiciouslogging : 1,
        'enforcefocus' => isset($config->defaultenforcefocus) ? (int) $config->defaultenforcefocus : 0,
        'allowpip' => isset($config->defaultallowpip) ? (int) $config->defaultallowpip : 1,
        'allowtranscriptdownload' => isset($config->defaultallowtranscriptdownload)
            ? (int) $config->defaultallowtranscriptdownload : 1,
    ];
}

/**
 * Resolve the configured "next" activity for the end-of-video overlay.
 *
 * Honours a manual cmid pick when the instance is configured for it (and the
 * picked module is still valid for standard Moodle activity navigation);
 * otherwise walks forward through course modules in Moodle's display order.
 * Falls back to the course view URL when no valid target is available.
 *
 * @param stdClass $course
 * @param cm_info|stdClass $cm current course module
 * @param stdClass $instance modernvideoplayer DB record
 * @return array{name: string, url: string, isfallback: bool}
 */
function modernvideoplayer_get_next_activity(stdClass $course, $cm, stdClass $instance): array {
    global $USER;

    if (!empty($USER->id)) {
        modernvideoplayer_sync_moodle_completion_for_user($course, $cm, $instance, (int) $USER->id);
    }

    $modinfo = get_fast_modinfo($course->id);
    $target = $instance->nextactivitytarget ?? 'auto_next';

    if ($target === 'manual' && !empty($instance->nextactivitymanualcmid)) {
        $manualid = (int) $instance->nextactivitymanualcmid;
        if (isset($modinfo->cms[$manualid])) {
            $targetactivity = modernvideoplayer_next_activity_from_cm($modinfo->cms[$manualid]);
            if ($targetactivity) {
                return $targetactivity;
            }
        }
        // Manual pick is gone or hidden - fall through to course fallback rather
        // than silently auto-advancing past the teacher's explicit choice.
        return modernvideoplayer_next_activity_fallback($course);
    }

    // Course_modinfo::get_cms() returns cms "in order of appearance" across
    // the whole course (including subsections), matching Moodle's standard
    // activity navigation ordering.
    $found = false;
    foreach ($modinfo->get_cms() as $candidate) {
        if ($found) {
            $targetactivity = modernvideoplayer_next_activity_from_cm($candidate);
            if ($targetactivity) {
                return $targetactivity;
            }
            continue;
        }
        if ((int) $candidate->id === (int) $cm->id) {
            $found = true;
        }
    }
    return modernvideoplayer_next_activity_fallback($course);
}

/**
 * Ensure Moodle completion reflects this plugin's completed playback state.
 *
 * Availability restrictions for the next course module read Moodle's
 * course_modules_completion table, not modernvideoplayer_progress. Calling
 * this before resolving the Continue target makes fresh page loads behave the
 * same as the post-completion AJAX path.
 *
 * @param stdClass $course
 * @param cm_info|stdClass $cm current course module
 * @param stdClass $instance modernvideoplayer DB record
 * @param int $userid user id
 * @return bool true when the activity is complete after sync
 */
function modernvideoplayer_sync_moodle_completion_for_user(
    stdClass $course,
    $cm,
    stdClass $instance,
    int $userid
): bool {
    global $DB;

    if ($userid <= 0 || (int) ($cm->completion ?? COMPLETION_TRACKING_NONE) !== COMPLETION_TRACKING_AUTOMATIC) {
        return false;
    }

    $progress = $DB->get_record('modernvideoplayer_progress', [
        'modernvideoplayerid' => $instance->id,
        'userid' => $userid,
    ]);
    if (!$progress) {
        return false;
    }

    $manager = new \mod_modernvideoplayer\local\completion_manager();
    if (!$manager->is_complete($instance, $progress)) {
        return false;
    }

    $completed = $manager->update($course, $cm, $instance, $progress);
    if ($completed) {
        $progress->timemodified = time();
        $DB->update_record('modernvideoplayer_progress', $progress);
        get_fast_modinfo((int) $course->id, 0, true);
    }

    return $completed;
}

/**
 * Convert a course module into an end-of-video Continue target when Moodle's
 * standard activity navigation would allow it.
 *
 * @param cm_info $candidate
 * @return array{name: string, url: string, isfallback: bool}|null
 */
function modernvideoplayer_next_activity_from_cm(cm_info $candidate): ?array {
    // Mirrors core navigation on Moodle 4.5+: skip unavailable, stealth,
    // and URL-less module types.
    if (
        !$candidate->uservisible ||
        $candidate->is_stealth() ||
        empty($candidate->url)
    ) {
        return null;
    }

    $url = new moodle_url($candidate->url, ['forceview' => 1]);
    return [
        'name' => $candidate->get_formatted_name(),
        'url' => $url->out(false),
        'isfallback' => false,
    ];
}

/**
 * Default fallback target (course view) for the next-activity overlay.
 *
 * @param stdClass $course
 * @return array{name: string, url: string, isfallback: bool}
 */
function modernvideoplayer_next_activity_fallback(stdClass $course): array {
    return [
        'name' => format_string($course->fullname),
        'url' => (new moodle_url('/course/view.php', ['id' => $course->id]))->out(false),
        'isfallback' => true,
    ];
}

/**
 * Get a single stored video file for a module context.
 *
 * @param context_module $context
 * @param string $filearea
 * @return stored_file|null
 */
function modernvideoplayer_get_file(context_module $context, string $filearea): ?stored_file {
    $fs = get_file_storage();
    $files = $fs->get_area_files($context->id, 'mod_modernvideoplayer', $filearea, 0, 'itemid, filepath, filename', false);
    if (!$files) {
        return null;
    }

    return reset($files);
}

/**
 * Build a public pluginfile URL for a stored file.
 *
 * @param stored_file $file
 * @return moodle_url
 */
function modernvideoplayer_file_url(stored_file $file): moodle_url {
    return moodle_url::make_pluginfile_url(
        $file->get_contextid(),
        $file->get_component(),
        $file->get_filearea(),
        $file->get_itemid(),
        $file->get_filepath(),
        $file->get_filename(),
        false
    );
}

/**
 * Build the caption track list for a module instance.
 *
 * Language is inferred from the filename pattern <name>.<lang>.vtt (e.g. foo.en.vtt,
 * foo.fr-CA.vtt). Files with no match fall back to the instance default language.
 * The track matching the default language is marked as the default <track>.
 *
 * @param context_module $context the module context
 * @param string $defaultlang the instance default caption language code
 * @return array<int, array{src: string, srclang: string, label: string, default: bool}>
 */
function modernvideoplayer_get_caption_tracks(context_module $context, string $defaultlang): array {
    $fs = get_file_storage();
    $files = $fs->get_area_files(
        $context->id,
        'mod_modernvideoplayer',
        'captions',
        0,
        'filename ASC',
        false
    );
    if (!$files) {
        return [];
    }

    $defaultlang = strtolower(trim($defaultlang)) ?: 'en';
    $tracks = [];
    $defaultindex = -1;

    foreach ($files as $file) {
        $name = $file->get_filename();
        // Only accept .vtt files defensively.
        if (!preg_match('/\.vtt$/i', $name)) {
            continue;
        }
        $lang = $defaultlang;
        $label = preg_replace('/\.vtt$/i', '', $name);
        if (preg_match('/[._-]([a-z]{2,3}(?:-[a-z]{2,4})?)\.vtt$/i', $name, $m)) {
            $lang = strtolower($m[1]);
            // Keep the user-friendly label without the language suffix.
            $label = preg_replace('/[._-]' . preg_quote($m[1], '/') . '\.vtt$/i', '', $name);
        }
        // Normalise the BCP-47 region portion to uppercase (en-US not en-us).
        if (strpos($lang, '-') !== false) {
            [$primary, $region] = explode('-', $lang, 2);
            $lang = strtolower($primary) . '-' . strtoupper($region);
        }
        $label = $label !== '' ? $label : $lang;

        $tracks[] = [
            'src' => modernvideoplayer_file_url($file)->out(false),
            'srclang' => $lang,
            'label' => $label,
            'default' => false,
        ];

        if ($defaultindex === -1 && strcasecmp($lang, $defaultlang) === 0) {
            $defaultindex = count($tracks) - 1;
        }
    }

    if ($tracks) {
        $tracks[$defaultindex >= 0 ? $defaultindex : 0]['default'] = true;
    }

    return $tracks;
}

/**
 * Get the chapter track (single WebVTT) for a module instance.
 *
 * @param context_module $context the module context
 * @return array|null ['src' => string, 'label' => string] or null when not uploaded
 */
function modernvideoplayer_get_chapter_track(context_module $context): ?array {
    $file = modernvideoplayer_get_file($context, 'chapters');
    if (!$file) {
        return null;
    }
    $name = $file->get_filename();
    if (!preg_match('/\.vtt$/i', $name)) {
        return null;
    }
    $label = preg_replace('/\.vtt$/i', '', $name);
    return [
        'src' => modernvideoplayer_file_url($file)->out(false),
        'label' => $label !== '' ? $label : $name,
    ];
}
