<?php
// This file is part of the Studyplan plugin for Moodle
//
// 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 <https://www.gnu.org/licenses/>.

/**
 * Class to process information about a course
 * @package    local_treestudyplan
 * @copyright  2023 P.M. Kuipers
 * @license    https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

namespace local_treestudyplan;

use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_description;
use core_external\external_value;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core\context;
use core\context\system as context_system;
use core\context\course as context_course;
use core\context\coursecat as context_coursecat;
use local_treestudyplan\local\helpers\formatter;

defined('MOODLE_INTERNAL') || die();

require_once($CFG->libdir . '/externallib.php');
require_once($CFG->libdir . '/gradelib.php');
require_once($CFG->dirroot . '/course/lib.php');

/**
 * Class to process information about a course
 */
class courseinfo {
    /**
     * Table name used in this class
     * @var string */
    const TABLE = 'course';

    /** @var stdClass */
    private $course;
    /** @var context */
    private $context;
    /** @var context */
    private $coursecontext;
    /** @var studyitem */
    private $studyitem;

    /**
     * Cache of enrolled students in a particular course
     * @var array
     */
    private static $coursestudents = [];
    /**
     * Return database identifier
     * @return int
     */
    public function id() {
        return $this->course->id;
    }

    /**
     * Return short name
     * @return string
     */
    public function shortname() {
        return $this->course->shortname;
    }

    /**
     * Return course record
     * @return \stdClass
     */
    public function course() {
        return $this->course;
    }

    /**
     * Return course context
     * @return \context
     */
    public function course_context() {
        return $this->coursecontext;
    }

    /**
     * Return course's category context
     * @return \context
     */
    public function category_context() {
        return $this->context;
    }

    /**
     * Check if current user is teacher in this course
     * @return bool
     */
    protected function am_teacher(): bool {
        global $USER;
        return is_enrolled($this->coursecontext, $USER, 'mod/assign:grade');
    }

    /**
     * Check if specified user can select gradables in this course
     * @param int $userid User id to check for . Leave empty to check current user
     */
    protected function i_can_select_gradables($userid = -1) {
        global $USER, $DB;
        if ($userid <= 0) {
            $usr = $USER;
        } else {
            $usr = $DB->get_record('user', ['id' => $userid, 'deleted' => 0]);
        }
        return($usr && is_enrolled($this->coursecontext, $usr, 'local/treestudyplan:selectowngradables'));
    }

    /**
     * Construct courseinfo based on course id and
     * @param int|stdClass $course Course id
     * @param studyitem|null $studyitem Studyitem linking this course (if applicable)
     */
    public function __construct($course, ?studyitem $studyitem = null) {
        $this->studyitem = $studyitem;
        if (is_object($course)) {
            $this->course = $course;
        } else if (is_numeric($course)) {
            $this->course = preloader::get_course($course);
        } else {
            throw new \moodle_exception("course parameter is not object or id (number)");
        }
        $this->context = context_coursecat::instance($this->course->category);
        $this->coursecontext = context_course::instance($this->course->id);
    }

    /**
     * Check if a course with the given ID exists
     * @param int $id Course id
     * @return bool
     */
    public static function exists($id) {
        global $DB;
        return is_numeric($id) && $DB->record_exists(self::TABLE, ['id' => $id]);
    }

    /**
     * Find course id from shortname
     * @param string $shortname Shortname of the course
     * @return int Course id
     */
    public static function id_from_shortname($shortname) {
        global $DB;

        return $DB->get_field(self::TABLE, "id", ['shortname' => $shortname]);
    }

    /**
     * Determine course timing [future, present or past] based on a course date
     * @param stdClass $course Course database record
     * @return string 'future', 'present' or 'past'
     */
    public static function coursetiming($course) {
        $now = time();
        if ($now > $course->startdate) {
            if ($course->enddate > 0 && $now > $course->enddate) {
                return "past";
            } else {
                return "present";
            }
        } else {
            return "future";
        }
    }

    /**
     * Determine course timing for this course [future, present or past]
     * @return string 'future', 'present' or 'past'
     */
    public function timing() {
        return self::coursetiming($this->course);
    }

