<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Chatbox extends Auth_Controller
{
    const CHAT_PAYLOAD_PREFIX = '__AI_CHAT_JSON__:';

    public function __construct()
    {
        parent::__construct();
        $this->load->model('Ai_chat_model');
    }

    public function sessions()
    {
        $id_user = (int) $this->session->userdata('id_user');

        if ($this->input->server('REQUEST_METHOD') === 'POST') {
            $judul = trim((string) $this->input->post('title', true));
            $id_session = $this->Ai_chat_model->create_session($id_user, $judul);
            $session = $this->Ai_chat_model->get_session($id_session, $id_user);

            return $this->json_response([
                'success' => true,
                'session' => $this->format_session($session),
            ]);
        }

        $sessions = $this->Ai_chat_model->list_sessions($id_user);
        $result = [];
        foreach ($sessions as $session) {
            $result[] = $this->format_session($session);
        }

        return $this->json_response([
            'success' => true,
            'sessions' => $result,
        ]);
    }

    public function messages($id_session = null)
    {
        $id_user = (int) $this->session->userdata('id_user');
        $id_session = (int) $id_session;
        $page = (int) $this->input->get('page', true);
        $per_page = (int) $this->input->get('per_page', true);
        $page = $page > 0 ? $page : 1;
        $default_page_size = (int) app_config('ai_chat_page_size', '30');
        $default_page_size = $default_page_size > 0 ? $default_page_size : 30;
        $per_page = $per_page > 0 ? $per_page : $default_page_size;
        $per_page = max(10, min(100, $per_page));

        if ($id_session <= 0) {
            return $this->json_response([
                'success' => false,
                'message' => 'ID sesi tidak valid.',
            ], 422);
        }

        $session = $this->Ai_chat_model->get_session($id_session, $id_user);
        if (!$session) {
            return $this->json_response([
                'success' => false,
                'message' => 'Sesi tidak ditemukan.',
            ], 404);
        }

        $total_messages = $this->Ai_chat_model->count_messages($id_session, $id_user);
        $messages = $this->Ai_chat_model->get_messages_page($id_session, $id_user, $page, $per_page);
        $rows = [];
        foreach ($messages as $msg) {
            $payload = $this->decode_chat_message_payload((string) $msg->message_text);
            $rows[] = [
                'id' => (int) $msg->id_message,
                'role' => (string) $msg->role,
                'text' => (string) $payload['text'],
                'images' => $payload['images'],
                'created_at' => (string) $msg->created_at,
            ];
        }

        return $this->json_response([
            'success' => true,
            'session' => $this->format_session($session),
            'messages' => $rows,
            'pagination' => [
                'page' => $page,
                'per_page' => $per_page,
                'total' => $total_messages,
                'has_more' => (($page * $per_page) < $total_messages),
            ],
        ]);
    }

    public function delete_session($id_session = null)
    {
        if ($this->input->server('REQUEST_METHOD') !== 'POST') {
            return $this->json_response([
                'success' => false,
                'message' => 'Metode request tidak valid.',
            ], 405);
        }

        $id_user = (int) $this->session->userdata('id_user');
        $id_session = (int) $id_session;
        if ($id_session <= 0) {
            return $this->json_response([
                'success' => false,
                'message' => 'ID sesi tidak valid.',
            ], 422);
        }

        $deleted = $this->Ai_chat_model->delete_session($id_session, $id_user);
        if (!$deleted) {
            return $this->json_response([
                'success' => false,
                'message' => 'Sesi tidak ditemukan atau tidak berhak dihapus.',
            ], 404);
        }

        return $this->json_response([
            'success' => true,
            'message' => 'Sesi berhasil dihapus.',
        ]);
    }

    public function send()
    {
        if ($this->input->server('REQUEST_METHOD') !== 'POST') {
            return $this->json_response([
                'success' => false,
                'message' => 'Metode request tidak valid.',
            ], 405);
        }

        $id_user = (int) $this->session->userdata('id_user');
        $id_session = (int) $this->input->post('id_session', true);
        $prompt = trim((string) $this->input->post('prompt', true));

        if ($id_session <= 0 || $prompt === '') {
            return $this->json_response([
                'success' => false,
                'message' => 'Data chat tidak valid.',
            ], 422);
        }

        $session = $this->Ai_chat_model->get_session($id_session, $id_user);
        if (!$session) {
            return $this->json_response([
                'success' => false,
                'message' => 'Sesi tidak ditemukan.',
            ], 404);
        }

        $this->Ai_chat_model->add_message($id_session, $id_user, 'user', $prompt);

        $ai_result = $this->generate_ai_reply($id_session, $id_user, $prompt);
        $reply = (string) ($ai_result['text'] ?? 'Maaf, respons AI belum tersedia saat ini.');
        $reply_images = isset($ai_result['images']) && is_array($ai_result['images']) ? $ai_result['images'] : [];
        $assistant_payload = $this->encode_chat_message_payload($reply, $reply_images);
        $this->Ai_chat_model->add_message($id_session, $id_user, 'assistant', $assistant_payload);

        $max_retention = (int) app_config('ai_chat_retention_messages', '400');
        $max_retention = $max_retention > 0 ? $max_retention : 400;
        $trimmed_rows = $this->Ai_chat_model->trim_session_messages($id_session, $id_user, $max_retention);

        audit_log('CHAT', 'AIChatbox', 'Session ID: ' . $id_session . ' | User ID: ' . $id_user . ' | Prompt length: ' . strlen($prompt));

        return $this->json_response([
            'success' => true,
            'reply' => $reply,
            'message' => [
                'text' => $reply,
                'images' => $reply_images,
            ],
            'provider' => (string) ($ai_result['provider'] ?? ''),
            'model' => (string) ($ai_result['model'] ?? ''),
            'retention' => [
                'max_messages' => $max_retention,
                'trimmed_rows' => $trimmed_rows,
            ],
        ]);
    }

    private function generate_ai_reply($id_session, $id_user, $prompt)
    {
        $data_context = $this->build_data_context($prompt);
        $grounded = $this->build_grounded_reply($prompt, $data_context);
        if (is_array($grounded) && !empty($grounded['text'])) {
            return [
                'text' => (string) $grounded['text'],
                'images' => isset($grounded['images']) && is_array($grounded['images']) ? $grounded['images'] : [],
                'provider' => 'internal-grounded',
                'model' => 'db-query-v2',
            ];
        }

        if (is_string($grounded) && $grounded !== '') {
            return [
                'text' => $grounded,
                'provider' => 'internal-grounded',
                'model' => 'db-query-v2',
            ];
        }

        $enabled = (string) app_config('ai_enabled', '0') === '1';
        $api_key = trim((string) app_config('ai_api_key', ''));
        $status = strtoupper(trim((string) app_config('ai_connection_status', 'DISCONNECTED')));
        $provider = strtolower(trim((string) app_config('ai_provider', 'groq')));
        $model = trim((string) app_config('ai_model', ''));

        if (!$enabled || $api_key === '' || $status !== 'CONNECTED') {
            return [
                'text' => 'AI belum aktif penuh. Cek konfigurasi AI (enabled, api key, dan status connected).',
                'provider' => $provider,
                'model' => $model,
            ];
        }

        if (!function_exists('curl_init')) {
            return [
                'text' => 'Server belum mendukung cURL, jadi AI belum bisa dihubungi.',
                'provider' => $provider,
                'model' => $model,
            ];
        }

        $context_rows = $this->Ai_chat_model->get_context_messages($id_session, $id_user, 12);
        $messages = [];
        $messages[] = [
            'role' => 'system',
            'content' => 'Anda adalah asisten admin payroll. Anda WAJIB mengutamakan DATA_KONTEKS yang diberikan sistem. Dilarang mengarang angka, nama pegawai, status, daftar, atau kesimpulan yang tidak ada di data. Jika data tidak tersedia, katakan tidak tersedia. Untuk pertanyaan cara, langkah, tutorial, atau menu mana, berikan panduan langkah demi langkah berdasarkan struktur menu aplikasi. Jangan menolak pertanyaan panduan hanya karena keterbatasan akses. Jika menu memang terbatas (misalnya manajemen pengguna), tetap jelaskan alur menunya dan sebutkan syarat role/otorisasi secara jelas.'
        ];
        $messages[] = [
            'role' => 'system',
            'content' => "DATA_KONTEKS_REALTIME:\n" . $data_context['context_text']
        ];

        foreach ($context_rows as $row) {
            $role = ((string) $row->role === 'assistant') ? 'assistant' : 'user';
            $messages[] = [
                'role' => $role,
                'content' => (string) $row->message_text,
            ];
        }

        if ($model === '') {
            if ($provider === 'groq') {
                $model = 'llama-3.1-8b-instant';
            } elseif ($provider === 'openai') {
                $model = 'gpt-4o-mini';
            } elseif ($provider === 'gemini') {
                $model = 'gemini-1.5-flash';
            } elseif ($provider === 'anthropic') {
                $model = 'claude-3-5-sonnet-20240620';
            }
        }

        $result = $this->request_provider_chat($provider, $model, $api_key, $messages);
        if (!(bool) ($result['success'] ?? false)) {
            return [
                'text' => 'Gagal menghubungi AI: ' . (string) ($result['message'] ?? 'Unknown error'),
                'provider' => $provider,
                'model' => $model,
            ];
        }

        return [
            'text' => (string) ($result['text'] ?? ''),
            'provider' => $provider,
            'model' => $model,
        ];
    }

    private function build_data_context($prompt)
    {
        $current_month = (int) date('n');
        $current_year = (int) date('Y');
        $prompt_text = strtolower(trim((string) $prompt));

        $active_employees = (int) $this->db
            ->from('tb_pegawai')
            ->where('status_pegawai', 'AKTIF')
            ->count_all_results();

        $inactive_employees = (int) $this->db
            ->from('tb_pegawai')
            ->where('status_pegawai !=', 'AKTIF')
            ->count_all_results();

        $total_divisi = $this->db->table_exists('tb_divisi')
            ? (int) $this->db->from('tb_divisi')->count_all_results()
            : 0;
        $total_jabatan = $this->db->table_exists('tb_jabatan')
            ? (int) $this->db->from('tb_jabatan')->count_all_results()
            : 0;
        $total_komponen = $this->db->table_exists('tb_komponen_gaji')
            ? (int) $this->db->from('tb_komponen_gaji')->where('is_active', 1)->count_all_results()
            : 0;
        $total_hari_libur = $this->db->table_exists('tb_hari_libur')
            ? (int) $this->db->from('tb_hari_libur')->where('YEAR(tanggal) =', $current_year, false)->count_all_results()
            : 0;

        $incomplete_total = (int) $this->db
            ->from('tb_pegawai')
            ->where('status_pegawai', 'AKTIF')
            ->group_start()
                ->where('tanggal_lahir IS NULL', null, false)
                ->or_where('alamat IS NULL', null, false)
                ->or_where("TRIM(COALESCE(alamat,'')) = ''", null, false)
            ->group_end()
            ->count_all_results();

        $incomplete_rows = $this->db
            ->select('id_pegawai, nip, nama_lengkap, tanggal_lahir, alamat')
            ->from('tb_pegawai')
            ->where('status_pegawai', 'AKTIF')
            ->group_start()
                ->where('tanggal_lahir IS NULL', null, false)
                ->or_where('alamat IS NULL', null, false)
                ->or_where("TRIM(COALESCE(alamat,'')) = ''", null, false)
            ->group_end()
            ->order_by('nip', 'ASC')
            ->limit(10)
            ->get()
            ->result();

        $pending_izin = (int) $this->db
            ->from('tb_izin_cuti')
            ->where('status', 'DRAFT')
            ->count_all_results();

        $pending_lembur = (int) $this->db
            ->from('tb_lembur')
            ->where('status', 'DRAFT')
            ->count_all_results();

        $pending_payroll_approval = (int) $this->db
            ->from('tb_payroll_periode')
            ->where('status', 'PROCESSED')
            ->count_all_results();

        $payroll_period_summary = [];
        if ($this->db->table_exists('tb_payroll_periode')) {
            $payroll_period_summary = $this->db
                ->select('id_periode, kode_periode, bulan, tahun, status, tipe_periode, tanggal_mulai, tanggal_selesai')
                ->from('tb_payroll_periode')
                ->order_by('tahun', 'DESC')
                ->order_by('bulan', 'DESC')
                ->limit(5)
                ->get()
                ->result();
        }

        $absensi_month_summary = [];
        if ($this->db->table_exists('tb_absensi')) {
            $absensi_month_summary = $this->db
                ->select('status, COUNT(*) AS total', false)
                ->from('tb_absensi')
                ->where('MONTH(tanggal)', $current_month)
                ->where('YEAR(tanggal)', $current_year)
                ->group_by('status')
                ->order_by('status', 'ASC')
                ->get()
                ->result();
        }

        $recent_izin = [];
        if ($this->db->table_exists('tb_izin_cuti')) {
            $recent_izin = $this->db
                ->select('ic.id_izin, ic.tipe, ic.status, ic.tanggal_mulai, ic.tanggal_selesai, pg.nip, pg.nama_lengkap', false)
                ->from('tb_izin_cuti ic')
                ->join('tb_pegawai pg', 'pg.id_pegawai = ic.id_pegawai', 'left')
                ->order_by('ic.created_at', 'DESC')
                ->limit(5)
                ->get()
                ->result();
        }

        $recent_lembur = [];
        if ($this->db->table_exists('tb_lembur')) {
            $recent_lembur = $this->db
                ->select('l.id_lembur, l.tanggal, l.durasi_jam, l.status, pg.nip, pg.nama_lengkap', false)
                ->from('tb_lembur l')
                ->join('tb_pegawai pg', 'pg.id_pegawai = l.id_pegawai', 'left')
                ->order_by('l.created_at', 'DESC')
                ->limit(5)
                ->get()
                ->result();
        }

        $matched_employees = $this->find_matching_employees($prompt_text);
        $matched_divisions = $this->find_matching_divisions($prompt_text);
        $matched_positions = $this->find_matching_positions($prompt_text);

        $payroll_month_row = $this->db
            ->select('SUM(p.gaji_neto) AS total_neto, COUNT(p.id_payroll) AS total_rows', false)
            ->from('tb_payroll p')
            ->join('tb_payroll_periode pp', 'pp.id_periode = p.id_periode', 'inner')
            ->where('pp.bulan', $current_month)
            ->where('pp.tahun', $current_year)
            ->where_in('pp.status', ['APPROVED', 'LOCKED'])
            ->get()
            ->row();

        $total_neto_month = (float) ($payroll_month_row && $payroll_month_row->total_neto !== null ? $payroll_month_row->total_neto : 0);
        $total_neto_rows = (int) ($payroll_month_row ? $payroll_month_row->total_rows : 0);

        $context_lines = [];
        $context_lines[] = 'AKSES_DATA: boleh memakai seluruh data operasional. Untuk menu terbatas (misalnya manajemen pengguna), AI tetap boleh memberi panduan langkah, tetapi tidak boleh mengklaim mengeksekusi perubahan.';
        $context_lines[] = 'Bulan berjalan: ' . $current_month . '/' . $current_year;
        $context_lines[] = 'Total pegawai aktif: ' . $active_employees;
        $context_lines[] = 'Total pegawai nonaktif/cuti panjang: ' . $inactive_employees;
        $context_lines[] = 'Master divisi: ' . $total_divisi . ' | master jabatan: ' . $total_jabatan . ' | komponen gaji aktif: ' . $total_komponen . ' | hari libur tahun ini: ' . $total_hari_libur;
        $context_lines[] = 'Pegawai aktif dengan data belum lengkap (alamat/tanggal lahir): ' . $incomplete_total;
        $context_lines[] = 'Pending approval izin/cuti (status DRAFT): ' . $pending_izin;
        $context_lines[] = 'Pending approval lembur (status DRAFT): ' . $pending_lembur;
        $context_lines[] = 'Pending approval payroll periode (status PROCESSED): ' . $pending_payroll_approval;
        $context_lines[] = 'Total pengeluaran payroll bulan berjalan (APPROVED/LOCKED): ' . $this->format_rupiah_local($total_neto_month) . ' | jumlah baris payroll: ' . $total_neto_rows;

        if (!empty($incomplete_rows)) {
            $row_lines = [];
            foreach ($incomplete_rows as $row) {
                $missing = [];
                if (empty($row->tanggal_lahir)) {
                    $missing[] = 'tanggal_lahir';
                }
                if (empty(trim((string) $row->alamat))) {
                    $missing[] = 'alamat';
                }
                $row_lines[] = trim((string) $row->nip) . ' - ' . trim((string) $row->nama_lengkap) . ' (kurang: ' . implode(', ', $missing) . ')';
            }
            $context_lines[] = 'Contoh daftar pegawai belum lengkap: ' . implode(' | ', $row_lines);
        } else {
            $context_lines[] = 'Contoh daftar pegawai belum lengkap: tidak ada.';
        }

        if (!empty($payroll_period_summary)) {
            $items = [];
            foreach ($payroll_period_summary as $row) {
                $items[] = trim((string) $row->kode_periode) . ' [' . trim((string) ($row->tipe_periode ?? 'REGULER')) . '] status=' . trim((string) $row->status);
            }
            $context_lines[] = 'Periode payroll terbaru: ' . implode(' | ', $items);
        }

        if (!empty($absensi_month_summary)) {
            $items = [];
            foreach ($absensi_month_summary as $row) {
                $items[] = trim((string) $row->status) . '=' . (int) $row->total;
            }
            $context_lines[] = 'Ringkasan absensi bulan berjalan: ' . implode(' | ', $items);
        }

        if (!empty($recent_izin)) {
            $items = [];
            foreach ($recent_izin as $row) {
                $items[] = trim((string) $row->nip) . ' - ' . trim((string) $row->nama_lengkap) . ' [' . trim((string) $row->tipe) . '/' . trim((string) $row->status) . ']';
            }
            $context_lines[] = 'Izin/cuti terbaru: ' . implode(' | ', $items);
        }

        if (!empty($recent_lembur)) {
            $items = [];
            foreach ($recent_lembur as $row) {
                $items[] = trim((string) $row->nip) . ' - ' . trim((string) $row->nama_lengkap) . ' [' . trim((string) $row->status) . ', ' . rtrim(rtrim((string) $row->durasi_jam, '0'), '.') . ' jam]';
            }
            $context_lines[] = 'Lembur terbaru: ' . implode(' | ', $items);
        }

        if (!empty($matched_employees)) {
            $items = [];
            foreach ($matched_employees as $row) {
                $items[] = trim((string) $row->nip) . ' - ' . trim((string) $row->nama_lengkap) . ' | ' . trim((string) ($row->nama_divisi ?? '-')) . ' | ' . trim((string) ($row->nama_jabatan ?? '-')) . ' | status=' . trim((string) $row->status_pegawai);
            }
            $context_lines[] = 'Pencocokan pegawai dari prompt: ' . implode(' | ', $items);
        }

        if (!empty($matched_divisions)) {
            $items = [];
            foreach ($matched_divisions as $row) {
                $items[] = trim((string) $row->nama_divisi);
            }
            $context_lines[] = 'Pencocokan divisi dari prompt: ' . implode(' | ', $items);
        }

        if (!empty($matched_positions)) {
            $items = [];
            foreach ($matched_positions as $row) {
                $items[] = trim((string) $row->nama_jabatan);
            }
            $context_lines[] = 'Pencocokan jabatan dari prompt: ' . implode(' | ', $items);
        }

        return [
            'active_employees' => $active_employees,
            'inactive_employees' => $inactive_employees,
            'incomplete_total' => $incomplete_total,
            'incomplete_rows' => $incomplete_rows,
            'pending_izin' => $pending_izin,
            'pending_lembur' => $pending_lembur,
            'pending_payroll_approval' => $pending_payroll_approval,
            'payroll_current_month_total_neto' => $total_neto_month,
            'payroll_current_month_rows' => $total_neto_rows,
            'payroll_period_summary' => $payroll_period_summary,
            'absensi_month_summary' => $absensi_month_summary,
            'recent_izin' => $recent_izin,
            'recent_lembur' => $recent_lembur,
            'matched_employees' => $matched_employees,
            'matched_divisions' => $matched_divisions,
            'matched_positions' => $matched_positions,
            'month' => $current_month,
            'year' => $current_year,
            'context_text' => implode("\n", $context_lines),
        ];
    }

    private function build_grounded_reply($prompt, array $context)
    {
        $text = strtolower(trim((string) $prompt));
        if ($text === '') {
            return '';
        }

        $tutorial_reply = $this->build_tutorial_reply($text);
        if ($tutorial_reply !== '') {
            return $tutorial_reply;
        }

        $ask_image_flow = (strpos($text, 'alur') !== false || strpos($text, 'fitur') !== false)
            && (strpos($text, 'gambar') !== false || strpos($text, 'image') !== false || strpos($text, 'penggajian') !== false || strpos($text, 'tampilkan') !== false || strpos($text, 'lihat') !== false);
        if ($ask_image_flow) {
            return [
                'text' => 'Saya tampilkan gambar alur dan fitur penggajian dari folder assets. Klik gambar untuk membuka popup ukuran lebih besar.',
                'images' => [
                    [
                        'url' => base_url('assets/penggajian-alur.jpg'),
                        'title' => 'Alur Penggajian',
                    ],
                    [
                        'url' => base_url('assets/penggajian-fitur.jpg'),
                        'title' => 'Fitur Penggajian',
                    ],
                ],
            ];
        }

        $ask_employee_list = (strpos($text, 'pegawai') !== false)
            && (strpos($text, 'siapa saja') !== false || strpos($text, 'daftar') !== false || strpos($text, 'list') !== false || strpos($text, 'cari') !== false || strpos($text, 'ada') !== false);
        if ($ask_employee_list) {
            $matches = $context['matched_employees'] ?? [];
            if (!empty($matches)) {
                $items = [];
                foreach ($matches as $row) {
                    $items[] = trim((string) $row->nip) . ' - ' . trim((string) $row->nama_lengkap) . ' | ' . trim((string) ($row->nama_divisi ?? '-')) . ' | ' . trim((string) ($row->nama_jabatan ?? '-')) . ' | status ' . trim((string) $row->status_pegawai);
                }
                return 'Saya menemukan pegawai yang cocok dari data operasional saat ini:' . "\n- " . implode("\n- ", $items);
            }

            return 'Saya belum menemukan pegawai yang cocok dari kata kunci tersebut pada data operasional saat ini. Silakan coba dengan nama atau NIP yang berbeda.';
        }

        $ask_incomplete = (strpos($text, 'belum lengkap') !== false && strpos($text, 'pegawai') !== false)
            || (strpos($text, 'kekurang') !== false && strpos($text, 'pegawai') !== false);
        if ($ask_incomplete) {
            $total = (int) ($context['incomplete_total'] ?? 0);
            $rows = $context['incomplete_rows'] ?? [];

            if ($total <= 0) {
                return 'Berdasarkan data saat ini, tidak ada pegawai aktif yang kekurangan data alamat atau tanggal lahir.';
            }

            $items = [];
            foreach ($rows as $row) {
                $missing = [];
                if (empty($row->tanggal_lahir)) {
                    $missing[] = 'tanggal lahir';
                }
                if (empty(trim((string) $row->alamat))) {
                    $missing[] = 'alamat';
                }
                $items[] = trim((string) $row->nip) . ' - ' . trim((string) $row->nama_lengkap) . ' (kurang: ' . implode(', ', $missing) . ')';
            }

            $reply = 'Berdasarkan data real saat ini, ada ' . $total . ' pegawai aktif dengan data belum lengkap (alamat/tanggal lahir).';
            if (!empty($items)) {
                $reply .= "\nContoh (maks 10):\n- " . implode("\n- ", $items);
            }
            if ($total > count($items)) {
                $reply .= "\nMasih ada " . ($total - count($items)) . ' pegawai lainnya.';
            }
            return $reply;
        }

        $ask_total_pengeluaran = (strpos($text, 'total pengeluaran') !== false)
            || (strpos($text, 'pengeluaran') !== false && strpos($text, 'bulan') !== false)
            || (strpos($text, 'total payroll') !== false);
        if ($ask_total_pengeluaran) {
            $month = (int) ($context['month'] ?? (int) date('n'));
            $year = (int) ($context['year'] ?? (int) date('Y'));
            $total_neto = (float) ($context['payroll_current_month_total_neto'] ?? 0);
            $rows = (int) ($context['payroll_current_month_rows'] ?? 0);

            if ($rows <= 0) {
                return 'Belum ada data payroll APPROVED/LOCKED untuk bulan ' . $month . '/' . $year . ', jadi total pengeluaran belum bisa dihitung dari data final.';
            }

            return 'Total pengeluaran payroll bulan ' . $month . '/' . $year . ' (berdasarkan data APPROVED/LOCKED) adalah ' . $this->format_rupiah_local($total_neto) . ' dari ' . $rows . ' baris payroll.';
        }

        $ask_pending_izin = (strpos($text, 'pending') !== false && strpos($text, 'izin') !== false)
            || (strpos($text, 'izin/cuti') !== false);
        if ($ask_pending_izin) {
            return 'Pending approval izin/cuti (status DRAFT) saat ini: ' . (int) ($context['pending_izin'] ?? 0) . ' data.';
        }

        $ask_pending_lembur = (strpos($text, 'pending') !== false && strpos($text, 'lembur') !== false);
        if ($ask_pending_lembur) {
            return 'Pending approval lembur (status DRAFT) saat ini: ' . (int) ($context['pending_lembur'] ?? 0) . ' data.';
        }

        $ask_status_payroll = (strpos($text, 'status payroll') !== false)
            || (strpos($text, 'payroll aktif') !== false);
        if ($ask_status_payroll) {
            $pending = (int) ($context['pending_payroll_approval'] ?? 0);
            if ($pending > 0) {
                return 'Status payroll: ada ' . $pending . ' periode payroll berstatus PROCESSED yang menunggu approval.';
            }
            return 'Status payroll: tidak ada periode berstatus PROCESSED yang menunggu approval saat ini.';
        }

        $ask_employee_list = (strpos($text, 'pegawai') !== false)
            && (strpos($text, 'siapa saja') !== false || strpos($text, 'daftar') !== false || strpos($text, 'list') !== false || strpos($text, 'cari') !== false || strpos($text, 'ada') !== false);
        if ($ask_employee_list) {
            $matches = $context['matched_employees'] ?? [];
            if (!empty($matches)) {
                $items = [];
                foreach ($matches as $row) {
                    $items[] = trim((string) $row->nip) . ' - ' . trim((string) $row->nama_lengkap) . ' | ' . trim((string) ($row->nama_divisi ?? '-')) . ' | ' . trim((string) ($row->nama_jabatan ?? '-')) . ' | status ' . trim((string) $row->status_pegawai);
                }
                return 'Saya menemukan pegawai yang cocok dari data operasional saat ini:' . "\n- " . implode("\n- ", $items);
            }

            return 'Saya belum menemukan pegawai yang cocok dari kata kunci tersebut pada data operasional saat ini.';
        }

        $ask_division = strpos($text, 'divisi') !== false;
        if ($ask_division) {
            $matches = $context['matched_divisions'] ?? [];
            if (!empty($matches)) {
                $items = [];
                foreach ($matches as $row) {
                    $items[] = trim((string) $row->nama_divisi);
                }
                return 'Divisi yang cocok dari data master saat ini:' . "\n- " . implode("\n- ", $items);
            }
        }

        $ask_position = strpos($text, 'jabatan') !== false;
        if ($ask_position) {
            $matches = $context['matched_positions'] ?? [];
            if (!empty($matches)) {
                $items = [];
                foreach ($matches as $row) {
                    $items[] = trim((string) $row->nama_jabatan);
                }
                return 'Jabatan yang cocok dari data master saat ini:' . "\n- " . implode("\n- ", $items);
            }
        }

        $ask_absensi = strpos($text, 'absensi') !== false;
        if ($ask_absensi && !empty($context['absensi_month_summary'])) {
            $items = [];
            foreach ($context['absensi_month_summary'] as $row) {
                $items[] = trim((string) $row->status) . ': ' . (int) $row->total;
            }
            return 'Ringkasan absensi bulan berjalan berdasarkan data real:' . "\n- " . implode("\n- ", $items);
        }

        $ask_recent_izin = strpos($text, 'izin') !== false || strpos($text, 'cuti') !== false;
        if ($ask_recent_izin && !empty($context['recent_izin'])) {
            $items = [];
            foreach ($context['recent_izin'] as $row) {
                $items[] = trim((string) $row->nip) . ' - ' . trim((string) $row->nama_lengkap) . ' | ' . trim((string) $row->tipe) . ' | ' . trim((string) $row->status) . ' | ' . trim((string) $row->tanggal_mulai) . ' s/d ' . trim((string) $row->tanggal_selesai);
            }
            return 'Data izin/cuti terbaru dari sistem:' . "\n- " . implode("\n- ", $items);
        }

        $ask_recent_lembur = strpos($text, 'lembur') !== false;
        if ($ask_recent_lembur && !empty($context['recent_lembur'])) {
            $items = [];
            foreach ($context['recent_lembur'] as $row) {
                $items[] = trim((string) $row->nip) . ' - ' . trim((string) $row->nama_lengkap) . ' | ' . trim((string) $row->status) . ' | ' . trim((string) $row->tanggal) . ' | ' . rtrim(rtrim((string) $row->durasi_jam, '0'), '.') . ' jam';
            }
            return 'Data lembur terbaru dari sistem:' . "\n- " . implode("\n- ", $items);
        }

        return '';
    }

    private function build_tutorial_reply($text)
    {
        $is_howto = (strpos($text, 'bagaimana') !== false)
            || (strpos($text, 'cara') !== false)
            || (strpos($text, 'langkah') !== false)
            || (strpos($text, 'panduan') !== false)
            || (strpos($text, 'tutorial') !== false)
            || (strpos($text, 'menu mana') !== false)
            || (strpos($text, 'di menu mana') !== false);

        $is_create_action = (strpos($text, 'tambah') !== false)
            || (strpos($text, 'menambah') !== false)
            || (strpos($text, 'input') !== false)
            || (strpos($text, 'buat ') !== false)
            || (strpos($text, 'membuat') !== false)
            || (strpos($text, 'simpan data') !== false);

        if (!$is_howto && !$is_create_action) {
            return '';
        }

        $is_followup_after = (strpos($text, 'setelah') !== false)
            || (strpos($text, 'sesudah') !== false)
            || (strpos($text, 'berikutnya') !== false)
            || (strpos($text, 'selanjutnya') !== false)
            || (strpos($text, 'langkah berikut') !== false)
            || (strpos($text, 'habis') !== false);

        if ($is_followup_after && strpos($text, 'pegawai') !== false) {
            return "Alur setelah menambahkan pegawai biasanya seperti ini:\n"
                . "1. Cek detail pegawai di Data Pegawai untuk memastikan data wajib sudah lengkap (NIP, Divisi, Jabatan, Tanggal Masuk).\n"
                . "2. Atur komponen gaji pegawai di MASTER DATA > PARAMETER PENGGAJIAN > Mapping Gaji (pilih pegawai lalu set tunjangan/potongan yang berlaku).\n"
                . "3. Jika pakai sistem shift, atur jadwal di MASTER DATA > MANAJEMEN PEGAWAI > Jadwal Kerja.\n"
                . "4. Mulai input transaksi periode berjalan: absensi, izin/cuti, dan lembur pada menu transaksional.\n"
                . "5. Saat periode siap, buka SISTEM PAYROLL > buat/proses periode payroll, lalu cek detail hasil per pegawai.\n"
                . "6. Jika hasil sudah benar, lakukan approval/lock payroll dan lanjutkan ke slip/laporan.\n"
                . "Urutan singkatnya: Tambah Pegawai -> Mapping Gaji -> Absensi/Izin/Lembur -> Proses Payroll -> Approval/Lock -> Slip/Laporan.";
        }

        if (strpos($text, 'pegawai') !== false) {
            return "Langkah menambahkan data pegawai:\n"
                . "1. Buka menu MASTER DATA > MANAJEMEN PEGAWAI > Data Pegawai.\n"
                . "2. Klik tombol Tambah Pegawai.\n"
                . "3. Isi bagian Identitas Dasar (minimal NIP, Nama Lengkap, Jenis Kelamin, dan Tanggal Masuk).\n"
                . "4. Isi Organisasi & Penggajian (Divisi, Jabatan, dan Status PTKP).\n"
                . "5. Lengkapi data penunjang bila ada (BPJS, rekening, pajak), lalu klik Tambah Pegawai/Simpan.\n"
                . "6. Cek kembali di daftar Data Pegawai untuk memastikan data sudah masuk.\n"
                . "Tips: jika data banyak, gunakan menu Import Pegawai dari halaman Data Pegawai agar lebih cepat.";
        }

        if (strpos($text, 'divisi') !== false) {
            return "Langkah menambahkan divisi:\n"
                . "1. Buka MASTER DATA > MANAJEMEN ORGANISASI > Data Divisi.\n"
                . "2. Klik tombol Tambah Divisi.\n"
                . "3. Isi nama divisi dan keterangan yang diperlukan.\n"
                . "4. Klik Simpan, lalu cek divisi baru di daftar.";
        }

        if (strpos($text, 'jabatan') !== false) {
            return "Langkah menambahkan jabatan:\n"
                . "1. Buka MASTER DATA > MANAJEMEN ORGANISASI > Data Jabatan.\n"
                . "2. Klik Tambah Jabatan.\n"
                . "3. Pilih divisi terkait, isi nama jabatan, lalu Simpan.\n"
                . "4. Pastikan jabatan muncul di daftar dan bisa dipilih saat input pegawai.";
        }

        if (strpos($text, 'absensi') !== false) {
            return "Langkah input absensi:\n"
                . "1. Buka MENU TRANSAKSIONAL > MANAJEMEN ABSENSI.\n"
                . "2. Klik Tambah untuk input manual, atau gunakan menu Import untuk data massal.\n"
                . "3. Isi periode/tanggal, pilih pegawai, isi status absensi, lalu Simpan.\n"
                . "4. Verifikasi data pada daftar absensi sebelum proses payroll.";
        }

        if ((strpos($text, 'izin') !== false) || (strpos($text, 'cuti') !== false)) {
            return "Langkah menambahkan izin/cuti:\n"
                . "1. Buka MENU TRANSAKSIONAL > MANAJEMEN IZIN.\n"
                . "2. Klik Tambah Izin/Cuti.\n"
                . "3. Pilih pegawai, tipe izin/cuti, tanggal mulai-selesai, lalu isi catatan bila perlu.\n"
                . "4. Simpan data, lalu lakukan approval sesuai alur di sistem.";
        }

        if (strpos($text, 'lembur') !== false) {
            return "Langkah menambahkan lembur:\n"
                . "1. Buka MENU TRANSAKSIONAL > MANAJEMEN LEMBUR.\n"
                . "2. Klik Tambah Lembur.\n"
                . "3. Pilih pegawai, isi tanggal, durasi jam, dan keterangan lembur.\n"
                . "4. Simpan, lalu lanjutkan proses approval agar masuk perhitungan payroll.";
        }

        if ((strpos($text, 'payroll') !== false) || (strpos($text, 'penggajian') !== false)) {
            return "Langkah membuat periode payroll:\n"
                . "1. Buka MENU TRANSAKSIONAL > SISTEM PAYROLL.\n"
                . "2. Klik Tambah Periode Payroll.\n"
                . "3. Isi bulan, tahun, tipe periode, dan rentang tanggal periode.\n"
                . "4. Simpan periode, lalu lakukan Proses untuk generate payroll.\n"
                . "5. Cek detail hasil, kemudian lanjutkan approval/lock jika sudah final.";
        }

        if ((strpos($text, 'komponen') !== false) && (strpos($text, 'gaji') !== false || strpos($text, 'payroll') !== false)) {
            return "Langkah menambahkan komponen gaji:\n"
                . "1. Buka MASTER DATA > PARAMETER PENGGAJIAN > Komponen Gaji.\n"
                . "2. Klik Tambah Komponen.\n"
                . "3. Isi nama komponen, tipe (tunjangan/potongan), metode hitung, dan formula bila diperlukan.\n"
                . "4. Simpan komponen, lalu map ke pegawai di menu Mapping Gaji.";
        }

        if ((strpos($text, 'pengguna') !== false) || (strpos($text, 'user') !== false) || (strpos($text, 'akun') !== false)) {
            return "Langkah menambahkan user aplikasi:\n"
                . "1. Buka PENGATURAN > MANAJEMEN PENGGUNA.\n"
                . "2. Klik Tambah Pengguna.\n"
                . "3. Isi nama, username, role, dan data akun lainnya.\n"
                . "4. Simpan, lalu uji login user baru.\n"
                . "Catatan: menu Manajemen Pengguna hanya tampil untuk role SUPERADMIN.";
        }

        if (strpos($text, 'konfigurasi') !== false) {
            return "Langkah update konfigurasi sistem:\n"
                . "1. Buka PENGATURAN > KONFIGURASI SISTEM.\n"
                . "2. Ubah parameter yang dibutuhkan (misalnya pengaturan payroll/AI).\n"
                . "3. Klik Simpan konfigurasi.\n"
                . "4. Lakukan uji fungsi terkait agar perubahan tervalidasi.";
        }

        if ($is_howto) {
            return 'Saya bisa bantu panduan langkah per menu. Sebutkan data apa yang ingin ditambahkan (misalnya: pegawai, divisi, jabatan, absensi, izin/cuti, lembur, payroll, komponen gaji, atau pengguna).';
        }

        return '';
    }

    private function find_matching_employees($prompt_text)
    {
        if (!$this->db->table_exists('tb_pegawai')) {
            return [];
        }

        $keywords = $this->extract_search_keywords($prompt_text);
        if (empty($keywords) && strpos($prompt_text, 'pegawai') === false) {
            return [];
        }

        $this->db
            ->select('pg.id_pegawai, pg.nip, pg.nama_lengkap, pg.status_pegawai, d.nama_divisi, j.nama_jabatan', false)
            ->from('tb_pegawai pg')
            ->join('tb_divisi d', 'd.id_divisi = pg.id_divisi', 'left')
            ->join('tb_jabatan j', 'j.id_jabatan = pg.id_jabatan', 'left');

        if (!empty($keywords)) {
            $this->db->group_start();
            foreach ($keywords as $keyword) {
                $this->db
                    ->or_like('pg.nama_lengkap', $keyword, 'both')
                    ->or_like('pg.nip', $keyword, 'both');
            }
            $this->db->group_end();
        }

        return $this->db
            ->order_by('pg.nama_lengkap', 'ASC')
            ->limit(8)
            ->get()
            ->result();
    }

    private function find_matching_divisions($prompt_text)
    {
        if (!$this->db->table_exists('tb_divisi') || strpos($prompt_text, 'divisi') === false) {
            return [];
        }

        $keywords = $this->extract_search_keywords($prompt_text);
        $this->db->select('id_divisi, nama_divisi')->from('tb_divisi');
        if (!empty($keywords)) {
            $this->db->group_start();
            foreach ($keywords as $keyword) {
                $this->db->or_like('nama_divisi', $keyword, 'both');
            }
            $this->db->group_end();
        }

        return $this->db->order_by('nama_divisi', 'ASC')->limit(8)->get()->result();
    }

    private function find_matching_positions($prompt_text)
    {
        if (!$this->db->table_exists('tb_jabatan') || strpos($prompt_text, 'jabatan') === false) {
            return [];
        }

        $keywords = $this->extract_search_keywords($prompt_text);
        $this->db->select('id_jabatan, nama_jabatan')->from('tb_jabatan');
        if (!empty($keywords)) {
            $this->db->group_start();
            foreach ($keywords as $keyword) {
                $this->db->or_like('nama_jabatan', $keyword, 'both');
            }
            $this->db->group_end();
        }

        return $this->db->order_by('nama_jabatan', 'ASC')->limit(8)->get()->result();
    }

    private function extract_search_keywords($prompt_text)
    {
        $clean = preg_replace('/[^a-z0-9\s]/', ' ', (string) $prompt_text);
        $parts = preg_split('/\s+/', trim((string) $clean));
        $stop_words = [
            'siapa', 'saja', 'yang', 'dan', 'atau', 'untuk', 'dari', 'pada', 'dengan', 'ada', 'cari', 'tampilkan',
            'lihat', 'cek', 'status', 'data', 'pegawai', 'divisi', 'jabatan', 'penggajian', 'payroll', 'alur', 'fitur',
            'gambar', 'image', 'menu', 'bulan', 'ini', 'aktif', 'pending', 'approval', 'ringkasan', 'periode', 'real',
            'apakah', 'apa', 'siapa', 'mana', 'berapa', 'nama', 'nip', 'adalah', 'bagaimana', 'di', 'ke', 'ke'
        ];

        $keywords = [];
        foreach ($parts as $part) {
            // Allow 2+ character keywords for better matching (not just 3+)
            if (strlen($part) < 2) {
                continue;
            }
            if (in_array($part, $stop_words, true)) {
                continue;
            }
            $keywords[] = $part;
        }

        return array_values(array_unique($keywords));
    }

    private function encode_chat_message_payload($text, array $images = [])
    {
        if (empty($images)) {
            return (string) $text;
        }

        return self::CHAT_PAYLOAD_PREFIX . json_encode([
            'text' => (string) $text,
            'images' => array_values($images),
        ]);
    }

    private function decode_chat_message_payload($message_text)
    {
        $default = [
            'text' => (string) $message_text,
            'images' => [],
        ];

        $prefix = self::CHAT_PAYLOAD_PREFIX;
        if (strpos((string) $message_text, $prefix) !== 0) {
            return $default;
        }

        $json = substr((string) $message_text, strlen($prefix));
        $decoded = json_decode($json, true);
        if (!is_array($decoded)) {
            return $default;
        }

        return [
            'text' => (string) ($decoded['text'] ?? ''),
            'images' => isset($decoded['images']) && is_array($decoded['images']) ? $decoded['images'] : [],
        ];
    }

    private function format_rupiah_local($number)
    {
        if (function_exists('rupiah')) {
            return 'Rp ' . rupiah($number);
        }

        return 'Rp ' . number_format((float) $number, 0, ',', '.');
    }

    private function request_provider_chat($provider, $model, $api_key, array $messages)
    {
        if ($provider === 'groq' || $provider === 'openai') {
            $url = $provider === 'groq'
                ? 'https://api.groq.com/openai/v1/chat/completions'
                : 'https://api.openai.com/v1/chat/completions';

            $payload = [
                'model' => $model,
                'messages' => $messages,
                'temperature' => 0.2,
            ];

            $res = $this->curl_json('POST', $url, [
                'Authorization: Bearer ' . $api_key,
                'Content-Type: application/json',
            ], $payload);

            if (!(bool) ($res['success'] ?? false)) {
                return $res;
            }

            $text = (string) ($res['response']['choices'][0]['message']['content'] ?? '');
            return [
                'success' => $text !== '',
                'text' => $text !== '' ? $text : 'AI tidak mengembalikan konten jawaban.',
                'message' => $text !== '' ? 'OK' : 'Empty content',
            ];
        }

        if ($provider === 'gemini') {
            $url = 'https://generativelanguage.googleapis.com/v1beta/models/' . rawurlencode($model) . ':generateContent?key=' . rawurlencode($api_key);
            $combined = [];
            foreach ($messages as $m) {
                $combined[] = strtoupper((string) $m['role']) . ': ' . (string) $m['content'];
            }
            $payload = [
                'contents' => [[
                    'parts' => [[
                        'text' => implode("\n\n", $combined)
                    ]]
                ]]
            ];

            $res = $this->curl_json('POST', $url, ['Content-Type: application/json'], $payload);
            if (!(bool) ($res['success'] ?? false)) {
                return $res;
            }

            $text = (string) ($res['response']['candidates'][0]['content']['parts'][0]['text'] ?? '');
            return [
                'success' => $text !== '',
                'text' => $text !== '' ? $text : 'AI tidak mengembalikan konten jawaban.',
                'message' => $text !== '' ? 'OK' : 'Empty content',
            ];
        }

        if ($provider === 'anthropic') {
            $url = 'https://api.anthropic.com/v1/messages';
            $anthropic_msgs = [];
            foreach ($messages as $m) {
                if ($m['role'] === 'system') {
                    continue;
                }
                $anthropic_msgs[] = [
                    'role' => $m['role'] === 'assistant' ? 'assistant' : 'user',
                    'content' => (string) $m['content'],
                ];
            }

            $payload = [
                'model' => $model,
                'max_tokens' => 600,
                'messages' => $anthropic_msgs,
            ];

            $res = $this->curl_json('POST', $url, [
                'x-api-key: ' . $api_key,
                'anthropic-version: 2023-06-01',
                'Content-Type: application/json',
            ], $payload);

            if (!(bool) ($res['success'] ?? false)) {
                return $res;
            }

            $text = (string) ($res['response']['content'][0]['text'] ?? '');
            return [
                'success' => $text !== '',
                'text' => $text !== '' ? $text : 'AI tidak mengembalikan konten jawaban.',
                'message' => $text !== '' ? 'OK' : 'Empty content',
            ];
        }

        return [
            'success' => false,
            'message' => 'Provider AI tidak didukung.',
        ];
    }

    private function curl_json($method, $url, array $headers, array $payload)
    {
        $ch = curl_init($url);
        if ($ch === false) {
            return ['success' => false, 'message' => 'Gagal inisialisasi cURL.'];
        }

        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_CONNECTTIMEOUT => 12,
            CURLOPT_TIMEOUT => 40,
            CURLOPT_CUSTOMREQUEST => strtoupper($method),
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_POSTFIELDS => json_encode($payload),
        ]);

        $raw = curl_exec($ch);
        $errno = curl_errno($ch);
        $error = curl_error($ch);
        $http_code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($errno !== 0) {
            return ['success' => false, 'message' => 'Koneksi gagal: ' . $error];
        }

        $decoded = json_decode((string) $raw, true);
        if ($http_code >= 200 && $http_code < 300) {
            return ['success' => true, 'response' => is_array($decoded) ? $decoded : []];
        }

        $message = 'HTTP ' . $http_code . ' dari provider AI.';
        if (is_array($decoded)) {
            if (!empty($decoded['error']['message'])) {
                $message = (string) $decoded['error']['message'];
            } elseif (!empty($decoded['message'])) {
                $message = (string) $decoded['message'];
            }
        }

        return [
            'success' => false,
            'message' => $message,
            'response' => is_array($decoded) ? $decoded : [],
        ];
    }

    private function format_session($row)
    {
        if (!$row) {
            return null;
        }

        return [
            'id' => (int) $row->id_session,
            'title' => (string) $row->judul,
            'last_message_at' => (string) $row->last_message_at,
            'created_at' => (string) $row->created_at,
            'updated_at' => (string) $row->updated_at,
        ];
    }

    private function json_response($payload, $status_code = 200)
    {
        $this->output
            ->set_status_header($status_code)
            ->set_content_type('application/json', 'utf-8')
            ->set_output(json_encode($payload));
        return;
    }
}
