<?php
/**
 * Parent Portal (core plugin)  -  v1.19.5
 *
 * As of v1.19.5 parents are **no longer WordPress users**. They live in the
 * dedicated `wp_tsm_parents` table with their own password hash and reset
 * token, and authenticate through TSM_Parent_Portal (cookie + transient
 * session  -  same pattern as students and teachers).
 *
 * Tables managed here:
 *   - wp_tsm_parents              parent accounts (new in 1.19.5)
 *   - wp_tsm_parent_links         parent <-> student many-to-many
 *                                  (parent_user_id column now stores tsm_parents.id)
 *   - wp_tsm_parent_invite_codes  one-time signup invites
 *
 * Front-end shortcodes:
 *   [tsm_parent_portal]  -  branded login + portal
 *   [tsm_parent_signup]  -  invite-code self-signup
 *
 * @package TradeSchoolManager
 * @since   1.14.0
 */

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

/*
 * This class manages its own custom tables (wp_tsm_parents,
 * wp_tsm_parent_links, wp_tsm_parent_invite_codes). Direct $wpdb calls are
 * required (no WP core API exists for custom tables) and table names are
 * always interpolated from $wpdb->prefix + literal strings  -  never user
 * input. Parameter values are passed through $wpdb->prepare() placeholders.
 *
 * Nonce checks live at the top of each POST handler (see handle_form_post
 * and handle_signup_post); per-field reads further down therefore do not
 * each need a NonceVerification annotation.
 */
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
// phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter
// phpcs:disable WordPress.Security.NonceVerification.Recommended

class TSM_Parents {

	const PORTAL_PAGE_OPT = 'tsm_parent_portal_page_id';
	const PORTAL_SLUG     = 'parent-portal';
	const SIGNUP_PAGE_OPT = 'tsm_parent_signup_page_id';
	const SIGNUP_SLUG     = 'parent-signup';

	public static function init() {
		add_action( 'init',       array( __CLASS__, 'register_shortcodes' ) );
		add_action( 'admin_menu', array( __CLASS__, 'admin_menu' ), 50 );
		add_filter( 'tradeschool_manager_admin_nav_items', array( __CLASS__, 'add_nav_item' ) );
		add_action( 'admin_post_tsm_parents_action', array( __CLASS__, 'handle_admin_action' ) );
		add_action( 'admin_post_nopriv_tsm_parent_signup', array( __CLASS__, 'handle_signup' ) );
		add_action( 'admin_post_tsm_parent_signup',        array( __CLASS__, 'handle_signup' ) );
		add_action( 'admin_post_nopriv_tsm_parent_message', array( __CLASS__, 'handle_parent_message' ) );
		add_action( 'admin_post_tsm_parent_message',        array( __CLASS__, 'handle_parent_message' ) );
		add_action( 'admin_init', array( __CLASS__, 'maybe_create_pages' ) );
	}

	/* ------------------------------------------------------------------ */
	/* Schema                                                              */
	/* ------------------------------------------------------------------ */

	public static function parents_table() { global $wpdb; return $wpdb->prefix . 'tsm_parents'; }
	public static function table()         { global $wpdb; return $wpdb->prefix . 'tsm_parent_links'; }
	public static function codes_table()   { global $wpdb; return $wpdb->prefix . 'tsm_parent_invite_codes'; }

	public static function install_schema() {
		global $wpdb;
		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
		$cs = $wpdb->get_charset_collate();
		$p  = $wpdb->prefix;

		dbDelta( "CREATE TABLE {$p}tsm_parents (
			id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
			first_name VARCHAR(100) NOT NULL DEFAULT '',
			last_name VARCHAR(100) NOT NULL DEFAULT '',
			email VARCHAR(190) NOT NULL,
			username VARCHAR(60) NULL,
			phone VARCHAR(40) NOT NULL DEFAULT '',
			password_hash VARCHAR(255) NULL,
			reset_token VARCHAR(64) NULL,
			reset_expires DATETIME NULL,
			created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
			PRIMARY KEY (id),
			UNIQUE KEY email (email),
			UNIQUE KEY username (username)
		) {$cs};" );

		dbDelta( "CREATE TABLE {$p}tsm_parent_links (
			id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
			parent_user_id BIGINT UNSIGNED NOT NULL,
			student_id BIGINT UNSIGNED NOT NULL,
			relationship VARCHAR(50) NULL,
			can_pay TINYINT(1) NOT NULL DEFAULT 1,
			created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
			PRIMARY KEY (id),
			UNIQUE KEY parent_student (parent_user_id, student_id),
			KEY student_id (student_id)
		) {$cs};" );

		dbDelta( "CREATE TABLE {$p}tsm_parent_invite_codes (
			id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
			code VARCHAR(32) NOT NULL,
			student_id BIGINT UNSIGNED NOT NULL,
			relationship VARCHAR(50) NULL,
			can_pay TINYINT(1) NOT NULL DEFAULT 1,
			email VARCHAR(190) NULL,
			expires_at DATETIME NULL,
			used_at DATETIME NULL,
			used_by BIGINT UNSIGNED NULL,
			created_by BIGINT UNSIGNED NOT NULL,
			created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
			PRIMARY KEY (id),
			UNIQUE KEY code (code),
			KEY student_id (student_id)
		) {$cs};" );

		self::migrate_wp_parents();
	}

