<?php
declare(strict_types=1);

namespace Help4BuilderSuite\Modules\Lms;

use Help4BuilderSuite\Contracts\ModuleContract;
use Help4BuilderSuite\Support\VideoPlayer;
use Help4BuilderSuite\Support\RobotsContract;
use WP_REST_Request;
use WP_REST_Response;

if (! defined('ABSPATH')) {
    exit;
}

// LMS progress/certificate records live in dedicated plugin tables.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching

final class Module implements ModuleContract
{
    public function key(): string
    {
        return 'lms';
    }

    public function boot(): void
    {
        add_action('init', [$this, 'registerPostTypes']);
        add_action('add_meta_boxes_h4_course', [$this, 'registerCourseMetaBox']);
        add_action('save_post_h4_course', [$this, 'saveCourseMeta']);
        add_filter('h4bs_visible_course_records', [$this, 'visibleCourseRecords'], 10, 2);
        add_filter('wp_sitemaps_posts_query_args', [$this, 'filterCourseSitemap'], 20, 2);
        add_action('pre_get_posts', [$this, 'filterCourseArchive']);
        add_filter('wp_robots', [$this, 'courseRobots']);
        add_action('template_redirect', [$this, 'startCourseHeadingGuard'], -90);
        add_filter('the_content', [$this, 'appendCourseDetails']);
        add_action('wp_head', [$this, 'renderCourseSchema'], 35);
        add_action('admin_menu', [$this, 'registerAdminPage']);
        add_action('rest_api_init', [$this, 'registerRestRoutes']);
        add_shortcode('h4_lms_courses', [$this, 'renderCoursesShortcode']);
        add_shortcode('h4_lms_progress', [$this, 'renderProgressShortcode']);
        add_shortcode('h4_lms_certificate', [$this, 'renderCertificateShortcode']);
        add_shortcode('h4_lms_video', [$this, 'renderVideoShortcode']);
        add_shortcode('holistic_lms_video', [$this, 'renderVideoShortcode']);
    }

    public function registerPostTypes(): void
    {
        register_post_type(
            'h4_course',
            [
                'label'           => __('Help4 Courses', 'help4-builder-suite'),
                'public'          => true,
                'has_archive'     => 'class-catalog',
                'rewrite'         => ['slug' => 'classes'],
                'show_ui'         => true,
                'show_in_rest'    => true,
                'menu_icon'       => 'dashicons-welcome-learn-more',
                'supports'        => ['title', 'editor', 'thumbnail', 'excerpt', 'revisions'],
                'show_in_menu'    => 'h4bs-settings',
                'capability_type' => 'post',
                'map_meta_cap'    => true,
            ]
        );

        register_post_type(
            'h4_lesson',
            [
                'label'           => __('Help4 Lessons', 'help4-builder-suite'),
                'public'          => false,
                'show_ui'         => true,
                'show_in_rest'    => true,
                'menu_icon'       => 'dashicons-media-document',
                'supports'        => ['title', 'editor', 'excerpt', 'revisions'],
                'show_in_menu'    => 'h4bs-settings',
                'capability_type' => 'post',
                'map_meta_cap'    => true,
            ]
        );
    }

    public function registerCourseMetaBox(): void
    {
        add_meta_box('h4bs-course-catalog', __('Class catalog details', 'help4-builder-suite'), [$this, 'renderCourseMetaBox'], 'h4_course', 'normal', 'high');
    }

    public function renderCourseMetaBox(\WP_Post $post): void
    {
        wp_nonce_field('h4bs_course_catalog', 'h4bs_course_catalog_nonce');
        $data = $this->courseData($post->ID);
        $fields = ['provider_name'=>'Provider / organization','provider_url'=>'Provider URL','ages'=>'Ages','level'=>'Level','discipline'=>'Discipline','schedule'=>'Schedule','credential'=>'Actual credential awarded (leave blank for ordinary classes)'];
        echo '<p>' . esc_html__('Use concise factual details. The public class page and Course schema use the same saved values.', 'help4-builder-suite') . '</p><div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px">';
        foreach ($fields as $key => $label) printf('<p><label><strong>%s</strong><br><input class="widefat" name="h4bs_course[%s]" value="%s"></label></p>', esc_html($label), esc_attr($key), esc_attr((string) $data[$key]));
        echo '<p><label><strong>' . esc_html__('Catalog visibility', 'help4-builder-suite') . '</strong><br><select name="h4bs_course[visibility]"><option value="visible" ' . selected($data['visibility'], 'visible', false) . '>' . esc_html__('Visible', 'help4-builder-suite') . '</option><option value="hidden" ' . selected($data['visibility'], 'hidden', false) . '>' . esc_html__('Hidden', 'help4-builder-suite') . '</option></select></label></p>';
        echo '<p style="grid-column:1/-1"><label><strong>' . esc_html__('Factual learning goals (one per line)', 'help4-builder-suite') . '</strong><br><textarea class="widefat" rows="5" name="h4bs_course[learning_goals]">' . esc_textarea(implode("\n", $data['learning_goals'])) . '</textarea></label></p></div>';
    }