    /**
     * Determine proper display name for this course based on config settings, custom fields etc...
     * @return string Display name for the course
     */
    public function displayname() {
        $displayfield = get_config("local_treestudyplan", "display_field");
        if ($displayfield == "idnumber") {
            $idnumber = trim((string)(preg_replace("/\s+/u", " ", $this->course->idnumber)));
            if (strlen($idnumber) > 0) {
                return $this->course->idnumber;
            }
        } else if ($displayfield == "fullname") {
            $fullname = trim((string)(preg_replace("/\s+/u", " ", $this->course->fullname)));
            if (strlen($fullname) > 0) {
                return formatter::format_string($fullname);
            }
        } else if (strpos($displayfield, "customfield_") === 0) {
            $fieldname = substr($displayfield, strlen("customfield_"));

            $handler = \core_customfield\handler::get_handler('core_course', 'course');
            $datas = $handler->get_instance_data($this->course->id);
            foreach ($datas as $data) {
                if ($data->get_field()->get('shortname') == $fieldname) {
                    $value = trim((string)(preg_replace("/\s+/u", " ", $data->get_value())));
                    if (strlen($value) > 0) {
                        return formatter::format_string($value);
                    }
                }
            }
        }
        // Fallback to shortname when the specified display field fails, since shortname is never empty.
        return $this->course->shortname;
    }

    /**
     * Webservice structure for basic info
     * @param int $value Webservice requirement constant
     */
    public static function simple_structure($value = VALUE_REQUIRED): external_description {
        return new external_single_structure([
            "id" => new external_value(PARAM_INT, 'linked course id'),
            "fullname" => new external_value(PARAM_RAW, 'linked course name'),
            "shortname" => new external_value(PARAM_RAW, 'linked course shortname'),
            "displayname" => new external_value(PARAM_RAW, 'linked course displayname'),
        ], 'referenced course information', $value);
    }

    /**
     * Webservice model for basic info
     * @return array Webservice data model
     */
    public function simple_model() {
        $info = [
            'id' => $this->course->id,
            'fullname' => formatter::format_string($this->course->fullname),
            'shortname' => $this->course->shortname,
            'displayname' => $this->displayname(),
        ];

        return $info;
    }

    /**
     * Webservice structure for basic info
     * @param int $value Webservice requirement constant
     */
    public static function categorystructure($value = VALUE_REQUIRED): external_description {
        return new external_single_structure([
            "name" => new external_value(PARAM_RAW, 'category name'),
            "path" => new external_multiple_structure(
                new external_value(PARAM_RAW)
            ),
        ], 'category information', $value);
    }

    /**
     * Create category model for category
     * @param int $id Id of category
     * @return array Webservice data model
     */
    public static function categorymodel($id) {

        $cat = preloader::get_category($id);
        $catpath = [];
        $path = explode("/", $cat->path);
        array_unshift($path); // Strip empty first part.
        foreach ($path as $pid) {
            $catpath[] = formatter::format_string((preloader::get_category($pid))->name);
        }
        return  [
            "name" => formatter::format_string($cat->name),
            "path" => $catpath,
        ];
    }


    /**
     * Webservice structure for editor info
     * @param int $value Webservice requirement constant
     */
    public static function editor_structure($value = VALUE_REQUIRED): external_description {
        return new external_single_structure([
            "id" => new external_value(PARAM_INT, 'linked course id'),
            "fullname" => new external_value(PARAM_RAW, 'linked course name'),
            "shortname" => new external_value(PARAM_RAW, 'linked course shortname'),
            "displayname" => new external_value(PARAM_RAW, 'linked course displayname'),
            "category" => self::categorystructure(VALUE_OPTIONAL),
            "ctxid" => new external_value(PARAM_INT, 'course context id name'),
            "grades" => new external_multiple_structure(
                gradeinfo::editor_structure(),
                'grade list (legacy list)',
                VALUE_OPTIONAL
            ),
            "completion" => corecompletioninfo::editor_structure(VALUE_OPTIONAL),
            "competency" => coursecompetencyinfo::editor_structure(VALUE_OPTIONAL),
            "timing" => new external_value(PARAM_TEXT, '(past|present|future)'),
            "startdate" => new external_value(PARAM_TEXT, 'Course start date'),
            "enddate" => new external_value(PARAM_TEXT, 'Course end date'),
            "amteacher" => new external_value(PARAM_BOOL, 'Requesting user is teacher in this course'),
            "canupdatecourse" => new external_value(PARAM_BOOL, "If the current user can update this course"),
            "canselectgradables" => new external_value(PARAM_BOOL, 'Requesting user can change selected gradables'),
            "numenrolled" => new external_value(PARAM_INT, 'number of students from this studyplan enrolled in the course'),
            "tag" => new external_value(PARAM_TEXT, 'Tag'),
            "extrafields" => self::extrafields_structure(),
        ], 'referenced course information', $value);
    }