	/**
	 * One-time migration: copy any existing WP users with the legacy
	 * `tsm_parent` role into the new tsm_parents table, and re-point
	 * their tsm_parent_links rows to the new parent IDs. Existing WP
	 * users are not deleted  -  just no longer used for parent auth.
	 */
	protected static function migrate_wp_parents() {
		if ( get_option( 'tsm_parents_migrated_to_table' ) ) { return; }
		global $wpdb;
		$pt = self::parents_table();
		$lt = self::table();
		$users = get_users( array( 'role' => 'tsm_parent', 'number' => 5000, 'fields' => array( 'ID', 'user_email', 'user_login', 'display_name' ) ) );
		foreach ( $users as $u ) {
			if ( empty( $u->user_email ) ) { continue; }
			$existing = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$pt} WHERE email = %s", $u->user_email ) );
			if ( $existing ) {
				$new_id = (int) $existing;
			} else {
				$first = get_user_meta( $u->ID, 'first_name', true );
				$last  = get_user_meta( $u->ID, 'last_name',  true );
				$phone = get_user_meta( $u->ID, 'tsm_parent_phone', true );
				$wpdb->insert( $pt, array(
					'first_name' => $first,
					'last_name'  => $last,
					'email'      => $u->user_email,
					'username'   => $u->user_login !== $u->user_email ? $u->user_login : null,
					'phone'      => $phone,
					'created_at' => current_time( 'mysql' ),
				) );
				$new_id = (int) $wpdb->insert_id;
			}
			if ( $new_id ) {
				// Re-point links from WP user ID to new tsm_parents.id.
				$wpdb->query( $wpdb->prepare( "UPDATE {$lt} SET parent_user_id = %d WHERE parent_user_id = %d", $new_id, $u->ID ) );
			}
		}
		update_option( 'tsm_parents_migrated_to_table', 1 );
	}

	/* ------------------------------------------------------------------ */
	/* Parent CRUD helpers                                                 */
	/* ------------------------------------------------------------------ */

	public static function get( $id ) {
		global $wpdb;
		return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM " . self::parents_table() . " WHERE id = %d", absint( $id ) ) );
	}

	public static function get_by_email( $email ) {
		global $wpdb;
		$email = sanitize_email( $email );
		if ( ! $email ) { return null; }
		return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM " . self::parents_table() . " WHERE email = %s", $email ) );
	}

	public static function all() {
		global $wpdb;
		return $wpdb->get_results( "SELECT * FROM " . self::parents_table() . " ORDER BY last_name, first_name" );
	}

	public static function set_password( $parent_id, $new_password ) {
		global $wpdb;
		if ( strlen( (string) $new_password ) < 6 ) { return false; }
		$wpdb->update(
			self::parents_table(),
			array(
				'password_hash' => wp_hash_password( $new_password ),
				'reset_token'   => null,
				'reset_expires' => null,
			),
			array( 'id' => absint( $parent_id ) )
		);
		return true;
	}

	public static function create_reset_token( $parent_id ) {
		global $wpdb;
		$row = self::get( $parent_id );
		if ( ! $row || empty( $row->email ) ) { return ''; }
		$token   = wp_generate_password( 40, false, false );
		$expires = gmdate( 'Y-m-d H:i:s', time() + DAY_IN_SECONDS );
		$wpdb->update( self::parents_table(),
			array( 'reset_token' => $token, 'reset_expires' => $expires ),
			array( 'id' => absint( $parent_id ) )
		);
		return $token;
	}

	public static function get_by_reset_token( $token ) {
		global $wpdb;
		$token = preg_replace( '/[^A-Za-z0-9]/', '', (string) $token );
		if ( strlen( $token ) < 20 ) { return null; }
		$now = gmdate( 'Y-m-d H:i:s' );
		return $wpdb->get_row( $wpdb->prepare(
			"SELECT * FROM " . self::parents_table() . " WHERE reset_token = %s AND reset_expires IS NOT NULL AND reset_expires > %s",
			$token, $now
		) );
	}

	public static function students_for_parent( $parent_id ) {
		global $wpdb;
		$t  = self::table();
		$st = $wpdb->prefix . 'tsm_students';
		return $wpdb->get_results( $wpdb->prepare(
			"SELECT s.*, l.relationship, l.can_pay
			   FROM {$t} l
			   JOIN {$st} s ON s.id = l.student_id
			  WHERE l.parent_user_id = %d
			  ORDER BY s.last_name ASC, s.first_name ASC",
			absint( $parent_id )
		) );
	}

	protected static function generate_code() {
		$alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
		$out = '';
		for ( $g = 0; $g < 3; $g++ ) {
			for ( $i = 0; $i < 4; $i++ ) { $out .= $alphabet[ wp_rand( 0, strlen( $alphabet ) - 1 ) ]; }
			if ( $g < 2 ) { $out .= '-'; }
		}
		return $out;
	}

	/* ------------------------------------------------------------------ */
	/* Admin screen                                                        */
	/* ------------------------------------------------------------------ */

	public static function add_nav_item( $items ) {
		$item = array( 'slug' => 'tsm-parents', 'label' => __( 'Sponsors', 'tradeschool-manager' ), 'icon' => 'dashicons-groups' );
		$out = array(); $inserted = false;
		foreach ( (array) $items as $it ) {
			$out[] = $it;
			if ( ! $inserted && isset( $it['slug'] ) && 'tsm-students' === $it['slug'] ) { $out[] = $item; $inserted = true; }
		}
		if ( ! $inserted ) { $out[] = $item; }
		return $out;
	}

	public static function admin_menu() {
		$cap = current_user_can( 'manage_tradeschool' ) ? 'manage_tradeschool' : 'manage_options';
		add_submenu_page( 'tsm-dashboard', __( 'Sponsors & Authorized Contacts', 'tradeschool-manager' ), __( 'Sponsors', 'tradeschool-manager' ), $cap, 'tsm-parents', array( __CLASS__, 'page_parents' ) );
	}

	public static function handle_admin_action() {
		if ( ! current_user_can( 'manage_options' ) && ! current_user_can( 'manage_tradeschool' ) ) {
			wp_die( esc_html__( 'Permission denied.', 'tradeschool-manager' ) );
		}
		check_admin_referer( 'tsm_parents_action' );

		global $wpdb;
		$pt = self::parents_table();
		$t  = self::table();
		$ct = self::codes_table();
		$op = isset( $_POST['op'] ) ? sanitize_key( wp_unslash( $_POST['op'] ) ) : '';
		$redirect = admin_url( 'admin.php?page=tsm-parents' );

		if ( 'create_code' === $op ) {
			$sid   = isset( $_POST['student_id'] ) ? (int) $_POST['student_id'] : 0;
			$rel   = sanitize_text_field( wp_unslash( $_POST['relationship'] ?? '' ) );
			$pay   = isset( $_POST['can_pay'] ) ? 1 : 0;
			$email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) );
			$days  = max( 1, (int) sanitize_text_field( wp_unslash( $_POST['expires_days'] ?? '30' ) ) );
			if ( $sid ) {
				$code = self::generate_code();
				$wpdb->insert( $ct, array(
					'code' => $code, 'student_id' => $sid, 'relationship' => $rel, 'can_pay' => $pay,
					'email' => $email ?: null,
					'expires_at' => gmdate( 'Y-m-d H:i:s', time() + $days * DAY_IN_SECONDS ),
					'created_by' => get_current_user_id(),
					'created_at' => current_time( 'mysql' ),
				) );
				$emailed = 0;
				if ( $email ) { $emailed = self::send_invite_email( $email, $code, $sid, $days ) ? 1 : 0; }
				wp_safe_redirect( add_query_arg( array( 'tsm_msg' => 'code_created', 'tsm_code' => rawurlencode( $code ), 'tsm_emailed' => $emailed ), $redirect ) ); exit;
			}
		}

		if ( 'revoke_code' === $op ) {
			$cid = absint( wp_unslash( $_POST['code_id'] ?? 0 ) );
			if ( $cid ) { $wpdb->delete( $ct, array( 'id' => $cid ) ); }
			wp_safe_redirect( add_query_arg( 'tsm_msg', 'code_revoked', $redirect ) ); exit;
		}

		if ( 'create_parent' === $op ) {
			$email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) );
			$first = sanitize_text_field( wp_unslash( $_POST['first_name'] ?? '' ) );
			$last  = sanitize_text_field( wp_unslash( $_POST['last_name']  ?? '' ) );
			$phone = sanitize_text_field( wp_unslash( $_POST['phone']      ?? '' ) );
			if ( ! is_email( $email ) ) { wp_safe_redirect( add_query_arg( 'tsm_msg', 'bad_email', $redirect ) ); exit; }
			$existing = self::get_by_email( $email );
			if ( $existing ) { wp_safe_redirect( add_query_arg( array( 'tsm_msg' => 'email_taken', 'edit' => (int) $existing->id ), $redirect ) ); exit; }
			$wpdb->insert( $pt, array(
				'first_name' => $first, 'last_name' => $last, 'email' => $email, 'phone' => $phone,
				'created_at' => current_time( 'mysql' ),
			) );
			$pid = (int) $wpdb->insert_id;
			if ( $pid ) { self::send_welcome_email( self::get( $pid ), true, 0 ); }
			wp_safe_redirect( add_query_arg( 'tsm_msg', 'parent_created', $redirect ) ); exit;
		}

		if ( 'update_parent' === $op ) {
			$pid   = absint( wp_unslash( $_POST['parent_user_id'] ?? 0 ) );
			$email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) );
			$first = sanitize_text_field( wp_unslash( $_POST['first_name'] ?? '' ) );
			$last  = sanitize_text_field( wp_unslash( $_POST['last_name']  ?? '' ) );
			$phone = sanitize_text_field( wp_unslash( $_POST['phone']      ?? '' ) );
			if ( ! $pid || ! is_email( $email ) ) { wp_safe_redirect( add_query_arg( 'tsm_msg', 'bad_email', $redirect ) ); exit; }
			$dup = self::get_by_email( $email );
			if ( $dup && (int) $dup->id !== $pid ) { wp_safe_redirect( add_query_arg( array( 'tsm_msg' => 'email_taken', 'edit' => $pid ), $redirect ) ); exit; }
			$wpdb->update( $pt, array( 'email' => $email, 'first_name' => $first, 'last_name' => $last, 'phone' => $phone ), array( 'id' => $pid ) );
			wp_safe_redirect( add_query_arg( array( 'tsm_msg' => 'parent_updated', 'edit' => $pid ), $redirect ) ); exit;
		}

		if ( 'set_password' === $op ) {
			$pid = absint( wp_unslash( $_POST['parent_user_id'] ?? 0 ) );
			// Password is hashed by self::set_password(); raw value must not be sanitized/altered.
			$pw  = isset( $_POST['new_password'] ) ? (string) wp_unslash( $_POST['new_password'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			if ( $pid && strlen( $pw ) >= 8 ) {
				self::set_password( $pid, $pw );
				wp_safe_redirect( add_query_arg( array( 'tsm_msg' => 'password_set', 'edit' => $pid ), $redirect ) ); exit;
			}
			wp_safe_redirect( add_query_arg( array( 'tsm_msg' => 'password_failed', 'edit' => $pid ), $redirect ) ); exit;
		}

		if ( 'send_reset' === $op ) {
			$pid = absint( wp_unslash( $_POST['parent_user_id'] ?? 0 ) );
			$row = $pid ? self::get( $pid ) : null;
			$ok  = 0;
			if ( $row && ! empty( $row->email ) ) { $ok = self::send_reset_email( $row ) ? 1 : 0; }
			wp_safe_redirect( add_query_arg( 'tsm_msg', $ok ? 'reset_sent' : 'reset_failed', $redirect ) ); exit;
		}

		if ( 'delete_parent' === $op ) {
			$pid = absint( wp_unslash( $_POST['parent_user_id'] ?? 0 ) );
			if ( $pid ) {
				$wpdb->delete( $t,  array( 'parent_user_id' => $pid ) );
				$wpdb->delete( $pt, array( 'id' => $pid ) );
			}
			wp_safe_redirect( add_query_arg( 'tsm_msg', 'parent_removed', $redirect ) ); exit;
		}

		if ( 'link' === $op ) {
			$pid = absint( wp_unslash( $_POST['parent_user_id'] ?? 0 ) );
			$sid = absint( wp_unslash( $_POST['student_id'] ?? 0 ) );
			$rel = sanitize_text_field( wp_unslash( $_POST['relationship'] ?? '' ) );
			$pay = isset( $_POST['can_pay'] ) ? 1 : 0;
			if ( $pid && $sid ) {
				$existing = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$t} WHERE parent_user_id=%d AND student_id=%d", $pid, $sid ) );
				if ( $existing ) {
					$wpdb->update( $t, array( 'relationship' => $rel, 'can_pay' => $pay ), array( 'id' => $existing ) );
				} else {
					$wpdb->insert( $t, array(
						'parent_user_id' => $pid, 'student_id' => $sid,
						'relationship' => $rel, 'can_pay' => $pay,
						'created_at' => current_time( 'mysql' ),
					) );
					$row = self::get( $pid );
					if ( $row ) { self::send_welcome_email( $row, false, $sid ); }
				}
			}
			wp_safe_redirect( add_query_arg( 'tsm_msg', 'linked', $redirect ) ); exit;
		}

		if ( 'unlink' === $op ) {
			$id = absint( wp_unslash( $_POST['link_id'] ?? 0 ) );
			if ( $id ) { $wpdb->delete( $t, array( 'id' => $id ) ); }
			wp_safe_redirect( add_query_arg( 'tsm_msg', 'unlinked', $redirect ) ); exit;
		}

		wp_safe_redirect( $redirect ); exit;
	}

	public static function page_parents() {
		$admin = class_exists( 'TSM_Admin' ) ? TSM_Admin::instance() : null;
		if ( $admin && method_exists( $admin, 'shell_open' ) ) { $admin->shell_open( __( 'Sponsors & Authorized Contacts', 'tradeschool-manager' ), false ); }
		global $wpdb;
		$pt = self::parents_table();
		$t  = self::table();
		$ct = self::codes_table();
		$st = $wpdb->prefix . 'tsm_students';

		$parents  = self::all();
		$students = $wpdb->get_results( "SELECT id, first_name, last_name FROM {$st} ORDER BY last_name, first_name LIMIT 1000" );
		$links    = $wpdb->get_results( "SELECT l.*, s.first_name, s.last_name FROM {$t} l JOIN {$st} s ON s.id = l.student_id ORDER BY l.id DESC LIMIT 500" );
		$codes    = $wpdb->get_results( "SELECT c.*, s.first_name, s.last_name FROM {$ct} c JOIN {$st} s ON s.id = c.student_id ORDER BY c.id DESC LIMIT 200" );

		$msg       = isset( $_GET['tsm_msg'] ) ? sanitize_key( wp_unslash( $_GET['tsm_msg'] ) ) : '';
		$last_code = isset( $_GET['tsm_code'] ) ? sanitize_text_field( wp_unslash( $_GET['tsm_code'] ) ) : '';
		$signup_url = self::signup_url();
		$portal_url = self::portal_url();
		$tab        = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'parents';
		if ( ! in_array( $tab, array( 'parents', 'invites', 'create' ), true ) ) { $tab = 'parents'; }
		$edit_pid   = isset( $_GET['edit'] ) ? (int) $_GET['edit'] : 0;
		$edit_row   = $edit_pid ? self::get( $edit_pid ) : null;
		$base_url   = admin_url( 'admin.php?page=tsm-parents' );

		$msg_text = array(
			'code_created'    => __( 'Invite code created.', 'tradeschool-manager' ),
			'code_revoked'    => __( 'Invite code revoked.', 'tradeschool-manager' ),
			'parent_created'  => __( 'Sponsor/contact account created and welcome email sent.', 'tradeschool-manager' ),
			'parent_updated'  => __( 'Sponsor/contact details updated.', 'tradeschool-manager' ),
			'parent_removed'  => __( 'Sponsor/contact account removed.', 'tradeschool-manager' ),
			'linked'          => __( 'Sponsor/contact linked to student.', 'tradeschool-manager' ),
			'unlinked'        => __( 'Sponsor/contact unlinked from student.', 'tradeschool-manager' ),
			'reset_sent'      => __( 'Password reset email sent.', 'tradeschool-manager' ),
			'reset_failed'    => __( 'Could not send reset email  -  check the sponsor/contact has an email on file.', 'tradeschool-manager' ),
			'password_set'    => __( 'New password saved.', 'tradeschool-manager' ),
			'password_failed' => __( 'Password must be at least 8 characters.', 'tradeschool-manager' ),
			'bad_email'       => __( 'A valid email address is required.', 'tradeschool-manager' ),
			'email_taken'     => __( 'That email is already used by another sponsor/contact.', 'tradeschool-manager' ),
		);
		TSM_Inline_Assets::style(
			'tsm-parent-admin',
			'.tsm-pa-wrap{max-width:1200px;}.tsm-pa-hero{background:linear-gradient(135deg,#1e3a8a,#2563eb);color:#fff;border-radius:12px;padding:22px 26px;margin:0 0 18px;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:14px;}.tsm-pa-hero h1{color:#fff;margin:0 0 4px;font-size:22px;}.tsm-pa-hero p{margin:0;opacity:.9;font-size:13px;}.tsm-pa-hero a.btn{background:rgba(255,255,255,.18);border:1px solid rgba(255,255,255,.35);color:#fff;padding:8px 14px;border-radius:8px;text-decoration:none;font-weight:600;font-size:13px;}.tsm-pa-tabs{display:flex;gap:4px;border-bottom:1px solid #e5e7eb;margin-bottom:18px;}.tsm-pa-tabs a{padding:10px 16px;text-decoration:none;color:#475569;border-bottom:3px solid transparent;font-weight:600;font-size:14px;}.tsm-pa-tabs a.active{color:#2563eb;border-bottom-color:#2563eb;}.tsm-pa-card{background:#fff;border:1px solid #e5e7eb;border-radius:10px;padding:20px;margin-bottom:18px;box-shadow:0 1px 2px rgba(15,23,42,.04);}.tsm-pa-grid{display:grid;grid-template-columns:1fr 1fr;gap:18px;}@media (max-width:900px){.tsm-pa-grid{grid-template-columns:1fr;}}.tsm-pa-actions{display:flex;gap:6px;flex-wrap:wrap;}.tsm-pa-actions form{display:inline;margin:0;}.tsm-pa-notice{padding:10px 14px;border-radius:8px;margin-bottom:14px;font-size:14px;}.tsm-pa-notice.ok{background:#ecfdf5;border:1px solid #a7f3d0;color:#065f46;}.tsm-pa-notice.err{background:#fef2f2;border:1px solid #fecaca;color:#991b1b;}.tsm-pa-empty{padding:24px;text-align:center;color:#6b7280;}.tsm-pa-pw-input{width:160px;padding:5px 8px;border:1px solid #d1d5db;border-radius:6px;}'
		);
		?>
		<div class="wrap tsm-pa-wrap">
			<div class="tsm-pa-hero">
				<div>
					<h1><?php esc_html_e( 'Sponsors & Authorized Contacts', 'tradeschool-manager' ); ?></h1>
					<p><?php /* translators: placeholder(s) for dynamic value(s). */ printf( esc_html__( 'Signup page: %s', 'tradeschool-manager' ), '<a style="color:#fff;text-decoration:underline;" href="' . esc_url( $signup_url ) . '" target="_blank">' . esc_html( $signup_url ) . '</a>' ); ?></p>
				</div>
				<div style="display:flex;gap:8px;">
					<a class="btn" href="<?php echo esc_url( $portal_url ); ?>" target="_blank"><?php esc_html_e( 'Open Portal', 'tradeschool-manager' ); ?></a>
					<a class="btn" href="<?php echo esc_url( add_query_arg( 'tab', 'create', $base_url ) ); ?>"><?php esc_html_e( '+ Add Contact', 'tradeschool-manager' ); ?></a>
				</div>
			</div>

			<?php if ( $msg && isset( $msg_text[ $msg ] ) ) :
				$cls = in_array( $msg, array( 'reset_failed', 'password_failed', 'bad_email', 'email_taken' ), true ) ? 'err' : 'ok'; ?>
				<div class="tsm-pa-notice <?php echo esc_attr( $cls ); ?>">
					<?php echo esc_html( $msg_text[ $msg ] ); ?>
					<?php if ( $last_code ) : ?>  -  <code><?php echo esc_html( $last_code ); ?></code><?php endif; ?>
				</div>
			<?php endif; ?>

			<nav class="tsm-pa-tabs">
				<a href="<?php /* translators: placeholder(s) for dynamic value(s). */ echo esc_url( add_query_arg( 'tab', 'parents', $base_url ) ); ?>" class="<?php echo $tab === 'parents' ? 'active' : ''; ?>"><?php printf( esc_html__( 'Contacts (%d)', 'tradeschool-manager' ), count( $parents ) ); ?></a>
				<a href="<?php echo esc_url( add_query_arg( 'tab', 'invites', $base_url ) ); ?>" class="<?php echo $tab === 'invites' ? 'active' : ''; ?>"><?php esc_html_e( 'Invite Codes', 'tradeschool-manager' ); ?></a>
				<a href="<?php echo esc_url( add_query_arg( 'tab', 'create', $base_url ) ); ?>" class="<?php echo $tab === 'create' ? 'active' : ''; ?>"><?php esc_html_e( 'Add / Link', 'tradeschool-manager' ); ?></a>
			</nav>

			<?php if ( 'parents' === $tab ) : ?>
				<?php if ( $edit_row ) : $display = trim( $edit_row->first_name . ' ' . $edit_row->last_name ) ?: $edit_row->email; ?>
					<div class="tsm-pa-card">
						<h2><?php /* translators: placeholder(s) for dynamic value(s). */ printf( esc_html__( 'Edit Sponsor/Contact  -  %s', 'tradeschool-manager' ), esc_html( $display ) ); ?>
							<a href="<?php echo esc_url( add_query_arg( 'tab', 'parents', $base_url ) ); ?>" class="button" style="float:right;"><?php esc_html_e( 'Back', 'tradeschool-manager' ); ?></a>
						</h2>
						<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
							<?php wp_nonce_field( 'tsm_parents_action' ); ?>
							<input type="hidden" name="action" value="tsm_parents_action">
							<input type="hidden" name="op" value="update_parent">
							<input type="hidden" name="parent_user_id" value="<?php echo (int) $edit_row->id; ?>">
							<table class="form-table"><tbody>
								<tr><th><?php esc_html_e( 'Email', 'tradeschool-manager' ); ?></th><td><input type="email" name="email" class="regular-text" value="<?php echo esc_attr( $edit_row->email ); ?>" required></td></tr>
								<tr><th><?php esc_html_e( 'First name', 'tradeschool-manager' ); ?></th><td><input type="text" name="first_name" class="regular-text" value="<?php echo esc_attr( $edit_row->first_name ); ?>"></td></tr>
								<tr><th><?php esc_html_e( 'Last name', 'tradeschool-manager' ); ?></th><td><input type="text" name="last_name" class="regular-text" value="<?php echo esc_attr( $edit_row->last_name ); ?>"></td></tr>
								<tr><th><?php esc_html_e( 'Phone', 'tradeschool-manager' ); ?></th><td><input type="text" name="phone" class="regular-text" value="<?php echo esc_attr( $edit_row->phone ); ?>"></td></tr>
							</tbody></table>
							<?php submit_button( __( 'Save Changes', 'tradeschool-manager' ) ); ?>
						</form>

						<hr style="margin:24px 0;">
						<h3><?php esc_html_e( 'Password', 'tradeschool-manager' ); ?></h3>
						<div class="tsm-pa-grid">
							<div>
								<p><strong><?php esc_html_e( 'Email a reset link', 'tradeschool-manager' ); ?></strong></p>
								<p class="description"><?php esc_html_e( 'Sends a one-time link the sponsor/contact uses to choose a new password.', 'tradeschool-manager' ); ?></p>
								<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
									<?php wp_nonce_field( 'tsm_parents_action' ); ?>
									<input type="hidden" name="action" value="tsm_parents_action">
									<input type="hidden" name="op" value="send_reset">
									<input type="hidden" name="parent_user_id" value="<?php echo (int) $edit_row->id; ?>">
									<button class="button button-secondary"><?php esc_html_e( 'Send Reset Link', 'tradeschool-manager' ); ?></button>
								</form>
							</div>
							<div>
								<p><strong><?php esc_html_e( 'Set a password manually', 'tradeschool-manager' ); ?></strong></p>
								<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
									<?php wp_nonce_field( 'tsm_parents_action' ); ?>
									<input type="hidden" name="action" value="tsm_parents_action">
									<input type="hidden" name="op" value="set_password">
									<input type="hidden" name="parent_user_id" value="<?php echo (int) $edit_row->id; ?>">
									<input type="text" name="new_password" class="tsm-pa-pw-input" placeholder="<?php esc_attr_e( 'min 8 chars', 'tradeschool-manager' ); ?>" minlength="8" required>
									<button class="button"><?php esc_html_e( 'Set Password', 'tradeschool-manager' ); ?></button>
								</form>
							</div>
						</div>

						<hr style="margin:24px 0;">
						<h3><?php esc_html_e( 'Linked Students', 'tradeschool-manager' ); ?></h3>
						<?php $my_links = self::students_for_parent( $edit_row->id ); ?>
						<?php if ( empty( $my_links ) ) : ?>
							<p class="description"><?php esc_html_e( 'No students linked. Use the Add / Link tab to add one.', 'tradeschool-manager' ); ?></p>
						<?php else : ?>
							<table class="widefat striped">
								<thead><tr><th><?php esc_html_e( 'Student', 'tradeschool-manager' ); ?></th><th><?php esc_html_e( 'Relationship', 'tradeschool-manager' ); ?></th><th><?php esc_html_e( 'Can Pay', 'tradeschool-manager' ); ?></th><th></th></tr></thead>
								<tbody>
								<?php foreach ( $my_links as $s ) :
									$link_id = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$t} WHERE parent_user_id=%d AND student_id=%d", $edit_row->id, $s->id ) ); ?>
									<tr>
										<td><?php echo esc_html( $s->last_name . ', ' . $s->first_name ); ?></td>
										<td><?php echo esc_html( $s->relationship ?: ' - ' ); ?></td>
										<td><?php echo ! empty( $s->can_pay ) ? 'Yes' : ' - '; ?></td>
										<td><form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="display:inline;">
											<?php wp_nonce_field( 'tsm_parents_action' ); ?>
											<input type="hidden" name="action" value="tsm_parents_action">
											<input type="hidden" name="op" value="unlink">
											<input type="hidden" name="link_id" value="<?php echo (int) $link_id; ?>">
											<button class="button button-small" onclick="return confirm('<?php echo esc_js( __( 'Unlink this student?', 'tradeschool-manager' ) ); ?>')"><?php esc_html_e( 'Unlink', 'tradeschool-manager' ); ?></button>
										</form></td>
									</tr>
								<?php endforeach; ?>
								</tbody>
							</table>
						<?php endif; ?>

						<hr style="margin:24px 0;">
						<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" onsubmit="return confirm('<?php echo esc_js( __( 'Delete this sponsor/contact account and all student links? This cannot be undone.', 'tradeschool-manager' ) ); ?>');">
							<?php wp_nonce_field( 'tsm_parents_action' ); ?>
							<input type="hidden" name="action" value="tsm_parents_action">
							<input type="hidden" name="op" value="delete_parent">
							<input type="hidden" name="parent_user_id" value="<?php echo (int) $edit_row->id; ?>">
							<button class="button" style="color:#b91c1c;"><?php esc_html_e( 'Delete Contact Account', 'tradeschool-manager' ); ?></button>
						</form>
					</div>
				<?php else : ?>
					<div class="tsm-pa-card">
						<h2><?php esc_html_e( 'All Sponsors & Contacts', 'tradeschool-manager' ); ?></h2>
						<?php if ( empty( $parents ) ) : ?>
							<div class="tsm-pa-empty"><?php esc_html_e( 'No sponsor/contact accounts yet. Create one or send an invite code.', 'tradeschool-manager' ); ?></div>
						<?php else : ?>
							<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( 'Phone', 'tradeschool-manager' ); ?></th>
									<th><?php esc_html_e( 'Linked Students', 'tradeschool-manager' ); ?></th>
									<th><?php esc_html_e( 'Actions', 'tradeschool-manager' ); ?></th>
								</tr></thead>
								<tbody>
								<?php foreach ( $parents as $p ) :
									$display = trim( $p->first_name . ' ' . $p->last_name ) ?: $p->email;
									$kids = self::students_for_parent( $p->id );
									$names = array();
									foreach ( $kids as $k ) { $names[] = $k->first_name . ' ' . substr( $k->last_name, 0, 1 ) . '.'; } ?>
									<tr>
										<td><strong><a href="<?php echo esc_url( add_query_arg( array( 'tab' => 'parents', 'edit' => $p->id ), $base_url ) ); ?>"><?php echo esc_html( $display ); ?></a></strong></td>
										<td><?php echo esc_html( $p->email ); ?></td>
										<td><?php echo esc_html( $p->phone ? tsm_format_phone( $p->phone ) : ' - ' ); ?></td>
										<td><?php echo $names ? esc_html( implode( ', ', $names ) ) : '<em> - </em>'; ?></td>
										<td class="tsm-pa-actions">
											<a class="button button-small" href="<?php echo esc_url( add_query_arg( array( 'tab' => 'parents', 'edit' => $p->id ), $base_url ) ); ?>"><?php esc_html_e( 'Edit', 'tradeschool-manager' ); ?></a>
											<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" onsubmit="return confirm('<?php echo esc_js( __( 'Email a password reset link to this sponsor/contact?', 'tradeschool-manager' ) ); ?>');">
												<?php wp_nonce_field( 'tsm_parents_action' ); ?>
												<input type="hidden" name="action" value="tsm_parents_action">
												<input type="hidden" name="op" value="send_reset">
												<input type="hidden" name="parent_user_id" value="<?php echo (int) $p->id; ?>">
												<button class="button button-small"><?php esc_html_e( 'Send Reset', 'tradeschool-manager' ); ?></button>
											</form>
										</td>
									</tr>
								<?php endforeach; ?>
								</tbody>
							</table>
						<?php endif; ?>
					</div>
				<?php endif; ?>

			<?php elseif ( 'invites' === $tab ) : ?>
				<div class="tsm-pa-card">
					<h2><?php esc_html_e( 'Issue Self-Signup Code', 'tradeschool-manager' ); ?></h2>
					<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
						<?php wp_nonce_field( 'tsm_parents_action' ); ?>
						<input type="hidden" name="action" value="tsm_parents_action">
						<input type="hidden" name="op" value="create_code">
						<table class="form-table"><tbody>
							<tr><th><?php esc_html_e( 'Student', 'tradeschool-manager' ); ?></th><td>
								<select name="student_id" required><option value=""><?php esc_html_e( ' -  Select  - ', 'tradeschool-manager' ); ?></option>
								<?php foreach ( $students as $s ) : ?><option value="<?php echo (int) $s->id; ?>"><?php echo esc_html( $s->last_name . ', ' . $s->first_name ); ?></option><?php endforeach; ?>
								</select></td></tr>
							<tr><th><?php esc_html_e( 'Relationship / role', 'tradeschool-manager' ); ?></th><td><input type="text" name="relationship" class="regular-text" placeholder="<?php esc_attr_e( 'Sponsor, employer, parent, guardian...', 'tradeschool-manager' ); ?>"></td></tr>
							<tr><th><?php esc_html_e( 'Email (optional)', 'tradeschool-manager' ); ?></th><td><input type="email" name="email" class="regular-text"></td></tr>
							<tr><th><?php esc_html_e( 'Expires in (days)', 'tradeschool-manager' ); ?></th><td><input type="number" name="expires_days" value="30" min="1" max="365" class="small-text"></td></tr>
							<tr><th><?php esc_html_e( 'Permissions', 'tradeschool-manager' ); ?></th><td><label><input type="checkbox" name="can_pay" value="1" checked> <?php esc_html_e( 'May pay invoices', 'tradeschool-manager' ); ?></label></td></tr>
						</tbody></table>
						<?php submit_button( __( 'Generate Code', 'tradeschool-manager' ), 'primary' ); ?>
					</form>
				</div>
				<div class="tsm-pa-card">
					<h2><?php esc_html_e( 'Outstanding Invite Codes', 'tradeschool-manager' ); ?></h2>
					<table class="widefat striped">
						<thead><tr><th><?php esc_html_e( 'Code', 'tradeschool-manager' ); ?></th><th><?php esc_html_e( 'Student', 'tradeschool-manager' ); ?></th><th><?php esc_html_e( 'Email', 'tradeschool-manager' ); ?></th><th><?php esc_html_e( 'Expires', 'tradeschool-manager' ); ?></th><th><?php esc_html_e( 'Status', 'tradeschool-manager' ); ?></th><th></th></tr></thead>
						<tbody>
						<?php if ( empty( $codes ) ) : ?><tr><td colspan="6" class="tsm-pa-empty"><?php esc_html_e( 'No invite codes yet.', 'tradeschool-manager' ); ?></td></tr>
						<?php else : foreach ( $codes as $c ) :
							$status = $c->used_at ? __( 'Redeemed', 'tradeschool-manager' ) : ( ( $c->expires_at && strtotime( $c->expires_at ) < time() ) ? __( 'Expired', 'tradeschool-manager' ) : __( 'Active', 'tradeschool-manager' ) ); ?>
							<tr><td><code><?php echo esc_html( $c->code ); ?></code></td><td><?php echo esc_html( $c->last_name . ', ' . $c->first_name ); ?></td><td><?php echo esc_html( $c->email ?: ' - ' ); ?></td><td><?php echo esc_html( $c->expires_at ?: ' - ' ); ?></td><td><?php echo esc_html( $status ); ?></td>
								<td><form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" style="display:inline;">
									<?php wp_nonce_field( 'tsm_parents_action' ); ?>
									<input type="hidden" name="action" value="tsm_parents_action"><input type="hidden" name="op" value="revoke_code"><input type="hidden" name="code_id" value="<?php echo (int) $c->id; ?>">
									<button class="button button-small" onclick="return confirm('<?php echo esc_js( __( 'Revoke this code?', 'tradeschool-manager' ) ); ?>')"><?php esc_html_e( 'Revoke', 'tradeschool-manager' ); ?></button>
								</form></td></tr>
						<?php endforeach; endif; ?>
						</tbody>
					</table>
				</div>

			<?php else : /* create */ ?>
				<div class="tsm-pa-grid">
					<div class="tsm-pa-card">
						<h2><?php esc_html_e( 'Create Sponsor/Contact Account', 'tradeschool-manager' ); ?></h2>
						<p class="description"><?php esc_html_e( 'Creates the account and emails a link the sponsor/contact uses to set their own password.', 'tradeschool-manager' ); ?></p>
						<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
							<?php wp_nonce_field( 'tsm_parents_action' ); ?>
							<input type="hidden" name="action" value="tsm_parents_action">
							<input type="hidden" name="op" value="create_parent">
							<table class="form-table"><tbody>
								<tr><th><?php esc_html_e( 'Email', 'tradeschool-manager' ); ?></th><td><input type="email" name="email" class="regular-text" required></td></tr>
								<tr><th><?php esc_html_e( 'First name', 'tradeschool-manager' ); ?></th><td><input type="text" name="first_name" class="regular-text"></td></tr>
								<tr><th><?php esc_html_e( 'Last name', 'tradeschool-manager' ); ?></th><td><input type="text" name="last_name" class="regular-text"></td></tr>
								<tr><th><?php esc_html_e( 'Phone', 'tradeschool-manager' ); ?></th><td><input type="text" name="phone" class="regular-text"></td></tr>
							</tbody></table>
							<?php submit_button( __( 'Create & Email Setup Link', 'tradeschool-manager' ), 'primary' ); ?>
						</form>
					</div>
					<div class="tsm-pa-card">
						<h2><?php esc_html_e( 'Link Sponsor/Contact to Student', 'tradeschool-manager' ); ?></h2>
						<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
							<?php wp_nonce_field( 'tsm_parents_action' ); ?>
							<input type="hidden" name="action" value="tsm_parents_action">
							<input type="hidden" name="op" value="link">
							<table class="form-table"><tbody>
								<tr><th><?php esc_html_e( 'Sponsor/contact', 'tradeschool-manager' ); ?></th><td>
									<select name="parent_user_id" required><option value=""><?php esc_html_e( ' -  Select  - ', 'tradeschool-manager' ); ?></option>
									<?php foreach ( $parents as $p ) : $d = trim( $p->first_name . ' ' . $p->last_name ) ?: $p->email; ?>
										<option value="<?php echo (int) $p->id; ?>"><?php echo esc_html( $d . ' (' . $p->email . ')' ); ?></option>
									<?php endforeach; ?>
									</select></td></tr>
								<tr><th><?php esc_html_e( 'Student', 'tradeschool-manager' ); ?></th><td>
									<select name="student_id" required><option value=""><?php esc_html_e( ' -  Select  - ', 'tradeschool-manager' ); ?></option>
									<?php foreach ( $students as $s ) : ?><option value="<?php echo (int) $s->id; ?>"><?php echo esc_html( $s->last_name . ', ' . $s->first_name ); ?></option><?php endforeach; ?>
									</select></td></tr>
								<tr><th><?php esc_html_e( 'Relationship / role', 'tradeschool-manager' ); ?></th><td><input type="text" name="relationship" class="regular-text"></td></tr>
								<tr><th><?php esc_html_e( 'Permissions', 'tradeschool-manager' ); ?></th><td><label><input type="checkbox" name="can_pay" value="1" checked> <?php esc_html_e( 'May pay invoices', 'tradeschool-manager' ); ?></label></td></tr>
							</tbody></table>
							<?php submit_button( __( 'Link Contact', 'tradeschool-manager' ) ); ?>
						</form>
					</div>
				</div>
			<?php endif; ?>
		</div>
		<?php
		if ( $admin && method_exists( $admin, 'shell_close' ) ) { $admin->shell_close( false ); }
	}

	/* ------------------------------------------------------------------ */
	/* Self-signup                                                         */
	/* ------------------------------------------------------------------ */

	public static function handle_signup() {
		check_admin_referer( 'tsm_parent_signup' );
		$signup_url = self::signup_url();
		$err = function( $slug ) use ( $signup_url ) { wp_safe_redirect( add_query_arg( 'tsm_err', $slug, $signup_url ) ); exit; };

		$code  = isset( $_POST['code'] )  ? strtoupper( sanitize_text_field( wp_unslash( $_POST['code'] ) ) ) : '';
		$email = sanitize_email( wp_unslash( $_POST['email'] ?? '' ) );
		$first = sanitize_text_field( wp_unslash( $_POST['first_name'] ?? '' ) );
		$last  = sanitize_text_field( wp_unslash( $_POST['last_name']  ?? '' ) );
		// Passwords are hashed in self::set_password(); raw values must not be sanitized.
		$pwd   = isset( $_POST['password']  ) ? (string) wp_unslash( $_POST['password']  ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$pwd2  = isset( $_POST['password2'] ) ? (string) wp_unslash( $_POST['password2'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized

		if ( ! $code || ! is_email( $email ) || strlen( $pwd ) < 8 || $pwd !== $pwd2 ) { $err( 'invalid' ); }

		global $wpdb;
		$ct = self::codes_table();
		$pt = self::parents_table();
		$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$ct} WHERE code = %s", $code ) );
		if ( ! $row ) { $err( 'bad_code' ); }
		if ( $row->used_at ) { $err( 'used' ); }
		if ( $row->expires_at && strtotime( $row->expires_at ) < time() ) { $err( 'expired' ); }
		if ( $row->email && strcasecmp( $row->email, $email ) !== 0 ) { $err( 'email_mismatch' ); }

		$existing = self::get_by_email( $email );
		if ( $existing ) {
			$pid = (int) $existing->id;
			// Update name if provided, set password.
			$wpdb->update( $pt, array( 'first_name' => $first, 'last_name' => $last ), array( 'id' => $pid ) );
			self::set_password( $pid, $pwd );
		} else {
			$wpdb->insert( $pt, array(
				'first_name' => $first, 'last_name' => $last, 'email' => $email,
				'password_hash' => wp_hash_password( $pwd ),
				'created_at' => current_time( 'mysql' ),
			) );
			$pid = (int) $wpdb->insert_id;
			if ( ! $pid ) { $err( 'user_err' ); }
		}

		// Link.
		$t = self::table();
		$linked = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM {$t} WHERE parent_user_id=%d AND student_id=%d", $pid, $row->student_id ) );
		if ( ! $linked ) {
			$wpdb->insert( $t, array(
				'parent_user_id' => $pid, 'student_id' => $row->student_id,
				'relationship'   => $row->relationship, 'can_pay' => $row->can_pay,
				'created_at'     => current_time( 'mysql' ),
			) );
		}
		$wpdb->update( $ct, array( 'used_at' => current_time( 'mysql' ), 'used_by' => $pid ), array( 'id' => $row->id ) );

		// Start session and send to portal.
		TSM_Parent_Portal::set_current( $pid );
		wp_safe_redirect( self::portal_url() );
		exit;
	}

	/* ------------------------------------------------------------------ */
	/* Pages, shortcodes, emails                                          */
	/* ------------------------------------------------------------------ */

	public static function maybe_create_pages() {
		self::ensure_page( self::PORTAL_PAGE_OPT, self::PORTAL_SLUG, __( 'Sponsor Portal', 'tradeschool-manager' ), '[tsm_parent_portal]' );
		self::ensure_page( self::SIGNUP_PAGE_OPT, self::SIGNUP_SLUG, __( 'Sponsor Signup', 'tradeschool-manager' ), '[tsm_parent_signup]' );
		self::rename_default_page_title( self::PORTAL_PAGE_OPT, 'Parent Portal', __( 'Sponsor Portal', 'tradeschool-manager' ) );
		self::rename_default_page_title( self::SIGNUP_PAGE_OPT, 'Parent Signup', __( 'Sponsor Signup', 'tradeschool-manager' ) );
	}

	protected static function ensure_page( $opt, $slug, $title, $content ) {
		if ( get_option( $opt ) ) { return; }
		$existing = get_page_by_path( $slug );
		if ( $existing ) { update_option( $opt, $existing->ID ); return; }
		$id = wp_insert_post( array( 'post_title' => $title, 'post_name' => $slug, 'post_status' => 'publish', 'post_type' => 'page', 'post_content' => $content ) );
		if ( $id && ! is_wp_error( $id ) ) { update_option( $opt, $id ); }
	}

	protected static function rename_default_page_title( $opt, $old_title, $new_title ) {
		$id = (int) get_option( $opt );
		if ( ! $id ) { return; }
		$page = get_post( $id );
		if ( ! $page || 'page' !== $page->post_type || $old_title !== $page->post_title ) { return; }
		wp_update_post( array( 'ID' => $id, 'post_title' => $new_title ) );
	}

	public static function portal_url() {
		$id = (int) get_option( self::PORTAL_PAGE_OPT );
		return $id ? get_permalink( $id ) : home_url( '/' . self::PORTAL_SLUG . '/' );
	}
	public static function signup_url() {
		$id = (int) get_option( self::SIGNUP_PAGE_OPT );
		return $id ? get_permalink( $id ) : home_url( '/' . self::SIGNUP_SLUG . '/' );
	}

	public static function register_shortcodes() {
		add_shortcode( 'tsm_parent_portal', array( __CLASS__, 'shortcode_portal' ) );
		add_shortcode( 'tsm_parent_signup', array( __CLASS__, 'shortcode_signup' ) );
	}

	private static function school_name() {
		$n = function_exists( 'tsm_get_setting' ) ? tsm_get_setting( 'school_name', '' ) : '';
		return $n ?: get_bloginfo( 'name' );
	}

	public static function send_reset_email( $row ) {
		if ( ! $row || empty( $row->email ) ) { return false; }
		$token     = self::create_reset_token( (int) $row->id );
		if ( ! $token ) { return false; }
		$school    = self::school_name();
		$reset_url = add_query_arg( 'tsm_p_reset', $token, self::portal_url() );
		$name      = trim( $row->first_name . ' ' . $row->last_name ) ?: $row->email;
		/* translators: placeholder(s) for dynamic value(s). */
		$subject   = sprintf( __( '[%s] Reset your sponsor portal password', 'tradeschool-manager' ), $school );
		$lines     = array(
			/* translators: placeholder(s) for dynamic value(s). */
			sprintf( __( 'Hello %s,', 'tradeschool-manager' ), $name ), '',
			/* translators: placeholder(s) for dynamic value(s). */
			sprintf( __( 'A password reset was requested for your %s sponsor portal account.', 'tradeschool-manager' ), $school ),
			__( 'Click the link below to choose a new password (link expires in 24 hours):', 'tradeschool-manager' ),
			$reset_url, '',
			__( "If you didn't request this, you can ignore this email.", 'tradeschool-manager' ),
			/* translators: placeholder(s) for dynamic value(s). */
			'', sprintf( __( ' -  %s', 'tradeschool-manager' ), $school ),
		);
		return wp_mail( $row->email, $subject, implode( "\n", $lines ) );
	}

	public static function send_welcome_email( $row, $is_new_account, $student_id = 0 ) {
		if ( ! $row || empty( $row->email ) ) { return false; }
		$school     = self::school_name();
		$portal_url = self::portal_url();
		$reset_url  = '';
		if ( $is_new_account ) {
			$token = self::create_reset_token( (int) $row->id );
			if ( $token ) { $reset_url = add_query_arg( 'tsm_p_reset', $token, $portal_url ); }
		}
		$student_name = '';
		if ( $student_id && class_exists( 'TSM_Students' ) ) {
			$s = TSM_Students::get( $student_id );
			if ( $s ) { $student_name = trim( ( $s->first_name ?? '' ) . ' ' . ( $s->last_name ?? '' ) ); }
		}
		$name = trim( $row->first_name . ' ' . $row->last_name ) ?: $row->email;
		$subject = $is_new_account
			/* translators: placeholder(s) for dynamic value(s). */
			? sprintf( __( '[%s] Your sponsor portal account', 'tradeschool-manager' ), $school )
			/* translators: placeholder(s) for dynamic value(s). */
			: sprintf( __( '[%s] A student has been linked to your sponsor portal', 'tradeschool-manager' ), $school );
		/* translators: placeholder(s) for dynamic value(s). */
		$lines = array( sprintf( __( 'Hello %s,', 'tradeschool-manager' ), $name ), '' );
		if ( $is_new_account ) {
			/* translators: placeholder(s) for dynamic value(s). */
			$lines[] = sprintf( __( 'A sponsor portal account has been created for you at %s.', 'tradeschool-manager' ), $school );
		} elseif ( $student_name ) {
			/* translators: placeholder(s) for dynamic value(s). */
			$lines[] = sprintf( __( '%s has been linked to your sponsor portal account.', 'tradeschool-manager' ), $student_name );
		}
		$lines[] = ''; $lines[] = __( 'Sponsor portal:', 'tradeschool-manager' ) . ' ' . $portal_url;
		$lines[] = __( 'Sign in with:', 'tradeschool-manager' ) . ' ' . $row->email;
		$lines[] = '';
		if ( $is_new_account && $reset_url ) {
			$lines[] = __( 'Click the link below to set your password and sign in:', 'tradeschool-manager' );
			$lines[] = $reset_url;
		}
		/* translators: placeholder(s) for dynamic value(s). */
		$lines[] = ''; $lines[] = sprintf( __( ' -  %s', 'tradeschool-manager' ), $school );
		return wp_mail( $row->email, $subject, implode( "\n", $lines ) );
	}

	public static function send_invite_email( $email, $code, $student_id, $days ) {
		$school     = self::school_name();
		$signup_url = add_query_arg( 'code', rawurlencode( $code ), self::signup_url() );
		$student_name = '';
		if ( $student_id && class_exists( 'TSM_Students' ) ) {
			$s = TSM_Students::get( $student_id );
			if ( $s ) { $student_name = trim( ( $s->first_name ?? '' ) . ' ' . ( $s->last_name ?? '' ) ); }
		}
		/* translators: placeholder(s) for dynamic value(s). */
		$subject = sprintf( __( '[%s] Create your sponsor portal account', 'tradeschool-manager' ), $school );
		$lines = array( __( 'Hello,', 'tradeschool-manager' ), '' );
		if ( $student_name ) {
			/* translators: placeholder(s) for dynamic value(s). */
			$lines[] = sprintf( __( '%1$s has invited you to create a sponsor portal account so you can view progress or make authorized payments for %2$s.', 'tradeschool-manager' ), $school, $student_name );
		}
		$lines[] = '';
		$lines[] = __( 'Sign up here:', 'tradeschool-manager' ) . ' ' . $signup_url;
		$lines[] = __( 'Your invite code:', 'tradeschool-manager' ) . ' ' . $code;
		$lines[] = '';
		/* translators: placeholder(s) for dynamic value(s). */
		$lines[] = sprintf( __( 'This code expires in %d days.', 'tradeschool-manager' ), (int) $days );
		/* translators: placeholder(s) for dynamic value(s). */
		$lines[] = ''; $lines[] = sprintf( __( ' -  %s', 'tradeschool-manager' ), $school );
		return wp_mail( $email, $subject, implode( "\n", $lines ) );
	}

	/* ------------------------------------------------------------------ */
	/* Signup shortcode                                                    */
	/* ------------------------------------------------------------------ */

	public static function shortcode_signup() {
		$err = isset( $_GET['tsm_err'] ) ? sanitize_key( wp_unslash( $_GET['tsm_err'] ) ) : '';
		$messages = array(
			'invalid'        => __( 'Please fill in every field. Password must be at least 8 characters and match.', 'tradeschool-manager' ),
			'bad_code'       => __( 'That invite code wasn\'t found.', 'tradeschool-manager' ),
			'used'           => __( 'This invite code has already been used.', 'tradeschool-manager' ),
			'expired'        => __( 'This invite code has expired.', 'tradeschool-manager' ),
			'email_mismatch' => __( 'This code is locked to a different email address.', 'tradeschool-manager' ),
			'user_err'       => __( 'Could not create the account. Try again.', 'tradeschool-manager' ),
		);
		ob_start();
		TSM_Inline_Assets::style(
			'tsm-parent-signup',
			'.tsm-ps-card{background:#fff;border:1px solid #e5e7eb;border-radius:10px;padding:24px;}.tsm-ps-card h2{margin:0 0 6px;}.tsm-ps-card p.lead{color:#6b7280;margin:0 0 18px;}.tsm-ps-card label{display:block;font-weight:600;margin:10px 0 4px;font-size:14px;}.tsm-ps-card input{width:100%;padding:9px 11px;border:1px solid #d1d5db;border-radius:6px;font-size:14px;}.tsm-ps-card .row{display:grid;grid-template-columns:1fr 1fr;gap:10px;}.tsm-ps-card button{margin-top:18px;background:#2563eb;color:#fff;border:0;padding:11px 16px;border-radius:6px;font-weight:600;cursor:pointer;width:100%;}.tsm-ps-err{background:#fef2f2;border:1px solid #fecaca;color:#991b1b;padding:10px 12px;border-radius:6px;margin-bottom:14px;font-size:14px;}.tsm-ps-foot{text-align:center;font-size:13px;color:#6b7280;margin-top:14px;}'
		);
		?>
		<div class="tsm-parent-signup" style="max-width:520px;margin:0 auto;font-family:inherit;">
			<div class="tsm-ps-card">
				<h2><?php esc_html_e( 'Sponsor Portal Signup', 'tradeschool-manager' ); ?></h2>
				<p class="lead"><?php esc_html_e( 'Use the invite code the school gave you to access linked student information.', 'tradeschool-manager' ); ?></p>
				<?php if ( $err && isset( $messages[ $err ] ) ) : ?>
					<div class="tsm-ps-err"><?php echo esc_html( $messages[ $err ] ); ?></div>
				<?php endif; ?>
				<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
					<?php wp_nonce_field( 'tsm_parent_signup' ); ?>
					<input type="hidden" name="action" value="tsm_parent_signup">
					<label><?php esc_html_e( 'Invite code', 'tradeschool-manager' ); ?></label>
					<input type="text" name="code" required value="<?php echo esc_attr( sanitize_text_field( wp_unslash( $_GET['code'] ?? '' ) ) ); ?>" style="text-transform:uppercase;letter-spacing:1px;"><?php // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public signup form prefill from invite link; value is escaped + sanitized. ?>
					<label><?php esc_html_e( 'Email', 'tradeschool-manager' ); ?></label>
					<input type="email" name="email" required>
					<div class="row">
						<div><label><?php esc_html_e( 'First name', 'tradeschool-manager' ); ?></label><input type="text" name="first_name" required></div>
						<div><label><?php esc_html_e( 'Last name', 'tradeschool-manager' ); ?></label><input type="text" name="last_name" required></div>
					</div>
					<div class="row">
						<div><label><?php esc_html_e( 'Password', 'tradeschool-manager' ); ?></label><input type="password" name="password" required minlength="8"></div>
						<div><label><?php esc_html_e( 'Confirm password', 'tradeschool-manager' ); ?></label><input type="password" name="password2" required minlength="8"></div>
					</div>
					<button type="submit"><?php esc_html_e( 'Create account', 'tradeschool-manager' ); ?></button>
				</form>
				<p class="tsm-ps-foot">
					<a href="<?php echo esc_url( self::portal_url() ); ?>"><?php esc_html_e( 'Already have an account? Sign in.', 'tradeschool-manager' ); ?></a>
				</p>
			</div>
		</div>
		<?php
		return ob_get_clean();
	}

	/* ------------------------------------------------------------------ */
	/* Portal shortcode                                                    */
	/* ------------------------------------------------------------------ */

	public static function shortcode_portal( $atts ) {
		$pid = class_exists( 'TSM_Parent_Portal' ) ? TSM_Parent_Portal::current_parent_id() : 0;

		// Reset-password flow: show form if URL has token (?tsm_p_reset=...).
		$reset_token = isset( $_GET['tsm_p_reset'] ) ? sanitize_text_field( wp_unslash( $_GET['tsm_p_reset'] ) ) : '';
		if ( $reset_token ) {
			$row = self::get_by_reset_token( $reset_token );
			if ( $row ) { return self::render_reset_card( $reset_token ); }
		}

		if ( ! $pid ) { return self::render_login_card(); }

		$parent = self::get( $pid );
		if ( ! $parent ) { TSM_Parent_Portal::logout(); return self::render_login_card(); }

		$students = self::students_for_parent( $pid );
		if ( empty( $students ) ) {
			return '<div class="tsm-parent-portal" style="max-width:520px;margin:24px auto;font-family:inherit;"><p>' . esc_html__( 'No students are linked to your account yet. Contact the school office.', 'tradeschool-manager' ) . '</p><p><a href="' . esc_url( add_query_arg( 'tsm_parent_logout', 1, self::portal_url() ) ) . '">' . esc_html__( 'Log out', 'tradeschool-manager' ) . '</a></p></div>';
		}

		$active_id = isset( $_GET['student'] ) ? (int) $_GET['student'] : (int) $students[0]->id;
		$active    = null;
		foreach ( $students as $s ) { if ( (int) $s->id === $active_id ) { $active = $s; break; } }
		if ( ! $active ) { $active = $students[0]; $active_id = (int) $active->id; }

		$tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : 'overview';
		$allowed_tabs = array( 'overview', 'grades', 'attendance', 'payments', 'contact', 'settings' );
		if ( ! in_array( $tab, $allowed_tabs, true ) ) { $tab = 'overview'; }

		$sent_msg = isset( $_GET['tsm_msg_sent'] ) ? '1' === $_GET['tsm_msg_sent'] : false;
		ob_start();
		TSM_Inline_Assets::style(
			'tsm-parent-portal',
			'.tsm-pp-tabs{display:flex;gap:4px;flex-wrap:wrap;border-bottom:1px solid #e5e7eb;margin-bottom:16px;overflow-x:auto;}.tsm-pp-tabs a{padding:10px 14px;text-decoration:none;color:#374151;border-bottom:3px solid transparent;font-weight:500;white-space:nowrap;}.tsm-pp-tabs a.active{color:var(--tsm-primary,#2563eb);border-bottom-color:var(--tsm-primary,#2563eb);}.tsm-pp-card{background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:18px;margin-bottom:16px;box-shadow:0 1px 3px rgba(15,23,42,.04);}.tsm-pp-table{width:100%;border-collapse:collapse;}.tsm-pp-table th,.tsm-pp-table td{padding:9px 10px;border-bottom:1px solid #f1f5f9;text-align:left;font-size:14px;}.tsm-pp-hero{background:var(--tsm-cover,linear-gradient(135deg,#1e3a8a,#2563eb));color:#fff;border-radius:14px;padding:22px 24px;margin-bottom:18px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;}.tsm-pp-hero h2{margin:0;color:#fff;font-size:22px;}.tsm-pp-hero .role{display:inline-block;background:rgba(255,255,255,.2);padding:3px 10px;border-radius:99px;font-size:12px;margin-top:4px;}.tsm-pp-btn{display:inline-block;background:var(--tsm-primary,#2563eb);color:#fff;border:0;padding:9px 14px;border-radius:8px;font-weight:600;cursor:pointer;text-decoration:none;font-size:14px;}'
		);
		?>
		<div class="tsm-parent-portal tsm-portal-wrap" style="font-family:inherit;">
			<div class="tsm-pp-hero">
				<div>
					<h2><?php echo esc_html( self::school_name() ); ?> <?php esc_html_e( 'Sponsor Portal', 'tradeschool-manager' ); ?></h2>
					<span class="role"><?php echo esc_html( trim( $parent->first_name . ' ' . $parent->last_name ) ?: $parent->email ); ?></span>
				</div>
				<div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">
					<form method="get" style="background:rgba(255,255,255,.9);border-radius:8px;padding:6px 10px;color:#111;margin:0;">
						<label><?php esc_html_e( 'Viewing:', 'tradeschool-manager' ); ?>
							<select name="student" onchange="this.form.submit()">
							<?php foreach ( $students as $s ) : ?>
								<option value="<?php echo (int) $s->id; ?>" <?php selected( $active_id, (int) $s->id ); ?>><?php echo esc_html( $s->first_name . ' ' . $s->last_name ); ?></option>
							<?php endforeach; ?>
							</select>
						</label>
						<input type="hidden" name="tab" value="<?php echo esc_attr( $tab ); ?>">
					</form>
					<a href="<?php echo esc_url( add_query_arg( 'tsm_parent_logout', 1, self::portal_url() ) ); ?>" class="tsm-pp-btn" style="background:rgba(255,255,255,.18);border:1px solid rgba(255,255,255,.4);"><?php esc_html_e( 'Log out', 'tradeschool-manager' ); ?></a>
				</div>
			</div>

			<?php if ( $sent_msg ) : ?>
				<div class="tsm-pp-card" style="border-left:4px solid #16a34a;background:#f0fdf4;"><strong><?php esc_html_e( 'Message sent.', 'tradeschool-manager' ); ?></strong></div>
			<?php endif; ?>

			<nav class="tsm-pp-tabs">
				<?php
				$labels = array(
					'overview'   => __( 'Overview', 'tradeschool-manager' ),
					'grades'     => __( 'Grades', 'tradeschool-manager' ),
					'attendance' => __( 'Attendance', 'tradeschool-manager' ),
				);
				if ( function_exists( 'tsm_addon_active' ) && tsm_addon_active( 'invoicing' ) ) {
					$labels['payments'] = __( 'Payments & Invoices', 'tradeschool-manager' );
				}
				$labels['contact']  = __( 'Contact Teacher', 'tradeschool-manager' );
				$labels['settings'] = __( 'Settings', 'tradeschool-manager' );
				$base = remove_query_arg( array( 'tab', 'tsm_msg_sent' ) );
				foreach ( $labels as $key => $label ) :
					$url = add_query_arg( array( 'student' => $active_id, 'tab' => $key ), $base ); ?>
					<a href="<?php echo esc_url( $url ); ?>" class="<?php echo $tab === $key ? 'active' : ''; ?>"><?php echo esc_html( $label ); ?></a>
				<?php endforeach; ?>
			</nav>

			<?php self::render_tab( $tab, $active, $parent ); ?>
		</div>
		<?php
		return ob_get_clean();
	}

	/* ------------------------------------------------------------------ */
	/* Login / reset cards                                                 */
	/* ------------------------------------------------------------------ */

	protected static function render_login_card() {
		$school = self::school_name();
		$err     = isset( $_GET['tsm_p_login_error'] ) ? 1 : 0;
		$out     = isset( $_GET['tsm_p_logged_out'] )  ? 1 : 0;
		$sent    = isset( $_GET['tsm_p_forgot_sent'] ) ? 1 : 0;
		$reset_done = isset( $_GET['tsm_p_reset_done'] ) ? 1 : 0;
		$mode    = isset( $_GET['tsm_p_view'] ) && 'forgot' === $_GET['tsm_p_view'] ? 'forgot' : 'login';
		ob_start();
		TSM_Inline_Assets::style(
			'tsm-parent-login',
			'.tsm-pl-card{background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:28px;box-shadow:0 4px 18px rgba(15,23,42,.05);}.tsm-pl-card h2{margin:0 0 4px;}.tsm-pl-card p.sub{color:#6b7280;margin:0 0 18px;font-size:14px;}.tsm-pl-card label{display:block;font-weight:600;margin:10px 0 4px;font-size:14px;}.tsm-pl-card input{width:100%;padding:10px 12px;border:1px solid #d1d5db;border-radius:6px;font-size:14px;box-sizing:border-box;}.tsm-pl-card button{margin-top:16px;background:#2563eb;color:#fff;border:0;padding:11px 16px;border-radius:6px;font-weight:600;cursor:pointer;width:100%;font-size:15px;}.tsm-pl-foot{text-align:center;font-size:13px;color:#6b7280;margin-top:14px;}.tsm-pl-err{background:#fef2f2;border:1px solid #fecaca;color:#991b1b;padding:10px 12px;border-radius:6px;margin-bottom:14px;font-size:14px;}.tsm-pl-ok{background:#ecfdf5;border:1px solid #a7f3d0;color:#065f46;padding:10px 12px;border-radius:6px;margin-bottom:14px;font-size:14px;}'
		);
		?>
		<div class="tsm-parent-portal" style="max-width:420px;margin:24px auto;font-family:inherit;">
			<div class="tsm-pl-card">
				<h2><?php echo esc_html( $school ); ?></h2>
				<p class="sub"><?php esc_html_e( 'Sponsor Portal', 'tradeschool-manager' ); ?></p>
				<?php if ( $err ) :
					$reason = isset( $_GET['tsm_p_login_reason'] ) ? sanitize_key( wp_unslash( $_GET['tsm_p_login_reason'] ) ) : '';
					$msg = __( 'Invalid email or password.', 'tradeschool-manager' );
					if ( 'no_user' === $reason )     { $msg = __( 'No sponsor/contact account found with that email. Use your invite code to sign up first.', 'tradeschool-manager' ); }
					elseif ( 'no_password' === $reason ) { $msg = __( 'This account has no password yet. Use "Forgot password?" to set one.', 'tradeschool-manager' ); }
					elseif ( 'bad_password' === $reason ) { $msg = __( 'Incorrect password. Use "Forgot password?" if needed.', 'tradeschool-manager' ); }
					?><div class="tsm-pl-err"><?php echo esc_html( $msg ); ?></div><?php endif; ?>
				<?php if ( $out ) : ?><div class="tsm-pl-ok"><?php esc_html_e( 'You have been logged out.', 'tradeschool-manager' ); ?></div><?php endif; ?>
				<?php if ( $sent ) : ?><div class="tsm-pl-ok"><?php esc_html_e( 'If that email is on file, a reset link is on its way.', 'tradeschool-manager' ); ?></div><?php endif; ?>
				<?php if ( $reset_done ) : ?><div class="tsm-pl-ok"><?php esc_html_e( 'Password updated. You can sign in below.', 'tradeschool-manager' ); ?></div><?php endif; ?>

				<?php if ( 'forgot' === $mode ) : ?>
					<form method="post">
						<?php wp_nonce_field( 'tsm_parent_forgot', 'tsm_parent_forgot_nonce' ); ?>
						<label><?php esc_html_e( 'Email', 'tradeschool-manager' ); ?></label>
						<input type="email" name="tsm_parent_email" required autocomplete="email">
						<button type="submit"><?php esc_html_e( 'Send reset link', 'tradeschool-manager' ); ?></button>
					</form>
					<p class="tsm-pl-foot"><a href="<?php echo esc_url( self::portal_url() ); ?>"><?php esc_html_e( 'Back to sign in', 'tradeschool-manager' ); ?></a></p>
				<?php else : ?>
					<form method="post">
						<?php wp_nonce_field( 'tsm_parent_login', 'tsm_parent_login_nonce' ); ?>
						<input type="hidden" name="tsm_parent_login" value="1">
						<label><?php esc_html_e( 'Email', 'tradeschool-manager' ); ?></label>
						<input type="text" name="tsm_parent_username" required autocomplete="username">
						<label><?php esc_html_e( 'Password', 'tradeschool-manager' ); ?></label>
						<input type="password" name="tsm_parent_password" required autocomplete="current-password">
						<button type="submit"><?php esc_html_e( 'Sign In', 'tradeschool-manager' ); ?></button>
					</form>
					<p class="tsm-pl-foot">
						<a href="<?php echo esc_url( add_query_arg( 'tsm_p_view', 'forgot', self::portal_url() ) ); ?>"><?php esc_html_e( 'Forgot password?', 'tradeschool-manager' ); ?></a>
						&nbsp;-&nbsp;
						<a href="<?php echo esc_url( self::signup_url() ); ?>"><?php esc_html_e( 'Have an invite code?', 'tradeschool-manager' ); ?></a>
					</p>
				<?php endif; ?>
			</div>
		</div>
		<?php
		return ob_get_clean();
	}

	protected static function render_reset_card( $token ) {
		$err = isset( $_GET['tsm_p_reset_error'] ) ? sanitize_key( wp_unslash( $_GET['tsm_p_reset_error'] ) ) : '';
		$msgs = array(
			'invalid'  => __( 'Reset link is invalid or has expired.', 'tradeschool-manager' ),
			'short'    => __( 'Password must be at least 8 characters.', 'tradeschool-manager' ),
			'mismatch' => __( 'Passwords do not match.', 'tradeschool-manager' ),
		);
		ob_start(); ?>
		<div class="tsm-parent-portal" style="max-width:420px;margin:24px auto;font-family:inherit;">
			<div style="background:#fff;border:1px solid #e5e7eb;border-radius:12px;padding:28px;box-shadow:0 4px 18px rgba(15,23,42,.05);">
				<h2 style="margin:0 0 4px;"><?php esc_html_e( 'Choose a new password', 'tradeschool-manager' ); ?></h2>
				<p style="color:#6b7280;margin:0 0 18px;font-size:14px;"><?php echo esc_html( self::school_name() ); ?></p>
				<?php if ( $err && isset( $msgs[ $err ] ) ) : ?>
					<div style="background:#fef2f2;border:1px solid #fecaca;color:#991b1b;padding:10px 12px;border-radius:6px;margin-bottom:14px;font-size:14px;"><?php echo esc_html( $msgs[ $err ] ); ?></div>
				<?php endif; ?>
				<form method="post">
					<?php wp_nonce_field( 'tsm_parent_reset', 'tsm_parent_reset_nonce' ); ?>
					<input type="hidden" name="tsm_parent_reset_token" value="<?php echo esc_attr( $token ); ?>">
					<label style="display:block;font-weight:600;margin:10px 0 4px;font-size:14px;"><?php esc_html_e( 'New password', 'tradeschool-manager' ); ?></label>
					<input type="password" name="tsm_parent_new_password" minlength="8" required autocomplete="new-password" style="width:100%;padding:10px 12px;border:1px solid #d1d5db;border-radius:6px;box-sizing:border-box;">
					<label style="display:block;font-weight:600;margin:10px 0 4px;font-size:14px;"><?php esc_html_e( 'Confirm password', 'tradeschool-manager' ); ?></label>
					<input type="password" name="tsm_parent_new_password2" minlength="8" required autocomplete="new-password" style="width:100%;padding:10px 12px;border:1px solid #d1d5db;border-radius:6px;box-sizing:border-box;">
					<button type="submit" style="margin-top:16px;background:#2563eb;color:#fff;border:0;padding:11px 16px;border-radius:6px;font-weight:600;cursor:pointer;width:100%;font-size:15px;"><?php esc_html_e( 'Set Password &amp; Sign In', 'tradeschool-manager' ); ?></button>
				</form>
			</div>
		</div>
		<?php
		return ob_get_clean();
	}

	/* ------------------------------------------------------------------ */
	/* Tab renderers                                                       */
	/* ------------------------------------------------------------------ */

	protected static function render_tab( $tab, $student, $parent ) {
		echo '<div class="tsm-pp-card">';
		switch ( $tab ) {
			case 'overview':
				echo '<h3 style="margin-top:0;">' . esc_html( $student->first_name . ' ' . $student->last_name ) . '</h3>';
				echo '<p style="color:#6b7280;font-size:13px;margin:0 0 12px;">' . esc_html__( 'Authorized view for sponsors and contacts. Contact the school office to change student details.', 'tradeschool-manager' ) . '</p>';
				echo '<table class="tsm-pp-table">';
				echo '<tr><th>' . esc_html__( 'Email', 'tradeschool-manager' ) . '</th><td>' . esc_html( $student->email ?: ' - ' ) . '</td></tr>';
				echo '<tr><th>' . esc_html__( 'Phone', 'tradeschool-manager' ) . '</th><td>' . esc_html( $student->phone ? tsm_format_phone( $student->phone ) : ' - ' ) . '</td></tr>';
				echo '<tr><th>' . esc_html__( 'Status', 'tradeschool-manager' ) . '</th><td>' . esc_html( $student->status ) . '</td></tr>';
				echo '</table>';
				break;
			case 'grades':     self::render_simple_grades( $student->id ); break;
			case 'attendance': self::render_simple_attendance( $student->id ); break;
			case 'payments':   if ( function_exists( 'tsm_addon_active' ) && tsm_addon_active( 'invoicing' ) ) { self::render_simple_payments( $student->id ); } break;
			case 'contact':    self::render_contact_teacher( $student ); break;
			case 'settings':   self::render_settings_tab( $parent ); break;
		}
		echo '</div>';
	}

	protected static function render_simple_grades( $student_id ) {
		global $wpdb;
		$g = $wpdb->prefix . 'tsm_grades'; $a = $wpdb->prefix . 'tsm_assignments'; $c = $wpdb->prefix . 'tsm_classes';
		$rows = $wpdb->get_results( $wpdb->prepare(
			"SELECT g.points_earned, g.excused, g.late_penalty, a.title AS assignment, a.points_possible, c.name AS class_name
			   FROM {$g} g
			   JOIN {$a} a ON a.id = g.assignment_id
			   JOIN {$c} c ON c.id = a.class_id
			  WHERE g.student_id = %d
			  ORDER BY g.id DESC LIMIT 200",
			$student_id
		) );
		if ( empty( $rows ) ) { echo '<p>' . esc_html__( 'No grades recorded yet.', 'tradeschool-manager' ) . '</p>'; return; }
		echo '<table class="tsm-pp-table"><thead><tr><th>' . esc_html__( 'Class', 'tradeschool-manager' ) . '</th><th>' . esc_html__( 'Assignment', 'tradeschool-manager' ) . '</th><th>' . esc_html__( 'Score', 'tradeschool-manager' ) . '</th></tr></thead><tbody>';
		foreach ( $rows as $r ) {
			if ( $r->excused ) {
				$score = esc_html__( 'Excused', 'tradeschool-manager' );
			} elseif ( null === $r->points_earned || '' === $r->points_earned ) {
				$score = ' - ';
			} else {
				$earned = max( 0, (float) $r->points_earned - (float) $r->late_penalty );
				$score  = rtrim( rtrim( number_format( $earned, 2, '.', '' ), '0' ), '.' ) . ' / ' . rtrim( rtrim( number_format( (float) $r->points_possible, 2, '.', '' ), '0' ), '.' );
			}
			echo '<tr><td>' . esc_html( $r->class_name ) . '</td><td>' . esc_html( $r->assignment ) . '</td><td>' . esc_html( $score ) . '</td></tr>';
		}
		echo '</tbody></table>';
	}

	protected static function render_simple_attendance( $student_id ) {
		global $wpdb;
		$t = $wpdb->prefix . 'tsm_attendance';
		$rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$t} WHERE student_id = %d ORDER BY date DESC LIMIT 200", $student_id ) );
		if ( empty( $rows ) ) { echo '<p>' . esc_html__( 'No attendance records.', 'tradeschool-manager' ) . '</p>'; return; }
		echo '<table class="tsm-pp-table"><thead><tr><th>' . esc_html__( 'Date', 'tradeschool-manager' ) . '</th><th>' . esc_html__( 'Status', 'tradeschool-manager' ) . '</th></tr></thead><tbody>';
		foreach ( $rows as $r ) { echo '<tr><td>' . esc_html( $r->date ) . '</td><td>' . esc_html( $r->status ) . '</td></tr>'; }
		echo '</tbody></table>';
	}

	protected static function render_simple_payments( $student_id ) {
		global $wpdb;
		$inv = $wpdb->prefix . 'tsm_invoices';
		$rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$inv} WHERE student_id = %d ORDER BY due_date ASC LIMIT 200", $student_id ) );
		if ( empty( $rows ) ) { echo '<p>' . esc_html__( 'No invoices on file.', 'tradeschool-manager' ) . '</p>'; return; }
		echo '<table class="tsm-pp-table"><thead><tr><th>' . esc_html__( 'Invoice', 'tradeschool-manager' ) . '</th><th>' . esc_html__( 'Due', 'tradeschool-manager' ) . '</th><th>' . esc_html__( 'Amount', 'tradeschool-manager' ) . '</th><th>' . esc_html__( 'Status', 'tradeschool-manager' ) . '</th></tr></thead><tbody>';
		foreach ( $rows as $r ) {
			$amt = isset( $r->total ) ? $r->total : ( isset( $r->amount ) ? $r->amount : '' );
			echo '<tr><td>#' . (int) $r->id . '</td><td>' . esc_html( TSM_Date_Utils::display( $r->due_date ?? '' ) ) . '</td><td>' . esc_html( number_format( (float) $amt, 2 ) ) . '</td><td>' . esc_html( $r->status ?? '' ) . '</td></tr>';
		}
		echo '</tbody></table>';
	}

	protected static function render_contact_teacher( $student ) {
		echo '<h3 style="margin-top:0;">' . esc_html__( 'Message a Teacher', 'tradeschool-manager' ) . '</h3>';
		if ( ! class_exists( 'TSM_Classes' ) || ! class_exists( 'TSM_Teachers' ) ) {
			echo '<p><em>' . esc_html__( 'Messaging is unavailable.', 'tradeschool-manager' ) . '</em></p>'; return;
		}
		$classes = TSM_Classes::classes_for_student( $student->id );
		if ( empty( $classes ) ) { echo '<p><em>' . esc_html__( 'No classes are linked to this student yet.', 'tradeschool-manager' ) . '</em></p>'; return; }
		// Build option list  -  include all classes with an assigned teacher; flag missing emails.
		$options    = array();
		$no_email   = array();
		foreach ( $classes as $c ) {
			$t = ! empty( $c->teacher_id ) ? TSM_Teachers::get( $c->teacher_id ) : null;
			if ( ! $t ) { continue; }
			$teacher_name = trim( $t->first_name . ' ' . $t->last_name );
			$label = $c->name . '  -  ' . ( $teacher_name ?: __( 'Teacher', 'tradeschool-manager' ) );
			if ( empty( $t->email ) ) {
				$no_email[] = $label;
				continue;
			}
			$options[ (int) $c->id ] = $label;
		}
		if ( empty( $options ) ) {
			echo '<p><em>' . esc_html__( 'No teacher email is on file for this student\'s classes. Please contact the school office.', 'tradeschool-manager' ) . '</em></p>';
			return;
		} ?>
		<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" class="tsm-pp-msg-form">
			<?php wp_nonce_field( 'tsm_parent_message' ); ?>
			<input type="hidden" name="action" value="tsm_parent_message">
			<input type="hidden" name="student_id" value="<?php echo (int) $student->id; ?>">
			<input type="hidden" name="return_url" value="<?php echo esc_url( add_query_arg( array( 'student' => $student->id, 'tab' => 'contact' ), remove_query_arg( 'tsm_msg_sent' ) ) ); ?>">
			<p><label><strong><?php esc_html_e( 'Class / Teacher', 'tradeschool-manager' ); ?></strong><br>
				<select name="class_id" required style="width:100%;padding:8px 10px;border:1px solid #d1d5db;border-radius:8px;">
				<?php foreach ( $options as $cid => $label ) : ?>
					<option value="<?php echo (int) $cid; ?>"><?php echo esc_html( $label ); ?></option>
				<?php endforeach; ?>
				</select></label></p>
			<?php if ( ! empty( $no_email ) ) : ?>
				<p style="font-size:13px;color:#6b7280;"><em><?php /* translators: placeholder(s) for dynamic value(s). */ echo esc_html( sprintf( __( 'Not shown (no email on file): %s', 'tradeschool-manager' ), implode( ', ', $no_email ) ) ); ?></em></p>
			<?php endif; ?>
			<p><label><strong><?php esc_html_e( 'Subject', 'tradeschool-manager' ); ?></strong><br>
				<input type="text" name="subject" required maxlength="180" style="width:100%;padding:8px 10px;border:1px solid #d1d5db;border-radius:8px;"></label></p>
			<p><label><strong><?php esc_html_e( 'Message', 'tradeschool-manager' ); ?></strong><br>
				<textarea name="body" required maxlength="4000" style="width:100%;min-height:90px;padding:8px 10px;border:1px solid #d1d5db;border-radius:8px;"></textarea></label></p>
			<p><button type="submit" class="tsm-pp-btn"><?php esc_html_e( 'Send Message', 'tradeschool-manager' ); ?></button></p>
		</form>
		<?php
	}

	public static function handle_parent_message() {
		check_admin_referer( 'tsm_parent_message' );
		$pid = class_exists( 'TSM_Parent_Portal' ) ? TSM_Parent_Portal::current_parent_id() : 0;
		if ( ! $pid ) { wp_die( esc_html__( 'Please sign in.', 'tradeschool-manager' ) ); }
		$parent     = self::get( $pid );
		$student_id = absint( $_POST['student_id'] ?? 0 );
		$class_id   = absint( $_POST['class_id'] ?? 0 );
		$subject    = sanitize_text_field( wp_unslash( $_POST['subject'] ?? '' ) );
		$body       = sanitize_textarea_field( wp_unslash( $_POST['body'] ?? '' ) );
		$return     = isset( $_POST['return_url'] ) ? esc_url_raw( wp_unslash( $_POST['return_url'] ) ) : self::portal_url();

		$ok = false;
		foreach ( self::students_for_parent( $pid ) as $s ) { if ( (int) $s->id === $student_id ) { $ok = true; break; } }
		if ( ! $ok || ! $subject || ! $body ) { wp_safe_redirect( add_query_arg( 'tsm_msg_sent', '0', $return ) ); exit; }

		$class   = class_exists( 'TSM_Classes' ) ? TSM_Classes::get( $class_id ) : null;
		$teacher = ( $class && ! empty( $class->teacher_id ) && class_exists( 'TSM_Teachers' ) ) ? TSM_Teachers::get( $class->teacher_id ) : null;
		if ( ! $teacher || empty( $teacher->email ) ) { wp_safe_redirect( add_query_arg( 'tsm_msg_sent', '0', $return ) ); exit; }

		$student      = TSM_Students::get( $student_id );
		$school       = self::school_name();
		$student_name = $student ? trim( $student->first_name . ' ' . $student->last_name ) : '';
		$parent_name  = trim( $parent->first_name . ' ' . $parent->last_name ) ?: $parent->email;
		$parent_email = $parent->email;

		/* translators: placeholder(s) for dynamic value(s). */
		$full_subject = '[' . $school . '] ' . sprintf( __( 'Sponsor/contact message re: %s', 'tradeschool-manager' ), $student_name ) . ' - ' . $subject;
		$full_body    = sprintf( "%s\n\nFrom: %s <%s>\nRegarding: %s\nClass: %s\n\n---\n%s\n",
			__( 'A sponsor/contact sent you a message through the sponsor portal.', 'tradeschool-manager' ),
			$parent_name, $parent_email, $student_name, $class->name, $body
		);
		$headers = array( 'Reply-To: ' . $parent_name . ' <' . $parent_email . '>' );

		// Replay guard  -  prevent rapid double-submits (browser back/forward, double-click,
		// admin-post re-entry) from sending the teacher the same message twice.
		$dedupe_key = 'tsm_pm_' . md5( $pid . '|' . $student_id . '|' . $class_id . '|' . $subject . '|' . $body );
		if ( get_transient( $dedupe_key ) ) {
			wp_safe_redirect( add_query_arg( 'tsm_msg_sent', '1', $return ) );
			exit;
		}
		set_transient( $dedupe_key, 1, 5 * MINUTE_IN_SECONDS );

		wp_mail( $teacher->email, $full_subject, $full_body, $headers );
		// NOTE: We intentionally do NOT send a second copy to the parent here. Sending the
		// teacher-addressed body back to the parent caused confusion ("teacher got it twice")
		// and offered no real value  -  the portal shows a sent confirmation instead.

		wp_safe_redirect( add_query_arg( 'tsm_msg_sent', '1', $return ) );
		exit;
	}

	/* ------------------------------------------------------------------ */
	/* Settings tab                                                        */
	/* ------------------------------------------------------------------ */

	protected static function render_settings_tab( $parent ) {
		$saved = isset( $_GET['tsm_profile_saved'] ) ? sanitize_key( wp_unslash( $_GET['tsm_profile_saved'] ) ) : '';
		$pwmsg = isset( $_GET['tsm_pw_msg'] )       ? sanitize_key( wp_unslash( $_GET['tsm_pw_msg'] ) )       : '';
		?>
		<h3 style="margin-top:0;"><?php esc_html_e( 'My Profile', 'tradeschool-manager' ); ?></h3>
		<?php if ( 'ok' === $saved )          { echo '<div style="background:#ecfdf5;color:#065f46;padding:10px 12px;border-radius:8px;margin-bottom:12px;">' . esc_html__( 'Profile updated.', 'tradeschool-manager' ) . '</div>'; } ?>
		<?php if ( 'email_taken' === $saved ) { echo '<div style="background:#fef2f2;color:#991b1b;padding:10px 12px;border-radius:8px;margin-bottom:12px;">' . esc_html__( 'That email is already in use.', 'tradeschool-manager' ) . '</div>'; } ?>
		<?php if ( 'bad_email' === $saved )   { echo '<div style="background:#fef2f2;color:#991b1b;padding:10px 12px;border-radius:8px;margin-bottom:12px;">' . esc_html__( 'Please enter a valid email.', 'tradeschool-manager' ) . '</div>'; } ?>
		<form method="post" style="max-width:520px;">
			<?php wp_nonce_field( 'tsm_parent_profile', 'tsm_parent_profile_nonce' ); ?>
			<p><label><strong><?php esc_html_e( 'First name', 'tradeschool-manager' ); ?></strong><br>
				<input type="text" name="first_name" value="<?php echo esc_attr( $parent->first_name ); ?>" style="width:100%;padding:8px 10px;border:1px solid #d1d5db;border-radius:8px;"></label></p>
			<p><label><strong><?php esc_html_e( 'Last name', 'tradeschool-manager' ); ?></strong><br>
				<input type="text" name="last_name" value="<?php echo esc_attr( $parent->last_name ); ?>" style="width:100%;padding:8px 10px;border:1px solid #d1d5db;border-radius:8px;"></label></p>
			<p><label><strong><?php esc_html_e( 'Email', 'tradeschool-manager' ); ?></strong><br>
				<input type="email" name="email" value="<?php echo esc_attr( $parent->email ); ?>" required style="width:100%;padding:8px 10px;border:1px solid #d1d5db;border-radius:8px;"></label></p>
			<p><label><strong><?php esc_html_e( 'Phone', 'tradeschool-manager' ); ?></strong><br>
				<input type="text" name="phone" value="<?php echo esc_attr( $parent->phone ); ?>" style="width:100%;padding:8px 10px;border:1px solid #d1d5db;border-radius:8px;"></label></p>
			<p><button class="tsm-pp-btn" type="submit"><?php esc_html_e( 'Save Profile', 'tradeschool-manager' ); ?></button></p>
		</form>

		<hr style="margin:24px 0;">
		<h3><?php esc_html_e( 'Change Password', 'tradeschool-manager' ); ?></h3>
		<?php if ( 'ok' === $pwmsg )       { echo '<div style="background:#ecfdf5;color:#065f46;padding:10px 12px;border-radius:8px;margin-bottom:12px;">' . esc_html__( 'Password updated.', 'tradeschool-manager' ) . '</div>'; } ?>
		<?php if ( 'current' === $pwmsg )  { echo '<div style="background:#fef2f2;color:#991b1b;padding:10px 12px;border-radius:8px;margin-bottom:12px;">' . esc_html__( 'Current password is incorrect.', 'tradeschool-manager' ) . '</div>'; } ?>
		<?php if ( 'short' === $pwmsg )    { echo '<div style="background:#fef2f2;color:#991b1b;padding:10px 12px;border-radius:8px;margin-bottom:12px;">' . esc_html__( 'New password must be at least 8 characters.', 'tradeschool-manager' ) . '</div>'; } ?>
		<?php if ( 'mismatch' === $pwmsg ) { echo '<div style="background:#fef2f2;color:#991b1b;padding:10px 12px;border-radius:8px;margin-bottom:12px;">' . esc_html__( 'New passwords do not match.', 'tradeschool-manager' ) . '</div>'; } ?>
		<form method="post" style="max-width:520px;">
			<?php wp_nonce_field( 'tsm_parent_chpw', 'tsm_parent_chpw_nonce' ); ?>
			<p><label><strong><?php esc_html_e( 'Current password', 'tradeschool-manager' ); ?></strong><br>
				<input type="password" name="tsm_parent_current_password" required autocomplete="current-password" style="width:100%;padding:8px 10px;border:1px solid #d1d5db;border-radius:8px;"></label></p>
			<p><label><strong><?php esc_html_e( 'New password', 'tradeschool-manager' ); ?></strong><br>
				<input type="password" name="tsm_parent_new_password" required minlength="8" autocomplete="new-password" style="width:100%;padding:8px 10px;border:1px solid #d1d5db;border-radius:8px;"></label></p>
			<p><label><strong><?php esc_html_e( 'Confirm new password', 'tradeschool-manager' ); ?></strong><br>
				<input type="password" name="tsm_parent_new_password2" required minlength="8" autocomplete="new-password" style="width:100%;padding:8px 10px;border:1px solid #d1d5db;border-radius:8px;"></label></p>
			<p><button class="tsm-pp-btn" type="submit"><?php esc_html_e( 'Change Password', 'tradeschool-manager' ); ?></button></p>
		</form>
		<?php
	}
}

TSM_Parents::init();