    public function saveCourseMeta(int $postId): void
    {
        if (! isset($_POST['h4bs_course_catalog_nonce']) || ! wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['h4bs_course_catalog_nonce'])), 'h4bs_course_catalog') || ! current_user_can('edit_post', $postId)) return;
        $raw = isset($_POST['h4bs_course']) && is_array($_POST['h4bs_course']) ? map_deep(wp_unslash($_POST['h4bs_course']), 'sanitize_text_field') : [];
        $clean = $this->sanitizeCourseData(is_array($raw) ? $raw : []);
        update_post_meta($postId, '_h4_course_catalog', $clean);
        $post = get_post($postId);
        $description = $post instanceof \WP_Post ? wp_strip_all_tags($post->post_excerpt ?: wp_trim_words($post->post_content, 35)) : '';
        update_post_meta($postId, '_h4_seo_noindex', $this->courseEligible($clean, $description) ? '0' : '1');
    }

    /** @return array<string, mixed> */
    private function courseData(int $postId): array
    {
        $raw = get_post_meta($postId, '_h4_course_catalog', true);
        return $this->sanitizeCourseData(is_array($raw) ? $raw : []);
    }

    /** @param array<string, mixed> $raw @return array<string, mixed> */
    private function sanitizeCourseData(array $raw): array
    {
        $goals = is_array($raw['learning_goals'] ?? null) ? $raw['learning_goals'] : preg_split('/\r?\n/', (string) ($raw['learning_goals'] ?? ''));
        return [
            'provider_name'=>sanitize_text_field((string) ($raw['provider_name'] ?? '')),
            'provider_url'=>esc_url_raw((string) ($raw['provider_url'] ?? '')),
            'ages'=>sanitize_text_field((string) ($raw['ages'] ?? '')),
            'level'=>sanitize_text_field((string) ($raw['level'] ?? '')),
            'discipline'=>sanitize_text_field((string) ($raw['discipline'] ?? '')),
            'schedule'=>sanitize_text_field((string) ($raw['schedule'] ?? '')),
            'credential'=>sanitize_text_field((string) ($raw['credential'] ?? '')),
            'visibility'=>(string) ($raw['visibility'] ?? 'visible') === 'hidden' ? 'hidden' : 'visible',
            'learning_goals'=>array_values(array_filter(array_map('sanitize_text_field', (array) $goals))),
        ];
    }

    /** @param array<string, mixed> $data */
    private function courseEligible(array $data, string $description): bool
    {
        return $data['visibility'] === 'visible' && trim($description) !== '' && $data['provider_name'] !== '' && $data['learning_goals'] !== [];
    }

    /** @param array<string, mixed> $args @return array<string, mixed> */
    public function filterCourseSitemap(array $args, string $postType): array
    {
        if ($postType !== 'h4_course') return $args;
        $excluded = [];
        foreach (get_posts(['post_type'=>'h4_course','post_status'=>'publish','posts_per_page'=>-1]) as $course) {
            $data = $this->courseData((int) $course->ID);
            $description = wp_strip_all_tags($course->post_excerpt ?: wp_trim_words($course->post_content, 35));
            if (! $this->courseEligible($data, $description)) $excluded[] = (int) $course->ID;
        }
        // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Core sitemap query must exclude the bounded set of ineligible published course IDs.
        if ($excluded) $args['post__not_in'] = array_values(array_unique(array_merge((array) ($args['post__not_in'] ?? []), $excluded)));
        return $args;
    }

    /** @return array<int, int> */
    private function ineligibleCourseIds(): array
    {
        $excluded = [];
        foreach (get_posts(['post_type'=>'h4_course','post_status'=>'publish','posts_per_page'=>-1]) as $course) {
            $data = $this->courseData((int) $course->ID);
            $description = wp_strip_all_tags($course->post_excerpt ?: wp_trim_words($course->post_content, 35));
            if (! $this->courseEligible($data, $description)) $excluded[] = (int) $course->ID;
        }
        return $excluded;
    }

    public function filterCourseArchive(\WP_Query $query): void
    {
        if (is_admin() || ! $query->is_main_query() || ! $query->is_post_type_archive('h4_course')) return;
        $excluded = $this->ineligibleCourseIds();
        // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Public catalog must omit the bounded set of fail-closed course records.
        if ($excluded) $query->set('post__not_in', $excluded);
    }

    /** @param array<string, bool> $robots @return array<string, bool> */
    public function courseRobots(array $robots): array
    {
        $noindex = false;
        if (is_singular('h4_course')) {
            $post = get_post((int) get_queried_object_id());
            if ($post instanceof \WP_Post) $noindex = ! $this->courseEligible($this->courseData($post->ID), wp_strip_all_tags($post->post_excerpt ?: wp_trim_words($post->post_content, 35)));
        } elseif (is_post_type_archive('h4_course')) {
            $published = (int) wp_count_posts('h4_course')->publish;
            $noindex = $published <= count($this->ineligibleCourseIds());
        }
        if ($noindex) { $robots['noindex']=true; $robots['follow']=true; unset($robots['index']); }
        return $robots;
    }

    public function startCourseHeadingGuard(): void
    {
        if (! is_singular('h4_course') && ! is_post_type_archive('h4_course')) return;
        ob_start([$this, 'guardCourseHeadingHtml']);
    }

    public function guardCourseHeadingHtml(string $html): string
    {
        if (! preg_match('/<main\b[^>]*>/i', $html, $mainMatch, PREG_OFFSET_CAPTURE)) return $html;
        $mainTag = (string) $mainMatch[0][0];
        $mainOffset = (int) $mainMatch[0][1];
        $prefix = substr($html, 0, $mainOffset);
        $main = substr($html, $mainOffset);
        $prefix = (string) preg_replace('/<h1\b([^>]*)>(.*?)<\/h1>/is', '<p$1 data-h4bs-heading-demoted="course-site-title">$2</p>', $prefix);

        if (is_post_type_archive('h4_course')) {
            $main = (string) preg_replace_callback('/<article\b[^>]*>.*?<\/article>/is', static function (array $match): string {
                return (string) preg_replace(['/<h1\b([^>]*)>/i', '/<\/h1>/i'], ['<h2$1>', '</h2>'], $match[0]);
            }, $main);
            if (! preg_match('/<h1\b/i', $main)) {
                $title = '<h1 class="h4bs-course-archive-title">' . esc_html__('Class catalog', 'help4-builder-suite') . '</h1>';
                $main = preg_replace('/' . preg_quote($mainTag, '/') . '/i', $mainTag . $title, $main, 1) ?: $main;
            }
        } else {
            $seen = 0;
            $main = (string) preg_replace_callback('/<h1\b([^>]*)>(.*?)<\/h1>/is', static function (array $match) use (&$seen): string {
                $seen++;
                return $seen === 1 ? $match[0] : '<h2' . $match[1] . '>' . $match[2] . '</h2>';
            }, $main);
        }
        return $prefix . $main;
    }

    /** @param array<int, array<string, mixed>> $records @return array<int, array<string, mixed>> */
    public function visibleCourseRecords(array $records, int $postId): array
    {
        foreach (get_posts(['post_type'=>'h4_course','post_status'=>'publish','posts_per_page'=>200,'orderby'=>'menu_order title','order'=>'ASC']) as $course) {
            $data = $this->courseData((int) $course->ID);
            if ($data['visibility'] !== 'visible') continue;
            $records[] = ['name'=>$course->post_title,'description'=>wp_strip_all_tags($course->post_excerpt ?: wp_trim_words($course->post_content, 35)),'url'=>get_permalink($course),'provider_name'=>$data['provider_name'],'provider_url'=>$data['provider_url'],'ages'=>$data['ages'],'level'=>$data['level'],'discipline'=>$data['discipline'],'schedule'=>$data['schedule'],'learning_goals'=>$data['learning_goals'],'credential'=>$data['credential'],'status'=>'publish'];
        }
        return $records;
    }

    public function appendCourseDetails(string $content): string
    {
        if (! is_singular('h4_course') || ! in_the_loop() || ! is_main_query()) return $content;
        $data = $this->courseData((int) get_queried_object_id());
        if ($data['visibility'] !== 'visible') return $content;
        $rows = ['Ages'=>$data['ages'],'Level'=>$data['level'],'Discipline'=>$data['discipline'],'Schedule'=>$data['schedule'],'Provider'=>$data['provider_name']];
        $html = '<section class="h4bs-course-facts" aria-labelledby="h4bs-course-facts-title"><h2 id="h4bs-course-facts-title">' . esc_html__('Class details', 'help4-builder-suite') . '</h2><dl>';
        foreach ($rows as $label=>$value) if ($value !== '') $html .= '<dt>' . esc_html($label) . '</dt><dd>' . esc_html((string) $value) . '</dd>';
        $html .= '</dl>';
        if ($data['learning_goals']) { $html .= '<h3>' . esc_html__('What participants will learn', 'help4-builder-suite') . '</h3><ul>'; foreach ($data['learning_goals'] as $goal) $html .= '<li>' . esc_html($goal) . '</li>'; $html .= '</ul>'; }
        return $content . $html . '</section>';
    }

    public function renderCourseSchema(): void
    {
        if (! is_singular('h4_course')) return;
        if (! RobotsContract::isIndexable()) return;
        $postId = (int) get_queried_object_id(); $post = get_post($postId); $data = $this->courseData($postId);
        $description = $post instanceof \WP_Post ? wp_strip_all_tags($post->post_excerpt ?: wp_trim_words($post->post_content, 35)) : '';
        if (! $post instanceof \WP_Post || ! $this->courseEligible($data, $description)) return;
        $schema=['@context'=>'https://schema.org','@type'=>'Course','@id'=>get_permalink($postId).'#course','url'=>get_permalink($postId),'name'=>$post->post_title,'description'=>$description,'provider'=>['@type'=>'Organization','name'=>$data['provider_name']],'teaches'=>$data['learning_goals']];
        if ($data['provider_url'] !== '') $schema['provider']['url']=$data['provider_url'];
        if ($data['level'] !== '') $schema['educationalLevel']=$data['level'];
        if ($data['ages'] !== '') $schema['typicalAgeRange']=$data['ages'];
        if ($data['credential'] !== '') $schema['educationalCredentialAwarded']=$data['credential'];
        wp_print_inline_script_tag(wp_json_encode($schema, JSON_UNESCAPED_SLASHES|JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_APOS|JSON_HEX_QUOT), ['type'=>'application/ld+json','class'=>'h4bs-course-schema']);
    }

    public function registerAdminPage(): void
    {
        add_submenu_page(
            'h4bs-settings',
            __('LMS Dashboard', 'help4-builder-suite'),
            __('LMS', 'help4-builder-suite'),
            'manage_options',
            'h4bs-lms',
            [$this, 'renderAdminPage']
        );
    }

    public function renderAdminPage(): void
    {
        if (! current_user_can('manage_options')) {
            return;
        }

        global $wpdb;
        $courses = (int) wp_count_posts('h4_course')->publish;
        $lessons = (int) wp_count_posts('h4_lesson')->publish;
        $enrollments = (int) $wpdb->get_var($wpdb->prepare('SELECT COUNT(*) FROM %i', $this->enrollmentsTable()));
        $certificates = (int) $wpdb->get_var($wpdb->prepare('SELECT COUNT(*) FROM %i', $this->certificatesTable()));
        ?>
        <div class="wrap h4bs-admin-wrap">
            <h1><?php esc_html_e('Help4 LMS', 'help4-builder-suite'); ?></h1>
            <p><?php esc_html_e('Course, enrollment, progress, and certification foundation is active.', 'help4-builder-suite'); ?></p>
            <p>
                <a class="button button-primary" href="<?php echo esc_url(admin_url('edit.php?post_type=h4_course')); ?>"><?php esc_html_e('Manage Courses', 'help4-builder-suite'); ?></a>
                <a class="button" href="<?php echo esc_url(admin_url('edit.php?post_type=h4_lesson')); ?>"><?php esc_html_e('Manage Lessons', 'help4-builder-suite'); ?></a>
            </p>

            <table class="widefat striped" style="max-width:900px;">
                <tbody>
                    <tr><th><?php esc_html_e('Published Courses', 'help4-builder-suite'); ?></th><td><?php echo esc_html((string) $courses); ?></td></tr>
                    <tr><th><?php esc_html_e('Published Lessons', 'help4-builder-suite'); ?></th><td><?php echo esc_html((string) $lessons); ?></td></tr>
                    <tr><th><?php esc_html_e('Enrollments', 'help4-builder-suite'); ?></th><td><?php echo esc_html((string) $enrollments); ?></td></tr>
                    <tr><th><?php esc_html_e('Certificates Issued', 'help4-builder-suite'); ?></th><td><?php echo esc_html((string) $certificates); ?></td></tr>
                </tbody>
            </table>
        </div>
        <?php
    }

    public function registerRestRoutes(): void
    {
        register_rest_route(
            'h4bs/v1',
            '/lms/courses',
            [
                'methods'             => 'GET',
                'callback'            => [$this, 'listCourses'],
                'permission_callback' => '__return_true',
            ]
        );

        register_rest_route(
            'h4bs/v1',
            '/lms/enroll',
            [
                'methods'             => 'POST',
                'callback'            => [$this, 'enrollLearner'],
                'permission_callback' => static fn (): bool => current_user_can('read'),
            ]
        );

        register_rest_route(
            'h4bs/v1',
            '/lms/progress',
            [
                'methods'             => 'POST',
                'callback'            => [$this, 'updateProgress'],
                'permission_callback' => static fn (): bool => current_user_can('read'),
            ]
        );

        register_rest_route(
            'h4bs/v1',
            '/lms/certificates/issue',
            [
                'methods'             => 'POST',
                'callback'            => [$this, 'issueCertificate'],
                'permission_callback' => static fn (): bool => current_user_can('manage_options'),
            ]
        );
    }

    public function listCourses(WP_REST_Request $request): WP_REST_Response
    {
        $limit = max(1, min(50, (int) $request->get_param('limit')));
        $items = get_posts(
            [
                'post_type'      => 'h4_course',
                'post_status'    => ['publish'],
                'posts_per_page' => $limit,
            ]
        );

        $courses = [];
        foreach ($items as $course) {
            $courses[] = [
                'id'      => (int) $course->ID,
                'title'   => (string) $course->post_title,
                'excerpt' => (string) $course->post_excerpt,
            ];
        }

        return new WP_REST_Response(['items' => $courses]);
    }

    public function enrollLearner(WP_REST_Request $request): WP_REST_Response
    {
        global $wpdb;

        $payload = $request->get_json_params();
        if (! is_array($payload)) {
            $payload = $request->get_params();
        }

        $isAdmin = current_user_can('manage_options');
        $currentUserId = get_current_user_id();

        $courseId = absint($payload['course_id'] ?? 0);
        $course = $courseId > 0 ? get_post($courseId) : null;
        if (! $course || $course->post_type !== 'h4_course') {
            return new WP_REST_Response(['message' => 'Invalid course'], 422);
        }
        if (! $isAdmin && $course->post_status !== 'publish') {
            return new WP_REST_Response(['message' => 'Invalid course'], 422);
        }

        $requestedUserId = absint($payload['user_id'] ?? 0);
        $userId = ($isAdmin && $requestedUserId > 0) ? $requestedUserId : $currentUserId;
        $email = sanitize_email((string) ($payload['email'] ?? ''));
        if (! $isAdmin) {
            $user = wp_get_current_user();
            $userEmail = sanitize_email((string) ($user->user_email ?? ''));
            $email = $userEmail !== '' ? $userEmail : $email;
        }
        if ($userId <= 0 && $email === '') {
            return new WP_REST_Response(['message' => 'User id or email required'], 422);
        }

        $table = $this->enrollmentsTable();
        $existingId = (int) $wpdb->get_var(
            $wpdb->prepare(
                'SELECT id FROM %i WHERE course_post_id = %d AND user_id = %d AND user_email = %s LIMIT 1',
                $table,
                $courseId,
                $userId,
                $email
            )
        );

        if ($existingId > 0) {
            $wpdb->update(
                $table,
                ['status' => 'active'],
                ['id' => $existingId],
                ['%s'],
                ['%d']
            );

            return new WP_REST_Response(['id' => $existingId, 'status' => 'active']);
        }

        $wpdb->insert(
            $table,
            [
                'course_post_id'   => $courseId,
                'user_id'          => $userId > 0 ? $userId : null,
                'user_email'       => $email,
                'status'           => 'active',
                'progress_percent' => 0,
                'payload'          => wp_json_encode($payload),
            ],
            ['%d', '%d', '%s', '%s', '%f', '%s']
        );

        return new WP_REST_Response(['id' => (int) $wpdb->insert_id, 'status' => 'active'], 201);
    }

    public function updateProgress(WP_REST_Request $request): WP_REST_Response
    {
        global $wpdb;

        $payload = $request->get_json_params();
        if (! is_array($payload)) {
            $payload = $request->get_params();
        }

        $enrollmentId = absint($payload['enrollment_id'] ?? 0);
        $percent = max(0, min(100, (float) ($payload['progress_percent'] ?? 0)));
        if ($enrollmentId <= 0) {
            return new WP_REST_Response(['message' => 'Enrollment id is required'], 422);
        }

        $table = $this->enrollmentsTable();
        $enrollment = $wpdb->get_row(
            $wpdb->prepare(
                'SELECT id, user_id, user_email FROM %i WHERE id = %d LIMIT 1',
                $table,
                $enrollmentId
            ),
            ARRAY_A
        );
        if (! is_array($enrollment)) {
            return new WP_REST_Response(['message' => 'Enrollment not found'], 404);
        }

        if (! current_user_can('manage_options')) {
            $currentUserId = get_current_user_id();
            if ($currentUserId <= 0) {
                return new WP_REST_Response(['message' => 'Unauthorized'], 403);
            }

            $currentUser = wp_get_current_user();
            $currentEmail = strtolower(sanitize_email((string) ($currentUser->user_email ?? '')));
            $rowUserId = absint($enrollment['user_id'] ?? 0);
            $rowEmail = strtolower(sanitize_email((string) ($enrollment['user_email'] ?? '')));

            $ownsByUserId = $rowUserId > 0 && $rowUserId === $currentUserId;
            $ownsByEmail = $rowUserId <= 0 && $rowEmail !== '' && $rowEmail === $currentEmail;
            if (! $ownsByUserId && ! $ownsByEmail) {
                return new WP_REST_Response(['message' => 'Unauthorized'], 403);
            }
        }

        $updated = $wpdb->update(
            $table,
            ['progress_percent' => $percent, 'status' => $percent >= 100 ? 'completed' : 'active'],
            ['id' => $enrollmentId],
            ['%f', '%s'],
            ['%d']
        );

        if ($updated === false) {
            return new WP_REST_Response(['message' => 'Unable to update progress'], 500);
        }

        return new WP_REST_Response(['id' => $enrollmentId, 'progress_percent' => $percent]);
    }

    public function issueCertificate(WP_REST_Request $request): WP_REST_Response
    {
        global $wpdb;

        $payload = $request->get_json_params();
        if (! is_array($payload)) {
            $payload = $request->get_params();
        }

        $enrollmentId = absint($payload['enrollment_id'] ?? 0);
        if ($enrollmentId <= 0) {
            return new WP_REST_Response(['message' => 'Enrollment id required'], 422);
        }

        $code = 'H4CERT-' . strtoupper(wp_generate_password(10, false, false));
        $wpdb->insert(
            $this->certificatesTable(),
            [
                'enrollment_id'    => $enrollmentId,
                'certificate_code' => $code,
                'payload'          => wp_json_encode($payload),
            ],
            ['%d', '%s', '%s']
        );

        return new WP_REST_Response(['certificate_code' => $code, 'id' => (int) $wpdb->insert_id], 201);
    }

    /**
     * @param array<string, mixed> $atts
     */
    public function renderCoursesShortcode(array $atts): string
    {
        $atts = shortcode_atts(['limit' => 12], $atts, 'h4_lms_courses');
        $items = get_posts(
            [
                'post_type'      => 'h4_course',
                'post_status'    => ['publish'],
                'posts_per_page' => max(1, min(60, (int) $atts['limit'])),
            ]
        );
        if (! $items) {
            return '<p>No courses available yet.</p>';
        }

        ob_start();
        ?>
        <div class="h4bs-template-library" data-h4bs-module="library" data-h4bs-skin="surface">
            <?php foreach ($items as $item) : ?>
                <article class="h4bs-template-card" data-h4bs-skin="card">
                    <h3><a href="<?php echo esc_url(get_permalink($item)); ?>"><?php echo esc_html($item->post_title); ?></a></h3>
                    <p><?php echo esc_html(wp_trim_words($item->post_excerpt ?: $item->post_content, 20)); ?></p>
                </article>
            <?php endforeach; ?>
        </div>
        <?php
        return (string) ob_get_clean();
    }

    /**
     * @param array<string, mixed> $atts
     */
    public function renderProgressShortcode(array $atts): string
    {
        global $wpdb;

        $userId = get_current_user_id();
        if ($userId <= 0) {
            return '<p>Please log in to view course progress.</p>';
        }

        $rows = $wpdb->get_results(
            $wpdb->prepare(
                'SELECT course_post_id, progress_percent, status FROM %i WHERE user_id = %d ORDER BY updated_at DESC LIMIT 50',
                $this->enrollmentsTable(),
                $userId
            ),
            ARRAY_A
        );
        if (! is_array($rows) || ! $rows) {
            return '<p>No enrollments yet.</p>';
        }

        ob_start();
        ?>
        <table class="widefat striped" data-h4bs-module="progress" data-h4bs-role="progress">
            <thead><tr><th><?php esc_html_e('Course', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Progress', 'help4-builder-suite'); ?></th><th><?php esc_html_e('Status', 'help4-builder-suite'); ?></th></tr></thead>
            <tbody>
                <?php foreach ($rows as $row) : ?>
                    <tr>
                        <td><?php echo esc_html(get_the_title((int) ($row['course_post_id'] ?? 0))); ?></td>
                        <td><?php echo esc_html(number_format((float) ($row['progress_percent'] ?? 0), 1)); ?>%</td>
                        <td><?php echo esc_html((string) ($row['status'] ?? 'active')); ?></td>
                    </tr>
                <?php endforeach; ?>
            </tbody>
        </table>
        <?php
        return (string) ob_get_clean();
    }

    /**
     * @param array<string, mixed> $atts
     */
    public function renderCertificateShortcode(array $atts): string
    {
        global $wpdb;

        $atts = shortcode_atts(['code' => ''], $atts, 'h4_lms_certificate');
        $code = sanitize_text_field((string) $atts['code']);
        if ($code === '') {
            return '<p>Certificate code is required.</p>';
        }

        $row = $wpdb->get_row(
            $wpdb->prepare(
                'SELECT * FROM %i WHERE certificate_code = %s LIMIT 1',
                $this->certificatesTable(),
                $code
            ),
            ARRAY_A
        );
        if (! is_array($row)) {
            return '<p>Certificate not found.</p>';
        }

        $style = $this->globalTokenStyleSnapshot();
        return '<div data-h4bs-module="certificates" data-h4bs-skin="card" style="' . esc_attr($style) . '"><p>Certificate verified: <strong>' . esc_html($code) . '</strong></p></div>';
    }

    /**
     * @param array<string, mixed> $atts
     */
    public function renderVideoShortcode(array $atts): string
    {
        $atts = shortcode_atts(
            [
                'post_id' => 0,
                'context' => 'lesson',
                'access' => 'logged_in',
                'source' => '',
                'hls' => '',
                'mp4' => '',
                'poster' => '',
                'fallback_image' => '',
                'title' => __('Course video', 'help4-builder-suite'),
                'autoplay' => '0',
                'muted' => '0',
                'loop' => '0',
            ],
            $atts,
            'h4_lms_video'
        );
        $postId = absint($atts['post_id']);
        $context = sanitize_key((string) $atts['context']) ?: 'lesson';
        $access = sanitize_key((string) $atts['access']) ?: 'logged_in';
        $allowed = $access === 'public' || ($access === 'logged_in' && is_user_logged_in());
        $allowed = (bool) apply_filters('h4bs_lms_video_access', $allowed, $postId, $context, $atts);
        if (! $allowed) {
            return '<div data-h4bs-module="video" data-h4bs-skin="card"><p>' . esc_html__('Please sign in with an account that has access to this video.', 'help4-builder-suite') . '</p></div>';
        }

        $sources = apply_filters(
            'h4bs_lms_video_sources',
            [
                'source' => esc_url_raw((string) $atts['source']),
                'hls' => esc_url_raw((string) $atts['hls']),
                'mp4' => esc_url_raw((string) $atts['mp4']),
                'poster' => esc_url_raw((string) $atts['poster']),
                'fallback_image' => esc_url_raw((string) $atts['fallback_image']),
            ],
            $postId,
            $context,
            $atts
        );
        $sources = is_array($sources) ? $sources : [];
        $source = esc_url_raw((string) ($sources['source'] ?? ''));
        $hls = esc_url_raw((string) ($sources['hls'] ?? ''));
        $mp4 = esc_url_raw((string) ($sources['mp4'] ?? ''));
        $poster = esc_url_raw((string) ($sources['poster'] ?? ''));
        $title = sanitize_text_field((string) $atts['title']) ?: __('Course video', 'help4-builder-suite');

        if ($source !== '' && preg_match('#(?:youtube\.com|youtu\.be|vimeo\.com)#i', $source) === 1) {
            $embed = wp_oembed_get($source, ['width' => 1280]);
            if (is_string($embed) && $embed !== '') {
                return '<div data-h4bs-module="video" data-h4bs-skin="surface" aria-label="' . esc_attr($title) . '">' . wp_kses_post($embed) . '</div>';
            }
        }
        if ($hls === '' && preg_match('/\.m3u8?(?:\?|$)/i', $source) === 1) {
            $hls = $source;
        } elseif ($mp4 === '' && preg_match('/\.(?:mp4|m4v|webm)(?:\?|$)/i', $source) === 1) {
            $mp4 = $source;
        }
        if ($hls === '' && $mp4 === '') {
            return '<div data-h4bs-module="video" data-h4bs-skin="card"><p>' . esc_html__('No playable video source is configured.', 'help4-builder-suite') . '</p></div>';
        }

        $html = VideoPlayer::render(
            [
                'primary' => $hls === '' ? $mp4 : '',
                'hls' => $hls,
                'fallback_video' => $hls !== '' ? $mp4 : '',
                'poster' => $poster,
                'fallback_image' => (string) ($sources['fallback_image'] ?? ''),
                'title' => $title,
                'controls' => true,
                'preload' => 'metadata',
                'aspect_ratio' => (string) ($sources['aspect_ratio'] ?? '16:9'),
                'autoplay' => rest_sanitize_boolean($atts['autoplay']),
                'muted' => rest_sanitize_boolean($atts['muted']),
                'loop' => rest_sanitize_boolean($atts['loop']),
                'legacy_lms' => true,
                'class' => 'h4bs-builder-video h4bs-lms-video',
            ]
        );
        return (string) apply_filters('h4bs_lms_video_html', $html, $sources, $postId, $context, $atts);
    }

    private function globalTokenStyleSnapshot(): string
    {
        $stored = get_option('h4bs_global_design_tokens', []);
        $tokens = apply_filters(
            'h4bs_builder_design_tokens',
            is_array($stored) ? $stored : [],
            '1.0'
        );
        $colors = is_array($tokens) && is_array($tokens['colors'] ?? null) ? $tokens['colors'] : [];
        $primary = sanitize_hex_color((string) ($colors['primary'] ?? '#0f6cbf')) ?: '#0f6cbf';
        $text = sanitize_hex_color((string) ($colors['text'] ?? '#1f2937')) ?: '#1f2937';
        $surface = sanitize_hex_color((string) ($colors['surface'] ?? '#ffffff')) ?: '#ffffff';
        $border = sanitize_hex_color((string) ($colors['border'] ?? '#d8e2ec')) ?: '#d8e2ec';
        return '--h4bs-color-primary:' . $primary . ';--h4bs-color-text:' . $text . ';--h4bs-color-surface:' . $surface . ';--h4bs-color-border:' . $border;
    }

    private function enrollmentsTable(): string
    {
        global $wpdb;
        return $wpdb->prefix . 'h4_lms_enrollments';
    }

    private function certificatesTable(): string
    {
        global $wpdb;
        return $wpdb->prefix . 'h4_lms_certificates';
    }
}