    /**
     * Webservice model for editor info
     * @return array Webservice data model
     */
    public function editor_model() {
        $timing = $this->timing();

        if (isset($this->studyitem)) {
            $numenrolled = $this->count_enrolled_students($this->studyitem->studyline()->studyplan()->find_linked_userids());
        } else {
            $numenrolled = 0;
        }

        $info = [
            'id' => $this->course->id,
            'fullname' => formatter::format_string($this->course->fullname),
            'shortname' => $this->course->shortname,
            'displayname' => $this->displayname(),
            'category' => self::categorymodel($this->course->category),
            'ctxid' => $this->coursecontext->id,
            'timing' => $timing,
            'startdate' => date("Y-m-d", $this->course->startdate),
            'enddate' => $this->course->enddate ? date("Y-m-d", $this->course->enddate) : "",
            'amteacher' => $this->am_teacher(),
            'canupdatecourse' => \has_capability("moodle/course:update", $this->coursecontext),
            'canselectgradables' => $this->i_can_select_gradables(),
            'tag' => "Editormodel",
            'extrafields' => $this->extrafields_model(true),
            'grades' => [],
            'numenrolled' => $numenrolled,
        ];

        if (isset($this->studyitem)) {
            $aggregator = $this->studyitem->studyline()->studyplan()->aggregator();

            if ($aggregator->use_manualactivityselection()) {
                $gradables = gradeinfo::list_course_gradables($this->course, $this->studyitem);
                foreach ($gradables as $gradable) {
                    $info['grades'][] = $gradable->editor_model();
                }
            }
            if ($aggregator->use_corecompletioninfo()) {
                $cc = new corecompletioninfo($this->course);
                $studentlist = $this->studyitem->studyline()->studyplan()->find_linked_userids();
                $info['completion'] = $cc->editor_model();
            }
            if ($aggregator->use_coursecompetencies()) {
                $ci = new coursecompetencyinfo($this->course, $this->studyitem);
                $studentlist = $this->studyitem->studyline()->studyplan()->find_linked_userids();
                $info['competency'] = $ci->editor_model($studentlist);
            }
        }
        return $info;
    }

    /**
     * Webservice structure for editor info
     * @param int $value Webservice requirement constant
     */
    public static function teacher_structure($value = VALUE_REQUIRED): external_description {
        return new external_single_structure([
            "id" => new external_value(PARAM_INT, 'linked course id'),
            "fullname" => new external_value(PARAM_RAW, 'linked course name'),
            "shortname" => new external_value(PARAM_RAW, 'linked course shortname'),
            "displayname" => new external_value(PARAM_RAW, 'linked course displayname'),
            "category" => self::categorystructure(VALUE_OPTIONAL),
            "ctxid" => new external_value(PARAM_INT, 'course context id name'),
            "grades" => new external_multiple_structure(
                gradeinfo::teacher_structure(),
                'grade list (legacy list)',
                VALUE_OPTIONAL
            ),
            "completion" => corecompletioninfo::teacher_structure(VALUE_OPTIONAL),
            "competency" => coursecompetencyinfo::teacher_structure(VALUE_OPTIONAL),
            "timing" => new external_value(PARAM_TEXT, '(past|present|future)'),
            "startdate" => new external_value(PARAM_TEXT, 'Course start date'),
            "enddate" => new external_value(PARAM_TEXT, 'Course end date'),
            "amteacher" => new external_value(PARAM_BOOL, 'Requesting user is teacher in this course'),
            "canupdatecourse" => new external_value(PARAM_BOOL, "If the current user can update this course"),
            "canselectgradables" => new external_value(PARAM_BOOL, 'Requesting user can change selected gradables'),
            "numenrolled" => new external_value(PARAM_INT, 'number of students from this studyplan enrolled in the course'),
            "tag" => new external_value(PARAM_TEXT, 'Tag'),
            "extrafields" => self::extrafields_structure(),
        ], 'referenced course information', $value);
    }

