<?php

/**
 * Trait: ASTGDRMSYS_Retake_Helper
 * Handles retake mark lookup and effective mark calculation.
 */

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

trait ASTGDRMSYS_Retake_Helper
{
	/**
	 * Fetch retake marks for a student result, keyed by subject code.
	 *
	 * @param int $sid Student result ID.
	 * @return array<string, array<string, mixed>>
	 */
	protected function get_retake_marks_by_sid($sid)
	{
		$table = esc_sql($this->prefix . 'astgdrmsys_retake_marks');

		if (! $this->retake_marks_table_exists($table)) {
			return array();
		}

		$rows = $this->wpdb->get_results(
			$this->wpdb->prepare(
				"SELECT * FROM {$table} WHERE sid = %d ORDER BY subject_code ASC",
				absint($sid)
			),
			ARRAY_A
		);

		$retakes = array();
		foreach ((array) $rows as $row) {
			$subject_code = isset($row['subject_code']) ? trim((string) $row['subject_code']) : '';
			if ('' === $subject_code) {
				continue;
			}

			$retakes[$subject_code] = $row;
		}

		return $retakes;
	}

	/**
	 * Return true when a student result has at least one retake record.
	 *
	 * @param int $sid Student result ID.
	 * @return bool
	 */
	protected function student_has_retake_marks($sid)
	{
		return ! empty($this->get_retake_marks_by_sid($sid));
	}

	/**
	 * Return true when any student result in a list has retake records.
	 *
	 * @param array<int, array<string, mixed>> $students Student result rows.
	 * @return bool
	 */
	protected function any_student_has_retake_marks(array $students)
	{
		if (empty($students)) {
			return false;
		}

		$sids = array();
		foreach ($students as $student) {
			if (! empty($student['sid'])) {
				$sids[] = absint($student['sid']);
			}
		}

		$sids = array_values(array_filter(array_unique($sids)));
		if (empty($sids)) {
			return false;
		}

		$table        = esc_sql($this->prefix . 'astgdrmsys_retake_marks');
		if (! $this->retake_marks_table_exists($table)) {
			return false;
		}

		$placeholders = implode(',', array_fill(0, count($sids), '%d'));
		$count        = $this->wpdb->get_var(
			$this->wpdb->prepare(
				"SELECT COUNT(*) FROM {$table} WHERE sid IN ({$placeholders})",
				$sids
			)
		);

		return (int) $count > 0;
	}

	/**
	 * Check whether the retake marks table exists for this request.
	 *
	 * @param string $table Fully-prefixed table name.
	 * @return bool
	 */
	protected function retake_marks_table_exists($table)
	{
		static $exists = null;

		if (null !== $exists) {
			return $exists;
		}

		$exists = ($table === $this->wpdb->get_var($this->wpdb->prepare('SHOW TABLES LIKE %s', $table)));

		return $exists;
	}

	/**
	 * Apply retake marks to stored marks and keep original totals for display.
	 *
	 * @param int   $sid   Student result ID.
	 * @param array $marks Stored marks.
	 * @return array
	 */
	protected function apply_retake_marks_to_marks($sid, array $marks)
	{
		$retakes = $this->get_retake_marks_by_sid($sid);

		if (empty($retakes)) {
			return $marks;
		}

		foreach ($retakes as $subject_code => $retake) {
			$retake_mark = isset($retake['retake_mark']) ? floatval($retake['retake_mark']) : null;

			if (null === $retake_mark) {
				continue;
			}

			$existing_mark = $marks[$subject_code] ?? null;
			$marks[$subject_code] = $this->apply_retake_to_subject_mark($existing_mark, $retake_mark, $retake);
		}

		return $marks;
	}

