<?php
require_once __DIR__ . '/../db.php';

function getCurricula() {
    return getDB()->query("SELECT * FROM curricula ORDER BY code")->fetchAll();
}

function getCurriculum($id) {
    $stmt = getDB()->prepare("SELECT * FROM curricula WHERE id = ?");
    $stmt->execute([$id]);
    return $stmt->fetch();
}

function getCourseGroups($curriculum_id, $parent_id = null) {
    if ($parent_id === null) {
        $stmt = getDB()->prepare("SELECT * FROM course_groups WHERE curriculum_id = ? AND parent_id IS NULL ORDER BY sort_order");
        $stmt->execute([$curriculum_id]);
    } else {
        $stmt = getDB()->prepare("SELECT * FROM course_groups WHERE parent_id = ? ORDER BY sort_order");
        $stmt->execute([$parent_id]);
    }
    return $stmt->fetchAll();
}

function getCoursesByGroup($group_id) {
    $stmt = getDB()->prepare("SELECT * FROM courses WHERE group_id = ? ORDER BY sort_order");
    $stmt->execute([$group_id]);
    return $stmt->fetchAll();
}

function getStudents($curriculum_id = null) {
    if ($curriculum_id === null && isStudent()) {
        $stmt = getDB()->prepare("
            SELECT s.*, c.code AS curriculum_code, c.name_th AS curriculum_name
            FROM students s
            JOIN curricula c ON s.curriculum_id = c.id
            WHERE s.id = ?
            ORDER BY s.student_code
        ");
        $stmt->execute([getCurrentUserId()]);
        return $stmt->fetchAll();
    }
    if ($curriculum_id === null && isAdvisor()) {
        $stmt = getDB()->prepare("
            SELECT s.*, c.code AS curriculum_code, c.name_th AS curriculum_name
            FROM students s
            JOIN curricula c ON s.curriculum_id = c.id
            WHERE s.advisor_id = ?
            ORDER BY s.student_code
        ");
        $stmt->execute([getCurrentUserId()]);
        return $stmt->fetchAll();
    }
    if ($curriculum_id) {
        $stmt = getDB()->prepare("
            SELECT s.*, c.code AS curriculum_code, c.name_th AS curriculum_name
            FROM students s
            JOIN curricula c ON s.curriculum_id = c.id
            WHERE s.curriculum_id = ?
            ORDER BY s.student_code
        ");
        $stmt->execute([$curriculum_id]);
        return $stmt->fetchAll();
    }
    return getDB()->query("
        SELECT s.*, c.code AS curriculum_code, c.name_th AS curriculum_name
        FROM students s
        JOIN curricula c ON s.curriculum_id = c.id
        ORDER BY s.student_code
    ")->fetchAll();
}

function getStudent($id) {
    $stmt = getDB()->prepare("
        SELECT s.*, c.code AS curriculum_code, c.name_th AS curriculum_name, c.total_credits
        FROM students s
        JOIN curricula c ON s.curriculum_id = c.id
        WHERE s.id = ?
    ");
    $stmt->execute([$id]);
    return $stmt->fetch();
}

function getFacultyList() {
    return [
        '12' => 'คณะนวัตกรรมดิจิทัลเทคโนโลยี',
        '11' => 'คณะบริหารธุรกิจ',
        '13' => 'คณะศึกษาศาสตร์และศิลปศาสตร์',
        '14' => 'คณะบัญชี',
        '15' => 'คณะนิติศาสตร์และรัฐศาสตร์',
    ];
}

function getTransferCreditsTotal($student_id) {
    $stmt = getDB()->prepare("SELECT COALESCE(SUM(credits), 0) FROM student_courses WHERE student_id = ? AND source_type = 'transfer'");
    $stmt->execute([$student_id]);
    return floatval($stmt->fetchColumn());
}

function getCompletedCreditsTotal($student_id) {
    $stmt = getDB()->prepare("SELECT COALESCE(SUM(credits), 0) FROM student_courses WHERE student_id = ?");
    $stmt->execute([$student_id]);
    return floatval($stmt->fetchColumn());
}

function getStudentCourses($student_id) {
    $stmt = getDB()->prepare("
        SELECT sc.*, c.code AS curriculum_course_code
        FROM student_courses sc
        LEFT JOIN courses c ON sc.course_id = c.id
        WHERE sc.student_id = ?
        ORDER BY sc.course_code
    ");
    $stmt->execute([$student_id]);
    return $stmt->fetchAll();
}

function getStudentCoursesByGroup($student_id, $group_id) {
    $stmt = getDB()->prepare("
        SELECT sc.* FROM student_courses sc
        JOIN courses c ON sc.course_id = c.id
        WHERE sc.student_id = ? AND c.group_id = ?
    ");
    $stmt->execute([$student_id, $group_id]);
    return $stmt->fetchAll();
}

/**
 * Build recursive tree of course groups for a curriculum
 */
function buildGroupTree($curriculum_id, $parent_id = null) {
    $groups = getCourseGroups($curriculum_id, $parent_id);
    $tree = [];
    foreach ($groups as $g) {
        $children = buildGroupTree($curriculum_id, $g['id']);
        $courses = getCoursesByGroup($g['id']);
        $node = [
            'group' => $g,
            'children' => $children,
            'courses' => $courses,
            'total_credits' => 0,
            'completed_credits' => 0,
        ];
        // Calculate total credits from courses
        foreach ($courses as $c) {
            $node['total_credits'] += $c['credits'];
        }
        // Add children totals
        foreach ($children as $ch) {
            $node['total_credits'] += $ch['total_credits'];
        }
        $tree[] = $node;
    }
    return $tree;
}

function getStudentSummary($student_id) {
    $student = getStudent($student_id);
    if (!$student) return null;

    $tree = buildGroupTree($student['curriculum_id']);

    $student_courses = getStudentCourses($student_id);
    $completed_map = [];
    foreach ($student_courses as $sc) {
        $completed_map[$sc['course_code']] = $sc;
    }

    // Mark completed courses recursively
    $total_completed = 0;
    foreach ($student_courses as $sc) {
        $total_completed += $sc['credits'];
    }

    $markCompleted = function(&$nodes) use (&$markCompleted, $completed_map) {
        foreach ($nodes as &$node) {
            foreach ($node['courses'] as &$course) {
                if (isset($completed_map[$course['code']])) {
                    $course['_completed'] = $completed_map[$course['code']];
                    $node['completed_credits'] += $course['credits'];
                }
            }
            if (!empty($node['children'])) {
                $markCompleted($node['children']);
                // Sum children completed credits
                foreach ($node['children'] as $ch) {
                    $node['completed_credits'] += $ch['completed_credits'];
                }
            }
        }
    };
    $markCompleted($tree);

    return [
        'student' => $student,
        'groups' => $tree,
        'total_completed' => $total_completed,
        'student_courses' => $student_courses,
        'completed_map' => $completed_map
    ];
}

function findCourseInCurriculum($curriculum_id, $code) {
    $stmt = getDB()->prepare("
        SELECT c.* FROM courses c
        JOIN course_groups cg ON c.group_id = cg.id
        WHERE cg.curriculum_id = ? AND c.code = ?
    ");
    $stmt->execute([$curriculum_id, $code]);
    return $stmt->fetch();
}

function getGroupSelectOptions($curriculum_id, $selected_id = null, $parent_id = null, $prefix = '') {
    $groups = getCourseGroups($curriculum_id, $parent_id);
    $html = '';
    foreach ($groups as $g) {
        $sel = $g['id'] == $selected_id ? ' selected' : '';
        $html .= '<option value="' . $g['id'] . '"' . $sel . '>' . $prefix . htmlspecialchars($g['name_th']) . '</option>';
        $html .= getGroupSelectOptions($curriculum_id, $selected_id, $g['id'], $prefix . '— ');
    }
    return $html;
}

function getGradeOptions($selected = null) {
    $grades = ['A', 'B+', 'B', 'C+', 'C', 'D+', 'D', 'F', 'S', 'U', 'กำลังเรียน'];
    $html = '';
    foreach ($grades as $g) {
        $sel = $g === $selected ? ' selected' : '';
        $html .= '<option value="' . $g . '"' . $sel . '>' . $g . '</option>';
    }
    return $html;
}

function getCourseTypeOptions($selected = null) {
    $types = [
        'เรียนปกติ' => 'เรียนปกติ',
        'เทียบโอนรายวิชา' => 'เทียบโอนรายวิชา',
        'เทียบโอนกลุ่มวิชา' => 'เทียบโอนกลุ่มวิชา',
        'กิจกรรม/สหกิจศึกษา' => 'กิจกรรม/สหกิจศึกษา',
        'ฝึกอบรม' => 'ฝึกอบรม',
    ];
    $html = '';
    foreach ($types as $val => $label) {
        $sel = $val === $selected ? ' selected' : '';
        $html .= '<option value="' . $val . '"' . $sel . '>' . $label . '</option>';
    }
    return $html;
}

function getTotalCurriculumCredits($curriculum_id) {
    $stmt = getDB()->prepare("SELECT total_credits FROM curricula WHERE id = ?");
    $stmt->execute([$curriculum_id]);
    $r = $stmt->fetch();
    return $r ? $r['total_credits'] : 0;
}

// ============================================================
// Access Control Helpers
// ============================================================

function requireStudentAccess($student_id) {
    if (isAdmin()) return;
    if (isStudent() && getCurrentUserId() == $student_id) return;
    $db = getDB();
    $stmt = $db->prepare("SELECT advisor_id, curriculum_id FROM students WHERE id = ?");
    $stmt->execute([$student_id]);
    $student = $stmt->fetch();
    if (!$student || $student['advisor_id'] != getCurrentUserId()) {
        header('Location: ' . BASE_URL . '/index.php');
        exit;
    }
}

// ============================================================
// User Management Functions
// ============================================================

function getUsers() {
    return getDB()->query("SELECT u.*, c.code AS curriculum_code, c.name_th AS curriculum_name FROM users u LEFT JOIN curricula c ON u.curriculum_id = c.id ORDER BY u.staff_code")->fetchAll();
}

function getAdvisors() {
    return getDB()->query("SELECT u.id, u.name, u.staff_code, c.code AS curriculum_code FROM users u LEFT JOIN curricula c ON u.curriculum_id = c.id WHERE u.role = 'advisor' ORDER BY u.name")->fetchAll();
}

function getUser($id) {
    $stmt = getDB()->prepare("SELECT u.*, c.code AS curriculum_code, c.name_th AS curriculum_name FROM users u LEFT JOIN curricula c ON u.curriculum_id = c.id WHERE u.id = ?");
    $stmt->execute([$id]);
    return $stmt->fetch();
}

function authenticateUser($staff_code, $password) {
    $stmt = getDB()->prepare("SELECT * FROM users WHERE staff_code = ?");
    $stmt->execute([$staff_code]);
    $user = $stmt->fetch();
    if ($user && password_verify($password, $user['password'])) {
        return $user;
    }
    return false;
}

// ============================================================
// PDF Parsing Functions
// ============================================================

function extractTextFromPDF($filepath) {
    $text = trySmalotParser($filepath);
    if (!empty(trim($text))) return $text;

    $text = tryPdftotext($filepath);
    if (!empty(trim($text))) return $text;

    $text = tryBasicPdfParse($filepath);
    if (!empty(trim($text))) return $text;

    return tryRawPdfRead($filepath);
}

function trySmalotParser($filepath) {
    if (!class_exists('Smalot\\PdfParser\\Parser')) {
        spl_autoload_register(function ($class) {
            $prefix = 'Smalot\\PdfParser\\';
            $base_dir = __DIR__ . '/../vendor/smalot/pdfparser/src/';
            if (strncmp($prefix, $class, strlen($prefix)) === 0) {
                $file = $base_dir . str_replace('\\', '/', $class) . '.php';
                if (file_exists($file)) { require $file; }
            }
        });
    }
    try {
        $parser = new Smalot\PdfParser\Parser();
        $pdf = $parser->parseFile($filepath);
        return $pdf->getText();
    } catch (Throwable $e) {
        return '';
    }
}

function tryPdftotext($filepath) {
    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
        $exe_paths = [
            'C:\xampp\htdocs\Transfer\vendor\pdftotext.exe',
            'pdftotext',
            'C:\Program Files\pdftotext\pdftotext.exe',
        ];
    } else {
        $exe_paths = [
            'pdftotext',
            '/usr/bin/pdftotext'
        ];
    }

    foreach ($exe_paths as $exe) {
        $output = [];
        $cmd = escapeshellcmd($exe) . ' ' . escapeshellarg($filepath) . ' -';
        @exec($cmd, $output, $return_var);
        if ($return_var === 0 && !empty(implode('', $output))) {
            return implode("\n", $output);
        }
    }
    return '';
}

function tryBasicPdfParse($filepath) {
    $content = file_get_contents($filepath);
    if ($content === false) return '';

    $text = '';
    $offset = 0;
    
    while (($start = strpos($content, 'stream', $offset)) !== false) {
        $stream_start = $start + 6;
        // Skip leading whitespace (usually CR/LF or LF) after 'stream'
        while ($stream_start < strlen($content) && ctype_space($content[$stream_start])) {
            $stream_start++;
        }
        
        $end = strpos($content, 'endstream', $stream_start);
        if ($end === false) {
            break;
        }
        
        $stream = substr($content, $stream_start, $end - $stream_start);
        $stream = rtrim($stream);
        
        $decoded = @gzuncompress($stream);
        if ($decoded === false) {
            $decoded = @gzuncompress(trim($stream));
        }
        
        if ($decoded !== false) {
            if (strlen($decoded) > 100000) continue;
            preg_match_all('/\((.*?)\)\s*Tj/s', $decoded, $text_matches);
            $text .= implode(' ', $text_matches[1]) . "\n";

            preg_match_all('/\[(.*?)\]\s*TJ/s', $decoded, $tj_matches);
            foreach ($tj_matches[1] as $tj) {
                preg_match_all('/\((.*?)\)/', $tj, $tj_text);
                $text .= implode('', $tj_text[1]) . ' ';
            }
        }
        
        $offset = $end + 9;
    }

    return $text;
}

function tryRawPdfRead($filepath) {
    $content = file_get_contents($filepath);
    if ($content === false) return '';
    
    // Cap binary parsing to prevent CPU exhaustion on very large or corrupt files
    if (strlen($content) > 500000) {
        $content = substr($content, 0, 500000);
    }

    $text = '';
    $len = strlen($content);
    $in_paren = false;
    $buffer = '';
    
    for ($i = 0; $i < $len; $i++) {
        $char = $content[$i];
        if ($char === '(') {
            $in_paren = true;
            $buffer = '';
        } else if ($char === ')') {
            if ($in_paren) {
                if (strlen($buffer) > 0 && strlen($buffer) <= 200) {
                    $text .= $buffer . "\n";
                }
                $in_paren = false;
            }
        } else if ($in_paren) {
            $buffer .= $char;
            if (strlen($buffer) > 200) {
                $in_paren = false;
            }
        }
    }
    return $text;
}

function parseCoursesFromText($text) {
    $courses = [];

    // Try Pattern A: 9-digit code (transfer PDF format with multi-line text)
    // Strip first 2 digits for 7-digit curriculum code
    $courses = parseTransferPdfCourses($text);

    // Fallback: Standard 7-digit code format
    if (empty($courses)) {
        $courses = parseStandardCourses($text);
    }

    return $courses;
}

function parseTransferPdfCourses($text) {
    $courses = [];
    $lines = explode("\n", $text);

    $lines = array_map(function($l) {
        return str_replace("\t", " ", trim($l));
    }, $lines);
    $lines = array_values(array_filter($lines, function($l) {
        return $l !== '';
    }));

    $current_code = '';
    $current_name_parts = [];

    $flushCourse = function() use (&$current_code, &$current_name_parts, &$courses) {
        if ($current_code && !empty($current_name_parts)) {
            $courses[] = finalizeTransferCourse($current_code, $current_name_parts);
            $current_code = '';
            $current_name_parts = [];
        }
    };

    for ($i = 0; $i < count($lines); $i++) {
        $line = $lines[$i];

        // Single regex: optional seq# (1-2 digits with optional trailing space) + 9-digit code + rest
        $m = [];
        if (preg_match('/^(\d{1,2})?\s*(\d{9})(.*)$/', $line, $m)) {
            $pdf_code = $m[2];
            $after_code = trim($m[3]);

            if ($current_code) {
                $courses[] = finalizeTransferCourse($current_code, $current_name_parts);
                $current_code = '';
                $current_name_parts = [];
            }

            $current_code = $pdf_code;
            $current_name_parts = [];

            if ($after_code !== '') {
                if (preg_match('/^(.+?)\s+(\d+\.\d)\s*$/', $after_code, $nm)) {
                    $current_name_parts[] = trim($nm[1]);
                    $courses[] = finalizeTransferCourse($current_code, $current_name_parts, floatval($nm[2]));
                    $current_code = '';
                    $current_name_parts = [];
                } else {
                    $current_name_parts[] = $after_code;
                }
            }
            continue;
        }

        if ($current_code) {
            if (preg_match('/^(\d+\.\d)\s*$/', $line, $cm)) {
                $courses[] = finalizeTransferCourse($current_code, $current_name_parts, floatval($cm[1]));
                $current_code = '';
                $current_name_parts = [];
                continue;
            }

            if (preg_match('/^(.+?)\s+(\d+\.\d)\s*$/', $line, $nm)) {
                $current_name_parts[] = trim($nm[1]);
                $courses[] = finalizeTransferCourse($current_code, $current_name_parts, floatval($nm[2]));
                $current_code = '';
                $current_name_parts = [];
                continue;
            }

            $current_name_parts[] = $line;
        }
    }

    if ($current_code && !empty($current_name_parts)) {
        $courses[] = finalizeTransferCourse($current_code, $current_name_parts);
    }

    return $courses;
}

function finalizeTransferCourse($pdf_code, $name_parts, $credits = null) {
    // Clean the name: remove extra whitespace, collapsed newlines
    $name = preg_replace('/\s+/', ' ', implode(' ', $name_parts));
    $name = trim($name);
    // Remove trailing orphan characters like "-"
    $name = rtrim($name, ' -');

    // Strip first 2 digits from 9-digit code to get 7-digit curriculum code
    $curriculum_code = substr($pdf_code, 2);

    // If credits not specified, try to find a number in the name
    if ($credits === null) {
        if (preg_match('/\b(\d+\.\d)\b/', $name, $cm)) {
            $credits = floatval($cm[1]);
            $name = trim(str_replace($cm[0], '', $name));
        } else {
            $credits = 3.0; // default
        }
    }

    return [
        'code' => $curriculum_code,
        'name_th' => $name,
        'credits' => $credits,
        'lecture' => 0,
        'practice' => 0,
        'self_study' => 0,
    ];
}

function parseStandardCourses($text) {
    $courses = [];

    // Pattern: Standard format "0001101 จิตวิทยาทั่วไป 3(3-0-6)"
    preg_match_all('/(\d{7})\s+([^\d]+?)\s+(\d+(?:\.\d)?)\s*\(\s*(\d+)\s*[-–]\s*(\d+)\s*[-–]\s*(\d+)\s*\)/', $text, $matches, PREG_SET_ORDER);
    foreach ($matches as $m) {
        $courses[] = [
            'code' => trim($m[1]),
            'name_th' => trim($m[2]),
            'credits' => floatval($m[3]),
            'lecture' => intval($m[4]),
            'practice' => intval($m[5]),
            'self_study' => intval($m[6]),
        ];
    }

    // Pattern: Simple format "0001101 จิตวิทยาทั่วไป 3"
    if (empty($courses)) {
        preg_match_all('/(\d{7})\s+([^\d]{2,80}?)\s+(\d+(?:\.\d)?)(?:\s|$)/', $text, $matches, PREG_SET_ORDER);
        foreach ($matches as $m) {
            $courses[] = [
                'code' => trim($m[1]),
                'name_th' => trim($m[2]),
                'credits' => floatval($m[3]),
                'lecture' => 0, 'practice' => 0, 'self_study' => 0,
            ];
        }
    }

    return $courses;
}

function getStudentTransferPdfPath($student_id) {
    $stmt = getDB()->prepare("SELECT DISTINCT source_file FROM student_courses WHERE student_id = ? AND source_type = 'transfer' AND source_file IS NOT NULL LIMIT 1");
    $stmt->execute([$student_id]);
    $filename = $stmt->fetchColumn();
    if ($filename) {
        return __DIR__ . '/../uploads/' . $filename;
    }
    return null;
}

function parseTransferReportFromPdf($filepath) {
    if (!$filepath || !file_exists($filepath)) return null;
    
    // Register autoloader for Smalot Parser just in case it is not registered yet
    spl_autoload_register(function ($class) {
        $prefix = 'Smalot\\PdfParser\\';
        $base_dir = __DIR__ . '/../vendor/smalot/pdfparser/src/';
        if (strncmp($prefix, $class, strlen($prefix)) === 0) {
            $file = $base_dir . str_replace('\\', '/', $class) . '.php';
            if (file_exists($file)) { require $file; }
        }
    });
    
    $text = extractTextFromPDF($filepath);
    $lines = explode("\n", $text);
    
    $pattern = '/^(?:(\d+)\s+)?(\d*)(\d{9})\s*(.+?)\s+(\d+\.\d)\s+(\d{5}-\d{4})\s*(.+?)\s+(\d+(?:\.\d)?)\s+([A-F][+-]?|[0-4](?:\.[0-9])?|S|U)\s*(\d+(?:\.\d+)?%)/u';
    $source_pattern = '/^(\d{5}-\d{4})\s*(.+?)\s+(\d+(?:\.\d)?)\s+([A-F][+-]?|[0-4](?:\.[0-9])?|S|U)\s*(\d+(?:\.\d+)?%)/u';
    $target_only_pattern = '/^(?:(\d+)\s+)?(\d*)(\d{9})\s*(.+?)\s+(\d+\.\d)\s*$/u';
    $untransferable_pattern = '/^(?:(\d+)\s+)?(\d*)(\d{5}-\d{4})\s*(.+?)\s+(\d+(?:\.\d)?)\s+([A-F][+-]?|[0-4](?:\.[0-9])?|[ก-๙]\.?|S|U|W|I)\s*(.+)$/u';
    
    $results = [];
    $untransferable = [];
    
    $start_transfer = false;
    $start_untransfer = false;
    
    foreach ($lines as $line) {
        $line = trim($line);
        if ($line === '') continue;
        
        if (mb_strpos($line, 'รายละเอียดวิชาที่เทียบโอนได้') !== false) {
            $start_transfer = true;
            $start_untransfer = false;
            continue;
        }
        if (mb_strpos($line, 'วิชาต้นทางที่ไม่สามารถเทียบโอนได้') !== false) {
            $start_transfer = false;
            $start_untransfer = true;
            continue;
        }
        if (mb_strpos($line, 'ผู้ประเมิน') !== false || mb_strpos($line, '(............................................)') !== false) {
            $start_transfer = false;
            $start_untransfer = false;
            break;
        }
        
        if ($start_transfer) {
            if (preg_match($pattern, $line, $m)) {
                $seq = !empty($m[1]) ? $m[1] : (!empty($m[2]) ? $m[2] : (count($results) + 1));
                $target_code = $m[3];
                $target_name = trim($m[4]);
                $target_credits = floatval($m[5]);
                
                $source_code = $m[6];
                $source_name = trim($m[7]);
                $source_credits = floatval($m[8]);
                $source_grade = $m[9];
                $similarity = $m[10];
                
                $results[] = [
                    'seq' => $seq,
                    'target_code' => $target_code,
                    'target_name' => $target_name,
                    'target_credits' => $target_credits,
                    'sources' => [
                        [
                            'code' => $source_code,
                            'name' => $source_name,
                            'credits' => $source_credits,
                            'grade' => $source_grade,
                            'similarity' => $similarity
                        ]
                    ]
                ];
            } else if (preg_match($target_only_pattern, $line, $m)) {
                $seq = !empty($m[1]) ? $m[1] : (!empty($m[2]) ? $m[2] : (count($results) + 1));
                $target_code = $m[3];
                $target_name = trim($m[4]);
                $target_credits = floatval($m[5]);
                
                $results[] = [
                    'seq' => $seq,
                    'target_code' => $target_code,
                    'target_name' => $target_name,
                    'target_credits' => $target_credits,
                    'sources' => []
                ];
            } else if (preg_match($source_pattern, $line, $m) && !empty($results)) {
                $source_code = $m[1];
                $source_name = trim($m[2]);
                $source_credits = floatval($m[3]);
                $source_grade = $m[4];
                $similarity = $m[5];
                
                $results[count($results) - 1]['sources'][] = [
                    'code' => $source_code,
                    'name' => $source_name,
                    'credits' => $source_credits,
                    'grade' => $source_grade,
                    'similarity' => $similarity
                ];
            }
        } else if ($start_untransfer) {
            if (preg_match($untransferable_pattern, $line, $m)) {
                $seq = !empty($m[1]) ? $m[1] : (!empty($m[2]) ? $m[2] : (count($untransferable) + 1));
                $source_code = $m[3];
                $source_name = trim($m[4]);
                $source_credits = floatval($m[5]);
                $source_grade = $m[6];
                $reason = trim($m[7]);
                
                $untransferable[] = [
                    'seq' => $seq,
                    'code' => $source_code,
                    'name' => $source_name,
                    'credits' => $source_credits,
                    'grade' => $source_grade,
                    'reason' => $reason
                ];
            }
        }
    }
    
    return [
        'transfers' => $results,
        'untransferable' => $untransferable
    ];
}