    /**
     * Webservice model for editor info
     * @return array Webservice data model
     */
    public function teacher_model() {
        $timing = $this->timing();

        if (isset($this->studyitem)) {
            $numenrolled = $this->count_enrolled_students($this->studyitem->studyline()->studyplan()->find_linked_userids());
        } else {
            $numenrolled = 0;
        }

        $info = [
            'id' => $this->course->id,
            'fullname' => formatter::format_string($this->course->fullname),
            'shortname' => $this->course->shortname,
            'displayname' => $this->displayname(),
            'category' => self::categorymodel($this->course->category),
            'ctxid' => $this->coursecontext->id,
            'timing' => $timing,
            'startdate' => date("Y-m-d", $this->course->startdate),
            'enddate' => $this->course->enddate ? date("Y-m-d", $this->course->enddate) : "",
            'amteacher' => $this->am_teacher(),
            'canupdatecourse' => \has_capability("moodle/course:update", $this->coursecontext),
            'canselectgradables' => $this->i_can_select_gradables(),
            'tag' => "Editormodel",
            'extrafields' => $this->extrafields_model(true),
            'grades' => [],
            'numenrolled' => $numenrolled,
        ];

        if (isset($this->studyitem)) {
            $aggregator = $this->studyitem->studyline()->studyplan()->aggregator();

            if ($aggregator->use_manualactivityselection()) {
                $gradables = gradeinfo::list_studyitem_gradables($this->studyitem);
                foreach ($gradables as $gradable) {
                    $info['grades'][] = $gradable->teacher_model($this->studyitem);
                }
            }
            if ($aggregator->use_corecompletioninfo()) {
                $cc = new corecompletioninfo($this->course);
                $studentlist = $this->studyitem->studyline()->studyplan()->find_linked_userids();
                $info['completion'] = $cc->teacher_model($studentlist);
            }
            if ($aggregator->use_coursecompetencies()) {
                $ci = new coursecompetencyinfo($this->course, $this->studyitem);
                $studentlist = $this->studyitem->studyline()->studyplan()->find_linked_userids();
                $info['competency'] = $ci->teacher_model($studentlist);
            }
        }
        return $info;
    }


    /**
     * Webservice structure for userinfo
     * @param int $value Webservice requirement constant
     */
    public static function user_structure($value = VALUE_REQUIRED): external_description {
        return new external_single_structure([
            "id" => new external_value(PARAM_INT, 'linked course id'),
            "fullname" => new external_value(PARAM_TEXT, 'linked course name'),
            "shortname" => new external_value(PARAM_TEXT, 'linked course shortname'),
            "displayname" => new external_value(PARAM_RAW, 'linked course displayname'),
            "category" => self::categorystructure(VALUE_OPTIONAL),
            "ctxid" => new external_value(PARAM_INT, 'course context id name'),
            "grades" => new external_multiple_structure(gradeinfo::user_structure(), 'grade list (legacy list)', VALUE_OPTIONAL),
            "completion" => corecompletioninfo::user_structure(VALUE_OPTIONAL),
            "competency" => coursecompetencyinfo::user_structure(VALUE_OPTIONAL),
            "timing" => new external_value(PARAM_TEXT, '(past|present|future)'),
            "startdate" => new external_value(PARAM_TEXT, 'Course start date'),
            "enddate" => new external_value(PARAM_TEXT, 'Course end date'),
            "enrolled" => new external_value(PARAM_BOOL, 'True if student is enrolled as student in this course'),
            "extrafields" => self::extrafields_structure(),
            "showprogressbar" => new external_value(PARAM_BOOL, "Whether to show the progress bar in the header"),
        ], 'course information', $value);
    }