	/**
	 * Add retake metadata to a subject row after it has already been built.
	 *
	 * @param array<string, mixed> $subject_row Subject row containing a mark key.
	 * @param array<string, mixed> $retake      Retake row.
	 * @return array<string, mixed>
	 */
	protected function apply_retake_to_subject_row(array $subject_row, array $retake)
	{
		$retake_mark = isset($retake['retake_mark']) ? floatval($retake['retake_mark']) : null;

		if (null === $retake_mark) {
			return $subject_row;
		}

		$subject_row['mark'] = $this->apply_retake_to_subject_mark(
			$subject_row['mark'] ?? null,
			$retake_mark,
			$retake
		);
		$subject_row['retake'] = $retake;

		if (isset($subject_row['subject'])) {
			$subject_row['remark']      = $this->calculate_subject_remark($subject_row['subject'], $subject_row['mark']);
			$subject_row['grade_point'] = $this->calculate_grade_point($subject_row['subject'], $subject_row['mark']);

			if (empty($subject_row['is_fourth'])) {
				$subject_row['gpa_without_4'] = $this->calculate_gpa_without_4($subject_row['subject'], $subject_row['mark']);
			}
		}

		return $subject_row;
	}

	/**
	 * Calculate a subject mark payload with retake metadata.
	 *
	 * @param mixed                $mark        Stored subject mark.
	 * @param float                $retake_mark Retake mark.
	 * @param array<string, mixed> $retake      Retake row.
	 * @return array<string, mixed>
	 */
	protected function apply_retake_to_subject_mark($mark, $retake_mark, array $retake = array())
	{
		$original_total = $this->get_original_subject_total($mark);
		$final_total    = (null === $original_total || $retake_mark > $original_total) ? $retake_mark : $original_total;
		$retake_counted = (null === $original_total || $retake_mark > $original_total);

		if (is_array($mark)) {
			$updated_mark = $mark;
		} else {
			$updated_mark = array(
				'final' => is_numeric($mark) ? floatval($mark) : $mark,
			);
		}

		unset($updated_mark['not_taken']);

		if (null !== $original_total) {
			$updated_mark['original_total'] = $original_total;
		}

		$updated_mark['retake_mark']        = $retake_mark;
		$updated_mark['retake_note']        = isset($retake['note']) ? (string) $retake['note'] : '';
		$updated_mark['retake_date']        = isset($retake['retake_date']) ? (string) $retake['retake_date'] : '';
		$updated_mark['retake_counted']     = $retake_counted ? 1 : 0;
		$updated_mark['final_counted_mark'] = $final_total;
		$updated_mark['total']              = $final_total;

		return $updated_mark;
	}

	/**
	 * Get the original total from a mark payload before retake replacement.
	 *
	 * @param mixed $mark Subject mark.
	 * @return float|null
	 */
	protected function get_original_subject_total($mark)
	{
		if (is_array($mark)) {
			if (isset($mark['original_total']) && is_numeric($mark['original_total'])) {
				return floatval($mark['original_total']);
			}

			if (isset($mark['total']) && is_numeric($mark['total'])) {
				return floatval($mark['total']);
			}

			$total       = 0;
			$has_numeric = false;
			foreach ($mark as $key => $value) {
				if (in_array($key, array('not_taken', 'fourth_subject', 'retake_mark', 'retake_counted', 'final_counted_mark', 'retake_note', 'retake_date'), true)) {
					continue;
				}

				if (is_numeric($value)) {
					$total       += floatval($value);
					$has_numeric  = true;
				}
			}

			return $has_numeric ? $total : null;
		}

		return is_numeric($mark) ? floatval($mark) : null;
	}

	/**
	 * Format a mark without forcing unnecessary decimal places.
	 *
	 * @param mixed $mark Mark value.
	 * @return string
	 */
	protected function format_retake_mark_value($mark)
	{
		if (! is_numeric($mark)) {
			return '' === (string) $mark ? '-' : (string) $mark;
		}

		$value = floatval($mark);

		return floor($value) === $value ? (string) intval($value) : rtrim(rtrim(number_format($value, 2, '.', ''), '0'), '.');
	}
}
