<?php
error_reporting(E_ALL);
ini_set('display_errors', '1');

require_once __DIR__ . '/../../config/database.php';
require_once __DIR__ . '/../../config/app.php';

// Telegram notification support: memakai konfigurasi yang sama dengan modul chat/laporan katim.
if (file_exists(__DIR__ . '/../../config/telegram.php')) {
    require_once __DIR__ . '/../../config/telegram.php';
}
if (file_exists(__DIR__ . '/../../includes/telegram_helper.php')) {
    require_once __DIR__ . '/../../includes/telegram_helper.php';
}

checkAuth();
$conn = getConnection();
$conn->set_charset('utf8mb4');

if (!function_exists('e')) {
    function e($value): string { return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8'); }
}

function ps_url(array $params = []): string
{
    $base = basename($_SERVER['PHP_SELF']);
    return $base . '?' . http_build_query(array_merge(['module' => 'patient_safety'], $params));
}

function ps_user(): array
{
    if (function_exists('current_user')) {
        return current_user() ?: [];
    }
    return $_SESSION['user'] ?? [];
}

function ps_user_id(): int
{
    $u = ps_user();
    return (int)($u['employee_id'] ?? $u['user_id'] ?? $u['id'] ?? $_SESSION['user_id'] ?? 0);
}

function ps_user_name(): string
{
    $u = ps_user();
    return (string)($u['full_name'] ?? $u['nama'] ?? $u['name'] ?? $_SESSION['nama_lengkap'] ?? $_SESSION['username'] ?? 'User');
}

function ps_role(): string
{
    $u = ps_user();
    return strtolower((string)($u['role'] ?? $_SESSION['role'] ?? $_SESSION['user_role'] ?? 'viewer'));
}

function ps_can_manage(): bool
{
    return in_array(ps_role(), ['superadmin', 'admin', 'editor', 'kepala unit', 'kepala_unit', 'mutu', 'komite mutu'], true);
}

function ps_table_exists(mysqli $conn, string $table): bool
{
    $table = $conn->real_escape_string($table);
    $res = $conn->query("SHOW TABLES LIKE '{$table}'");
    return $res && $res->num_rows > 0;
}

function ps_column_exists(mysqli $conn, string $table, string $column): bool
{
    $table = $conn->real_escape_string($table);
    $column = $conn->real_escape_string($column);
    $res = $conn->query("SHOW COLUMNS FROM `{$table}` LIKE '{$column}'");
    return $res && $res->num_rows > 0;
}

function ps_add_column(mysqli $conn, string $column, string $definition): void
{
    if (!ps_column_exists($conn, 'patient_safety_incidents', $column)) {
        $conn->query("ALTER TABLE patient_safety_incidents ADD COLUMN {$column} {$definition}");
    }
}

function ps_setup(mysqli $conn): void
{
    $conn->query("CREATE TABLE IF NOT EXISTS patient_safety_incidents (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        incident_number VARCHAR(60) NOT NULL UNIQUE,
        report_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        incident_date DATETIME NOT NULL,
        unit_id BIGINT UNSIGNED NULL,
        location_detail VARCHAR(190) NULL,
        reporter_user_id BIGINT UNSIGNED NULL,
        reporter_name VARCHAR(190) NULL,
        reporter_unit VARCHAR(190) NULL,
        patient_name VARCHAR(190) NULL,
        patient_initial VARCHAR(50) NULL,
        medical_record_no VARCHAR(80) NULL,
        birth_date DATE NULL,
        age VARCHAR(50) NULL,
        gender ENUM('L','P','-') NOT NULL DEFAULT '-',
        service_type VARCHAR(80) NULL,
        dpjp VARCHAR(190) NULL,
        incident_category ENUM('KPCS','KNC','KTC','KTD','SENTINEL') NOT NULL DEFAULT 'KNC',
        incident_type VARCHAR(150) NOT NULL,
        incident_subtype VARCHAR(150) NULL,
        chronology LONGTEXT NOT NULL,
        immediate_action TEXT NULL,
        patient_impact TEXT NULL,
        family_disclosure ENUM('ya','tidak','tidak_perlu') NOT NULL DEFAULT 'tidak_perlu',
        witnesses VARCHAR(255) NULL,
        severity_score TINYINT UNSIGNED NOT NULL DEFAULT 1,
        probability_score TINYINT UNSIGNED NOT NULL DEFAULT 1,
        risk_score TINYINT UNSIGNED NOT NULL DEFAULT 1,
        risk_grade ENUM('BIRU','HIJAU','KUNING','MERAH') NOT NULL DEFAULT 'BIRU',
        investigation_type VARCHAR(80) NOT NULL DEFAULT 'Investigasi sederhana <= 7 hari',
        root_cause TEXT NULL,
        contributing_factors TEXT NULL,
        recommendation TEXT NULL,
        action_plan TEXT NULL,
        pic_name VARCHAR(190) NULL,
        target_date DATE NULL,
        follow_up TEXT NULL,
        effectiveness_monitoring TEXT NULL,
        external_report ENUM('tidak','ya') NOT NULL DEFAULT 'tidak',
        status ENUM('reported','verified','investigation','follow_up','closed') NOT NULL DEFAULT 'reported',
        verified_by VARCHAR(190) NULL,
        verified_at DATETIME NULL,
        closed_at DATETIME NULL,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        updated_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        KEY idx_ps_date (incident_date),
        KEY idx_ps_unit (unit_id),
        KEY idx_ps_category (incident_category),
        KEY idx_ps_grade (risk_grade),
        KEY idx_ps_status (status)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");

    $columns = [
        'report_date' => "DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER incident_number",
        'location_detail' => "VARCHAR(190) NULL AFTER unit_id",
        'reporter_name' => "VARCHAR(190) NULL AFTER reporter_user_id",
        'reporter_unit' => "VARCHAR(190) NULL AFTER reporter_name",
        'patient_initial' => "VARCHAR(50) NULL AFTER patient_name",
        'birth_date' => "DATE NULL AFTER medical_record_no",
        'age' => "VARCHAR(50) NULL AFTER birth_date",
        'gender' => "ENUM('L','P','-') NOT NULL DEFAULT '-' AFTER age",
        'service_type' => "VARCHAR(80) NULL AFTER gender",
        'dpjp' => "VARCHAR(190) NULL AFTER service_type",
        'incident_category' => "ENUM('KPCS','KNC','KTC','KTD','SENTINEL') NOT NULL DEFAULT 'KNC' AFTER dpjp",
        'incident_subtype' => "VARCHAR(150) NULL AFTER incident_type",
        'immediate_action' => "TEXT NULL AFTER chronology",
        'patient_impact' => "TEXT NULL AFTER immediate_action",
        'family_disclosure' => "ENUM('ya','tidak','tidak_perlu') NOT NULL DEFAULT 'tidak_perlu' AFTER patient_impact",
        'witnesses' => "VARCHAR(255) NULL AFTER family_disclosure",
        'severity_score' => "TINYINT UNSIGNED NOT NULL DEFAULT 1 AFTER witnesses",
        'probability_score' => "TINYINT UNSIGNED NOT NULL DEFAULT 1 AFTER severity_score",
        'risk_score' => "TINYINT UNSIGNED NOT NULL DEFAULT 1 AFTER probability_score",
        'risk_grade' => "ENUM('BIRU','HIJAU','KUNING','MERAH') NOT NULL DEFAULT 'BIRU' AFTER risk_score",
        'investigation_type' => "VARCHAR(80) NOT NULL DEFAULT 'Investigasi sederhana <= 7 hari' AFTER risk_grade",
        'root_cause' => "TEXT NULL AFTER investigation_type",
        'contributing_factors' => "TEXT NULL AFTER root_cause",
        'recommendation' => "TEXT NULL AFTER contributing_factors",
        'action_plan' => "TEXT NULL AFTER recommendation",
        'pic_name' => "VARCHAR(190) NULL AFTER action_plan",
        'target_date' => "DATE NULL AFTER pic_name",
        'follow_up' => "TEXT NULL AFTER target_date",
        'effectiveness_monitoring' => "TEXT NULL AFTER follow_up",
        'external_report' => "ENUM('tidak','ya') NOT NULL DEFAULT 'tidak' AFTER effectiveness_monitoring",
        'verified_by' => "VARCHAR(190) NULL AFTER status",
        'verified_at' => "DATETIME NULL AFTER verified_by",
    ];
    foreach ($columns as $col => $def) { ps_add_column($conn, $col, $def); }

    // Normalisasi tipe enum bila tabel lama sudah ada dari versi sebelumnya.
    $conn->query("ALTER TABLE patient_safety_incidents MODIFY status ENUM('reported','verified','investigation','follow_up','closed') NOT NULL DEFAULT 'reported'");
    $conn->query("ALTER TABLE patient_safety_incidents MODIFY incident_category ENUM('KPCS','KNC','KTC','KTD','SENTINEL') NOT NULL DEFAULT 'KNC'");
    $conn->query("ALTER TABLE patient_safety_incidents MODIFY risk_grade ENUM('BIRU','HIJAU','KUNING','MERAH') NOT NULL DEFAULT 'BIRU'");
    $conn->query("ALTER TABLE patient_safety_incidents MODIFY external_report ENUM('tidak','ya') NOT NULL DEFAULT 'tidak'");
    $conn->query("ALTER TABLE patient_safety_incidents MODIFY family_disclosure ENUM('ya','tidak','tidak_perlu') NOT NULL DEFAULT 'tidak_perlu'");

    if (ps_table_exists($conn, 'patient_incidents')) {
        $conn->query("INSERT IGNORE INTO patient_safety_incidents
            (incident_number, report_date, incident_date, unit_id, reporter_user_id, patient_name, medical_record_no, incident_category, incident_type, chronology, immediate_action, severity_score, probability_score, risk_score, risk_grade, investigation_type, follow_up, status, closed_at, created_at, updated_at)
            SELECT incident_number, COALESCE(created_at, NOW()), incident_date, unit_id, reporter_user_id, patient_name, medical_record_no,
                CASE WHEN severity='sentinel' THEN 'SENTINEL' WHEN severity='berat' THEN 'KTD' ELSE 'KNC' END,
                incident_type, chronology, immediate_action,
                CASE severity WHEN 'ringan' THEN 2 WHEN 'sedang' THEN 3 WHEN 'berat' THEN 4 WHEN 'sentinel' THEN 5 ELSE 1 END,
                2,
                CASE severity WHEN 'ringan' THEN 4 WHEN 'sedang' THEN 6 WHEN 'berat' THEN 8 WHEN 'sentinel' THEN 10 ELSE 2 END,
                CASE WHEN severity IN ('berat','sentinel') THEN 'MERAH' WHEN severity='sedang' THEN 'KUNING' ELSE 'HIJAU' END,
                CASE WHEN severity='sentinel' THEN 'RCA komprehensif <= 45 hari' WHEN severity='berat' THEN 'RCA / Simple RCA' ELSE 'Investigasi sederhana' END,
                follow_up_notes,
                CASE WHEN status IN ('selesai','ditutup') THEN 'closed' WHEN status='investigasi' THEN 'investigation' ELSE 'reported' END,
                closed_at, created_at, updated_at
            FROM patient_incidents");
    }
}

function ps_grade(int $severity, int $probability, string $category = ''): array
{
    $severity = max(1, min(5, $severity));
    $probability = max(1, min(5, $probability));
    if ($category === 'SENTINEL' || $severity >= 5) {
        return ['MERAH', $severity * $probability, 'RCA komprehensif <= 45 hari'];
    }
    $matrix = [
        1 => [1=>'BIRU', 2=>'BIRU', 3=>'HIJAU', 4=>'HIJAU', 5=>'KUNING'],
        2 => [1=>'BIRU', 2=>'HIJAU', 3=>'HIJAU', 4=>'KUNING', 5=>'KUNING'],
        3 => [1=>'HIJAU', 2=>'HIJAU', 3=>'KUNING', 4=>'KUNING', 5=>'MERAH'],
        4 => [1=>'HIJAU', 2=>'KUNING', 3=>'KUNING', 4=>'MERAH', 5=>'MERAH'],
        5 => [1=>'KUNING', 2=>'KUNING', 3=>'MERAH', 4=>'MERAH', 5=>'MERAH'],
    ];
    $grade = $matrix[$severity][$probability] ?? 'BIRU';
    $investigation = match ($grade) {
        'BIRU' => 'Investigasi sederhana <= 7 hari',
        'HIJAU' => 'Investigasi sederhana <= 14 hari',
        'KUNING' => 'RCA / Simple RCA',
        'MERAH' => 'RCA komprehensif <= 45 hari',
        default => 'Investigasi sederhana',
    };
    return [$grade, $severity * $probability, $investigation];
}

function ps_next_number(mysqli $conn): string
{
    $ym = date('Ym');
    $prefix = 'IKP/' . $ym . '/';
    $stmt = $conn->prepare("SELECT COUNT(*) c FROM patient_safety_incidents WHERE incident_number LIKE CONCAT(?, '%')");
    $stmt->bind_param('s', $prefix);
    $stmt->execute();
    $c = (int)($stmt->get_result()->fetch_assoc()['c'] ?? 0) + 1;
    return $prefix . str_pad((string)$c, 4, '0', STR_PAD_LEFT);
}

function ps_units(mysqli $conn): array
{
    $rows = [];
    $res = $conn->query("SELECT id, name FROM units WHERE is_active = 1 ORDER BY category, name");
    if ($res) { while ($r = $res->fetch_assoc()) { $rows[] = $r; } }
    return $rows;
}

function ps_get(mysqli $conn, int $id): ?array
{
    $stmt = $conn->prepare("SELECT p.*, u.name unit_name FROM patient_safety_incidents p LEFT JOIN units u ON p.unit_id = u.id WHERE p.id = ?");
    $stmt->bind_param('i', $id);
    $stmt->execute();
    $row = $stmt->get_result()->fetch_assoc();
    return $row ?: null;
}


function ps_unit_name(mysqli $conn, int $unitId): string
{
    if ($unitId <= 0) { return '-'; }
    $stmt = $conn->prepare("SELECT name FROM units WHERE id = ? LIMIT 1");
    if (!$stmt) { return '-'; }
    $stmt->bind_param('i', $unitId);
    $stmt->execute();
    $row = $stmt->get_result()->fetch_assoc();
    return (string)($row['name'] ?? '-');
}

function ps_short_text($value, int $limit = 900): string
{
    $text = trim((string)$value);
    if ($text === '') { return '-'; }
    if (function_exists('mb_strlen') && mb_strlen($text, 'UTF-8') > $limit) {
        return mb_substr($text, 0, $limit - 3, 'UTF-8') . '...';
    }
    if (!function_exists('mb_strlen') && strlen($text) > $limit) {
        return substr($text, 0, $limit - 3) . '...';
    }
    return $text;
}

function ps_tg_html($value): string
{
    if (function_exists('telegram_html')) {
        return telegram_html($value);
    }
    return htmlspecialchars((string)$value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

function ps_detail_url(int $id): string
{
    $base = defined('BASE_URL') ? rtrim((string)BASE_URL, '/') . '/' : '';
    return $base . 'index.php?module=patient_safety&action=view&id=' . $id;
}

function buildPatientSafetyTelegramMessage(array $data): string
{
    $tanggalInsiden = !empty($data['incident_date']) ? date('d/m/Y H:i', strtotime((string)$data['incident_date'])) : '-';
    $tglLapor = date('d/m/Y H:i');
    $patient = trim((string)($data['patient_name'] ?? ''));
    if ($patient === '') { $patient = trim((string)($data['patient_initial'] ?? '')); }
    if ($patient === '') { $patient = '-'; }

    $gradeEmoji = match ((string)($data['risk_grade'] ?? '')) {
        'MERAH' => '🔴',
        'KUNING' => '🟡',
        'HIJAU' => '🟢',
        'BIRU' => '🔵',
        default => '⚪',
    };

    $message  = "<b>🚨 LAPORAN IKP / PATIENT SAFETY BARU</b>\n";
    $message .= "<b>RS BaliMéd Denpasar</b>\n\n";
    $message .= "<b>No IKP</b> : " . ps_tg_html($data['incident_number'] ?? '-') . "\n";
    $message .= "<b>Tanggal Lapor</b> : " . ps_tg_html($tglLapor) . "\n";
    $message .= "<b>Tanggal Insiden</b> : " . ps_tg_html($tanggalInsiden) . "\n";
    $message .= "<b>Unit/Lokasi</b> : " . ps_tg_html($data['unit_name'] ?? '-') . " / " . ps_tg_html($data['location_detail'] ?? '-') . "\n";
    $message .= "<b>Pelapor</b> : " . ps_tg_html($data['reporter_name'] ?? '-') . "\n\n";

    $message .= "<b>A. Data Pasien</b>\n";
    $message .= "• Pasien/Inisial: <b>" . ps_tg_html($patient) . "</b>\n";
    $message .= "• No. RM: " . ps_tg_html($data['medical_record_no'] ?? '-') . "\n";
    $message .= "• Umur/JK: " . ps_tg_html($data['age'] ?? '-') . " / " . ps_tg_html($data['gender'] ?? '-') . "\n";
    $message .= "• Layanan: " . ps_tg_html($data['service_type'] ?? '-') . "\n";
    $message .= "• DPJP: " . ps_tg_html($data['dpjp'] ?? '-') . "\n\n";

    $message .= "<b>B. Klasifikasi & Grading</b>\n";
    $message .= "• Jenis IKP: <b>" . ps_tg_html($data['incident_category'] ?? '-') . "</b>\n";
    $message .= "• Tipe: " . ps_tg_html($data['incident_type'] ?? '-') . "\n";
    $message .= "• Subtipe: " . ps_tg_html($data['incident_subtype'] ?? '-') . "\n";
    $message .= "• Severity/Probability: " . (int)($data['severity_score'] ?? 0) . "/" . (int)($data['probability_score'] ?? 0) . "\n";
    $message .= "• Grade: <b>" . $gradeEmoji . " " . ps_tg_html($data['risk_grade'] ?? '-') . " / Skor " . (int)($data['risk_score'] ?? 0) . "</b>\n";
    $message .= "• Investigasi: " . ps_tg_html($data['investigation_type'] ?? '-') . "\n";
    $message .= "• Lapor eksternal: " . ps_tg_html($data['external_report'] ?? '-') . "\n\n";

    $message .= "<b>C. Kronologi Objektif</b>\n";
    $message .= ps_tg_html(ps_short_text($data['chronology'] ?? '-', 1100)) . "\n\n";

    $message .= "<b>D. Tindakan Segera</b>\n";
    $message .= ps_tg_html(ps_short_text($data['immediate_action'] ?? '-', 700)) . "\n\n";

    $impact = trim((string)($data['patient_impact'] ?? ''));
    if ($impact !== '') {
        $message .= "<b>E. Dampak pada Pasien/Saksi</b>\n";
        $message .= ps_tg_html(ps_short_text($impact, 600)) . "\n\n";
    }

    $message .= "<b>Link Detail</b> : " . ps_tg_html($data['detail_url'] ?? '-') . "\n\n";
    $message .= "<i>Dikirim otomatis dari Modul Patient Safety.</i>";
    return $message;
}

function ps_send_telegram_notification(array $data, ?string &$error = null): bool
{
    $error = null;
    if (!function_exists('telegram_is_configured') || !function_exists('sendTelegramMessage')) {
        $error = 'Fungsi Telegram belum tersedia. Cek config/telegram.php dan includes/telegram_helper.php';
        error_log('[Patient Safety Telegram] ' . $error);
        return false;
    }
    if (!telegram_is_configured()) {
        $error = 'Telegram belum dikonfigurasi atau status notif nonaktif.';
        error_log('[Patient Safety Telegram] ' . $error);
        return false;
    }
    $msg = buildPatientSafetyTelegramMessage($data);
    $ok = sendTelegramMessage($msg, $error);
    if (!$ok) {
        error_log('[Patient Safety Telegram] Gagal kirim IKP ' . (string)($data['incident_number'] ?? '-') . ' | Error: ' . (string)$error);
    }
    return $ok;
}



function ps_mapping_setup(mysqli $conn): void
{
    $conn->query("CREATE TABLE IF NOT EXISTS patient_safety_mapping_imports (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        file_name VARCHAR(255) NOT NULL,
        original_file_name VARCHAR(255) NULL,
        tahun VARCHAR(10) NULL,
        periode VARCHAR(80) NULL,
        total_rows INT UNSIGNED NOT NULL DEFAULT 0,
        total_mapped INT UNSIGNED NOT NULL DEFAULT 0,
        total_unmapped INT UNSIGNED NOT NULL DEFAULT 0,
        uploaded_by BIGINT UNSIGNED NULL,
        uploaded_by_name VARCHAR(190) NULL,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        KEY idx_psmi_tahun (tahun),
        KEY idx_psmi_created (created_at)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");

    $conn->query("CREATE TABLE IF NOT EXISTS patient_safety_mapping_rows (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
        import_id BIGINT UNSIGNED NOT NULL,
        no_urut INT UNSIGNED NULL,
        tanggal_insiden VARCHAR(80) NULL,
        waktu_insiden VARCHAR(80) NULL,
        unit_mapping VARCHAR(80) NULL,
        bagian_unit VARCHAR(120) NULL,
        area_lokasi VARCHAR(255) NULL,
        unit_penyebab VARCHAR(255) NULL,
        sasaran_kp VARCHAR(255) NULL,
        tipe_insiden VARCHAR(255) NULL,
        jenis_insiden VARCHAR(80) NULL,
        grading VARCHAR(80) NULL,
        insiden_terjadi LONGTEXT NULL,
        penyebab_insiden LONGTEXT NULL,
        kategori_risiko VARCHAR(190) NULL,
        penanganan LONGTEXT NULL,
        rekomendasi LONGTEXT NULL,
        pasien VARCHAR(190) NULL,
        no_rm VARCHAR(80) NULL,
        pelapor VARCHAR(190) NULL,
        penerima_laporan VARCHAR(190) NULL,
        status_mapping ENUM('mapped','unmapped','edited') NOT NULL DEFAULT 'mapped',
        raw_json LONGTEXT NULL,
        created_at TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
        KEY idx_psmr_import (import_id),
        KEY idx_psmr_unit (unit_mapping),
        KEY idx_psmr_grade (grading),
        CONSTRAINT fk_ps_mapping_import FOREIGN KEY (import_id) REFERENCES patient_safety_mapping_imports(id) ON DELETE CASCADE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
}

function ps_norm_key(string $s): string
{
    $s = strtolower(trim($s));
    $s = str_replace(['\xc2\xa0', "\n", "\r", "\t"], ' ', $s);
    $s = preg_replace('/[^a-z0-9]+/i', '', $s);
    return $s ?: '';
}

function ps_excel_date_to_text($value): string
{
    $value = trim((string)$value);
    if ($value === '') { return ''; }
    if (is_numeric($value)) {
        $n = (float)$value;
        if ($n > 25000 && $n < 80000) {
            $ts = (int)(($n - 25569) * 86400);
            return gmdate('d/m/Y', $ts);
        }
        if ($n > 0 && $n < 1) {
            $seconds = (int)round($n * 86400);
            return gmdate('H:i', $seconds);
        }
    }
    return $value;
}

function ps_xlsx_col_index(string $cellRef): int
{
    preg_match('/^([A-Z]+)/i', $cellRef, $m);
    $letters = strtoupper($m[1] ?? 'A');
    $num = 0;
    for ($i = 0; $i < strlen($letters); $i++) { $num = $num * 26 + (ord($letters[$i]) - 64); }
    return max(1, $num);
}

function ps_read_xlsx_simple(string $path): array
{
    $sheets = ps_read_xlsx_sheets_simple($path);
    foreach ($sheets as $sheet) {
        if (!empty($sheet['rows'])) { return $sheet['rows']; }
    }
    return [];
}

function ps_read_xlsx_sheets_simple(string $path): array
{
    if (!class_exists('ZipArchive')) { throw new RuntimeException('ZipArchive belum aktif di server. Aktifkan extension zip PHP.'); }
    $zip = new ZipArchive();
    if ($zip->open($path) !== true) { throw new RuntimeException('File Excel tidak bisa dibuka. Pastikan format .xlsx.'); }

    $shared = [];
    $sharedXml = $zip->getFromName('xl/sharedStrings.xml');
    if ($sharedXml !== false) {
        $sx = @simplexml_load_string($sharedXml);
        if ($sx) {
            foreach ($sx->si as $si) {
                $parts = [];
                if (isset($si->t)) { $parts[] = (string)$si->t; }
                if (isset($si->r)) { foreach ($si->r as $r) { $parts[] = (string)$r->t; } }
                $shared[] = implode('', $parts);
            }
        }
    }

    $sheetTargets = [];
    $workbookXml = $zip->getFromName('xl/workbook.xml');
    $relsXml = $zip->getFromName('xl/_rels/workbook.xml.rels');
    if ($workbookXml !== false && $relsXml !== false) {
        $wb = @simplexml_load_string($workbookXml);
        $rels = @simplexml_load_string($relsXml);
        $ridTarget = [];
        if ($rels) {
            foreach ($rels->Relationship as $rel) {
                $ra = $rel->attributes();
                $ridTarget[(string)$ra['Id']] = 'xl/' . ltrim((string)$ra['Target'], '/');
            }
        }
        if ($wb && isset($wb->sheets->sheet)) {
            foreach ($wb->sheets->sheet as $sheetNode) {
                $attrs = $sheetNode->attributes();
                $name = (string)($attrs['name'] ?? 'Sheet');
                $rattrs = $sheetNode->attributes('http://schemas.openxmlformats.org/officeDocument/2006/relationships');
                $rid = (string)($rattrs['id'] ?? '');
                if ($rid !== '' && isset($ridTarget[$rid])) {
                    $sheetTargets[] = ['name' => $name, 'path' => $ridTarget[$rid]];
                }
            }
        }
    }
    if (!$sheetTargets) { $sheetTargets[] = ['name' => 'Sheet1', 'path' => 'xl/worksheets/sheet1.xml']; }

    $all = [];
    foreach ($sheetTargets as $info) {
        $xml = $zip->getFromName($info['path']);
        if ($xml === false) { continue; }
        $sheet = @simplexml_load_string($xml);
        if (!$sheet || !isset($sheet->sheetData->row)) { continue; }
        $rows = [];
        foreach ($sheet->sheetData->row as $row) {
            $out = [];
            foreach ($row->c as $c) {
                $a = $c->attributes();
                $ref = (string)($a['r'] ?? 'A1');
                $idx = ps_xlsx_col_index($ref) - 1;
                $type = (string)($a['t'] ?? '');
                $val = '';
                if ($type === 's') {
                    $si = (int)((string)$c->v);
                    $val = $shared[$si] ?? '';
                } elseif ($type === 'inlineStr') {
                    $parts = [];
                    if (isset($c->is->t)) { $parts[] = (string)$c->is->t; }
                    if (isset($c->is->r)) { foreach ($c->is->r as $r) { $parts[] = (string)$r->t; } }
                    $val = implode('', $parts);
                } else {
                    // Rumus Google/Excel biasanya tetap memiliki cached value di node <v>.
                    $val = (string)($c->v ?? '');
                }
                $out[$idx] = trim($val);
            }
            if ($out) {
                $max = max(array_keys($out));
                $full = [];
                for ($i=0; $i <= $max; $i++) { $full[] = $out[$i] ?? ''; }
                $rows[] = $full;
            }
        }
        $all[] = ['name' => $info['name'], 'rows' => $rows];
    }
    $zip->close();
    return $all;
}

function ps_header_alias_hit(string $cellNorm, array $aliases): bool
{
    if ($cellNorm === '') { return false; }
    foreach ($aliases as $alias) {
        $a = ps_norm_key($alias);
        if ($a === '') { continue; }
        if ($cellNorm === $a || str_contains($cellNorm, $a) || str_contains($a, $cellNorm)) { return true; }
    }
    return false;
}

function ps_find_header_row(array $rows): array
{
    $aliases = [
        'timestamp','nama pasien','nomor rm','no rm','tanggal insiden','waktu insiden',
        'grading risiko','grading resiko','insiden grading risiko','insiden grading resiko',
        'kronologis insiden','kronologi insiden','jenis insiden',
        'insiden berdasarkan 6 sasaran','tempat insiden lokasi kejadian','tempat insiden','lokasi kejadian',
        'unit departemen terkait yang menyebabkan insiden','unit departemen penyebab','unit penyebab',
        'akibat insiden','tindakan yang dilakukan segera','tindakan segera',
        'type insiden','tipe insiden','penyebab langsung insiden akar masalah','penyebab akar masalah',
        'rekomendasi','pembuat laporan','penerima laporan','investigasi sederhana'
    ];
    foreach ($rows as $i => $row) {
        $score = 0;
        foreach ($row as $cell) {
            if (ps_header_alias_hit(ps_norm_key((string)$cell), $aliases)) { $score++; }
        }
        $normCells = array_map(fn($c) => ps_norm_key((string)$c), $row);
        $hasIdentity = false; $hasTanggal = false; $hasKronologi = false; $hasJenis = false;
        foreach ($normCells as $nc) {
            if (ps_header_alias_hit($nc, ['timestamp','nama pasien','nomor rm','no rm'])) { $hasIdentity = true; }
            if (ps_header_alias_hit($nc, ['tanggal insiden'])) { $hasTanggal = true; }
            if (ps_header_alias_hit($nc, ['kronologis insiden','kronologi insiden'])) { $hasKronologi = true; }
            if (ps_header_alias_hit($nc, ['jenis insiden'])) { $hasJenis = true; }
        }
        if ($score >= 4 && $hasIdentity && $hasTanggal && $hasKronologi && $hasJenis) {
            $headers = [];
            foreach ($row as $k => $cell) { $headers[$k] = ps_norm_key((string)$cell); }
            return [$i, $headers];
        }
    }
    throw new RuntimeException('Header Excel rekap tidak ditemukan. Pastikan file memiliki sheet bulanan/rekap dengan kolom Timestamp, Nama Pasien, Tanggal Insiden, Kronologis Insiden, Jenis Insiden, Lokasi, Type Insiden, Rekomendasi.');
}

function ps_assoc_value(array $assoc, array $keys): string
{
    foreach ($keys as $key) {
        $nk = ps_norm_key($key);
        if (isset($assoc[$nk]) && trim((string)$assoc[$nk]) !== '') { return trim((string)$assoc[$nk]); }
    }
    foreach ($assoc as $k => $v) {
        foreach ($keys as $key) {
            $nk = ps_norm_key($key);
            if ($nk !== '' && (str_contains($k, $nk) || str_contains($nk, $k)) && trim((string)$v) !== '') { return trim((string)$v); }
        }
    }
    return '';
}

function ps_mapping_master_lists(): array
{
    return [
        'tipe' => [
            'Administrasi Klinik','Proses/ Prosedur Klinis','Dokumentasi',
            'Infeksi Nosokomial (Hospital Associated Infection)','Medikasi/ Cairan Infus',
            'Transfusi Darah/ Produk Darah','Nutrisi','Oksigen/ Gas',
            'Alat Medis/ Alat Kesehatan/ Equipment Property','Pasien','Jatuh','Kecelakaan',
            'Infrakstruktur/ Bangunan/ Benda Lain yang Terpasang Tetap',
            'Resource/ Manajemen Organisasi','Laboratorium/ Patologi'
        ],
        'kategori_risiko' => [
            'Risiko Keuangan','Risiko Kebijakan','Risiko Kepatuhan','Risiko Legal',
            'Risiko Fraud','Risiko Reputasi','Risiko Operasional-Klinis'
        ],
        'bagian_unit' => ['Unit Medis','Unit Penunjang','Unit Umum'],
        'unit_medis' => [
            'Unit Canigara','Unit Cempaka','Unit Cendana','Unit ICU','Unit Ruang Bayi dan NICU',
            'Unit IGD','Unit Ruang Bersalin','Unit Ruang Operasi','Unit Poliklinik','Unit Hemodialisa'
        ],
        'unit_penunjang' => [
            'Unit Radiologi','Unit Farmasi','Unit Laboratorium','Unit Gizi','Unit Rekam Medis'
        ],
        'unit_umum' => [
            'Unit House Keeping','Unit Laundry','Unit Sterilisasi Dan Binatu/ CSSD','Unit Admisi',
            'Unit Pengadaan Bahan Mentah','Unit Gudang Logistik','Unit Keamanan dan Transportasi',
            'Unit Driver','Unit Guest Relation Officer/ GRO','Unit IPSRS',
            'Unit Kesehatan Lingkungan (Kesling)','Unit Kasir','Unit Pemeliharaan Gedung'
        ],
        'sasaran' => [
            'Identifikasi Pasien','Risiko Jatuh','Keamanan Obat','Risiko Infeksi',
            'Tepat Lokasi dan Tepat prosedur','Komunikasi Efektif'
        ],
        'unit_tabs' => ['IGD','HD','POLI','FARMASI','RADIOLOGI','ADMISI','CANIGARA','CEMPAKA','R BAYI','CENDANA','FB','OK','ICU','BELUM TERMAPPING']
    ];
}

function ps_map_unit_name(string $lokasi, string $unitPenyebab): array
{
    // Mapping utama mengikuti kolom Area/Lokasi di file "MAPING INSIDEN TIAP UNIT".
    // Unit penyebab dipakai sebagai fallback bila lokasi kosong/tidak terbaca.
    $sources = [$lokasi, $unitPenyebab, trim($lokasi . ' ' . $unitPenyebab)];
    $rules = [
        'IGD' => ['igd','ugd','emergency','gawatdarurat','gawat darurat'],
        'HD' => ['hemodialisa','hemodialisis',' unit hd ',' ruang hd ',' hd '],
        'POLI' => ['poliklinik',' poli ','rawat jalan','rajal'],
        'FARMASI' => ['farmasi','apotek'],
        'RADIOLOGI' => ['radiologi','rontgen','xray','x ray','ct scan','ctscan'],
        'ADMISI' => ['admisi','admission','pendaftaran'],
        'CANIGARA' => ['canigara'],
        'CEMPAKA' => ['cempaka'],
        'R BAYI' => ['ruang bayi','r bayi','rbayi','perinatologi','nicu','bayi dan nicu'],
        'CENDANA' => ['cendana'],
        'FB' => ['f&b',' f b ','fb','gizi','food','nutrition','dapur','boga'],
        'OK' => ['kamar operasi','ruang operasi',' ruang ok ',' unit ok ',' ok ','bedah','operasi'],
        'ICU' => [' icu ','intensive','intensif'],
    ];
    foreach ($sources as $src) {
        $s = ' ' . strtolower((string)$src) . ' ';
        if (trim($s) === '') { continue; }
        foreach ($rules as $unit => $terms) {
            foreach ($terms as $term) {
                if (str_contains($s, strtolower($term))) { return [$unit, 'mapped']; }
            }
        }
    }
    return ['BELUM TERMAPPING', 'unmapped'];
}

function ps_unit_group_from_mapping(string $unitMap, string $lokasi = '', string $unitPenyebab = ''): string
{
    $unit = strtoupper(trim($unitMap));
    if (in_array($unit, ['IGD','HD','POLI','CANIGARA','CEMPAKA','R BAYI','CENDANA','OK','ICU'], true)) {
        return 'Unit Medis';
    }
    if (in_array($unit, ['FARMASI','RADIOLOGI','FB'], true)) {
        return 'Unit Penunjang';
    }
    if (in_array($unit, ['ADMISI'], true)) {
        return 'Unit Umum';
    }

    $txt = strtolower($lokasi . ' ' . $unitPenyebab);
    foreach (['radiologi','farmasi','laboratorium','gizi','rekam medis'] as $term) {
        if (str_contains($txt, $term)) { return 'Unit Penunjang'; }
    }
    foreach (['admisi','kasir','laundry','security','keamanan','driver','ipsrs','house keeping','cssd','gudang','logistik','gro','gedung'] as $term) {
        if (str_contains($txt, $term)) { return 'Unit Umum'; }
    }
    return 'Unit Medis';
}

function ps_risk_category(string $grading, string $jenis, string $tipe, string $kronologi = '', string $penyebab = '', string $rekomendasi = ''): string
{
    $txt = strtolower($grading . ' ' . $jenis . ' ' . $tipe . ' ' . $kronologi . ' ' . $penyebab . ' ' . $rekomendasi);
    if (str_contains($txt, 'fraud') || str_contains($txt, 'kecurangan')) { return 'Risiko Fraud'; }
    if (str_contains($txt, 'legal') || str_contains($txt, 'hukum') || str_contains($txt, 'tuntutan')) { return 'Risiko Legal'; }
    if (str_contains($txt, 'reputasi') || str_contains($txt, 'komplain') || str_contains($txt, 'keluhan')) { return 'Risiko Reputasi'; }
    if (str_contains($txt, 'biaya') || str_contains($txt, 'cash') || str_contains($txt, 'kasir') || str_contains($txt, 'klaim') || str_contains($txt, 'tagihan')) { return 'Risiko Keuangan'; }
    if (str_contains($txt, 'kebijakan') || str_contains($txt, 'regulasi')) { return 'Risiko Kebijakan'; }
    if (str_contains($txt, 'spo') || str_contains($txt, 'sop') || str_contains($txt, 'kepatuhan') || str_contains($txt, 'tidak melakukan') || str_contains($txt, 'tidak mengecek') || str_contains($txt, 'lupa')) { return 'Risiko Kepatuhan'; }
    return 'Risiko Operasional-Klinis';
}

function ps_parse_mapping_rows(string $xlsxPath, string $tahunFilter = ''): array
{
    $sheets = ps_read_xlsx_sheets_simple($xlsxPath);
    $data = [];
    $foundSheets = 0;
    $skipLabels = ['tipeinsiden','subtipeinsiden','pelaporaninsiden','investigasisederhana','rinciansubtipeinsiden'];

    foreach ($sheets as $sheetInfo) {
        $sheetName = (string)($sheetInfo['name'] ?? '');
        $rows = $sheetInfo['rows'] ?? [];
        if (!$rows) { continue; }

        $sheetNameLower = strtolower($sheetName);
        if (str_contains($sheetNameLower, 'database') || str_contains($sheetNameLower, 'kamus') || str_contains($sheetNameLower, 'cara menarik') || str_contains($sheetNameLower, 'rekap') || str_contains($sheetNameLower, 'name manager')) {
            continue;
        }

        if ($tahunFilter !== '' && preg_match('/20\d{2}/', $sheetName) && !str_contains($sheetName, $tahunFilter)) {
            continue;
        }

        try {
            [$headerIndex, $headers] = ps_find_header_row($rows);
        } catch (Throwable $e) {
            continue;
        }

        $foundSheets++;
        for ($i = $headerIndex + 1; $i < count($rows); $i++) {
            $row = $rows[$i];
            $assoc = [];
            $nonEmpty = 0;
            foreach ($headers as $idx => $key) {
                $val = trim((string)($row[$idx] ?? ''));
                if ($key !== '') { $assoc[$key] = $val; }
                if ($val !== '') { $nonEmpty++; }
            }
            if ($nonEmpty < 3) { continue; }
            $first = ps_norm_key((string)($row[0] ?? ''));
            if (in_array($first, $skipLabels, true)) { continue; }

            $tgl = ps_excel_date_to_text(ps_assoc_value($assoc, ['Tanggal Insiden']));
            $waktu = ps_excel_date_to_text(ps_assoc_value($assoc, ['Waktu Insiden']));
            $lokasi = ps_assoc_value($assoc, ['Tempat Insiden / Lokasi Kejadian', 'Tempat Insiden/ Lokasi Kejadian', 'Tempat Insiden', 'Lokasi Kejadian']);
            $unitPenyebab = ps_assoc_value($assoc, ['Unit/Departemen Terkait yang Menyebabkan Insiden', 'Unit/ Departemen Terkait Yang Menyebabkan Insiden', 'Unit/Departemen Penyebab', 'Unit Penyebab']);
            [$unitMap, $statusMap] = ps_map_unit_name($lokasi, $unitPenyebab);

            $jenis = ps_assoc_value($assoc, ['Jenis Insiden']);
            $tipe = ps_assoc_value($assoc, ['Type Insiden', 'Tipe Insiden']);
            if (in_array(ps_norm_key($tipe), ['tipeinsiden','typeinsiden'], true)) {
                $tipe = trim((string)($row[25] ?? ''));
            }
            $grading = ps_assoc_value($assoc, ['Grading Risiko', 'Grading Resiko', 'Insiden Grading Risiko', 'Insiden Grading Resiko']);
            $kronologi = ps_assoc_value($assoc, ['Kronologis Insiden', 'Kronologi Insiden']);

            if ($kronologi === '' && $jenis === '' && $lokasi === '') { continue; }
            if (in_array(ps_norm_key($tgl), ['jumlah','total'], true)) { continue; }

            $penyebab = ps_assoc_value($assoc, ['Penyebab / Akar Masalah', 'Penyebab Langsung Insiden/Akar Masalah', 'Penyebab Langsung', 'Akar Masalah']);
            $tindakan = ps_assoc_value($assoc, ['Tindakan Segera', 'Tindakan Yang Dilakukan Segera Setelah Kejadian Dan Hasilnya']);
            $rekom = ps_assoc_value($assoc, ['Rekomendasi']);
            $penanganan = trim($tindakan . ($rekom !== '' ? "\nRekomendasi: " . $rekom : ''));
            $kategoriRisiko = ps_assoc_value($assoc, ['Kategori Risiko Insiden', 'Kategori Risiko']);
            if ($kategoriRisiko === '') {
                $kategoriRisiko = ps_risk_category($grading, $jenis, $tipe, $kronologi, $penyebab, $rekom);
            }
            $bagianUnit = ps_unit_group_from_mapping($unitMap, $lokasi, $unitPenyebab);

            $raw = $assoc;
            $raw['_sheet_name'] = $sheetName;
            $raw['_unit_mapping'] = $unitMap;
            $raw['_bagian_unit'] = $bagianUnit;
            $raw['_kategori_risiko'] = $kategoriRisiko;

            $data[] = [
                'tanggal_insiden' => $tgl,
                'waktu_insiden' => $waktu,
                'unit_mapping' => $unitMap,
                'bagian_unit' => $bagianUnit,
                'area_lokasi' => $lokasi,
                'unit_penyebab' => $unitPenyebab,
                'sasaran_kp' => ps_assoc_value($assoc, ['Insiden Berdasarkan 6 Sasaran', '6 Sasaran']),
                'tipe_insiden' => $tipe,
                'jenis_insiden' => $jenis,
                'grading' => $grading,
                'insiden_terjadi' => $kronologi,
                'penyebab_insiden' => $penyebab,
                'kategori_risiko' => $kategoriRisiko,
                'penanganan' => $penanganan,
                'rekomendasi' => $rekom,
                'pasien' => ps_assoc_value($assoc, ['Nama Pasien']),
                'no_rm' => ps_assoc_value($assoc, ['Nomor RM', 'No RM']),
                'pelapor' => ps_assoc_value($assoc, ['Pembuat Laporan', 'Orang Pertama Yang Melaporkan Insiden']),
                'penerima_laporan' => ps_assoc_value($assoc, ['Penerima Laporan']),
                'status_mapping' => $statusMap,
                'raw_json' => json_encode($raw, JSON_UNESCAPED_UNICODE),
            ];
        }
    }

    if ($foundSheets === 0) {
        throw new RuntimeException('Header Excel rekap tidak ditemukan pada semua sheet. File contoh memiliki header di sheet bulanan seperti JANUARI 2025, FEBRUARI 2025, dst.');
    }
    if (!$data) {
        throw new RuntimeException('Header ditemukan, tetapi tidak ada baris laporan yang bisa diproses. Cek tahun/periode yang dipilih atau isi data pada sheet bulanan.');
    }

    return $data;
}

function ps_save_mapping_import(mysqli $conn, string $storedName, string $originalName, string $tahun, string $periode, array $rows): int
{
    $mapped = 0; $unmapped = 0;
    foreach ($rows as $r) { if (($r['status_mapping'] ?? '') === 'unmapped') { $unmapped++; } else { $mapped++; } }
    $uid = ps_user_id(); $uname = ps_user_name(); $total = count($rows);
    $stmt = $conn->prepare("INSERT INTO patient_safety_mapping_imports (file_name, original_file_name, tahun, periode, total_rows, total_mapped, total_unmapped, uploaded_by, uploaded_by_name) VALUES (?,?,?,?,?,?,?,?,?)");
    $stmt->bind_param('ssssiiiis', $storedName, $originalName, $tahun, $periode, $total, $mapped, $unmapped, $uid, $uname);
    if (!$stmt->execute()) { throw new RuntimeException('Gagal menyimpan riwayat import: ' . $stmt->error); }
    $importId = (int)$conn->insert_id;

    $stmtRow = $conn->prepare("INSERT INTO patient_safety_mapping_rows
        (import_id, no_urut, tanggal_insiden, waktu_insiden, unit_mapping, bagian_unit, area_lokasi, unit_penyebab, sasaran_kp, tipe_insiden, jenis_insiden, grading, insiden_terjadi, penyebab_insiden, kategori_risiko, penanganan, rekomendasi, pasien, no_rm, pelapor, penerima_laporan, status_mapping, raw_json)
        VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
    foreach ($rows as $i => $r) {
        $no = $i + 1;
        $stmtRow->bind_param('iisssssssssssssssssssss', $importId, $no, $r['tanggal_insiden'], $r['waktu_insiden'], $r['unit_mapping'], $r['bagian_unit'], $r['area_lokasi'], $r['unit_penyebab'], $r['sasaran_kp'], $r['tipe_insiden'], $r['jenis_insiden'], $r['grading'], $r['insiden_terjadi'], $r['penyebab_insiden'], $r['kategori_risiko'], $r['penanganan'], $r['rekomendasi'], $r['pasien'], $r['no_rm'], $r['pelapor'], $r['penerima_laporan'], $r['status_mapping'], $r['raw_json']);
        if (!$stmtRow->execute()) { throw new RuntimeException('Gagal menyimpan baris mapping no ' . $no . ': ' . $stmtRow->error); }
    }
    return $importId;
}

function ps_mapping_imports(mysqli $conn): array
{
    $rows = [];
    $res = $conn->query("SELECT * FROM patient_safety_mapping_imports ORDER BY created_at DESC, id DESC LIMIT 20");
    if ($res) { while ($r = $res->fetch_assoc()) { $rows[] = $r; } }
    return $rows;
}

function ps_mapping_rows(mysqli $conn, int $importId): array
{
    if ($importId <= 0) { return []; }
    $stmt = $conn->prepare("SELECT * FROM patient_safety_mapping_rows WHERE import_id=? ORDER BY FIELD(unit_mapping,'IGD','HD','POLI','FARMASI','RADIOLOGI','ADMISI','CANIGARA','CEMPAKA','R BAYI','CENDANA','FB','OK','ICU','BELUM TERMAPPING'), no_urut ASC");
    $stmt->bind_param('i', $importId);
    $stmt->execute();
    return $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
}

function ps_latest_import_id(mysqli $conn): int
{
    $res = $conn->query("SELECT id FROM patient_safety_mapping_imports ORDER BY created_at DESC, id DESC LIMIT 1");
    $row = $res ? $res->fetch_assoc() : null;
    return (int)($row['id'] ?? 0);
}

function ps_mapping_summary(array $rows): array
{
    $summary = ['unit'=>[], 'grade'=>[], 'jenis'=>[], 'sasaran'=>[], 'bagian'=>[], 'tipe'=>[], 'kategori_risiko'=>[]];
    foreach ($rows as $r) {
        foreach ([['unit','unit_mapping'], ['grade','grading'], ['jenis','jenis_insiden'], ['sasaran','sasaran_kp'], ['bagian','bagian_unit'], ['tipe','tipe_insiden'], ['kategori_risiko','kategori_risiko']] as [$bucket, $key]) {
            $v = trim((string)($r[$key] ?? '')) ?: '-';
            $summary[$bucket][$v] = ($summary[$bucket][$v] ?? 0) + 1;
        }
    }
    foreach ($summary as $k => $v) { arsort($summary[$k]); }
    return $summary;
}

function ps_count_rows_by(array $rows, string $key, string $value): int
{
    $n = 0;
    foreach ($rows as $r) {
        if (trim((string)($r[$key] ?? '')) === $value) { $n++; }
    }
    return $n;
}

function ps_mapping_headers(): array
{
    return [
        'NO',
        'TANGGAL INSIDEN TERJADI',
        'BAGIAN UNIT ',
        'AREA/ LOKASI',
        'SASARAN KESELAMATAN PASIEN',
        'TIPE INSIDEN',
        'INSIDEN YANG TERJADI',
        'PENYEBAB INSIDEN',
        'KATEGORI RISIKO INSIDEN',
        'PENANGANAN YANG TELAH DILAKUKAN '
    ];
}

function ps_mapping_row_for_export(array $r, int $no): array
{
    return [
        $no,
        $r['tanggal_insiden'] ?? '',
        $r['bagian_unit'] ?? '',
        $r['area_lokasi'] ?? '',
        $r['sasaran_kp'] ?? '',
        $r['tipe_insiden'] ?? '',
        $r['insiden_terjadi'] ?? '',
        $r['penyebab_insiden'] ?? '',
        $r['kategori_risiko'] ?? '',
        $r['penanganan'] ?? ''
    ];
}

function ps_export_title_rows(string $title, string $subtitle, array $headers, array $rows): array
{
    $out = [];
    $out[] = array_fill(0, 10, '');
    $out[] = ['', '', $title, '', '', '', '', '', '', ''];
    $out[] = array_fill(0, 10, '');
    $out[] = ['', '', $subtitle, '', '', '', '', '', '', ''];
    for ($i=0; $i<5; $i++) { $out[] = array_fill(0, 10, ''); }
    $out[] = $headers;
    foreach ($rows as $r) { $out[] = $r; }
    return $out;
}

function ps_build_name_manager_sheet(): array
{
    $m = ps_mapping_master_lists();
    $headers = ['TIPE INSIDEN','KATEGORI RISIKO INSIDEN','BAGIAN UNIT','UNIT MEDIS ','UNIT PENUNJANG','UNIT UMUM','SASARAN INSIDEN'];
    $max = max(count($m['tipe']), count($m['kategori_risiko']), count($m['bagian_unit']), count($m['unit_medis']), count($m['unit_penunjang']), count($m['unit_umum']), count($m['sasaran']));
    $data = [$headers];
    for ($i=0; $i<$max; $i++) {
        $data[] = [
            $m['tipe'][$i] ?? '',
            $m['kategori_risiko'][$i] ?? '',
            $m['bagian_unit'][$i] ?? '',
            isset($m['unit_medis'][$i]) ? ' ' . $m['unit_medis'][$i] : '',
            isset($m['unit_penunjang'][$i]) ? ' ' . $m['unit_penunjang'][$i] : '',
            isset($m['unit_umum'][$i]) ? ' ' . $m['unit_umum'][$i] : '',
            $m['sasaran'][$i] ?? ''
        ];
    }
    return $data;
}

function ps_build_rekapan_sheet(array $rows, string $tahun = ''): array
{
    $m = ps_mapping_master_lists();
    $tahunText = $tahun !== '' ? 'TAHUN ' . $tahun : 'TAHUN ' . date('Y');
    $out = [];
    $out[] = array_fill(0, 5, '');
    $out[] = ['', '', 'DAFTAR INSIDEN KESELAMATAN PASIEN UNIT:…...', '', ''];
    $out[] = array_fill(0, 5, '');
    $out[] = ['', '', $tahunText, '', ''];
    for ($i=0; $i<5; $i++) { $out[] = array_fill(0, 5, ''); }

    $sections = [
        ['BAGIAN UNIT', $m['bagian_unit'], 'bagian_unit'],
        ['SASARAN KESELAMATAN PASIEN', $m['sasaran'], 'sasaran_kp'],
        ['TIPE INSIDEN', $m['tipe'], 'tipe_insiden'],
        ['KATEGORI RISIKO', $m['kategori_risiko'], 'kategori_risiko']
    ];
    foreach ($sections as $sec) {
        [$judul, $items, $key] = $sec;
        $out[] = ['', '', 'PERSENTASE INSIDEN KESELAMATAN PASIEN BERDASARKAN ' . $judul . ' ' . $tahunText, '', ''];
        $out[] = ['', '', $judul, 'JUMLAH', 'PERSENTASE'];
        $total = 0; $counts = [];
        foreach ($items as $it) {
            $counts[$it] = ps_count_rows_by($rows, $key, $it);
            $total += $counts[$it];
        }
        $denom = max(1, $total);
        foreach ($items as $it) {
            $pct = $total > 0 ? round(($counts[$it] / $denom) * 100, 2) . '%' : '0%';
            $out[] = ['', '', $it, $counts[$it], $pct];
        }
        $out[] = ['', '', 'TOTAL', $total, $total > 0 ? '100%' : '0%'];
        for ($i=0; $i<15; $i++) { $out[] = array_fill(0, 5, ''); }
    }
    return $out;
}

function ps_build_mapping_export_sheets(array $rows, string $tahun = ''): array
{
    $headers = ps_mapping_headers();
    $sheets = [];
    $sheets['NAME MANAGER'] = ps_build_name_manager_sheet();
    $sheets['REKAPAN'] = ps_build_rekapan_sheet($rows, $tahun);

    $allRows = [];
    foreach ($rows as $i => $r) { $allRows[] = ps_mapping_row_for_export($r, $i + 1); }
    $sheets['DAFTAR INSIDEN'] = ps_export_title_rows('DAFTAR INSIDEN KESELAMATAN PASIEN', 'OLEH:…...... (ISI NAMA UNIT)', $headers, $allRows);

    foreach (ps_mapping_master_lists()['unit_tabs'] as $unit) {
        $unitRows = []; $no = 1;
        foreach ($rows as $r) {
            if (($r['unit_mapping'] ?? '') === $unit) {
                $unitRows[] = ps_mapping_row_for_export($r, $no++);
            }
        }
        $subtitleMap = [
            'POLI' => 'OLEH: POLIKLINIK',
            'R BAYI' => 'OLEH: RUANG BAYI',
            'OK' => 'OLEH: OK ',
            'BELUM TERMAPPING' => 'OLEH: BELUM TERMAPPING'
        ];
        $subtitle = $subtitleMap[$unit] ?? ('OLEH:' . $unit);
        $sheets[$unit] = ps_export_title_rows('DAFTAR INSIDEN KESELAMATAN PASIEN ' . ($tahun ?: date('Y')), $subtitle, $headers, $unitRows);
    }
    return $sheets;
}

function ps_xlsx_col(int $n): string
{
    $s = '';
    while ($n > 0) {
        $n--;
        $s = chr(65 + ($n % 26)) . $s;
        $n = intdiv($n, 26);
    }
    return $s;
}

function ps_xml($s): string { return htmlspecialchars((string)$s, ENT_QUOTES | ENT_XML1, 'UTF-8'); }

function ps_xlsx_sheet_xml(array $data): string
{
    $xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>';
    foreach ($data as $rIdx => $row) {
        $rn = $rIdx + 1; $xml .= '<row r="' . $rn . '">';
        foreach (array_values($row) as $cIdx => $val) {
            $ref = ps_xlsx_col($cIdx + 1) . $rn;
            $style = $rIdx === 0 ? ' s="1"' : '';
            $xml .= '<c r="' . $ref . '" t="inlineStr"' . $style . '><is><t>' . ps_xml($val) . '</t></is></c>';
        }
        $xml .= '</row>';
    }
    $xml .= '</sheetData></worksheet>';
    return $xml;
}

function ps_make_xlsx(array $sheets, string $filepath): void
{
    if (!class_exists('ZipArchive')) { throw new RuntimeException('ZipArchive belum aktif, export Excel tidak bisa dibuat.'); }
    $zip = new ZipArchive();
    if ($zip->open($filepath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) { throw new RuntimeException('File export tidak bisa dibuat.'); }
    $count = count($sheets);
    $zip->addFromString('[Content_Types].xml', '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>' . implode('', array_map(fn($i)=>'<Override PartName="/xl/worksheets/sheet'.$i.'.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>', range(1,$count))) . '</Types>');
    $zip->addFromString('_rels/.rels', '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>');
    $sheetXml = ''; $relsXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">';
    $i=1;
    foreach ($sheets as $name => $data) {
        $safeName = substr(str_replace([':', '\\', '/', '?', '*', '[', ']'], ' ', (string)$name), 0, 31);
        $sheetXml .= '<sheet name="' . ps_xml($safeName) . '" sheetId="' . $i . '" r:id="rId' . $i . '"/>';
        $relsXml .= '<Relationship Id="rId' . $i . '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet' . $i . '.xml"/>';
        $zip->addFromString('xl/worksheets/sheet' . $i . '.xml', ps_xlsx_sheet_xml($data));
        $i++;
    }
    $relsXml .= '<Relationship Id="rId' . $i . '" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/></Relationships>';
    $zip->addFromString('xl/workbook.xml', '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets>' . $sheetXml . '</sheets></workbook>');
    $zip->addFromString('xl/_rels/workbook.xml.rels', $relsXml);
    $zip->addFromString('xl/styles.xml', '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><fonts count="2"><font><sz val="11"/><name val="Calibri"/></font><font><b/><sz val="11"/><name val="Calibri"/></font></fonts><fills count="1"><fill><patternFill patternType="none"/></fill></fills><borders count="1"><border/></borders><cellXfs count="2"><xf fontId="0" fillId="0" borderId="0" xfId="0"/><xf fontId="1" fillId="0" borderId="0" xfId="0" applyFont="1"/></cellXfs></styleSheet>');
    $zip->close();
}

ps_setup($conn);
ps_mapping_setup($conn);
$units = ps_units($conn);
$canManage = ps_can_manage();
$message = '';
$error = '';
$exportDownloadLink = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $psAction = $_POST['ps_action'] ?? 'save_report';
    $id = (int)($_POST['id'] ?? 0);

    if ($psAction === 'save_report') {
        $incidentDate = trim($_POST['incident_date'] ?? '') . ' ' . trim($_POST['incident_time'] ?? '00:00');
        $unitId = (int)($_POST['unit_id'] ?? 0);
        $category = trim($_POST['incident_category'] ?? 'KNC');
        $incidentType = trim($_POST['incident_type'] ?? '');
        $chronology = trim($_POST['chronology'] ?? '');
        $severity = (int)($_POST['severity_score'] ?? 1);
        $probability = (int)($_POST['probability_score'] ?? 1);
        [$grade, $score, $investigation] = ps_grade($severity, $probability, $category);

        if (!$unitId || !$incidentType || !$chronology || trim($_POST['incident_date'] ?? '') === '') {
            $error = 'Data wajib belum lengkap. Minimal isi tanggal, unit, jenis/kategori insiden, tipe insiden, dan kronologi.';
        } else {
            $number = ps_next_number($conn);
            $reporterName = ps_user_name();
            $reporterUnit = (string)(ps_user()['unit_name'] ?? ps_user()['unit'] ?? '');
            $reporterId = ps_user_id();
            $location = trim($_POST['location_detail'] ?? '');
            $patientName = trim($_POST['patient_name'] ?? '');
            $patientInitial = trim($_POST['patient_initial'] ?? '');
            $rm = trim($_POST['medical_record_no'] ?? '');
            $birthDate = trim($_POST['birth_date'] ?? '') ?: null;
            $age = trim($_POST['age'] ?? '');
            $gender = in_array($_POST['gender'] ?? '-', ['L','P','-'], true) ? $_POST['gender'] : '-';
            $serviceType = trim($_POST['service_type'] ?? '');
            $dpjp = trim($_POST['dpjp'] ?? '');
            $subtype = trim($_POST['incident_subtype'] ?? '');
            $immediate = trim($_POST['immediate_action'] ?? '');
            $impact = trim($_POST['patient_impact'] ?? '');
            $disclosure = in_array($_POST['family_disclosure'] ?? 'tidak_perlu', ['ya','tidak','tidak_perlu'], true) ? $_POST['family_disclosure'] : 'tidak_perlu';
            $witnesses = trim($_POST['witnesses'] ?? '');
            $external = ($category === 'SENTINEL' || $category === 'KTD' || $grade === 'MERAH') ? 'ya' : 'tidak';

            $stmt = $conn->prepare("INSERT INTO patient_safety_incidents
                (incident_number, incident_date, unit_id, location_detail, reporter_user_id, reporter_name, reporter_unit, patient_name, patient_initial, medical_record_no, birth_date, age, gender, service_type, dpjp, incident_category, incident_type, incident_subtype, chronology, immediate_action, patient_impact, family_disclosure, witnesses, severity_score, probability_score, risk_score, risk_grade, investigation_type, external_report, status)
                VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,'reported')");
            $stmt->bind_param('ssisissssssssssssssssssiiisss', $number, $incidentDate, $unitId, $location, $reporterId, $reporterName, $reporterUnit, $patientName, $patientInitial, $rm, $birthDate, $age, $gender, $serviceType, $dpjp, $category, $incidentType, $subtype, $chronology, $immediate, $impact, $disclosure, $witnesses, $severity, $probability, $score, $grade, $investigation, $external);
            if ($stmt->execute()) {
                $newId = (int)$conn->insert_id;
                $unitName = ps_unit_name($conn, $unitId);
                $telegramData = [
                    'id' => $newId,
                    'incident_number' => $number,
                    'incident_date' => $incidentDate,
                    'unit_name' => $unitName,
                    'location_detail' => $location,
                    'reporter_name' => $reporterName,
                    'reporter_unit' => $reporterUnit,
                    'patient_name' => $patientName,
                    'patient_initial' => $patientInitial,
                    'medical_record_no' => $rm,
                    'age' => $age,
                    'gender' => $gender,
                    'service_type' => $serviceType,
                    'dpjp' => $dpjp,
                    'incident_category' => $category,
                    'incident_type' => $incidentType,
                    'incident_subtype' => $subtype,
                    'chronology' => $chronology,
                    'immediate_action' => $immediate,
                    'patient_impact' => $impact,
                    'severity_score' => $severity,
                    'probability_score' => $probability,
                    'risk_score' => $score,
                    'risk_grade' => $grade,
                    'investigation_type' => $investigation,
                    'external_report' => $external,
                    'detail_url' => ps_detail_url($newId),
                ];

                $telegramError = null;
                $telegramSent = ps_send_telegram_notification($telegramData, $telegramError);
                if ($telegramSent) {
                    $message = 'Laporan IKP berhasil disimpan dengan nomor ' . $number . ' dan sudah terkirim ke Telegram.';
                } else {
                    $message = 'Laporan IKP berhasil disimpan dengan nomor ' . $number . ', tetapi Telegram belum terkirim. Detail: ' . (string)$telegramError;
                }
            } else {
                $error = 'Gagal menyimpan laporan: ' . $stmt->error;
            }
        }
    }

    if ($psAction === 'save_investigation' && $canManage) {
        $root = trim($_POST['root_cause'] ?? '');
        $contrib = trim($_POST['contributing_factors'] ?? '');
        $recom = trim($_POST['recommendation'] ?? '');
        $plan = trim($_POST['action_plan'] ?? '');
        $pic = trim($_POST['pic_name'] ?? '');
        $target = trim($_POST['target_date'] ?? '') ?: null;
        $follow = trim($_POST['follow_up'] ?? '');
        $monitor = trim($_POST['effectiveness_monitoring'] ?? '');
        $status = $_POST['status'] ?? 'investigation';
        if (!in_array($status, ['verified','investigation','follow_up','closed'], true)) { $status = 'investigation'; }
        $verifiedBy = ps_user_name();
        $closedAt = ($status === 'closed') ? date('Y-m-d H:i:s') : null;
        $stmt = $conn->prepare("UPDATE patient_safety_incidents SET root_cause=?, contributing_factors=?, recommendation=?, action_plan=?, pic_name=?, target_date=?, follow_up=?, effectiveness_monitoring=?, status=?, verified_by=COALESCE(verified_by,?), verified_at=COALESCE(verified_at,NOW()), closed_at=? WHERE id=?");
        $stmt->bind_param('sssssssssssi', $root, $contrib, $recom, $plan, $pic, $target, $follow, $monitor, $status, $verifiedBy, $closedAt, $id);
        if ($stmt->execute()) { $message = 'Investigasi/tindak lanjut berhasil diperbarui.'; } else { $error = 'Gagal update investigasi: ' . $stmt->error; }
    }

    if ($psAction === 'upload_mapping_excel' && $canManage) {
        try {
            if (empty($_FILES['mapping_excel']['tmp_name']) || !is_uploaded_file($_FILES['mapping_excel']['tmp_name'])) {
                throw new RuntimeException('File Excel belum dipilih.');
            }
            $original = $_FILES['mapping_excel']['name'] ?? 'rekap_ikp.xlsx';
            $ext = strtolower(pathinfo($original, PATHINFO_EXTENSION));
            if ($ext !== 'xlsx') { throw new RuntimeException('Format yang didukung sementara .xlsx. Silakan simpan ulang Excel menjadi .xlsx.'); }
            $uploadDir = realpath(__DIR__ . '/..') . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'patient_safety';
            if (!is_dir($uploadDir)) { @mkdir($uploadDir, 0755, true); }
            $stored = 'rekap_ikp_' . date('Ymd_His') . '_' . preg_replace('/[^a-zA-Z0-9_.-]/', '_', basename($original));
            $target = $uploadDir . DIRECTORY_SEPARATOR . $stored;
            if (!move_uploaded_file($_FILES['mapping_excel']['tmp_name'], $target)) {
                throw new RuntimeException('Upload gagal. Pastikan folder uploads/patient_safety bisa ditulis.');
            }
            $tahun = trim((string)($_POST['tahun'] ?? date('Y')));
            if ($tahun === '') { $tahun = date('Y'); }
            $periode = trim((string)($_POST['periode'] ?? 'Tahunan'));
            if ($periode === '') { $periode = 'Tahunan'; }
            $rowsMap = ps_parse_mapping_rows($target, $tahun);
            if (!$rowsMap) { throw new RuntimeException('Tidak ada data laporan yang terbaca dari Excel.'); }
            $importId = ps_save_mapping_import($conn, $stored, $original, $tahun, $periode, $rowsMap);
            $message = 'Upload dan mapping berhasil. Total ' . count($rowsMap) . ' baris tersimpan.';
            $_GET['import_id'] = $importId;
            $_GET['action'] = 'mapping';
        } catch (Throwable $ex) {
            $error = 'Gagal proses mapping Excel: ' . $ex->getMessage();
            $_GET['action'] = 'mapping';
        }
    }

}

if (($_GET['action'] ?? '') === 'export') {
    // Modul ini biasanya di-include setelah header/layout index.php sudah tercetak.
    // Jadi export tidak boleh memakai header(), karena akan memicu warning
    // "Cannot modify header information". Solusinya: buat file CSV fisik lalu tampilkan tombol download.
    $exportDir = realpath(__DIR__ . '/..') . DIRECTORY_SEPARATOR . 'exports';
    if (!is_dir($exportDir)) { @mkdir($exportDir, 0755, true); }

    $filename = 'laporan_patient_safety_' . date('Ymd_His') . '.csv';
    $filepath = $exportDir . DIRECTORY_SEPARATOR . $filename;
    $out = @fopen($filepath, 'w');

    if (!$out) {
        $error = 'Gagal membuat file export. Pastikan folder /exports bisa ditulis oleh hosting.';
    } else {
        // BOM UTF-8 agar Excel membaca karakter Indonesia dengan benar.
        fwrite($out, "\xEF\xBB\xBF");
        fputcsv($out, ['No IKP','Tgl Insiden','Unit','Pasien/Inisial','No RM','Kategori','Tipe','Subtipe','Severity','Probability','Skor','Grade','Investigasi','Status','PIC','Target','RTL','Monitoring','Lapor Eksternal'], ';');
        $res = $conn->query("SELECT p.*, u.name unit_name FROM patient_safety_incidents p LEFT JOIN units u ON p.unit_id=u.id ORDER BY p.incident_date DESC");
        while ($r = $res->fetch_assoc()) {
            fputcsv($out, [
                $r['incident_number'], $r['incident_date'], $r['unit_name'],
                $r['patient_name'] ?: $r['patient_initial'], $r['medical_record_no'],
                $r['incident_category'], $r['incident_type'], $r['incident_subtype'],
                $r['severity_score'], $r['probability_score'], $r['risk_score'], $r['risk_grade'],
                $r['investigation_type'], $r['status'], $r['pic_name'], $r['target_date'],
                $r['follow_up'], $r['effectiveness_monitoring'], $r['external_report']
            ], ';');
        }
        fclose($out);
        $exportDownloadLink = 'exports/' . $filename;
        $message = 'Export berhasil dibuat.';
    }
}



if (($_GET['action'] ?? '') === 'export_mapping') {
    try {
        $importId = (int)($_GET['import_id'] ?? 0);
        if ($importId <= 0) { $importId = ps_latest_import_id($conn); }
        $rowsExport = ps_mapping_rows($conn, $importId);
        if (!$rowsExport) { throw new RuntimeException('Data mapping belum tersedia untuk diexport.'); }
        $exportDir = realpath(__DIR__ . '/..') . DIRECTORY_SEPARATOR . 'exports';
        if (!is_dir($exportDir)) { @mkdir($exportDir, 0755, true); }
        $filename = 'mapping_ikp_' . date('Ymd_His') . '.xlsx';
        $filepath = $exportDir . DIRECTORY_SEPARATOR . $filename;
        $selectedImport = null;
        $stmtImp = $conn->prepare("SELECT * FROM patient_safety_mapping_imports WHERE id=? LIMIT 1");
        if ($stmtImp) {
            $stmtImp->bind_param('i', $importId);
            $stmtImp->execute();
            $selectedImport = $stmtImp->get_result()->fetch_assoc();
        }
        ps_make_xlsx(ps_build_mapping_export_sheets($rowsExport, (string)($selectedImport['tahun'] ?? '')), $filepath);
        $exportDownloadLink = 'exports/' . $filename;
        $message = 'Export Excel mapping berhasil dibuat.';
        $_GET['action'] = 'mapping';
        $_GET['import_id'] = $importId;
    } catch (Throwable $ex) {
        $error = 'Gagal export mapping Excel: ' . $ex->getMessage();
        $_GET['action'] = 'mapping';
    }
}
$action = $_GET['action'] ?? 'dashboard';
$viewId = (int)($_GET['id'] ?? 0);
$view = $viewId ? ps_get($conn, $viewId) : null;

$where = [];
$params = [];
$types = '';
$filterMonth = $_GET['month'] ?? date('Y-m');
$filterUnit = (int)($_GET['unit_id'] ?? 0);
$filterGrade = $_GET['grade'] ?? '';
$filterStatus = $_GET['status'] ?? '';
if ($filterMonth) { $where[] = "DATE_FORMAT(p.incident_date,'%Y-%m') = ?"; $params[] = $filterMonth; $types .= 's'; }
if ($filterUnit) { $where[] = "p.unit_id = ?"; $params[] = $filterUnit; $types .= 'i'; }
if (in_array($filterGrade, ['BIRU','HIJAU','KUNING','MERAH'], true)) { $where[] = "p.risk_grade = ?"; $params[] = $filterGrade; $types .= 's'; }
if (in_array($filterStatus, ['reported','verified','investigation','follow_up','closed'], true)) { $where[] = "p.status = ?"; $params[] = $filterStatus; $types .= 's'; }
$whereSql = $where ? 'WHERE ' . implode(' AND ', $where) : '';

$sql = "SELECT p.*, u.name unit_name FROM patient_safety_incidents p LEFT JOIN units u ON p.unit_id=u.id {$whereSql} ORDER BY p.incident_date DESC LIMIT 300";
$stmt = $conn->prepare($sql);
if ($params) { $stmt->bind_param($types, ...$params); }
$stmt->execute();
$incidents = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);

$stats = ['total'=>0,'KPCS'=>0,'KNC'=>0,'KTC'=>0,'KTD'=>0,'SENTINEL'=>0,'MERAH'=>0,'overdue'=>0];
foreach ($incidents as $r) {
    $stats['total']++;
    $stats[$r['incident_category']] = ($stats[$r['incident_category']] ?? 0) + 1;
    if ($r['risk_grade'] === 'MERAH') $stats['MERAH']++;
    if (!empty($r['target_date']) && $r['status'] !== 'closed' && strtotime($r['target_date']) < strtotime(date('Y-m-d'))) $stats['overdue']++;
}

$gradeClass = ['BIRU'=>'bg-blue-100 text-blue-800 border-blue-200','HIJAU'=>'bg-green-100 text-green-800 border-green-200','KUNING'=>'bg-yellow-100 text-yellow-800 border-yellow-200','MERAH'=>'bg-red-100 text-red-800 border-red-200'];
$statusLabel = ['reported'=>'Laporan masuk','verified'=>'Terverifikasi','investigation'=>'Investigasi','follow_up'=>'Tindak lanjut','closed'=>'Selesai'];
$typesList = ['Administrasi klinis','Proses/prosedur klinis','Dokumentasi','Infeksi terkait pelayanan','Medikasi/cairan infus','Darah/produk darah','Gizi/nutrisi','Oksigen/gas medis','Alat medis','Pasien jatuh','Identifikasi pasien','Anestesi/sedasi','Laboratorium','Infrastruktur/sarana','Sumber daya/manajemen','Lain-lain'];
?>

<div class="min-h-screen bg-slate-50 p-4 md:p-6">
    <div class="mb-6 rounded-3xl bg-gradient-to-r from-red-700 via-rose-600 to-orange-500 p-6 text-white shadow-lg">
        <div class="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
            <div>
                <p class="text-sm uppercase tracking-widest text-red-100">PMKP 8 - PMKP 9</p>
                <h1 class="mt-1 text-2xl md:text-3xl font-bold">Patient Safety / Pelaporan IKP</h1>
                <p class="mt-2 max-w-3xl text-sm text-red-50">Form pelaporan, koding KPCS-KNC-KTC-KTD-Sentinel, grading matriks risiko, investigasi/RCA, tabulasi dan laporan tindak lanjut.</p>
            </div>
            <div class="flex flex-wrap gap-2">
                <a href="<?= e(ps_url(['action'=>'dashboard'])) ?>" class="rounded-xl bg-white/15 px-4 py-2 text-sm font-semibold hover:bg-white/25"><i class="fa fa-chart-line mr-2"></i>Dashboard</a>
                <a href="<?= e(ps_url(['action'=>'add'])) ?>" class="rounded-xl bg-white px-4 py-2 text-sm font-semibold text-red-700 shadow"><i class="fa fa-plus mr-2"></i>Lapor IKP</a>
                <a href="<?= e(ps_url(['action'=>'mapping'])) ?>" class="rounded-xl bg-white/15 px-4 py-2 text-sm font-semibold hover:bg-white/25"><i class="fa fa-table mr-2"></i>Mapping Excel</a>
                <a href="<?= e(ps_url(['action'=>'export'])) ?>" class="rounded-xl bg-black/20 px-4 py-2 text-sm font-semibold hover:bg-black/30"><i class="fa fa-file-export mr-2"></i>Export CSV</a>
            </div>
        </div>
    </div>

    <?php if ($message): ?>
        <div class="mb-4 rounded-2xl border border-green-200 bg-green-50 px-4 py-3 text-green-800">
            <i class="fa fa-check-circle mr-2"></i><?= e($message) ?>
            <?php if (!empty($exportDownloadLink)): ?>
                <a class="ml-2 font-bold underline" href="<?= e($exportDownloadLink) ?>" download>Download file sekarang</a>
            <?php endif; ?>
        </div>
    <?php endif; ?>
    <?php if ($error): ?><div class="mb-4 rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-red-800"><i class="fa fa-triangle-exclamation mr-2"></i><?= e($error) ?></div><?php endif; ?>

    <?php if ($action === 'add'): ?>
        <div class="rounded-3xl bg-white p-5 shadow-sm border border-slate-100">
            <h2 class="mb-4 text-xl font-bold text-slate-800">Form Laporan Insiden Keselamatan Pasien</h2>
            <form method="post" action="<?= e(ps_url(['action'=>'add'])) ?>" class="space-y-6">
                <input type="hidden" name="ps_action" value="save_report">
                <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
                    <div><label class="text-sm font-semibold">Tanggal Insiden *</label><input type="date" name="incident_date" value="<?= date('Y-m-d') ?>" required class="mt-1 w-full rounded-xl border-slate-300"></div>
                    <div><label class="text-sm font-semibold">Jam *</label><input type="time" name="incident_time" value="<?= date('H:i') ?>" required class="mt-1 w-full rounded-xl border-slate-300"></div>
                    <div><label class="text-sm font-semibold">Unit Lokasi *</label><select name="unit_id" required class="mt-1 w-full rounded-xl border-slate-300"><option value="">Pilih unit</option><?php foreach($units as $u): ?><option value="<?= (int)$u['id'] ?>"><?= e($u['name']) ?></option><?php endforeach; ?></select></div>
                    <div><label class="text-sm font-semibold">Detail Lokasi</label><input name="location_detail" placeholder="Ruang/kamar/area" class="mt-1 w-full rounded-xl border-slate-300"></div>
                </div>

                <div class="rounded-2xl bg-slate-50 p-4">
                    <h3 class="mb-3 font-bold text-slate-700">Data Pasien</h3>
                    <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
                        <div><label class="text-sm font-semibold">Nama Pasien</label><input name="patient_name" class="mt-1 w-full rounded-xl border-slate-300"></div>
                        <div><label class="text-sm font-semibold">Inisial</label><input name="patient_initial" placeholder="Untuk laporan anonim" class="mt-1 w-full rounded-xl border-slate-300"></div>
                        <div><label class="text-sm font-semibold">No. RM</label><input name="medical_record_no" class="mt-1 w-full rounded-xl border-slate-300"></div>
                        <div><label class="text-sm font-semibold">Tanggal Lahir</label><input type="date" name="birth_date" class="mt-1 w-full rounded-xl border-slate-300"></div>
                        <div><label class="text-sm font-semibold">Umur</label><input name="age" placeholder="mis. 45 th" class="mt-1 w-full rounded-xl border-slate-300"></div>
                        <div><label class="text-sm font-semibold">JK</label><select name="gender" class="mt-1 w-full rounded-xl border-slate-300"><option value="-">-</option><option value="L">Laki-laki</option><option value="P">Perempuan</option></select></div>
                        <div><label class="text-sm font-semibold">Jenis Layanan</label><select name="service_type" class="mt-1 w-full rounded-xl border-slate-300"><option>Rawat Inap</option><option>Rawat Jalan</option><option>IGD</option><option>OK</option><option>HD</option><option>Lainnya</option></select></div>
                        <div><label class="text-sm font-semibold">DPJP</label><input name="dpjp" class="mt-1 w-full rounded-xl border-slate-300"></div>
                    </div>
                </div>

                <div class="grid grid-cols-1 md:grid-cols-4 gap-4">
                    <div><label class="text-sm font-semibold">Jenis Insiden *</label><select name="incident_category" required class="mt-1 w-full rounded-xl border-slate-300"><option value="KPCS">KPCS</option><option value="KNC" selected>KNC</option><option value="KTC">KTC</option><option value="KTD">KTD</option><option value="SENTINEL">Sentinel</option></select></div>
                    <div><label class="text-sm font-semibold">Tipe Insiden *</label><select name="incident_type" required class="mt-1 w-full rounded-xl border-slate-300"><option value="">Pilih tipe</option><?php foreach($typesList as $t): ?><option><?= e($t) ?></option><?php endforeach; ?></select></div>
                    <div><label class="text-sm font-semibold">Subtipe/Keterangan</label><input name="incident_subtype" placeholder="mis. high alert, pasien jatuh" class="mt-1 w-full rounded-xl border-slate-300"></div>
                    <div><label class="text-sm font-semibold">Keluarga Dijelaskan?</label><select name="family_disclosure" class="mt-1 w-full rounded-xl border-slate-300"><option value="tidak_perlu">Tidak perlu</option><option value="ya">Ya</option><option value="tidak">Tidak</option></select></div>
                </div>

                <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
                    <div><label class="text-sm font-semibold">Severity / Dampak *</label><select name="severity_score" required class="mt-1 w-full rounded-xl border-slate-300"><option value="1">1 - Tidak signifikan</option><option value="2">2 - Minor</option><option value="3">3 - Moderat</option><option value="4">4 - Mayor</option><option value="5">5 - Katastropik/Sentinel</option></select></div>
                    <div><label class="text-sm font-semibold">Probability / Probabilitas *</label><select name="probability_score" required class="mt-1 w-full rounded-xl border-slate-300"><option value="1">1 - Sangat jarang</option><option value="2">2 - Jarang</option><option value="3">3 - Mungkin</option><option value="4">4 - Sering</option><option value="5">5 - Sangat sering</option></select></div>
                </div>

                <div><label class="text-sm font-semibold">Kronologi Objektif *</label><textarea name="chronology" required rows="5" class="mt-1 w-full rounded-xl border-slate-300" placeholder="Tulis kronologi runtut: tanggal/jam, apa yang terjadi, siapa yang mengetahui, kondisi pasien, tanpa menyalahkan individu."></textarea></div>
                <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
                    <div><label class="text-sm font-semibold">Tindakan Segera</label><textarea name="immediate_action" rows="4" class="mt-1 w-full rounded-xl border-slate-300" placeholder="Stabilisasi pasien, lapor DPJP/MOD/kepala unit, observasi, dll."></textarea></div>
                    <div><label class="text-sm font-semibold">Dampak pada Pasien & Saksi</label><textarea name="patient_impact" rows="4" class="mt-1 w-full rounded-xl border-slate-300" placeholder="Cedera/tidak cedera, terapi tambahan, rawat lebih lama, dll."></textarea><input name="witnesses" class="mt-2 w-full rounded-xl border-slate-300" placeholder="Saksi/orang/unit terkait"></div>
                </div>
                <div class="flex justify-end gap-2"><a href="<?= e(ps_url()) ?>" class="rounded-xl border px-5 py-2">Batal</a><button class="rounded-xl bg-red-600 px-6 py-2 font-semibold text-white shadow hover:bg-red-700"><i class="fa fa-save mr-2"></i>Simpan Laporan</button></div>
            </form>
        </div>
    <?php elseif ($action === 'view' && $view): ?>
        <div class="grid grid-cols-1 lg:grid-cols-3 gap-5">
            <div class="lg:col-span-2 rounded-3xl bg-white p-5 shadow-sm border border-slate-100">
                <div class="mb-4 flex items-start justify-between gap-3"><div><h2 class="text-xl font-bold"><?= e($view['incident_number']) ?></h2><p class="text-sm text-slate-500"><?= date('d/m/Y H:i', strtotime($view['incident_date'])) ?> • <?= e($view['unit_name'] ?? '-') ?></p></div><span class="rounded-full border px-3 py-1 text-xs font-bold <?= e($gradeClass[$view['risk_grade']] ?? '') ?>"><?= e($view['risk_grade']) ?> / Skor <?= (int)$view['risk_score'] ?></span></div>
                <div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
                    <div class="rounded-2xl bg-slate-50 p-4"><b>Pasien</b><br><?= e($view['patient_name'] ?: $view['patient_initial'] ?: '-') ?><br>RM: <?= e($view['medical_record_no'] ?: '-') ?><br>DPJP: <?= e($view['dpjp'] ?: '-') ?></div>
                    <div class="rounded-2xl bg-slate-50 p-4"><b>Klasifikasi</b><br><?= e($view['incident_category']) ?> • <?= e($view['incident_type']) ?><br><?= e($view['incident_subtype'] ?: '-') ?><br><?= e($view['investigation_type']) ?></div>
                </div>
                <h3 class="mt-5 font-bold">Kronologi</h3><div class="mt-2 whitespace-pre-wrap rounded-2xl border bg-white p-4 text-sm"><?= e($view['chronology']) ?></div>
                <h3 class="mt-5 font-bold">Tindakan Segera</h3><div class="mt-2 whitespace-pre-wrap rounded-2xl border bg-white p-4 text-sm"><?= e($view['immediate_action'] ?: '-') ?></div>
                <h3 class="mt-5 font-bold">Hasil Investigasi / RCA / Tindak Lanjut</h3>
                <div class="mt-2 grid grid-cols-1 md:grid-cols-2 gap-3 text-sm">
                    <div class="rounded-2xl border p-3"><b>Faktor Kontribusi</b><div class="whitespace-pre-wrap"><?= e($view['contributing_factors'] ?: '-') ?></div></div>
                    <div class="rounded-2xl border p-3"><b>Akar Masalah</b><div class="whitespace-pre-wrap"><?= e($view['root_cause'] ?: '-') ?></div></div>
                    <div class="rounded-2xl border p-3"><b>Rekomendasi</b><div class="whitespace-pre-wrap"><?= e($view['recommendation'] ?: '-') ?></div></div>
                    <div class="rounded-2xl border p-3"><b>RTL / Monitoring</b><div class="whitespace-pre-wrap"><?= e(($view['follow_up'] ?: '-') . "\n" . ($view['effectiveness_monitoring'] ?: '')) ?></div></div>
                </div>
            </div>
            <div class="rounded-3xl bg-white p-5 shadow-sm border border-slate-100">
                <h3 class="mb-3 font-bold">Update Investigasi</h3>
                <?php if ($canManage): ?>
                <form method="post" action="<?= e(ps_url(['action'=>'view','id'=>(int)$view['id']])) ?>" class="space-y-3 text-sm">
                    <input type="hidden" name="ps_action" value="save_investigation"><input type="hidden" name="id" value="<?= (int)$view['id'] ?>">
                    <textarea name="contributing_factors" rows="3" class="w-full rounded-xl border-slate-300" placeholder="Faktor kontribusi"><?= e($view['contributing_factors']) ?></textarea>
                    <textarea name="root_cause" rows="3" class="w-full rounded-xl border-slate-300" placeholder="Akar masalah"><?= e($view['root_cause']) ?></textarea>
                    <textarea name="recommendation" rows="3" class="w-full rounded-xl border-slate-300" placeholder="Rekomendasi perbaikan"><?= e($view['recommendation']) ?></textarea>
                    <textarea name="action_plan" rows="3" class="w-full rounded-xl border-slate-300" placeholder="Rencana aksi"><?= e($view['action_plan']) ?></textarea>
                    <input name="pic_name" value="<?= e($view['pic_name']) ?>" class="w-full rounded-xl border-slate-300" placeholder="PIC tindak lanjut">
                    <input type="date" name="target_date" value="<?= e($view['target_date']) ?>" class="w-full rounded-xl border-slate-300">
                    <textarea name="follow_up" rows="3" class="w-full rounded-xl border-slate-300" placeholder="Tindak lanjut"><?= e($view['follow_up']) ?></textarea>
                    <textarea name="effectiveness_monitoring" rows="3" class="w-full rounded-xl border-slate-300" placeholder="Monitoring efektivitas"><?= e($view['effectiveness_monitoring']) ?></textarea>
                    <select name="status" class="w-full rounded-xl border-slate-300"><?php foreach($statusLabel as $k=>$v): if($k==='reported') continue; ?><option value="<?= e($k) ?>" <?= $view['status']===$k?'selected':'' ?>><?= e($v) ?></option><?php endforeach; ?></select>
                    <button class="w-full rounded-xl bg-red-600 px-4 py-2 font-semibold text-white"><i class="fa fa-save mr-2"></i>Simpan Update</button>
                </form>
                <?php else: ?><p class="text-sm text-slate-500">Akses update investigasi hanya untuk admin/editor/komite mutu.</p><?php endif; ?>
            </div>
        </div>

    <?php elseif ($action === 'mapping'): ?>
        <?php
            $importList = ps_mapping_imports($conn);
            $selectedImportId = (int)($_GET['import_id'] ?? 0);
            if ($selectedImportId <= 0) { $selectedImportId = ps_latest_import_id($conn); }
            $mapRows = ps_mapping_rows($conn, $selectedImportId);
            $mapSummary = ps_mapping_summary($mapRows);
            $selectedImport = null;
            foreach ($importList as $imp) { if ((int)$imp['id'] === $selectedImportId) { $selectedImport = $imp; break; } }
        ?>
        <div class="grid grid-cols-1 lg:grid-cols-3 gap-5">
            <div class="lg:col-span-1 rounded-3xl bg-white p-5 shadow-sm border border-slate-100">
                <h2 class="text-xl font-bold text-slate-800">Mapping Excel IKP</h2>
                <p class="mt-1 text-sm text-slate-500">Upload file <b>Rekapan Laporan Insiden Keselamatan Pasien</b>, sistem otomatis mapping ke format <b>Mapping Insiden Tiap Unit</b>.</p>
                <?php if ($canManage): ?>
                <form method="post" enctype="multipart/form-data" action="<?= e(ps_url(['action'=>'mapping'])) ?>" class="mt-5 space-y-3">
                    <input type="hidden" name="ps_action" value="upload_mapping_excel">
                    <div><label class="text-sm font-semibold">File Excel Rekap (.xlsx)</label><input type="file" name="mapping_excel" accept=".xlsx" required class="mt-1 w-full rounded-xl border border-slate-300 p-2 text-sm"></div>
                    <div class="grid grid-cols-2 gap-3">
                        <div><label class="text-sm font-semibold">Tahun</label><input name="tahun" value="<?= date('Y') ?>" class="mt-1 w-full rounded-xl border-slate-300"></div>
                        <div><label class="text-sm font-semibold">Periode</label><input name="periode" value="Tahunan" class="mt-1 w-full rounded-xl border-slate-300" placeholder="Bulan/Triwulan/Tahunan"></div>
                    </div>
                    <button class="w-full rounded-xl bg-red-600 px-4 py-2 font-semibold text-white hover:bg-red-700"><i class="fa fa-upload mr-2"></i>Upload & Proses Mapping</button>
                </form>
                <?php else: ?>
                    <div class="mt-4 rounded-2xl bg-yellow-50 p-3 text-sm text-yellow-800">Upload hanya untuk admin/editor/komite mutu.</div>
                <?php endif; ?>

                <div class="mt-6">
                    <h3 class="mb-2 font-bold text-slate-700">Riwayat Upload</h3>
                    <div class="space-y-2">
                        <?php if (!$importList): ?><div class="rounded-xl bg-slate-50 p-3 text-sm text-slate-500">Belum ada upload mapping.</div><?php endif; ?>
                        <?php foreach ($importList as $imp): ?>
                            <a href="<?= e(ps_url(['action'=>'mapping','import_id'=>(int)$imp['id']])) ?>" class="block rounded-2xl border <?= (int)$imp['id']===$selectedImportId?'border-red-300 bg-red-50':'border-slate-100 bg-slate-50' ?> p-3 text-sm hover:bg-red-50">
                                <div class="font-bold text-slate-800"><?= e($imp['original_file_name'] ?: $imp['file_name']) ?></div>
                                <div class="mt-1 text-xs text-slate-500"><?= e($imp['periode'] ?: '-') ?> <?= e($imp['tahun'] ?: '') ?> • <?= date('d/m/Y H:i', strtotime($imp['created_at'])) ?></div>
                                <div class="mt-1 text-xs"><b><?= (int)$imp['total_rows'] ?></b> baris • mapped <?= (int)$imp['total_mapped'] ?> • belum <?= (int)$imp['total_unmapped'] ?></div>
                            </a>
                        <?php endforeach; ?>
                    </div>
                </div>
            </div>

            <div class="lg:col-span-2 space-y-5">
                <?php if ($selectedImport && $mapRows): ?>
                    <div class="rounded-3xl bg-white p-5 shadow-sm border border-slate-100">
                        <div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
                            <div>
                                <h3 class="text-lg font-bold text-slate-800">Hasil Mapping: <?= e($selectedImport['original_file_name'] ?: $selectedImport['file_name']) ?></h3>
                                <p class="text-sm text-slate-500">Periode <?= e($selectedImport['periode'] ?: '-') ?> <?= e($selectedImport['tahun'] ?: '') ?>. Data sudah tersimpan di database dan siap diexport.</p>
                            </div>
                            <a href="<?= e(ps_url(['action'=>'export_mapping','import_id'=>$selectedImportId])) ?>" class="rounded-xl bg-emerald-600 px-4 py-2 text-sm font-semibold text-white hover:bg-emerald-700"><i class="fa fa-file-excel mr-2"></i>Export mapping.xlsx</a>
                        </div>
                        <div class="mt-4 grid grid-cols-2 md:grid-cols-4 gap-3">
                            <div class="rounded-2xl bg-slate-50 p-4"><div class="text-xs text-slate-500">Total Data</div><div class="text-2xl font-bold"><?= count($mapRows) ?></div></div>
                            <div class="rounded-2xl bg-green-50 p-4"><div class="text-xs text-green-700">Termapping</div><div class="text-2xl font-bold text-green-800"><?= (int)$selectedImport['total_mapped'] ?></div></div>
                            <div class="rounded-2xl bg-amber-50 p-4"><div class="text-xs text-amber-700">Belum Mapping</div><div class="text-2xl font-bold text-amber-800"><?= (int)$selectedImport['total_unmapped'] ?></div></div>
                            <div class="rounded-2xl bg-red-50 p-4"><div class="text-xs text-red-700">Unit Terbanyak</div><div class="text-lg font-bold text-red-800"><?= e(array_key_first($mapSummary['unit']) ?: '-') ?></div></div>
                        </div>
                    </div>

                    <div class="grid grid-cols-1 md:grid-cols-2 gap-5">
                        <div class="rounded-3xl bg-white p-5 shadow-sm border border-slate-100">
                            <h3 class="mb-3 font-bold text-slate-800">Tabulasi per Unit</h3>
                            <div class="overflow-x-auto"><table class="min-w-full text-sm"><thead class="bg-slate-100 text-left text-xs uppercase text-slate-500"><tr><th class="px-3 py-2">Unit</th><th class="px-3 py-2">Jumlah</th></tr></thead><tbody class="divide-y divide-slate-100">
                                <?php foreach ($mapSummary['unit'] as $k=>$v): ?><tr><td class="px-3 py-2 font-semibold"><?= e($k) ?></td><td class="px-3 py-2"><?= (int)$v ?></td></tr><?php endforeach; ?>
                            </tbody></table></div>
                        </div>
                        <div class="rounded-3xl bg-white p-5 shadow-sm border border-slate-100">
                            <h3 class="mb-3 font-bold text-slate-800">Tabulasi Risiko / Grading</h3>
                            <div class="overflow-x-auto"><table class="min-w-full text-sm"><thead class="bg-slate-100 text-left text-xs uppercase text-slate-500"><tr><th class="px-3 py-2">Grading</th><th class="px-3 py-2">Jumlah</th></tr></thead><tbody class="divide-y divide-slate-100">
                                <?php foreach ($mapSummary['grade'] as $k=>$v): ?><tr><td class="px-3 py-2 font-semibold"><?= e($k) ?></td><td class="px-3 py-2"><?= (int)$v ?></td></tr><?php endforeach; ?>
                            </tbody></table></div>
                        </div>
                    </div>

                    <div class="rounded-3xl bg-white shadow-sm border border-slate-100 overflow-hidden">
                        <div class="border-b p-4 flex items-center justify-between"><h3 class="font-bold text-slate-800">Preview Data Mapping</h3><span class="text-xs text-slate-500">Maksimal 500 data per upload</span></div>
                        <div class="overflow-x-auto"><table class="min-w-full text-sm">
                            <thead class="bg-slate-100 text-left text-xs uppercase text-slate-500"><tr><th class="px-4 py-3">No</th><th class="px-4 py-3">Tanggal Insiden Terjadi</th><th class="px-4 py-3">Bagian Unit</th><th class="px-4 py-3">Area/Lokasi</th><th class="px-4 py-3">Sasaran KP</th><th class="px-4 py-3">Tipe Insiden</th><th class="px-4 py-3">Insiden yang Terjadi</th><th class="px-4 py-3">Penyebab Insiden</th><th class="px-4 py-3">Kategori Risiko</th><th class="px-4 py-3">Penanganan</th><th class="px-4 py-3">Unit Mapping</th></tr></thead>
                            <tbody class="divide-y divide-slate-100">
                                <?php foreach (array_slice($mapRows, 0, 500) as $r): ?>
                                    <tr class="hover:bg-slate-50 align-top">
                                        <td class="px-4 py-3"><?= (int)$r['no_urut'] ?></td>
                                        <td class="px-4 py-3"><?= e($r['tanggal_insiden']) ?></td>
                                        <td class="px-4 py-3"><?= e($r['bagian_unit']) ?></td>
                                        <td class="px-4 py-3"><?= e($r['area_lokasi']) ?></td>
                                        <td class="px-4 py-3"><?= e($r['sasaran_kp']) ?></td>
                                        <td class="px-4 py-3"><?= e($r['tipe_insiden']) ?></td>
                                        <td class="px-4 py-3 min-w-[260px]"><?= e(ps_short_text($r['insiden_terjadi'], 180)) ?></td>
                                        <td class="px-4 py-3 min-w-[260px]"><?= e(ps_short_text($r['penyebab_insiden'], 180)) ?></td>
                                        <td class="px-4 py-3"><?= e($r['kategori_risiko']) ?></td>
                                        <td class="px-4 py-3 min-w-[260px]"><?= e(ps_short_text($r['penanganan'], 180)) ?></td>
                                        <td class="px-4 py-3"><span class="rounded-full px-2 py-1 text-xs font-bold <?= $r['status_mapping']==='unmapped'?'bg-amber-100 text-amber-800':'bg-green-100 text-green-800' ?>"><?= e($r['unit_mapping']) ?></span></td>
                                    </tr>
                                <?php endforeach; ?>
                            </tbody>
                        </table></div>
                    </div>
                <?php else: ?>
                    <div class="rounded-3xl bg-white p-8 text-center shadow-sm border border-slate-100">
                        <div class="mx-auto mb-3 flex h-14 w-14 items-center justify-center rounded-full bg-red-50 text-red-600"><i class="fa fa-file-excel text-2xl"></i></div>
                        <h3 class="text-lg font-bold text-slate-800">Belum ada data mapping aktif</h3>
                        <p class="mt-1 text-sm text-slate-500">Upload Excel rekap laporan IKP untuk mulai membuat mapping insiden tiap unit.</p>
                    </div>
                <?php endif; ?>
            </div>
        </div>

    <?php else: ?>
        <div class="mb-5 rounded-3xl bg-white p-5 shadow-sm border border-slate-100">
            <div class="mb-3 flex items-center justify-between gap-3">
                <div>
                    <h2 class="text-lg font-bold text-slate-800">Alur Pelaporan Patient Safety</h2>
                    <p class="text-sm text-slate-500">Sesuai plan IKP: pelaporan maksimal 2x24 jam, grading, investigasi/RCA, tabulasi, dan pelaporan PMKP.</p>
                </div>
                <span class="rounded-full bg-red-50 px-3 py-1 text-xs font-bold text-red-700">PMKP 8 - PMKP 9</span>
            </div>
            <div class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-2 text-xs">
                <?php foreach ([
                    'Insiden terjadi', 'Pasien diamankan', 'Petugas isi Form IKP maksimal 2x24 jam', 'Kepala unit verifikasi', 'Komite Mutu melakukan grading',
                    'Biru: investigasi sederhana <= 7 hari', 'Hijau: investigasi sederhana <= 14 hari', 'Kuning: simple RCA/RCA', 'Merah/Sentinel: RCA <= 45 hari', 'Rekomendasi & RTL dibuat',
                    'Efektivitas dipantau', 'Tabulasi bulanan & analisis triwulan', 'Laporan triwulan ke Direktur/Dewas', 'Sentinel/KTD serius dilaporkan eksternal', 'Pembelajaran & perbaikan sistem'
                ] as $i => $step): ?>
                    <div class="rounded-2xl border border-slate-100 bg-slate-50 p-3"><b class="text-red-700"><?= $i+1 ?>.</b> <?= e($step) ?></div>
                <?php endforeach; ?>
            </div>
        </div>

        <form method="get" class="mb-5 rounded-3xl bg-white p-4 shadow-sm border border-slate-100">
            <input type="hidden" name="module" value="patient_safety">
            <div class="grid grid-cols-1 md:grid-cols-5 gap-3">
                <input type="month" name="month" value="<?= e($filterMonth) ?>" class="rounded-xl border-slate-300">
                <select name="unit_id" class="rounded-xl border-slate-300"><option value="0">Semua Unit</option><?php foreach($units as $u): ?><option value="<?= (int)$u['id'] ?>" <?= $filterUnit===(int)$u['id']?'selected':'' ?>><?= e($u['name']) ?></option><?php endforeach; ?></select>
                <select name="grade" class="rounded-xl border-slate-300"><option value="">Semua Grade</option><?php foreach(['BIRU','HIJAU','KUNING','MERAH'] as $g): ?><option value="<?= $g ?>" <?= $filterGrade===$g?'selected':'' ?>><?= $g ?></option><?php endforeach; ?></select>
                <select name="status" class="rounded-xl border-slate-300"><option value="">Semua Status</option><?php foreach($statusLabel as $k=>$v): ?><option value="<?= e($k) ?>" <?= $filterStatus===$k?'selected':'' ?>><?= e($v) ?></option><?php endforeach; ?></select>
                <button class="rounded-xl bg-slate-800 px-4 py-2 font-semibold text-white"><i class="fa fa-filter mr-2"></i>Tampilkan</button>
            </div>
        </form>

        <div class="mb-5 grid grid-cols-2 md:grid-cols-4 lg:grid-cols-8 gap-3">
            <?php foreach ([['Total',$stats['total'],'fa-list'],['KPCS',$stats['KPCS'],'fa-warning'],['KNC',$stats['KNC'],'fa-random'],['KTC',$stats['KTC'],'fa-user-check'],['KTD',$stats['KTD'],'fa-heart-crack'],['Sentinel',$stats['SENTINEL'],'fa-skull-crossbones'],['Merah',$stats['MERAH'],'fa-fire'],['Overdue',$stats['overdue'],'fa-clock']] as $s): ?>
            <div class="rounded-2xl bg-white p-4 shadow-sm border border-slate-100"><div class="text-xs text-slate-500"><i class="fa <?= e($s[2]) ?> mr-1"></i><?= e($s[0]) ?></div><div class="mt-1 text-2xl font-bold text-slate-800"><?= (int)$s[1] ?></div></div>
            <?php endforeach; ?>
        </div>

        <div class="rounded-3xl bg-white shadow-sm border border-slate-100 overflow-hidden">
            <div class="border-b p-4 flex items-center justify-between"><h2 class="font-bold text-slate-800">Register & Tabulasi IKP</h2><span class="text-xs text-slate-500">Maksimal tampil 300 data terakhir</span></div>
            <div class="overflow-x-auto"><table class="min-w-full text-sm">
                <thead class="bg-slate-100 text-left text-xs uppercase text-slate-500"><tr><th class="px-4 py-3">No</th><th class="px-4 py-3">Tanggal</th><th class="px-4 py-3">Unit</th><th class="px-4 py-3">Pasien</th><th class="px-4 py-3">Jenis/Tipe</th><th class="px-4 py-3">Grading</th><th class="px-4 py-3">Investigasi</th><th class="px-4 py-3">Status</th><th class="px-4 py-3">Aksi</th></tr></thead>
                <tbody class="divide-y divide-slate-100">
                    <?php if (!$incidents): ?><tr><td colspan="9" class="px-4 py-8 text-center text-slate-500">Belum ada data IKP pada filter ini.</td></tr><?php endif; ?>
                    <?php foreach($incidents as $r): ?>
                    <tr class="hover:bg-slate-50"><td class="px-4 py-3 font-semibold"><?= e($r['incident_number']) ?></td><td class="px-4 py-3"><?= date('d/m/Y H:i', strtotime($r['incident_date'])) ?></td><td class="px-4 py-3"><?= e($r['unit_name'] ?? '-') ?></td><td class="px-4 py-3"><?= e($r['patient_name'] ?: $r['patient_initial'] ?: '-') ?></td><td class="px-4 py-3"><b><?= e($r['incident_category']) ?></b><br><span class="text-slate-500"><?= e($r['incident_type']) ?></span></td><td class="px-4 py-3"><span class="rounded-full border px-2 py-1 text-xs font-bold <?= e($gradeClass[$r['risk_grade']] ?? '') ?>"><?= e($r['risk_grade']) ?> <?= (int)$r['risk_score'] ?></span></td><td class="px-4 py-3"><?= e($r['investigation_type']) ?></td><td class="px-4 py-3"><?= e($statusLabel[$r['status']] ?? $r['status']) ?></td><td class="px-4 py-3"><a href="<?= e(ps_url(['action'=>'view','id'=>(int)$r['id']])) ?>" class="rounded-lg bg-red-50 px-3 py-1 font-semibold text-red-700 hover:bg-red-100">Detail</a></td></tr>
                    <?php endforeach; ?>
                </tbody>
            </table></div>
        </div>
    <?php endif; ?>
</div>