    /**
     * List all users enrolled in a course as student by userid
     * @param int $courseid Course id of the course to check
     * @return int[] Array if user ids
     */
    public static function get_course_students($courseid): array {
        global $CFG;
        if (!array_key_exists($courseid, self::$coursestudents)) {
            $students = [];
            $context = context_course::instance($courseid);
            foreach (explode(',', $CFG->gradebookroles) as $roleid) {
                $roleid = trim($roleid);
                $students = array_keys(get_role_users($roleid, $context, false, 'u.id', 'u.id ASC'));
            }
            self::$coursestudents[$courseid] = $students;
        }
        return self::$coursestudents[$courseid];
    }

    /**
     * Check if a user is enrolled as a student in this course
     * (Has a gradebook role)
     * @param int $userid The user Id to check
     */
    public function is_enrolled_student($userid): bool {
        global $CFG;
        foreach (explode(',', $CFG->gradebookroles) as $roleid) {
            if (user_has_role_assignment($userid, $roleid, $this->coursecontext->id)) {
                return true;
            }
        }
        return false;
    }

    /**
     * List how many users from a list  also enrolled as students
     * (Has a gradebook role)
     * @param int[] $userids The user Ids to check
     */
    private function count_enrolled_students(array $userids) {
        $count = 0;
        foreach ($userids as $userid) {
            if ($this->is_enrolled_student($userid)) {
                $count++;
            }
        }
        return $count;
    }

    /**
     * Webservice model for user info
     * @param int $userid ID of user to check specific info for
     * @return array Webservice data model
     */
    public function user_model($userid) {
        $timing = $this->timing();

        $info = [
            'id' => $this->course->id,
            'fullname' => $this->course->fullname,
            'shortname' => $this->course->shortname,
            'displayname' => $this->displayname(),
            'category' => self::categorymodel($this->course->category),
            'ctxid' => $this->coursecontext->id,
            'timing' => $timing,
            'startdate' => date("Y-m-d", $this->course->startdate),
            'enddate' => $this->course->enddate ? date("Y-m-d", $this->course->enddate) : "",
            'grades' => [],
            'enrolled' => $this->is_enrolled_student($userid),
            'extrafields' => $this->extrafields_model(),
            'showprogressbar' => get_config("local_treestudyplan", "courseprogressbar"),
        ];

        if (isset($this->studyitem)) {
            $aggregator = $this->studyitem->studyline()->studyplan()->aggregator();

            if ($aggregator->use_manualactivityselection()) {
                $gradables = gradeinfo::list_studyitem_gradables($this->studyitem);
                foreach ($gradables as $gi) {
                    $info['grades'][] = $gi->user_model($userid);
                }
            }
            if ($aggregator->use_corecompletioninfo()) {
                $cc = new corecompletioninfo($this->course);
                $info['completion'] = $cc->user_model($userid);
            }
            if ($aggregator->use_coursecompetencies()) {
                $ci = new coursecompetencyinfo($this->course, $this->studyitem);
                $info['competency'] = $ci->user_model($userid);
            }
        }
        return $info;
    }

    /**
     * Webservice structure for extra fields
     * @param int $value Webservice requirement constant
     */
    public static function extrafields_structure($value = VALUE_REQUIRED): external_description {
        return new external_multiple_structure(new external_single_structure([
            "title" => new external_value(PARAM_RAW, 'title'),
            "value" => new external_value(PARAM_RAW, 'value'),
            "position" => new external_value(PARAM_TEXT, 'position'),
            "type" => new external_value(PARAM_TEXT, 'value type'),
            "fieldname" => new external_value(PARAM_TEXT, 'field name'),
            "checked" => new external_value(PARAM_BOOL, 'checkbox value', VALUE_OPTIONAL),
            "courseid" => new external_value(PARAM_TEXT, 'course id number'),
        ], 'referenced course information'), $value);
    }

