<?php
/**
 * Admin UI controller.
 *
 * @package TradeSchoolManager
 */

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

/*
 * Admin shell hooks like `tradeschool_manager_admin_menu`, `tradeschool_manager_settings_tabs`,
 * `tradeschool_manager_after_save_student`, etc. all use the long plugin prefix
 * (see phpcs.xml.dist) and are part of the documented add-on API  - 
 * renaming them would break every TradeSchool add-on plugin.
 *
 * Direct $wpdb usage in this file targets the plugin's own custom
 * tables only; nonces are checked at the top of each POST handler in
 * handle_actions(), and subsequent reads of validated $_GET/$_POST
 * tab/state values therefore do not each need a per-line annotation.
 */
// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// phpcs:disable WordPress.Security.NonceVerification.Recommended

class TSM_Admin {
	private static $instance = null;
	public static function instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	private static function escape_csv_cell( $value ) {
		if ( is_scalar( $value ) || null === $value ) {
			$value = (string) $value;
			if ( '' !== $value && preg_match( '/^[=\-+@]/', $value ) ) {
				return "'" . $value;
			}
		}
		return $value;
	}

	private static function putcsv( $handle, $row ) {
		fputcsv( $handle, array_map( array( __CLASS__, 'escape_csv_cell' ), (array) $row ) );
	}

	private static function sanitize_hook_payload( $value ) {
		if ( is_array( $value ) ) {
			$clean = array();
			foreach ( $value as $key => $item ) {
				$clean[ sanitize_key( (string) $key ) ] = self::sanitize_hook_payload( $item );
			}
			return $clean;
		}
		if ( is_scalar( $value ) || null === $value ) {
			return sanitize_textarea_field( (string) $value );
		}
		return '';
	}

	private static function sanitize_grade_payload( $values ) {
		$values = (array) $values;
		return array(
			'status'         => isset( $values['status'] ) ? sanitize_key( $values['status'] ) : '',
			'points_earned'  => isset( $values['points_earned'] ) && '' !== $values['points_earned'] ? (float) $values['points_earned'] : '',
			'bonus'          => isset( $values['bonus'] ) && '' !== $values['bonus'] ? (float) $values['bonus'] : '',
			'late_penalty'   => isset( $values['late_penalty'] ) && '' !== $values['late_penalty'] ? (float) $values['late_penalty'] : '',
			'submitted_date' => isset( $values['submitted_date'] ) ? sanitize_text_field( $values['submitted_date'] ) : '',
			'comments'       => isset( $values['comments'] ) ? sanitize_textarea_field( $values['comments'] ) : '',
			'excused'        => ! empty( $values['excused'] ) ? 1 : 0,
		);
	}

	private function __construct() {
		add_action( 'admin_menu', array( $this, 'menu' ) );
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
		add_action( 'admin_init', array( $this, 'handle_post' ) );
		add_action( 'admin_post_tradeschool_manager_core_report_card', array( $this, 'download_core_report_card' ) );
		add_action( 'admin_post_tradeschool_manager_core_roster_pdf', array( $this, 'download_core_roster_pdf' ) );
		add_filter( 'admin_body_class', array( $this, 'body_class' ) );
		add_action( 'admin_notices', array( $this, 'render_flash' ) );
	}

	/**
	 * Display one-shot admin flash messages set during POST handlers
	 * (e.g. license-lock blocks).
	 */
	public function render_flash() {
		$flash = get_transient( 'tsm_admin_flash' );
		if ( ! $flash ) { return; }
		delete_transient( 'tsm_admin_flash' );
		$type = isset( $flash['type'] ) ? sanitize_html_class( $flash['type'] ) : 'info';
		$msg  = isset( $flash['message'] ) ? $flash['message'] : '';
		if ( ! $msg ) { return; }
		echo '<div class="notice notice-' . esc_attr( $type ) . ' is-dismissible"><p>' . esc_html( $msg ) . '</p></div>';
	}

	public function body_class( $classes ) {
		if ( $this->is_tsm_page() ) {
			$classes .= ' tsm-app-page';
		}
		return $classes;
	}

	private function is_tsm_page() {
		$page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore
		return ( 0 === strpos( $page, 'tsm-' ) ) || 'tsm-dashboard' === $page || 'tsmp-' === substr( $page, 0, 5 );
	}

	public function enqueue( $hook ) {
		if ( false === strpos( (string) $hook, 'tsm' ) ) {
			return;
		}
		wp_enqueue_style(
			'tsm-admin',
			TSM_PLUGIN_URL . 'assets/css/admin.css',
			array(),
			TSM_VERSION
		);
		wp_enqueue_script(
			'tsm-chartjs',
			TSM_PLUGIN_URL . 'assets/js/chart.umd.min.js',
			array(),
			'4.4.1',
			true
		);
		wp_enqueue_script(
			'tsm-admin',
			TSM_PLUGIN_URL . 'assets/js/admin.js',
			array( 'jquery', 'tsm-chartjs' ),
			TSM_VERSION,
			true
		);
	}

	public function menu() {
		$cap   = 'manage_tradeschool';
		$icon  = 'dashicons-welcome-learn-more';
		add_menu_page( 'TradeSchool', 'TradeSchool', $cap, 'tsm-dashboard', array( $this, 'page_dashboard' ), $icon, 30 );
		add_submenu_page( 'tsm-dashboard', __( 'Dashboard', 'tradeschool-manager' ), __( 'Dashboard', 'tradeschool-manager' ), $cap, 'tsm-dashboard', array( $this, 'page_dashboard' ) );
		add_submenu_page( 'tsm-dashboard', __( 'Students', 'tradeschool-manager' ), __( 'Students', 'tradeschool-manager' ), $cap, 'tsm-students', array( $this, 'page_students' ) );
		add_submenu_page( 'tsm-dashboard', __( 'Teachers', 'tradeschool-manager' ), __( 'Teachers', 'tradeschool-manager' ), $cap, 'tsm-teachers', array( $this, 'page_teachers' ) );
		add_submenu_page( 'tsm-dashboard', __( 'Classes', 'tradeschool-manager' ), __( 'Classes', 'tradeschool-manager' ), $cap, 'tsm-classes', array( $this, 'page_classes' ) );
		add_submenu_page( 'tsm-dashboard', __( 'Programs', 'tradeschool-manager' ), __( 'Programs', 'tradeschool-manager' ), $cap, 'tsm-programs', array( $this, 'page_programs' ) );
		if ( function_exists( 'tsm_addon_active' ) && tsm_addon_active( 'invoicing' ) ) {
			add_submenu_page( 'tsm-dashboard', __( 'Tuition Audit', 'tradeschool-manager' ), __( 'Tuition Audit', 'tradeschool-manager' ), $cap, 'tsm-tuition-audit', array( $this, 'page_tuition_audit' ) );
		}
		add_submenu_page( 'tsm-dashboard', __( 'School Years', 'tradeschool-manager' ), __( 'School Years', 'tradeschool-manager' ), $cap, 'tsm-school-years', array( $this, 'page_school_years' ) );
		add_submenu_page( 'tsm-dashboard', __( 'Assignments', 'tradeschool-manager' ), __( 'Assignments', 'tradeschool-manager' ), $cap, 'tsm-assignments', array( $this, 'page_assignments' ) );
		add_submenu_page( 'tsm-dashboard', __( 'Gradebook', 'tradeschool-manager' ), __( 'Gradebook', 'tradeschool-manager' ), $cap, 'tsm-gradebook', array( $this, 'page_gradebook' ) );
		if ( function_exists( 'tsm_addon_active' ) && tsm_addon_active( 'reports_transcripts' ) ) {
			add_submenu_page( 'tsm-dashboard', __( 'Reports', 'tradeschool-manager' ), __( 'Reports', 'tradeschool-manager' ), $cap, 'tsm-reports', array( $this, 'page_reports' ) );
		}
		add_submenu_page( 'tsm-dashboard', __( 'Attendance', 'tradeschool-manager' ), __( 'Attendance', 'tradeschool-manager' ), $cap, 'tsm-attendance', array( $this, 'page_pro_locked_attendance' ) );
		// Applications submenu is registered by the standalone Applications add-on (v1.1.0+).
		// Manual payment recording removed in v1.6.4  -  superseded by the
		// Invoicing system (Invoices submenu, registered by TSMP_Features).
		add_submenu_page( 'tsm-dashboard', __( 'Settings', 'tradeschool-manager' ), __( 'Settings', 'tradeschool-manager' ), $cap, 'tsm-settings', array( $this, 'page_settings' ) );
		add_submenu_page( 'tsm-dashboard', __( 'Shortcodes', 'tradeschool-manager' ), __( 'Shortcodes', 'tradeschool-manager' ), $cap, 'tsm-shortcodes', array( $this, 'page_shortcodes' ) );
		add_submenu_page( 'tsm-dashboard', __( 'Documentation', 'tradeschool-manager' ), __( 'Documentation', 'tradeschool-manager' ), $cap, 'tsm-docs', array( $this, 'page_docs' ) );
		add_submenu_page( 'tsm-dashboard', __( 'About', 'tradeschool-manager' ), __( 'About', 'tradeschool-manager' ), $cap, 'tsm-about', array( $this, 'page_about' ) );
		// License page removed in v1.6.4  -  All-In-One build has no licensing.

		/** Pro plugin hooks here to register additional submenus. */
		do_action( 'tradeschool_manager_admin_menu', 'tsm-dashboard', $cap );
	}

	private function tabs() {
		// Legacy method kept for back-compat. The branded shell now renders
		// navigation in the sidebar instead.
	}

	/**
	 * Return the navigation items used in the branded sidebar.
	 *
	 * Filterable so the Pro plugin can add/replace items.
	 */
	private function nav_items() {
		$items = array(
			array( 'slug' => 'tsm-dashboard',    'label' => __( 'Dashboard',    'tradeschool-manager' ), 'icon' => 'dashicons-chart-area' ),
			array( 'slug' => 'tsm-students',     'label' => __( 'Students',     'tradeschool-manager' ), 'icon' => 'dashicons-groups' ),
			array( 'slug' => 'tsm-parents',      'label' => __( 'Sponsors',     'tradeschool-manager' ), 'icon' => 'dashicons-groups' ),
			array( 'slug' => 'tsm-teachers',     'label' => __( 'Teachers',     'tradeschool-manager' ), 'icon' => 'dashicons-businessperson' ),
			array( 'slug' => 'tsm-programs',     'label' => __( 'Programs',     'tradeschool-manager' ), 'icon' => 'dashicons-portfolio' ),
			array( 'slug' => 'tsm-classes',      'label' => __( 'Classes',      'tradeschool-manager' ), 'icon' => 'dashicons-welcome-learn-more' ),
			array( 'slug' => 'tsm-assignments',  'label' => __( 'Assignments',  'tradeschool-manager' ), 'icon' => 'dashicons-clipboard' ),
			array( 'slug' => 'tsm-gradebook',    'label' => __( 'Gradebook',    'tradeschool-manager' ), 'icon' => 'dashicons-book-alt' ),
			array( 'slug' => 'tsm-attendance',   'label' => __( 'Attendance',   'tradeschool-manager' ), 'icon' => 'dashicons-yes-alt' ),
			array( 'slug' => 'tsm-school-years', 'label' => __( 'School Years', 'tradeschool-manager' ), 'icon' => 'dashicons-calendar-alt' ),
			array( 'slug' => 'tsm-notes',        'label' => __( 'Notes',        'tradeschool-manager' ), 'icon' => 'dashicons-format-aside' ),
			array( 'slug' => 'tsm-announcements','label' => __( 'Announcements','tradeschool-manager' ), 'icon' => 'dashicons-megaphone' ),
			array( 'slug' => 'tsm-email-students','label' => __( 'Email Students','tradeschool-manager' ), 'icon' => 'dashicons-email' ),
			array( 'slug' => 'tsm-brevo',        'label' => __( 'Email Deliverability', 'tradeschool-manager' ), 'icon' => 'dashicons-cloud' ),
			array( 'slug' => 'tsm-csv',          'label' => __( 'Import / Export', 'tradeschool-manager' ), 'icon' => 'dashicons-upload' ),
		);

		if ( function_exists( 'tsm_addon_active' ) && tsm_addon_active( 'reports_transcripts' ) ) {
			$items[] = array( 'slug' => 'tsm-reports', 'label' => __( 'Reports', 'tradeschool-manager' ), 'icon' => 'dashicons-chart-bar', 'section' => 'addons' );
		}
		if ( function_exists( 'tsm_addon_active' ) && tsm_addon_active( 'custom_fields' ) ) {
			$items[] = array( 'slug' => 'tsm-custom-fields', 'label' => __( 'Custom Fields & Docs', 'tradeschool-manager' ), 'icon' => 'dashicons-forms', 'section' => 'addons' );
		}
		if ( function_exists( 'tsm_addon_active' ) && tsm_addon_active( 'invoicing' ) ) {
			$items[] = array( 'slug' => 'tsm-invoicing', 'label' => __( 'Invoicing', 'tradeschool-manager' ), 'icon' => 'dashicons-media-spreadsheet', 'section' => 'addons' );
			$items[] = array( 'slug' => 'tsm-tuition-audit', 'label' => __( 'Tuition Audit', 'tradeschool-manager' ), 'icon' => 'dashicons-money-alt', 'section' => 'addons' );
		}
		if ( function_exists( 'tsm_addon_active' ) && tsm_addon_active( 'workshops' ) ) {
			$items[] = array( 'slug' => 'tsm-workshops', 'label' => __( 'Workshops', 'tradeschool-manager' ), 'icon' => 'dashicons-tickets-alt', 'section' => 'addons' );
		}
		if ( function_exists( 'tsm_addon_active' ) && tsm_addon_active( 'certificates' ) ) {
			$items[] = array( 'slug' => 'tsm-certificates', 'label' => __( 'Certificates', 'tradeschool-manager' ), 'icon' => 'dashicons-awards', 'section' => 'addons' );
		}
		if ( function_exists( 'tsm_addon_active' ) && tsm_addon_active( 'applications' ) ) {
			$items[] = array( 'slug' => 'tsm-applications', 'label' => __( 'Applications', 'tradeschool-manager' ), 'icon' => 'dashicons-media-document', 'section' => 'addons' );
		}
		$items[] = array( 'slug' => 'tsm-settings',   'label' => __( 'Settings',      'tradeschool-manager' ), 'icon' => 'dashicons-admin-generic', 'section' => 'system' );
		$items[] = array( 'slug' => 'tsm-shortcodes', 'label' => __( 'Shortcodes',    'tradeschool-manager' ), 'icon' => 'dashicons-shortcode',     'section' => 'system' );
		$items[] = array( 'slug' => 'tsm-docs',       'label' => __( 'Documentation', 'tradeschool-manager' ), 'icon' => 'dashicons-book',          'section' => 'system' );
		$items[] = array( 'slug' => 'tsm-about',      'label' => __( 'About',         'tradeschool-manager' ), 'icon' => 'dashicons-info',          'section' => 'system' );
		return $this->normalize_nav_items( apply_filters( 'tradeschool_manager_admin_nav_items', $items ) );
	}

	private function normalize_nav_items( $items ) {
		$hidden_sidebar_slugs = array(
			'tsm-emails' => true,
		);
		$defaults = array(
			'tsm-dashboard'      => array( 'label' => __( 'Dashboard', 'tradeschool-manager' ), 'icon' => 'dashicons-chart-area', 'section' => 'manage' ),
			'tsm-students'       => array( 'label' => __( 'Students', 'tradeschool-manager' ), 'icon' => 'dashicons-groups', 'section' => 'manage' ),
			'tsm-parents'        => array( 'label' => __( 'Sponsors', 'tradeschool-manager' ), 'icon' => 'dashicons-groups', 'section' => 'manage' ),
			'tsm-teachers'       => array( 'label' => __( 'Teachers', 'tradeschool-manager' ), 'icon' => 'dashicons-businessperson', 'section' => 'manage' ),
			'tsm-programs'       => array( 'label' => __( 'Programs', 'tradeschool-manager' ), 'icon' => 'dashicons-portfolio', 'section' => 'manage' ),
			'tsm-classes'        => array( 'label' => __( 'Classes', 'tradeschool-manager' ), 'icon' => 'dashicons-welcome-learn-more', 'section' => 'manage' ),
			'tsm-assignments'    => array( 'label' => __( 'Assignments', 'tradeschool-manager' ), 'icon' => 'dashicons-clipboard', 'section' => 'manage' ),
			'tsm-gradebook'      => array( 'label' => __( 'Gradebook', 'tradeschool-manager' ), 'icon' => 'dashicons-book-alt', 'section' => 'manage' ),
			'tsm-attendance'     => array( 'label' => __( 'Attendance', 'tradeschool-manager' ), 'icon' => 'dashicons-yes-alt', 'section' => 'manage' ),
			'tsm-school-years'   => array( 'label' => __( 'School Years', 'tradeschool-manager' ), 'icon' => 'dashicons-calendar-alt', 'section' => 'manage' ),
			'tsm-notes'          => array( 'label' => __( 'Notes', 'tradeschool-manager' ), 'icon' => 'dashicons-format-aside', 'section' => 'manage' ),
			'tsm-announcements'  => array( 'label' => __( 'Announcements', 'tradeschool-manager' ), 'icon' => 'dashicons-megaphone', 'section' => 'manage' ),
			'tsm-email-students' => array( 'label' => __( 'Email Students', 'tradeschool-manager' ), 'icon' => 'dashicons-email', 'section' => 'manage' ),
			'tsm-brevo'          => array( 'label' => __( 'Email Deliverability', 'tradeschool-manager' ), 'icon' => 'dashicons-cloud', 'section' => 'manage' ),
			'tsm-csv'            => array( 'label' => __( 'Import / Export', 'tradeschool-manager' ), 'icon' => 'dashicons-upload', 'section' => 'manage' ),
			'tsm-reports'        => array( 'label' => __( 'Reports', 'tradeschool-manager' ), 'icon' => 'dashicons-chart-bar', 'section' => 'addons' ),
			'tsm-custom-fields'  => array( 'label' => __( 'Custom Fields & Docs', 'tradeschool-manager' ), 'icon' => 'dashicons-forms', 'section' => 'addons' ),
			'tsm-invoicing'      => array( 'label' => __( 'Invoicing', 'tradeschool-manager' ), 'icon' => 'dashicons-media-spreadsheet', 'section' => 'addons' ),
			'tsm-tuition-audit'  => array( 'label' => __( 'Tuition Audit', 'tradeschool-manager' ), 'icon' => 'dashicons-money-alt', 'section' => 'addons' ),
			'tsm-workshops'      => array( 'label' => __( 'Workshops', 'tradeschool-manager' ), 'icon' => 'dashicons-tickets-alt', 'section' => 'addons' ),
			'tsm-certificates'   => array( 'label' => __( 'Certificates', 'tradeschool-manager' ), 'icon' => 'dashicons-awards', 'section' => 'addons' ),
			'tsm-applications'   => array( 'label' => __( 'Applications', 'tradeschool-manager' ), 'icon' => 'dashicons-media-document', 'section' => 'addons' ),
			'tsm-settings'       => array( 'label' => __( 'Settings', 'tradeschool-manager' ), 'icon' => 'dashicons-admin-generic', 'section' => 'system' ),
			'tsm-shortcodes'     => array( 'label' => __( 'Shortcodes', 'tradeschool-manager' ), 'icon' => 'dashicons-shortcode', 'section' => 'system' ),
			'tsm-docs'           => array( 'label' => __( 'Documentation', 'tradeschool-manager' ), 'icon' => 'dashicons-book', 'section' => 'system' ),
			'tsm-about'          => array( 'label' => __( 'About', 'tradeschool-manager' ), 'icon' => 'dashicons-info', 'section' => 'system' ),
		);
		$known_order = array(
			'manage' => array( 'tsm-dashboard', 'tsm-students', 'tsm-parents', 'tsm-teachers', 'tsm-programs', 'tsm-classes', 'tsm-assignments', 'tsm-gradebook', 'tsm-attendance', 'tsm-school-years', 'tsm-notes', 'tsm-announcements', 'tsm-email-students', 'tsm-brevo', 'tsm-csv' ),
			'addons' => array( 'tsm-reports', 'tsm-custom-fields', 'tsm-invoicing', 'tsm-tuition-audit', 'tsm-workshops', 'tsm-certificates', 'tsm-applications' ),
			'system' => array( 'tsm-settings', 'tsm-shortcodes', 'tsm-docs', 'tsm-about' ),
		);
		$by_slug = array();
		foreach ( (array) $items as $item ) {
			if ( empty( $item['slug'] ) ) {
				continue;
			}
			$slug = sanitize_key( $item['slug'] );
			if ( isset( $hidden_sidebar_slugs[ $slug ] ) ) {
				continue;
			}
			$by_slug[ $slug ] = array_merge( isset( $defaults[ $slug ] ) ? $defaults[ $slug ] : array(), $item, array( 'slug' => $slug ) );
		}
		$ordered = array();
		foreach ( $known_order as $section => $slugs ) {
			foreach ( $slugs as $slug ) {
				if ( isset( $by_slug[ $slug ] ) ) {
					$ordered[] = array_merge( $by_slug[ $slug ], array( 'section' => $section ) );
					unset( $by_slug[ $slug ] );
				}
			}
		}
		foreach ( $by_slug as $item ) {
			$section = isset( $item['section'] ) ? sanitize_key( $item['section'] ) : 'addons';
			$ordered[] = array_merge( $item, array( 'section' => in_array( $section, array( 'manage', 'addons', 'system' ), true ) ? $section : 'addons' ) );
		}
		return $ordered;
	}

	private function has_nav_section( $items, $section ) {
		foreach ( (array) $items as $item ) {
			if ( isset( $item['section'] ) && $section === $item['section'] ) {
				return true;
			}
		}
		return false;
	}

	private function render_sidebar() {
		$current = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : 'tsm-dashboard'; // phpcs:ignore
		$items   = $this->nav_items();
		?>
		<aside class="tsm-sidebar" role="navigation" aria-label="TradeSchool Manager">
			<div class="tsm-brand">
				<svg class="tsm-brand-mark" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
					<path d="M32 4 L60 16 V32 C60 48 46 58 32 60 C18 58 4 48 4 32 V16 Z" fill="#1f6feb"/>
					<path d="M32 8 L56 18 V32 C56 45 44 54 32 56 C20 54 8 45 8 32 V18 Z" fill="#0b2545"/>
					<path d="M14 24 L32 17 L50 24 L32 31 Z" fill="#fff"/>
					<path d="M32 31 V41" stroke="#fff" stroke-width="2.4" stroke-linecap="round"/>
					<circle cx="32" cy="44" r="3.2" fill="#fff"/>
				</svg>
				<span class="tsm-brand-text">
					<span class="tsm-brand-1">TradeSchool</span>
					<span class="tsm-brand-2">MANAGER</span>
				</span>
			</div>
			<nav class="tsm-nav">
				<div class="tsm-nav-section"><?php esc_html_e( 'Manage', 'tradeschool-manager' ); ?></div>
				<?php
				foreach ( $items as $item ) {
					if ( isset( $item['section'] ) && 'manage' !== $item['section'] ) {
						continue;
					}
					$active   = ( $item['slug'] === $current ) ? ' is-active' : '';
					$pro_html = ! empty( $item['pro'] ) ? '<span class="tsm-pro-badge">' . esc_html__( 'Pro', 'tradeschool-manager' ) . '</span>' : '';
					printf(
						'<a class="%1$s" href="%2$s"><span class="dashicons %3$s"></span><span>%4$s</span>%5$s</a>',
						esc_attr( 'tsm-nav-item' . $active ),
						esc_url( admin_url( 'admin.php?page=' . $item['slug'] ) ),
						esc_attr( $item['icon'] ),
						esc_html( $item['label'] ),
						$pro_html // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
					);
				}
				?>
				<?php if ( $this->has_nav_section( $items, 'addons' ) ) : ?>
				<div class="tsm-nav-section"><?php esc_html_e( 'Add-ons', 'tradeschool-manager' ); ?></div>
				<?php
				foreach ( $items as $item ) {
					if ( ! isset( $item['section'] ) || 'addons' !== $item['section'] ) {
						continue;
					}
					$active   = ( $item['slug'] === $current ) ? ' is-active' : '';
					$pro_html = ! empty( $item['pro'] ) ? '<span class="tsm-pro-badge">' . esc_html__( 'Pro', 'tradeschool-manager' ) . '</span>' : '';
					printf(
						'<a class="%1$s" href="%2$s"><span class="dashicons %3$s"></span><span>%4$s</span>%5$s</a>',
						esc_attr( 'tsm-nav-item' . $active ),
						esc_url( admin_url( 'admin.php?page=' . $item['slug'] ) ),
						esc_attr( $item['icon'] ),
						esc_html( $item['label'] ),
						$pro_html // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
					);
				}
				?>
				<?php endif; ?>
				<div class="tsm-nav-section"><?php esc_html_e( 'System', 'tradeschool-manager' ); ?></div>
				<?php
				foreach ( $items as $item ) {
					if ( ! isset( $item['section'] ) || 'system' !== $item['section'] ) {
						continue;
					}
					$active = ( $item['slug'] === $current ) ? ' is-active' : '';
					printf(
						'<a class="%1$s" href="%2$s"><span class="dashicons %3$s"></span><span>%4$s</span></a>',
						esc_attr( 'tsm-nav-item' . $active ),
						esc_url( admin_url( 'admin.php?page=' . $item['slug'] ) ),
						esc_attr( $item['icon'] ),
						esc_html( $item['label'] )
					);
				}
				?>
			</nav>
			<div class="tsm-sidebar-foot">
				<strong style="color:#fff;display:block;margin-bottom:4px;letter-spacing:.3px;"><?php esc_html_e( 'TradeSchool Manager', 'tradeschool-manager' ); ?></strong>
				<span style="display:block;margin-bottom:6px;color:#93a4be;">v<?php echo esc_html( TSM_VERSION ); ?></span>
				<?php
				printf(
					/* translators: %s: linked author name */
					esc_html__( 'Built by %s', 'tradeschool-manager' ),
					'<a href="https://cjswebservice.com/" target="_blank" rel="noopener">CJs Web Service</a>'
				);
				?>
			</div>
		</aside>
		<?php
	}

	private function setup_progress( $counts = null ) {
		$s = TSM_Settings::get_all();
		global $wpdb;
		$p = $wpdb->prefix;

		if ( ! is_array( $counts ) ) {
			$counts = array(
				'students' => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$p}tsm_students" ), // phpcs:ignore
				'classes'  => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$p}tsm_classes" ), // phpcs:ignore
				'programs' => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$p}tsm_programs" ), // phpcs:ignore
			);
		}

		$school_configured = ! empty( $s['school_name'] ) && ! empty( $s['school_logo'] );
		$has_year         = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$p}tsm_school_years" ) > 0; // phpcs:ignore
		$has_teacher      = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$p}tsm_teachers" ) > 0; // phpcs:ignore
		$has_class        = ! empty( $counts['classes'] );
		$has_program      = ! empty( $counts['programs'] );
		$has_student      = ! empty( $counts['students'] );
		$has_assignment   = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$p}tsm_assignments" ) > 0; // phpcs:ignore
		$portal_pages_set = ! empty( $s['student_login_page'] ) && ! empty( $s['student_portal_page'] ) && ! empty( $s['teacher_portal_page'] );

		$steps = array(
			array( 'done' => $school_configured, 'label' => __( 'Configure your school name and logo', 'tradeschool-manager' ), 'url' => admin_url( 'admin.php?page=tsm-settings&tab=general' ) ),
			array( 'done' => $has_year,         'label' => __( 'Add a school year', 'tradeschool-manager' ),                 'url' => admin_url( 'admin.php?page=tsm-school-years' ) ),
			array( 'done' => $has_teacher,      'label' => __( 'Add teachers', 'tradeschool-manager' ),                      'url' => admin_url( 'admin.php?page=tsm-teachers' ) ),
			array( 'done' => $has_class,        'label' => __( 'Add a class', 'tradeschool-manager' ),                       'url' => admin_url( 'admin.php?page=tsm-classes' ) ),
			array( 'done' => $has_program,      'label' => __( 'Add a program', 'tradeschool-manager' ),                     'url' => admin_url( 'admin.php?page=tsm-programs' ) ),
			array( 'done' => $has_student,      'label' => __( 'Add students', 'tradeschool-manager' ),                      'url' => admin_url( 'admin.php?page=tsm-students' ) ),
			array( 'done' => $has_assignment,   'label' => __( 'Create an assignment', 'tradeschool-manager' ),              'url' => admin_url( 'admin.php?page=tsm-assignments' ) ),
			array( 'done' => $portal_pages_set, 'label' => __( 'Confirm portal pages in Settings', 'tradeschool-manager' ),  'url' => admin_url( 'admin.php?page=tsm-settings&tab=portal' ) ),
		);

		if ( class_exists( 'TSMP_Emails' ) ) {
			$emails_reviewed = ! empty( get_option( 'tsm_emails_reviewed' ) );
			$steps[] = array(
				'done'  => $emails_reviewed,
				'label' => __( 'Review your email templates', 'tradeschool-manager' ),
				'url'   => admin_url( 'admin.php?page=tsm-emails' ),
			);
		}

		$done_count = count( array_filter( $steps, function( $st ) { return ! empty( $st['done'] ); } ) );
		$total      = count( $steps );
		$next       = null;
		foreach ( $steps as $step ) {
			if ( empty( $step['done'] ) ) {
				$next = $step;
				break;
			}
		}

		return array(
			'steps'      => $steps,
			'done_count' => $done_count,
			'total'      => $total,
			'pct'        => (int) round( ( $done_count / max( 1, $total ) ) * 100 ),
			'all_done'   => ( $done_count === $total ),
			'next'       => $next,
		);
	}

	private function render_setup_banner() {
		if ( get_user_meta( get_current_user_id(), 'tsm_hide_setup_checklist', true ) ) {
			return;
		}
		$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore
		if ( 'tsm-about' === $page ) {
			return;
		}
		$progress = $this->setup_progress();
		if ( $progress['all_done'] || empty( $progress['next'] ) ) {
			return;
		}
		$about_url = admin_url( 'admin.php?page=tsm-about' );
		?>
		<div class="tsm-setup-banner">
			<div class="tsm-setup-banner-main">
				<span class="tsm-setup-banner-kicker"><?php esc_html_e( 'Setup checklist', 'tradeschool-manager' ); ?></span>
				<strong>
					<?php
					printf(
						/* translators: 1: completed setup steps, 2: total setup steps. */
						esc_html__( 'Step %1$d of %2$d:', 'tradeschool-manager' ),
						(int) $progress['done_count'] + 1,
						(int) $progress['total']
					);
					?>
				</strong>
				<span><?php echo esc_html( $progress['next']['label'] ); ?></span>
			</div>
			<div class="tsm-setup-banner-actions">
				<a class="button button-primary" href="<?php echo esc_url( $progress['next']['url'] ); ?>"><?php esc_html_e( 'Continue setup', 'tradeschool-manager' ); ?></a>
				<a class="button" href="<?php echo esc_url( $about_url ); ?>"><?php esc_html_e( 'View checklist', 'tradeschool-manager' ); ?></a>
			</div>
		</div>
		<?php
	}

	public function shell_open( $title, $with_panel = true ) {
		echo '<div class="wrap tsm-wrap"><div class="tsm-app">';
		$this->render_sidebar();
		echo '<div class="tsm-main">';
		echo '<header class="tsm-topbar"><h1>' . esc_html( $title ) . '</h1>';
		echo '<span class="tsm-topbar-meta">';
		if ( class_exists( 'TSM_Active_Year' ) ) {
			TSM_Active_Year::render_switcher( 'admin' );
			echo ' &nbsp; ';
		}
		echo esc_html( get_bloginfo( 'name' ) ) . '</span></header>';
		echo '<div class="tsm-content">';
		$this->render_setup_banner();
		if ( $with_panel ) { echo '<div class="tsm-panel">'; }
	}
	public function shell_close( $with_panel = true ) {
		if ( $with_panel ) { echo '</div>'; }
		echo '</div></div></div></div>';
	}

	/* ---------- Pages ---------- */

	public function page_dashboard() {
		$this->shell_open( __( 'Dashboard', 'tradeschool-manager' ), false );
		global $wpdb;
		$active_year = class_exists( 'TSM_Active_Year' ) ? (int) TSM_Active_Year::get() : 0;

		// Year-aware student count: students with at least one enrollment in a class in the active year.
		// Year=0  no filter (all years).
		if ( $active_year ) {
			$student_count = (int) $wpdb->get_var( $wpdb->prepare(
				"SELECT COUNT(DISTINCT e.student_id)
				   FROM {$wpdb->prefix}tsm_enrollments e
				   INNER JOIN {$wpdb->prefix}tsm_classes c ON c.id = e.class_id
				   INNER JOIN {$wpdb->prefix}tsm_students s ON s.id = e.student_id
				  WHERE c.school_year_id = %d AND ( s.status IS NULL OR s.status <> 'dropped' )",
				$active_year
			) ); // phpcs:ignore
			$class_count = (int) $wpdb->get_var( $wpdb->prepare(
				"SELECT COUNT(*) FROM {$wpdb->prefix}tsm_classes WHERE school_year_id = %d",
				$active_year
			) ); // phpcs:ignore
			$assignment_count = (int) $wpdb->get_var( $wpdb->prepare(
				"SELECT COUNT(*) FROM {$wpdb->prefix}tsm_assignments WHERE school_year_id = %d",
				$active_year
			) ); // phpcs:ignore
			$teacher_count = (int) $wpdb->get_var( $wpdb->prepare(
				"SELECT COUNT(DISTINCT teacher_id) FROM {$wpdb->prefix}tsm_classes WHERE school_year_id = %d AND teacher_id > 0",
				$active_year
			) ); // phpcs:ignore
		} else {
			$student_count    = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}tsm_students WHERE status IS NULL OR status <> 'dropped'" ); // phpcs:ignore
			$teacher_count    = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}tsm_teachers" ); // phpcs:ignore
			$class_count      = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}tsm_classes" ); // phpcs:ignore
			$assignment_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}tsm_assignments" ); // phpcs:ignore
		}
		$counts = array(
			'students'    => $student_count,
			'teachers'    => $teacher_count,
			'classes'     => $class_count,
			'assignments' => $assignment_count,
		);

		// Class averages  -  restrict to the active year's classes.
		// v1.18.10  -  fixed: previously queried g.score (column doesn't exist),
		// so the chart never rendered. Now computes pct from points_earned /
		// points_possible, ignoring excused / null grades and zero-point items.
		$avg_year_sql = $active_year ? $wpdb->prepare( ' WHERE c.school_year_id = %d', $active_year ) : ''; // phpcs:ignore
		$averages = $wpdb->get_results( // phpcs:ignore
			"SELECT c.name AS class_name,
			        AVG( ( GREATEST( g.points_earned - COALESCE(g.late_penalty,0), 0 ) / a.points_possible ) * 100 ) AS avg_score
			 FROM {$wpdb->prefix}tsm_classes c
			 LEFT JOIN {$wpdb->prefix}tsm_assignments a ON a.class_id = c.id AND a.points_possible > 0 AND a.extra_credit = 0
			 LEFT JOIN {$wpdb->prefix}tsm_grades g ON g.assignment_id = a.id AND g.excused = 0 AND g.points_earned IS NOT NULL
			 {$avg_year_sql}
			 GROUP BY c.id ORDER BY c.name ASC LIMIT 8"
		);
		$labels = array();
		$values = array();
		foreach ( (array) $averages as $row ) {
			if ( null === $row->avg_score ) { continue; }
			$labels[] = $row->class_name;
			$values[] = round( (float) $row->avg_score, 1 );
		}

		// Recent applications with program + enrollment/payment plan details (v1.10.33).
		$apps_table = $wpdb->prefix . 'tsm_application_submissions';
		$prog_table = $wpdb->prefix . 'tsm_programs';
		$stu_table  = $wpdb->prefix . 'tsm_students';
		$enr_table  = $wpdb->prefix . 'tsm_program_enrollments';
		$has_apps   = ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $apps_table ) ) === $apps_table ); // phpcs:ignore
		$recent_apps = array();
		if ( $has_apps ) {
			$recent_apps = $wpdb->get_results( // phpcs:ignore
				"SELECT s.id, s.applicant_name, s.applicant_email, s.status, s.submitted_at,
				        s.program_id, p.name AS program_name,
				        stu.id AS student_id, stu.first_name, stu.last_name,
				        e.id AS enrollment_id, e.tuition_amount, e.installments, e.frequency, e.deposit, e.first_payment_date
				 FROM {$apps_table} s
				 LEFT JOIN {$prog_table} p ON p.id = s.program_id
				 LEFT JOIN {$stu_table} stu ON stu.email = s.applicant_email
				 LEFT JOIN {$enr_table} e ON e.student_id = stu.id AND e.program_id = s.program_id
				 ORDER BY s.submitted_at DESC LIMIT 10"
			);
		}
		?>
		<div class="tsm-cards">
			<div class="tsm-card">
				<span class="tsm-card-icon"><span class="dashicons dashicons-groups"></span></span>
				<span class="tsm-card-label"><?php esc_html_e( 'Students', 'tradeschool-manager' ); ?></span>
				<span class="tsm-card-num"><?php echo esc_html( $counts['students'] ); ?></span>
				<span class="tsm-card-sub"><?php esc_html_e( 'Active Students', 'tradeschool-manager' ); ?></span>
			</div>
			<div class="tsm-card">
				<span class="tsm-card-icon"><span class="dashicons dashicons-welcome-learn-more"></span></span>
				<span class="tsm-card-label"><?php esc_html_e( 'Classes', 'tradeschool-manager' ); ?></span>
				<span class="tsm-card-num"><?php echo esc_html( $counts['classes'] ); ?></span>
				<span class="tsm-card-sub"><?php esc_html_e( 'Active Classes', 'tradeschool-manager' ); ?></span>
			</div>
			<div class="tsm-card">
				<span class="tsm-card-icon"><span class="dashicons dashicons-clipboard"></span></span>
				<span class="tsm-card-label"><?php esc_html_e( 'Assignments', 'tradeschool-manager' ); ?></span>
				<span class="tsm-card-num"><?php echo esc_html( $counts['assignments'] ); ?></span>
				<span class="tsm-card-sub"><?php esc_html_e( 'All-time', 'tradeschool-manager' ); ?></span>
			</div>
			<div class="tsm-card">
				<span class="tsm-card-icon"><span class="dashicons dashicons-businessperson"></span></span>
				<span class="tsm-card-label"><?php esc_html_e( 'Teachers', 'tradeschool-manager' ); ?></span>
				<span class="tsm-card-num"><?php echo esc_html( $counts['teachers'] ); ?></span>
				<span class="tsm-card-sub"><?php esc_html_e( 'On staff', 'tradeschool-manager' ); ?></span>
			</div>
		</div>

		<div class="tsm-panel">
			<div class="tsm-panel-h"><h2><?php esc_html_e( 'Class Averages', 'tradeschool-manager' ); ?></h2></div>
			<div class="tsm-chart-wrap"><canvas id="tsm-chart-averages"></canvas></div>
		</div>

		<?php if ( function_exists( 'tsm_addon_active' ) && tsm_addon_active( 'applications' ) ) : ?>
		<div class="tsm-panel">
			<div class="tsm-panel-h tsm-panel-h-row">
				<h2><?php esc_html_e( 'Recent Student Applications', 'tradeschool-manager' ); ?></h2>
				<a class="button" href="<?php echo esc_url( admin_url( 'admin.php?page=tsm-applications' ) ); ?>"><?php esc_html_e( 'View all', 'tradeschool-manager' ); ?></a>
			</div>
			<table class="tsm-table">
				<thead><tr>
					<th><?php esc_html_e( 'Applicant', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'Program', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'App Status', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'Payment Plan', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'Submitted', 'tradeschool-manager' ); ?></th>
					<th></th>
				</tr></thead>
				<tbody>
				<?php if ( empty( $recent_apps ) ) : ?>
					<tr><td colspan="6" class="tsm-empty"><?php esc_html_e( 'No applications yet. Public applications will appear here once submitted.', 'tradeschool-manager' ); ?></td></tr>
				<?php else :
					$cs2 = method_exists( 'TSM_Settings', 'currency_symbol' ) ? TSM_Settings::currency_symbol() : '$';
					$status_labels = array(
						'new'      => __( 'New', 'tradeschool-manager' ),
						'review'   => __( 'In Review', 'tradeschool-manager' ),
						'approved' => __( 'Approved', 'tradeschool-manager' ),
						'denied'   => __( 'Denied', 'tradeschool-manager' ),
					);
					$status_classes = array( 'new' => 'tsm-status-new', 'review' => 'tsm-status-review', 'approved' => 'tsm-status-approved', 'denied' => 'tsm-status-denied' );
					foreach ( $recent_apps as $a ) :
						$st_label = isset( $status_labels[ $a->status ] ) ? $status_labels[ $a->status ] : ucfirst( $a->status );
						$st_class = isset( $status_classes[ $a->status ] ) ? $status_classes[ $a->status ] : 'tsm-status-default';
						if ( $a->enrollment_id ) {
							$plan = $cs2 . number_format( (float) $a->tuition_amount, 2 );
							if ( (int) $a->installments > 1 ) {
								/* translators: placeholder(s) for dynamic value(s). */
								$plan .= ' &middot; ' . sprintf( esc_html__( '%1$d x %2$s', 'tradeschool-manager' ), (int) $a->installments, esc_html( $a->frequency ) );
							}
							if ( (float) $a->deposit > 0 ) {
								$plan .= ' &middot; ' . esc_html__( 'Deposit', 'tradeschool-manager' ) . ' ' . $cs2 . number_format( (float) $a->deposit, 2 );
							}
						} else {
							$plan = '<span class="tsm-muted">' . esc_html__( 'Not enrolled', 'tradeschool-manager' ) . '</span>';
						}
						$applicant = trim( $a->first_name . ' ' . $a->last_name );
						if ( '' === $applicant ) { $applicant = $a->applicant_name; }
						$view_url = admin_url( 'admin.php?page=tsm-applications&action=view&id=' . (int) $a->id );
						?>
						<tr>
							<td>
								<strong><?php echo esc_html( $applicant ); ?></strong><br>
								<small class="tsm-muted"><?php echo esc_html( $a->applicant_email ); ?></small>
							</td>
							<td><?php echo esc_html( $a->program_name ?: ' - ' ); ?></td>
							<td><span class="tsm-status-pill <?php echo esc_attr( $st_class ); ?>"><?php echo esc_html( $st_label ); ?></span></td>
							<td><?php echo wp_kses_post( $plan ); ?></td>
							<td><?php echo esc_html( $a->submitted_at ? mysql2date( 'M j, Y', $a->submitted_at ) : ' - ' ); ?></td>
							<td><a class="button button-small" href="<?php echo esc_url( $view_url ); ?>"><?php esc_html_e( 'Review', 'tradeschool-manager' ); ?></a></td>
						</tr>
					<?php endforeach; endif; ?>
				</tbody>
			</table>
		</div>
		<?php endif; ?>


		<?php
		// --- Invoicing snapshot (v1.6.1) ---
		if ( class_exists( 'TSMP_Invoices' ) ) :
			$ag      = TSMP_Invoices::dashboard_aging();
			$months  = TSMP_Invoices::dashboard_monthly_summary( 12 );
			$cs      = method_exists( 'TSM_Settings', 'currency_symbol' ) ? TSM_Settings::currency_symbol() : '$';
			$inv_url = admin_url( 'admin.php?page=tsm-invoices' );
			?>
			<div class="tsm-cards tsmp-aging-cards">
				<a class="tsm-card tsmp-aging tsmp-aging-pastdue" href="<?php echo esc_url( add_query_arg( 'status', 'past_due', $inv_url ) ); ?>">
					<span class="tsm-card-icon"><span class="dashicons dashicons-warning"></span></span>
					<span class="tsm-card-label"><?php esc_html_e( 'Past Due', 'tradeschool-manager' ); ?></span>
					<span class="tsm-card-num"><?php echo esc_html( $cs . number_format( $ag['past_due_amt'], 2 ) ); ?></span>
					<span class="tsm-card-sub"><?php echo esc_html( sprintf( '%d invoice(s)', $ag['past_due_count'] ) ); ?></span>
				</a>
				<a class="tsm-card tsmp-aging tsmp-aging-soon" href="<?php echo esc_url( add_query_arg( 'status', 'open', $inv_url ) ); ?>">
					<span class="tsm-card-icon"><span class="dashicons dashicons-calendar-alt"></span></span>
					<span class="tsm-card-label"><?php esc_html_e( 'Due Within 30 Days', 'tradeschool-manager' ); ?></span>
					<span class="tsm-card-num"><?php echo esc_html( $cs . number_format( $ag['soon_amt'], 2 ) ); ?></span>
					<span class="tsm-card-sub"><?php echo esc_html( sprintf( '%d invoice(s)', $ag['soon_count'] ) ); ?></span>
				</a>
				<a class="tsm-card tsmp-aging tsmp-aging-open" href="<?php echo esc_url( add_query_arg( 'status', 'open', $inv_url ) ); ?>">
					<span class="tsm-card-icon"><span class="dashicons dashicons-media-spreadsheet"></span></span>
					<span class="tsm-card-label"><?php esc_html_e( 'Total Outstanding', 'tradeschool-manager' ); ?></span>
					<span class="tsm-card-num"><?php echo esc_html( $cs . number_format( $ag['open_amt'], 2 ) ); ?></span>
					<span class="tsm-card-sub"><?php echo esc_html( sprintf( '%d open invoice(s)', $ag['open_count'] ) ); ?></span>
				</a>
				<div class="tsm-card tsmp-aging tsmp-aging-collected">
					<span class="tsm-card-icon"><span class="dashicons dashicons-money-alt"></span></span>
					<span class="tsm-card-label"><?php esc_html_e( 'Collected This Month', 'tradeschool-manager' ); ?></span>
					<span class="tsm-card-num"><?php echo esc_html( $cs . number_format( $ag['collected_mtd'], 2 ) ); ?></span>
					<span class="tsm-card-sub"><?php echo esc_html( date_i18n( 'F Y' ) ); ?></span>
				</div>
			</div>

			<div class="tsm-panel">
				<div class="tsm-panel-h"><h2><?php esc_html_e( 'Invoicing  -  Last 12 Months', 'tradeschool-manager' ); ?></h2></div>
				<table class="tsm-table tsmp-monthly">
					<thead>
						<tr>
							<th><?php esc_html_e( 'Month', 'tradeschool-manager' ); ?></th>
							<th class="num"><?php esc_html_e( 'Invoices Issued', 'tradeschool-manager' ); ?></th>
							<th class="num"><?php esc_html_e( 'Invoiced Total', 'tradeschool-manager' ); ?></th>
							<th class="num"><?php esc_html_e( 'Collected', 'tradeschool-manager' ); ?></th>
							<th class="num"><?php esc_html_e( 'Past Due (Open)', 'tradeschool-manager' ); ?></th>
							<th class="num"><?php esc_html_e( 'Net (Collected - Invoiced)', 'tradeschool-manager' ); ?></th>
						</tr>
					</thead>
					<tbody>
					<?php foreach ( $months as $m ) :
						$net = $m['collected'] - $m['invoiced_total']; ?>
						<tr>
							<td><strong><?php echo esc_html( $m['label'] ); ?></strong></td>
							<td class="num"><?php echo esc_html( $m['invoiced_count'] ); ?></td>
							<td class="num"><?php echo esc_html( $cs . number_format( $m['invoiced_total'], 2 ) ); ?></td>
							<td class="num positive"><?php echo esc_html( $cs . number_format( $m['collected'], 2 ) ); ?></td>
							<td class="num <?php echo $m['past_due_total'] > 0 ? 'negative' : ''; ?>">
								<?php echo esc_html( $cs . number_format( $m['past_due_total'], 2 ) ); ?>
								<?php if ( $m['past_due_count'] ) : ?><small>(<?php echo (int) $m['past_due_count']; ?>)</small><?php endif; ?>
							</td>
							<td class="num <?php echo $net >= 0 ? 'positive' : 'negative'; ?>"><?php echo esc_html( $cs . number_format( $net, 2 ) ); ?></td>
						</tr>
					<?php endforeach; ?>
					</tbody>
				</table>
			</div>

			<?php
			TSM_Inline_Assets::style(
				'tsm-dashboard-aging',
				'.tsmp-aging-cards .tsm-card{transition:transform .15s ease,box-shadow .15s ease}'
				. '.tsmp-aging-cards a.tsm-card:hover{transform:translateY(-2px);box-shadow:0 6px 18px rgba(0,0,0,.08)}'
				. '.tsmp-aging-pastdue{border-left:4px solid #b00020}'
				. '.tsmp-aging-soon{border-left:4px solid #d97706}'
				. '.tsmp-aging-open{border-left:4px solid #1f6feb}'
				. '.tsmp-aging-collected{border-left:4px solid #0a7d2c}'
				. '.tsmp-monthly th.num,.tsmp-monthly td.num{text-align:right;font-variant-numeric:tabular-nums}'
				. '.tsmp-monthly td.positive{color:#0a7d2c}'
				. '.tsmp-monthly td.negative{color:#b00020}'
				. '.tsmp-monthly td small{color:#777;margin-left:4px}'
			);
			?>
		<?php endif; ?>

		<?php /* Pro promo removed in v1.6.4  -  All-In-One build has every feature unlocked. */ ?>

		<div class="tsm-trustbar">
			<div><span class="dashicons dashicons-shield"></span> <?php esc_html_e( 'Secure', 'tradeschool-manager' ); ?></div>
			<div><span class="dashicons dashicons-desktop"></span> <?php esc_html_e( 'Easy to Use', 'tradeschool-manager' ); ?></div>
			<div><span class="dashicons dashicons-sos"></span> <?php esc_html_e( 'Dedicated Support', 'tradeschool-manager' ); ?></div>
			<div><span class="dashicons dashicons-chart-line"></span> <?php esc_html_e( 'Built for Trade Schools', 'tradeschool-manager' ); ?></div>
		</div>

		<?php
		$empty_message = __( 'No grade data yet  -  averages will appear once grades are recorded.', 'tradeschool-manager' );
		TSM_Inline_Assets::script(
			'tsm-dashboard-chart',
			"(function(){\n"
			. "\tvar labels = " . wp_json_encode( array_values( $labels ) ) . ";\n"
			. "\tvar values = " . wp_json_encode( array_values( $values ) ) . ";\n"
			. "\tvar emptyMessage = " . wp_json_encode( $empty_message ) . ";\n"
			. "\tdocument.addEventListener('DOMContentLoaded', function(){\n"
			. "\t\tif (!window.Chart) { return; }\n"
			. "\t\tvar el = document.getElementById('tsm-chart-averages');\n"
			. "\t\tif (!el) { return; }\n"
			. "\t\tif (!labels.length) {\n"
			. "\t\t\tvar p = document.createElement('p');\n"
			. "\t\t\tp.style.cssText = 'color:#667085;margin:0;padding:24px 0;text-align:center;';\n"
			. "\t\t\tp.textContent = emptyMessage;\n"
			. "\t\t\tel.parentNode.replaceChildren(p);\n"
			. "\t\t\treturn;\n"
			. "\t\t}\n"
			. "\t\tnew Chart(el.getContext('2d'), {\n"
			. "\t\t\ttype: 'line',\n"
			. "\t\t\tdata: { labels: labels, datasets: [{ label: " . wp_json_encode( __( 'Average', 'tradeschool-manager' ) ) . ", data: values, borderColor: '#1f6feb', backgroundColor: 'rgba(31,111,235,0.10)', borderWidth: 2.5, tension: 0.35, fill: true, pointRadius: 4, pointBackgroundColor: '#1f6feb' }] },\n"
			. "\t\t\toptions: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true, max: 100, ticks: { callback: function(v){ return v + '%'; } } }, x: { grid: { display: false } } } }\n"
			. "\t\t});\n"
			. "\t});\n"
			. "})();\n"
		);
		$this->shell_close( false );
	}

	/**
	 * Render a horizontal sub-tab nav for an admin page.
	 *
	 * @param string $page_slug  The page slug (e.g. 'tsm-students').
	 * @param array  $tabs       [ slug => label ].
	 * @param string $current    Current tab slug.
	 */
	private function render_tabs( $page_slug, $tabs, $current, $disabled = array() ) {
		echo '<h2 class="nav-tab-wrapper tsm-subtabs">';
		$disabled = array_flip( (array) $disabled );
		foreach ( $tabs as $slug => $label ) {
			if ( isset( $disabled[ $slug ] ) ) {
				echo '<span class="nav-tab nav-tab-disabled" aria-disabled="true" title="' . esc_attr__( 'Locked while viewing an archived school year. Switch to a current year in the topbar to make changes.', 'tradeschool-manager' ) . '" style="opacity:.45;cursor:not-allowed;pointer-events:none;">' . esc_html( $label ) . ' (Locked)</span>';
				continue;
			}
			$url   = esc_url( add_query_arg( array( 'page' => $page_slug, 'tab' => $slug ), admin_url( 'admin.php' ) ) );
			$class = 'nav-tab' . ( $current === $slug ? ' nav-tab-active' : '' );
			echo '<a class="' . esc_attr( $class ) . '" href="' . esc_url( $url ) . '">' . esc_html( $label ) . '</a>';
		}
		echo '</h2>';
	}

	/**
	 * Inline notice rendered above any "create / edit" surface when the active
	 * school year is archived. Pairs with render_tabs() disabled tabs so the
	 * intent is unmistakable in the UI.
	 */
	private function render_archived_notice( $what = '' ) {
		$msg = $what
			? sprintf( /* translators: %s: entity, e.g. students. */ __( 'The active school year is archived  -  creating or editing %s is locked. Switch to a current year in the topbar to make changes.', 'tradeschool-manager' ), $what )
			: __( 'The active school year is archived  -  changes are locked. Switch to a current year in the topbar to make changes.', 'tradeschool-manager' );
		echo '<div class="notice notice-warning inline" style="margin:6px 0 14px;"><p>Locked: ' . esc_html( $msg ) . '</p></div>';
	}

	public function page_about() {
		// Hide-checklist toggle.
		if ( isset( $_GET['tsm_hide_checklist'] ) && check_admin_referer( 'tsm_hide_checklist' ) ) {
			update_user_meta( get_current_user_id(), 'tsm_hide_setup_checklist', 1 );
			wp_safe_redirect( remove_query_arg( array( 'tsm_hide_checklist', '_wpnonce' ) ) );
			exit;
		}
		if ( isset( $_GET['tsm_show_checklist'] ) && check_admin_referer( 'tsm_show_checklist' ) ) {
			delete_user_meta( get_current_user_id(), 'tsm_hide_setup_checklist' );
			wp_safe_redirect( remove_query_arg( array( 'tsm_show_checklist', '_wpnonce' ) ) );
			exit;
		}
		if ( isset( $_GET['tsm_mark_emails_reviewed'] ) && check_admin_referer( 'tsm_mark_emails_reviewed' ) ) {
			update_option( 'tsm_emails_reviewed', 1, false );
			wp_safe_redirect( remove_query_arg( array( 'tsm_mark_emails_reviewed', '_wpnonce' ) ) );
			exit;
		}


		$this->shell_open( __( 'About TradeSchool Manager', 'tradeschool-manager' ), false );
		global $wpdb;
		$p = $wpdb->prefix;
		$counts = array(
			'students' => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$p}tsm_students" ), // phpcs:ignore
			'teachers' => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$p}tsm_teachers" ), // phpcs:ignore
			'classes'  => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$p}tsm_classes" ),  // phpcs:ignore
			'programs' => (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$p}tsm_programs" ), // phpcs:ignore
		);
		$db_ver = get_option( 'tsm_db_version', ' - ' );
		$site   = get_bloginfo( 'name' );
		?>
		<div class="tsm-about">
			<div class="tsm-panel tsm-about-hero">
				<div class="tsm-about-hero-text">
					<h2><?php /* translators: %s: plugin version */ echo esc_html( sprintf( __( 'TradeSchool Manager %s', 'tradeschool-manager' ), TSM_VERSION ) ); ?></h2>
					<p class="tsm-about-tag"><?php esc_html_e( 'A purpose-built student-information and gradebook plugin for trade schools, vocational programs, and hands-on training.', 'tradeschool-manager' ); ?></p>
					<p>
						<a class="button button-primary" href="<?php echo esc_url( admin_url( 'admin.php?page=tsm-docs' ) ); ?>"><?php esc_html_e( 'Documentation', 'tradeschool-manager' ); ?></a>
						<a class="button" href="https://tradeschoolmanager.com/support/" target="_blank" rel="noopener"><?php esc_html_e( 'Get Support', 'tradeschool-manager' ); ?></a>
						<a class="button" href="<?php echo esc_url( admin_url( 'admin.php?page=tsm-shortcodes' ) ); ?>"><?php esc_html_e( 'Shortcodes', 'tradeschool-manager' ); ?></a>
						<a class="button" href="<?php echo esc_url( admin_url( 'admin.php?page=tsm-settings' ) ); ?>"><?php esc_html_e( 'Open Settings', 'tradeschool-manager' ); ?></a>
					</p>
				</div>
				<div class="tsm-about-hero-meta">
					<div><strong><?php esc_html_e( 'Plugin Version', 'tradeschool-manager' ); ?></strong><span><?php echo esc_html( TSM_VERSION ); ?></span></div>
					<div><strong><?php esc_html_e( 'Database Version', 'tradeschool-manager' ); ?></strong><span><?php echo esc_html( $db_ver ); ?></span></div>
					<div><strong><?php esc_html_e( 'WordPress', 'tradeschool-manager' ); ?></strong><span><?php echo esc_html( get_bloginfo( 'version' ) ); ?></span></div>
					<div><strong><?php esc_html_e( 'PHP', 'tradeschool-manager' ); ?></strong><span><?php echo esc_html( PHP_VERSION ); ?></span></div>
					<div><strong><?php esc_html_e( 'Site', 'tradeschool-manager' ); ?></strong><span><?php echo esc_html( $site ); ?></span></div>
				</div>
			</div>

			<div class="tsm-cards">
				<div class="tsm-card"><span class="tsm-card-label"><?php esc_html_e( 'Students', 'tradeschool-manager' ); ?></span><span class="tsm-card-num"><?php echo esc_html( $counts['students'] ); ?></span></div>
				<div class="tsm-card"><span class="tsm-card-label"><?php esc_html_e( 'Teachers', 'tradeschool-manager' ); ?></span><span class="tsm-card-num"><?php echo esc_html( $counts['teachers'] ); ?></span></div>
				<div class="tsm-card"><span class="tsm-card-label"><?php esc_html_e( 'Classes', 'tradeschool-manager' ); ?></span><span class="tsm-card-num"><?php echo esc_html( $counts['classes'] ); ?></span></div>
				<div class="tsm-card"><span class="tsm-card-label"><?php esc_html_e( 'Programs', 'tradeschool-manager' ); ?></span><span class="tsm-card-num"><?php echo esc_html( $counts['programs'] ); ?></span></div>
			</div>

			<?php $this->render_setup_checklist( $counts ); ?>

			<div class="tsm-about-grid">
				<div class="tsm-panel">
					<div class="tsm-panel-h"><h2><?php esc_html_e( "What's Included (Free Core)", 'tradeschool-manager' ); ?></h2></div>
					<div class="tsm-panel-body">
						<ul class="tsm-feature-list">
							<li><strong><?php esc_html_e( 'Students, Teachers & Sponsors', 'tradeschool-manager' ); ?></strong>  -  <?php esc_html_e( 'Profiles with photos, emergency contacts, sponsor contacts, and portal accounts.', 'tradeschool-manager' ); ?></li>
							<li><strong><?php esc_html_e( 'Programs & School Years', 'tradeschool-manager' ); ?></strong>  -  <?php esc_html_e( 'Multi-year programs with enrollment, GPA, year rollover, and historical preservation.', 'tradeschool-manager' ); ?></li>
							<li><strong><?php esc_html_e( 'Classes & Gradebook', 'tradeschool-manager' ); ?></strong>  -  <?php esc_html_e( 'Points, weighted, or pass/fail grading with assignment categories and extra credit.', 'tradeschool-manager' ); ?></li>
							<li><strong><?php esc_html_e( 'Attendance', 'tradeschool-manager' ); ?></strong>  -  <?php esc_html_e( 'Daily attendance tracking with per-class and per-student reports.', 'tradeschool-manager' ); ?></li>
							<li><strong><?php esc_html_e( 'Announcements', 'tradeschool-manager' ); ?></strong>  -  <?php esc_html_e( 'School-wide or class-specific announcements visible in the portals.', 'tradeschool-manager' ); ?></li>
							<li><strong><?php esc_html_e( 'Student / Teacher / Sponsor Portals', 'tradeschool-manager' ); ?></strong>  -  <?php esc_html_e( 'Front-end shortcodes you drop onto any WordPress page.', 'tradeschool-manager' ); ?></li>
							<li><strong><?php esc_html_e( 'CSV Import / Export', 'tradeschool-manager' ); ?></strong>  -  <?php esc_html_e( 'Move students, classes, and assignments without lock-in.', 'tradeschool-manager' ); ?></li>
							<li><strong><?php esc_html_e( 'Branded PDF Report Cards & Rosters', 'tradeschool-manager' ); ?></strong>  -  <?php esc_html_e( 'Unified PDF engine renders documents on your school letterhead.', 'tradeschool-manager' ); ?></li>
							<li><strong><?php esc_html_e( 'Active School Year switcher', 'tradeschool-manager' ); ?></strong>  -  <?php esc_html_e( 'One global control scopes every list, dashboard, and gradebook.', 'tradeschool-manager' ); ?></li>
							<li><strong><?php esc_html_e( 'Brevo Email Delivery (optional)', 'tradeschool-manager' ); ?></strong>  -  <?php esc_html_e( '300 transactional emails/day on the free plan, with automatic fallback to wp_mail().', 'tradeschool-manager' ); ?></li>
						</ul>
					</div>
				</div>

				<div class="tsm-panel">
					<div class="tsm-panel-h"><h2><?php esc_html_e( 'Optional Add-Ons', 'tradeschool-manager' ); ?></h2></div>
					<div class="tsm-panel-body">
						<p class="description"><?php esc_html_e( 'The free core is fully functional on its own. These capabilities ship as separate add-on plugins.', 'tradeschool-manager' ); ?></p>
						<ul class="tsm-feature-list">
							<?php
							$addon_rows = array(
								'certificates'        => __( 'Auto-issued PDF certificates with reminder emails.', 'tradeschool-manager' ),
								'reports_transcripts' => __( 'Multi-year transcripts, summary report cards, GPA, credits.', 'tradeschool-manager' ),
								'workshops'           => __( 'Short-form, year-agnostic courses with their own gradebook and certificates.', 'tradeschool-manager' ),
								'applications'       => __( 'Public application forms and an admissions pipeline.', 'tradeschool-manager' ),
								'invoicing'           => __( 'Tuition invoicing, payment plans, e-sign contracts, optional Stripe checkout.', 'tradeschool-manager' ),
								'custom_fields'       => __( 'Per-program metadata plus audit-logged document uploads.', 'tradeschool-manager' ),
							);
							$reg = class_exists( 'TSM_Addons' ) ? TSM_Addons::registry() : array();
							foreach ( $addon_rows as $slug => $desc ) :
								$label  = isset( $reg[ $slug ]['name'] ) ? $reg[ $slug ]['name'] : ucfirst( $slug );
								$active = function_exists( 'tsm_addon_active' ) ? tsm_addon_active( $slug ) : false;
								?>
								<li>
									<strong><?php echo esc_html( $label ); ?></strong>  -  <?php echo esc_html( $desc ); ?>
									<?php if ( $active ) : ?>
										<span class="tsm-pill" style="background:#d3f9d8;color:#2b8a3e;padding:1px 8px;border-radius:10px;font-size:11px;margin-left:6px;"><?php esc_html_e( 'Active', 'tradeschool-manager' ); ?></span>
									<?php else : ?>
										<span class="tsm-pill" style="background:#fff3bf;color:#7a5f00;padding:1px 8px;border-radius:10px;font-size:11px;margin-left:6px;"><?php esc_html_e( 'Not installed', 'tradeschool-manager' ); ?></span>
									<?php endif; ?>
								</li>
							<?php endforeach; ?>
						</ul>
						<p><a class="button" href="<?php echo esc_url( admin_url( 'admin.php?page=tsm-settings&tab=addons' ) ); ?>"><?php esc_html_e( 'Browse Add-Ons', 'tradeschool-manager' ); ?></a> <a class="button" href="https://tradeschoolmanager.com/add-ons/" target="_blank" rel="noopener"><?php esc_html_e( 'Compare on tradeschoolmanager.com', 'tradeschool-manager' ); ?></a></p>
					</div>
				</div>
			</div>

			<div class="tsm-panel">
				<div class="tsm-panel-h"><h2><?php esc_html_e( 'Credits & License', 'tradeschool-manager' ); ?></h2></div>
				<div class="tsm-panel-body">
					<p><?php echo wp_kses_post( __( 'Built and maintained by <a href="https://cjswebservice.com/" target="_blank" rel="noopener">CJs Web Service</a>. Product home: <a href="https://tradeschoolmanager.com/" target="_blank" rel="noopener">tradeschoolmanager.com</a>. Released under GPL-2.0-or-later.', 'tradeschool-manager' ) ); ?></p>
					<p class="description"><?php esc_html_e( 'Bundled libraries: TCPDF + FPDI for PDF generation.', 'tradeschool-manager' ); ?></p>
				</div>
			</div>
		</div>
		<?php
		$this->shell_close( false );
	}

	/**
	 * Live setup checklist  -  auto-marks complete based on real data.
	 */
	private function render_setup_checklist( $counts ) {
		if ( get_user_meta( get_current_user_id(), 'tsm_hide_setup_checklist', true ) ) {
			$show_url = wp_nonce_url( add_query_arg( 'tsm_show_checklist', '1' ), 'tsm_show_checklist' );
			echo '<p class="description" style="margin:12px 0;"><a href="' . esc_url( $show_url ) . '">' . esc_html__( 'Show setup checklist', 'tradeschool-manager' ) . '</a></p>';
			return;
		}
		$progress   = $this->setup_progress( $counts );
		$steps      = $progress['steps'];
		$done_count = $progress['done_count'];
		$total      = $progress['total'];
		$pct        = $progress['pct'];
		$all_done   = $progress['all_done'];
		// Email Templates is the final step. We don't try to detect whether the
		// admin _read_ the templates  -  we just expose the dismiss link so
		// they can mark it done themselves once they've reviewed/branded them.
		$hide_url = wp_nonce_url( add_query_arg( 'tsm_hide_checklist', '1' ), 'tsm_hide_checklist' );
		?>
		<div class="tsm-panel" style="margin-bottom:20px;">
			<div class="tsm-panel-h" style="display:flex;justify-content:space-between;align-items:center;">
				<h2 style="margin:0;">
					<?php if ( $all_done ) : ?>
						<?php esc_html_e( 'Setup complete', 'tradeschool-manager' ); ?>
					<?php else : ?>
						<?php /* translators: 1: completed steps, 2: total steps */ printf( esc_html__( 'Setup Checklist (%1$d of %2$d)', 'tradeschool-manager' ), (int) $done_count, (int) $total ); ?>
					<?php endif; ?>
				</h2>
				<a class="button button-small" href="<?php echo esc_url( $hide_url ); ?>"><?php esc_html_e( 'Hide', 'tradeschool-manager' ); ?></a>
			</div>
			<div class="tsm-panel-body">
				<div style="background:#e2e8f0;border-radius:999px;height:8px;overflow:hidden;margin-bottom:14px;">
					<div style="background:<?php echo $all_done ? '#16a34a' : '#1f6feb'; ?>;height:100%;width:<?php echo esc_attr( $pct ); ?>%;transition:width .3s;"></div>
				</div>
				<ol class="tsm-checklist" style="list-style:none;padding:0;margin:0;">
					<?php foreach ( $steps as $st ) : ?>
						<li style="padding:8px 0;border-bottom:1px solid #f1f5f9;display:flex;align-items:center;gap:10px;">
							<span style="font-size:18px;width:24px;text-align:center;"><?php echo $st['done'] ? 'Done' : 'Open'; ?></span>
							<?php if ( $st['done'] ) : ?>
								<span style="color:#475569;text-decoration:line-through;"><?php echo esc_html( $st['label'] ); ?></span>
							<?php else : ?>
								<a href="<?php echo esc_url( $st['url'] ); ?>" style="font-weight:600;"><?php echo esc_html( $st['label'] ); ?></a>
								<?php if ( ! empty( $st['extra'] ) ) { echo wp_kses_post( $st['extra'] ); } ?>
							<?php endif; ?>
						</li>

					<?php endforeach; ?>
				</ol>
				<?php if ( $all_done ) : ?>
					<p class="description" style="margin-top:12px;"><?php esc_html_e( 'Nice work  -  your school is ready to roll. You can hide this checklist anytime.', 'tradeschool-manager' ); ?></p>
				<?php endif; ?>
			</div>
		</div>
		<?php
	}

	/**
	 * Shortcodes reference page  -  one home for every shortcode the system exposes.
	 * Add-ons hook into the `tradeschool_manager_shortcode_registry` filter to register their own.
	 */
	public function page_shortcodes() {
		$this->shell_open( __( 'Shortcodes', 'tradeschool-manager' ) );
		$core = array(
			__( 'Core', 'tradeschool-manager' ) => array(
				array( 'code' => 'tsm_student_login',     'desc' => __( 'Student sign-in form. Place on the page selected as the Student Login Page.', 'tradeschool-manager' ) ),
				array( 'code' => 'tsm_student_portal',    'desc' => __( 'Student portal: grades, assignments, documents, certificates, payments.', 'tradeschool-manager' ) ),
				array( 'code' => 'tsm_student_gradebook', 'desc' => __( 'Standalone gradebook view for a signed-in student.', 'tradeschool-manager' ) ),
				array( 'code' => 'tsm_student_payments',  'desc' => __( 'Standalone payments/invoices view for a signed-in student.', 'tradeschool-manager' ) ),
				array( 'code' => 'tsm_teacher_login',     'desc' => __( 'Teacher sign-in form. Place on the page selected as the Teacher Login Page.', 'tradeschool-manager' ) ),
				array( 'code' => 'tsm_teacher_portal',    'desc' => __( 'Teacher portal: classes, gradebook, attendance, announcements.', 'tradeschool-manager' ) ),
				array( 'code' => 'tsm_parent_portal',     'desc' => __( 'Sponsor portal: authorized view of each linked student, including grades and payments when enabled.', 'tradeschool-manager' ) ),
				array( 'code' => 'tsm_parent_signup',     'desc' => __( 'Sponsor/contact signup and password-set form from an invite link.', 'tradeschool-manager' ) ),
			),
		);
		$sections = apply_filters( 'tradeschool_manager_shortcode_registry', $core );
		?>
		<p class="description" style="margin-bottom:16px;"><?php esc_html_e( 'Paste any shortcode below into a WordPress page or post. Add-ons register additional shortcodes here automatically when active.', 'tradeschool-manager' ); ?></p>

		<?php foreach ( $sections as $section_label => $rows ) : ?>
			<h2 style="margin-top:24px;"><?php echo esc_html( $section_label ); ?></h2>
			<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:14px;">
				<?php foreach ( $rows as $row ) :
					$code = isset( $row['code'] ) ? $row['code'] : '';
					$desc = isset( $row['desc'] ) ? $row['desc'] : '';
					$attrs = isset( $row['attrs'] ) ? (array) $row['attrs'] : array();
					?>
					<div class="tsm-panel" style="padding:14px;">
						<code style="display:block;background:#0f172a;color:#a5f3fc;padding:8px 10px;border-radius:6px;font-size:13px;margin-bottom:8px;">[<?php echo esc_html( $code ); ?>]</code>
						<p style="margin:0 0 8px;color:#475569;font-size:13px;"><?php echo esc_html( $desc ); ?></p>
						<?php if ( $attrs ) : ?>
							<p class="description" style="margin:0 0 8px;"><strong><?php esc_html_e( 'Attributes:', 'tradeschool-manager' ); ?></strong>
								<?php foreach ( $attrs as $aname => $adesc ) : ?>
									<br><code><?php echo esc_html( $aname ); ?></code>  -  <?php echo esc_html( $adesc ); ?>
								<?php endforeach; ?>
							</p>
						<?php endif; ?>
						<button type="button" class="button button-small tsm-copy-shortcode" data-code="[<?php echo esc_attr( $code ); ?>]"><?php esc_html_e( 'Copy', 'tradeschool-manager' ); ?></button>
					</div>
				<?php endforeach; ?>
			</div>
		<?php endforeach; ?>

		<?php
		TSM_Inline_Assets::script(
			'tsm-copy-shortcodes',
			"(function(){\n"
			. "\tvar copiedLabel = " . wp_json_encode( __( 'Copied!', 'tradeschool-manager' ) ) . ";\n"
			. "\tdocument.addEventListener('click', function(e){\n"
			. "\t\tvar b = e.target.closest('.tsm-copy-shortcode');\n"
			. "\t\tif (!b || !navigator.clipboard) { return; }\n"
			. "\t\tnavigator.clipboard.writeText(b.dataset.code).then(function(){\n"
			. "\t\t\tvar t = b.textContent;\n"
			. "\t\t\tb.textContent = copiedLabel;\n"
			. "\t\t\tsetTimeout(function(){ b.textContent = t; }, 1200);\n"
			. "\t\t});\n"
			. "\t});\n"
			. "})();\n"
		);
		$this->shell_close();
	}

	/**
	 * Documentation page  -  renders bundled markdown docs from /docs/.
	 */
	public function page_docs() {
		$this->shell_open( __( 'Documentation', 'tradeschool-manager' ) );
		$docs_dir = TSM_PLUGIN_DIR . 'docs/';
		$index = array(
			'getting-started.md'         => __( 'Getting Started', 'tradeschool-manager' ),
			'school-years-and-rollover.md' => __( 'School Years & Rollover', 'tradeschool-manager' ),
			'gradebook.md'               => __( 'Gradebook', 'tradeschool-manager' ),
			'portals.md'                 => __( 'Student / Teacher / Sponsor Portals', 'tradeschool-manager' ),
			'shortcodes.md'              => __( 'Shortcodes', 'tradeschool-manager' ),
			'email-and-brevo.md'         => __( 'Email & Brevo Delivery', 'tradeschool-manager' ),
		);
		$current = isset( $_GET['doc'] ) ? sanitize_file_name( wp_unslash( $_GET['doc'] ) ) : 'getting-started.md'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( ! isset( $index[ $current ] ) ) { $current = 'getting-started.md'; }
		$path = $docs_dir . $current;
		?>
		<div style="display:grid;grid-template-columns:240px 1fr;gap:20px;align-items:start;">
			<aside class="tsm-panel" style="padding:12px;">
				<h3 style="margin:4px 4px 10px;"><?php esc_html_e( 'Topics', 'tradeschool-manager' ); ?></h3>
				<ul style="list-style:none;padding:0;margin:0;">
					<?php foreach ( $index as $file => $label ) :
						$active = ( $file === $current );
						$url    = add_query_arg( 'doc', $file, admin_url( 'admin.php?page=tsm-docs' ) );
						?>
						<li><a href="<?php echo esc_url( $url ); ?>" style="display:block;padding:6px 8px;border-radius:6px;<?php echo $active ? 'background:#1f6feb;color:#fff;font-weight:600;' : 'color:#0f172a;'; ?>text-decoration:none;"><?php echo esc_html( $label ); ?></a></li>
					<?php endforeach; ?>
				</ul>
				<p class="description" style="margin-top:12px;"><a href="https://tradeschoolmanager.com/docs/" target="_blank" rel="noopener"><?php esc_html_e( 'Full docs online', 'tradeschool-manager' ); ?></a></p>
			</aside>
			<article class="tsm-panel" style="padding:24px;">
				<?php
				if ( file_exists( $path ) ) {
					$md = file_get_contents( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
					echo wp_kses_post( $this->render_markdown( $md ) );
				} else {
					echo '<p>' . esc_html__( 'Documentation file not found.', 'tradeschool-manager' ) . '</p>';
				}
				?>
			</article>
		</div>
		<?php
		$this->shell_close();
	}

	/**
	 * Tiny, dependency-free Markdown renderer good enough for our bundled docs
	 * (headings, paragraphs, lists, inline code, links, bold/italic, code fences).
	 */
	private function render_markdown( $md ) {
		$md = str_replace( "\r\n", "\n", (string) $md );
		// Extract fenced code blocks first.
		$blocks = array();
		$md = preg_replace_callback( '/```(.*?)\n(.*?)```/s', function( $m ) use ( &$blocks ) {
			$blocks[] = '<pre style="background:#0f172a;color:#a5f3fc;padding:12px;border-radius:8px;overflow:auto;"><code>' . esc_html( $m[2] ) . '</code></pre>';
			return "\0BLOCK" . ( count( $blocks ) - 1 ) . "\0";
		}, $md );
		$lines = explode( "\n", $md );
		$out = '';
		$in_ul = false; $in_ol = false; $para = array();
		$flush_para = function() use ( &$para, &$out ) {
			if ( ! empty( $para ) ) {
				$text = implode( ' ', $para );
				$out .= '<p>' . $text . '</p>';
				$para = array();
			}
		};
		$inline = function( $s ) {
			$s = esc_html( $s );
			$s = preg_replace( '/`([^`]+)`/', '<code>$1</code>', $s );
			$s = preg_replace( '/\*\*([^*]+)\*\*/', '<strong>$1</strong>', $s );
			$s = preg_replace( '/\*([^*]+)\*/', '<em>$1</em>', $s );
			$s = preg_replace_callback( '/\[([^\]]+)\]\(([^)]+)\)/', function( $m ) {
				return '<a href="' . esc_url( $m[2] ) . '" target="_blank" rel="noopener">' . $m[1] . '</a>';
			}, $s );
			return $s;
		};
		foreach ( $lines as $line ) {
			if ( preg_match( '/^\0BLOCK(\d+)\0$/', trim( $line ), $m ) ) {
				$flush_para();
				if ( $in_ul ) { $out .= '</ul>'; $in_ul = false; }
				if ( $in_ol ) { $out .= '</ol>'; $in_ol = false; }
				$out .= $blocks[ (int) $m[1] ];
				continue;
			}
			if ( preg_match( '/^(#{1,6})\s+(.*)$/', $line, $m ) ) {
				$flush_para();
				if ( $in_ul ) { $out .= '</ul>'; $in_ul = false; }
				if ( $in_ol ) { $out .= '</ol>'; $in_ol = false; }
				$lvl = strlen( $m[1] );
				$out .= '<h' . $lvl . '>' . $inline( $m[2] ) . '</h' . $lvl . '>';
				continue;
			}
			if ( preg_match( '/^\s*[-*]\s+(.*)$/', $line, $m ) ) {
				$flush_para();
				if ( $in_ol ) { $out .= '</ol>'; $in_ol = false; }
				if ( ! $in_ul ) { $out .= '<ul>'; $in_ul = true; }
				$out .= '<li>' . $inline( $m[1] ) . '</li>';
				continue;
			}
			if ( preg_match( '/^\s*\d+\.\s+(.*)$/', $line, $m ) ) {
				$flush_para();
				if ( $in_ul ) { $out .= '</ul>'; $in_ul = false; }
				if ( ! $in_ol ) { $out .= '<ol>'; $in_ol = true; }
				$out .= '<li>' . $inline( $m[1] ) . '</li>';
				continue;
			}
			if ( '' === trim( $line ) ) {
				$flush_para();
				if ( $in_ul ) { $out .= '</ul>'; $in_ul = false; }
				if ( $in_ol ) { $out .= '</ol>'; $in_ol = false; }
				continue;
			}
			$para[] = $inline( $line );
		}
		$flush_para();
		if ( $in_ul ) { $out .= '</ul>'; }
		if ( $in_ol ) { $out .= '</ol>'; }
		return $out;
	}


	public function page_students() {
		$this->shell_open( __( 'Students', 'tradeschool-manager' ) );
		$editing = isset( $_GET['edit'] ) ? TSM_Students::get( absint( $_GET['edit'] ) ) : null; // phpcs:ignore
		// Default tab: edit screen forces "add" tab; otherwise honor ?tab.
		$tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : ''; // phpcs:ignore
		if ( $editing ) { $tab = 'add'; }
		$workshops_on = class_exists( 'TSM_Workshops' );
		$valid_tabs   = array( 'add', 'current', 'all', 'unenrolled', 'dropped' );
		if ( $workshops_on ) { $valid_tabs[] = 'workshops'; }
		if ( ! in_array( $tab, $valid_tabs, true ) ) { $tab = 'current'; }

		$tabs = array(
			'add'        => __( 'Add New Student', 'tradeschool-manager' ),
			'current'    => __( 'Current Students', 'tradeschool-manager' ),
			'all'        => __( 'All Students', 'tradeschool-manager' ),
			'unenrolled' => __( 'Unenrolled', 'tradeschool-manager' ),
		);
		if ( $workshops_on ) {
			$tabs['workshops'] = __( 'Workshop Students', 'tradeschool-manager' );
		}
		$tabs['dropped'] = __( 'Dropped Students', 'tradeschool-manager' );
		$archived = class_exists( 'TSM_School_Years' ) && TSM_School_Years::is_active_year_archived();
		// Editing an existing student is ALWAYS allowed (students roll across years).
		// Only block creating brand-new students while viewing an archived year.
		$block_new = $archived && ! $editing;
		if ( $block_new && 'add' === $tab ) { $tab = 'current'; }
		$this->render_tabs( 'tsm-students', $tabs, $tab, $block_new ? array( 'add' ) : array() );
		if ( $block_new ) {
			echo '<div class="notice notice-warning inline" style="margin:6px 0 14px;"><p>Locked: ' . esc_html__( 'The active school year is archived  -  adding new students is locked. Existing students can still be edited (they may roll into other programs or workshops). Switch to a current year in the topbar to add new students.', 'tradeschool-manager' ) . '</p></div>';
		}

		$locked_create = ! $editing && class_exists( 'TSM_License' ) && TSM_License::is_creation_locked();

		if ( 'add' === $tab && ( $editing || ! $block_new ) ) {
			if ( $locked_create ) { TSM_License::render_lock_notice( __( 'students', 'tradeschool-manager' ) ); }
			?>
			<?php if ( ! $locked_create ) : ?>
			<?php if ( $editing ) : $this->render_student_header_card( $editing ); endif; ?>
			<h2 style="<?php echo $editing ? 'margin-top:18px;' : ''; ?>"><?php echo $editing ? esc_html__( 'Edit Student', 'tradeschool-manager' ) : esc_html__( 'Add Student', 'tradeschool-manager' ); ?></h2>
			<div class="<?php echo $editing ? 'tsm-student-edit-grid' : ''; ?>">
				<div class="tsm-student-edit-main">
					<form method="post" enctype="multipart/form-data" class="tsm-entry-form">
						<?php wp_nonce_field( 'tsm_save_student' ); ?>
						<input type="hidden" name="tsm_action" value="save_student" />
						<input type="hidden" name="id" value="<?php echo esc_attr( $editing->id ?? 0 ); ?>" />

						<!-- ===== Identity ===== -->
						<div class="tsm-form-section">
							<div class="tsm-form-section-head">
								<span class="dashicons dashicons-id"></span>
								<div>
									<h3><?php esc_html_e( 'Student Identity', 'tradeschool-manager' ); ?></h3>
									<p><?php esc_html_e( 'Basic identifying details. Name fields are required.', 'tradeschool-manager' ); ?></p>
								</div>
							</div>
							<div class="tsm-form-grid">
								<div class="tsm-field">
									<label><?php esc_html_e( 'First Name', 'tradeschool-manager' ); ?> <span class="tsm-req">*</span></label>
									<input class="regular-text" required name="first_name" value="<?php echo esc_attr( $editing->first_name ?? '' ); ?>">
								</div>
								<div class="tsm-field">
									<label><?php esc_html_e( 'Last Name', 'tradeschool-manager' ); ?> <span class="tsm-req">*</span></label>
									<input class="regular-text" required name="last_name" value="<?php echo esc_attr( $editing->last_name ?? '' ); ?>">
								</div>
								<div class="tsm-field">
									<label><?php esc_html_e( 'Email', 'tradeschool-manager' ); ?></label>
									<input class="regular-text" type="email" name="email" value="<?php echo esc_attr( $editing->email ?? '' ); ?>">
								</div>
								<div class="tsm-field">
									<label><?php esc_html_e( 'Phone', 'tradeschool-manager' ); ?></label>
									<input class="regular-text" name="phone" value="<?php echo esc_attr( $editing->phone ?? '' ); ?>">
								</div>
								<div class="tsm-field tsm-field-wide">
									<label><?php esc_html_e( 'Profile Photo', 'tradeschool-manager' ); ?></label>
									<?php if ( ! empty( $editing->photo_url ) ) : ?>
										<div style="margin-bottom:8px;"><img src="<?php echo esc_url( $editing->photo_url ); ?>" style="max-width:96px;max-height:96px;border-radius:50%;border:2px solid #ddd;"></div>
									<?php endif; ?>
									<input type="file" name="photo_file" accept="image/png,image/jpeg,image/webp">
									<input class="regular-text" type="url" name="photo_url" value="<?php echo esc_attr( $editing->photo_url ?? '' ); ?>" placeholder="https://...">
									<p class="description"><?php esc_html_e( 'Upload a PNG/JPG/WEBP, or paste an image URL.', 'tradeschool-manager' ); ?></p>
								</div>
							</div>
						</div>

						<!-- ===== Portal Access ===== -->
						<div class="tsm-form-section">
							<div class="tsm-form-section-head">
								<span class="dashicons dashicons-lock"></span>
								<div>
									<h3><?php esc_html_e( 'Portal Access', 'tradeschool-manager' ); ?></h3>
									<p><?php esc_html_e( 'Credentials the student uses to sign in to their portal.', 'tradeschool-manager' ); ?></p>
								</div>
							</div>
							<div class="tsm-form-grid">
								<div class="tsm-field">
									<label><?php esc_html_e( 'Portal Username', 'tradeschool-manager' ); ?></label>
									<input class="regular-text" name="username" value="<?php echo esc_attr( $editing->username ?? '' ); ?>">
								</div>
								<div class="tsm-field">
									<label><?php esc_html_e( 'Portal Password', 'tradeschool-manager' ); ?></label>
									<input class="regular-text" type="password" name="password" autocomplete="new-password" placeholder="<?php echo $editing ? esc_attr__( 'Leave blank to keep current', 'tradeschool-manager' ) : ''; ?>">
								</div>
							</div>
						</div>

						<!-- ===== Emergency Contact ===== -->
						<div class="tsm-form-section">
							<div class="tsm-form-section-head">
								<span class="dashicons dashicons-sos"></span>
								<div>
									<h3><?php esc_html_e( 'Emergency Contact', 'tradeschool-manager' ); ?></h3>
									<p><?php esc_html_e( 'Who to call if something happens during class hours.', 'tradeschool-manager' ); ?></p>
								</div>
							</div>
							<div class="tsm-form-grid">
								<div class="tsm-field"><label><?php esc_html_e( 'Contact Name', 'tradeschool-manager' ); ?></label><input class="regular-text" name="emergency_name" value="<?php echo esc_attr( $editing->emergency_name ?? '' ); ?>"></div>
								<div class="tsm-field"><label><?php esc_html_e( 'Contact Phone', 'tradeschool-manager' ); ?></label><input class="regular-text" name="emergency_phone" value="<?php echo esc_attr( $editing->emergency_phone ?? '' ); ?>"></div>
								<div class="tsm-field tsm-field-wide"><label><?php esc_html_e( 'Relationship', 'tradeschool-manager' ); ?></label><input class="regular-text" name="emergency_relation" value="<?php echo esc_attr( $editing->emergency_relation ?? '' ); ?>"></div>
							</div>
						</div>

						<?php
						// Add-on / custom-field rows: render inside a section wrapper.
						ob_start();
						do_action( 'tradeschool_manager_student_edit_fields', $editing );
						$extra = trim( ob_get_clean() );
						if ( '' !== $extra ) : ?>
						<div class="tsm-form-section">
							<div class="tsm-form-section-head">
								<span class="dashicons dashicons-forms"></span>
								<div>
									<h3><?php esc_html_e( 'Additional Details', 'tradeschool-manager' ); ?></h3>
									<p><?php esc_html_e( 'Custom fields and add-on data.', 'tradeschool-manager' ); ?></p>
								</div>
							</div>
							<table class="form-table tsm-extra-fields"><?php echo $extra; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output from add-on filter, already escaped. ?></table>
						</div>
						<?php endif; ?>

						<?php if ( ! $editing && class_exists( 'TSM_Programs' ) ) :
							$enroll_programs = TSM_Programs::all( array( 'active' => true ) );
							$enroll_year     = class_exists( 'TSM_Active_Year' ) ? (int) TSM_Active_Year::get() : 0;
							$workshops_on    = class_exists( 'TSM_Workshops' );
							$workshop_insts  = $workshops_on ? TSM_Workshops::all_instances() : array();
							if ( ! empty( $enroll_programs ) || ! empty( $workshop_insts ) ) : ?>
						<div class="tsm-form-section">
							<div class="tsm-form-section-head">
								<span class="dashicons dashicons-welcome-learn-more"></span>
								<div>
									<h3><?php esc_html_e( 'Initial Enrollment', 'tradeschool-manager' ); ?> <span class="tsm-optional"><?php esc_html_e( '(optional)', 'tradeschool-manager' ); ?></span></h3>
									<p><?php esc_html_e( 'Enroll this student into a program and/or workshop the moment you save. Programs auto-cascade into all their classes for the chosen school year.', 'tradeschool-manager' ); ?></p>
								</div>
							</div>
							<div class="tsm-form-grid">
								<?php if ( ! empty( $enroll_programs ) ) : ?>
								<div class="tsm-field">
									<label><?php esc_html_e( 'Program', 'tradeschool-manager' ); ?></label>
									<select name="enroll_program_id">
										<option value="0"><?php esc_html_e( ' -  None  - ', 'tradeschool-manager' ); ?></option>
										<?php foreach ( $enroll_programs as $p ) : ?>
											<option value="<?php echo (int) $p->id; ?>"><?php echo esc_html( $p->name ); ?></option>
										<?php endforeach; ?>
									</select>
								</div>
								<div class="tsm-field">
									<label><?php esc_html_e( 'School Year', 'tradeschool-manager' ); ?></label>
									<?php $years = class_exists( 'TSM_School_Years' ) ? TSM_School_Years::all() : array(); ?>
									<select name="enroll_school_year_id">
										<option value="0"><?php esc_html_e( ' -  Use program default  - ', 'tradeschool-manager' ); ?></option>
										<?php foreach ( (array) $years as $y ) : ?>
											<option value="<?php echo (int) $y->id; ?>" <?php selected( $enroll_year, (int) $y->id ); ?>><?php echo esc_html( $y->name ); ?></option>
										<?php endforeach; ?>
									</select>
								</div>
								<?php endif; ?>
								<?php if ( ! empty( $workshop_insts ) ) : ?>
								<div class="tsm-field tsm-field-wide">
									<label><?php esc_html_e( 'Workshop', 'tradeschool-manager' ); ?></label>
									<select name="enroll_workshop_instance_id">
										<option value="0"><?php esc_html_e( ' -  None  - ', 'tradeschool-manager' ); ?></option>
										<?php foreach ( $workshop_insts as $wi ) :
											$lbl = trim( ( $wi->workshop_name ?? '' ) . ( ! empty( $wi->label ) ? '  -  ' . $wi->label : '' ) );
											if ( ! empty( $wi->start_date ) ) { $lbl .= ' (' . esc_html( $wi->start_date ) . ')'; } ?>
											<option value="<?php echo (int) $wi->id; ?>"><?php echo esc_html( $lbl ); ?></option>
										<?php endforeach; ?>
									</select>
								</div>
								<?php endif; ?>
							</div>
						</div>
						<?php endif; endif; ?>

						<div class="tsm-form-actions">
							<button class="button button-primary button-hero"><?php echo $editing ? esc_html__( 'Update Student', 'tradeschool-manager' ) : esc_html__( 'Add Student', 'tradeschool-manager' ); ?></button>
							<?php if ( $editing ) : ?> <a class="button" href="<?php echo esc_url( admin_url( 'admin.php?page=tsm-students&tab=current' ) ); ?>"><?php esc_html_e( 'Cancel', 'tradeschool-manager' ); ?></a><?php endif; ?>
						</div>
					</form>
				</div>
				<?php if ( $editing ) : ?>
				<aside class="tsm-student-edit-side">
					<?php if ( class_exists( 'TSM_Programs' ) ) { $this->render_student_programs_compact( $editing ); } ?>
					<?php $this->render_student_classes_compact( $editing ); ?>
					<?php $this->render_student_drop_card( $editing ); ?>
				</aside>
				<?php endif; ?>
			</div>
			<?php if ( $editing ) : ?>
				<?php if ( class_exists( 'TSM_Programs' ) ) { $this->render_student_enrollments_panel( $editing ); } ?>
				<?php if ( class_exists( 'TSM_Student_Docs' ) ) { $this->render_student_documents_panel( $editing ); } ?>
			<?php endif; ?>
			<?php endif; ?>
			<?php
		} elseif ( 'current' === $tab || 'all' === $tab ) {
			$year       = class_exists( 'TSM_Active_Year' ) ? (int) TSM_Active_Year::get() : 0;
			$is_all_tab = ( 'all' === $tab );
			$students   = $is_all_tab ? TSM_Students::active( 0 ) : TSM_Students::active( $year );
			$heading    = $is_all_tab ? __( 'All Students', 'tradeschool-manager' ) : __( 'Current Students', 'tradeschool-manager' );
			?>
			<h2><?php echo esc_html( $heading ); ?>
				<a href="<?php echo esc_url( admin_url( 'admin.php?page=tsm-students&tab=add' ) ); ?>" class="page-title-action"><?php esc_html_e( 'Add New', 'tradeschool-manager' ); ?></a>
			</h2>
			<?php if ( $is_all_tab ) : ?>
				<p class="description"><?php esc_html_e( 'Every active student in the system, regardless of school year. Use Current Students to focus on the active year.', 'tradeschool-manager' ); ?></p>
			<?php elseif ( $year ) : ?>
				<p class="description"><?php esc_html_e( 'Showing students enrolled in at least one class or program in the active school year. Switch to the All Students tab to see everyone.', 'tradeschool-manager' ); ?></p>
			<?php endif; ?>
			<table class="widefat striped">
				<thead><tr><th><?php esc_html_e( 'Name', 'tradeschool-manager' ); ?></th><th><?php esc_html_e( 'Email', 'tradeschool-manager' ); ?></th><th><?php esc_html_e( 'Username', 'tradeschool-manager' ); ?></th><th></th></tr></thead>
				<tbody>
				<?php foreach ( $students as $s ) : ?>
					<tr>
						<td><?php echo esc_html( $s->last_name . ', ' . $s->first_name ); ?></td>
						<td><?php echo esc_html( $s->email ); ?></td>
						<td><?php echo esc_html( $s->username ); ?></td>
						<td>
							<a class="button button-small" href="<?php echo esc_url( add_query_arg( array( 'page' => 'tsm-students', 'edit' => $s->id ), admin_url( 'admin.php' ) ) ); ?>"><?php esc_html_e( 'Edit', 'tradeschool-manager' ); ?></a>
							<a class="button button-small" style="color:#b32d2d;border-color:#e0a8a8;" href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'page' => 'tsm-students', 'tsm_action' => 'delete_student', 'id' => $s->id ), admin_url( 'admin.php' ) ), 'tsm_delete_student_' . $s->id ) ); ?>" onclick="return confirm('<?php esc_attr_e( 'PERMANENTLY delete this student and ALL their records (enrollments, grades, invoices, certificates)? This cannot be undone. For real students, use Drop instead.', 'tradeschool-manager' ); ?>')"><?php esc_html_e( 'Delete', 'tradeschool-manager' ); ?></a>
						</td>
					</tr>
				<?php endforeach; ?>
				<?php if ( empty( $students ) ) : ?>
					<tr><td colspan="4"><?php esc_html_e( 'No students found.', 'tradeschool-manager' ); ?></td></tr>
				<?php endif; ?>
				</tbody>
			</table>
			<?php
		} elseif ( 'unenrolled' === $tab || 'workshops' === $tab ) {
			global $wpdb;
			$st_tbl   = TSM_Students::table();
			$en_tbl   = $wpdb->prefix . 'tsm_enrollments';        // class enrollments
			$pen_tbl  = $wpdb->prefix . 'tsm_program_enrollments';
			$wsh_on   = $workshops_on;
			$wen_tbl  = $wsh_on ? ( $wpdb->prefix . 'tsm_workshop_enrollments' ) : '';

			if ( 'unenrolled' === $tab ) {
				// Students with no program, no class, and no workshop enrollment.
				$wsh_join = $wsh_on ? "LEFT JOIN {$wen_tbl} wen ON wen.student_id = s.id" : '';
				$wsh_null = $wsh_on ? 'AND wen.student_id IS NULL' : '';
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Plugin-owned table names only.
				$students = $wpdb->get_results(
					"SELECT s.* FROM {$st_tbl} s
					 LEFT JOIN {$en_tbl}  en  ON en.student_id  = s.id
					 LEFT JOIN {$pen_tbl} pen ON pen.student_id = s.id
					 {$wsh_join}
					 WHERE ( s.status IS NULL OR s.status <> 'dropped' )
					   AND en.student_id  IS NULL
					   AND pen.student_id IS NULL
					   {$wsh_null}
					 GROUP BY s.id
					 ORDER BY s.last_name ASC, s.first_name ASC"
				);
				$heading = __( 'Unenrolled Students', 'tradeschool-manager' );
				$descr   = __( 'Active students not currently enrolled in any program, class, or workshop. Edit a student to enroll them.', 'tradeschool-manager' );
			} else {
				// Workshop-only: in at least one workshop, NOT in any program/class.
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Plugin-owned table names only.
				$students = $wpdb->get_results(
					"SELECT s.* FROM {$st_tbl} s
					 INNER JOIN {$wen_tbl} wen ON wen.student_id = s.id
					 LEFT JOIN  {$en_tbl}  en  ON en.student_id  = s.id
					 LEFT JOIN  {$pen_tbl} pen ON pen.student_id = s.id
					 WHERE ( s.status IS NULL OR s.status <> 'dropped' )
					   AND en.student_id  IS NULL
					   AND pen.student_id IS NULL
					 GROUP BY s.id
					 ORDER BY s.last_name ASC, s.first_name ASC"
				);
				$heading = __( 'Workshop Students', 'tradeschool-manager' );
				$descr   = __( 'Students enrolled in a workshop but not in any program or class. The main "Current Students" tab focuses on program/class enrollment; this bucket keeps workshop-only attendees visible.', 'tradeschool-manager' );
			}
			?>
			<h2><?php echo esc_html( $heading ); ?>
				<a href="<?php echo esc_url( admin_url( 'admin.php?page=tsm-students&tab=add' ) ); ?>" class="page-title-action"><?php esc_html_e( 'Add New', 'tradeschool-manager' ); ?></a>
			</h2>
			<p class="description"><?php echo esc_html( $descr ); ?></p>
			<table class="widefat striped">
				<thead><tr><th><?php esc_html_e( 'Name', 'tradeschool-manager' ); ?></th><th><?php esc_html_e( 'Email', 'tradeschool-manager' ); ?></th><th><?php esc_html_e( 'Username', 'tradeschool-manager' ); ?></th><th></th></tr></thead>
				<tbody>
				<?php foreach ( (array) $students as $s ) : ?>
					<tr>
						<td><?php echo esc_html( $s->last_name . ', ' . $s->first_name ); ?></td>
						<td><?php echo esc_html( $s->email ); ?></td>
						<td><?php echo esc_html( $s->username ); ?></td>
						<td><a class="button button-small" href="<?php echo esc_url( add_query_arg( array( 'page' => 'tsm-students', 'edit' => $s->id ), admin_url( 'admin.php' ) ) ); ?>"><?php esc_html_e( 'Edit', 'tradeschool-manager' ); ?></a></td>
					</tr>
				<?php endforeach; ?>
				<?php if ( empty( $students ) ) : ?>
					<tr><td colspan="4"><?php esc_html_e( 'No students in this bucket.', 'tradeschool-manager' ); ?></td></tr>
				<?php endif; ?>
				</tbody>
			</table>
			<?php
		} else { // dropped
			$students = TSM_Students::dropped();
			?>
			<h2><?php esc_html_e( 'Dropped Students', 'tradeschool-manager' ); ?></h2>
			<p class="description"><?php esc_html_e( 'These students remain in the system for historical reporting. Reinstate to return them to active status.', 'tradeschool-manager' ); ?></p>
			<table class="widefat striped">
				<thead><tr>
					<th><?php esc_html_e( 'Name', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'Email', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'Dropped Date', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'Reason', 'tradeschool-manager' ); ?></th>
					<th></th>
				</tr></thead>
				<tbody>
				<?php foreach ( $students as $s ) : ?>
					<tr>
						<td><?php echo esc_html( $s->last_name . ', ' . $s->first_name ); ?></td>
						<td><?php echo esc_html( $s->email ); ?></td>
						<td><?php echo esc_html( $s->dropped_date ?: ' - ' ); ?></td>
						<td><?php echo esc_html( wp_trim_words( (string) $s->dropped_reason, 18 ) ); ?></td>
						<td>
							<a class="button button-small" href="<?php echo esc_url( add_query_arg( array( 'page' => 'tsm-students', 'edit' => $s->id ), admin_url( 'admin.php' ) ) ); ?>"><?php esc_html_e( 'View', 'tradeschool-manager' ); ?></a>
							<a class="button button-small" style="color:#b32d2d;border-color:#e0a8a8;" href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'page' => 'tsm-students', 'tab' => 'dropped', 'tsm_action' => 'delete_student', 'id' => $s->id ), admin_url( 'admin.php' ) ), 'tsm_delete_student_' . $s->id ) ); ?>" onclick="return confirm('<?php esc_attr_e( 'PERMANENTLY delete this student and ALL their records? This cannot be undone.', 'tradeschool-manager' ); ?>')"><?php esc_html_e( 'Delete', 'tradeschool-manager' ); ?></a>
						</td>
					</tr>
				<?php endforeach; ?>
				<?php if ( empty( $students ) ) : ?>
					<tr><td colspan="5"><?php esc_html_e( 'No dropped students.', 'tradeschool-manager' ); ?></td></tr>
				<?php endif; ?>
				</tbody>
			</table>
			<?php
		}
		$this->shell_close();
	}

	public function page_teachers() {
		$this->shell_open( __( 'Teachers', 'tradeschool-manager' ) );
		$editing = isset( $_GET['edit'] ) ? TSM_Teachers::get( absint( $_GET['edit'] ) ) : null; // phpcs:ignore
		$tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : ''; // phpcs:ignore
		if ( $editing ) { $tab = 'add'; }
		if ( ! in_array( $tab, array( 'add', 'all' ), true ) ) { $tab = 'all'; }

		$this->render_tabs( 'tsm-teachers', array(
			'add' => __( 'Add New Teacher', 'tradeschool-manager' ),
			'all' => __( 'All Teachers', 'tradeschool-manager' ),
		), $tab );

		$locked_create = ! $editing && class_exists( 'TSM_License' ) && TSM_License::is_creation_locked();

		if ( 'add' === $tab ) {
			if ( $locked_create ) { TSM_License::render_lock_notice( __( 'teachers', 'tradeschool-manager' ) ); }
			?>
			<?php if ( ! $locked_create ) : ?>
			<h2><?php echo $editing ? esc_html__( 'Edit Teacher', 'tradeschool-manager' ) : esc_html__( 'Add Teacher', 'tradeschool-manager' ); ?></h2>
			<form method="post" class="tsm-entry-form">
				<?php wp_nonce_field( 'tsm_save_teacher' ); ?>
				<input type="hidden" name="tsm_action" value="save_teacher" />
				<input type="hidden" name="id" value="<?php echo esc_attr( $editing->id ?? 0 ); ?>" />

				<div class="tsm-form-section">
					<div class="tsm-form-section-head">
						<span class="dashicons dashicons-id"></span>
						<div>
							<h3><?php esc_html_e( 'Teacher Identity', 'tradeschool-manager' ); ?></h3>
							<p><?php esc_html_e( 'Public name and contact email. Used on rosters, gradebooks and PDFs.', 'tradeschool-manager' ); ?></p>
						</div>
					</div>
					<div class="tsm-form-grid">
						<div class="tsm-field">
							<label><?php esc_html_e( 'First Name', 'tradeschool-manager' ); ?> <span class="tsm-req">*</span></label>
							<input required class="regular-text" name="first_name" value="<?php echo esc_attr( $editing->first_name ?? '' ); ?>">
						</div>
						<div class="tsm-field">
							<label><?php esc_html_e( 'Last Name', 'tradeschool-manager' ); ?> <span class="tsm-req">*</span></label>
							<input required class="regular-text" name="last_name" value="<?php echo esc_attr( $editing->last_name ?? '' ); ?>">
						</div>
						<div class="tsm-field">
							<label><?php esc_html_e( 'Email', 'tradeschool-manager' ); ?></label>
							<input class="regular-text" type="email" name="email" value="<?php echo esc_attr( $editing->email ?? '' ); ?>">
						</div>
						<div class="tsm-field">
							<label><?php esc_html_e( 'Phone Number', 'tradeschool-manager' ); ?></label>
							<input class="regular-text" type="tel" name="phone" value="<?php echo esc_attr( $editing->phone ?? '' ); ?>" placeholder="(555) 555-1234">
						</div>
					</div>
				</div>

				<div class="tsm-form-section">
					<div class="tsm-form-section-head">
						<span class="dashicons dashicons-lock"></span>
						<div>
							<h3><?php esc_html_e( 'Teacher Portal Access', 'tradeschool-manager' ); ?></h3>
							<p><?php esc_html_e( 'Credentials for the teacher portal. Leave the password blank when editing to keep the current one.', 'tradeschool-manager' ); ?></p>
						</div>
					</div>
					<div class="tsm-form-grid">
						<div class="tsm-field">
							<label><?php esc_html_e( 'Username', 'tradeschool-manager' ); ?></label>
							<input class="regular-text" name="username" value="<?php echo esc_attr( $editing->username ?? '' ); ?>">
						</div>
						<div class="tsm-field">
							<label><?php esc_html_e( 'Password', 'tradeschool-manager' ); ?></label>
							<input class="regular-text" type="password" name="password" autocomplete="new-password" placeholder="<?php echo $editing ? esc_attr__( 'Leave blank to keep current', 'tradeschool-manager' ) : ''; ?>">
						</div>
					</div>
				</div>

				<div class="tsm-form-actions">
					<button class="button button-primary button-hero"><?php echo $editing ? esc_html__( 'Update Teacher', 'tradeschool-manager' ) : esc_html__( 'Save Teacher', 'tradeschool-manager' ); ?></button>
					<?php if ( $editing ) : ?> <a class="button" href="<?php echo esc_url( admin_url( 'admin.php?page=tsm-teachers&tab=all' ) ); ?>"><?php esc_html_e( 'Cancel', 'tradeschool-manager' ); ?></a><?php endif; ?>
				</div>
			</form>
			<?php endif; ?>
			<?php
		} else {
			$teachers = TSM_Teachers::all();
			?>
			<h2><?php esc_html_e( 'All Teachers', 'tradeschool-manager' ); ?>
				<a href="<?php echo esc_url( admin_url( 'admin.php?page=tsm-teachers&tab=add' ) ); ?>" class="page-title-action"><?php esc_html_e( 'Add New', 'tradeschool-manager' ); ?></a>
			</h2>

			<table class="widefat striped">
				<thead><tr>
					<th style="width:80px;"><?php esc_html_e( 'ID', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'Name', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'Email', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'Phone', 'tradeschool-manager' ); ?></th>
					<th></th>
				</tr></thead>
				<tbody>
					<?php foreach ( $teachers as $t ) : ?>
						<tr>
							<td><code>#<?php echo esc_html( $t->id ); ?></code></td>
							<td><?php echo esc_html( $t->last_name . ', ' . $t->first_name ); ?></td>
							<td><?php echo esc_html( $t->email ); ?></td>
							<td><?php echo esc_html( tsm_format_phone( $t->phone ?? '' ) ); ?></td>
							<td>
								<a class="button button-small" href="<?php echo esc_url( add_query_arg( array( 'page' => 'tsm-teachers', 'edit' => $t->id ), admin_url( 'admin.php' ) ) ); ?>"><?php esc_html_e( 'Edit', 'tradeschool-manager' ); ?></a>
								<a class="button button-small" href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'tsm_action' => 'delete_teacher', 'id' => $t->id ) ), 'tsm_delete_teacher_' . $t->id ) ); ?>" onclick="return confirm('<?php esc_attr_e( 'Delete this teacher?', 'tradeschool-manager' ); ?>')"><?php esc_html_e( 'Delete', 'tradeschool-manager' ); ?></a>
							</td>
						</tr>
					<?php endforeach; ?>
					<?php if ( empty( $teachers ) ) : ?>
						<tr><td colspan="5"><?php esc_html_e( 'No teachers yet.', 'tradeschool-manager' ); ?></td></tr>
					<?php endif; ?>
				</tbody>
			</table>
			<?php
		}
		$this->shell_close();
	}

	public function page_school_years() {
		$this->shell_open( __( 'School Years', 'tradeschool-manager' ) );

		$tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'years'; // phpcs:ignore
		$valid_tabs = array( 'years', 'rollover', 'archive' );
		if ( ! in_array( $tab, $valid_tabs, true ) ) { $tab = 'years'; }

		$this->render_tabs( 'tsm-school-years', array(
			'years'    => __( 'School Years', 'tradeschool-manager' ),
			'rollover' => __( 'Year Rollover', 'tradeschool-manager' ),
			'archive'  => __( 'Archive', 'tradeschool-manager' ),
		), $tab );

		if ( 'archive' === $tab ) {
			if ( class_exists( 'TSMP_Archive_Theme' ) ) {
				TSMP_Archive_Theme::render_admin_archive();
			} else {
				echo '<p>' . esc_html__( 'Archive feature unavailable.', 'tradeschool-manager' ) . '</p>';
			}
			$this->shell_close();
			return;
		}

		echo '<div class="notice notice-info inline" style="margin:10px 0 18px;"><p><strong>' . esc_html__( 'Heads up:', 'tradeschool-manager' ) . '</strong> ' . esc_html__( 'School years apply to Programs and Classes only. Workshops are scheduled outside the yearly calendar and do not roll into year-based GPA, report cards, or rollups.', 'tradeschool-manager' ) . '</p></div>';
		$years = TSM_School_Years::all();
		$locked_create = class_exists( 'TSM_License' ) && TSM_License::is_creation_locked();
		if ( 'years' === $tab && $locked_create ) { TSM_License::render_lock_notice( __( 'school years', 'tradeschool-manager' ) ); }
		?>
		<?php if ( 'years' === $tab ) : ?>
		<?php if ( ! $locked_create ) : ?>
		<form method="post">
			<?php wp_nonce_field( 'tsm_save_year' ); ?>
			<input type="hidden" name="tsm_action" value="save_year">
			<table class="form-table">
				<tr><th><?php esc_html_e( 'Name', 'tradeschool-manager' ); ?></th><td><input required class="regular-text" name="name" placeholder="2024-2025"></td></tr>
				<tr><th><?php esc_html_e( 'Start Date', 'tradeschool-manager' ); ?></th><td><input type="date" name="start_date"></td></tr>
				<tr><th><?php esc_html_e( 'End Date', 'tradeschool-manager' ); ?></th><td><input type="date" name="end_date"></td></tr>
			</table>
			<p><button class="button button-primary"><?php esc_html_e( 'Add School Year', 'tradeschool-manager' ); ?></button></p>
		</form>
		<?php endif; ?>
		<table class="widefat striped">
			<thead><tr><th><?php esc_html_e( 'Name', 'tradeschool-manager' ); ?></th><th><?php esc_html_e( 'Start', 'tradeschool-manager' ); ?></th><th><?php esc_html_e( 'End', 'tradeschool-manager' ); ?></th><th><?php esc_html_e( 'Status', 'tradeschool-manager' ); ?></th><th><?php esc_html_e( 'Classes', 'tradeschool-manager' ); ?></th><th></th></tr></thead>
			<tbody>
			<?php
			global $wpdb;
			$current_year = TSM_School_Years::current();
			foreach ( $years as $y ) :
				$class_count = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}tsm_classes WHERE school_year_id = %d", $y->id ) );
				$is_current  = $current_year && (int) $current_year->id === (int) $y->id;
				?>
				<tr>
					<td><strong><?php echo esc_html( $y->name ); ?></strong>
						<?php if ( $is_current ) : ?><span class="tsm-pill" style="background:#16a34a;color:#fff;font-size:11px;padding:2px 8px;border-radius:10px;margin-left:6px;">Current</span><?php endif; ?>
						<?php if ( ! empty( $y->archived ) ) : ?><span class="tsm-pill" style="background:#777;color:#fff;font-size:11px;padding:2px 8px;border-radius:10px;margin-left:6px;">Archived</span><?php endif; ?>
					</td>
					<td><?php echo esc_html( $y->start_date ); ?></td>
					<td><?php echo esc_html( $y->end_date ); ?></td>
					<td><?php echo esc_html( $y->status ); ?></td>
					<td><?php echo (int) $class_count; ?></td>
					<td><a class="button button-small" href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'tsm_action' => 'delete_year', 'id' => $y->id ) ), 'tsm_delete_year_' . $y->id ) ); ?>" onclick="return confirm('<?php esc_attr_e( 'Delete this school year? Classes assigned to it will become orphaned. (Records and payments are preserved.)', 'tradeschool-manager' ); ?>')"><?php esc_html_e( 'Delete', 'tradeschool-manager' ); ?></a></td>
				</tr>
			<?php endforeach; ?>
			</tbody>
		</table>
		<?php endif; /* end 'years' tab */ ?>

		<?php if ( 'rollover' === $tab ) : ?>
		<h2 style="margin-top:8px;"><?php esc_html_e( 'Roll Classes Into a New Year', 'tradeschool-manager' ); ?></h2>
		<p class="description" style="max-width:780px;">
			<?php esc_html_e( 'Copy class definitions and assignments from a previous school year into a new one. The new classes start with an empty roster, an empty gradebook, and an empty attendance log so the new year is a clean slate. The original year  -  including students, grades, payments, invoices, and certificates  -  is left completely untouched and remains available for historical reports and PDFs.', 'tradeschool-manager' ); ?>
		</p>
		<?php
		$rollover_from = isset( $_GET['rollover_from'] ) ? absint( $_GET['rollover_from'] ) : 0; // phpcs:ignore
		$src_classes   = $rollover_from ? TSM_Classes::all( $rollover_from ) : array();
		?>
		<form method="get" style="margin:10px 0;display:flex;gap:10px;align-items:end;flex-wrap:wrap;">
			<input type="hidden" name="page" value="tsm-school-years">
			<label style="display:flex;flex-direction:column;font-weight:600;font-size:12px;">
				<?php esc_html_e( 'Source year (copy FROM)', 'tradeschool-manager' ); ?>
				<select name="rollover_from" onchange="this.form.submit()" style="min-width:220px;">
					<option value="0"> - </option>
					<?php foreach ( $years as $y ) : ?>
						<option value="<?php echo esc_attr( $y->id ); ?>" <?php selected( $rollover_from, $y->id ); ?>><?php echo esc_html( $y->name ); ?></option>
					<?php endforeach; ?>
				</select>
			</label>
		</form>

		<?php if ( $rollover_from ) : ?>
			<form method="post" style="border:1px solid #ddd;border-radius:6px;padding:16px;background:#fff;max-width:920px;">
				<?php wp_nonce_field( 'tsm_rollover_year' ); ?>
				<input type="hidden" name="tsm_action" value="rollover_year">
				<input type="hidden" name="from_year_id" value="<?php echo esc_attr( $rollover_from ); ?>">

				<table class="form-table" style="margin-top:0;">
					<tr><th style="width:200px;"><?php esc_html_e( 'Destination year (copy TO)', 'tradeschool-manager' ); ?></th><td>
						<select required name="to_year_id">
							<option value=""><?php esc_html_e( ' -  Select destination year  - ', 'tradeschool-manager' ); ?></option>
							<?php foreach ( $years as $y ) :
								if ( (int) $y->id === (int) $rollover_from ) { continue; } ?>
								<option value="<?php echo esc_attr( $y->id ); ?>"><?php echo esc_html( $y->name ); ?></option>
							<?php endforeach; ?>
						</select>
						<p class="description"><?php esc_html_e( 'The destination year must already exist. Create it above first if needed.', 'tradeschool-manager' ); ?></p>
					</td></tr>
				</table>

				<h3 style="margin-bottom:6px;"><?php esc_html_e( 'Classes to copy', 'tradeschool-manager' ); ?></h3>
				<?php if ( empty( $src_classes ) ) : ?>
					<p><em><?php esc_html_e( 'The selected source year has no classes.', 'tradeschool-manager' ); ?></em></p>
				<?php else : ?>
					<p style="margin:0 0 6px;"><label><input type="checkbox" onclick="this.closest('form').querySelectorAll('input[name=\'class_ids[]\']').forEach(c=>c.checked=this.checked);" checked> <strong><?php esc_html_e( 'Select all', 'tradeschool-manager' ); ?></strong></label></p>
					<table class="widefat striped" style="margin-top:4px;">
						<thead><tr><th style="width:40px;"></th><th><?php esc_html_e( 'Class', 'tradeschool-manager' ); ?></th><th><?php esc_html_e( 'Method', 'tradeschool-manager' ); ?></th><th><?php esc_html_e( 'Assignments', 'tradeschool-manager' ); ?></th></tr></thead>
						<tbody>
							<?php foreach ( $src_classes as $sc ) :
								$ac = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}tsm_assignments WHERE class_id = %d", $sc->id ) ); ?>
								<tr>
									<td><input type="checkbox" name="class_ids[]" value="<?php echo esc_attr( $sc->id ); ?>" checked></td>
									<td><?php echo esc_html( $sc->name ); ?></td>
									<td><?php echo esc_html( $sc->grading_method ); ?></td>
									<td><?php echo (int) $ac; ?></td>
								</tr>
							<?php endforeach; ?>
						</tbody>
					</table>
					<p style="margin-top:14px;"><button class="button button-primary"><?php esc_html_e( 'Copy selected classes into destination year', 'tradeschool-manager' ); ?></button></p>
					<p class="description"><?php esc_html_e( 'What gets copied: class name, description, teacher, grading method, weighted categories, and every assignment (with points, category, EC/required flags). What does NOT get copied: student roster, grades, attendance, due dates (cleared so you can re-schedule).', 'tradeschool-manager' ); ?></p>
				<?php endif; ?>
			</form>
		<?php endif; ?>
		<?php endif; /* end 'rollover' tab */ ?>
		<?php
		$this->shell_close();
	}

	public function page_classes() {
		$this->shell_open( __( 'Classes', 'tradeschool-manager' ) );
		$active_year = class_exists( 'TSM_Active_Year' ) ? TSM_Active_Year::get() : 0;
		$classes  = TSM_Classes::all( $active_year );
		$years    = TSM_School_Years::all();
		$teachers = TSM_Teachers::all();
		$students = TSM_Students::active();
		$editing  = isset( $_GET['edit'] ) ? TSM_Classes::get( absint( $_GET['edit'] ) ) : null; // phpcs:ignore
		$tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : ''; // phpcs:ignore
		if ( $editing ) { $tab = 'add'; }
		if ( ! in_array( $tab, array( 'add', 'all' ), true ) ) { $tab = 'all'; }

		$archived = class_exists( 'TSM_School_Years' ) && TSM_School_Years::is_active_year_archived();
		if ( $archived && 'add' === $tab ) { $tab = 'all'; }
		$this->render_tabs( 'tsm-classes', array(
			'add' => __( 'Add New Class', 'tradeschool-manager' ),
			'all' => __( 'All Classes', 'tradeschool-manager' ),
		), $tab, $archived ? array( 'add' ) : array() );
		if ( $archived ) { $this->render_archived_notice( __( 'classes', 'tradeschool-manager' ) ); }

		$locked_create = ! $editing && class_exists( 'TSM_License' ) && TSM_License::is_creation_locked();
		if ( 'add' === $tab && ! $archived && $locked_create ) { TSM_License::render_lock_notice( __( 'classes', 'tradeschool-manager' ) ); }
		?>
		<?php if ( 'add' === $tab && ! $archived ) : ?>
		<?php if ( ! $locked_create ) : ?>
		<h2><?php echo $editing ? esc_html__( 'Edit Class', 'tradeschool-manager' ) : esc_html__( 'Add Class', 'tradeschool-manager' ); ?></h2>
		<form method="post" class="tsm-entry-form">
			<?php wp_nonce_field( 'tsm_save_class' ); ?>
			<input type="hidden" name="tsm_action" value="save_class">
			<input type="hidden" name="id" value="<?php echo esc_attr( $editing->id ?? 0 ); ?>">

			<div class="tsm-form-section">
				<div class="tsm-form-section-head">
					<span class="dashicons dashicons-welcome-learn-more"></span>
					<div>
						<h3><?php esc_html_e( 'Class Basics', 'tradeschool-manager' ); ?></h3>
						<p><?php esc_html_e( 'Where this class lives, who teaches it, and how it is graded.', 'tradeschool-manager' ); ?></p>
					</div>
				</div>
				<div class="tsm-form-grid">
					<div class="tsm-field">
						<label><?php esc_html_e( 'Class Name', 'tradeschool-manager' ); ?> <span class="tsm-req">*</span></label>
						<input required class="regular-text" name="name" value="<?php echo esc_attr( $editing->name ?? '' ); ?>">
					</div>
					<div class="tsm-field">
						<label><?php esc_html_e( 'School Year', 'tradeschool-manager' ); ?> <span class="tsm-req">*</span></label>
						<select required name="school_year_id">
							<option value=""><?php esc_html_e( ' -  Select  - ', 'tradeschool-manager' ); ?></option>
							<?php $sel_year = $editing->school_year_id ?? ( $active_year ?: 0 ); ?>
							<?php foreach ( $years as $y ) : ?>
								<option value="<?php echo esc_attr( $y->id ); ?>" <?php selected( $sel_year, $y->id ); ?>><?php echo esc_html( $y->name ); ?></option>
							<?php endforeach; ?>
						</select>
					</div>
					<div class="tsm-field">
						<label><?php esc_html_e( 'Primary Teacher', 'tradeschool-manager' ); ?></label>
						<select name="teacher_id">
							<option value="0"> - </option>
							<?php foreach ( $teachers as $t ) : ?>
								<option value="<?php echo esc_attr( $t->id ); ?>" <?php selected( $editing->teacher_id ?? 0, $t->id ); ?>><?php echo esc_html( $t->last_name . ', ' . $t->first_name ); ?></option>
							<?php endforeach; ?>
						</select>
					</div>
					<div class="tsm-field">
						<label><?php esc_html_e( 'Grading Method', 'tradeschool-manager' ); ?></label>
						<select name="grading_method" id="tsm-grading-method">
							<?php foreach ( array( 'points' => 'Points', 'weighted' => 'Weighted Categories', 'pass_fail' => 'Pass/Fail' ) as $k => $lbl ) : ?>
								<option value="<?php echo esc_attr( $k ); ?>" <?php selected( $editing->grading_method ?? 'points', $k ); ?>><?php echo esc_html( $lbl ); ?></option>
							<?php endforeach; ?>
						</select>
						<p class="description"><?php esc_html_e( 'Choose "Weighted Categories" to define category weights you can assign to each assignment.', 'tradeschool-manager' ); ?></p>
					</div>
				</div>
			</div>

			<div class="tsm-form-section">
				<div class="tsm-form-section-head">
					<span class="dashicons dashicons-groups"></span>
					<div>
						<h3><?php esc_html_e( 'Additional Teachers', 'tradeschool-manager' ); ?></h3>
						<p><?php esc_html_e( 'Co-teachers can enter grades and attendance for this class. The primary teacher is excluded automatically.', 'tradeschool-manager' ); ?></p>
					</div>
				</div>
				<div>
					<?php
					$co_selected = ( $editing && $editing->id ) ? TSM_Classes::co_teacher_ids( $editing->id ) : array();
					$primary_id  = (int) ( $editing->teacher_id ?? 0 );
					$teachers_js = array();
					foreach ( $teachers as $t ) {
						$teachers_js[] = array(
							'id'    => (int) $t->id,
							'label' => $t->last_name . ', ' . $t->first_name,
						);
					}
					?>
					<table class="widefat" id="tsm-coteachers-table" style="max-width:420px;">
						<tbody>
							<?php if ( empty( $co_selected ) ) : ?>
								<tr class="tsm-coteacher-empty"><td colspan="2"><em><?php esc_html_e( 'No additional teachers.', 'tradeschool-manager' ); ?></em></td></tr>
							<?php else : foreach ( $co_selected as $tid ) : ?>
								<tr>
									<td><select name="co_teacher_ids[]" class="tsm-coteacher-select" data-current="<?php echo (int) $tid; ?>" style="min-width:260px;"></select></td>
									<td style="width:60px;"><button type="button" class="button button-small tsm-coteacher-remove">&times;</button></td>
								</tr>
							<?php endforeach; endif; ?>
						</tbody>
					</table>
					<p><button type="button" class="button" id="tsm-coteacher-add"><?php esc_html_e( '+ Add Teacher', 'tradeschool-manager' ); ?></button></p>
					<p class="description"><?php esc_html_e( 'Co-teachers can enter grades and attendance for this class. The primary teacher is excluded automatically.', 'tradeschool-manager' ); ?></p>
					<?php
					TSM_Inline_Assets::script(
						'tsm-coteachers',
						"(function(){\n"
						. "\tvar allTeachers = " . wp_json_encode( $teachers_js ) . ";\n"
						. "\tvar selectTeacher = " . wp_json_encode( __( 'Select teacher', 'tradeschool-manager' ) ) . ";\n"
						. "\tvar emptyLabel = " . wp_json_encode( __( 'No additional teachers.', 'tradeschool-manager' ) ) . ";\n"
						. "\tvar primarySel = document.querySelector('select[name=\"teacher_id\"]');\n"
						. "\tvar table = document.getElementById('tsm-coteachers-table');\n"
						. "\tvar tbl = table ? table.querySelector('tbody') : null;\n"
						. "\tvar addBtn = document.getElementById('tsm-coteacher-add');\n"
						. "\tif (!tbl || !addBtn) { return; }\n"
						. "\tfunction pickedIds(exceptSelect){ var ids = []; tbl.querySelectorAll('select.tsm-coteacher-select').forEach(function(s){ if (s === exceptSelect) { return; } var v = parseInt(s.value, 10); if (v) { ids.push(v); } }); return ids; }\n"
						. "\tfunction refreshOptions(){ var primary = parseInt(primarySel ? primarySel.value : 0, 10) || 0; tbl.querySelectorAll('select.tsm-coteacher-select').forEach(function(s){ var cur = parseInt(s.value, 10) || parseInt(s.getAttribute('data-current'), 10) || 0; var taken = pickedIds(s); s.textContent = ''; var blank = document.createElement('option'); blank.value = '0'; blank.textContent = ' -  ' + selectTeacher + '  - '; s.appendChild(blank); allTeachers.forEach(function(t){ if (t.id === primary) { return; } if (taken.indexOf(t.id) !== -1 && t.id !== cur) { return; } var option = document.createElement('option'); option.value = String(t.id); option.textContent = t.label; if (t.id === cur) { option.selected = true; } s.appendChild(option); }); if (cur) { s.value = String(cur); } s.removeAttribute('data-current'); }); }\n"
						. "\tfunction removeEmptyRow(){ var empty = tbl.querySelector('.tsm-coteacher-empty'); if (empty) { empty.remove(); } }\n"
						. "\tfunction maybeRestoreEmpty(){ if (!tbl.querySelector('tr')) { var tr = document.createElement('tr'); tr.className = 'tsm-coteacher-empty'; var td = document.createElement('td'); td.colSpan = 2; var em = document.createElement('em'); em.textContent = emptyLabel; td.appendChild(em); tr.appendChild(td); tbl.appendChild(tr); } }\n"
						. "\taddBtn.addEventListener('click', function(){ removeEmptyRow(); var tr = document.createElement('tr'); var selectCell = document.createElement('td'); var select = document.createElement('select'); select.name = 'co_teacher_ids[]'; select.className = 'tsm-coteacher-select'; select.style.minWidth = '260px'; selectCell.appendChild(select); var actionCell = document.createElement('td'); actionCell.style.width = '60px'; var remove = document.createElement('button'); remove.type = 'button'; remove.className = 'button button-small tsm-coteacher-remove'; remove.textContent = 'x'; actionCell.appendChild(remove); tr.appendChild(selectCell); tr.appendChild(actionCell); tbl.appendChild(tr); refreshOptions(); });\n"
						. "\ttbl.addEventListener('click', function(e){ if (e.target.classList.contains('tsm-coteacher-remove')) { e.target.closest('tr').remove(); maybeRestoreEmpty(); refreshOptions(); } });\n"
						. "\ttbl.addEventListener('change', function(e){ if (e.target.classList.contains('tsm-coteacher-select')) { refreshOptions(); } });\n"
						. "\tif (primarySel) { primarySel.addEventListener('change', refreshOptions); }\n"
						. "\trefreshOptions();\n"
						. "})();\n"
					);
					?>
				</div>
			</div>

			<?php
			$existing_cats = array();
			if ( $editing && ! empty( $editing->grading_config ) ) {
				$cfg = json_decode( $editing->grading_config, true );
				if ( is_array( $cfg ) && ! empty( $cfg['categories'] ) ) {
					foreach ( $cfg['categories'] as $cname => $cweight ) {
						$existing_cats[] = array( 'name' => $cname, 'weight' => $cweight );
					}
				}
			}
			$show_cats = ( $editing && 'weighted' === ( $editing->grading_method ?? '' ) );
			?>
			<div class="tsm-form-section" id="tsm-categories-row" style="<?php echo $show_cats ? '' : 'display:none;'; ?>">
				<div class="tsm-form-section-head">
					<span class="dashicons dashicons-chart-pie"></span>
					<div>
						<h3><?php esc_html_e( 'Grade Categories', 'tradeschool-manager' ); ?></h3>
						<p><?php esc_html_e( 'Define weighted categories (e.g. Homework 30%, Tests 70%). Percentages must total exactly 100%.', 'tradeschool-manager' ); ?></p>
					</div>
				</div>
				<table class="widefat" id="tsm-categories-table" style="max-width:520px;">
					<thead><tr>
						<th><?php esc_html_e( 'Category Name', 'tradeschool-manager' ); ?></th>
						<th style="width:120px;"><?php esc_html_e( 'Weight (%)', 'tradeschool-manager' ); ?></th>
						<th style="width:60px;"></th>
					</tr></thead>
					<tbody>
						<?php if ( empty( $existing_cats ) ) :
							$existing_cats = array(
								array( 'name' => 'Homework', 'weight' => 30 ),
								array( 'name' => 'Tests',    'weight' => 70 ),
							);
						endif;
						foreach ( $existing_cats as $i => $row ) : ?>
							<tr>
								<td><input type="text" name="grading_config[categories][<?php echo (int) $i; ?>][name]" value="<?php echo esc_attr( $row['name'] ); ?>" class="regular-text" placeholder="<?php esc_attr_e( 'e.g. Homework', 'tradeschool-manager' ); ?>"></td>
								<td><input type="number" step="0.01" min="0" name="grading_config[categories][<?php echo (int) $i; ?>][weight]" value="<?php echo esc_attr( $row['weight'] ); ?>"></td>
								<td><button type="button" class="button button-small tsm-cat-remove">&times;</button></td>
							</tr>
						<?php endforeach; ?>
					</tbody>
				</table>
				<p style="margin-top:10px;">
					<button type="button" class="button" id="tsm-cat-add"><?php esc_html_e( '+ Add Category', 'tradeschool-manager' ); ?></button>
					<span class="description" id="tsm-cat-total" style="margin-left:10px;"></span>
				</p>
				<p class="description"><?php esc_html_e( 'Saving with no rows means everything goes to "Uncategorized".', 'tradeschool-manager' ); ?></p>
			</div>
			<?php
			TSM_Inline_Assets::script(
				'tsm-class-categories',
				"(function(){\n"
				. "\tvar sel = document.getElementById('tsm-grading-method');\n"
				. "\tvar row = document.getElementById('tsm-categories-row');\n"
				. "\tvar table = document.getElementById('tsm-categories-table');\n"
				. "\tvar tbl = table ? table.querySelector('tbody') : null;\n"
				. "\tvar addBtn = document.getElementById('tsm-cat-add');\n"
				. "\tvar totalEl = document.getElementById('tsm-cat-total');\n"
				. "\tif (!sel || !row || !tbl || !addBtn || !totalEl) { return; }\n"
				. "\tvar form = sel.closest('form');\n"
				. "\tfunction recalc(){ var sum = 0; tbl.querySelectorAll('input[type=number]').forEach(function(i){ sum += parseFloat(i.value) || 0; }); var rounded = Math.round(sum * 100) / 100; var ok = Math.abs(rounded - 100) < 0.01; totalEl.textContent = 'Total: ' + rounded + '%' + (ok ? ' ' : ' (must equal 100%)'); totalEl.style.color = ok ? '#15803d' : '#dc2626'; totalEl.style.fontWeight = '600'; }\n"
				. "\tfunction toggle(){ row.style.display = (sel.value === 'weighted') ? '' : 'none'; recalc(); }\n"
				. "\tfunction nextIndex(){ return tbl.querySelectorAll('tr').length; }\n"
				. "\tfunction makeInput(type, name){ var input = document.createElement('input'); input.type = type; input.name = name; return input; }\n"
				. "\tsel.addEventListener('change', toggle);\n"
				. "\taddBtn.addEventListener('click', function(){ var i = nextIndex(); var tr = document.createElement('tr'); var nameCell = document.createElement('td'); var nameInput = makeInput('text', 'grading_config[categories][' + i + '][name]'); nameInput.className = 'regular-text'; nameInput.placeholder = 'Category name'; nameCell.appendChild(nameInput); var weightCell = document.createElement('td'); var weightInput = makeInput('number', 'grading_config[categories][' + i + '][weight]'); weightInput.step = '0.01'; weightInput.min = '0'; weightInput.value = '0'; weightCell.appendChild(weightInput); var actionCell = document.createElement('td'); var remove = document.createElement('button'); remove.type = 'button'; remove.className = 'button button-small tsm-cat-remove'; remove.textContent = 'x'; actionCell.appendChild(remove); tr.appendChild(nameCell); tr.appendChild(weightCell); tr.appendChild(actionCell); tbl.appendChild(tr); recalc(); });\n"
				. "\ttbl.addEventListener('click', function(e){ if (e.target.classList.contains('tsm-cat-remove')) { e.target.closest('tr').remove(); recalc(); } });\n"
				. "\ttbl.addEventListener('input', recalc);\n"
				. "\tif (form) { form.addEventListener('submit', function(e){ if (sel.value !== 'weighted') { return; } var sum = 0; tbl.querySelectorAll('input[type=number]').forEach(function(i){ sum += parseFloat(i.value) || 0; }); var rounded = Math.round(sum * 100) / 100; if (Math.abs(rounded - 100) >= 0.01) { e.preventDefault(); alert('Weighted category percentages must total exactly 100% (currently ' + rounded + '%).'); totalEl.scrollIntoView({behavior:'smooth', block:'center'}); } }); }\n"
				. "\trecalc();\n"
				. "})();\n"
			);
			?>

			<div class="tsm-form-section">
				<div class="tsm-form-section-head">
					<span class="dashicons dashicons-portfolio"></span>
					<div>
						<h3><?php esc_html_e( 'Description & Standards', 'tradeschool-manager' ); ?></h3>
						<p><?php esc_html_e( 'Public description, passing threshold, and credit value for transcripts.', 'tradeschool-manager' ); ?></p>
					</div>
				</div>
				<div class="tsm-form-grid">
					<div class="tsm-field tsm-field-wide">
						<label><?php esc_html_e( 'Description', 'tradeschool-manager' ); ?></label>
						<textarea class="large-text" name="description" rows="3"><?php echo esc_textarea( $editing->description ?? '' ); ?></textarea>
					</div>
					<div class="tsm-field">
						<label><?php esc_html_e( 'Passing %', 'tradeschool-manager' ); ?></label>
						<input type="number" min="0" max="100" step="0.01" name="passing_pct" value="<?php echo esc_attr( $editing->passing_pct ?? 70 ); ?>">
						<p class="description"><?php esc_html_e( 'Minimum final percentage to pass.', 'tradeschool-manager' ); ?></p>
					</div>
					<div class="tsm-field">
						<label><?php esc_html_e( 'Credits', 'tradeschool-manager' ); ?></label>
						<input type="number" min="0" step="0.01" name="credits" value="<?php echo esc_attr( $editing->credits ?? 0 ); ?>">
						<p class="description"><?php esc_html_e( 'Credit hours / units awarded on transcripts.', 'tradeschool-manager' ); ?></p>
					</div>
				</div>
				<?php
				// Allow add-ons to inject extra rows. Captured into a table for compatibility with existing tradeschool_manager_after_class_form_fields hooks.
				ob_start();
				do_action( 'tradeschool_manager_after_class_form_fields', $editing );
				$class_extra = trim( ob_get_clean() );
				if ( '' !== $class_extra ) {
					echo '<table class="form-table tsm-extra-fields">' . $class_extra . '</table>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped by add-on filter.
				}
				?>
			</div>

			<div class="tsm-form-actions">
				<button class="button button-primary button-hero"><?php echo $editing ? esc_html__( 'Update Class', 'tradeschool-manager' ) : esc_html__( 'Save Class', 'tradeschool-manager' ); ?></button>
				<?php if ( $editing ) : ?><a class="button" href="<?php echo esc_url( admin_url( 'admin.php?page=tsm-classes&tab=all' ) ); ?>"><?php esc_html_e( 'Cancel', 'tradeschool-manager' ); ?></a><?php endif; ?>
			</div>
		</form>
		<?php endif; ?>

		<?php if ( $editing ) : ?>
			<h2><?php esc_html_e( 'Enroll Students', 'tradeschool-manager' ); ?></h2>
			<?php $enroll_locked = class_exists( 'TSM_License' ) && TSM_License::is_creation_locked();
			if ( $enroll_locked ) : TSM_License::render_lock_notice( __( 'class enrollments', 'tradeschool-manager' ) ); else : ?>
			<form method="post">
				<?php wp_nonce_field( 'tsm_enroll' ); ?>
				<input type="hidden" name="tsm_action" value="enroll">
				<input type="hidden" name="class_id" value="<?php echo esc_attr( $editing->id ); ?>">
				<select name="student_id" required>
					<option value=""><?php esc_html_e( ' -  Select student  - ', 'tradeschool-manager' ); ?></option>
					<?php foreach ( $students as $s ) : ?>
						<option value="<?php echo esc_attr( $s->id ); ?>"><?php echo esc_html( $s->last_name . ', ' . $s->first_name ); ?></option>
					<?php endforeach; ?>
				</select>
				<button class="button"><?php esc_html_e( 'Enroll', 'tradeschool-manager' ); ?></button>
			</form>
			<?php endif; ?>
			<table class="widefat striped" style="margin-top:10px;">
				<thead><tr><th><?php esc_html_e( 'Enrolled Students', 'tradeschool-manager' ); ?></th><th></th></tr></thead>
				<tbody>
					<?php foreach ( TSM_Classes::students_in_class( $editing->id ) as $s ) : ?>
						<tr>
							<td><?php echo esc_html( $s->last_name . ', ' . $s->first_name ); ?></td>
							<td><a class="button button-small" href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'tsm_action' => 'unenroll', 'class_id' => $editing->id, 'student_id' => $s->id ) ), 'tsm_unenroll_' . $editing->id . '_' . $s->id ) ); ?>"><?php esc_html_e( 'Remove', 'tradeschool-manager' ); ?></a></td>
						</tr>
					<?php endforeach; ?>
				</tbody>
			</table>
		<?php endif; ?>

		<?php endif; /* end add tab */ ?>

		<?php if ( 'all' === $tab ) : ?>
		<h2><?php esc_html_e( 'All Classes', 'tradeschool-manager' ); ?>
			<a href="<?php echo esc_url( admin_url( 'admin.php?page=tsm-classes&tab=add' ) ); ?>" class="page-title-action"><?php esc_html_e( 'Add New', 'tradeschool-manager' ); ?></a>
			<?php if ( $active_year ) : $ay = TSM_School_Years::get( $active_year ); if ( $ay ) : ?>
				<span class="description" style="font-size:13px;font-weight:400;margin-left:10px;">
					<?php /* translators: placeholder(s) for dynamic value(s). */ echo esc_html( sprintf( __( 'Showing year: %s  -  switch using the year selector in the top bar.', 'tradeschool-manager' ), $ay->name ) ); ?>
				</span>
			<?php endif; endif; ?>
		</h2>
		<table class="widefat striped">
			<thead><tr><th><?php esc_html_e( 'Name', 'tradeschool-manager' ); ?></th><th><?php esc_html_e( 'Year', 'tradeschool-manager' ); ?></th><th><?php esc_html_e( 'Method', 'tradeschool-manager' ); ?></th><th><?php esc_html_e( 'Status', 'tradeschool-manager' ); ?></th><th></th></tr></thead>
			<tbody>
			<?php foreach ( $classes as $c ) :
				$y = TSM_School_Years::get( $c->school_year_id );
				$is_completed = ( ( $c->status ?? '' ) === 'completed' ); ?>
				<tr>
					<td><?php echo esc_html( $c->name ); ?></td>
					<td><?php echo esc_html( $y->name ?? '' ); ?></td>
					<td><?php echo esc_html( $c->grading_method ); ?></td>
					<td><?php echo $is_completed ? '<span class="tsm-status-pill tsm-status-pill--done">' . esc_html__( 'Completed', 'tradeschool-manager' ) . '</span>' : '<span class="tsm-status-pill tsm-status-pill--active">' . esc_html( ucfirst( $c->status ?? 'active' ) ) . '</span>'; ?></td>
					<td>
						<div class="tsm-row-actions">
						<a class="button button-small" href="<?php echo esc_url( add_query_arg( array( 'page' => 'tsm-classes', 'edit' => $c->id ), admin_url( 'admin.php' ) ) ); ?>"><?php esc_html_e( 'Edit', 'tradeschool-manager' ); ?></a>
						<?php
						$in_program = class_exists( 'TSM_Programs' ) ? TSM_Programs::programs_for_class( $c->id ) : array();
						if ( ! $is_completed && empty( $in_program ) ) : ?>
							<a class="button button-small button-primary" href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'tsm_action' => 'complete_class', 'id' => $c->id ) ), 'tsm_complete_class_' . $c->id ) ); ?>" onclick="return confirm('<?php echo esc_attr__( 'Mark this class completed?', 'tradeschool-manager' ); ?>')"><?php esc_html_e( 'Mark Completed', 'tradeschool-manager' ); ?></a>
						<?php elseif ( ! $is_completed && ! empty( $in_program ) ) : ?>
							<span class="tsm-pill tsm-pill--muted" title="<?php esc_attr_e( 'This class is part of a program. Mark the program completed to complete its classes.', 'tradeschool-manager' ); ?>"><span class="dashicons dashicons-portfolio" aria-hidden="true"></span> <?php esc_html_e( 'In program', 'tradeschool-manager' ); ?></span>
						<?php endif; ?>
						<a class="button button-small button-link-delete" href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'tsm_action' => 'delete_class', 'id' => $c->id ) ), 'tsm_delete_class_' . $c->id ) ); ?>" onclick="return confirm('Delete?')"><?php esc_html_e( 'Delete', 'tradeschool-manager' ); ?></a>
						</div>
					</td>
				</tr>
			<?php endforeach; ?>
			<?php if ( empty( $classes ) ) : ?>
				<tr><td colspan="5"><?php esc_html_e( 'No classes yet.', 'tradeschool-manager' ); ?></td></tr>
			<?php endif; ?>
			</tbody>
		</table>
		<?php endif; ?>
		<?php
		$this->shell_close();
	}

	public function page_assignments() {
		$this->shell_open( __( 'Assignments', 'tradeschool-manager' ) );
		$active_year   = class_exists( 'TSM_Active_Year' ) ? TSM_Active_Year::get() : 0;
		$class_id      = isset( $_GET['class_id'] ) ? absint( $_GET['class_id'] ) : 0; // phpcs:ignore
		$year_filter   = isset( $_GET['year_id'] ) ? absint( $_GET['year_id'] ) : $active_year; // phpcs:ignore
		$edit_id       = isset( $_GET['edit'] ) ? absint( $_GET['edit'] ) : 0; // phpcs:ignore
		$tab           = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : ( $edit_id ? 'new' : 'current' ); // phpcs:ignore
		$valid_tabs    = array( 'new', 'current' );
		if ( ! in_array( $tab, $valid_tabs, true ) ) { $tab = 'current'; }
		$classes       = TSM_Classes::all();
		$years         = TSM_School_Years::all();
		$assign_locked = class_exists( 'TSM_License' ) && TSM_License::is_creation_locked();
		$editing       = $edit_id ? TSM_Assignments::get( $edit_id ) : null;
		if ( $editing && ! $class_id ) { $class_id = (int) $editing->class_id; }

		$archived = class_exists( 'TSM_School_Years' ) && TSM_School_Years::is_active_year_archived();
		if ( $archived && 'new' === $tab ) { $tab = 'current'; }

		$base_url = admin_url( 'admin.php?page=tsm-assignments' );
		$tab_url  = function( $t ) use ( $base_url ) { return add_query_arg( 'tab', $t, $base_url ); };
		$new_label = $editing ? __( 'Edit Assignment', 'tradeschool-manager' ) : __( 'New Assignment', 'tradeschool-manager' );
		?>
		<h2 class="nav-tab-wrapper" style="margin-top:6px;">
			<?php if ( $archived ) : ?>
				<span class="nav-tab nav-tab-disabled" aria-disabled="true" title="<?php esc_attr_e( 'Locked while viewing an archived school year. Switch to a current year in the topbar to make changes.', 'tradeschool-manager' ); ?>" style="opacity:.45;cursor:not-allowed;pointer-events:none;"><?php echo esc_html( $new_label ); ?> (Locked)</span>
			<?php else : ?>
				<a href="<?php echo esc_url( $tab_url( 'new' ) ); ?>" class="nav-tab <?php echo $tab === 'new' ? 'nav-tab-active' : ''; ?>"><?php echo esc_html( $new_label ); ?></a>
			<?php endif; ?>
			<a href="<?php echo esc_url( $tab_url( 'current' ) ); ?>" class="nav-tab <?php echo $tab === 'current' ? 'nav-tab-active' : ''; ?>"><?php esc_html_e( 'Current Assignments', 'tradeschool-manager' ); ?></a>
			<a href="<?php echo esc_url( add_query_arg( array( 'page' => 'tsm-csv', 'csv_type' => 'assignments' ), admin_url( 'admin.php' ) ) ); ?>" class="nav-tab"><?php esc_html_e( 'Import / Export CSV', 'tradeschool-manager' ); ?></a>
		</h2>
		<?php if ( $archived ) { $this->render_archived_notice( __( 'assignments', 'tradeschool-manager' ) ); } ?>

		<?php if ( 'new' === $tab && ! $archived ) :
			if ( $assign_locked && ! $editing ) {
				TSM_License::render_lock_notice( __( 'assignments', 'tradeschool-manager' ) );
			} else {
				$current_class = $class_id ? TSM_Classes::get( $class_id ) : null;
				?>
				<form method="post" class="tsm-entry-form" style="margin-top:14px;">
					<?php wp_nonce_field( 'tsm_save_assignment' ); ?>
					<input type="hidden" name="tsm_action" value="save_assignment">
					<?php if ( $editing ) : ?>
						<input type="hidden" name="id" value="<?php echo esc_attr( $editing->id ); ?>">
					<?php endif; ?>

					<div class="tsm-form-section">
						<div class="tsm-form-section-head">
							<span class="dashicons dashicons-welcome-write-blog"></span>
							<div>
								<h3><?php esc_html_e( 'Assignment Details', 'tradeschool-manager' ); ?></h3>
								<p><?php esc_html_e( 'Where this assignment lives in the gradebook and what it asks students to do.', 'tradeschool-manager' ); ?></p>
							</div>
						</div>
						<div class="tsm-form-grid">
							<div class="tsm-field">
								<label><?php esc_html_e( 'Class', 'tradeschool-manager' ); ?> <span class="tsm-req">*</span></label>
								<select name="class_id" required onchange="document.getElementById('tsm-assn-syid').value = this.options[this.selectedIndex].getAttribute('data-year') || 0;">
									<option value=""><?php esc_html_e( ' -  Select class  - ', 'tradeschool-manager' ); ?></option>
									<?php foreach ( $classes as $c ) : ?>
										<option value="<?php echo esc_attr( $c->id ); ?>" data-year="<?php echo esc_attr( $c->school_year_id ); ?>" <?php selected( $class_id, $c->id ); ?>><?php echo esc_html( $c->name ); ?></option>
									<?php endforeach; ?>
								</select>
								<input type="hidden" id="tsm-assn-syid" name="school_year_id" value="<?php echo esc_attr( $current_class->school_year_id ?? 0 ); ?>">
							</div>
							<div class="tsm-field">
								<label><?php esc_html_e( 'Title', 'tradeschool-manager' ); ?> <span class="tsm-req">*</span></label>
								<input required class="regular-text" name="title" value="<?php echo esc_attr( $editing->title ?? '' ); ?>">
							</div>
							<?php
							$class_cats = $class_id ? TSM_Classes::categories( $class_id ) : array();
							$cats_map = array();
							$pf_map   = array();
							foreach ( $classes as $c ) {
								$cats_map[ (int) $c->id ] = TSM_Classes::categories( $c->id );
								$pf_map[ (int) $c->id ]   = ( 'pass_fail' === $c->grading_method );
							}
							$cur_class = $class_id ? TSM_Classes::get( $class_id ) : null;
							$is_pf_cls = $cur_class && 'pass_fail' === $cur_class->grading_method;
							?>
							<div class="tsm-field tsm-field-wide tsm-assn-category-row" style="<?php echo $is_pf_cls ? 'display:none;' : ''; ?>">
								<label><?php esc_html_e( 'Category', 'tradeschool-manager' ); ?></label>
								<select name="category" id="tsm-assn-category-select" style="<?php echo empty( $class_cats ) ? 'display:none;' : ''; ?>">
									<option value=""><?php esc_html_e( ' -  Uncategorized  - ', 'tradeschool-manager' ); ?></option>
									<?php foreach ( $class_cats as $cat_name ) : ?>
										<option value="<?php echo esc_attr( $cat_name ); ?>" <?php selected( $editing->category ?? '', $cat_name ); ?>><?php echo esc_html( $cat_name ); ?></option>
									<?php endforeach; ?>
								</select>
								<input type="text" id="tsm-assn-category-free" class="regular-text" name="category_free" placeholder="<?php esc_attr_e( 'e.g. Homework (optional)', 'tradeschool-manager' ); ?>" value="<?php echo esc_attr( $editing && empty( $class_cats ) ? ( $editing->category ?? '' ) : '' ); ?>" style="<?php echo ! empty( $class_cats ) ? 'display:none;' : ''; ?>">
								<p class="description" id="tsm-assn-category-hint"><?php echo empty( $class_cats ) ? esc_html__( 'Tip: set Grading Method to "Weighted Categories" on the class to pick from a dropdown here.', 'tradeschool-manager' ) : ''; ?></p>
							</div>
							<div class="tsm-field tsm-field-wide">
								<label><?php esc_html_e( 'Description', 'tradeschool-manager' ); ?></label>
								<textarea class="large-text" name="description" rows="3"><?php echo esc_textarea( $editing->description ?? '' ); ?></textarea>
							</div>
						</div>
					</div>
					<?php
					TSM_Inline_Assets::script(
						'tsm-assignment-category',
						"(function(){\n"
						. "\tvar map = " . wp_json_encode( $cats_map ) . ";\n"
						. "\tvar pf = " . wp_json_encode( $pf_map ) . ";\n"
						. "\tvar uncategorized = " . wp_json_encode( __( ' -  Uncategorized  - ', 'tradeschool-manager' ) ) . ";\n"
						. "\tvar hintText = " . wp_json_encode( __( 'Tip: set Grading Method to \"Weighted Categories\" on the class to pick from a dropdown here.', 'tradeschool-manager' ) ) . ";\n"
						. "\tvar classSel = document.querySelector('select[name=\"class_id\"]');\n"
						. "\tvar catRow = document.querySelector('.tsm-assn-category-row');\n"
						. "\tvar catSel = document.getElementById('tsm-assn-category-select');\n"
						. "\tvar catFree = document.getElementById('tsm-assn-category-free');\n"
						. "\tvar hint = document.getElementById('tsm-assn-category-hint');\n"
						. "\tfunction applyPF(isPF){ if (catRow) { catRow.style.display = isPF ? 'none' : ''; } var pts = document.getElementById('tsm-assn-points-row-pts'); var ec = document.getElementById('tsm-assn-points-row-ec'); var pfNote = document.getElementById('tsm-assn-pf-note'); if (pts) { pts.style.display = isPF ? 'none' : ''; } if (ec) { ec.style.display = isPF ? 'none' : ''; } if (pfNote) { pfNote.style.display = isPF ? '' : 'none'; } }\n"
						. "\tif (!classSel || !catSel || !catFree) { return; }\n"
						. "\tclassSel.addEventListener('change', function(){ var cid = parseInt(classSel.value, 10) || 0; applyPF(!!pf[cid]); var cats = (map[cid] && map[cid].length) ? map[cid] : []; if (cats.length) { catSel.textContent = ''; var blank = document.createElement('option'); blank.value = ''; blank.textContent = uncategorized; catSel.appendChild(blank); cats.forEach(function(n){ var option = document.createElement('option'); option.value = n; option.textContent = n; catSel.appendChild(option); }); catSel.style.display = ''; catFree.style.display = 'none'; catFree.value = ''; if (hint) { hint.textContent = ''; } } else { catSel.style.display = 'none'; catFree.style.display = ''; if (hint) { hint.textContent = hintText; } } });\n"
						. "})();\n"
					);
					?>

					<div id="tsm-assn-pf-note" class="notice notice-info inline" style="<?php echo $is_pf_cls ? '' : 'display:none;'; ?>margin:14px 0;">
						<p style="margin:6px 0;"><span class="dashicons dashicons-yes-alt" style="color:#2271b1;"></span>
							<?php esc_html_e( 'This is a Pass/Fail class  -  points and categories don\'t apply. Mark Pass or Fail per student on the gradebook screen.', 'tradeschool-manager' ); ?>
						</p>
					</div>

					<div class="tsm-form-section">
						<div class="tsm-form-section-head">
							<span class="dashicons dashicons-calendar-alt"></span>
							<div>
								<h3><?php esc_html_e( 'Schedule & Scoring', 'tradeschool-manager' ); ?></h3>
								<p><?php esc_html_e( 'When the assignment is given, when it\'s due, and (for points/weighted classes) how many points it\'s worth.', 'tradeschool-manager' ); ?></p>
							</div>
						</div>
						<div class="tsm-form-grid">
							<div class="tsm-field">
								<label><?php esc_html_e( 'Assigned Date', 'tradeschool-manager' ); ?></label>
								<input type="text" name="assigned_date" value="<?php echo esc_attr( TSM_Date_Utils::display( $editing->assigned_date ?? '', '' ) ); ?>" placeholder="MM/DD/YYYY">
							</div>
							<div class="tsm-field">
								<label><?php esc_html_e( 'Due Date', 'tradeschool-manager' ); ?></label>
								<input type="text" name="due_date" value="<?php echo esc_attr( TSM_Date_Utils::display( $editing->due_date ?? '', '' ) ); ?>" placeholder="MM/DD/YYYY">
							</div>
							<div class="tsm-field" id="tsm-assn-points-row-pts" style="<?php echo $is_pf_cls ? 'display:none;' : ''; ?>">
								<label><?php esc_html_e( 'Points Possible', 'tradeschool-manager' ); ?></label>
								<input type="number" step="0.01" name="points_possible" value="<?php echo esc_attr( $editing->points_possible ?? 100 ); ?>">
							</div>
							<div class="tsm-field" id="tsm-assn-points-row-ec" style="<?php echo $is_pf_cls ? 'display:none;' : ''; ?>">
								<label><?php esc_html_e( 'Extra Credit', 'tradeschool-manager' ); ?></label>
								<label style="font-weight:400;text-transform:none;letter-spacing:0;font-size:13px;"><input type="checkbox" name="extra_credit" value="1" <?php checked( ! empty( $editing->extra_credit ) ); ?>> <?php esc_html_e( 'Extra credit (does not add to points possible)', 'tradeschool-manager' ); ?></label>
								<input type="hidden" name="required" value="1">
							</div>
						</div>
					</div>


					<div class="tsm-form-actions">
						<button class="button button-primary button-hero"><?php echo $editing ? esc_html__( 'Update Assignment', 'tradeschool-manager' ) : esc_html__( 'Save Assignment', 'tradeschool-manager' ); ?></button>
						<?php if ( $editing ) : ?>
							<a class="button" href="<?php echo esc_url( $tab_url( 'current' ) ); ?>"><?php esc_html_e( 'Cancel', 'tradeschool-manager' ); ?></a>
						<?php endif; ?>
					</div>
				</form>
			<?php } ?>

		<?php elseif ( 'current' === $tab ) :
			// Year is fixed by the global topbar switcher  -  no per-page year picker.
			$year_filter = (int) $active_year;
			?>
			<form method="get" style="margin:14px 0;display:flex;gap:10px;align-items:flex-end;flex-wrap:wrap;">
				<input type="hidden" name="page" value="tsm-assignments">
				<input type="hidden" name="tab" value="current">
				<label style="display:flex;flex-direction:column;font-weight:600;font-size:12px;">
					<?php esc_html_e( 'Class', 'tradeschool-manager' ); ?>
					<select name="class_id" onchange="this.form.submit()" style="min-width:240px;">
						<option value="0"><?php esc_html_e( ' -  All classes  - ', 'tradeschool-manager' ); ?></option>
						<?php foreach ( $classes as $c ) :
							if ( $year_filter && (int) $c->school_year_id !== $year_filter ) { continue; } ?>
							<option value="<?php echo esc_attr( $c->id ); ?>" <?php selected( $class_id, $c->id ); ?>><?php echo esc_html( $c->name ); ?></option>
						<?php endforeach; ?>
					</select>
				</label>
			</form>
			<?php
			$rows = TSM_Assignments::all( $class_id, $year_filter );
			$class_lookup = array();
			foreach ( $classes as $c ) { $class_lookup[ (int) $c->id ] = $c->name; }
			?>
			<table class="widefat striped">
				<thead>
					<tr>
						<th><?php esc_html_e( 'Title', 'tradeschool-manager' ); ?></th>
						<th><?php esc_html_e( 'Class', 'tradeschool-manager' ); ?></th>
						<th><?php esc_html_e( 'Category', 'tradeschool-manager' ); ?></th>
						<th><?php esc_html_e( 'Due', 'tradeschool-manager' ); ?></th>
						<th><?php esc_html_e( 'Points', 'tradeschool-manager' ); ?></th>
						<th></th>
					</tr>
				</thead>
				<tbody>
				<?php foreach ( $rows as $a ) : ?>
					<tr>
						<td><strong><?php echo esc_html( $a->title ); ?></strong></td>
						<td><?php echo esc_html( $class_lookup[ (int) $a->class_id ] ?? ( '#' . (int) $a->class_id ) ); ?></td>
						<td><?php echo esc_html( $a->category ); ?></td>
						<td><?php echo esc_html( TSM_Date_Utils::display( $a->due_date ) ); ?></td>
						<td><?php echo esc_html( $a->points_possible ); ?><?php echo $a->extra_credit ? ' EC' : ''; ?></td>
						<td>
							<a class="button button-small button-primary" href="<?php echo esc_url( add_query_arg( array( 'page' => 'tsm-gradebook', 'class_id' => $a->class_id, 'assignment_id' => $a->id ), admin_url( 'admin.php' ) ) ); ?>"><?php esc_html_e( 'Enter Grades', 'tradeschool-manager' ); ?></a>
							<a class="button button-small" href="<?php echo esc_url( add_query_arg( array( 'page' => 'tsm-assignments', 'tab' => 'new', 'edit' => $a->id ), admin_url( 'admin.php' ) ) ); ?>"><?php esc_html_e( 'Edit', 'tradeschool-manager' ); ?></a>
							<a class="button button-small" href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'tsm_action' => 'delete_assignment', 'id' => $a->id ) ), 'tsm_delete_assignment_' . $a->id ) ); ?>" onclick="return confirm('Delete?')"><?php esc_html_e( 'Delete', 'tradeschool-manager' ); ?></a>
						</td>
					</tr>
				<?php endforeach; ?>
				<?php if ( empty( $rows ) ) : ?>
					<tr><td colspan="6"><?php esc_html_e( 'No assignments found. Create one in the New Assignment tab.', 'tradeschool-manager' ); ?></td></tr>
				<?php endif; ?>
				</tbody>
			</table>

		<?php endif;
		$this->shell_close();
	}


	public function page_gradebook() {
		$this->shell_open( __( 'Gradebook', 'tradeschool-manager' ) );

		// Per-assignment grade entry view (matches teacher portal layout).
		$assignment_id = isset( $_GET['assignment_id'] ) ? absint( $_GET['assignment_id'] ) : 0; // phpcs:ignore
		if ( $assignment_id ) {
			$this->render_assignment_grading( $assignment_id );
			$this->shell_close();
			return;
		}

		$active_year = class_exists( 'TSM_Active_Year' ) ? TSM_Active_Year::get() : 0;
		$year_id  = (int) $active_year; // Year is fixed by the global topbar switcher.
		$class_id = isset( $_GET['class_id'] ) ? absint( $_GET['class_id'] ) : 0; // phpcs:ignore
		$tab      = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'gradebook'; // phpcs:ignore
		$tabs     = array(
			'gradebook' => __( 'Gradebook', 'tradeschool-manager' ),
			'reports'   => __( 'Class Reports', 'tradeschool-manager' ),
		);
		if ( ! isset( $tabs[ $tab ] ) ) {
			$tab = 'gradebook';
		}

		$this->render_tabs( 'tsm-gradebook', $tabs, $tab );

		if ( 'reports' === $tab ) {
			$this->render_core_class_report( 'tsm-gradebook' );
			$this->shell_close();
			return;
		}

		$classes = TSM_Classes::all( $year_id );
		if ( $class_id && $year_id ) {
			$valid = false;
			foreach ( $classes as $c ) { if ( (int) $c->id === $class_id ) { $valid = true; break; } }
			if ( ! $valid ) { $class_id = 0; }
		}
		?>
		<form method="get" class="tsm-gradebook-filters" style="display:flex;gap:14px;align-items:flex-end;flex-wrap:wrap;margin:6px 0 16px;">
			<input type="hidden" name="page" value="tsm-gradebook">
			<label style="display:flex;flex-direction:column;font-weight:600;font-size:12px;">
				<?php esc_html_e( 'Class', 'tradeschool-manager' ); ?>
				<select name="class_id" onchange="this.form.submit()" style="min-width:240px;">
					<option value="0"><?php esc_html_e( ' -  Select class  - ', 'tradeschool-manager' ); ?></option>
					<?php foreach ( $classes as $c ) : ?>
						<option value="<?php echo esc_attr( $c->id ); ?>" <?php selected( $class_id, $c->id ); ?>><?php echo esc_html( $c->name ); ?></option>
					<?php endforeach; ?>
				</select>
			</label>
		</form>
		<?php if ( ! $class_id ) : ?>
			<p class="description"><?php esc_html_e( 'Select a class to view the gradebook. Switch the active school year in the topbar to load a different year\'s classes. Click any assignment to enter grades in a per-assignment view.', 'tradeschool-manager' ); ?></p>
		<?php else :
			$assignments = TSM_Assignments::for_class( $class_id );
			$students    = TSM_Classes::students_in_class( $class_id );
			$class_obj   = TSM_Classes::get( $class_id );
			?>
			<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;flex-wrap:wrap;gap:8px;">
				<div><strong><?php echo esc_html( $class_obj ? $class_obj->name : '' ); ?></strong>
					<span class="description" style="margin-left:8px;"><?php echo (int) count( $students ); ?> <?php esc_html_e( 'students', 'tradeschool-manager' ); ?> | <?php echo (int) count( $assignments ); ?> <?php esc_html_e( 'assignments', 'tradeschool-manager' ); ?></span></div>
				<div><a class="button" href="<?php echo esc_url( add_query_arg( array( 'page' => 'tsm-assignments', 'class_id' => $class_id ), admin_url( 'admin.php' ) ) ); ?>"><?php esc_html_e( 'Manage assignments', 'tradeschool-manager' ); ?></a></div>
			</div>
			<?php if ( empty( $assignments ) ) : ?>
				<div class="notice notice-info inline"><p><?php esc_html_e( 'No assignments yet for this class. Add one first to start entering grades.', 'tradeschool-manager' ); ?></p></div>
			<?php else : ?>
				<table class="widefat striped tsm-gradebook-overview">
					<thead>
						<tr>
							<th><?php esc_html_e( 'Assignment', 'tradeschool-manager' ); ?></th>
							<th><?php esc_html_e( 'Due', 'tradeschool-manager' ); ?></th>
							<th><?php esc_html_e( 'Type', 'tradeschool-manager' ); ?></th>
							<th style="text-align:right;"><?php esc_html_e( 'Value', 'tradeschool-manager' ); ?></th>
							<th style="text-align:right;"><?php esc_html_e( 'Graded', 'tradeschool-manager' ); ?></th>
							<th style="text-align:right;"><?php esc_html_e( 'Avg Grade', 'tradeschool-manager' ); ?></th>
							<th></th>
						</tr>
					</thead>
					<tbody>
					<?php foreach ( $assignments as $a ) :
						$sum_pct = 0; $n_pct = 0;
						foreach ( $students as $s ) {
							$g = TSM_Gradebook::get_grade( $a->id, $s->id );
							$total = TSM_Gradebook::total_for( $g );
							if ( null === $total || (float) $a->points_possible <= 0 ) { continue; }
							$sum_pct += ( $total / (float) $a->points_possible ) * 100;
							$n_pct++;
						}
						$avg = $n_pct ? round( $sum_pct / $n_pct, 2 ) : null;
						$enter_url = add_query_arg( array( 'page' => 'tsm-gradebook', 'assignment_id' => $a->id ), admin_url( 'admin.php' ) );
						?>
						<tr>
							<td><a href="<?php echo esc_url( $enter_url ); ?>"><strong><?php echo esc_html( $a->title ); ?></strong></a><?php if ( $a->category ) : ?><br><small class="description"><?php echo esc_html( $a->category ); ?></small><?php endif; ?></td>
							<td><?php echo esc_html( TSM_Date_Utils::display( $a->due_date ?? '' ) ); ?></td>
							<td><?php echo $a->extra_credit ? esc_html__( 'Extra Credit', 'tradeschool-manager' ) : esc_html__( 'Standard', 'tradeschool-manager' ); ?></td>
							<td style="text-align:right;"><?php echo esc_html( rtrim( rtrim( number_format( (float) $a->points_possible, 2 ), '0' ), '.' ) ); ?></td>
							<td style="text-align:right;"><?php echo (int) $n_pct; ?> / <?php echo (int) count( $students ); ?></td>
							<td style="text-align:right;"><?php echo $avg !== null ? esc_html( $avg . '%' ) : ' - '; ?></td>
							<td style="text-align:right;"><a class="button button-primary button-small" href="<?php echo esc_url( $enter_url ); ?>"><?php esc_html_e( 'Enter Grades', 'tradeschool-manager' ); ?></a></td>
						</tr>
					<?php endforeach; ?>
					</tbody>
				</table>
			<?php endif; ?>

		<?php endif;
		$this->shell_close();
	}

	/**
	 * Simplified per-assignment grade entry for Pass/Fail classes.
	 * Hides points/bonus/penalty  -  only result + submitted + comments.
	 */
	private function render_pass_fail_grade_entry( $assignment, $students, $class_yr_archived, $back_url ) {
		// Result picker maps to canonical statuses on submit (no schema change).
		$results = array(
			'ungraded'         => __( ' - ',       'tradeschool-manager' ),
			'completed_ontime' => __( 'Pass',     'tradeschool-manager' ),
			'completed_late'   => __( 'Pass (Late)', 'tradeschool-manager' ),
			'missing'          => __( 'Fail',     'tradeschool-manager' ),
			'excused'          => __( 'Excused',  'tradeschool-manager' ),
			'excluded'         => __( 'Excluded', 'tradeschool-manager' ),
		);
		?>
		<p class="description" style="margin:0 0 12px;">
			<span class="dashicons dashicons-yes-alt" style="color:#2271b1;"></span>
			<?php esc_html_e( 'This class is graded Pass / Fail. Mark each student\'s result  -  no point values needed.', 'tradeschool-manager' ); ?>
		</p>
		<form method="post" class="tsm-grade-entry-form">
			<?php wp_nonce_field( 'tsm_save_grades' ); ?>
			<input type="hidden" name="tsm_action" value="save_grades">
			<input type="hidden" name="class_id" value="<?php echo (int) $assignment->class_id; ?>">
			<input type="hidden" name="assignment_id" value="<?php echo (int) $assignment->id; ?>">
			<fieldset <?php disabled( $class_yr_archived ); ?> style="border:0;padding:0;margin:0;<?php echo $class_yr_archived ? 'opacity:.7;' : ''; ?>">
			<div class="tsm-grade-entry-scroll">
				<table class="widefat striped tsm-grade-entry tsm-grade-entry-pf">
					<thead><tr>
						<th style="width:30%;"><?php esc_html_e( 'Student', 'tradeschool-manager' ); ?></th>
						<th style="width:22%;"><?php esc_html_e( 'Result', 'tradeschool-manager' ); ?></th>
						<th style="width:18%;"><?php esc_html_e( 'Submitted', 'tradeschool-manager' ); ?></th>
						<th><?php esc_html_e( 'Comments', 'tradeschool-manager' ); ?></th>
					</tr></thead>
					<tbody>
					<?php foreach ( $students as $s ) :
						$g  = TSM_Gradebook::get_grade( $assignment->id, $s->id );
						$st = $g ? (string) $g->status : '';
						if ( '' === $st ) { $st = 'ungraded'; }
						?>
						<tr class="tsm-grade-row-pf" data-status="<?php echo esc_attr( $st ); ?>">
							<td data-label="<?php esc_attr_e( 'Student', 'tradeschool-manager' ); ?>"><strong><?php echo esc_html( $s->last_name . ', ' . $s->first_name ); ?></strong></td>
							<td data-label="<?php esc_attr_e( 'Result', 'tradeschool-manager' ); ?>">
								<select name="grades[<?php echo (int) $s->id; ?>][status]" class="tsm-ge-pf-status" <?php disabled( $class_yr_archived ); ?>>
									<?php foreach ( $results as $k => $lbl ) : ?>
										<option value="<?php echo esc_attr( $k ); ?>" <?php selected( $st, $k ); ?>><?php echo esc_html( $lbl ); ?></option>
									<?php endforeach; ?>
								</select>
							</td>
							<td data-label="<?php esc_attr_e( 'Submitted', 'tradeschool-manager' ); ?>">
								<input type="text" name="grades[<?php echo (int) $s->id; ?>][submitted_date]" value="<?php echo esc_attr( TSM_Date_Utils::display( $g->submitted_date ?? '', '' ) ); ?>" placeholder="MM/DD/YYYY">
							</td>
							<td data-label="<?php esc_attr_e( 'Comments', 'tradeschool-manager' ); ?>">
								<input type="text" name="grades[<?php echo (int) $s->id; ?>][comments]" value="<?php echo esc_attr( $g->comments ?? '' ); ?>" placeholder="<?php esc_attr_e( 'Optional', 'tradeschool-manager' ); ?>" style="width:100%;">
							</td>
						</tr>
					<?php endforeach; ?>
					</tbody>
				</table>
			</div>
			</fieldset>
			<p style="margin-top:14px;">
				<button class="button button-primary button-large" <?php disabled( $class_yr_archived ); ?>><?php esc_html_e( 'Save Results', 'tradeschool-manager' ); ?></button>
				<a class="button" href="<?php echo esc_url( $back_url ); ?>"><?php esc_html_e( 'Cancel', 'tradeschool-manager' ); ?></a>
			</p>
		</form>
		<?php
		TSM_Inline_Assets::script(
			'tsm-pass-fail-grade-entry',
			"(function(){\n"
			. "\tfunction fmt(d){ var p = function(n){ return (n < 10 ? '0' : '') + n; }; return p(d.getMonth() + 1) + '/' + p(d.getDate()) + '/' + d.getFullYear(); }\n"
			. "\tfunction todayDisplay(){ return fmt(new Date()); }\n"
			. "\tdocument.querySelectorAll('.tsm-grade-row-pf').forEach(function(row){ var sel = row.querySelector('.tsm-ge-pf-status'); var sub = row.querySelector('input[name*=\"[submitted_date]\"]'); if (!sel || !sub) { return; } sel.addEventListener('change', function(){ row.dataset.status = sel.value; if (['completed_ontime','completed_late','missing'].indexOf(sel.value) >= 0 && !sub.value) { sub.value = todayDisplay(); } else if (['ungraded','excluded'].indexOf(sel.value) >= 0) { sub.value = ''; } }); });\n"
			. "})();\n"
		);
	}

	/**
	 * Per-assignment grade entry table (admin).
	 */
	private function render_assignment_grading( $assignment_id ) {
		$assignment = TSM_Assignments::get( $assignment_id );
		if ( ! $assignment ) {
			echo '<div class="notice notice-error"><p>' . esc_html__( 'Assignment not found.', 'tradeschool-manager' ) . '</p></div>';
			return;
		}
		$class    = TSM_Classes::get( $assignment->class_id );
		$students = TSM_Classes::students_in_class( $assignment->class_id );
		$statuses = TSM_Gradebook::statuses();
		unset( $statuses[''] );
		$back_url = add_query_arg( array( 'page' => 'tsm-gradebook', 'class_id' => $assignment->class_id ), admin_url( 'admin.php' ) );
		?>
		<p style="margin:6px 0 14px;"><a href="<?php echo esc_url( $back_url ); ?>">&larr; <?php esc_html_e( 'Back to gradebook', 'tradeschool-manager' ); ?></a></p>
		<div class="tsm-assn-head" style="display:flex;justify-content:space-between;align-items:flex-end;flex-wrap:wrap;gap:10px;margin-bottom:10px;">
			<div>
				<h2 style="margin:0;"><?php echo esc_html( $assignment->title ); ?></h2>
				<p class="description" style="margin:4px 0 0;">
					<?php echo esc_html( $class ? $class->name : '' ); ?>
					<?php if ( ! empty( $assignment->category ) ) : ?> | <?php echo esc_html( $assignment->category ); ?><?php endif; ?>
					| <?php echo esc_html( $assignment->points_possible ); ?> <?php esc_html_e( 'pts', 'tradeschool-manager' ); ?>
					<?php if ( ! empty( $assignment->extra_credit ) ) : ?> | <?php esc_html_e( 'Extra credit', 'tradeschool-manager' ); ?><?php endif; ?>
					<?php if ( $assignment->due_date ) : ?> | <?php esc_html_e( 'Due', 'tradeschool-manager' ); ?> <?php echo esc_html( TSM_Date_Utils::display( $assignment->due_date ) ); ?><?php endif; ?>
				</p>
			</div>
			<a class="button" href="<?php echo esc_url( add_query_arg( array( 'page' => 'tsm-assignments', 'tab' => 'new', 'edit' => $assignment->id ), admin_url( 'admin.php' ) ) ); ?>"><?php esc_html_e( 'Edit Assignment', 'tradeschool-manager' ); ?></a>
		</div>
		<?php
		$class_yr_archived = $class && class_exists( 'TSM_School_Years' ) && TSM_School_Years::is_archived( (int) $class->school_year_id );
		if ( $class_yr_archived ) { $this->render_archived_notice( __( 'grades', 'tradeschool-manager' ) ); }
		$is_pf = $class && 'pass_fail' === $class->grading_method;
		?>
		<?php if ( empty( $students ) ) : ?>
			<div class="notice notice-info inline"><p><?php esc_html_e( 'No active students enrolled in this class yet.', 'tradeschool-manager' ); ?></p></div>
			<?php return; endif; ?>
		<?php if ( $is_pf ) : $this->render_pass_fail_grade_entry( $assignment, $students, $class_yr_archived, $back_url ); return; endif; ?>
		<form method="post" class="tsm-grade-entry-form">
			<?php wp_nonce_field( 'tsm_save_grades' ); ?>
			<input type="hidden" name="tsm_action" value="save_grades">
			<input type="hidden" name="class_id" value="<?php echo (int) $assignment->class_id; ?>">
			<input type="hidden" name="assignment_id" value="<?php echo (int) $assignment->id; ?>">
			<fieldset <?php disabled( $class_yr_archived ); ?> style="border:0;padding:0;margin:0;<?php echo $class_yr_archived ? 'opacity:.7;' : ''; ?>">
			<div class="tsm-grade-entry-scroll">
				<table class="widefat striped tsm-grade-entry" data-points="<?php echo esc_attr( $assignment->points_possible ); ?>" data-due="<?php echo esc_attr( $assignment->due_date ?? '' ); ?>">
					<thead><tr>
						<th><?php esc_html_e( 'Student', 'tradeschool-manager' ); ?></th>
						<th><?php esc_html_e( 'Status', 'tradeschool-manager' ); ?></th>
						<th><?php esc_html_e( 'Submitted', 'tradeschool-manager' ); ?></th>
						<th><?php esc_html_e( 'Raw Score', 'tradeschool-manager' ); ?></th>
						<th><?php esc_html_e( 'Bonus', 'tradeschool-manager' ); ?></th>
						<th><?php esc_html_e( 'Penalty %', 'tradeschool-manager' ); ?></th>
						<th><?php esc_html_e( 'Total', 'tradeschool-manager' ); ?></th>
						<th><?php esc_html_e( 'Grade', 'tradeschool-manager' ); ?></th>
						<th><?php esc_html_e( 'Counts', 'tradeschool-manager' ); ?></th>
						<th><?php esc_html_e( 'Comments', 'tradeschool-manager' ); ?></th>
					</tr></thead>
					<tbody>
					<?php foreach ( $students as $s ) :
						$g  = TSM_Gradebook::get_grade( $assignment->id, $s->id );
						$st = $g ? (string) $g->status : ''; ?>
						<tr id="student-<?php echo (int) $s->id; ?>" class="tsm-grade-row" data-status="<?php echo esc_attr( $st ?: 'ungraded' ); ?>">
							<td data-label="<?php esc_attr_e( 'Student', 'tradeschool-manager' ); ?>"><?php echo esc_html( $s->last_name . ', ' . $s->first_name ); ?></td>
							<td data-label="<?php esc_attr_e( 'Status', 'tradeschool-manager' ); ?>">
								<select name="grades[<?php echo (int) $s->id; ?>][status]" class="tsm-ge-status" <?php disabled( $class_yr_archived ); ?>>
									<?php foreach ( $statuses as $k => $v ) : ?>
										<option value="<?php echo esc_attr( $k ); ?>" <?php selected( $st ?: 'ungraded', $k ); ?>><?php echo esc_html( $v['label'] ); ?></option>
									<?php endforeach; ?>
								</select>
							</td>
							<td data-label="<?php esc_attr_e( 'Submitted', 'tradeschool-manager' ); ?>"><input type="text" class="tsm-ge-submitted" name="grades[<?php echo (int) $s->id; ?>][submitted_date]" value="<?php echo esc_attr( TSM_Date_Utils::display( $g->submitted_date ?? '', '' ) ); ?>" placeholder="MM/DD/YYYY"></td>
							<td data-label="<?php esc_attr_e( 'Raw Score', 'tradeschool-manager' ); ?>"><input type="number" step="0.01" class="tsm-ge-raw" name="grades[<?php echo (int) $s->id; ?>][points_earned]" value="<?php echo esc_attr( $g->points_earned ?? '' ); ?>" placeholder="0"></td>
							<td data-label="<?php esc_attr_e( 'Bonus', 'tradeschool-manager' ); ?>"><input type="number" step="0.01" class="tsm-ge-bonus" name="grades[<?php echo (int) $s->id; ?>][bonus]" value="<?php echo esc_attr( $g->bonus ?? '' ); ?>"></td>
							<td data-label="<?php esc_attr_e( 'Penalty %', 'tradeschool-manager' ); ?>"><input type="number" step="0.01" min="0" max="100" class="tsm-ge-lp" name="grades[<?php echo (int) $s->id; ?>][late_penalty]" value="<?php echo esc_attr( $g->late_penalty ?? '' ); ?>"></td>
							<td data-label="<?php esc_attr_e( 'Total', 'tradeschool-manager' ); ?>" class="tsm-ge-total"> - </td>
							<td data-label="<?php esc_attr_e( 'Grade', 'tradeschool-manager' ); ?>" class="tsm-ge-pct"> - </td>
							<td data-label="<?php esc_attr_e( 'Counts', 'tradeschool-manager' ); ?>" class="tsm-ge-counts"> - </td>
							<td data-label="<?php esc_attr_e( 'Comments', 'tradeschool-manager' ); ?>"><input type="text" class="tsm-ge-comments" name="grades[<?php echo (int) $s->id; ?>][comments]" value="<?php echo esc_attr( $g->comments ?? '' ); ?>" placeholder="<?php esc_attr_e( 'Optional', 'tradeschool-manager' ); ?>"></td>
						</tr>
					<?php endforeach; ?>
					</tbody>
				</table>
			</div>
			</fieldset>
			<p style="margin-top:14px;"><button class="button button-primary button-large" <?php disabled( $class_yr_archived ); ?>><?php esc_html_e( 'Save Grades', 'tradeschool-manager' ); ?></button>
				<a class="button" href="<?php echo esc_url( $back_url ); ?>"><?php esc_html_e( 'Cancel', 'tradeschool-manager' ); ?></a></p>
		</form>
		<?php
		$grade_meta = array_map(
			function( $v ) {
				return array(
					'counts' => $v['counts'],
					'zero'   => $v['zero'],
				);
			},
			$statuses
		);
		TSM_Inline_Assets::script(
			'tsm-grade-entry',
			"(function(){\n"
			. "\tvar META = " . wp_json_encode( $grade_meta ) . ";\n"
			. "\tvar TABLE = document.querySelector('.tsm-grade-entry');\n"
			. "\tif (!TABLE) { return; }\n"
			. "\tvar POSSIBLE = parseFloat(TABLE.dataset.points) || 0;\n"
			. "\tvar DUE = TABLE.dataset.due || '';\n"
			. "\tvar YES = " . wp_json_encode( __( 'Yes', 'tradeschool-manager' ) ) . ";\n"
			. "\tvar NO = " . wp_json_encode( __( 'No', 'tradeschool-manager' ) ) . ";\n"
			. "\tfunction fmt(d){ var p = function(n){ return (n < 10 ? '0' : '') + n; }; return p(d.getMonth() + 1) + '/' + p(d.getDate()) + '/' + d.getFullYear(); }\n"
			. "\tfunction todayDisplay(){ return fmt(new Date()); }\n"
			. "\tfunction displayYmd(v){ var m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(v || ''); return m ? m[2] + '/' + m[3] + '/' + m[1] : v; }\n"
			. "\tfunction recalc(row){ var statusEl = row.querySelector('.tsm-ge-status'); if (!statusEl) { return; } var st = statusEl.value; var m = META[st] || { counts: false, zero: false }; var rawEl = row.querySelector('.tsm-ge-raw'); var bonusEl = row.querySelector('.tsm-ge-bonus'); var penaltyEl = row.querySelector('.tsm-ge-lp'); var raw = parseFloat(rawEl ? rawEl.value : 0) || 0; var bn = parseFloat(bonusEl ? bonusEl.value : 0) || 0; var lp = Math.max(0, Math.min(100, parseFloat(penaltyEl ? penaltyEl.value : 0) || 0)); row.dataset.status = st; var tCell = row.querySelector('.tsm-ge-total'); var pCell = row.querySelector('.tsm-ge-pct'); var cCell = row.querySelector('.tsm-ge-counts'); if (!tCell || !pCell || !cCell) { return; } if (!m.counts) { tCell.textContent = ' - '; pCell.textContent = ' - '; cCell.textContent = NO; return; } var total = m.zero ? 0 : Math.max(0, raw * (1 - lp / 100) + bn); tCell.textContent = total.toFixed(2); pCell.textContent = POSSIBLE > 0 ? ((total / POSSIBLE) * 100).toFixed(2) + '%' : ' - '; cCell.textContent = YES; }\n"
			. "\tfunction onStatus(row){ var statusEl = row.querySelector('.tsm-ge-status'); var sub = row.querySelector('.tsm-ge-submitted'); if (!statusEl) { return; } var st = statusEl.value; if (sub && ['completed_ontime','completed_late','incomplete','missing'].indexOf(st) >= 0 && !sub.value) { sub.value = todayDisplay(); } else if (sub && ['ungraded','excluded'].indexOf(st) >= 0) { sub.value = ''; } recalc(row); }\n"
			. "\tfunction onRaw(row){ var status = row.querySelector('.tsm-ge-status'); var raw = row.querySelector('.tsm-ge-raw'); var sub = row.querySelector('.tsm-ge-submitted'); if (status && raw && '' !== raw.value && ['','ungraded'].indexOf(status.value) >= 0) { status.value = 'completed_ontime'; if (sub && !sub.value) { sub.value = displayYmd(DUE) || todayDisplay(); } } recalc(row); }\n"
			. "\tdocument.querySelectorAll('.tsm-grade-row').forEach(function(row){ var status = row.querySelector('.tsm-ge-status'); if (status) { status.addEventListener('change', function(){ onStatus(row); }); } ['tsm-ge-raw','tsm-ge-bonus','tsm-ge-lp'].forEach(function(c){ var el = row.querySelector('.' + c); if (el) { el.addEventListener('input', function(){ c === 'tsm-ge-raw' ? onRaw(row) : recalc(row); }); } }); recalc(row); });\n"
			. "})();\n"
		);
	}

	private function render_core_class_report( $page_slug = 'tsm-gradebook' ) {
		$class_id   = isset( $_GET['class_id'] ) ? absint( $_GET['class_id'] ) : 0; // phpcs:ignore
		$active_yr  = class_exists( 'TSM_Active_Year' ) ? (int) TSM_Active_Year::get() : 0;
		$classes    = $active_yr ? TSM_Classes::all( $active_yr ) : TSM_Classes::all();
		$tab_value  = 'tsm-gradebook' === $page_slug ? 'reports' : 'class';

		if ( $active_yr ) {
			$yr_obj = class_exists( 'TSM_School_Years' ) ? TSM_School_Years::get( $active_yr ) : null;
			/* translators: %s: selected school year name. */
			echo '<p class="description" style="margin:6px 0 10px;">' . sprintf( esc_html__( 'Showing classes for school year: %s. Use the year switcher in the topbar to change.', 'tradeschool-manager' ), '<strong>' . esc_html( $yr_obj->name ?? '' ) . '</strong>' ) . '</p>';
		} else {
			echo '<p class="description" style="margin:6px 0 10px;">' . esc_html__( 'Showing classes from all school years.', 'tradeschool-manager' ) . '</p>';
		}
		?>
		<form method="get" class="tsm-gradebook-filters" style="display:flex;gap:14px;align-items:flex-end;flex-wrap:wrap;margin:6px 0 16px;">
			<input type="hidden" name="page" value="<?php echo esc_attr( $page_slug ); ?>">
			<input type="hidden" name="tab" value="<?php echo esc_attr( $tab_value ); ?>">
			<label style="display:flex;flex-direction:column;font-weight:600;font-size:12px;">
				<?php esc_html_e( 'Class', 'tradeschool-manager' ); ?>
				<select name="class_id" onchange="this.form.submit()" style="min-width:240px;">
					<option value="0"><?php esc_html_e( ' -  Select class  - ', 'tradeschool-manager' ); ?></option>
					<?php foreach ( $classes as $c ) : ?>
						<option value="<?php echo esc_attr( $c->id ); ?>" <?php selected( $class_id, $c->id ); ?>><?php echo esc_html( $c->name ); ?></option>
					<?php endforeach; ?>
				</select>
			</label>
		</form>
		<?php if ( $class_id ) :
			$class_obj  = TSM_Classes::get( $class_id );
			$rows       = TSM_Reports::class_summary( $class_id );
			$roster_url = wp_nonce_url(
				add_query_arg(
					array(
						'action'   => 'tradeschool_manager_core_roster_pdf',
						'class_id' => $class_id,
					),
					admin_url( 'admin-post.php' )
				),
				'tradeschool_manager_core_roster_pdf_' . $class_id
			);
			?>
			<div style="display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;margin:10px 0 14px;">
				<div>
					<strong><?php echo esc_html( $class_obj ? $class_obj->name : __( 'Class Report', 'tradeschool-manager' ) ); ?></strong>
					<span class="description" style="margin-left:8px;"><?php echo (int) count( $rows ); ?> <?php esc_html_e( 'students', 'tradeschool-manager' ); ?></span>
				</div>
				<a class="button button-primary" href="<?php echo esc_url( $roster_url ); ?>">
					<?php esc_html_e( 'Download PDF Roster', 'tradeschool-manager' ); ?>
				</a>
			</div>
			<table class="widefat striped">
				<thead>
					<tr>
						<th><?php esc_html_e( 'Student', 'tradeschool-manager' ); ?></th>
						<th><?php esc_html_e( 'Grade', 'tradeschool-manager' ); ?></th>
						<th><?php esc_html_e( 'Letter', 'tradeschool-manager' ); ?></th>
						<th style="text-align:right;"><?php esc_html_e( 'Admin PDF', 'tradeschool-manager' ); ?></th>
					</tr>
				</thead>
				<tbody>
				<?php foreach ( $rows as $row ) : ?>
					<tr>
						<td><?php echo esc_html( $row['student']->last_name . ', ' . $row['student']->first_name ); ?></td>
						<td><?php echo esc_html( $row['grade']['percent'] !== null ? $row['grade']['percent'] . '%' : ' - ' ); ?></td>
						<td><?php echo esc_html( $row['grade']['letter'] ); ?></td>
						<td style="text-align:right;">
							<?php
							$report_url = wp_nonce_url(
								add_query_arg(
									array(
										'action'     => 'tradeschool_manager_core_report_card',
										'student_id' => (int) $row['student']->id,
									),
									admin_url( 'admin-post.php' )
								),
								'tradeschool_manager_core_report_card_' . (int) $row['student']->id
							);
							?>
							<a class="button button-small" href="<?php echo esc_url( $report_url ); ?>"><?php esc_html_e( 'Report Card PDF', 'tradeschool-manager' ); ?></a>
						</td>
					</tr>
				<?php endforeach; ?>
				<?php if ( empty( $rows ) ) : ?>
					<tr><td colspan="4"><?php esc_html_e( 'No active students found for this class.', 'tradeschool-manager' ); ?></td></tr>
				<?php endif; ?>
				</tbody>
			</table>
		<?php else : ?>
			<p class="description"><?php esc_html_e( 'Select a class to view the class report.', 'tradeschool-manager' ); ?></p>
		<?php endif;
	}

	public function download_core_report_card() {
		if ( ! current_user_can( 'manage_tradeschool' ) ) {
			wp_die( esc_html__( 'You do not have permission to download this report card.', 'tradeschool-manager' ) );
		}

		$student_id = isset( $_GET['student_id'] ) ? absint( $_GET['student_id'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		check_admin_referer( 'tradeschool_manager_core_report_card_' . $student_id );

		if ( ! $student_id || ! class_exists( 'TSMP_PDF' ) ) {
			wp_die( esc_html__( 'Report card PDF is unavailable.', 'tradeschool-manager' ) );
		}

		TSMP_PDF::output_report_card( $student_id );
		exit;
	}

	public function download_core_roster_pdf() {
		if ( ! current_user_can( 'manage_tradeschool' ) ) {
			wp_die( esc_html__( 'You do not have permission to download this roster.', 'tradeschool-manager' ) );
		}

		$class_id = isset( $_GET['class_id'] ) ? absint( $_GET['class_id'] ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		check_admin_referer( 'tradeschool_manager_core_roster_pdf_' . $class_id );

		$class = $class_id ? TSM_Classes::get( $class_id ) : null;
		if ( ! $class || ! class_exists( 'TSMP_PDF' ) ) {
			wp_die( esc_html__( 'Roster PDF is unavailable.', 'tradeschool-manager' ) );
		}

		$rows = array();
		foreach ( TSM_Reports::class_summary( $class_id ) as $row ) {
			$student = $row['student'];
			$grade   = $row['grade'];
			$rows[]  = array(
				array( 'value' => esc_html( trim( $student->last_name . ', ' . $student->first_name ) ), 'bold' => true ),
				array( 'value' => esc_html( $student->email ?? '' ) ),
				array( 'value' => esc_html( $student->phone ?? '' ) ),
				array( 'value' => esc_html( null !== $grade['percent'] ? $grade['percent'] . '%' : ' - ' ), 'align' => 'right' ),
				array( 'value' => esc_html( $grade['letter'] ), 'align' => 'center' ),
			);
		}

		$year_name = '';
		if ( ! empty( $class->school_year_id ) && class_exists( 'TSM_School_Years' ) ) {
			$year = TSM_School_Years::get( (int) $class->school_year_id );
			if ( $year && ! empty( $year->name ) ) {
				$year_name = $year->name;
			}
		}

		TSMP_PDF::output_summary_report(
			array(
				'title'       => __( 'Class Roster', 'tradeschool-manager' ),
				'subtitle'    => $class->name,
				'orientation' => 'L',
				'filename'    => 'class-roster-' . sanitize_title( $class->name ) . '.pdf',
				'meta'        => array(
					array( 'label' => __( 'Class', 'tradeschool-manager' ), 'value' => esc_html( $class->name ) ),
					array( 'label' => __( 'School Year', 'tradeschool-manager' ), 'value' => esc_html( $year_name ?: __( 'Not assigned', 'tradeschool-manager' ) ) ),
					array( 'label' => __( 'Students', 'tradeschool-manager' ), 'value' => (string) count( $rows ), 'highlight' => true ),
				),
				'sections'    => array(
					array(
						'heading' => __( 'Roster', 'tradeschool-manager' ),
						'columns' => array(
							array( 'label' => __( 'Student', 'tradeschool-manager' ), 'width' => '30%' ),
							array( 'label' => __( 'Email', 'tradeschool-manager' ), 'width' => '28%' ),
							array( 'label' => __( 'Phone', 'tradeschool-manager' ), 'width' => '18%' ),
							array( 'label' => __( 'Grade', 'tradeschool-manager' ), 'width' => '12%', 'align' => 'right' ),
							array( 'label' => __( 'Letter', 'tradeschool-manager' ), 'width' => '12%', 'align' => 'center' ),
						),
						'rows'    => $rows,
						'empty'   => __( 'No active students found for this class.', 'tradeschool-manager' ),
					),
				),
			)
		);
		exit;
	}

	public function page_reports() {
		// Delegate to the standalone Reports & Transcripts add-on when available.
		if ( class_exists( 'TSR_Admin' ) ) {
			$this->shell_open( __( 'Reports', 'tradeschool-manager' ) );
			TSR_Admin::render_page();
			$this->shell_close();
			return;
		}
		$this->shell_open( __( 'Reports', 'tradeschool-manager' ) );

		$reports_addon = function_exists( 'tsm_addon_active' ) && tsm_addon_active( 'reports_transcripts' );
		$has_advanced  = $reports_addon && class_exists( 'TSMP_Reports' );
		$has_audit     = $reports_addon && class_exists( 'TSMP_Audit' );
		$has_transcripts = $reports_addon && class_exists( 'TSM_Transcripts' );

		$tabs = array( 'class' => __( 'Class Reports', 'tradeschool-manager' ) );
		if ( $has_advanced )    { $tabs['advanced']    = __( 'Advanced Reports', 'tradeschool-manager' ); }
		if ( $has_transcripts ) { $tabs['transcripts'] = __( 'Transcripts', 'tradeschool-manager' ); }
		if ( $has_audit )       { $tabs['audit']       = __( 'Audit Log', 'tradeschool-manager' ); }

		$tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'class'; // phpcs:ignore
		if ( ! isset( $tabs[ $tab ] ) ) { $tab = 'class'; }

		// Only render the sub-tabs if there is more than one available.
		if ( count( $tabs ) > 1 ) {
			$this->render_tabs( 'tsm-reports', $tabs, $tab );
		}

		if ( 'advanced' === $tab && $has_advanced ) {
			TSMP_Reports::render_admin();
		} elseif ( 'transcripts' === $tab && $has_transcripts ) {
			TSM_Transcripts::render_admin();
		} elseif ( 'audit' === $tab && $has_audit ) {
			TSMP_Audit::render_admin();
		} else {
			$this->render_core_class_report( 'tsm-reports' );
			if ( ! $has_advanced && function_exists( 'tradeschool_manager_is_pro_active' ) && function_exists( 'tsm_upgrade_notice' ) && ! tradeschool_manager_is_pro_active() ) {
				tsm_upgrade_notice( __( 'Advanced Reports & Analytics', 'tradeschool-manager' ) );
			}
		}

		$this->shell_close();
	}

	public function page_settings() {
		// Must run BEFORE shell_open so wp.media scripts are registered for the footer.
		wp_enqueue_media();
		$this->shell_open( __( 'Settings', 'tradeschool-manager' ) );
		$s = TSM_Settings::get_all();
		$active_tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'general';
		if ( 'license' === $active_tab ) {
			$active_tab = 'addons';
		}
		$tabs = array(
			'general'   => __( 'General', 'tradeschool-manager' ),
			'portal'    => __( 'Portal Pages', 'tradeschool-manager' ),
			'terms'     => __( 'Terminology', 'tradeschool-manager' ),
		);
		if ( function_exists( 'tsm_addon_active' ) && tsm_addon_active( 'invoicing' ) ) {
			$tabs['invoicing'] = __( 'Invoicing', 'tradeschool-manager' );
			$tabs['legal']     = __( 'Legal / Contracts', 'tradeschool-manager' );
		}
		$tabs['addons']   = __( 'Add-ons', 'tradeschool-manager' );
		$tabs['advanced'] = __( 'Advanced', 'tradeschool-manager' );
		$tabs = apply_filters( 'tradeschool_manager_settings_tabs', $tabs );
		$base = admin_url( 'admin.php?page=tsm-settings' );
		?>
		<h2 class="nav-tab-wrapper tsmp-settings-tabs">
			<?php foreach ( $tabs as $k => $label ) :
				$class = 'nav-tab' . ( $k === $active_tab ? ' nav-tab-active' : '' ); ?>
				<a class="<?php echo esc_attr( $class ); ?>" href="<?php echo esc_url( add_query_arg( 'tab', $k, $base ) ); ?>"><?php echo esc_html( $label ); ?></a>
			<?php endforeach; ?>
		</h2>

		<?php if ( 'addons' === $active_tab ) :
			TSM_Addons::render_settings_tab();
			$this->shell_close();
			return;
		endif; ?>

		<?php if ( 'stripe' === $active_tab ) : ?>
			<div class="tsm-card" style="max-width:760px;">
				<h3><?php esc_html_e( 'Stripe settings moved to Invoicing', 'tradeschool-manager' ); ?></h3>
				<p><?php esc_html_e( 'Stripe is used for invoice payments, payment plans, and Stripe Tax. Manage Stripe from the Invoicing add-on so payment setup, webhook status, and tax collection stay in one place.', 'tradeschool-manager' ); ?></p>
				<p><a class="button button-primary" href="<?php echo esc_url( admin_url( 'admin.php?page=tsm-invoicing&tab=stripe' ) ); ?>"><?php esc_html_e( 'Open Stripe setup', 'tradeschool-manager' ); ?></a></p>
			</div>
			<?php
			$this->shell_close();
			return;
		endif; ?>



		<?php if ( 'advanced' !== $active_tab ) : ?>
		<form method="post">
			<?php wp_nonce_field( 'tsm_save_settings' ); ?>
			<input type="hidden" name="tsm_action" value="save_settings">
			<input type="hidden" name="_active_tab" value="<?php echo esc_attr( $active_tab ); ?>">
			<table class="form-table">
				<?php if ( 'general' === $active_tab ) : ?>
					<tr><th><?php esc_html_e( 'School Name', 'tradeschool-manager' ); ?></th><td><input class="regular-text" name="school_name" value="<?php echo esc_attr( $s['school_name'] ?? '' ); ?>"></td></tr>
					<tr><th><?php esc_html_e( 'School Logo', 'tradeschool-manager' ); ?></th><td>
						<?php $logo_url = $s['school_logo'] ?? ''; ?>
						<div class="tsm-logo-picker" style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">
							<input type="url" class="regular-text" id="tsm_school_logo" name="school_logo" value="<?php echo esc_attr( $logo_url ); ?>" placeholder="https://...">
							<button type="button" class="button" id="tsm_school_logo_select"><?php esc_html_e( 'Select from Media Library', 'tradeschool-manager' ); ?></button>
							<button type="button" class="button" id="tsm_school_logo_clear" <?php echo $logo_url ? '' : 'style="display:none;"'; ?>><?php esc_html_e( 'Remove', 'tradeschool-manager' ); ?></button>
						</div>
						<div id="tsm_school_logo_preview" style="<?php echo $logo_url ? '' : 'display:none;'; ?>margin-top:10px;">
							<img src="<?php echo esc_url( $logo_url ); ?>" alt="" style="max-height:80px;max-width:240px;border:1px solid #e2e8f0;padding:6px;background:#fff;border-radius:4px;">
						</div>
						<?php
						TSM_Inline_Assets::script(
							'tsm-school-logo-picker',
							"(function(){\n"
							. "\tfunction ready(fn){ if (document.readyState !== 'loading') { fn(); } else { document.addEventListener('DOMContentLoaded', fn); } }\n"
							. "\tready(function(){ var btn = document.getElementById('tsm_school_logo_select'); var clr = document.getElementById('tsm_school_logo_clear'); var inp = document.getElementById('tsm_school_logo'); var prev = document.getElementById('tsm_school_logo_preview'); if (!btn || !inp) { return; } var frame; btn.addEventListener('click', function(e){ e.preventDefault(); if (typeof wp === 'undefined' || !wp.media) { window.alert('WordPress Media Library is not loaded on this page.'); return; } if (frame) { frame.open(); return; } frame = wp.media({ title: " . wp_json_encode( __( 'Select School Logo', 'tradeschool-manager' ) ) . ", button: { text: " . wp_json_encode( __( 'Use this logo', 'tradeschool-manager' ) ) . " }, multiple: false, library: { type: 'image' } }); frame.on('select', function(){ var att = frame.state().get('selection').first().toJSON(); inp.value = att.url; if (prev) { prev.style.display = ''; var img = prev.querySelector('img'); if (img) { img.src = att.url; } } if (clr) { clr.style.display = ''; } }); frame.open(); }); if (clr) { clr.addEventListener('click', function(e){ e.preventDefault(); inp.value = ''; if (prev) { prev.style.display = 'none'; } clr.style.display = 'none'; }); } });\n"
							. "})();\n"
						);
						?>
					</td></tr>
					<tr><th><?php esc_html_e( 'Brand Color', 'tradeschool-manager' ); ?></th><td><input type="color" name="brand_color" value="<?php echo esc_attr( $s['brand_color'] ?? '#1f6feb' ); ?>"></td></tr>
					<tr><th><?php esc_html_e( 'Grading Scale', 'tradeschool-manager' ); ?></th><td><textarea name="grading_scale" rows="6" class="large-text code"><?php echo esc_textarea( $s['grading_scale'] ?? '' ); ?></textarea><p class="description"><?php esc_html_e( 'One per line, e.g. A: 90', 'tradeschool-manager' ); ?></p></td></tr>
					<tr><th><?php esc_html_e( 'Application Notification Email', 'tradeschool-manager' ); ?></th><td><input class="regular-text" type="email" name="application_notify_email" value="<?php echo esc_attr( $s['application_notify_email'] ?? '' ); ?>" placeholder="<?php echo esc_attr( get_option( 'admin_email' ) ); ?>"><p class="description"><?php esc_html_e( 'Where to send notifications when a new program application is submitted. Defaults to the WordPress admin email.', 'tradeschool-manager' ); ?></p></td></tr>

				<?php elseif ( 'portal' === $active_tab ) : ?>
					<tr><td colspan="2"><p class="description"><?php esc_html_e( 'Pick the WordPress pages that contain the portal shortcodes. After login, students and teachers are redirected to the matching portal page.', 'tradeschool-manager' ); ?></p></td></tr>
					<?php
					// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_dropdown_pages() echoes safe core-escaped HTML; option_none label is plain text passed to core which escapes it.
					$tsm_none_label = __( ' -  Use current page  - ', 'tradeschool-manager' );
					?>
					<tr><th><?php esc_html_e( 'Student Login Page', 'tradeschool-manager' ); ?></th><td><?php
					wp_dropdown_pages( array( 'name' => 'student_login_page', 'show_option_none' => $tsm_none_label, 'option_none_value' => 0, 'selected' => (int) ( $s['student_login_page'] ?? 0 ) ) ); ?><p class="description"><?php esc_html_e( 'Page that contains the [tsm_student_login] shortcode.', 'tradeschool-manager' ); ?></p></td></tr>
					<tr><th><?php esc_html_e( 'Student Portal Page', 'tradeschool-manager' ); ?></th><td><?php
					wp_dropdown_pages( array( 'name' => 'student_portal_page', 'show_option_none' => $tsm_none_label, 'option_none_value' => 0, 'selected' => (int) ( $s['student_portal_page'] ?? 0 ) ) ); ?><p class="description"><?php esc_html_e( 'Page that contains the [tsm_student_portal] shortcode. Students land here after sign in.', 'tradeschool-manager' ); ?></p></td></tr>
					<tr><th><?php esc_html_e( 'Teacher Login Page', 'tradeschool-manager' ); ?></th><td><?php
					wp_dropdown_pages( array( 'name' => 'teacher_login_page', 'show_option_none' => $tsm_none_label, 'option_none_value' => 0, 'selected' => (int) ( $s['teacher_login_page'] ?? 0 ) ) ); ?><p class="description"><?php esc_html_e( 'Page that contains the [tsm_teacher_login] shortcode.', 'tradeschool-manager' ); ?></p></td></tr>
					<tr><th><?php esc_html_e( 'Teacher Portal Page', 'tradeschool-manager' ); ?></th><td><?php
					wp_dropdown_pages( array( 'name' => 'teacher_portal_page', 'show_option_none' => $tsm_none_label, 'option_none_value' => 0, 'selected' => (int) ( $s['teacher_portal_page'] ?? 0 ) ) ); ?><p class="description"><?php esc_html_e( 'Page that contains the [tsm_teacher_portal] shortcode. Teachers land here after sign in.', 'tradeschool-manager' ); ?></p></td></tr>
					<?php
					$tsm_layouts = class_exists( 'TSM_Portal_Theme' ) ? TSM_Portal_Theme::layouts() : array( 'console' => 'Focused (default)' );
					$tsm_layout_current = $s['portal_layout'] ?? 'console';
					?>
					<tr><th><?php esc_html_e( 'Portal Layout', 'tradeschool-manager' ); ?></th><td>
						<select name="portal_layout">
							<?php foreach ( $tsm_layouts as $k => $label ) : ?>
								<option value="<?php echo esc_attr( $k ); ?>" <?php selected( $tsm_layout_current, $k ); ?>><?php echo esc_html( $label ); ?></option>
							<?php endforeach; ?>
						</select>
						<p class="description"><?php esc_html_e( 'Visual layout for the Student and Teacher portals. Sponsor portals are unaffected. Focused is the default portal workspace.', 'tradeschool-manager' ); ?></p>
					</td></tr>
					<?php // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped ?>

				<?php elseif ( 'terms' === $active_tab ) : ?>
					<tr><th><?php esc_html_e( 'Term: Class', 'tradeschool-manager' ); ?></th><td><input class="regular-text" name="term_class" value="<?php echo esc_attr( $s['terminology']['class'] ?? 'Class' ); ?>"></td></tr>
					<tr><th><?php esc_html_e( 'Term: Student', 'tradeschool-manager' ); ?></th><td><input class="regular-text" name="term_student" value="<?php echo esc_attr( $s['terminology']['student'] ?? 'Student' ); ?>"></td></tr>
					<tr><th><?php esc_html_e( 'Term: Teacher', 'tradeschool-manager' ); ?></th><td><input class="regular-text" name="term_teacher" value="<?php echo esc_attr( $s['terminology']['teacher'] ?? 'Teacher' ); ?>"></td></tr>

				<?php elseif ( 'invoicing' === $active_tab ) : ?>
					<tr><th><?php esc_html_e( 'Currency Code', 'tradeschool-manager' ); ?></th><td><input class="small-text" name="currency" value="<?php echo esc_attr( $s['currency'] ?? 'USD' ); ?>"> <input class="small-text" name="currency_symbol" value="<?php echo esc_attr( $s['currency_symbol'] ?? '$' ); ?>"> <span class="description">Code (USD) and symbol ($)</span></td></tr>
					<tr><th><?php esc_html_e( 'Invoice Number Prefix', 'tradeschool-manager' ); ?></th><td><input class="regular-text" name="invoice_prefix" value="<?php echo esc_attr( $s['invoice_prefix'] ?? 'INV-' ); ?>"> Next #: <input class="small-text" name="invoice_next_number" value="<?php echo esc_attr( $s['invoice_next_number'] ?? 1001 ); ?>"></td></tr>
					<tr><th><?php esc_html_e( 'Tax Rate (%) / Label', 'tradeschool-manager' ); ?></th><td><input class="small-text" type="number" step="0.001" name="tax_rate" value="<?php echo esc_attr( $s['tax_rate'] ?? 0 ); ?>"> <input class="regular-text" name="tax_label" value="<?php echo esc_attr( $s['tax_label'] ?? 'Tax' ); ?>"></td></tr>
					<tr><th><?php esc_html_e( 'Late Fee Amount / Grace Days', 'tradeschool-manager' ); ?></th><td><input class="small-text" type="number" step="0.01" name="late_fee_amount" value="<?php echo esc_attr( $s['late_fee_amount'] ?? 0 ); ?>"> <input class="small-text" type="number" name="late_fee_grace_days" value="<?php echo esc_attr( $s['late_fee_grace_days'] ?? 7 ); ?>"> <span class="description">Auto-applied N days after due date.</span></td></tr>
					<tr><th><?php esc_html_e( 'Default Reminder Schedule', 'tradeschool-manager' ); ?></th><td><input class="regular-text" name="reminder_schedule" value="<?php echo esc_attr( $s['reminder_schedule'] ?? '7,3,1,-1,-7' ); ?>"><p class="description">Comma-separated days. Positive = before due, negative = after due. Per-invoice override available.</p></td></tr>
					<tr><th><?php esc_html_e( 'Invoice Footer / Terms', 'tradeschool-manager' ); ?></th><td><textarea name="invoice_terms" rows="3" class="large-text"><?php echo esc_textarea( $s['invoice_terms'] ?? '' ); ?></textarea></td></tr>

				<?php elseif ( 'legal' === $active_tab ) :
					$tpl_html  = $s['contract_template_html']  ?? ( class_exists( 'TSM_Contracts' ) ? TSM_Contracts::default_template_html() : '' );
					$consent   = $s['contract_esign_consent']  ?? ( class_exists( 'TSM_Contracts' ) ? TSM_Contracts::default_esign_consent() : '' );
					$extras    = $s['contract_extra_clauses']  ?? '';
					$sign_page = (int) ( $s['contract_sign_page'] ?? 0 );
					?>
					<tr><td colspan="2">
						<div style="background:#fff8e1;border-left:4px solid #f59e0b;padding:14px 16px;margin:0 0 14px;border-radius:4px;">
							<p style="margin:0 0 6px;font-weight:600;color:#92400e;">Important: <?php esc_html_e( 'You are responsible for your contract content.', 'tradeschool-manager' ); ?></p>
							<p style="margin:0;color:#78350f;font-size:13px;line-height:1.5;">
								<?php esc_html_e( 'The contract template below, any extra clauses, and the ESIGN consent text are entirely your responsibility. TradeSchool Manager and its developers provide the tooling only  -  we do not provide legal advice and assume no liability for the agreements you issue. Have every contract reviewed by a qualified attorney licensed in your jurisdiction before issuing it to applicants or students.', 'tradeschool-manager' ); ?>
							</p>
						</div>
					</td></tr>
					<tr><td colspan="2"><p class="description"><?php esc_html_e( 'Configure the master tuition contract template used when issuing tuition loans from the Applications screen. The active template is snapshotted onto each contract at the moment it is created  -  future edits here will not change already-issued contracts.', 'tradeschool-manager' ); ?></p></td></tr>
					<tr><th><?php esc_html_e( 'Signing Page', 'tradeschool-manager' ); ?></th>
						<td><?php
						// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- wp_dropdown_pages() echoes safe core-escaped HTML; label and selected id passed to core which escapes them.
						$tsm_sign_label = __( ' -  Select a page with the [tsm_contract_sign] shortcode  - ', 'tradeschool-manager' );
						wp_dropdown_pages( array( 'name' => 'contract_sign_page', 'show_option_none' => $tsm_sign_label, 'option_none_value' => 0, 'selected' => $sign_page ) );
						// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
						?>
						<p class="description"><?php esc_html_e( 'The public page that hosts the [tsm_contract_sign] shortcode. Signing links are emailed to the applicant and resolve to this page.', 'tradeschool-manager' ); ?></p></td></tr>
					<tr><th><?php esc_html_e( 'Contract Template', 'tradeschool-manager' ); ?></th>
						<td>
							<?php wp_editor( $tpl_html, 'contract_template_html', array( 'textarea_name' => 'contract_template_html', 'textarea_rows' => 18, 'media_buttons' => false ) ); ?>
							<p class="description"><?php esc_html_e( 'Available merge tags:', 'tradeschool-manager' ); ?>
							<code>{school_name}</code> <code>{applicant_name}</code> <code>{applicant_email}</code> <code>{program_name}</code>
							<code>{principal}</code> <code>{deposit}</code> <code>{amount_financed}</code>
							<code>{finance_fee}</code> <code>{fee_mode}</code> <code>{fee_value}</code> <code>{fee_disclosure}</code>
							<code>{installments}</code> <code>{frequency}</code> <code>{first_payment_date}</code>
							<code>{total_paid}</code> <code>{payment_schedule_table}</code>
							<code>{today}</code> <code>{extra_clauses}</code> <code>{signed_name}</code></p>
						</td></tr>
					<tr><th><?php esc_html_e( 'Extra Clauses / Addenda', 'tradeschool-manager' ); ?></th>
						<td>
							<?php wp_editor( $extras, 'contract_extra_clauses', array( 'textarea_name' => 'contract_extra_clauses', 'textarea_rows' => 8, 'media_buttons' => false ) ); ?>
							<p class="description"><?php esc_html_e( 'Optional content injected at the {extra_clauses} merge tag  -  useful for state-specific addenda or school-specific policies.', 'tradeschool-manager' ); ?></p>
						</td></tr>
					<tr><th><?php esc_html_e( 'ESIGN Consent Text', 'tradeschool-manager' ); ?></th>
						<td><textarea name="contract_esign_consent" rows="6" class="large-text"><?php echo esc_textarea( $consent ); ?></textarea>
						<p class="description"><?php esc_html_e( 'Shown next to the consent checkbox on the signing page. Required for ESIGN/UETA compliance.', 'tradeschool-manager' ); ?></p></td></tr>

				<?php elseif ( 'stripe' === $active_tab ) : ?>
					<tr><td colspan="2"><p class="description">Webhook URL: <code><?php echo esc_html( class_exists( 'TSMP_Stripe' ) ? TSMP_Stripe::webhook_url() : home_url( '/?tsm_stripe_webhook=1' ) ); ?></code><br>Add this in Stripe Dashboard  Developers  Webhooks. Subscribe to <code>payment_intent.succeeded</code> and <code>charge.refunded</code>.</p></td></tr>
					<tr><th><?php esc_html_e( 'Mode', 'tradeschool-manager' ); ?></th><td><select name="stripe_mode"><option value="test" <?php selected( $s['stripe_mode'] ?? 'test', 'test' ); ?>>Test</option><option value="live" <?php selected( $s['stripe_mode'] ?? 'test', 'live' ); ?>>Live</option></select></td></tr>
					<tr><th><?php esc_html_e( 'Test Publishable Key', 'tradeschool-manager' ); ?></th><td><input class="large-text code" name="stripe_test_pk" value="<?php echo esc_attr( $s['stripe_test_pk'] ?? '' ); ?>" placeholder="pk_test_..."></td></tr>
					<tr><th><?php esc_html_e( 'Test Secret Key', 'tradeschool-manager' ); ?></th><td><input class="large-text code" type="password" name="stripe_test_sk" value="<?php echo esc_attr( $s['stripe_test_sk'] ?? '' ); ?>" placeholder="sk_test_..."></td></tr>
					<tr><th><?php esc_html_e( 'Test Webhook Secret', 'tradeschool-manager' ); ?></th><td><input class="large-text code" type="password" name="stripe_test_whsec" value="<?php echo esc_attr( $s['stripe_test_whsec'] ?? '' ); ?>" placeholder="whsec_..."></td></tr>
					<tr><th><?php esc_html_e( 'Live Publishable Key', 'tradeschool-manager' ); ?></th><td><input class="large-text code" name="stripe_live_pk" value="<?php echo esc_attr( $s['stripe_live_pk'] ?? '' ); ?>" placeholder="pk_live_..."></td></tr>
					<tr><th><?php esc_html_e( 'Live Secret Key', 'tradeschool-manager' ); ?></th><td><input class="large-text code" type="password" name="stripe_live_sk" value="<?php echo esc_attr( $s['stripe_live_sk'] ?? '' ); ?>" placeholder="sk_live_..."></td></tr>
					<tr><th><?php esc_html_e( 'Live Webhook Secret', 'tradeschool-manager' ); ?></th><td><input class="large-text code" type="password" name="stripe_live_whsec" value="<?php echo esc_attr( $s['stripe_live_whsec'] ?? '' ); ?>" placeholder="whsec_..."></td></tr>
				<?php endif; ?>
			</table>
			<p><button class="button button-primary"><?php esc_html_e( 'Save Settings', 'tradeschool-manager' ); ?></button></p>
		</form>
		<?php else : ?>
			<form method="post">
				<?php wp_nonce_field( 'tsm_save_advanced' ); ?>
				<input type="hidden" name="tsm_action" value="save_advanced">
				<table class="form-table">
					<tr><th scope="row"><?php esc_html_e( 'Hide Page Title feature', 'tradeschool-manager' ); ?></th><td>
						<?php $pth_on = class_exists( 'TSM_Page_Title_Hider' ) && TSM_Page_Title_Hider::is_active(); ?>
						<p style="margin:0 0 8px;">
							<strong style="display:inline-block;min-width:80px;"><?php echo $pth_on ? '<span style="color:#15803d;">' . esc_html__( 'Active', 'tradeschool-manager' ) . '</span>' : '<span style="color:#64748b;">' . esc_html__( 'Inactive', 'tradeschool-manager' ) . '</span>'; ?></strong>
							<button type="submit" name="toggle_page_title_hider" value="<?php echo $pth_on ? '0' : '1'; ?>" class="button <?php echo $pth_on ? '' : 'button-primary'; ?>">
								<?php echo $pth_on ? esc_html__( 'Deactivate', 'tradeschool-manager' ) : esc_html__( 'Activate', 'tradeschool-manager' ); ?>
							</button>
						</p>
						<p class="description"><?php esc_html_e( 'Adds a "Hide page title on the live page" checkbox to every Page editor (both block & classic). Handy when a page already renders its own heading (e.g. shortcodes like [tsm_teacher_portal]).', 'tradeschool-manager' ); ?></p>
					</td></tr>
					<tr><th><?php esc_html_e( 'Data on uninstall', 'tradeschool-manager' ); ?></th><td>
						<label><input type="checkbox" name="remove_data" value="1" <?php checked( get_option( 'tsm_remove_data_on_uninstall' ) ); ?>> <?php esc_html_e( 'Delete all plugin data on uninstall', 'tradeschool-manager' ); ?></label>
					</td></tr>
				</table>
				<p><button class="button button-primary"><?php esc_html_e( 'Save', 'tradeschool-manager' ); ?></button></p>
			</form>
		<?php endif; ?>
		<?php
		TSM_Inline_Assets::style( 'tsm-settings-tabs', '.tsmp-settings-tabs{margin-top:8px}' );
		$this->shell_close();
	}

	/* Email Deliverability UI now lives in TSMP_Brevo::render_admin()
	 * (the dedicated "Email Deliverability" admin page), which exposes
	 * Standard Mail, Brevo, and About Brevo & Plans tabs. */




	public function page_pro_locked_attendance() {
		$this->shell_open( __( 'Attendance', 'tradeschool-manager' ) );
		do_action( 'tradeschool_manager_render_attendance' );
		$this->shell_close();
	}
	// page_license() and page_pro_locked_payments() removed in v1.6.4.

	/* ---------- POST/GET action router ---------- */

	public function handle_post() {
		if ( ! current_user_can( 'manage_tradeschool' ) ) {
			return;
		}
		$action = isset( $_REQUEST['tsm_action'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['tsm_action'] ) ) : '';
		if ( ! $action ) {
			return;
		}

		// --- License lockout: block "create new" actions when license is inactive. ---
		// Editing existing records, collecting payments, recording grades, marking
		// enrollment status, and viewing/exports remain allowed.
		if ( class_exists( 'TSM_License' ) && TSM_License::is_creation_locked() ) {
			$id_post = absint( $_POST['id'] ?? 0 );
			$creating_new = array(
				'save_student'  => array( 'context' => __( 'students', 'tradeschool-manager' ),     'slug' => 'tsm-students',     'creates' => 0 === $id_post ),
				'save_teacher'  => array( 'context' => __( 'teachers', 'tradeschool-manager' ),     'slug' => 'tsm-teachers',     'creates' => 0 === $id_post ),
				'save_class'    => array( 'context' => __( 'classes', 'tradeschool-manager' ),      'slug' => 'tsm-classes',      'creates' => 0 === $id_post ),
				'save_year'     => array( 'context' => __( 'school years', 'tradeschool-manager' ), 'slug' => 'tsm-school-years', 'creates' => true ),
				'save_assignment' => array( 'context' => __( 'assignments', 'tradeschool-manager' ),'slug' => 'tsm-assignments',  'creates' => true ),
				'enroll'        => array( 'context' => __( 'class enrollments', 'tradeschool-manager' ), 'slug' => 'tsm-classes', 'creates' => true ),
				'save_program'  => array( 'context' => __( 'programs', 'tradeschool-manager' ),     'slug' => 'tsm-programs',     'creates' => 0 === $id_post ),
				'enroll_program'=> array( 'context' => __( 'program enrollments', 'tradeschool-manager' ), 'slug' => 'tsm-students', 'creates' => true ),
			);
			if ( isset( $creating_new[ $action ] ) && ! empty( $creating_new[ $action ]['creates'] ) ) {
				TSM_License::block_or_die( $creating_new[ $action ]['context'], $creating_new[ $action ]['slug'] );
			}
		}

		switch ( $action ) {
			case 'save_student':
				check_admin_referer( 'tsm_save_student' );
				$id   = absint( $_POST['id'] ?? 0 );
				$post = wp_unslash( $_POST ); // phpcs:ignore

				// Handle profile photo file upload (overrides any pasted URL).
				if ( ! empty( $_FILES['photo_file']['name'] ) ) {
					require_once ABSPATH . 'wp-admin/includes/file.php';
					$uploaded = wp_handle_upload(
						$_FILES['photo_file'], // phpcs:ignore
						array(
							'test_form' => false,
							'mimes'     => array(
								'png'  => 'image/png',
								'jpg'  => 'image/jpeg',
								'jpeg' => 'image/jpeg',
								'webp' => 'image/webp',
							),
						)
					);
					if ( ! empty( $uploaded['url'] ) ) {
						$post['photo_url'] = $uploaded['url'];
					}
				}

				$new_id = TSM_Students::save( $post, $id );
				$hook_post = self::sanitize_hook_payload( $post );
				unset( $hook_post['_wpnonce'], $hook_post['_wp_http_referer'], $hook_post['password'] );
				/** Pro hooks here to persist custom field values, audit log, etc. */
				do_action( 'tradeschool_manager_after_save_student', $new_id, $hook_post );

				// Initial enrollment cascade (only meaningful when creating a new
				// student; on edit, use the dedicated enrollment panel instead).
				if ( ! $id && is_numeric( $new_id ) && (int) $new_id > 0 ) {
					$ep = absint( $post['enroll_program_id'] ?? 0 );
					if ( $ep && class_exists( 'TSM_Programs' ) ) {
						TSM_Programs::enroll( array(
							'student_id'     => (int) $new_id,
							'program_id'     => $ep,
							'school_year_id' => absint( $post['enroll_school_year_id'] ?? 0 ),
							'billing_mode'   => 'manual',
						) );
					}
					$ewi = absint( $post['enroll_workshop_instance_id'] ?? 0 );
					if ( $ewi && class_exists( 'TSM_Workshops' ) ) {
						TSM_Workshops::enroll( $ewi, (int) $new_id );
					}
				}
				$this->redirect( 'tsm-students' );
				break;

			case 'delete_student':
				check_admin_referer( 'tsm_delete_student_' . absint( $_GET['id'] ?? 0 ) );
				TSM_Students::delete( absint( $_GET['id'] ?? 0 ) );
				$this->redirect( 'tsm-students' );
				break;

			case 'drop_student':
				$id = absint( $_POST['id'] ?? 0 );
				check_admin_referer( 'tsm_drop_student_' . $id );
				$reason = isset( $_POST['dropped_reason'] ) ? wp_unslash( $_POST['dropped_reason'] ) : ''; // phpcs:ignore
				$date   = isset( $_POST['dropped_date'] ) ? sanitize_text_field( wp_unslash( $_POST['dropped_date'] ) ) : '';
				TSM_Students::drop( $id, $reason, $date );
				do_action( 'tradeschool_manager_after_drop_student', $id, $reason, $date );
				set_transient( 'tsm_admin_flash', array( 'type' => 'success', 'message' => __( 'Student dropped. Their records were preserved.', 'tradeschool-manager' ) ), 30 );
				wp_safe_redirect( admin_url( 'admin.php?page=tsm-students&tab=dropped' ) );
				exit;

			case 'reinstate_student':
				$id = absint( $_POST['id'] ?? 0 );
				check_admin_referer( 'tsm_reinstate_student_' . $id );
				TSM_Students::reinstate( $id );
				do_action( 'tradeschool_manager_after_reinstate_student', $id );
				set_transient( 'tsm_admin_flash', array( 'type' => 'success', 'message' => __( 'Student reinstated.', 'tradeschool-manager' ) ), 30 );
				wp_safe_redirect( admin_url( 'admin.php?page=tsm-students&edit=' . $id ) );
				exit;

			case 'save_teacher':
				check_admin_referer( 'tsm_save_teacher' );
				TSM_Teachers::save( wp_unslash( $_POST ), absint( $_POST['id'] ?? 0 ) ); // phpcs:ignore
				$this->redirect( 'tsm-teachers' );
				break;

			case 'delete_teacher':
				check_admin_referer( 'tsm_delete_teacher_' . absint( $_GET['id'] ?? 0 ) );
				TSM_Teachers::delete( absint( $_GET['id'] ?? 0 ) );
				$this->redirect( 'tsm-teachers' );
				break;

			case 'save_year':
				check_admin_referer( 'tsm_save_year' );
				TSM_School_Years::save( wp_unslash( $_POST ) ); // phpcs:ignore
				$this->redirect( 'tsm-school-years' );
				break;

			case 'delete_year':
				check_admin_referer( 'tsm_delete_year_' . absint( $_GET['id'] ?? 0 ) );
				TSM_School_Years::delete( absint( $_GET['id'] ?? 0 ) );
				$this->redirect( 'tsm-school-years' );
				break;

			case 'rollover_year':
				check_admin_referer( 'tsm_rollover_year' );
				$from = absint( $_POST['from_year_id'] ?? 0 );
				$to   = absint( $_POST['to_year_id'] ?? 0 );
				$ids  = isset( $_POST['class_ids'] ) ? array_map( 'absint', (array) $_POST['class_ids'] ) : array(); // phpcs:ignore
				$res  = TSM_School_Years::rollover( $from, $to, $ids );
				$msg  = sprintf(
					/* translators: 1 = number of classes copied, 2 = number of assignments copied */
					__( 'Rolled over %1$d classes and %2$d assignments. New classes start with empty rosters and gradebooks.', 'tradeschool-manager' ),
					(int) $res['copied_classes'], (int) $res['copied_assignments']
				);
				set_transient( 'tsm_admin_flash', array( 'type' => 'success', 'message' => $msg ), 30 );
				wp_safe_redirect( admin_url( 'admin.php?page=tsm-school-years' ) );
				exit;

			case 'save_class':
				check_admin_referer( 'tsm_save_class' );
				$id = TSM_Classes::save( wp_unslash( $_POST ), absint( $_POST['id'] ?? 0 ) ); // phpcs:ignore
				if ( is_wp_error( $id ) ) {
					set_transient( 'tsm_admin_flash', array( 'type' => 'error', 'message' => $id->get_error_message() ), 30 );
					$this->redirect( 'tsm-classes' );
					break;
				}
				if ( $id ) {
					$co = isset( $_POST['co_teacher_ids'] ) ? (array) wp_unslash( $_POST['co_teacher_ids'] ) : array(); // phpcs:ignore
					TSM_Classes::set_co_teachers( $id, $co );
				}
				$hook_post = self::sanitize_hook_payload( wp_unslash( $_POST ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized recursively immediately before use.
				unset( $hook_post['_wpnonce'], $hook_post['_wp_http_referer'] );
				do_action( 'tradeschool_manager_class_saved', $id, $hook_post );
				$this->redirect( 'tsm-classes' );
				break;

			case 'delete_class':
				check_admin_referer( 'tsm_delete_class_' . absint( $_GET['id'] ?? 0 ) );
				TSM_Classes::delete( absint( $_GET['id'] ?? 0 ) );
				$this->redirect( 'tsm-classes' );
				break;

			case 'complete_class':
				$cid = absint( $_GET['id'] ?? 0 );
				check_admin_referer( 'tsm_complete_class_' . $cid );
				$in_program = class_exists( 'TSM_Programs' ) ? TSM_Programs::programs_for_class( $cid ) : array();
				if ( ! empty( $in_program ) ) {
					set_transient( 'tsm_admin_flash', array( 'type' => 'error', 'message' => __( 'This class belongs to a program. Mark the program completed instead  -  its classes will be completed automatically.', 'tradeschool-manager' ) ), 30 );
				} else {
					TSM_Classes::mark_completed( $cid );
					set_transient( 'tsm_admin_flash', array( 'type' => 'success', 'message' => __( 'Class marked completed.', 'tradeschool-manager' ) ), 30 );
				}
				$this->redirect( 'tsm-classes' );
				break;

			case 'complete_program':
				$pid = absint( $_GET['id'] ?? 0 );
				check_admin_referer( 'tsm_complete_program_' . $pid );
				TSM_Programs::mark_completed( $pid );
				$msg = __( 'Program marked completed.', 'tradeschool-manager' );
				set_transient( 'tsm_admin_flash', array( 'type' => 'success', 'message' => $msg ), 30 );
				$this->redirect( 'tsm-programs' );
				break;

			case 'enroll':
				check_admin_referer( 'tsm_enroll' );
				TSM_Classes::enroll( absint( $_POST['class_id'] ?? 0 ), absint( $_POST['student_id'] ?? 0 ) );
				$this->redirect( 'tsm-classes', array( 'edit' => absint( $_POST['class_id'] ?? 0 ) ) );
				break;

			case 'unenroll':
				check_admin_referer( 'tsm_unenroll_' . absint( $_GET['class_id'] ?? 0 ) . '_' . absint( $_GET['student_id'] ?? 0 ) );
				TSM_Classes::unenroll( absint( $_GET['class_id'] ?? 0 ), absint( $_GET['student_id'] ?? 0 ) );
				$this->redirect( 'tsm-classes', array( 'edit' => absint( $_GET['class_id'] ?? 0 ) ) );
				break;

			case 'save_assignment':
				check_admin_referer( 'tsm_save_assignment' );
				$asd = wp_unslash( $_POST ); // phpcs:ignore
				// Coalesce category select + free-text fallback used when the class has no weighted categories.
				if ( empty( $asd['category'] ) && ! empty( $asd['category_free'] ) ) {
					$asd['category'] = $asd['category_free'];
				}
				$aid = TSM_Assignments::save( $asd, absint( $_POST['id'] ?? 0 ) );
				if ( is_wp_error( $aid ) ) {
					set_transient( 'tsm_admin_flash', array( 'type' => 'error', 'message' => $aid->get_error_message() ), 30 );
				}
				$this->redirect( 'tsm-assignments', array( 'class_id' => absint( $_POST['class_id'] ?? 0 ) ) );
				break;

			case 'delete_assignment':
				check_admin_referer( 'tsm_delete_assignment_' . absint( $_GET['id'] ?? 0 ) );
				$a = TSM_Assignments::get( absint( $_GET['id'] ?? 0 ) );
				TSM_Assignments::delete( absint( $_GET['id'] ?? 0 ) );
				$this->redirect( 'tsm-assignments', array( 'class_id' => $a ? $a->class_id : 0 ) );
				break;

			case 'download_assignments_template':
				check_admin_referer( 'tsm_assignments_template' );
				if ( ! current_user_can( 'manage_options' ) ) { wp_die( 'Forbidden' ); }
				nocache_headers();
				header( 'Content-Type: text/csv; charset=utf-8' );
				header( 'Content-Disposition: attachment; filename="assignments-template.csv"' );
				$out = fopen( 'php://output', 'w' );
				self::putcsv( $out, array( 'class_id', 'class_name', 'title', 'category', 'assigned_date', 'due_date', 'points_possible', 'extra_credit', 'required', 'description' ) );
				self::putcsv( $out, array( '', 'Welding 101', 'Safety Quiz 1', 'Quizzes', '09/05/2025', '09/12/2025', '20', '0', '1', 'Open-book quiz on shop safety.' ) );
				self::putcsv( $out, array( '', 'Welding 101', 'Final Project', 'Projects', '11/01/2025', '12/15/2025', '100', '0', '1', 'Capstone weld assembly.' ) );
				// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
				fclose( $out );
				exit;

			case 'export_assignments':
				check_admin_referer( 'tsm_export_assignments' );
				if ( ! current_user_can( 'manage_options' ) ) { wp_die( 'Forbidden' ); }
				$f_class = absint( $_POST['class_id'] ?? 0 );
				$f_year  = absint( $_POST['school_year_id'] ?? 0 );
				$rows    = TSM_Assignments::all( $f_class, $f_year );
				$classes = TSM_Classes::all();
				$cmap    = array();
				foreach ( $classes as $c ) { $cmap[ (int) $c->id ] = $c->name; }
				nocache_headers();
				header( 'Content-Type: text/csv; charset=utf-8' );
				header( 'Content-Disposition: attachment; filename="assignments-' . gmdate( 'Y-m-d' ) . '.csv"' );
				$out = fopen( 'php://output', 'w' );
				self::putcsv( $out, array( 'class_id', 'class_name', 'title', 'category', 'assigned_date', 'due_date', 'points_possible', 'extra_credit', 'required', 'description' ) );
				foreach ( $rows as $r ) {
					self::putcsv( $out, array(
						(int) $r->class_id,
						$cmap[ (int) $r->class_id ] ?? '',
						$r->title,
						$r->category,
						TSM_Date_Utils::csv( $r->assigned_date ),
						TSM_Date_Utils::csv( $r->due_date ),
						$r->points_possible,
						$r->extra_credit ? 1 : 0,
						$r->required ? 1 : 0,
						wp_strip_all_tags( (string) $r->description ),
					) );
				}
				// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
				fclose( $out );
				exit;

			case 'import_assignments':
				check_admin_referer( 'tsm_import_assignments' );
				if ( ! current_user_can( 'manage_options' ) ) { wp_die( 'Forbidden' ); }
				if ( class_exists( 'TSM_License' ) && TSM_License::is_creation_locked() ) {
					TSM_License::block_or_die( __( 'assignments', 'tradeschool-manager' ), 'tsm-assignments' );
				}
				$update_existing = ! empty( $_POST['update_existing'] );
				// $_FILES tmp_name is a server-generated PHP path; is_uploaded_file() validates it.
				$csv_tmp = isset( $_FILES['csv_file']['tmp_name'] ) ? sanitize_text_field( wp_unslash( $_FILES['csv_file']['tmp_name'] ) ) : '';
				if ( '' === $csv_tmp || ! is_uploaded_file( $csv_tmp ) ) {
					set_transient( 'tsm_admin_flash', array( 'type' => 'error', 'message' => __( 'No CSV file uploaded.', 'tradeschool-manager' ) ), 30 );
					$this->redirect( 'tsm-assignments', array( 'tab' => 'csv' ) );
				}
				$fh = fopen( $csv_tmp, 'r' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
				if ( ! $fh ) {
					set_transient( 'tsm_admin_flash', array( 'type' => 'error', 'message' => __( 'Could not open CSV.', 'tradeschool-manager' ) ), 30 );
					$this->redirect( 'tsm-assignments', array( 'tab' => 'csv' ) );
				}
				$header = fgetcsv( $fh );
				if ( ! $header ) {
					// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
					fclose( $fh );
					set_transient( 'tsm_admin_flash', array( 'type' => 'error', 'message' => __( 'CSV is empty.', 'tradeschool-manager' ) ), 30 );
					$this->redirect( 'tsm-assignments', array( 'tab' => 'csv' ) );
				}
				$header  = array_map( function( $h ) { return strtolower( trim( (string) $h ) ); }, $header );
				$idx     = array_flip( $header );
				$classes = TSM_Classes::all();
				$by_name = array();
				$by_id   = array();
				foreach ( $classes as $c ) {
					$by_name[ strtolower( trim( $c->name ) ) ] = $c;
					$by_id[ (int) $c->id ] = $c;
				}
				$created = 0; $updated = 0; $skipped = 0; $errors = array();
				$line_no = 1;
				while ( ( $row = fgetcsv( $fh ) ) !== false ) {
					$line_no++;
					$get = function( $key ) use ( $row, $idx ) { return isset( $idx[ $key ] ) && isset( $row[ $idx[ $key ] ] ) ? trim( (string) $row[ $idx[ $key ] ] ) : ''; };
					$title = $get( 'title' );
					if ( '' === $title ) { $skipped++; continue; }
					$cls = null;
					$cid = (int) $get( 'class_id' );
					if ( $cid && isset( $by_id[ $cid ] ) ) {
						$cls = $by_id[ $cid ];
					} else {
						$cname = strtolower( $get( 'class_name' ) );
						if ( $cname && isset( $by_name[ $cname ] ) ) { $cls = $by_name[ $cname ]; }
					}
					if ( ! $cls ) {
						$errors[] = sprintf( /* translators: %d row */ __( 'Row %d: class not found.', 'tradeschool-manager' ), $line_no );
						$skipped++;
						continue;
					}
					$data = array(
						'class_id'        => (int) $cls->id,
						'school_year_id'  => (int) $cls->school_year_id,
						'title'           => $title,
						'category'        => $get( 'category' ),
						'assigned_date'   => $get( 'assigned_date' ),
						'due_date'        => $get( 'due_date' ),
						'points_possible' => floatval( $get( 'points_possible' ) ),
						'extra_credit'    => in_array( strtolower( $get( 'extra_credit' ) ), array( '1', 'yes', 'true', 'y' ), true ) ? 1 : 0,
						'required'        => in_array( strtolower( $get( 'required' ) ), array( '0', 'no', 'false', 'n' ), true ) ? 0 : 1,
						'description'     => $get( 'description' ),
					);
					$existing = $update_existing ? TSM_Assignments::find_by_class_title( (int) $cls->id, $title ) : null;
					if ( $existing ) {
						TSM_Assignments::save( $data, (int) $existing->id );
						$updated++;
					} else {
						TSM_Assignments::save( $data );
						$created++;
					}
				}
				// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
				fclose( $fh );
				$msg = sprintf(
					/* translators: 1: created, 2: updated, 3: skipped */
					__( 'Import complete. Created: %1$d | Updated: %2$d | Skipped: %3$d', 'tradeschool-manager' ),
					$created, $updated, $skipped
				);
				if ( $errors ) {
					$msg .= '  -  ' . implode( ' ', array_slice( $errors, 0, 5 ) );
					if ( count( $errors ) > 5 ) { $msg .= ' ...'; }
				}
				set_transient( 'tsm_admin_flash', array( 'type' => $errors ? 'warning' : 'success', 'message' => $msg ), 60 );
				$this->redirect( 'tsm-assignments', array( 'tab' => 'current' ) );
				break;

			case 'save_grades':
				check_admin_referer( 'tsm_save_grades' );
				$grades  = isset( $_POST['grades'] ) ? (array) wp_unslash( $_POST['grades'] ) : array(); // phpcs:ignore
				$assn_id = absint( $_POST['assignment_id'] ?? 0 );
				if ( $assn_id ) {
					// Per-assignment view: grades[student_id][field]
					foreach ( $grades as $sid => $vals ) {
						$clean_vals = self::sanitize_grade_payload( $vals );
						$prev       = TSM_Gradebook::get_grade( $assn_id, absint( $sid ) );
						TSM_Gradebook::save_grade( $assn_id, absint( $sid ), $clean_vals );
						do_action( 'tradeschool_manager_after_save_grade', $assn_id, absint( $sid ), $clean_vals, $prev );
					}
					$this->redirect( 'tsm-gradebook', array( 'assignment_id' => $assn_id, 'saved' => 1 ) );
				} else {
					// Legacy matrix: grades[assignment_id][student_id][field]
					foreach ( $grades as $aid => $by_student ) {
						foreach ( (array) $by_student as $sid => $vals ) {
							$clean_vals = self::sanitize_grade_payload( $vals );
							$prev       = TSM_Gradebook::get_grade( absint( $aid ), absint( $sid ) );
							TSM_Gradebook::save_grade( absint( $aid ), absint( $sid ), $clean_vals );
							do_action( 'tradeschool_manager_after_save_grade', absint( $aid ), absint( $sid ), $clean_vals, $prev );
						}
					}
					$this->redirect( 'tsm-gradebook', array( 'class_id' => absint( $_POST['class_id'] ?? 0 ) ) );
				}
				break;

			case 'save_program':
				check_admin_referer( 'tsm_save_program' );
				$pid = TSM_Programs::save( wp_unslash( $_POST ), absint( $_POST['id'] ?? 0 ) ); // phpcs:ignore
				if ( is_wp_error( $pid ) ) {
					set_transient( 'tsm_admin_flash', array( 'type' => 'error', 'message' => $pid->get_error_message() ), 30 );
					$this->redirect( 'tsm-programs', array( 'tab' => 'add' ) );
					break;
				}
				if ( ! $pid ) {
					global $wpdb;
					$msg = __( 'Could not save program.', 'tradeschool-manager' );
					if ( ! empty( $wpdb->last_error ) ) { $msg .= ' (' . $wpdb->last_error . ')'; }
					set_transient( 'tsm_admin_flash', array( 'type' => 'error', 'message' => $msg ), 30 );
					$this->redirect( 'tsm-programs', array( 'tab' => 'add' ) );
					break;
				}
				$class_ids = isset( $_POST['program_class_ids'] ) ? (array) wp_unslash( $_POST['program_class_ids'] ) : array(); // phpcs:ignore
				$save_year = isset( $_POST['program_class_year_scope'] ) ? absint( $_POST['program_class_year_scope'] ) : 0;
				TSM_Programs::set_classes( $pid, array_map( 'absint', $class_ids ), $save_year );
				$hook_post = self::sanitize_hook_payload( wp_unslash( $_POST ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized recursively immediately before use.
				unset( $hook_post['_wpnonce'], $hook_post['_wp_http_referer'] );
				do_action( 'tradeschool_manager_program_saved', $pid, $hook_post );
				$this->redirect( 'tsm-programs', array( 'edit' => $pid, 'saved' => 1 ) );
				break;

			case 'delete_program':
				check_admin_referer( 'tsm_delete_program_' . absint( $_GET['id'] ?? 0 ) );
				TSM_Programs::delete( absint( $_GET['id'] ?? 0 ) );
				$this->redirect( 'tsm-programs' );
				break;

			case 'enroll_program':
				check_admin_referer( 'tsm_enroll_program' );
				$args = array(
					'student_id'     => absint( $_POST['student_id'] ?? 0 ),
					'program_id'     => absint( $_POST['program_id'] ?? 0 ),
					'school_year_id' => absint( $_POST['school_year_id'] ?? 0 ),
					'tuition_amount' => $_POST['tuition_amount'] ?? '', // phpcs:ignore
					'billing_mode'   => sanitize_text_field( $_POST['billing_mode'] ?? 'manual' ), // phpcs:ignore
					'installments'   => absint( $_POST['installments'] ?? 1 ),
					'frequency'      => sanitize_text_field( $_POST['frequency'] ?? 'monthly' ), // phpcs:ignore
					'start_date'     => sanitize_text_field( $_POST['start_date'] ?? '' ), // phpcs:ignore
					'due_date'       => sanitize_text_field( $_POST['due_date'] ?? '' ), // phpcs:ignore
					'notes'          => sanitize_textarea_field( $_POST['enrollment_notes'] ?? '' ), // phpcs:ignore
				);
				TSM_Programs::enroll( $args );
				$this->redirect( 'tsm-students', array( 'edit' => absint( $_POST['student_id'] ?? 0 ) ) );
				break;

			case 'unenroll_program':
				check_admin_referer( 'tsm_unenroll_program_' . absint( $_GET['enrollment_id'] ?? 0 ) );
				$enr = TSM_Programs::get_enrollment( absint( $_GET['enrollment_id'] ?? 0 ) );
				TSM_Programs::delete_enrollment( absint( $_GET['enrollment_id'] ?? 0 ) );
				$this->redirect( 'tsm-students', array( 'edit' => $enr ? absint( $enr->student_id ) : 0 ) );
				break;

			case 'update_enrollment_status':
				check_admin_referer( 'tsm_update_enrollment_' . absint( $_GET['enrollment_id'] ?? 0 ) );
				$enr = TSM_Programs::get_enrollment( absint( $_GET['enrollment_id'] ?? 0 ) );
				TSM_Programs::update_enrollment_status( absint( $_GET['enrollment_id'] ?? 0 ), sanitize_text_field( wp_unslash( $_GET['status'] ?? 'active' ) ) );
				$this->redirect( 'tsm-students', array( 'edit' => $enr ? absint( $enr->student_id ) : 0 ) );
				break;

			case 'edit_enrollment_terms':
				$eid = absint( $_POST['enrollment_id'] ?? 0 );
				check_admin_referer( 'tsm_edit_enrollment_terms_' . $eid );
				$enr = TSM_Programs::get_enrollment( $eid );
				if ( $enr ) {
					TSM_Programs::update_enrollment_terms( $eid, array(
						'tuition_amount'     => $_POST['tuition_amount'] ?? '', // phpcs:ignore
						'deposit'            => $_POST['deposit'] ?? 0, // phpcs:ignore
						'fee_mode'           => sanitize_text_field( wp_unslash( $_POST['fee_mode'] ?? 'none' ) ), // phpcs:ignore
						'fee_value'          => $_POST['fee_value'] ?? 0, // phpcs:ignore
						'billing_mode'       => sanitize_text_field( wp_unslash( $_POST['billing_mode'] ?? 'manual' ) ), // phpcs:ignore
						'installments'       => absint( $_POST['installments'] ?? 1 ),
						'frequency'          => sanitize_text_field( wp_unslash( $_POST['frequency'] ?? 'monthly' ) ), // phpcs:ignore
						'first_payment_date' => sanitize_text_field( wp_unslash( $_POST['first_payment_date'] ?? '' ) ), // phpcs:ignore
					) );
				}
				$this->redirect( 'tsm-students', array( 'edit' => $enr ? absint( $enr->student_id ) : 0, 'enr_updated' => 1 ) );
				break;

			case 'sync_enrollment_contract':
				$eid = absint( $_GET['enrollment_id'] ?? 0 );
				$cid = absint( $_GET['contract_id'] ?? 0 );
				check_admin_referer( 'tsm_sync_enrollment_' . $eid );
				$enr = TSM_Programs::get_enrollment( $eid );
				if ( $enr ) { TSM_Programs::sync_from_contract( $eid, $cid ); }
				$this->redirect( 'tsm-students', array( 'edit' => $enr ? absint( $enr->student_id ) : 0, 'enr_synced' => 1 ) );
				break;

			// Application-related action cases (invoice_application_fee, tsm_app_*)
			// were moved to dedicated admin_post handlers in the standalone
			// Applications add-on (v1.1.0+).
			case 'save_settings':
				check_admin_referer( 'tsm_save_settings' );
				TSM_Settings::update( TSM_Settings::sanitize_post( wp_unslash( $_POST ) ) ); // phpcs:ignore
				$tab = isset( $_POST['_active_tab'] ) ? sanitize_key( wp_unslash( $_POST['_active_tab'] ) ) : '';
				$this->redirect( 'tsm-settings', $tab ? array( 'tab' => $tab, 'updated' => 1 ) : array( 'updated' => 1 ) );
				break;

			case 'save_advanced':
				check_admin_referer( 'tsm_save_advanced' );
				update_option( 'tsm_remove_data_on_uninstall', ! empty( $_POST['remove_data'] ) );
				if ( isset( $_POST['toggle_page_title_hider'] ) ) {
					update_option( 'tsm_page_title_hider_active', ! empty( $_POST['toggle_page_title_hider'] ) ? 1 : 0 );
				}
				$this->redirect( 'tsm-settings', array( 'tab' => 'advanced', 'updated' => 1 ) );
				break;
		}
	}

	/* ---------- Programs (v1.7.0) ---------- */

	public function page_programs() {
		$this->shell_open( __( 'Programs', 'tradeschool-manager' ) );
		$editing = isset( $_GET['edit'] ) ? TSM_Programs::get( absint( $_GET['edit'] ) ) : null; // phpcs:ignore
		$programs = TSM_Programs::all();
		$active_year_id = class_exists( 'TSM_Active_Year' ) ? (int) TSM_Active_Year::get() : 0;
		$all_classes = TSM_Classes::all( $active_year_id );
		$attached_ids = array();
		if ( $editing ) {
			foreach ( TSM_Programs::classes_for( $editing->id, $active_year_id ) as $c ) { $attached_ids[ (int) $c->id ] = true; }
		}
		$tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : ''; // phpcs:ignore
		if ( $editing ) { $tab = 'add'; }
		if ( ! in_array( $tab, array( 'add', 'all' ), true ) ) { $tab = 'all'; }

		$archived = class_exists( 'TSM_School_Years' ) && TSM_School_Years::is_active_year_archived();
		if ( $archived && 'add' === $tab ) { $tab = 'all'; }
		$this->render_tabs( 'tsm-programs', array(
			'add' => __( 'Add New Program', 'tradeschool-manager' ),
			'all' => __( 'All Programs', 'tradeschool-manager' ),
		), $tab, $archived ? array( 'add' ) : array() );
		if ( $archived ) { $this->render_archived_notice( __( 'programs', 'tradeschool-manager' ) ); }

		$locked_create = ! $editing && class_exists( 'TSM_License' ) && TSM_License::is_creation_locked();
		if ( 'add' === $tab && ! $archived && $locked_create ) { TSM_License::render_lock_notice( __( 'programs', 'tradeschool-manager' ) ); }
		?>
		<?php if ( 'add' === $tab && ! $archived ) : ?>
		<?php if ( ! $locked_create ) : ?>
		<h2><?php echo $editing ? esc_html__( 'Edit Program', 'tradeschool-manager' ) : esc_html__( 'New Program', 'tradeschool-manager' ); ?></h2>
		<form method="post" class="tsm-entry-form">
			<?php wp_nonce_field( 'tsm_save_program' ); ?>
			<input type="hidden" name="tsm_action" value="save_program">
			<?php if ( $editing ) : ?><input type="hidden" name="id" value="<?php echo esc_attr( $editing->id ); ?>"><?php endif; ?>

			<div class="tsm-form-section">
				<div class="tsm-form-section-head">
					<span class="dashicons dashicons-portfolio"></span>
					<div>
						<h3><?php esc_html_e( 'Program Overview', 'tradeschool-manager' ); ?></h3>
						<p><?php esc_html_e( 'Public-facing details for this program. Shown on the catalog, applications, and contracts.', 'tradeschool-manager' ); ?></p>
					</div>
				</div>
				<div class="tsm-form-grid">
					<div class="tsm-field">
						<label><?php esc_html_e( 'Program Name', 'tradeschool-manager' ); ?> <span class="tsm-req">*</span></label>
						<input class="regular-text" name="name" required value="<?php echo esc_attr( $editing->name ?? '' ); ?>">
					</div>
					<div class="tsm-field">
						<label><?php esc_html_e( 'Code', 'tradeschool-manager' ); ?></label>
						<input class="regular-text" name="code" value="<?php echo esc_attr( $editing->code ?? '' ); ?>" placeholder="WELD-101">
					</div>
					<div class="tsm-field">
						<label><?php esc_html_e( 'Status', 'tradeschool-manager' ); ?></label>
						<select name="status">
							<?php $st = $editing->status ?? 'active'; ?>
							<option value="active"   <?php selected( $st, 'active' );   ?>><?php esc_html_e( 'Active', 'tradeschool-manager' ); ?></option>
							<option value="archived" <?php selected( $st, 'archived' ); ?>><?php esc_html_e( 'Archived', 'tradeschool-manager' ); ?></option>
						</select>
					</div>
					<div class="tsm-field">
						<label><?php esc_html_e( 'Passing %', 'tradeschool-manager' ); ?></label>
						<input type="number" min="0" max="100" step="0.01" name="passing_pct" value="<?php echo esc_attr( $editing->passing_pct ?? 70 ); ?>">
						<p class="description"><?php esc_html_e( 'Minimum average across the program\'s classes to qualify as completed.', 'tradeschool-manager' ); ?></p>
					</div>
					<div class="tsm-field tsm-field-wide">
						<label><?php esc_html_e( 'Description', 'tradeschool-manager' ); ?></label>
						<textarea name="description" rows="3" class="large-text"><?php echo esc_textarea( $editing->description ?? '' ); ?></textarea>
					</div>
				</div>
			</div>

			<div class="tsm-form-section">
				<div class="tsm-form-section-head">
					<span class="dashicons dashicons-calendar-alt"></span>
					<div>
						<h3><?php esc_html_e( 'Schedule', 'tradeschool-manager' ); ?></h3>
						<p><?php esc_html_e( 'When the program runs. Used to set tuition invoice due dates and to filter enrollments.', 'tradeschool-manager' ); ?></p>
					</div>
				</div>
				<div class="tsm-form-grid">
					<div class="tsm-field">
						<label><?php esc_html_e( 'Program Start Date', 'tradeschool-manager' ); ?></label>
						<input type="date" name="start_date" value="<?php echo esc_attr( $editing->start_date ?? '' ); ?>">
					</div>
					<div class="tsm-field">
						<label><?php esc_html_e( 'Program End Date', 'tradeschool-manager' ); ?></label>
						<input type="date" name="end_date" value="<?php echo esc_attr( $editing->end_date ?? '' ); ?>">
					</div>
				</div>
			</div>

			<div class="tsm-form-section">
				<div class="tsm-form-section-head">
					<span class="dashicons dashicons-money-alt"></span>
					<div>
						<h3><?php esc_html_e( 'Tuition & Billing', 'tradeschool-manager' ); ?></h3>
						<?php if ( function_exists( 'tsm_addon_active' ) && tsm_addon_active( 'invoicing' ) ) : ?>
							<p><?php esc_html_e( 'Defaults applied when generating a tuition contract or auto-invoicing on enrollment.', 'tradeschool-manager' ); ?></p>
						<?php else : ?>
							<p><?php esc_html_e( 'Displayed on the public catalog. Install the Invoicing & Contracts add-on to actually bill and collect.', 'tradeschool-manager' ); ?></p>
						<?php endif; ?>
					</div>
				</div>
				<div class="tsm-form-grid">
					<div class="tsm-field">
						<label><?php esc_html_e( 'Total Tuition', 'tradeschool-manager' ); ?></label>
						<input type="number" step="0.01" name="total_cost" value="<?php echo esc_attr( $editing->total_cost ?? '0.00' ); ?>">
					</div>
					<?php if ( function_exists( 'tsm_addon_active' ) && tsm_addon_active( 'invoicing' ) ) : ?>
					<div class="tsm-field">
						<label><?php esc_html_e( 'Default Billing', 'tradeschool-manager' ); ?></label>
						<select name="default_billing">
							<?php $b = $editing->default_billing ?? 'manual'; ?>
							<option value="manual" <?php selected( $b, 'manual' ); ?>><?php esc_html_e( 'Manual (no auto-invoice)', 'tradeschool-manager' ); ?></option>
							<option value="full"   <?php selected( $b, 'full' );   ?>><?php esc_html_e( 'Single full invoice', 'tradeschool-manager' ); ?></option>
							<option value="plan"   <?php selected( $b, 'plan' );   ?>><?php esc_html_e( 'Payment plan', 'tradeschool-manager' ); ?></option>
						</select>
					</div>
					<div class="tsm-field">
						<label><?php esc_html_e( 'Default Installments', 'tradeschool-manager' ); ?></label>
						<input type="number" min="1" name="default_installments" value="<?php echo esc_attr( $editing->default_installments ?? 1 ); ?>">
					</div>
					<div class="tsm-field">
						<label><?php esc_html_e( 'Default Frequency', 'tradeschool-manager' ); ?></label>
						<select name="default_frequency">
							<?php $f = $editing->default_frequency ?? 'monthly'; ?>
							<option value="weekly"    <?php selected( $f, 'weekly' );    ?>><?php esc_html_e( 'Weekly', 'tradeschool-manager' ); ?></option>
							<option value="biweekly"  <?php selected( $f, 'biweekly' );  ?>><?php esc_html_e( 'Every 2 weeks', 'tradeschool-manager' ); ?></option>
							<option value="monthly"   <?php selected( $f, 'monthly' );   ?>><?php esc_html_e( 'Monthly', 'tradeschool-manager' ); ?></option>
							<option value="quarterly" <?php selected( $f, 'quarterly' ); ?>><?php esc_html_e( 'Quarterly', 'tradeschool-manager' ); ?></option>
						</select>
					</div>
					<div class="tsm-field">
						<label><?php esc_html_e( 'Default Deposit', 'tradeschool-manager' ); ?></label>
						<input type="number" min="0" step="0.01" name="default_deposit" value="<?php echo esc_attr( $editing->default_deposit ?? '0.00' ); ?>">
						<p class="description"><?php esc_html_e( 'Subtracted from principal before financing.', 'tradeschool-manager' ); ?></p>
					</div>
					<div class="tsm-field">
						<label><?php esc_html_e( 'Application Fee', 'tradeschool-manager' ); ?></label>
						<input type="number" min="0" step="0.01" name="default_application_fee" value="<?php echo esc_attr( $editing->default_application_fee ?? '0.00' ); ?>">
						<p class="description"><?php esc_html_e( 'One-time fee invoiced from the application view. 0 disables.', 'tradeschool-manager' ); ?></p>
					</div>
					<div class="tsm-field tsm-field-wide">
						<label><?php esc_html_e( 'Default Financing Fee', 'tradeschool-manager' ); ?></label>
						<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;">
							<?php $fm = $editing->default_fee_mode ?? 'none'; ?>
							<select name="default_fee_mode" style="max-width:260px;">
								<option value="none"    <?php selected( $fm, 'none' );    ?>><?php esc_html_e( 'No financing fee', 'tradeschool-manager' ); ?></option>
								<option value="flat"    <?php selected( $fm, 'flat' );    ?>><?php esc_html_e( 'Flat dollar fee', 'tradeschool-manager' ); ?></option>
								<option value="percent" <?php selected( $fm, 'percent' ); ?>><?php esc_html_e( 'Percent of amount financed', 'tradeschool-manager' ); ?></option>
							</select>
							<label style="font-weight:400;text-transform:none;letter-spacing:0;font-size:13px;"><?php esc_html_e( 'Fee value:', 'tradeschool-manager' ); ?>
								<input type="number" min="0" step="0.01" name="default_fee_value" style="width:110px" value="<?php echo esc_attr( $editing->default_fee_value ?? 0 ); ?>"></label>
						</div>
						<p class="description"><?php esc_html_e( 'One-time fee added to the financed amount and split across installments. NOT interest  -  no APR is disclosed or charged.', 'tradeschool-manager' ); ?></p>
					</div>
					<div class="tsm-field tsm-field-wide">
						<label><?php esc_html_e( 'Invoice Due Date Policy', 'tradeschool-manager' ); ?></label>
						<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;">
							<?php $ddp = $editing->due_date_policy ?? 'on_start'; ?>
							<select name="due_date_policy" style="max-width:340px;">
								<option value="on_start"          <?php selected( $ddp, 'on_start' ); ?>><?php esc_html_e( 'Due on first day of class (Program Start Date)', 'tradeschool-manager' ); ?></option>
								<option value="on_approval"       <?php selected( $ddp, 'on_approval' ); ?>><?php esc_html_e( 'Due immediately on approval', 'tradeschool-manager' ); ?></option>
								<option value="days_before_start" <?php selected( $ddp, 'days_before_start' ); ?>><?php esc_html_e( 'Due N days before start date', 'tradeschool-manager' ); ?></option>
							</select>
							<label style="font-weight:400;text-transform:none;letter-spacing:0;font-size:13px;"><?php esc_html_e( 'Days before:', 'tradeschool-manager' ); ?>
								<input type="number" min="0" name="due_date_offset_days" style="width:80px" value="<?php echo esc_attr( $editing->due_date_offset_days ?? 0 ); ?>"></label>
						</div>
						<p class="description"><?php esc_html_e( 'Controls when the auto-generated tuition invoice (or first installment) is due on enrollment.', 'tradeschool-manager' ); ?></p>
					</div>
					<?php endif; ?>
				</div>
			</div>

			<div class="tsm-form-section">
				<div class="tsm-form-section-head">
					<span class="dashicons dashicons-welcome-learn-more"></span>
					<div>
						<h3><?php esc_html_e( 'Classes In This Program', 'tradeschool-manager' ); ?></h3>
						<p><?php esc_html_e( 'Students enrolled into the program will be cascade-enrolled into every checked class.', 'tradeschool-manager' ); ?></p>
					</div>
				</div>
				<input type="hidden" name="program_class_year_scope" value="<?php echo esc_attr( $active_year_id ); ?>">
				<?php
				$year_label = '';
				if ( $active_year_id && class_exists( 'TSM_School_Years' ) ) {
					$yr = TSM_School_Years::get( $active_year_id );
					if ( $yr ) { $year_label = $yr->name; }
				}
				if ( $year_label ) : ?>
					<p class="description" style="margin:0 0 10px;">
						<?php /* translators: %s: school year label */ printf( esc_html__( 'Showing classes for %s. Switch the active school year (top bar) to manage classes from another year  -  other years\' attachments are preserved.', 'tradeschool-manager' ), '<strong>' . esc_html( $year_label ) . '</strong>' ); ?>
					</p>
				<?php else : ?>
					<p class="description" style="margin:0 0 10px;"><?php esc_html_e( 'Showing classes from all years.', 'tradeschool-manager' ); ?></p>
				<?php endif; ?>
				<?php if ( empty( $all_classes ) ) : ?>
					<em><?php esc_html_e( 'No classes for this year  -  create classes first or switch years.', 'tradeschool-manager' ); ?></em>
				<?php else : ?>
					<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:6px 14px;">
					<?php foreach ( $all_classes as $c ) : ?>
						<label style="font-weight:400;text-transform:none;letter-spacing:0;">
							<input type="checkbox" name="program_class_ids[]" value="<?php echo esc_attr( $c->id ); ?>" <?php checked( ! empty( $attached_ids[ (int) $c->id ] ) ); ?>>
							<?php echo esc_html( $c->name ); ?>
						</label>
					<?php endforeach; ?>
					</div>
				<?php endif; ?>

				<?php
				ob_start();
				do_action( 'tradeschool_manager_after_program_form_fields', $editing );
				$prog_extra = trim( ob_get_clean() );
				if ( '' !== $prog_extra ) {
					echo '<table class="form-table tsm-extra-fields" style="margin-top:14px;">' . wp_kses_post( $prog_extra ) . '</table>';
				}
				?>
			</div>

			<div class="tsm-form-actions">
				<button class="button button-primary button-hero"><?php echo $editing ? esc_html__( 'Update Program', 'tradeschool-manager' ) : esc_html__( 'Create Program', 'tradeschool-manager' ); ?></button>
				<?php if ( $editing ) : ?><a class="button" href="<?php echo esc_url( admin_url( 'admin.php?page=tsm-programs' ) ); ?>"><?php esc_html_e( 'Cancel', 'tradeschool-manager' ); ?></a><?php endif; ?>
			</div>
		</form>
		<?php endif; ?>

		<?php if ( $editing && class_exists( 'TSM_Student_Docs' ) ) { $this->render_program_doc_types_panel( $editing ); } ?>

		<?php endif; /* end add tab */ ?>

		<?php if ( 'all' === $tab ) : ?>
		<h2><?php esc_html_e( 'All Programs', 'tradeschool-manager' ); ?>
			<a href="<?php echo esc_url( admin_url( 'admin.php?page=tsm-programs&tab=add' ) ); ?>" class="page-title-action"><?php esc_html_e( 'Add New', 'tradeschool-manager' ); ?></a>
		</h2>
		<table class="widefat striped">
			<thead><tr>
				<th><?php esc_html_e( 'Name', 'tradeschool-manager' ); ?></th>
				<th><?php esc_html_e( 'Code', 'tradeschool-manager' ); ?></th>
				<th><?php esc_html_e( 'Tuition', 'tradeschool-manager' ); ?></th>
				<th><?php esc_html_e( 'Classes', 'tradeschool-manager' ); ?></th>
				<th><?php esc_html_e( 'Enrollments', 'tradeschool-manager' ); ?></th>
				<th><?php esc_html_e( 'Status', 'tradeschool-manager' ); ?></th>
				<th></th>
			</tr></thead>
			<tbody>
			<?php if ( empty( $programs ) ) : ?>
				<tr><td colspan="7"><?php esc_html_e( 'No programs yet.', 'tradeschool-manager' ); ?></td></tr>
			<?php endif; ?>
			<?php foreach ( $programs as $p ) :
				$cls = TSM_Programs::classes_for( $p->id, $active_year_id );
				$enr = TSM_Programs::enrollments_for_program( $p->id );
			?>
				<tr>
					<td><strong><?php echo esc_html( $p->name ); ?></strong></td>
					<td><?php echo esc_html( $p->code ); ?></td>
					<td>$<?php echo esc_html( number_format_i18n( (float) $p->total_cost, 2 ) ); ?></td>
					<td><?php echo (int) count( $cls ); ?></td>
					<td><?php echo (int) count( $enr ); ?></td>
					<td><?php echo esc_html( ucfirst( $p->status ) ); ?></td>
					<td>
						<a class="button button-small" href="<?php echo esc_url( add_query_arg( array( 'page' => 'tsm-programs', 'edit' => $p->id ), admin_url( 'admin.php' ) ) ); ?>"><?php esc_html_e( 'Edit', 'tradeschool-manager' ); ?></a>
						<?php if ( empty( $p->completed_at ) ) : ?>
							<a class="button button-small" href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'tsm_action' => 'complete_program', 'id' => $p->id ) ), 'tsm_complete_program_' . $p->id ) ); ?>" onclick="return confirm('<?php echo esc_attr__( 'Mark this program completed?', 'tradeschool-manager' ); ?>')"><?php esc_html_e( 'Mark Completed', 'tradeschool-manager' ); ?></a>
						<?php else : ?>
							<span class="description" style="color:#0a7;"><?php esc_html_e( 'Completed', 'tradeschool-manager' ); ?></span>
						<?php endif; ?>
						<a class="button button-small" href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'tsm_action' => 'delete_program', 'id' => $p->id ) ), 'tsm_delete_program_' . $p->id ) ); ?>" onclick="return confirm('<?php esc_attr_e( 'Delete this program? Existing enrollments and invoices are kept.', 'tradeschool-manager' ); ?>')"><?php esc_html_e( 'Delete', 'tradeschool-manager' ); ?></a>
					</td>
				</tr>
			<?php endforeach; ?>
			</tbody>
		</table>
		<?php endif; ?>
		<?php
		$this->shell_close();
	}

	public function page_tuition_audit() {
		$this->shell_open( __( 'Tuition Audit', 'tradeschool-manager' ) );
		if ( ! ( function_exists( 'tsm_addon_active' ) && tsm_addon_active( 'invoicing' ) ) ) {
			echo '<div class="notice notice-info"><p>' . esc_html__( 'Tuition Audit requires the Invoicing & Contracts add-on.', 'tradeschool-manager' ) . '</p></div>';
			$this->shell_close();
			return;
		}
		$rows = TSM_Programs::audit_rows();
		$grand = array( 'tuition' => 0, 'paid' => 0, 'outstanding' => 0 );
		?>
		<p class="description"><?php esc_html_e( 'Roll-up of every student with at least one program enrollment. Outstanding = total tuition minus amount paid across all programs.', 'tradeschool-manager' ); ?></p>
		<table class="widefat striped">
			<thead><tr>
				<th><?php esc_html_e( 'Student', 'tradeschool-manager' ); ?></th>
				<th><?php esc_html_e( 'Programs', 'tradeschool-manager' ); ?></th>
				<th><?php esc_html_e( 'Tuition', 'tradeschool-manager' ); ?></th>
				<th><?php esc_html_e( 'Paid', 'tradeschool-manager' ); ?></th>
				<th><?php esc_html_e( 'Outstanding', 'tradeschool-manager' ); ?></th>
				<th></th>
			</tr></thead>
			<tbody>
			<?php if ( empty( $rows ) ) : ?>
				<tr><td colspan="6"><?php esc_html_e( 'No program enrollments yet.', 'tradeschool-manager' ); ?></td></tr>
			<?php endif; ?>
			<?php foreach ( $rows as $r ) :
				$grand['tuition']     += $r['rollup']['tuition'];
				$grand['paid']        += $r['rollup']['paid'];
				$grand['outstanding'] += $r['rollup']['outstanding'];
				$names = array();
				foreach ( $r['rollup']['enrollments'] as $en ) { $names[] = $en['enrollment']->program_name; }
			?>
				<tr>
					<td><strong><?php echo esc_html( $r['student']->last_name . ', ' . $r['student']->first_name ); ?></strong></td>
					<td><?php echo esc_html( implode( ', ', array_filter( $names ) ) ); ?></td>
					<td>$<?php echo esc_html( number_format_i18n( $r['rollup']['tuition'], 2 ) ); ?></td>
					<td>$<?php echo esc_html( number_format_i18n( $r['rollup']['paid'], 2 ) ); ?></td>
					<td><strong>$<?php echo esc_html( number_format_i18n( $r['rollup']['outstanding'], 2 ) ); ?></strong></td>
					<td><a class="button button-small" href="<?php echo esc_url( admin_url( 'admin.php?page=tsm-students&edit=' . $r['student']->id ) ); ?>"><?php esc_html_e( 'Open', 'tradeschool-manager' ); ?></a></td>
				</tr>
			<?php endforeach; ?>
			</tbody>
			<tfoot><tr>
				<th colspan="2"><?php esc_html_e( 'Totals', 'tradeschool-manager' ); ?></th>
				<th>$<?php echo esc_html( number_format_i18n( $grand['tuition'], 2 ) ); ?></th>
				<th>$<?php echo esc_html( number_format_i18n( $grand['paid'], 2 ) ); ?></th>
				<th>$<?php echo esc_html( number_format_i18n( $grand['outstanding'], 2 ) ); ?></th>
				<th></th>
			</tr></tfoot>
		</table>
		<?php
		$this->shell_close();
	}

	/**
	 * Render the Programs enrollment panel under the student edit form.
	 */
	/**
	 * Top-of-page hero card showing photo, name, contact, status pills.
	 */
	private function render_student_header_card( $student ) {
		$enrollments = class_exists( 'TSM_Programs' ) ? TSM_Programs::enrollments_for_student( $student->id ) : array();
		$classes     = TSM_Classes::classes_for_student( $student->id );
		$is_dropped  = 'dropped' === ( $student->status ?? '' );
		?>
		<div class="tsm-student-header">
			<div class="tsm-student-header__photo">
				<?php if ( ! empty( $student->photo_url ) ) : ?>
					<img src="<?php echo esc_url( $student->photo_url ); ?>" alt="">
				<?php else : ?>
					<div class="tsm-student-header__initials"><?php echo esc_html( strtoupper( substr( $student->first_name ?? '?', 0, 1 ) . substr( $student->last_name ?? '', 0, 1 ) ) ); ?></div>
				<?php endif; ?>
			</div>
			<div class="tsm-student-header__main">
				<h1><?php echo esc_html( trim( ( $student->first_name ?? '' ) . ' ' . ( $student->last_name ?? '' ) ) ); ?>
					<?php if ( $is_dropped ) : ?>
						<span class="tsm-status-pill tsm-status-denied" style="vertical-align:middle;margin-left:8px;font-size:12px;"><?php esc_html_e( 'Dropped', 'tradeschool-manager' ); ?></span>
					<?php else : ?>
						<span class="tsm-status-pill tsm-status-approved" style="vertical-align:middle;margin-left:8px;font-size:12px;"><?php esc_html_e( 'Active', 'tradeschool-manager' ); ?></span>
					<?php endif; ?>
				</h1>
				<div class="tsm-student-header__meta">
					<?php if ( ! empty( $student->email ) ) : ?><span><span class="dashicons dashicons-email-alt"></span> <a href="mailto:<?php echo esc_attr( $student->email ); ?>"><?php echo esc_html( $student->email ); ?></a></span><?php endif; ?>
					<?php if ( ! empty( $student->phone ) ) : ?><span><span class="dashicons dashicons-phone"></span> <?php echo esc_html( tsm_format_phone( $student->phone ) ); ?></span><?php endif; ?>
					<?php if ( ! empty( $student->username ) ) : ?><span><span class="dashicons dashicons-admin-users"></span> <?php echo esc_html( $student->username ); ?></span><?php endif; ?>
				</div>
				<div class="tsm-student-header__stats">
					<div><strong><?php echo (int) count( $enrollments ); ?></strong> <?php echo esc_html( _n( 'Program', 'Programs', count( $enrollments ), 'tradeschool-manager' ) ); ?></div>
					<div><strong><?php echo (int) count( $classes ); ?></strong> <?php echo esc_html( _n( 'Class', 'Classes', count( $classes ), 'tradeschool-manager' ) ); ?></div>
				</div>
			</div>
		</div>
		<?php
	}

	/**
	 * Sidebar: compact program enrollments list.
	 */
	private function render_student_programs_compact( $student ) {
		$enrollments = TSM_Programs::enrollments_for_student( $student->id );
		$inv_on = function_exists( 'tsm_addon_active' ) && tsm_addon_active( 'invoicing' );
		?>
		<div class="tsm-side-card">
			<div class="tsm-side-card__head">
				<h3><?php esc_html_e( 'Programs', 'tradeschool-manager' ); ?></h3>
				<a href="#tsm-enrollments" class="tsm-side-card__link"><?php esc_html_e( 'Manage', 'tradeschool-manager' ); ?> </a>
			</div>
			<div class="tsm-side-card__body">
				<?php if ( empty( $enrollments ) ) : ?>
					<p class="tsm-empty"><?php esc_html_e( 'Not enrolled in any programs yet.', 'tradeschool-manager' ); ?></p>
				<?php else : foreach ( $enrollments as $e ) :
					$bal  = $inv_on ? TSM_Programs::balance_for_enrollment( $e->id ) : null;
					$year = $e->school_year_id ? TSM_School_Years::get( $e->school_year_id ) : null;
				?>
					<div class="tsm-side-row">
						<div class="tsm-side-row__main">
							<strong><?php echo esc_html( $e->program_name ); ?></strong>
							<div class="tsm-side-row__sub">
								<?php echo esc_html( $year ? $year->name : ' - ' ); ?>
								<span class="tsm-status-pill <?php echo 'completed' === $e->status ? 'tsm-status-approved' : ( 'active' === $e->status ? 'tsm-status-new' : 'tsm-status-default' ); ?>"><?php echo esc_html( ucfirst( $e->status ) ); ?></span>
							</div>
						</div>
						<?php if ( $inv_on ) : ?>
							<div class="tsm-side-row__amount">
								<span class="tsm-muted" style="font-size:11px;"><?php esc_html_e( 'Balance', 'tradeschool-manager' ); ?></span><br>
								<strong>$<?php echo esc_html( number_format_i18n( $bal['outstanding'], 2 ) ); ?></strong>
							</div>
						<?php endif; ?>
					</div>
				<?php endforeach; endif; ?>
			</div>
		</div>
		<?php
	}

	/**
	 * Sidebar: enrolled classes list (and a quick-enroll link).
	 */
	private function render_student_classes_compact( $student ) {
		$classes = TSM_Classes::classes_for_student( $student->id );
		?>
		<div class="tsm-side-card">
			<div class="tsm-side-card__head">
				<h3><?php esc_html_e( 'Classes', 'tradeschool-manager' ); ?></h3>
				<a href="<?php echo esc_url( admin_url( 'admin.php?page=tsm-classes' ) ); ?>" class="tsm-side-card__link"><?php esc_html_e( 'All classes', 'tradeschool-manager' ); ?> </a>
			</div>
			<div class="tsm-side-card__body">
				<?php if ( empty( $classes ) ) : ?>
					<p class="tsm-empty"><?php esc_html_e( 'Not enrolled in any classes yet. Open a class and use the Enroll Students panel to add this student.', 'tradeschool-manager' ); ?></p>
				<?php else : foreach ( $classes as $c ) :
					$year = ! empty( $c->school_year_id ) ? TSM_School_Years::get( $c->school_year_id ) : null;
				?>
					<div class="tsm-side-row">
						<div class="tsm-side-row__main">
							<a href="<?php echo esc_url( admin_url( 'admin.php?page=tsm-classes&edit=' . (int) $c->id ) ); ?>"><strong><?php echo esc_html( $c->name ); ?></strong></a>
							<div class="tsm-side-row__sub">
								<?php echo esc_html( $year ? $year->name : ' - ' ); ?>
								<?php if ( ! empty( $c->status ) ) : ?>
									<span class="tsm-status-pill <?php echo 'completed' === $c->status ? 'tsm-status-approved' : ( 'active' === $c->status ? 'tsm-status-new' : 'tsm-status-default' ); ?>"><?php echo esc_html( ucfirst( $c->status ) ); ?></span>
								<?php endif; ?>
							</div>
						</div>
					</div>
				<?php endforeach; endif; ?>
			</div>
		</div>
		<?php
	}

	/**
	 * Sidebar: drop / reinstate card.
	 */
	private function render_student_drop_card( $student ) {
		$is_dropped = 'dropped' === ( $student->status ?? '' );
		if ( ! $is_dropped ) : ?>
			<div class="tsm-side-card tsm-side-card--danger">
				<div class="tsm-side-card__head"><h3><?php esc_html_e( 'Drop Student', 'tradeschool-manager' ); ?></h3></div>
				<div class="tsm-side-card__body">
					<p class="description" style="margin-top:0;"><?php esc_html_e( 'Marks the student inactive. All grades, attendance, and payment records are preserved.', 'tradeschool-manager' ); ?></p>
					<form method="post" onsubmit="return confirm('<?php esc_attr_e( 'Drop this student? Their records will be preserved.', 'tradeschool-manager' ); ?>');">
						<?php wp_nonce_field( 'tsm_drop_student_' . $student->id ); ?>
						<input type="hidden" name="tsm_action" value="drop_student" />
						<input type="hidden" name="id" value="<?php echo esc_attr( $student->id ); ?>" />
						<p><label><strong><?php esc_html_e( 'Drop Date', 'tradeschool-manager' ); ?></strong><br>
							<input type="date" name="dropped_date" value="<?php echo esc_attr( current_time( 'Y-m-d' ) ); ?>" required style="width:100%;"></label></p>
						<p><label><strong><?php esc_html_e( 'Reason', 'tradeschool-manager' ); ?></strong><br>
							<textarea name="dropped_reason" rows="3" style="width:100%;" placeholder="<?php esc_attr_e( 'Optional notes', 'tradeschool-manager' ); ?>"></textarea></label></p>
						<p style="margin-bottom:0;"><button class="button" style="background:#b32d2d;border-color:#a02828;color:#fff;width:100%;"><?php esc_html_e( 'Drop Student', 'tradeschool-manager' ); ?></button></p>
					</form>
				</div>
			</div>
		<?php else : ?>
			<div class="tsm-side-card" style="border-color:#c2dcf0;background:#f0f7fc;">
				<div class="tsm-side-card__head"><h3><?php esc_html_e( 'Dropped', 'tradeschool-manager' ); ?></h3></div>
				<div class="tsm-side-card__body">
					<p style="margin-top:0;"><strong><?php esc_html_e( 'Date:', 'tradeschool-manager' ); ?></strong> <?php echo esc_html( $student->dropped_date ?: ' - ' ); ?></p>
					<?php if ( ! empty( $student->dropped_reason ) ) : ?>
						<p><strong><?php esc_html_e( 'Reason:', 'tradeschool-manager' ); ?></strong><br><?php echo nl2br( esc_html( $student->dropped_reason ) ); ?></p>
					<?php endif; ?>
					<form method="post">
						<?php wp_nonce_field( 'tsm_reinstate_student_' . $student->id ); ?>
						<input type="hidden" name="tsm_action" value="reinstate_student" />
						<input type="hidden" name="id" value="<?php echo esc_attr( $student->id ); ?>" />
						<p style="margin-bottom:0;"><button class="button button-primary" style="width:100%;"><?php esc_html_e( 'Reinstate Student', 'tradeschool-manager' ); ?></button></p>
					</form>
				</div>
			</div>
		<?php endif;
	}

	private function render_student_enrollments_panel( $student ) {
		$enrollments = TSM_Programs::enrollments_for_student( $student->id );
		$programs    = TSM_Programs::all( array( 'active' => true ) );
		$years       = TSM_School_Years::all();
		$editing_enr_id = absint( $_GET['edit_enrollment'] ?? 0 ); // phpcs:ignore
		$inv_on = function_exists( 'tsm_addon_active' ) && tsm_addon_active( 'invoicing' );
		?>
		<div class="tsm-card" id="tsm-enrollments" style="margin-top:24px;">
			<h2><?php esc_html_e( 'Program Enrollments', 'tradeschool-manager' ); ?></h2>
			<?php if ( $inv_on ) : ?><p class="description"><?php esc_html_e( 'Edit terms to adjust the tuition, deposit, financing fee, and payment schedule for an active enrollment. If a tuition contract has been signed, click "Sync from contract" to make the payment plan match the signed agreement exactly.', 'tradeschool-manager' ); ?></p><?php endif; ?>
			<table class="widefat striped">
				<thead><tr>
					<th><?php esc_html_e( 'Program', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'School Year', 'tradeschool-manager' ); ?></th>
					<?php if ( $inv_on ) : ?>
					<th><?php esc_html_e( 'Tuition', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'Plan', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'Billed', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'Paid', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'Outstanding', 'tradeschool-manager' ); ?></th>
					<?php endif; ?>
					<th><?php esc_html_e( 'Status', 'tradeschool-manager' ); ?></th>
					<th></th>
				</tr></thead>
				<tbody>
				<?php if ( empty( $enrollments ) ) : ?>
					<tr><td colspan="9"><em><?php esc_html_e( 'Not enrolled in any programs yet.', 'tradeschool-manager' ); ?></em></td></tr>
				<?php endif; ?>
				<?php foreach ( $enrollments as $e ) :
					$bal = TSM_Programs::balance_for_enrollment( $e->id );
					$year = $e->school_year_id ? TSM_School_Years::get( $e->school_year_id ) : null;
					$signed = class_exists( 'TSM_Contracts' ) ? TSM_Contracts::signed_for_student( $student->id, $e->program_id ) : array();
				?>
					<tr>
						<td><strong><?php echo esc_html( $e->program_name ); ?></strong><?php if ( $e->program_code ) echo ' <code>' . esc_html( $e->program_code ) . '</code>'; ?>
							<?php if ( ! empty( $e->contract_id ) ) : ?><br><small style="color:#0a7;"><?php /* translators: %d contract id */ printf( esc_html__( 'Synced to Contract #%d', 'tradeschool-manager' ), (int) $e->contract_id ); ?></small><?php endif; ?>
						</td>
						<td><?php echo esc_html( $year ? $year->name : ' - ' ); ?></td>
						<?php if ( $inv_on ) : ?>
						<td>$<?php echo esc_html( number_format_i18n( $bal['tuition'], 2 ) ); ?></td>
						<td><?php
							if ( 'plan' === $e->billing_mode ) {
								/* translators: 1: number of installments, 2: frequency */
								printf( esc_html__( '%1$d x %2$s', 'tradeschool-manager' ), (int) ( $e->installments ?: 1 ), esc_html( $e->frequency ?: 'monthly' ) );
							} elseif ( 'full' === $e->billing_mode ) {
								esc_html_e( 'Full invoice', 'tradeschool-manager' );
							} else {
								esc_html_e( 'Manual', 'tradeschool-manager' );
							}
						?></td>
						<td>$<?php echo esc_html( number_format_i18n( $bal['billed'], 2 ) ); ?></td>
						<td>$<?php echo esc_html( number_format_i18n( $bal['paid'], 2 ) ); ?></td>
						<td><strong>$<?php echo esc_html( number_format_i18n( $bal['outstanding'], 2 ) ); ?></strong></td>
						<?php endif; ?>
						<td><?php echo esc_html( ucfirst( $e->status ) ); ?></td>
						<td>
							<?php if ( $inv_on ) : ?>
							<a class="button button-small" href="<?php echo esc_url( add_query_arg( array( 'page' => 'tsm-students', 'edit' => $student->id, 'edit_enrollment' => $e->id ), admin_url( 'admin.php' ) ) ); ?>#tsm-enr-edit"><?php esc_html_e( 'Edit terms', 'tradeschool-manager' ); ?></a>
							<?php if ( ! empty( $signed ) ) :
								$first = $signed[0]; ?>
								<a class="button button-small button-primary" href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'tsm_action' => 'sync_enrollment_contract', 'enrollment_id' => $e->id, 'contract_id' => $first->id ) ), 'tsm_sync_enrollment_' . $e->id ) ); ?>"
								   onclick="return confirm('<?php esc_attr_e( 'Replace the unpaid invoices for this enrollment with the schedule from the signed contract?', 'tradeschool-manager' ); ?>')"
								   title="<?php /* translators: %d contract id */ printf( esc_attr__( 'Sync to Contract #%d', 'tradeschool-manager' ), (int) $first->id ); ?>"><?php esc_html_e( 'Sync from contract', 'tradeschool-manager' ); ?></a>
							<?php endif; ?>
							<?php endif; ?>
							<?php if ( 'active' === $e->status ) : ?>
								<a class="button button-small" href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'tsm_action' => 'update_enrollment_status', 'enrollment_id' => $e->id, 'status' => 'completed' ) ), 'tsm_update_enrollment_' . $e->id ) ); ?>"><?php esc_html_e( 'Complete', 'tradeschool-manager' ); ?></a>
							<?php endif; ?>
							<a class="button button-small" href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'tsm_action' => 'unenroll_program', 'enrollment_id' => $e->id ) ), 'tsm_unenroll_program_' . $e->id ) ); ?>" onclick="return confirm('<?php esc_attr_e( 'Remove this enrollment? Existing invoices remain.', 'tradeschool-manager' ); ?>')"><?php esc_html_e( 'Remove', 'tradeschool-manager' ); ?></a>
						</td>
					</tr>
				<?php endforeach; ?>
				</tbody>
			</table>

			<?php
			// v1.10.29  -  inline edit-terms form.
			$editing_enr = $editing_enr_id ? TSM_Programs::get_enrollment( $editing_enr_id ) : null;
			if ( $inv_on && $editing_enr && (int) $editing_enr->student_id === (int) $student->id ) :
			?>
				<h3 id="tsm-enr-edit" style="margin-top:18px;"><?php /* translators: %s program name */ printf( esc_html__( 'Edit Enrollment Terms  -  %s', 'tradeschool-manager' ), esc_html( TSM_Programs::get( $editing_enr->program_id )->name ?? '' ) ); ?></h3>
				<p class="description" style="margin-top:0;"><?php esc_html_e( 'Saving will delete any unpaid, no-payment invoices for this enrollment and regenerate them using the new terms. Invoices that already received a payment are left untouched.', 'tradeschool-manager' ); ?></p>
				<form method="post">
					<?php wp_nonce_field( 'tsm_edit_enrollment_terms_' . $editing_enr->id ); ?>
					<input type="hidden" name="tsm_action" value="edit_enrollment_terms">
					<input type="hidden" name="enrollment_id" value="<?php echo (int) $editing_enr->id; ?>">
					<table class="form-table">
						<tr><th><?php esc_html_e( 'Total Tuition', 'tradeschool-manager' ); ?></th>
							<td><input type="number" step="0.01" min="0" name="tuition_amount" value="<?php echo esc_attr( $editing_enr->tuition_amount ); ?>"></td></tr>
						<tr><th><?php esc_html_e( 'Deposit', 'tradeschool-manager' ); ?></th>
							<td><input type="number" step="0.01" min="0" name="deposit" value="<?php echo esc_attr( $editing_enr->deposit ?? 0 ); ?>"></td></tr>
						<tr><th><?php esc_html_e( 'Financing Fee', 'tradeschool-manager' ); ?></th>
							<td><select name="fee_mode">
								<?php foreach ( array( 'none' => 'No fee', 'flat' => 'Flat $', 'percent' => '% of financed' ) as $mk => $ml ) : ?>
									<option value="<?php echo esc_attr( $mk ); ?>" <?php selected( $editing_enr->fee_mode ?? 'none', $mk ); ?>><?php echo esc_html( $ml ); ?></option>
								<?php endforeach; ?>
							</select>
							<input type="number" step="0.01" min="0" name="fee_value" style="width:120px;" value="<?php echo esc_attr( $editing_enr->fee_value ?? 0 ); ?>">
							<p class="description"><?php esc_html_e( 'A one-time fee added to the remaining balance and split evenly across installments. Disclosed as a financing fee  -  not interest.', 'tradeschool-manager' ); ?></p></td></tr>
						<tr><th><?php esc_html_e( 'Billing Mode', 'tradeschool-manager' ); ?></th>
							<td><select name="billing_mode">
								<option value="manual" <?php selected( $editing_enr->billing_mode, 'manual' ); ?>><?php esc_html_e( 'Manual (no auto-invoice)', 'tradeschool-manager' ); ?></option>
								<option value="full"   <?php selected( $editing_enr->billing_mode, 'full' );   ?>><?php esc_html_e( 'Single full invoice', 'tradeschool-manager' ); ?></option>
								<option value="plan"   <?php selected( $editing_enr->billing_mode, 'plan' );   ?>><?php esc_html_e( 'Payment plan', 'tradeschool-manager' ); ?></option>
							</select></td></tr>
						<tr><th><?php esc_html_e( 'Installments', 'tradeschool-manager' ); ?></th>
							<td><input type="number" min="1" name="installments" value="<?php echo esc_attr( $editing_enr->installments ?: 1 ); ?>"></td></tr>
						<tr><th><?php esc_html_e( 'Frequency', 'tradeschool-manager' ); ?></th>
							<td><select name="frequency">
								<?php foreach ( array( 'weekly' => 'Weekly', 'biweekly' => 'Every 2 weeks', 'monthly' => 'Monthly', 'quarterly' => 'Quarterly' ) as $fk => $fl ) : ?>
									<option value="<?php echo esc_attr( $fk ); ?>" <?php selected( $editing_enr->frequency ?: 'monthly', $fk ); ?>><?php echo esc_html( $fl ); ?></option>
								<?php endforeach; ?>
							</select></td></tr>
						<tr><th><?php esc_html_e( 'First Payment Date', 'tradeschool-manager' ); ?></th>
							<td><input type="date" name="first_payment_date" value="<?php echo esc_attr( $editing_enr->first_payment_date ?: current_time( 'Y-m-d' ) ); ?>"></td></tr>
					</table>
					<p>
						<button class="button button-primary"><?php esc_html_e( 'Save & Regenerate Invoices', 'tradeschool-manager' ); ?></button>
						<a class="button" href="<?php echo esc_url( admin_url( 'admin.php?page=tsm-students&edit=' . $student->id ) ); ?>"><?php esc_html_e( 'Cancel', 'tradeschool-manager' ); ?></a>
					</p>
				</form>
			<?php endif; ?>

			<h3 style="margin-top:18px;"><?php esc_html_e( 'Enroll in a Program', 'tradeschool-manager' ); ?></h3>
			<?php $enr_locked = class_exists( 'TSM_License' ) && TSM_License::is_creation_locked();
			if ( $enr_locked ) : TSM_License::render_lock_notice( __( 'program enrollments', 'tradeschool-manager' ) );
			elseif ( empty( $programs ) ) : ?>
				<p><em><?php esc_html_e( 'No active programs yet.', 'tradeschool-manager' ); ?></em> <a href="<?php echo esc_url( admin_url( 'admin.php?page=tsm-programs' ) ); ?>"><?php esc_html_e( 'Create one ', 'tradeschool-manager' ); ?></a></p>
			<?php else : ?>
				<form method="post">
					<?php wp_nonce_field( 'tsm_enroll_program' ); ?>
					<input type="hidden" name="tsm_action" value="enroll_program">
					<input type="hidden" name="student_id" value="<?php echo esc_attr( $student->id ); ?>">
					<table class="form-table">
						<tr><th><?php esc_html_e( 'Program', 'tradeschool-manager' ); ?></th>
							<td><select name="program_id" required id="tsm-enr-program">
								<option value=""> - </option>
								<?php foreach ( $programs as $p ) : ?>
									<option value="<?php echo esc_attr( $p->id ); ?>"
										data-cost="<?php echo esc_attr( $p->total_cost ); ?>"
										data-billing="<?php echo esc_attr( $p->default_billing ); ?>"
										data-installments="<?php echo esc_attr( $p->default_installments ); ?>"
										data-frequency="<?php echo esc_attr( $p->default_frequency ); ?>"
										data-deposit="<?php echo esc_attr( $p->default_deposit ?? 0 ); ?>"
										data-fee-mode="<?php echo esc_attr( $p->default_fee_mode ?? 'none' ); ?>"
										data-fee-value="<?php echo esc_attr( $p->default_fee_value ?? 0 ); ?>"><?php echo esc_html( $p->name ); ?>  -  $<?php echo esc_html( number_format_i18n( $p->total_cost, 2 ) ); ?></option>
								<?php endforeach; ?>
							</select></td></tr>
						<tr><th><?php esc_html_e( 'School Year', 'tradeschool-manager' ); ?></th>
							<td><select name="school_year_id">
								<option value=""> - </option>
								<?php foreach ( $years as $y ) : ?>
									<option value="<?php echo esc_attr( $y->id ); ?>"><?php echo esc_html( $y->name ); ?></option>
								<?php endforeach; ?>
							</select></td></tr>
						<?php if ( $inv_on ) : ?>
						<tr><th><?php esc_html_e( 'Tuition (override)', 'tradeschool-manager' ); ?></th>
							<td><input type="number" step="0.01" name="tuition_amount" id="tsm-enr-tuition" placeholder="<?php esc_attr_e( 'Defaults to program total', 'tradeschool-manager' ); ?>"></td></tr>
						<tr><th><?php esc_html_e( 'Deposit', 'tradeschool-manager' ); ?></th>
							<td><input type="number" step="0.01" min="0" name="deposit" id="tsm-enr-deposit" value="0"></td></tr>
						<tr><th><?php esc_html_e( 'Financing Fee', 'tradeschool-manager' ); ?></th>
							<td><select name="fee_mode" id="tsm-enr-feemode">
								<?php foreach ( array( 'none' => 'No fee', 'flat' => 'Flat $', 'percent' => '% of financed' ) as $mk => $ml ) : ?>
									<option value="<?php echo esc_attr( $mk ); ?>"><?php echo esc_html( $ml ); ?></option>
								<?php endforeach; ?>
							</select>
							<input type="number" step="0.01" min="0" name="fee_value" id="tsm-enr-feeval" style="width:120px;" value="0"></td></tr>
						<tr><th><?php esc_html_e( 'Billing', 'tradeschool-manager' ); ?></th>
							<td><select name="billing_mode" id="tsm-bill-mode">
								<option value="manual"><?php esc_html_e( 'Manual (no invoice now)', 'tradeschool-manager' ); ?></option>
								<option value="full"><?php esc_html_e( 'Bill in full', 'tradeschool-manager' ); ?></option>
								<option value="plan"><?php esc_html_e( 'Payment plan', 'tradeschool-manager' ); ?></option>
							</select></td></tr>
						<tr class="tsm-plan-row"><th><?php esc_html_e( 'Installments', 'tradeschool-manager' ); ?></th>
							<td><input type="number" min="1" name="installments" id="tsm-enr-installments" value="4"></td></tr>
						<tr class="tsm-plan-row"><th><?php esc_html_e( 'Frequency', 'tradeschool-manager' ); ?></th>
							<td><select name="frequency" id="tsm-enr-freq">
								<option value="weekly"><?php esc_html_e( 'Weekly', 'tradeschool-manager' ); ?></option>
								<option value="biweekly"><?php esc_html_e( 'Every 2 weeks', 'tradeschool-manager' ); ?></option>
								<option value="monthly" selected><?php esc_html_e( 'Monthly', 'tradeschool-manager' ); ?></option>
								<option value="quarterly"><?php esc_html_e( 'Quarterly', 'tradeschool-manager' ); ?></option>
							</select></td></tr>
						<tr class="tsm-plan-row"><th><?php esc_html_e( 'First Payment Date', 'tradeschool-manager' ); ?></th>
							<td><input type="date" name="first_payment_date" value="<?php echo esc_attr( current_time( 'Y-m-d' ) ); ?>"></td></tr>
						<?php endif; ?>
						<tr><th><?php esc_html_e( 'Start Date', 'tradeschool-manager' ); ?></th>
							<td><input type="date" name="start_date" value="<?php echo esc_attr( current_time( 'Y-m-d' ) ); ?>"></td></tr>
						<?php if ( $inv_on ) : ?>
						<tr><th><?php esc_html_e( 'Due Date (full bill)', 'tradeschool-manager' ); ?></th>
							<td><input type="date" name="due_date" value="<?php echo esc_attr( current_time( 'Y-m-d' ) ); ?>"></td></tr>
						<?php endif; ?>
						<tr><th><?php esc_html_e( 'Notes', 'tradeschool-manager' ); ?></th>
							<td><textarea name="enrollment_notes" rows="2" class="large-text"></textarea></td></tr>
					</table>
					<p><button class="button button-primary"><?php esc_html_e( 'Enroll Student', 'tradeschool-manager' ); ?></button></p>
				</form>
				<?php if ( $inv_on ) : ?>
				<?php
				TSM_Inline_Assets::script(
					'tsm-enrollment-billing',
					"(function(){\n"
					. "\tvar sel = document.getElementById('tsm-enr-program');\n"
					. "\tvar mode = document.getElementById('tsm-bill-mode');\n"
					. "\tvar planRows = document.querySelectorAll('.tsm-plan-row');\n"
					. "\tfunction setValue(id, value){ var el = document.getElementById(id); if (el) { el.value = value; } }\n"
					. "\tfunction togglePlan(){ if (!mode) { return; } planRows.forEach(function(r){ r.style.display = (mode.value === 'plan') ? '' : 'none'; }); }\n"
					. "\tfunction applyDefaults(){ var o = sel && sel.options[sel.selectedIndex]; if (!o || !o.value || !mode) { return; } if (o.dataset.billing) { mode.value = o.dataset.billing; } setValue('tsm-enr-tuition', o.dataset.cost || ''); setValue('tsm-enr-installments', o.dataset.installments || 1); setValue('tsm-enr-freq', o.dataset.frequency || 'monthly'); setValue('tsm-enr-deposit', o.dataset.deposit || 0); setValue('tsm-enr-feemode', o.dataset.feeMode || 'none'); setValue('tsm-enr-feeval', o.dataset.feeValue || 0); togglePlan(); }\n"
					. "\tif (sel) { sel.addEventListener('change', applyDefaults); }\n"
					. "\tif (mode) { mode.addEventListener('change', togglePlan); }\n"
					. "\ttogglePlan();\n"
					. "})();\n"
				);
				?>
				<?php endif; ?>
			<?php endif; ?>
		</div>
		<?php
	}

	/**
	 * Required-Documents config panel under the Program edit form.
	 * Lets the admin define which documents students enrolled in the program
	 * must provide (e.g., Government ID, High School Diploma, W-9).
	 */
	private function render_program_doc_types_panel( $program ) {
		$types    = TSM_Student_Docs::types_for_program( $program->id );
		$licensed = ! function_exists( 'tsm_cf_license_active' ) || tsm_cf_license_active();
		?>
		<div class="tsm-card" id="tsm-doc-types" style="margin-top:24px;">
			<h2><?php esc_html_e( 'Required Documents', 'tradeschool-manager' ); ?></h2>
			<p class="description"><?php esc_html_e( 'List the documents every student in this program must provide. Students upload them from their portal Settings tab; admins can also upload on their behalf from the student profile. PDF and image files (JPG, PNG, WEBP, HEIC) up to 15 MB are accepted.', 'tradeschool-manager' ); ?></p>
			<?php if ( ! $licensed ) : $button = function_exists( 'tsm_cf_license_button_details' ) ? tsm_cf_license_button_details() : array(); ?>
				<div class="notice notice-warning inline" style="margin:12px 0;">
					<p><strong><?php esc_html_e( 'Custom Fields & Student Documents license required', 'tradeschool-manager' ); ?></strong></p>
					<p><?php echo esc_html( function_exists( 'tsm_cf_license_message' ) ? tsm_cf_license_message() : __( 'Required document setup is read-only until the license is active.', 'tradeschool-manager' ) ); ?></p>
					<?php if ( ! empty( $button['url'] ) ) : ?>
						<p><a class="<?php echo esc_attr( $button['class'] ?? 'button button-primary' ); ?>" href="<?php echo esc_url( $button['url'] ); ?>"><?php echo esc_html( $button['label'] ?? __( 'Enter license key', 'tradeschool-manager' ) ); ?></a></p>
					<?php endif; ?>
				</div>
			<?php endif; ?>
			<table class="widefat striped">
				<thead><tr>
					<th><?php esc_html_e( 'Document', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'Description', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'Required', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'Order', 'tradeschool-manager' ); ?></th>
					<th></th>
				</tr></thead>
				<tbody>
				<?php if ( empty( $types ) ) : ?>
					<tr><td colspan="5"><em><?php esc_html_e( 'No required documents configured yet.', 'tradeschool-manager' ); ?></em></td></tr>
				<?php endif; ?>
				<?php foreach ( $types as $t ) : ?>
					<tr>
						<td><strong><?php echo esc_html( $t->label ); ?></strong></td>
						<td><?php echo esc_html( $t->description ); ?></td>
						<td><?php echo $t->required ? esc_html__( 'Yes', 'tradeschool-manager' ) : esc_html__( 'Optional', 'tradeschool-manager' ); ?></td>
						<td><?php echo (int) $t->sort_order; ?></td>
						<td>
							<?php if ( $licensed ) : ?>
								<a class="button button-small"
									href="<?php echo esc_url( wp_nonce_url(
										add_query_arg( array(
											'action'     => 'tsm_doc_type_delete',
											'id'         => $t->id,
											'program_id' => $program->id,
										), admin_url( 'admin-post.php' ) ),
										'tsm_doc_type_delete_' . $t->id
									) ); ?>"
									onclick="return confirm('<?php esc_attr_e( 'Remove this required document? Already-uploaded files are kept.', 'tradeschool-manager' ); ?>');">
									<?php esc_html_e( 'Remove', 'tradeschool-manager' ); ?>
								</a>
							<?php else : ?>
								<span class="description"><?php esc_html_e( 'Read-only', 'tradeschool-manager' ); ?></span>
							<?php endif; ?>
						</td>
					</tr>
				<?php endforeach; ?>
				</tbody>
			</table>

			<?php if ( $licensed ) : ?>
				<h3 style="margin-top:18px;"><?php esc_html_e( 'Add Required Document', 'tradeschool-manager' ); ?></h3>
				<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
					<?php wp_nonce_field( 'tsm_doc_type_save' ); ?>
					<input type="hidden" name="action" value="tsm_doc_type_save">
					<input type="hidden" name="program_id" value="<?php echo esc_attr( $program->id ); ?>">
					<table class="form-table">
						<tr><th><label><?php esc_html_e( 'Label', 'tradeschool-manager' ); ?></label></th>
							<td><input class="regular-text" name="label" required placeholder="<?php esc_attr_e( 'e.g. Government Photo ID', 'tradeschool-manager' ); ?>"></td></tr>
						<tr><th><label><?php esc_html_e( 'Description', 'tradeschool-manager' ); ?></label></th>
							<td><textarea name="description" rows="2" class="large-text" placeholder="<?php esc_attr_e( 'Optional details shown to the student.', 'tradeschool-manager' ); ?>"></textarea></td></tr>
						<tr><th><?php esc_html_e( 'Required?', 'tradeschool-manager' ); ?></th>
							<td><label><input type="checkbox" name="required" value="1" checked> <?php esc_html_e( 'Yes, this document is mandatory', 'tradeschool-manager' ); ?></label></td></tr>
						<tr><th><label><?php esc_html_e( 'Sort Order', 'tradeschool-manager' ); ?></label></th>
							<td><input type="number" min="0" name="sort_order" value="<?php echo esc_attr( count( $types ) ); ?>" style="width:90px;"></td></tr>
					</table>
					<p><button class="button button-primary"><?php esc_html_e( 'Add Document', 'tradeschool-manager' ); ?></button></p>
				</form>
			<?php endif; ?>
		</div>
		<?php
	}

	/**
	 * Required-Documents panel on the Student edit screen.
	 * Shows which docs are required (across all enrolled programs), which are
	 * already on file, and provides upload/delete/download controls. Files are
	 * served via a logged audit trail so the school can prove chain-of-custody
	 * if government auditors request records.
	 */
	private function render_student_documents_panel( $student ) {
		$status   = TSM_Student_Docs::status_for_student( $student->id );
		$other    = array();
		$licensed = ! function_exists( 'tsm_cf_license_active' ) || tsm_cf_license_active();
		$by_type_ids = array();
		foreach ( $status as $row ) { $by_type_ids[ (int) $row['type']->id ] = true; }
		foreach ( TSM_Student_Docs::docs_for_student( $student->id ) as $d ) {
			if ( ! $d->doc_type_id || empty( $by_type_ids[ (int) $d->doc_type_id ] ) ) {
				$other[] = $d;
			}
		}

		$satisfied_count = 0; $required_count = 0;
		foreach ( $status as $row ) {
			if ( $row['type']->required ) {
				$required_count++;
				if ( $row['satisfied'] ) { $satisfied_count++; }
			}
		}
		?>
		<div class="tsm-card" id="tsm-student-docs" style="margin-top:24px;">
			<h2><?php esc_html_e( 'Required Documents', 'tradeschool-manager' ); ?>
				<?php if ( $required_count ) : ?>
					<span class="tsm-pill" style="background:<?php echo $satisfied_count >= $required_count ? '#e2f6e2' : '#fff4cf'; ?>;color:#333;font-size:12px;font-weight:500;padding:2px 10px;border-radius:12px;margin-left:10px;">
						<?php /* translators: 1: count satisfied, 2: count required */ echo esc_html( sprintf( __( '%1$d of %2$d on file', 'tradeschool-manager' ), $satisfied_count, $required_count ) ); ?>
					</span>
				<?php endif; ?>
			</h2>
			<p class="description"><?php esc_html_e( 'Documents required by the program(s) this student is enrolled in. Upload PDF or image files (JPG, PNG, WEBP, HEIC). All downloads are recorded in the audit log for compliance.', 'tradeschool-manager' ); ?></p>
			<?php if ( ! $licensed ) : ?>
				<div class="notice notice-warning inline" style="margin:12px 0;">
					<p><strong><?php esc_html_e( 'Uploads disabled', 'tradeschool-manager' ); ?></strong></p>
					<p><?php echo esc_html( function_exists( 'tsm_cf_license_message' ) ? tsm_cf_license_message() : __( 'Existing documents remain available, but uploads are disabled until the license is active.', 'tradeschool-manager' ) ); ?></p>
				</div>
			<?php endif; ?>

			<?php if ( empty( $status ) ) : ?>
				<p><em><?php esc_html_e( 'This student is not enrolled in a program with required documents yet.', 'tradeschool-manager' ); ?></em></p>
			<?php else : ?>
			<table class="widefat striped">
				<thead><tr>
					<th><?php esc_html_e( 'Document', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'Program', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'Status', 'tradeschool-manager' ); ?></th>
					<th><?php esc_html_e( 'On File', 'tradeschool-manager' ); ?></th>
					<th></th>
				</tr></thead>
				<tbody>
				<?php foreach ( $status as $row ) :
					$t = $row['type']; $docs = $row['docs'];
				?>
					<tr>
						<td>
							<strong><?php echo esc_html( $t->label ); ?></strong>
							<?php if ( ! empty( $t->description ) ) : ?><br><span class="description"><?php echo esc_html( $t->description ); ?></span><?php endif; ?>
						</td>
						<td><?php echo esc_html( $t->program_name ); ?></td>
						<td>
							<?php if ( ! $t->required ) : ?>
								<span style="color:#666;"><?php esc_html_e( 'Optional', 'tradeschool-manager' ); ?></span>
							<?php elseif ( $row['satisfied'] ) : ?>
								<span style="color:#1a7d2e;font-weight:600;"> <?php esc_html_e( 'On file', 'tradeschool-manager' ); ?></span>
							<?php else : ?>
								<span style="color:#a02828;font-weight:600;"><?php esc_html_e( 'Missing', 'tradeschool-manager' ); ?></span>
							<?php endif; ?>
						</td>
						<td>
							<?php if ( empty( $docs ) ) : ?>
								<em><?php esc_html_e( 'None', 'tradeschool-manager' ); ?></em>
							<?php else : foreach ( $docs as $d ) : ?>
								<div style="margin-bottom:4px;">
									<a href="<?php echo esc_url( TSM_Student_Docs::download_url( $d->id, 'inline' ) ); ?>" target="_blank">
										<?php echo esc_html( $d->original_name ); ?>
									</a>
									<small style="color:#666;">(<?php echo esc_html( size_format( $d->file_size ) ); ?>, <?php echo esc_html( ucfirst( $d->uploaded_by ) ); ?>, <?php echo esc_html( mysql2date( 'M j, Y', $d->uploaded_at ) ); ?>)</small>
									<a class="button button-small" href="<?php echo esc_url( TSM_Student_Docs::download_url( $d->id, 'attachment' ) ); ?>"><?php esc_html_e( 'Download', 'tradeschool-manager' ); ?></a>
									<a class="button button-small" href="<?php echo esc_url( TSM_Student_Docs::delete_url( $d->id ) ); ?>" onclick="return confirm('<?php esc_attr_e( 'Delete this file? This cannot be undone.', 'tradeschool-manager' ); ?>');"><?php esc_html_e( 'Delete', 'tradeschool-manager' ); ?></a>
								</div>
							<?php endforeach; endif; ?>
						</td>
						<td>
							<?php if ( $licensed ) : ?>
								<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" enctype="multipart/form-data">
									<?php wp_nonce_field( 'tsm_doc_admin_upload' ); ?>
									<input type="hidden" name="action" value="tsm_doc_admin_upload">
									<input type="hidden" name="student_id" value="<?php echo esc_attr( $student->id ); ?>">
									<input type="hidden" name="doc_type_id" value="<?php echo esc_attr( $t->id ); ?>">
									<input type="file" name="doc_file" required accept=".pdf,image/png,image/jpeg,image/webp,image/heic">
									<button class="button button-small button-primary"><?php esc_html_e( 'Upload', 'tradeschool-manager' ); ?></button>
								</form>
							<?php else : ?>
								<span class="description"><?php esc_html_e( 'Upload disabled', 'tradeschool-manager' ); ?></span>
							<?php endif; ?>
						</td>
					</tr>
				<?php endforeach; ?>
				</tbody>
			</table>
			<?php endif; ?>

			<h3 style="margin-top:18px;"><?php esc_html_e( 'Other Documents', 'tradeschool-manager' ); ?></h3>
			<?php if ( empty( $other ) ) : ?>
				<p><em><?php esc_html_e( 'No additional files on record.', 'tradeschool-manager' ); ?></em></p>
			<?php else : ?>
				<ul>
				<?php foreach ( $other as $d ) : ?>
					<li>
						<a href="<?php echo esc_url( TSM_Student_Docs::download_url( $d->id, 'inline' ) ); ?>" target="_blank"><?php echo esc_html( $d->label ); ?></a>
						<small style="color:#666;">(<?php echo esc_html( $d->original_name ); ?>, <?php echo esc_html( size_format( $d->file_size ) ); ?>, <?php echo esc_html( ucfirst( $d->uploaded_by ) ); ?>, <?php echo esc_html( mysql2date( 'M j, Y', $d->uploaded_at ) ); ?>)</small>
						<a class="button button-small" href="<?php echo esc_url( TSM_Student_Docs::download_url( $d->id, 'attachment' ) ); ?>"><?php esc_html_e( 'Download', 'tradeschool-manager' ); ?></a>
						<a class="button button-small" href="<?php echo esc_url( TSM_Student_Docs::delete_url( $d->id ) ); ?>" onclick="return confirm('<?php esc_attr_e( 'Delete this file?', 'tradeschool-manager' ); ?>');"><?php esc_html_e( 'Delete', 'tradeschool-manager' ); ?></a>
					</li>
				<?php endforeach; ?>
				</ul>
			<?php endif; ?>

			<?php if ( $licensed ) : ?>
				<h4 style="margin-top:14px;"><?php esc_html_e( 'Upload an Other Document', 'tradeschool-manager' ); ?></h4>
				<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" enctype="multipart/form-data" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
					<?php wp_nonce_field( 'tsm_doc_admin_upload' ); ?>
					<input type="hidden" name="action" value="tsm_doc_admin_upload">
					<input type="hidden" name="student_id" value="<?php echo esc_attr( $student->id ); ?>">
					<input type="text" name="label" placeholder="<?php esc_attr_e( 'Label (e.g. Transcript request)', 'tradeschool-manager' ); ?>" required>
					<input type="file" name="doc_file" required accept=".pdf,image/png,image/jpeg,image/webp,image/heic">
					<button class="button"><?php esc_html_e( 'Upload', 'tradeschool-manager' ); ?></button>
				</form>
			<?php endif; ?>

			<?php $audit = TSM_Student_Docs::audit_for_student( $student->id, 25 ); if ( ! empty( $audit ) ) : ?>
				<details style="margin-top:18px;">
					<summary style="cursor:pointer;font-weight:600;"><?php esc_html_e( 'Audit Log (recent activity)', 'tradeschool-manager' ); ?></summary>
					<table class="widefat striped" style="margin-top:8px;">
						<thead><tr>
							<th><?php esc_html_e( 'When', 'tradeschool-manager' ); ?></th>
							<th><?php esc_html_e( 'Event', 'tradeschool-manager' ); ?></th>
							<th><?php esc_html_e( 'Actor', 'tradeschool-manager' ); ?></th>
							<th><?php esc_html_e( 'IP', 'tradeschool-manager' ); ?></th>
							<th><?php esc_html_e( 'Detail', 'tradeschool-manager' ); ?></th>
						</tr></thead>
						<tbody>
						<?php foreach ( $audit as $a ) : ?>
							<tr>
								<td><?php echo esc_html( mysql2date( 'M j, Y g:ia', $a->created_at ) ); ?></td>
								<td><?php echo esc_html( ucfirst( $a->event ) ); ?></td>
								<td><?php echo esc_html( $a->actor ); ?></td>
								<td><code style="font-size:11px;"><?php echo esc_html( $a->ip_address ); ?></code></td>
								<td><small><?php echo esc_html( $a->detail ); ?></small></td>
							</tr>
						<?php endforeach; ?>
						</tbody>
					</table>
				</details>
			<?php endif; ?>
		</div>
		<?php
	}


	/* ---------- Applications ---------- */
	/* page_applications + render_applications_* + form-builder JS were moved
	 * into the standalone Applications add-on (TSM_Apps_Admin) in v1.18.5.
	 */



	private function redirect( $page, $extra = array() ) {
		$url = admin_url( 'admin.php?page=' . $page );
		if ( $extra ) {
			$url = add_query_arg( $extra, $url );
		}
		wp_safe_redirect( $url );
		exit;
	}
}