    /**
     * Webservice model for basic info
     * @param bool $includeteacher Set to true if in teacher mode, so fields set to visible as teacher will show
     * @return array Webservice data model
     */
    public function extrafields_model($includeteacher = false) {
        $list = [];
        for ($i = 1; $i <= 5; $i++) {
            $field = get_config('local_treestudyplan', "courseinfo{$i}_field");
            if ($field) {
                $title = self::extrafields_localize_title(get_config('local_treestudyplan', "courseinfo{$i}_title"));
                $pos = get_config('local_treestudyplan', "courseinfo{$i}_position");
                [$value, $type, $raw] = $this->extrafields_value($field, $includeteacher);
                if ($type) {
                    $list[] = [
                        "title" => $title,
                        "value" => $value,
                        "position" => $pos,
                        "type" => $type,
                        "fieldname" => $field,
                        "courseid" => $this->course->id,
                        "checked" => ($type == "checkbox") ? ($raw ? true : false) : null,
                    ];
                }
            }
        }
        return $list;
    }

    /**
     * Localize title for extra field
     * @param string $field
     */
    protected static function extrafields_localize_title($field) {
        $lang = trim(current_language());
        $lines = explode("\n", $field);
        $title = "";
        $fallback = ""; // Fallback to first title.
        foreach ($lines as $l) {
            $parts = explode("|", $l, 2);
            if (count($parts) > 0) {
                // Set the first line as fallback.
                if (!empty($parts[0])) {
                    $fallback = $parts[0];
                }
                if (count($parts) == 1 && empty($title)) {
                    // Set line without language as default if no localized line found.
                    $title = trim($parts[0]);
                } else if (trim($parts[1]) == $lang) {
                    return trim($parts[0]);
                }
            }
        }

        // Return default title or fall back to first localizef title.
        return (strlen($title) > 0) ? $title : $fallback;
    }

    /**
     * Determine value and type of an extra field for this course
     * @param string $fieldname The name if the extra field
     * @param bool $includeteacher Set to true if in teacher mode, so fields set to visible as teacher will show
     * @return array [value, type] of the field for this
     */
    protected function extrafields_value($fieldname, $includeteacher = false) {
        global $PAGE;
        if ($fieldname == "description") {
            // Process embedded files.
            $value = \file_rewrite_pluginfile_urls(
                // The description content.
                $this->course()->summary,
                // The pluginfile URL which will serve the request.
                'pluginfile.php',
                // The combination of contextid / component / filearea / itemid.
                // Form the virtual bucket that file are stored in.
                $this->coursecontext->id, // System instance is always used for this.
                'course',
                'summary',
                ''
            );
            return [$value, "textarea", $this->course()->summary];
        } else if ($fieldname == "idnumber") {
            $idnumber = trim((string)(preg_replace("/\s+/u", " ", $this->course->idnumber)));
            return [$idnumber, "text", $this->course->idnumber];
        } else if ($fieldname == "contacts") {
            $cle = new \core_course_list_element($this->course());
            $contacts = $cle->get_course_contacts();
            $value = "";
            foreach ($contacts as $contact) {
                if (strlen($value) > 0) {
                    $value .= ", ";
                }
                $value .= $contact["username"];
            }
            if (empty($value)) {
                $value = get_string("none");
            }
            return [$value, "text", $value];
        } else if (strpos($fieldname, "customfield_") === 0) {
            $fieldshortname = substr($fieldname, strlen("customfield_"));

            $handler = \core_customfield\handler::get_handler('core_course', 'course');
            $datas = $handler->get_instance_data($this->course->id);
            foreach ($datas as $data) {
                $field = $data->get_field();
                $fshortname = $field->get('shortname');
                if ($fshortname == $fieldshortname) {
                    $visibility = $field->get_configdata_property("visibility");
                    $raw = $data->get_value();
                    $type = $field->get('type');

                    // Only show if visibility is "Everyone" or ("Teachers" and in teacher view ).
                    if ($visibility > 0 && ($visibility == 2 || $includeteacher)) {
                        if ($type == "date") {
                            // Date should be converted to YYYY-MM-DD so the javascript can properly format it.
                            if ($raw == 0) {
                                $value = "";
                            } else {
                                // Convert to YYYY-MM-DD format.
                                $value = date("Y-m-d", $raw);
                            }
                        } else {
                            if (empty($PAGE->context)) {
                                $PAGE->set_context(context_system::instance());
                            }
                            // Everything else can just use the export value.
                            $value = $data->export_value();
                        }

                        return [$value, $type, $raw];
                    }
                }
            }
        }
        // Fallback to empty if finding a match fails.
        return [null, null, null];
    }
}
