from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.contrib import messages
from django.http import JsonResponse, HttpResponse
import csv
import requests
import io as _io
from django.utils import timezone
from django.utils.dateparse import parse_datetime
from django.views.decorators.http import require_POST
from django.core.mail import send_mail, EmailMultiAlternatives
from django.template.loader import render_to_string
from email.mime.image import MIMEImage
import qrcode
import io
import uuid
from django.db.models import Count, Sum
from django.urls import reverse
from django.db.models import Avg, Count
from .models import ContributionFile

from .models import Bengkel, Jemputan, Kehadiran, PenganjurRequest, UserProfile, ContributionFile
from django.utils.translation import gettext as _

# ==============================================================================
# FUNGSI BANTUAN (HELPER): AI ENGINE (GEMINI)
# ==============================================================================
def panggil_ai_dwi_enjin(prompt, guna_json_format=False, enforce_language=None):
    """
    Call Google Gemini for AI tasks. Gemini-only implementation for optimization.
    """
    from django.conf import settings
    
    # Perlindungan Format JSON & Penetapan Bahasa Dinamik
    if guna_json_format:
        prompt += "\n\n[AMARAN SISTEM]: Kekalkan struktur kunci (keys) JSON di dalam format asal sepertimana contoh yang diberikan. JANGAN terjemah keys JSON tersebut ke bahasa lain untuk mengelakkan ralat pangkalan data."
        
    if enforce_language:
        prompt += f"\n\n[ARAHAN BAHASA]: Anda WAJIB memberikan nilai/jawapan (values) dan penerangan di dalam bahasa {enforce_language}."
    
    try:
        if getattr(settings, 'OPENAI_API_KEY', None):
            from openai import OpenAI
            client = OpenAI(api_key=settings.OPENAI_API_KEY)
            
            # Ciri magis OpenAI: JSON Mode
            kwargs = {
                "model": getattr(settings, 'LLM_MODEL', 'gpt-4o-mini'),
                "messages": [{"role": "user", "content": prompt}]
            }
            # KITA TUTUP PAKSAAN JSON OBJECT SUPAYA AI BOLEH PULANGKAN ARRAY [...]
            # if guna_json_format:
            #     kwargs["response_format"] = {"type": "json_object"}
                
            resp = client.chat.completions.create(**kwargs)
            
            if resp.choices[0].message.content:
                print(f"🚀 [AI] Berjaya menggunakan OpenAI ({kwargs['model']}).")
                return resp.choices[0].message.content.strip()
    except Exception as e:
        print(f"❌ [AI] Ralat OpenAI: {e}")
        
    return None

# Tambahkan ini di bawah fungsi panggil_ai_dwi_enjin sedia ada

def proses_metrik_dokumen_ai(bengkel_pk):
    """
    Membaca setiap fail peserta untuk bengkel, menganalisisnya dengan AI, 
    dan mengemas kini metrik (Novelty, Actionability, dll) terus ke pangkalan data.
    """
    from .models import Bengkel, ContributionFile
    import json
    import re
    
    bengkel = Bengkel.objects.get(pk=bengkel_pk)
    # Cari fail-fail rujukan yang belum dianalisis (atau yang temanya kosong)
    fail_belum_proses = ContributionFile.objects.filter(contribution__bengkel=bengkel, tema_utama__isnull=True)
    
    if not fail_belum_proses.exists():
        # Jika semua dah ada tema, kita ambil semua untuk paksa analisis semula
        fail_belum_proses = ContributionFile.objects.filter(contribution__bengkel=bengkel)
        
    for cf in fail_belum_proses:
        import os
        from django.conf import settings
        
        # Ekstrak teks dari fail fizikal
        abs_path = os.path.join(settings.MEDIA_ROOT, cf.file.name)
        teks_dokumen = _extract_text_from_file(abs_path)
        
        if not teks_dokumen.strip():
            cf.tema_utama = "Tiada Teks Dijumpai"
            cf.save()
            continue
            
        # Potong teks kepada saiz yang selesa untuk AI (contoh 8000 aksara)
        teks_potong = teks_dokumen[:8000]
        
        # Prompt Khas untuk memetakan ke Papan Pemuka Penganjur
        prompt = f"""
        Anda adalah Penganalisis Korpus Dokumen KKM. 
        TUGASAN: Baca dokumen di bawah dan nilaikan tahap kognitif, kebolehgunaan, dan tema utamanya.
        
        KANDUNGAN DOKUMEN:
        {teks_potong}
        
        ARAHAN PENILAIAN (WAJIB IKUT):
        1. "tema_utama": Berikan SATU kata kunci atau kategori yang paling dominan (Cth: Infrastruktur IT, Latihan Staf, Polisi Privasi, Kewangan).
        2. "nada_dokumen": Pilih satu sahaja (Positif / Negatif / Neutral).
        3. "kompleksiti": Pilih satu sahaja (Sistem Holistik / Linear Silo / Berwawasan).
        4. "skor_novelty": Berikan markah dari 1.0 hingga 10.0 berdasarkan sejauh mana info ini unik (bukan sekadar copy-paste definisi internet).
        5. "indeks_tindakan": Berikan markah peratusan 0 hingga 100 berdasarkan sama ada dokumen ini banyak memberi cadangan praktikal untuk bertindak.
        
        KEMBALIKAN OUTPUT FORMAT JSON SAHAJA:
        {{
          "tema_utama": "Tema Pendek",
          "nada_dokumen": "Neutral",
          "kompleksiti": "Sistem Holistik",
          "skor_novelty": 7.5,
          "indeks_tindakan": 60
        }}
        """
        
        jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
        
        if jawapan_ai:
            try:
                # Bersihkan JSON dari tag markdown jika ada
                match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
                json_str = match.group(0) if match else jawapan_ai
                data_json = json.loads(json_str)
                
                # Update Database! Ini yang akan menghidupkan graf anda.
                cf.tema_utama = data_json.get("tema_utama", "Lain-lain")[:100]
                cf.nada_dokumen = data_json.get("nada_dokumen", "Neutral")[:50]
                cf.kompleksiti_kognitif = data_json.get("kompleksiti", "Linear Silo")[:50]
                cf.skor_novelty = float(data_json.get("skor_novelty", 0.0))
                cf.indeks_kebolehtindakan = int(data_json.get("indeks_tindakan", 0))
                cf.save()
                
            except Exception as e:
                print(f"Ralat AI Metrik Dokumen: {e}")
                cf.tema_utama = "Ralat Analisis"
                cf.save()


def _home_url(user):
    """Return the correct home URL for a given user based on role."""
    if user.is_superuser:
        return reverse("superadmin:dashboard")
    if user.is_staff:
        return reverse("bengkel:penganjur_home")
    return reverse("bengkel:dashboard")


# ── Helpers ──────────────────────────────────────────────────────────────────

def _bengkel_form_save(request, bengkel=None):
    """Extract POST data and save Bengkel. Returns (instance, errors)."""
    errors = {}
    title         = request.POST.get("title", "").strip()
    description   = request.POST.get("description", "").strip()
    tarikh_raw    = request.POST.get("tarikh", "").strip()
    tarikh_tamat_raw = request.POST.get("tarikh_tamat", "").strip()
    lokasi_nama   = request.POST.get("lokasi_nama", "").strip()
    lokasi_alamat = request.POST.get("lokasi_alamat", "").strip()
    org_nama      = request.POST.get("organizer_nama", "").strip()
    org_email     = request.POST.get("organizer_email", "").strip()
    org_tel       = request.POST.get("organizer_telefon", "").strip()
    vid_ucapan    = request.POST.get("video_ucapan_url", "").strip()
    vid_arah      = request.POST.get("video_arah_url", "").strip()
    had           = request.POST.get("had_peserta", "0").strip() or "0"
    
    jenis_acara   = request.POST.get("jenis_acara", "Bengkel").strip()
    privasi       = request.POST.get("privasi", "Tertutup").strip()

    if not title:        errors["title"]          = "Tajuk wajib diisi."
    if not tarikh_raw:   errors["tarikh"]         = "Tarikh & masa mula wajib diisi."
    if not lokasi_nama:  errors["lokasi_nama"]    = "Nama lokasi wajib diisi."
    if not org_nama:     errors["organizer_nama"] = "Nama penganjur wajib diisi."

    # Parse datetime-local strings (format: "YYYY-MM-DDTHH:MM") into aware datetimes
    tarikh_dt = None
    if tarikh_raw and "tarikh" not in errors:
        dt = parse_datetime(tarikh_raw)
        if dt is None:
            errors["tarikh"] = "Format tarikh tidak sah."
        else:
            tarikh_dt = timezone.make_aware(dt) if timezone.is_naive(dt) else dt

    tarikh_tamat_dt = None
    if tarikh_tamat_raw:
        dt2 = parse_datetime(tarikh_tamat_raw)
        if dt2 is not None:
            tarikh_tamat_dt = timezone.make_aware(dt2) if timezone.is_naive(dt2) else dt2

    if errors:
        return None, errors, {
            "title": title, "description": description,
            "tarikh": tarikh_raw, "tarikh_tamat": tarikh_tamat_raw,
            "lokasi_nama": lokasi_nama, "lokasi_alamat": lokasi_alamat,
            "organizer_nama": org_nama, "organizer_email": org_email,
            "organizer_telefon": org_tel, "video_ucapan_url": vid_ucapan,
            "video_arah_url": vid_arah, "had_peserta": had,
        }

    try:
        had_int = int(had)
    except ValueError:
        had_int = 0

    if bengkel is None:
        bengkel = Bengkel(created_by=request.user)

    bengkel.title             = title
    bengkel.jenis_acara       = jenis_acara
    bengkel.privasi           = privasi
    bengkel.description       = description
    bengkel.tarikh            = tarikh_dt
    bengkel.tarikh_tamat      = tarikh_tamat_dt
    bengkel.lokasi_nama       = lokasi_nama
    bengkel.lokasi_alamat     = lokasi_alamat
    bengkel.organizer_nama    = org_nama
    bengkel.organizer_email   = org_email
    bengkel.organizer_telefon = org_tel
    bengkel.video_ucapan_url  = vid_ucapan
    bengkel.video_arah_url    = vid_arah
    bengkel.had_peserta       = had_int
    bengkel.save()

    # ── Save tentative items ──────────────────────────────────────────────
    from .models import TentativeBengkel
    masa_list       = request.POST.getlist("tentative_masa")
    aktiviti_list   = request.POST.getlist("tentative_aktiviti")
    penerangan_list = request.POST.getlist("tentative_penerangan")

    # Replace all existing items with submitted ones
    bengkel.tentative.all().delete()
    for i, (masa_val, aktiviti_val) in enumerate(zip(masa_list, aktiviti_list)):
        masa_val     = masa_val.strip()
        aktiviti_val = aktiviti_val.strip()
        if masa_val and aktiviti_val:
            penerangan_val = penerangan_list[i].strip() if i < len(penerangan_list) else ""
            TentativeBengkel.objects.create(
                bengkel=bengkel, masa=masa_val, aktiviti=aktiviti_val,
                penerangan=penerangan_val, urutan=i
            )

    return bengkel, {}, {}


# ── Workshop CRUD ─────────────────────────────────────────────────────────────

@login_required
def penganjur_home(request):
    """Laman utama khas untuk penganjur."""
    if not request.user.is_staff or request.user.is_superuser:
        return redirect("home")

    now = timezone.now()
    bengkel_qs = Bengkel.objects.filter(created_by=request.user)

    total_bengkel   = bengkel_qs.count()
    akan_datang     = bengkel_qs.filter(tarikh__gte=now).order_by("tarikh")
    sudah_lepas     = bengkel_qs.filter(tarikh__lt=now).order_by("-tarikh")
    total_peserta   = Jemputan.objects.filter(bengkel__in=bengkel_qs).count()
    total_hadir     = Kehadiran.objects.filter(jemputan__bengkel__in=bengkel_qs).count()
    total_diterima  = Jemputan.objects.filter(bengkel__in=bengkel_qs, status="accepted").count()

    return render(request, "bengkel/penganjur_home.html", {
        "akan_datang":    akan_datang[:5],
        "sudah_lepas":    sudah_lepas[:3],
        "total_bengkel":  total_bengkel,
        "total_peserta":  total_peserta,
        "total_hadir":    total_hadir,
        "total_diterima": total_diterima,
        "active":         "home",
    })


@login_required
def bengkel_list(request):
    if not request.user.is_staff:
        return redirect("bengkel:dashboard")
    if request.user.is_superuser:
        qs = Bengkel.objects.all()
    else:
        qs = Bengkel.objects.filter(created_by=request.user)
    return render(request, "bengkel/list.html", {"bengkel_list": qs, "active": "bengkel"})


@login_required
def bengkel_create(request):
    if not request.user.is_staff:
        return redirect("bengkel:dashboard")
    if request.method == "POST":
        instance, errors, form_data = _bengkel_form_save(request)
        if instance:
            messages.success(request, _('Bengkel "%s" berjaya dicipta.') % instance.title)
            return redirect("bengkel:detail", pk=instance.pk)
        return render(request, "bengkel/form.html", {
            "errors": errors, "form_data": form_data,
            "mode": "create", "active": "bengkel", "bengkel": None,
        })
    return render(request, "bengkel/form.html", {"mode": "create", "active": "bengkel", "bengkel": None})


@login_required
def bengkel_detail(request, pk):
    if not request.user.is_staff:
        return redirect("bengkel:dashboard")
    bengkel = get_object_or_404(Bengkel, pk=pk)
    if not request.user.is_superuser and bengkel.created_by != request.user:
        messages.error(request, "Anda tidak mempunyai akses ke bengkel ini.")
        return redirect("bengkel:list")
    
    # Tarik jemputan dan render halaman Panel Kawalan Penganjur (detail.html)
    jemputan = bengkel.jemputan.prefetch_related("kehadiran").all()
    return render(request, "bengkel/detail.html", {
        "bengkel": bengkel, "jemputan": jemputan, "active": "bengkel",
    })


@login_required
def bengkel_edit(request, pk):
    if not request.user.is_staff:
        return redirect("bengkel:dashboard")
    bengkel = get_object_or_404(Bengkel, pk=pk)
    if not request.user.is_superuser and bengkel.created_by != request.user:
        messages.error(request, "Anda tidak mempunyai akses untuk mengedit bengkel ini.")
        return redirect("bengkel:list")
    if request.method == "POST":
        instance, errors, form_data = _bengkel_form_save(request, bengkel=bengkel)
        if instance:
            messages.success(request, _("Maklumat bengkel berjaya dikemaskini."))
            return redirect("bengkel:detail", pk=instance.pk)
        return render(request, "bengkel/form.html", {
            "bengkel": bengkel, "errors": errors, "form_data": form_data,
            "mode": "edit", "active": "bengkel",
        })
    form_data = {
        "title": bengkel.title,
        "jenis_acara": bengkel.jenis_acara,
        "privasi": bengkel.privasi,
        "description": bengkel.description,
        "tarikh": bengkel.tarikh.strftime("%Y-%m-%dT%H:%M") if bengkel.tarikh else "",
        "tarikh_tamat": bengkel.tarikh_tamat.strftime("%Y-%m-%dT%H:%M") if bengkel.tarikh_tamat else "",
        "lokasi_nama": bengkel.lokasi_nama,
        "lokasi_alamat": bengkel.lokasi_alamat,
        "organizer_nama": bengkel.organizer_nama,
        "organizer_email": bengkel.organizer_email,
        "organizer_telefon": bengkel.organizer_telefon,
        "video_ucapan_url": bengkel.video_ucapan_url,
        "video_arah_url": bengkel.video_arah_url,
        "had_peserta": bengkel.had_peserta,
    }
    return render(request, "bengkel/form.html", {
        "bengkel": bengkel, "mode": "edit", "active": "bengkel", "form_data": form_data,
        "tentative_items": bengkel.tentative.all(),
    })


@login_required
@require_POST
def bengkel_delete(request, pk):
    if not request.user.is_staff:
        return redirect("bengkel:dashboard")
    bengkel = get_object_or_404(Bengkel, pk=pk)
    if not request.user.is_superuser and bengkel.created_by != request.user:
        messages.error(request, "Anda tidak mempunyai akses untuk memadam bengkel ini.")
        return redirect("bengkel:list")
    title = bengkel.title
    bengkel.delete()
    messages.success(request, f'Bengkel "{title}" telah dipadam.')
    return redirect("bengkel:list")


# ── Invitations ───────────────────────────────────────────────────────────────

def _send_jemputan_email(request, j):
    """Send invitation email with accept/reject link. Silently skips if no email."""
    if not j.email:
        return
    try:
        inv_url      = request.build_absolute_uri(f"/bengkel/i/{j.token}/")
        check_in_url = request.build_absolute_uri(f"/bengkel/hadir/{j.qr_token}/")

        buf = io.BytesIO()
        qr_obj = qrcode.QRCode(
            version=None, error_correction=qrcode.constants.ERROR_CORRECT_H,
            box_size=8, border=2,
        )
        qr_obj.add_data(check_in_url)
        qr_obj.make(fit=True)
        qr_obj.make_image(fill_color="#0f172a", back_color="white").save(buf, format="PNG")
        qr_bytes = buf.getvalue()

        subject   = f"Jemputan Bengkel: {j.bengkel.title}"
        html_body = render_to_string("bengkel/email_jemputan.html", {
            "jemputan": j, "bengkel": j.bengkel,
            "inv_url": inv_url, "check_in_url": check_in_url,
        })
        plain_body = (
            f"Anda dijemput ke {j.bengkel.title}.\n"
            f"Sahkan kehadiran: {inv_url}"
        )
        msg = EmailMultiAlternatives(
            subject=subject, body=plain_body, from_email=None, to=[j.email],
        )
        msg.attach_alternative(html_body, "text/html")
        msg.mixed_subtype = "related"
        qr_mime = MIMEImage(qr_bytes)
        qr_mime.add_header("Content-ID", "<jemputan_qr>")
        qr_mime.add_header("Content-Disposition", "inline", filename="jemputan_qr.png")
        msg.attach(qr_mime)
        msg.send(fail_silently=True)
    except Exception:
        pass

@login_required
def jemputan_list(request, pk):
    if not request.user.is_staff:
        return redirect("bengkel:dashboard")
    bengkel = get_object_or_404(Bengkel, pk=pk)
    if not request.user.is_superuser and bengkel.created_by != request.user:
        messages.error(request, "Anda tidak mempunyai akses ke senarai jemputan ini.")
        return redirect("bengkel:list")
    jemputan = bengkel.jemputan.prefetch_related("kehadiran").all()
    base_url = request.build_absolute_uri("/").rstrip("/")
    return render(request, "bengkel/jemputan_list.html", {
        "bengkel": bengkel, "jemputan": jemputan,
        "base_url": base_url, "active": "bengkel",
    })


@login_required
def jemput(request, pk):
    if not request.user.is_staff:
        return redirect("bengkel:dashboard")
    bengkel = get_object_or_404(Bengkel, pk=pk)

    if request.method == "POST":
        action = request.POST.get("action", "individu")

        if action == "individu":
            email = request.POST.get("email", "").strip().lower()

            def _form_err(msg):
                return render(request, "bengkel/jemput_form.html", {
                    "bengkel": bengkel, "active": "bengkel",
                    "individu_error": msg, "individu_email": email,
                })

            if not email:
                return _form_err("Sila masukkan alamat e-mel.")

            # Check user exists
            try:
                target_user = User.objects.get(email__iexact=email)
            except User.DoesNotExist:
                return _form_err(f"Tiada akaun berdaftar dengan e-mel \"{email}\". Pastikan pengguna sudah mendaftar akaun terlebih dahulu.")

            # Check already invited to this bengkel
            existing = Jemputan.objects.filter(bengkel=bengkel, email__iexact=email).first()
            if existing:
                status_label = {"accepted": "diterima ✓", "rejected": "ditolak ✗", "pending": "belum dijawab"}.get(existing.status, existing.status)
                return _form_err(f"Pengguna ini sudah mempunyai jemputan untuk bengkel ini (status: {status_label}).")

            # Create jemputan pre-filled with user's info
            profile = UserProfile.objects.filter(user=target_user).first()
            j = Jemputan.objects.create(
                bengkel=bengkel,
                dijemput_oleh=request.user,
                user=target_user,
                email=target_user.email,
                nama=target_user.get_full_name() or target_user.username,
                organisasi=profile.organisasi if profile else "",
                jawatan=profile.jabatan if profile else "",
            )
            _send_jemputan_email(request, j)
            messages.success(request, f"Jemputan berjaya dihantar kepada {j.nama} ({j.email}).")
            return redirect("bengkel:jemputan_info", jid=j.pk)

        else:
            # QR untuk semua / fallback count-based
            try:
                count = max(1, min(int(request.POST.get("count", 1)), 1000))
            except (ValueError, TypeError):
                count = 1
            created = [Jemputan.objects.create(bengkel=bengkel, dijemput_oleh=request.user) for _ in range(count)]
            if count == 1:
                return redirect("bengkel:jemputan_info", jid=created[0].pk)
            messages.success(request, f"{count} jemputan berjaya dijana.")
            return redirect("bengkel:jemputan_list", pk=bengkel.pk)

    return render(request, "bengkel/jemput_form.html", {
        "bengkel": bengkel, "active": "bengkel",
    })


@login_required
def jemputan_csv_template(request):
    """Download a blank CSV template for bulk invitations."""
    response = HttpResponse(content_type="text/csv; charset=utf-8")
    response["Content-Disposition"] = 'attachment; filename="template_jemputan.csv"'
    response.write("\ufeff")  # UTF-8 BOM so Excel opens correctly
    writer = csv.writer(response)
    writer.writerow(["email", "nama", "organisasi", "jawatan"])
    writer.writerow(["ahmad@example.com", "Ahmad bin Abu", "KKM", "Pengurus Projek"])
    writer.writerow(["siti@example.com", "Siti Aminah", "Hospital Putrajaya", "Doktor"])
    return response


@login_required
def jemputan_info(request, jid):
    """Show generated link + QR for a jemputan so staff can copy/share manually."""
    if not request.user.is_staff:
        return redirect("bengkel:dashboard")
    j = get_object_or_404(Jemputan, pk=jid)
    inv_url = request.build_absolute_uri(f"/bengkel/i/{j.token}/")
    return render(request, "bengkel/jemputan_info.html", {
        "jemputan": j, "bengkel": j.bengkel,
        "inv_url": inv_url, "active": "bengkel",
    })


@login_required
@require_POST
def jemputan_delete(request, jid):
    if not request.user.is_staff:
        return redirect("bengkel:dashboard")
    j = get_object_or_404(Jemputan, pk=jid)
    bengkel_pk = j.bengkel_id
    j.delete()
    messages.success(request, "Jemputan telah dibuang.")
    return redirect("bengkel:jemputan_list", pk=bengkel_pk)

@login_required
@require_POST
def jemputan_approve(request, jid):
    """Penganjur meluluskan permohonan peserta dari portal awam."""
    if not request.user.is_staff:
        return redirect("bengkel:dashboard")
    j = get_object_or_404(Jemputan, pk=jid)
    j.status = "accepted"
    j.save()
    _send_ticket_email(request, j) # Sistem hantar tiket QR ke e-mel peserta secara automatik
    messages.success(request, f"Permohonan {j.nama} telah DILULUSKAN. Tiket telah dihantar ke e-mel beliau.")
    return redirect("bengkel:jemputan_list", pk=j.bengkel_id)

@login_required
@require_POST
def jemputan_reject(request, jid):
    """Penganjur menolak permohonan peserta dari portal awam."""
    if not request.user.is_staff:
        return redirect("bengkel:dashboard")
    j = get_object_or_404(Jemputan, pk=jid)
    j.status = "rejected"
    j.save()
    messages.success(request, f"Permohonan {j.nama} telah DITOLAK.")
    return redirect("bengkel:jemputan_list", pk=j.bengkel_id)


@login_required
def jemputan_qr_image(request, jid):
    """Return QR code PNG for a jemputan's check-in URL."""
    if not request.user.is_staff:
        return redirect("bengkel:dashboard")
    j = get_object_or_404(Jemputan, pk=jid)
    check_in_url = request.build_absolute_uri(f"/bengkel/hadir/{j.qr_token}/")

    buf = io.BytesIO()
    qr  = qrcode.QRCode(
        version=None,
        error_correction=qrcode.constants.ERROR_CORRECT_H,
        box_size=10,
        border=3,
    )
    qr.add_data(check_in_url)
    qr.make(fit=True)
    img = qr.make_image(fill_color="#0f172a", back_color="white")
    img.save(buf, format="PNG")

    return HttpResponse(buf.getvalue(), content_type="image/png")


@login_required
@require_POST
def jemputan_send_email(request, jid):
    """Manually trigger invitation email for a single jemputan."""
    if not request.user.is_staff:
        return redirect("bengkel:dashboard")
    j = get_object_or_404(Jemputan, pk=jid)
    bengkel = j.bengkel

    inv_url      = request.build_absolute_uri(f"/bengkel/i/{j.token}/")
    check_in_url = request.build_absolute_uri(f"/bengkel/hadir/{j.qr_token}/")

    try:
        qr_buf = io.BytesIO()
        qr_obj = qrcode.QRCode(
            version=None,
            error_correction=qrcode.constants.ERROR_CORRECT_H,
            box_size=8,
            border=2,
        )
        qr_obj.add_data(check_in_url)
        qr_obj.make(fit=True)
        qr_pil = qr_obj.make_image(fill_color="#0f172a", back_color="white")
        qr_pil.save(qr_buf, format="PNG")
        qr_bytes = qr_buf.getvalue()

        subject   = f"Jemputan Bengkel: {bengkel.title}"
        html_body = render_to_string("bengkel/email_jemputan.html", {
            "jemputan": j, "bengkel": bengkel,
            "inv_url": inv_url, "check_in_url": check_in_url,
        })
        plain_body = (
            f"Anda dijemput ke {bengkel.title}.\n"
            f"Sahkan kehadiran: {inv_url}"
        )

        msg = EmailMultiAlternatives(
            subject=subject, body=plain_body, from_email=None, to=[j.email],
        )
        msg.attach_alternative(html_body, "text/html")
        msg.mixed_subtype = "related"
        qr_mime = MIMEImage(qr_bytes)
        qr_mime.add_header("Content-ID", "<jemputan_qr>")
        qr_mime.add_header("Content-Disposition", "inline", filename="tiket_qr.png")
        msg.attach(qr_mime)
        msg.send(fail_silently=False)
        messages.success(request, f"E-mel jemputan berjaya dihantar kepada {j.nama} ({j.email}).")
    except Exception as exc:
        messages.error(request, f"Gagal hantar e-mel kepada {j.nama}: {exc}")

    return redirect("bengkel:jemputan_list", pk=bengkel.pk)


# ── Open Registration Link ────────────────────────────────────────────────────

def _send_ticket_email(request, j):
    """Send a tiket QR email to the invitee. Silently skips if no email."""
    if not j.email:
        return
    try:
        tiket_url    = request.build_absolute_uri(f"/bengkel/i/{j.token}/tiket/")
        check_in_url = request.build_absolute_uri(f"/bengkel/hadir/{j.qr_token}/")

        # Build QR PNG of the check-in URL
        buf = _io.BytesIO()
        qr_obj = qrcode.QRCode(
            version=None, error_correction=qrcode.constants.ERROR_CORRECT_H,
            box_size=8, border=2,
        )
        qr_obj.add_data(check_in_url)
        qr_obj.make(fit=True)
        qr_obj.make_image(fill_color="#0f172a", back_color="white").save(buf, format="PNG")
        qr_bytes = buf.getvalue()

        subject    = f"Tiket Anda — {j.bengkel.title}"
        html_body  = render_to_string("bengkel/email_tiket.html", {
            "jemputan": j, "bengkel": j.bengkel,
            "tiket_url": tiket_url, "check_in_url": check_in_url,
        })
        plain_body = (
            f"Terima kasih, {j.nama}!\n\n"
            f"Pendaftaran anda untuk {j.bengkel.title} telah berjaya.\n"
            f"Lihat tiket QR anda: {tiket_url}"
        )
        msg = EmailMultiAlternatives(
            subject=subject, body=plain_body, from_email=None, to=[j.email],
        )
        msg.attach_alternative(html_body, "text/html")
        msg.mixed_subtype = "related"
        qr_mime = MIMEImage(qr_bytes)
        qr_mime.add_header("Content-ID", "<tiket_qr>")
        qr_mime.add_header("Content-Disposition", "inline", filename="tiket_qr.png")
        msg.attach(qr_mime)
        msg.send(fail_silently=True)
    except Exception:
        pass  # Never block registration because of email failure

@login_required
def reg_toggle(request, pk):
    """Enable/disable the open registration link for a bengkel."""
    if not request.user.is_staff:
        return redirect("bengkel:dashboard")
    bengkel = get_object_or_404(Bengkel, pk=pk)
    if request.method == "POST":
        bengkel.reg_enabled = not bengkel.reg_enabled
        had = request.POST.get("reg_had", "0")
        try:
            bengkel.reg_had = max(0, int(had))
        except (ValueError, TypeError):
            bengkel.reg_had = 0
        bengkel.save(update_fields=["reg_enabled", "reg_had"])
    return redirect("bengkel:jemputan_list", pk=pk)


@login_required
def reg_reset(request, pk):
    """Generate a fresh registration token, invalidating the old link."""
    if not request.user.is_staff:
        return redirect("bengkel:dashboard")
    bengkel = get_object_or_404(Bengkel, pk=pk)
    if request.method == "POST":
        bengkel.reg_token = uuid.uuid4()
        bengkel.save(update_fields=["reg_token"])
        messages.success(request, "Link pendaftaran awam telah diset semula. Link lama tidak lagi sah.")
    return redirect("bengkel:jemputan_list", pk=pk)


@login_required
def open_reg_info(request, pk):
    """Show the shared open-registration QR + link for a bengkel (staff)."""
    if not request.user.is_staff:
        return redirect("bengkel:dashboard")
    bengkel = get_object_or_404(Bengkel, pk=pk)
    # Auto-enable when staff visits this page
    if not bengkel.reg_enabled:
        bengkel.reg_enabled = True
        bengkel.save(update_fields=["reg_enabled"])
    reg_url = request.build_absolute_uri(f"/bengkel/r/{bengkel.reg_token}/")
    return render(request, "bengkel/open_reg_info.html", {
        "bengkel": bengkel, "reg_url": reg_url, "active": "bengkel",
    })


@login_required
def open_reg_qr_image(request, pk):
    """Return a PNG QR code for the open registration URL."""
    if not request.user.is_staff:
        return redirect("bengkel:dashboard")
    bengkel = get_object_or_404(Bengkel, pk=pk)
    reg_url = request.build_absolute_uri(f"/bengkel/r/{bengkel.reg_token}/")
    buf = _io.BytesIO()
    qr_obj = qrcode.QRCode(
        version=None, error_correction=qrcode.constants.ERROR_CORRECT_H,
        box_size=10, border=3,
    )
    qr_obj.add_data(reg_url)
    qr_obj.make(fit=True)
    qr_pil = qr_obj.make_image(fill_color="#0f172a", back_color="white")
    qr_pil.save(buf, format="PNG")
    return HttpResponse(buf.getvalue(), content_type="image/png")


def open_reg(request, reg_token):
    """Public open-registration page — anyone with the link can self-register."""
    bengkel = get_object_or_404(Bengkel, reg_token=reg_token, reg_enabled=True)

    # Check seat cap
    if bengkel.reg_had > 0:
        registered = Jemputan.objects.filter(bengkel=bengkel).count()
        if registered >= bengkel.reg_had:
            return render(request, "bengkel/open_reg.html", {
                "bengkel": bengkel, "penuh": True,
            })

    if request.method == "POST":
        nama     = request.POST.get("nama", "").strip()
        email    = request.POST.get("email", "").strip()
        username = request.POST.get("username", "").strip()
        org      = request.POST.get("organisasi", "").strip()
        jawatan  = request.POST.get("jawatan", "").strip()
        unit     = request.POST.get("unit", "").strip()
        alamat   = request.POST.get("alamat", "").strip()

        errors = {}
        if not nama:
            errors["nama"] = "Nama penuh wajib diisi."
        if not email:
            errors["email"] = "E-mel wajib diisi."
        if not username:
            errors["username"] = "Nama pengguna wajib diisi."
        elif len(username) < 3:
            errors["username"] = "Nama pengguna mesti sekurang-kurangnya 3 aksara."
        elif User.objects.filter(username=username).exists():
            errors["username"] = "Nama pengguna ini telah digunakan."

        if not errors:
            import random, string, re as _re
            from django.core.mail import EmailMultiAlternatives as _EMA
            from django.conf import settings as _cfg

            # Check for existing active user with same email
            linked_user = User.objects.filter(email__iexact=email, is_active=True).first() if email else None
            new_account_pw = None

            if not linked_user and email:
                # Auto-create a Peserta account using the entered username
                requested_username = username or _re.sub(r'[^\w]', '_', email.split('@')[0])[:24] or "peserta"
                candidate = requested_username
                suffix = 1
                while User.objects.filter(username=candidate).exists():
                    candidate = f"{requested_username}_{suffix}"
                    suffix += 1
                new_account_pw = ''.join(random.choices(string.ascii_letters + string.digits, k=10))
                name_parts = nama.strip().split()
                linked_user = User.objects.create_user(
                    username=candidate,
                    email=email,
                    password=new_account_pw,
                    first_name=name_parts[0] if name_parts else nama,
                    last_name=' '.join(name_parts[1:]) if len(name_parts) > 1 else '',
                    is_active=True,
                )
                from bengkel.models import UserProfile
                profile, _ = UserProfile.objects.get_or_create(user=linked_user)
                profile.organisasi = org
                profile.jabatan    = jawatan
                profile.save()

            j = Jemputan.objects.create(
                bengkel=bengkel,
                nama=nama, email=email, username=username,
                organisasi=org, jawatan=jawatan, unit=unit, alamat=alamat,
                user=linked_user,
                status="accepted",
                responded_at=timezone.now(),
            )
            _send_ticket_email(request, j)

            # Send account credentials email if new account was created
            if new_account_pw and email:
                login_url = request.build_absolute_uri("/login/")
                tiket_url = request.build_absolute_uri(f"/bengkel/i/{j.token}/tiket/")
                try:
                    _html = f"""
                    <div style="font-family:Inter,Arial,sans-serif;max-width:560px;margin:auto;background:#0f172a;
                                color:#e2e8f0;padding:36px 40px;border-radius:16px;">
                      <h2 style="color:#2dd4bf;margin-top:0;">Akaun Peserta Telah Didaftarkan &#127881;</h2>
                      <p>Terima kasih kerana mendaftar untuk <strong>{bengkel.title}</strong>.</p>
                      <p>Akaun sistem telah dicipta secara automatik untuk anda:</p>
                      <div style="background:#1e293b;border-radius:10px;padding:20px 24px;margin:20px 0;
                                  border-left:4px solid #2dd4bf;">
                        <table style="width:100%;border-collapse:collapse;font-size:14px;">
                          <tr><td style="padding:6px 0;color:#94a3b8;width:140px;">Nama Pengguna</td>
                              <td style="padding:6px 0;font-family:monospace;font-size:15px;
                                         color:#f1f5f9;font-weight:700;">{linked_user.username}</td></tr>
                          <tr><td style="padding:6px 0;color:#94a3b8;">Kata Laluan</td>
                              <td style="padding:6px 0;font-family:monospace;font-size:15px;
                                         color:#fbbf24;font-weight:700;">{new_account_pw}</td></tr>
                        </table>
                      </div>
                      <p style="font-size:13px;color:#94a3b8;">
                        Anda <strong style="color:#f87171;">AMAT DISYORKAN</strong> untuk menukar kata laluan
                        selepas log masuk pertama.
                      </p>
                      <div style="display:flex;gap:12px;margin-top:20px;flex-wrap:wrap;">
                        <a href="{login_url}" style="background:#2dd4bf;color:#0f172a;text-decoration:none;
                           font-weight:700;padding:11px 22px;border-radius:9px;font-size:13px;">Log Masuk</a>
                        <a href="{tiket_url}" style="background:#1e293b;color:#94a3b8;text-decoration:none;
                           font-weight:600;padding:11px 22px;border-radius:9px;font-size:13px;
                           border:1px solid #334155;">Lihat Tiket</a>
                      </div>
                      <hr style="border:none;border-top:1px solid #1e293b;margin:28px 0;">
                      <p style="font-size:12px;color:#475569;margin:0;">Sistem Pengurusan Bengkel &middot; NDHB</p>
                    </div>
                    """
                    _txt = (
                        f"Akaun peserta telah dicipta.\n\n"
                        f"Nama Pengguna : {linked_user.username}\n"
                        f"Kata Laluan   : {new_account_pw}\n\n"
                        f"Log masuk: {login_url}\nTiket anda: {tiket_url}"
                    )
                    _msg = _EMA(
                        subject=f"Akaun & Tiket Anda — {bengkel.title}",
                        body=_txt,
                        from_email=getattr(_cfg, "DEFAULT_FROM_EMAIL", "noreply@ndhb.my"),
                        to=[email],
                    )
                    _msg.attach_alternative(_html, "text/html")
                    _msg.send()
                except Exception:
                    pass  # Never block registration because of email failure

            return redirect("bengkel:tiket", token=j.token)

        return render(request, "bengkel/open_reg.html", {
            "bengkel": bengkel, "errors": errors, "post": request.POST,
        })

    return render(request, "bengkel/open_reg.html", {
        "bengkel": bengkel, "errors": {}, "post": {},
    })


# ── Peserta Self Check-In (Kamera Peserta) ──────────────────────────────────

@login_required
def peserta_scan_kamera(request, pk):
    """Membuka antaramuka kamera di telefon bimbit peserta untuk mengimbas Master QR."""
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # Semak sama ada peserta ini mempunyai jemputan SAH ke bengkel ini
    try:
        jemputan = Jemputan.objects.get(bengkel=bengkel, user=request.user, status="accepted")
    except Jemputan.DoesNotExist:
        messages.error(request, f"Maaf, anda tidak mempunyai jemputan yang sah untuk acara '{bengkel.title}'.")
        return redirect("bengkel:dashboard")

    # Semak jika sudah daftar masuk
    if jemputan.sudah_hadir:
        messages.warning(request, f"Kehadiran anda untuk acara '{bengkel.title}' telah pun disahkan.")
        return redirect("bengkel:peserta_lobi", pk=bengkel.pk)

    return render(request, "bengkel/peserta_scan_kamera.html", {
        "bengkel": bengkel,
    })

@login_required
def master_qr_live_ajax(request, pk):
    """API untuk Penganjur memaparkan senarai kehadiran secara 'real-time' di Skrin Projektor."""
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})
        
    kehadiran_list = Kehadiran.objects.filter(jemputan__bengkel=bengkel).select_related("jemputan").order_by("-checked_in_at")[:15] # Ambil 15 terawal
    jumlah_hadir = Kehadiran.objects.filter(jemputan__bengkel=bengkel).count()
    
    senarai = []
    for k in kehadiran_list:
        senarai.append({
            "nama": k.jemputan.nama,
            "masa": k.checked_in_at.strftime("%H:%M")
        })
        
    return JsonResponse({
        "success": True,
        "jumlah_hadir": jumlah_hadir,
        "senarai": senarai
    })


# ── Public Invitation Pages (no login required) ───────────────────────────────

def invitation_response(request, token):
    j = get_object_or_404(Jemputan, token=token)
    
    # --- PEMBAIKAN PEPIJAT (Fail-Safe): Tampal data kosong dari profil induk ---
    if request.user.is_authenticated and j.user == request.user:
        profile = getattr(request.user, 'profile', None)
        has_changed = False
        
        if profile:
            if not j.organisasi and profile.organisasi:
                j.organisasi = profile.organisasi
                has_changed = True
            if not j.jawatan and profile.jabatan:
                j.jawatan = profile.jabatan
                has_changed = True
            if not j.nama and request.user.get_full_name():
                j.nama = request.user.get_full_name()
                has_changed = True
                
        # Simpan semula ke pangkalan data supaya form HTML dapat membaca nilai yang lengkap
        if has_changed:
            j.save()
            
    return render(request, "bengkel/response.html", {"jemputan": j, "bengkel": j.bengkel})


def invitation_accept(request, token):
    j = get_object_or_404(Jemputan, token=token)
    if request.method == "POST" and j.status == "pending":
        
        # 1. Sedut data dari Borang Kehadiran
        nama = request.POST.get("nama", "").strip()
        if nama:
            j.nama = nama.upper() # Paksa huruf besar di pangkalan data
        email = request.POST.get("email", "").strip()
        if email:
            j.email = email
        org = request.POST.get("organisasi", "").strip()
        if org:
            j.organisasi = org
        jaw = request.POST.get("jawatan", "").strip()
        if jaw:
            j.jawatan = jaw
            
        j.status = "accepted"
        j.responded_at = timezone.now()
        j.catatan_invitee = request.POST.get("catatan", "").strip()
        j.save()
        
        # 2. SINKRONISASI DATA KE PROFIL INDUK (SINGLE SOURCE OF TRUTH)
        if j.user:
            # Update Nama Pertama & Akhir di Jadual Induk User
            if nama:
                name_parts = j.nama.split()
                j.user.first_name = name_parts[0] if name_parts else j.nama
                j.user.last_name = " ".join(name_parts[1:]) if len(name_parts) > 1 else ""
                j.user.save(update_fields=['first_name', 'last_name'])
            
            # Update Organisasi & Jabatan di Jadual UserProfile
            from .models import UserProfile
            profile, _ = UserProfile.objects.get_or_create(user=j.user)
            has_changed = False
            
            if org and profile.organisasi != org:
                profile.organisasi = org
                has_changed = True
            if jaw and profile.jabatan != jaw:
                profile.jabatan = jaw
                has_changed = True
                
            if has_changed:
                profile.save()
                
            # Tarik balik semua resit jemputan lain milik user ini supaya seragam
            Jemputan.objects.filter(user=j.user).exclude(id=j.id).update(
                nama=j.nama, organisasi=org, jawatan=jaw
            )

        _send_ticket_email(request, j)
    return redirect("bengkel:tiket", token=token)


def invitation_reject(request, token):
    j = get_object_or_404(Jemputan, token=token)
    if request.method == "POST" and j.status == "pending":
        j.status        = "rejected"
        j.responded_at  = timezone.now()
        j.catatan_invitee = request.POST.get("catatan", "").strip()
        j.save()
    return redirect("bengkel:response", token=token)


def invitation_ticket(request, token):
    j = get_object_or_404(Jemputan, token=token)
    if j.status != "accepted":
        return redirect("bengkel:response", token=token)
    
    # --- LOGIK PENGIRAAN MEJA (Cerminan dari analitik_sme.html) ---
    semua_peserta = list(Jemputan.objects.filter(bengkel=j.bengkel, status="accepted"))
    def get_sort_val(p):
        nama_to_use = p.nama or p.email or "?"
        id_to_use = p.user_id if p.user_id else p.id
        return (len(nama_to_use) * 10) + id_to_use
    
    semua_peserta.sort(key=get_sort_val)
    
    no_meja_str = "Meja VVIP" if "VIP" in (j.jawatan or "").upper() else "Menunggu"
    if "VIP" not in (j.jawatan or "").upper():
        try:
            indeks_saya = semua_peserta.index(j)
            
            # --- PERUBAHAN: Gunakan kapasiti rasmi dari DB ---
            kapasiti = getattr(j.bengkel, 'kapasiti_semeja', 10)
            no_meja_int = (indeks_saya // kapasiti) + 1
            no_meja_str = f"MEJA {no_meja_int:02d}"
        except ValueError:
            pass

    check_in_url = request.build_absolute_uri(f"/bengkel/hadir/{j.qr_token}/")
    return render(request, "bengkel/tiket.html", {
        "jemputan": j, "bengkel": j.bengkel, "check_in_url": check_in_url, "no_meja": no_meja_str
    })


# ── Public Portal (no login required) — peserta mendaftar sendiri ─────────────

def portal_list(request):
    """Public page listing all open bengkels (termasuk yang lepas)."""
    from django.utils import timezone
    # Tarik SEMUA acara "Terbuka" tanpa mengira tarikh, susun yang terbaru di atas
    bengkels = Bengkel.objects.filter(privasi="Terbuka").order_by("-tarikh")
    return render(request, "bengkel/portal_list.html", {"bengkels": bengkels})


def portal_detail(request, pk):
    """Public detail + self-registration form for a bengkel."""
    bengkel = get_object_or_404(Bengkel, pk=pk)
    penuh = bool(bengkel.had_peserta and bengkel.jumlah_diterima >= bengkel.had_peserta)
    form_data = {
        "nama": "",
        "email": "",
        "username": "",
        "organisasi": "",
        "jawatan": "",
        "unit": "",
        "alamat": "",
    }

    if request.method == "POST":
        nama        = request.POST.get("nama", "").strip()
        email       = request.POST.get("email", "").strip()
        username    = request.POST.get("username", "").strip()
        organisasi  = request.POST.get("organisasi", "").strip()
        jawatan     = request.POST.get("jawatan", "").strip()
        unit        = request.POST.get("unit", "").strip()
        alamat      = request.POST.get("alamat", "").strip()
        form_data = {
            "nama": nama,
            "email": email,
            "username": username,
            "organisasi": organisasi,
            "jawatan": jawatan,
            "unit": unit,
            "alamat": alamat,
        }
        errors = {}
        if penuh:
            errors["__all__"] = "Pendaftaran untuk bengkel ini telah penuh."
        if not nama:  errors["nama"]  = "Nama wajib diisi."
        if not email: errors["email"] = "E-mel wajib diisi."
        
        # HANYA buat semakan (validation) username jika pengguna BELUM log masuk
        if not request.user.is_authenticated:
            if not username: errors["username"] = "Nama pengguna wajib diisi."
            elif len(username) < 3:
                errors["username"] = "Nama pengguna mesti sekurang-kurangnya 3 aksara."
            elif User.objects.filter(username=username).exists():
                errors["username"] = "Nama pengguna ini telah digunakan."

        if not errors:
            if Jemputan.objects.filter(bengkel=bengkel, email__iexact=email).exists():
                errors["email"] = "E-mel ini sudah didaftarkan untuk bengkel ini."
            else:
                # Kenal pasti adakah yang memohon ini pengguna berdaftar (melalui borang 1-Klik)
                user_obj = request.user if request.user.is_authenticated else None
                
                # --- PEMBAIKAN PEPJAT: Tarik data dari profil induk jika borang kosong (1-Klik) ---
                if user_obj and not (organisasi and jawatan):
                    profile = getattr(user_obj, 'profile', None)
                    if profile:
                        if not organisasi: organisasi = profile.organisasi
                        if not jawatan: jawatan = profile.jabatan
                        if not unit: unit = profile.unit
                        if not alamat: alamat = profile.alamat
                        if not nama: nama = user_obj.get_full_name() or user_obj.username
                        if not email: email = user_obj.email
                
                j = Jemputan.objects.create(
                    bengkel=bengkel,
                    nama=nama, email=email, username=username,
                    organisasi=organisasi, jawatan=jawatan, unit=unit, alamat=alamat,
                    user=user_obj,              # Ikat akaun sistem jika ada
                    status="pending",           # Menunggu kelulusan penganjur
                    responded_at=timezone.now(),
                )
                messages.success(request, f"🚀 Berjaya! Permohonan penyertaan ke '{bengkel.title}' telah dihantar. Penganjur akan menerima notifikasi kelulusan anda sebentar lagi.")
                
                # Jika user sudah log masuk, redirect ke dashboard. Jika awam, ke list.
                if request.user.is_authenticated:
                    return redirect("bengkel:dashboard")
                return redirect("bengkel:portal_list")

        return render(request, "bengkel/portal_detail.html", {
            "bengkel": bengkel,
            "errors": errors,
            "form_data": form_data,
            "penuh": penuh,
        })

    return render(request, "bengkel/portal_detail.html", {
        "bengkel": bengkel,
        "errors": {},
        "form_data": form_data,
        "penuh": penuh,
    })


def portal_tiket(request, token):
    """QR ticket for self-registered attendee."""
    j = get_object_or_404(Jemputan, token=token, status="accepted")
    
    # --- LOGIK PENGIRAAN MEJA (Cerminan dari analitik_sme.html) ---
    semua_peserta = list(Jemputan.objects.filter(bengkel=j.bengkel, status="accepted"))
    def get_sort_val(p):
        nama_to_use = p.nama or p.email or "?"
        id_to_use = p.user_id if p.user_id else p.id
        return (len(nama_to_use) * 10) + id_to_use
    
    semua_peserta.sort(key=get_sort_val)
    
    no_meja_str = "Meja VVIP" if "VIP" in (j.jawatan or "").upper() else "Menunggu"
    if "VIP" not in (j.jawatan or "").upper():
        try:
            indeks_saya = semua_peserta.index(j)
            
            # --- PERUBAHAN: Gunakan kapasiti rasmi dari DB ---
            kapasiti = getattr(j.bengkel, 'kapasiti_semeja', 10)
            no_meja_int = (indeks_saya // kapasiti) + 1
            no_meja_str = f"MEJA {no_meja_int:02d}"
        except ValueError:
            pass

    check_in_url = request.build_absolute_uri(f"/bengkel/hadir/{j.qr_token}/")
    return render(request, "bengkel/tiket.html", {
        "jemputan": j, "bengkel": j.bengkel, "check_in_url": check_in_url, "no_meja": no_meja_str
    })


# ── User Dashboard (login required) ──────────────────────────────────────────

@login_required
def user_dashboard(request):
    """Logged-in user sees all their jemputan, split by upcoming/past, with global gamification stats."""
    from bengkel.signals import link_jemputan_to_user, ensure_profile
    from django.utils import timezone
    from .models import (
        ContributionFile, SpafPainPoint, AnalisisSWOT, AnalisisPESTEL, 
        AnalisisVMOST, Analisis5C, AnalisisSOAR, SpafRootCauseAnalysis, 
        SpafRiskAnalysis, ThemeVote, PillarVote, ThemeIdea, PillarIdea, 
        StrategyIdea, ActionPlanIdea, AllocationIdea
    )

    link_jemputan_to_user(request.user)
    ensure_profile(request.user)

    now = timezone.now()
    user = request.user

    # 1. Tarik semua jemputan sepanjang zaman
    all_jemputan = Jemputan.objects.filter(user=user).select_related("bengkel").prefetch_related("bengkel__tentative", "kehadiran").order_by("bengkel__tarikh")

    # 2. Asingkan acara akan datang dan acara lepas
    akan_datang = []
    sudah_lepas = []
    total_hadir = 0

    for j in all_jemputan:
        if j.sudah_hadir:
            total_hadir += 1
            
        # Logik Semasa: Guna Tarikh Tamat (jika ada) untuk tentukan adakah acara masih aktif
        tarikh_rujukan = j.bengkel.tarikh_tamat if j.bengkel.tarikh_tamat else j.bengkel.tarikh
        
        # Jika tarikh tamat (atau mula) adalah selepas hari kelmarin, ia dianggap MASIH AKTIF
        if tarikh_rujukan >= now - timezone.timedelta(days=1):
            akan_datang.append(j)
        else:
            sudah_lepas.append(j)
            
    # Susun semula acara lepas supaya yang terbaru di atas
    sudah_lepas.reverse()

    # 3. KIRA SKOR KESELURUHAN SEPANJANG ZAMAN (LIFETIME SCORE)
    # (Telah diselaraskan dengan logik bengkel_tags untuk elak point bleeding)
    jumlah_fail = ContributionFile.objects.filter(contribution__jemputan__user=user).count()
    
    # Kita hanya kira analisis situasi yang diikat pada bengkel yang sah
    jum_swot = AnalisisSWOT.objects.filter(user=user, bengkel__isnull=False).count()
    jum_pestel = AnalisisPESTEL.objects.filter(user=user, bengkel__isnull=False).count()
    jum_vmost = AnalisisVMOST.objects.filter(user=user, bengkel__isnull=False).count()
    jum_5c = Analisis5C.objects.filter(user=user, bengkel__isnull=False).count()
    jum_soar = AnalisisSOAR.objects.filter(user=user, bengkel__isnull=False).count()
    
    total_spaf = jum_swot + jum_pestel + jum_vmost + jum_5c + jum_soar
    
    jum_undi_tema = ThemeVote.objects.filter(user=user, tema__bengkel__isnull=False).count()
    jum_undi_teras = PillarVote.objects.filter(user=user, pillar__bengkel__isnull=False).count()
    total_undian = jum_undi_tema + jum_undi_teras
    
    jum_idea_tema = ThemeIdea.objects.filter(user=user, tema__bengkel__isnull=False).count()
    jum_idea_teras = PillarIdea.objects.filter(user=user, pillar__bengkel__isnull=False).count()
    jum_idea_strategi = StrategyIdea.objects.filter(user=user, strategi__bengkel__isnull=False).count()
    jum_idea_action = ActionPlanIdea.objects.filter(user=user, action_plan__strategi__bengkel__isnull=False).count()
    jum_idea_bajet = AllocationIdea.objects.filter(user=user, action_plan__strategi__bengkel__isnull=False).count()
    
    total_dna = jum_idea_tema + jum_idea_teras + jum_idea_strategi + jum_idea_action + jum_idea_bajet
    
    # Formula Rubrik Gamifikasi (Mencegah Fail Sampah & Plagiat)
    skor_fail_ai = 0
    # Hanya kira fail TULEN (bukan ciplak)
    fail_dinilai = ContributionFile.objects.filter(contribution__jemputan__user=user, is_duplicate=False).exclude(skor_novelty=0.0)
    for f in fail_dinilai:
        # --- IDEA 1: DARABKAN DENGAN SKOR RELEVANSI ---
        pengali_relevansi = (f.skor_relevansi / 10.0) if getattr(f, 'skor_relevansi', 0) else 0
        mata_mentah = (f.skor_novelty * 2) + (f.indeks_kebolehtindakan * 0.2)
        skor_fail_ai += int(mata_mentah * pengali_relevansi)
    
    # Fail yang belum diproses dapat 2 mata deposit (Kecuali jika fail tersebut plagiat)
    jumlah_fail_tulen = ContributionFile.objects.filter(contribution__jemputan__user=user, is_duplicate=False).count()
    fail_belum_dinilai = jumlah_fail_tulen - fail_dinilai.count()
    deposit_fail = fail_belum_dinilai * 2 
    
    skor_keseluruhan = skor_fail_ai + deposit_fail + (total_spaf * 10) + (total_undian * 2) + (total_dna * 8)

    # 4. Tentukan Persona AI Peserta
    persona = "Pemerhati Aktif"
    if total_dna > total_spaf and total_dna > 5:
        persona = "Pemikir Strategik"
    elif total_spaf > total_dna and total_spaf > 5:
        persona = "Penyelesai Masalah (Problem Solver)"
    elif jumlah_fail >= 3:
        persona = "Pakar Domain (SME)"

    stats = {
        "total_jemputan": all_jemputan.count(),
        "total_diterima": all_jemputan.filter(status="accepted").count(),
        "total_hadir": total_hadir,
        "skor_keseluruhan": skor_keseluruhan,
        "persona": persona
    }

    penganjur_req = PenganjurRequest.objects.filter(user=request.user).first()

    # --- AUTOMATIK AUTO-LOCK: Semak status kewujudan data untuk fasa seterusnya ---
    from .models import BlueprintTheme, BlueprintPillar, BlueprintStrategy, BlueprintActionPlan, BlueprintIndicator, BlueprintAllocation
    
    themes_unlocked      = BlueprintTheme.objects.filter(bengkel__in=[j.bengkel for j in all_jemputan]).exists()
    pillars_unlocked     = BlueprintPillar.objects.filter(bengkel__in=[j.bengkel for j in all_jemputan]).exists()
    strategies_unlocked  = BlueprintStrategy.objects.filter(bengkel__in=[j.bengkel for j in all_jemputan]).exists()
    actions_unlocked     = BlueprintActionPlan.objects.filter(bengkel__in=[j.bengkel for j in all_jemputan]).exists()
    indicators_unlocked  = BlueprintIndicator.objects.filter(bengkel__in=[j.bengkel for j in all_jemputan]).exists()
    allocations_unlocked = BlueprintAllocation.objects.filter(bengkel__in=[j.bengkel for j in all_jemputan]).exists()

    return render(request, "bengkel/user_dashboard.html", {
        "akan_datang": akan_datang,
        "sudah_lepas": sudah_lepas,
        "stats": stats,
        "active": "dashboard",
        "penganjur_req": penganjur_req,
        # Hantar state kebenaran akses fasa ke HTML
        "themes_unlocked": themes_unlocked,
        "pillars_unlocked": pillars_unlocked,
        "strategies_unlocked": strategies_unlocked,
        "actions_unlocked": actions_unlocked,
        "indicators_unlocked": indicators_unlocked,
        "allocations_unlocked": allocations_unlocked,
    })

# ── Edit Profile ──────────────────────────────────────────────────────────────

@login_required
def edit_profile(request):
    from .models import UserProfile
    profile, _ = UserProfile.objects.get_or_create(user=request.user)

    if request.method == "POST":
        first_name = request.POST.get("first_name", "").strip()
        last_name  = request.POST.get("last_name", "").strip()
        email      = request.POST.get("email", "").strip()
        telefon    = request.POST.get("telefon", "").strip()
        jabatan    = request.POST.get("jabatan", "").strip()
        organisasi = request.POST.get("organisasi", "").strip()
        unit       = request.POST.get("unit", "").strip()
        alamat     = request.POST.get("alamat", "").strip()

        errors = {}
        if not first_name:
            errors["first_name"] = "Nama pertama wajib diisi."
        if not email:
            errors["email"] = "E-mel wajib diisi."
        elif (
            User.objects.exclude(pk=request.user.pk)
            .filter(email__iexact=email).exists()
        ):
            errors["email"] = "E-mel ini sudah digunakan oleh pengguna lain."

        if errors:
            return render(request, "bengkel/edit_profile.html", {
                "errors":    errors,
                "form_data": request.POST,
                "profile":   profile,
                "back_url":  reverse("bengkel:profil_kognitif", kwargs={"uid": request.user.id}),
            })

        request.user.first_name = first_name
        request.user.last_name  = last_name
        request.user.email      = email
        request.user.save()

        profile.telefon    = telefon
        profile.jabatan    = jabatan
        profile.organisasi = organisasi
        profile.unit       = unit
        profile.alamat     = alamat
        profile.save()

        # [SINKRONISASI DATA KEKAL] 
        # Kemas kini semua rekod jemputan (resit penyertaan) supaya Penganjur nampak nama baharu!
        from .models import Jemputan
        nama_penuh = f"{first_name} {last_name}".strip()
        Jemputan.objects.filter(user=request.user).update(
            nama=nama_penuh,
            email=email,
            jawatan=jabatan,
            organisasi=organisasi,
            unit=unit,
            alamat=alamat
        )

        messages.success(request, "Profil berjaya dikemaskini.")
        return redirect("bengkel:profil_kognitif", uid=request.user.id)

    return render(request, "bengkel/edit_profile.html", {
        "profile":  profile,
        "back_url":  reverse("bengkel:profil_kognitif", kwargs={"uid": request.user.id}),
        "form_data": {
            "first_name": request.user.first_name,
            "last_name":  request.user.last_name,
            "email":      request.user.email,
            "username":   request.user.username,
            "telefon":    profile.telefon,
            "jabatan":    profile.jabatan,
            "organisasi": profile.organisasi,
            "unit":       profile.unit,
            "alamat":     profile.alamat,
        },
    })


# ── Change Password ───────────────────────────────────────────────────────────

@login_required
def change_password(request):
    if request.method == "POST":
        current  = request.POST.get("current_password", "")
        new_pw   = request.POST.get("new_password", "")
        confirm  = request.POST.get("confirm_password", "")

        errors = {}
        if not request.user.check_password(current):
            errors["current_password"] = "Kata laluan semasa tidak betul."
        if len(new_pw) < 8:
            errors["new_password"] = "Kata laluan baru mestilah sekurang-kurangnya 8 aksara."
        elif new_pw != confirm:
            errors["confirm_password"] = "Pengesahan kata laluan tidak sepadan."

        if errors:
            return render(request, "bengkel/change_password.html", {"errors": errors, "back_url": reverse("bengkel:edit_profile")})

        from django.contrib.auth import update_session_auth_hash
        request.user.set_password(new_pw)
        request.user.save()
        update_session_auth_hash(request, request.user)
        messages.success(request, "Kata laluan berjaya ditukar.")
        return redirect("bengkel:edit_profile")

    return render(request, "bengkel/change_password.html", {"errors": {}, "back_url": reverse("bengkel:edit_profile")})


# ── Penganjur Approval Request ────────────────────────────────────────────────

@login_required
def peserta_saya(request):
    """Penganjur — see all participants across their own bengkel."""
    if not request.user.is_staff or request.user.is_superuser:
        return redirect("bengkel:dashboard")

    bengkel_qs = Bengkel.objects.filter(created_by=request.user)
    jemputan = (
        Jemputan.objects
        .filter(bengkel__in=bengkel_qs)
        .select_related("bengkel", "user")
        .prefetch_related("kehadiran")
        .order_by("bengkel__tarikh", "nama")
    )

    # Filters
    bengkel_filter = request.GET.get("bengkel", "")
    status_filter  = request.GET.get("status", "")
    cari           = request.GET.get("cari", "").strip()

    if bengkel_filter:
        jemputan = jemputan.filter(bengkel__pk=bengkel_filter)
    if status_filter:
        jemputan = jemputan.filter(status=status_filter)
    if cari:
        jemputan = jemputan.filter(nama__icontains=cari) | jemputan.filter(email__icontains=cari) | jemputan.filter(organisasi__icontains=cari)

    return render(request, "bengkel/peserta_saya.html", {
        "jemputan":       jemputan,
        "bengkel_list":   bengkel_qs,
        "bengkel_filter": bengkel_filter,
        "status_filter":  status_filter,
        "cari":           cari,
        "active":         "peserta",
    })


@login_required
@require_POST
def mohon_penganjur(request):
    """Peserta submits a request to become Penganjur."""
    if request.user.is_staff or request.user.is_superuser:
        return redirect("bengkel:dashboard")

    # One active request at a time
    existing = PenganjurRequest.objects.filter(user=request.user).first()
    if existing and existing.status == "pending":
        messages.warning(request, "Permohonan anda masih dalam semakan.")
        return redirect("bengkel:dashboard")

    # Re-use or create
    sebab = request.POST.get("sebab", "").strip()
    if existing:
        existing.status = "pending"
        existing.sebab  = sebab
        existing.catatan_admin = ""
        existing.save()
    else:
        PenganjurRequest.objects.create(user=request.user, sebab=sebab)

    messages.success(request, "Permohonan anda telah dihantar. Sila tunggu kelulusan daripada admin.")
    return redirect("bengkel:dashboard")


# ── Problem Statement submission by attendee ──────────────────────────────────

def submit_pernyataan(request, token):
    """Attendee submits a problem statement for the bengkel they attended."""
    from problems.models import ProblemStatement
    import re as _re
    from collections import Counter

    STOP_WORDS = {
        "the","a","an","and","or","but","in","on","at","to","for","of","with",
        "by","from","is","are","was","were","be","been","being","have","has",
        "had","do","does","did","will","would","could","should","may","might",
        "shall","can","that","this","these","those","it","its","not","no",
        "so","yet","as","if","when","where","which","who","whom","how","why",
        "what","i","we","you","he","she","they","me","us","him","her","them",
        "my","our","your","his","their","into","than","then","also","more",
        "such","there","about","up","out","all","very","just","each","every",
        "some","other","need","still","even","too","many","much",
    }

    j = get_object_or_404(Jemputan, token=token, status="accepted")
    if not j.sudah_hadir:
        return render(request, "bengkel/submit_pernyataan.html", {
            "jemputan": j, "bengkel": j.bengkel, "belum_hadir": True,
        })

    DOMAINS = [
        "Electronic Medical Records (EMR/EHR)",
        "Telehealth & Telemedicine",
        "Health Information Exchange",
        "Medical Imaging & Diagnostics",
        "Pharmacy & Medication Management",
        "Laboratory Information Systems",
        "Patient Engagement & Mobile Health",
        "Administrative & Billing Systems",
        "Cybersecurity & Data Privacy",
        "Artificial Intelligence & Analytics",
        "IoT & Wearable Devices",
        "Supply Chain & Inventory",
        "Human Resource Management",
        "Interoperability & Standards",
        "Digital Infrastructure & Connectivity",
        "Regulatory & Compliance",
        "Training & Digital Literacy",
        "Other",
    ]

    if request.method == "POST":
        title        = request.POST.get("title", "").strip()
        description  = request.POST.get("description", "").strip()
        domain       = request.POST.get("domain", "").strip()
        priority     = request.POST.get("priority", "medium")
        region       = request.POST.get("region", "").strip()

        errors = {}
        if not title:       errors["title"]       = "Tajuk wajib diisi."
        if not description: errors["description"] = "Penerangan wajib diisi."
        if not domain:      errors["domain"]      = "Domain wajib dipilih."

        if not errors:
            words = _re.findall(r"\b[a-zA-Z]{3,}\b", (title + " " + description).lower())
            keywords = [w for w, _ in Counter([w for w in words if w not in STOP_WORDS]).most_common(10)]

            ps = ProblemStatement.objects.create(
                title=title,
                description=description,
                domain=domain,
                priority=priority,
                region=region,
                submitter_type=j.jawatan or "Peserta Bengkel",
                keywords=keywords,
                word_count=len(description.split()),
                submitted_by=j.user,
                jemputan=j,
            )
            return render(request, "bengkel/submit_pernyataan.html", {
                "jemputan": j, "bengkel": j.bengkel, "berjaya": True, "ps": ps,
            })

        return render(request, "bengkel/submit_pernyataan.html", {
            "jemputan": j, "bengkel": j.bengkel,
            "errors": errors, "domains": DOMAINS,
            "form_data": {"title": title, "description": description,
                          "domain": domain, "priority": priority, "region": region},
        })

    pernyataan_sedia = ProblemStatement.objects.filter(jemputan=j)
    return render(request, "bengkel/submit_pernyataan.html", {
        "jemputan": j, "bengkel": j.bengkel,
        "domains": DOMAINS, "pernyataan_sedia": pernyataan_sedia,
    })


# ─────────────────────────────────────────────────────────────────────────────
# CONTRIBUTION — file upload + comment per peserta
# ─────────────────────────────────────────────────────────────────────────────

def contribute(request, token):
    """Peserta yang sudah hadir boleh upload fail rujukan & tulis ulasan."""
    j = get_object_or_404(Jemputan, token=token, status="accepted")

    if not j.sudah_hadir:
        return render(request, "bengkel/contribute.html", {
            "jemputan": j, "bengkel": j.bengkel, "belum_hadir": True,
        })

    from .models import BengkelContribution, ContributionFile

    contribution, _ = BengkelContribution.objects.get_or_create(
        bengkel=j.bengkel, jemputan=j
    )

    if request.method == "POST":
        # Each uploaded file is paired with a summary: POST sends
        # file_summary_0, file_summary_1, ... alongside the files list.
        files = request.FILES.getlist("files")
        summaries = []
        for i in range(len(files)):
            summaries.append(request.POST.get(f"file_summary_{i}", "").strip())

        for f, summary in zip(files, summaries):
            ContributionFile.objects.create(
                contribution=contribution,
                file=f,
                original_name=f.name,
                summary=summary,
            )

        comment_lines = request.POST.getlist("comment_line")
        comment_raw = request.POST.get("comment", "").strip()
        comment = "\n".join(l.strip() for l in comment_lines if l.strip()) or comment_raw
        if comment:
            contribution.comment = comment
        elif not comment and not files:
            # no input at all — keep existing comment
            pass
        contribution.save()

        messages.success(request, "Sumbangan anda telah disimpan.")

        # Check if ALL accepted+hadir peserta have submitted — if so, trigger LLM
        bengkel = j.bengkel
        hadir_ids = set(
            Kehadiran.objects.filter(jemputan__bengkel=bengkel)
            .values_list("jemputan_id", flat=True)
        )
        submitted_ids = set(
            BengkelContribution.objects.filter(bengkel=bengkel)
            .values_list("jemputan_id", flat=True)
        )
        if hadir_ids and hadir_ids.issubset(submitted_ids):
            # Proses terus menggunakan fungsi sedia ada tanpa Celery
            _process_bengkel(bengkel.pk)

        return redirect("bengkel:contribute", token=token)

    return render(request, "bengkel/contribute.html", {
        "jemputan": j,
        "bengkel": j.bengkel,
        "contribution": contribution,
    })


# ─────────────────────────────────────────────────────────────────────────────
# LLM PROCESSING — collect contributions, call Gemini, generate PDFs
# ─────────────────────────────────────────────────────────────────────────────

def _extract_text_from_file(file_path):
    """Extract plain text using RAM-friendly libraries."""
    import os
    ext = os.path.splitext(file_path)[1].lower()
    try:
        if ext == ".pdf":
            # Guna pypdf yang sangat ringan untuk RAM 512MB
            import pypdf
            teks_penuh = []
            with open(file_path, "rb") as f:
                reader = pypdf.PdfReader(f)
                for page in reader.pages:
                    teks = page.extract_text()
                    if teks:
                        teks_penuh.append(teks)
            return "\n".join(teks_penuh)
            
        elif ext in (".docx", ".doc"):
            import docx as _docx
            doc = _docx.Document(file_path)
            return "\n".join(p.text for p in doc.paragraphs)
            
        else:
            # Try reading as plain text
            with open(file_path, encoding="utf-8", errors="ignore") as fh:
                return fh.read()
    except Exception as e:
        print(f"Ralat mengekstrak teks: {e}")
        return ""

def _process_bengkel(bengkel_pk):
    """
    Background task: collect all contributions, call Gemini, generate PDFs.
    Called in a daemon thread when all peserta have submitted.
    """
    import os, json, re as _re
    import django
    django.setup()

    from django.conf import settings as _settings
    from .models import Bengkel, BengkelContribution, BengkelLaporan

    bengkel = Bengkel.objects.get(pk=bengkel_pk)

    # Mark a single "processing" record so UI can show progress
    BengkelLaporan.objects.filter(bengkel=bengkel, status__in=["pending", "failed"]).delete()
    placeholder = BengkelLaporan.objects.create(
        bengkel=bengkel,
        tajuk="Sedang memproses semua sumbangan…",
        status="processing",
    )

    try:
        # ── 1. Collect all contributions ─────────────────────────────────────
        corpus = []
        for contrib in BengkelContribution.objects.filter(bengkel=bengkel).select_related("jemputan"):
            name = contrib.jemputan.nama or "Peserta"
            parts = [f"### Nama: {name}"]
            if contrib.comment:
                parts.append(f"**Ulasan/Pendapat:**\n{contrib.comment}")
            for cf in contrib.files.all():
                abs_path = os.path.join(_settings.MEDIA_ROOT, cf.file.name)
                text = _extract_text_from_file(abs_path)
                file_part = f"**Fail: {cf.original_name}**"
                if cf.summary:
                    file_part += f"\nRingkasan oleh peserta: {cf.summary}"
                if text.strip():
                    file_part += f"\nKandungan fail:\n{text[:5000]}"
                parts.append(file_part)
            corpus.append("\n".join(parts))

        # ── 2. Chunked OpenAI processing (50 peserta per batch) ───────────────
        import time
        from openai import OpenAI
        client = OpenAI(api_key=_settings.OPENAI_API_KEY)

        BATCH_SIZE = 50
        batch_summaries = []

        def _call_ai(prompt_text, retries=3):
            for attempt in range(retries):
                try:
                    resp = client.chat.completions.create(
                        model=getattr(_settings, 'LLM_MODEL', 'gpt-4o-mini'),
                        messages=[{"role": "user", "content": prompt_text}],
                        response_format={"type": "json_object"}
                    )
                    return resp.choices[0].message.content.strip()
                except Exception as e:
                    if attempt < retries - 1:
                        time.sleep(5)
                    else:
                        raise

        def _strip_fences(text):
            text = _re.sub(r"^```json\s*", "", text, flags=_re.MULTILINE)
            text = _re.sub(r"^```\s*$", "", text, flags=_re.MULTILINE)
            return text.strip()

        # Step A: Summarise each batch of 50
        for i in range(0, len(corpus), BATCH_SIZE):
            batch = corpus[i:i + BATCH_SIZE]
            batch_text = "\n\n---\n\n".join(batch)
            batch_num = i // BATCH_SIZE + 1
            total_batches = (len(corpus) + BATCH_SIZE - 1) // BATCH_SIZE

            placeholder.tajuk = f"Memproses kumpulan {batch_num}/{total_batches}…"
            placeholder.save()

            batch_prompt = f"""
Anda ialah penganalisis bengkel digital kesihatan Malaysia: "{bengkel.title}".
Ini adalah sumbangan daripada kumpulan {batch_num} (peserta {i+1}–{i+len(batch)}).

TUGASAN: Analisis sumbangan dan hasilkan ringkasan structured.
Kembalikan HANYA JSON sah (tanpa markdown, tanpa ```):
{{
  "domains": [
    {{
      "tajuk": "nama domain",
      "isu_utama": ["isu 1", "isu 2"],
      "cadangan": ["cadangan 1"],
      "sumber": ["Nama Peserta: petikan ringkas"],
      "isu_berulang": ["isu yang disebut >1 peserta dalam batch ini"]
    }}
  ]
}}

=== SUMBANGAN ===
{batch_text}
"""
            raw = _call_gemini(batch_prompt)
            raw = _strip_fences(raw)
            try:
                batch_result = json.loads(raw)
                batch_summaries.append(batch_result)
            except json.JSONDecodeError:
                # If JSON fails, store raw text and continue
                batch_summaries.append({"raw": raw, "domains": []})

            # Polite delay to stay within 15 RPM
            if i + BATCH_SIZE < len(corpus):
                time.sleep(4)

        # Step B: Synthesise all batch summaries into final report
        placeholder.tajuk = "Menyatukan semua analisis kumpulan…"
        placeholder.save()

        summaries_text = json.dumps(batch_summaries, ensure_ascii=False, indent=2)

        final_prompt = f"""
Anda ialah penganalisis bengkel digital kesihatan Malaysia: "{bengkel.title}".
Di bawah adalah ringkasan analisis daripada {len(batch_summaries)} kumpulan peserta (jumlah {len(corpus)} peserta).

TUGASAN:
1. Gabungkan semua domains yang serupa/sama.
2. Susun semua isu utama — tandakan isu DUPLIKAT/BERULANG dan nyatakan ia disebut oleh ramai peserta.
3. Senaraikan cadangan terbaik per domain.
4. Tulis ringkasan eksekutif keseluruhan bengkel.

Kembalikan HANYA JSON sah (tanpa markdown, tanpa ```):
{{
  "ringkasan_eksekutif": "...",
  "domains": [
    {{
      "tajuk": "...",
      "isu_utama": ["..."],
      "cadangan": ["..."],
      "sumber": ["..."],
      "isu_duplikat": [
        {{"isu": "...", "disebut_oleh": ["nama1", "nama2"]}}
      ]
    }}
  ]
}}

=== RINGKASAN KUMPULAN ===
{summaries_text[:80000]}
"""
        raw_final = _call_gemini(final_prompt)
        raw_final = _strip_fences(raw_final)
        data = json.loads(raw_final)

        # ── 3. Generate PDFs ──────────────────────────────────────────────────
        from fpdf import FPDF
        import datetime

        placeholder.delete()

        def _make_pdf(tajuk_laporan, ringkasan, domains_subset):
            pdf = FPDF()
            pdf.set_auto_page_break(auto=True, margin=15)
            pdf.add_page()

            # Title
            pdf.set_font("Helvetica", "B", 16)
            pdf.set_fill_color(15, 23, 42)
            pdf.set_text_color(255, 255, 255)
            pdf.cell(0, 12, tajuk_laporan[:80], ln=True, fill=True, align="C")
            pdf.ln(4)

            # Bengkel info
            pdf.set_font("Helvetica", "", 9)
            pdf.set_text_color(100, 100, 100)
            pdf.cell(0, 5, f"Bengkel: {bengkel.title}   |   Laporan dijana: {datetime.date.today()}", ln=True)
            pdf.ln(6)

            # Executive summary (only in summary PDF)
            if ringkasan:
                pdf.set_font("Helvetica", "B", 11)
                pdf.set_text_color(15, 23, 42)
                pdf.cell(0, 7, "Ringkasan Eksekutif", ln=True)
                pdf.set_font("Helvetica", "", 10)
                pdf.set_text_color(50, 50, 50)
                pdf.multi_cell(0, 6, ringkasan.encode("latin-1", "replace").decode("latin-1"))
                pdf.ln(6)

            for domain in domains_subset:
                # Domain heading
                pdf.set_font("Helvetica", "B", 13)
                pdf.set_fill_color(30, 58, 138)
                pdf.set_text_color(255, 255, 255)
                pdf.cell(0, 9, domain["tajuk"][:80].encode("latin-1", "replace").decode("latin-1"),
                         ln=True, fill=True)
                pdf.ln(2)

                # Isu utama
                pdf.set_font("Helvetica", "B", 10)
                pdf.set_text_color(15, 23, 42)
                pdf.cell(0, 6, "Isu Utama:", ln=True)
                pdf.set_font("Helvetica", "", 10)
                pdf.set_text_color(50, 50, 50)
                for isu in domain.get("isu_utama", []):
                    pdf.multi_cell(0, 5, f"  - {isu}".encode("latin-1", "replace").decode("latin-1"))

                # Cadangan
                pdf.ln(2)
                pdf.set_font("Helvetica", "B", 10)
                pdf.set_text_color(15, 23, 42)
                pdf.cell(0, 6, "Cadangan:", ln=True)
                pdf.set_font("Helvetica", "", 10)
                pdf.set_text_color(50, 50, 50)
                for c in domain.get("cadangan", []):
                    pdf.multi_cell(0, 5, f"  - {c}".encode("latin-1", "replace").decode("latin-1"))

                # Duplikat
                duplikat = domain.get("isu_duplikat", [])
                if duplikat:
                    pdf.ln(2)
                    pdf.set_font("Helvetica", "B", 10)
                    pdf.set_text_color(180, 50, 50)
                    pdf.cell(0, 6, "Isu Bertindih (Telah Digabungkan):", ln=True)
                    pdf.set_font("Helvetica", "I", 9)
                    pdf.set_text_color(120, 40, 40)
                    for d in duplikat:
                        disebut = ", ".join(d.get("disebut_oleh", []))
                        pdf.multi_cell(0, 5,
                            f"  \"{d['isu']}\" — disebut oleh: {disebut}"
                            .encode("latin-1", "replace").decode("latin-1"))

                pdf.ln(6)

            # Save to media
            import tempfile
            with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp:
                tmp_path = tmp.name
            pdf.output(tmp_path)

            import shutil
            from django.core.files import File
            laporan = BengkelLaporan.objects.create(bengkel=bengkel, tajuk=tajuk_laporan, status="done")
            with open(tmp_path, "rb") as f:
                laporan.pdf_file.save(f"laporan_{laporan.pk}.pdf", File(f), save=True)
            os.unlink(tmp_path)

        # One PDF per domain
        for domain in data.get("domains", []):
            _make_pdf(domain["tajuk"], None, [domain])

        # Ringkasan PDF
        _make_pdf(
            f"Ringkasan Eksekutif — {bengkel.title}",
            data.get("ringkasan_eksekutif", ""),
            data.get("domains", [])
        )

    except Exception as exc:
        placeholder.tajuk = "Gagal memproses sumbangan"
        placeholder.status = "failed"
        placeholder.ralat  = str(exc)
        placeholder.save()


# ─────────────────────────────────────────────────────────────────────────────
# LAPORAN — view list of generated PDFs for a bengkel
# ─────────────────────────────────────────────────────────────────────────────

@login_required
def laporan_list(request, pk):
    """Penganjur / peserta views the LLM-generated laporan for a bengkel."""
    bengkel = get_object_or_404(Bengkel, pk=pk)

    from .models import BengkelContribution

    # Only allow: bengkel creator, staff, superuser, or peserta yg hadir
    is_organizer = request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user
    is_peserta   = Jemputan.objects.filter(bengkel=bengkel, user=request.user, status="accepted").exists()

    if not (is_organizer or is_peserta):
        messages.error(request, "Anda tidak mempunyai akses ke laporan ini.")
        return redirect("bengkel:dashboard")

    laporan   = bengkel.laporan.all()
    hadir_ids = set(Kehadiran.objects.filter(jemputan__bengkel=bengkel).values_list("jemputan_id", flat=True))
    submitted_ids = set(BengkelContribution.objects.filter(bengkel=bengkel).values_list("jemputan_id", flat=True))
    jumlah_hadir     = len(hadir_ids)
    jumlah_submitted = len(submitted_ids & hadir_ids)

    return render(request, "bengkel/laporan_list.html", {
        "bengkel": bengkel,
        "laporan": laporan,
        "jumlah_hadir":     jumlah_hadir,
        "jumlah_submitted": jumlah_submitted,
        "semua_submitted":  hadir_ids and hadir_ids.issubset(submitted_ids),
        "is_organizer":     is_organizer,
    })


# ─────────────────────────────────────────────────────────────────────────────
# TENTATIVE — penganjur urus jadual program
# ─────────────────────────────────────────────────────────────────────────────

@login_required
def tentative_manage(request, pk):
    bengkel = get_object_or_404(Bengkel, pk=pk)
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        messages.error(request, "Akses ditolak.")
        return redirect("bengkel:penganjur_home")

    from .models import TentativeBengkel

    if request.method == "POST":
        masa     = request.POST.get("masa", "").strip()
        aktiviti = request.POST.get("aktiviti", "").strip()
        penerangan = request.POST.get("penerangan", "").strip()
        urutan   = request.POST.get("urutan", "0").strip() or "0"
        if masa and aktiviti:
            TentativeBengkel.objects.create(
                bengkel=bengkel, masa=masa, aktiviti=aktiviti,
                penerangan=penerangan, urutan=int(urutan)
            )
            messages.success(request, "Item tentative berjaya ditambah.")
        else:
            messages.error(request, "Masa dan aktiviti diperlukan.")
        return redirect("bengkel:tentative", pk=pk)

    items = bengkel.tentative.all()
    if request.user.is_superuser:
        from django.urls import reverse
        back_url = reverse("superadmin:detail_bengkel", kwargs={"bid": bengkel.pk})
    else:
        from django.urls import reverse
        back_url = reverse("bengkel:detail", kwargs={"pk": bengkel.pk})
    return render(request, "bengkel/tentative.html", {"bengkel": bengkel, "items": items, "back_url": back_url})


@login_required
@require_POST
def tentative_edit(request, pk, tid):
    bengkel = get_object_or_404(Bengkel, pk=pk)
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        messages.error(request, "Akses ditolak.")
        return redirect("bengkel:penganjur_home")

    from .models import TentativeBengkel
    item = get_object_or_404(TentativeBengkel, pk=tid, bengkel=bengkel)
    masa      = request.POST.get("masa", "").strip()
    aktiviti  = request.POST.get("aktiviti", "").strip()
    penerangan = request.POST.get("penerangan", "").strip()
    urutan    = request.POST.get("urutan", "0").strip() or "0"
    if masa and aktiviti:
        item.masa       = masa
        item.aktiviti   = aktiviti
        item.penerangan = penerangan
        item.urutan     = int(urutan)
        item.save()
        messages.success(request, "Item tentative berjaya dikemaskini.")
    else:
        messages.error(request, "Masa dan aktiviti diperlukan.")
    return redirect("bengkel:tentative", pk=pk)


@login_required
@require_POST
def tentative_delete(request, pk, tid):
    bengkel = get_object_or_404(Bengkel, pk=pk)
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        messages.error(request, "Akses ditolak.")
        return redirect("bengkel:penganjur_home")

    from .models import TentativeBengkel
    item = get_object_or_404(TentativeBengkel, pk=tid, bengkel=bengkel)
    item.delete()
    messages.success(request, "Item tentative berjaya dipadam.")
    return redirect("bengkel:tentative", pk=pk)

@login_required
def peserta_reference(request, pk):
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # 1. Cari Jemputan milik user untuk bengkel ini
    jemputan = get_object_or_404(Jemputan, bengkel=bengkel, user=request.user, status="accepted")
    
    # 2. Dapatkan (atau cipta) rekod Contribution
    from .models import BengkelContribution, ContributionFile
    contribution, _ = BengkelContribution.objects.get_or_create(
        bengkel=bengkel, jemputan=jemputan
    )

    # 3. Logik Muat Naik Fail (POST)
    if request.method == "POST":
        action = request.POST.get("action", "")
        
        # Kes A: Muat Naik Fail Baharu
        if action == "upload_file":
            import hashlib
            fail_list = request.FILES.getlist("files")
            
            fail_berjaya = 0
            fail_ciplak = 0
            
            for f in fail_list:
                # --- IDEA 2: JANA COP JARI DIGITAL (HASH) ---
                file_hash = hashlib.sha256()
                for chunk in f.chunks():
                    file_hash.update(chunk)
                hash_hex = file_hash.hexdigest()
                
                # Semak sekiranya dokumen dengan Hash ini sudah wujud dalam Bengkel ini
                is_dup = ContributionFile.objects.filter(contribution__bengkel=bengkel, fail_hash=hash_hex).exists()
                
                # 1. Simpan fail ke pangkalan data
                cf = ContributionFile.objects.create(
                    contribution=contribution,
                    file=f,
                    original_name=f.name,
                    summary="Fail Rujukan (SME)", # Nilai lalai
                    fail_hash=hash_hex,
                    is_duplicate=is_dup
                )
                
                if is_dup:
                    fail_ciplak += 1
                else:
                    fail_berjaya += 1

            if fail_list:
                # --- PAPARKAN POP-UP AMARAN PLAGIAT & BERJAYA ---
                if fail_ciplak > 0:
                    messages.warning(request, f"⚠️ {fail_ciplak} fail ditolak markahnya kerana dikesan sebagai PLAGIAT (telah dimuat naik sebelum ini oleh peserta lain/anda).")
                if fail_berjaya > 0:
                    messages.success(request, f"✅ {fail_berjaya} fail tulen berjaya dimuat naik dan sedang diproses oleh AI.")

                # 2. Cetuskan AI/Celery untuk memproses di belakang tabir
                try:
                    import os
                    import boto3
                    import json
                    from django.conf import settings
                    
                    is_live_render = os.environ.get('RENDER', '').lower() == 'true'
                    if not is_live_render:
                        # LOCAL: Panggil Webhook/Task secara terus tanpa membebankan views (Guna Memurai)
                        import requests
                        webhook_url = request.build_absolute_uri('/bengkel/api/worker-webhook/')
                        requests.post(webhook_url, json={"bengkel_pk": bengkel.pk}, timeout=1) 
                    else:
                        # LIVE (RENDER): Hantar isyarat ke SQS
                        sqs_client = boto3.client(
                            'sqs',
                            aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
                            aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
                            region_name=settings.AWS_REGION_NAME
                        )
                        webhook_url = request.build_absolute_uri('/bengkel/api/worker-webhook/')
                        sqs_client.send_message(
                            QueueUrl=settings.AWS_SQS_QUEUE_URL,
                            MessageBody=json.dumps({
                                "bengkel_pk": bengkel.pk,
                                "target_url": webhook_url
                            })
                        )
                except Exception as e:
                    print(f"Ralat mencetuskan Celery/Webhook: {e}")

                messages.success(request, f"{len(fail_list)} fail berjaya dimuat naik dan sedang diekstrak oleh sistem di belakang tabir. Boleh *refresh* sebentar lagi.")
            return redirect("bengkel:peserta_reference", pk=pk)
            
        # Kes B: Padam Fail (Satu persatu)
        elif action == "delete_file":
            fid = request.POST.get("file_id")
            try:
                cf = ContributionFile.objects.get(pk=fid, contribution=contribution)
                cf.delete()
                messages.success(request, "Fail berjaya dipadam.")
            except ContributionFile.DoesNotExist:
                messages.error(request, "Fail tidak dijumpai.")
            return redirect("bengkel:peserta_reference", pk=pk)
            
        # Kes C: Padam Banyak Fail Serentak (Batch Delete)
        elif action == "delete_multiple":
            fids = request.POST.getlist("file_ids") # Ambil senarai ID fail dari checkbox
            if fids:
                # Padam semua fail yang ID nya ada dalam senarai fids
                ContributionFile.objects.filter(pk__in=fids, contribution=contribution).delete()
                messages.success(request, f"{len(fids)} fail berjaya dipadam secara pukal.")
            else:
                messages.error(request, "Tiada fail dipilih untuk dipadam.")
            return redirect("bengkel:peserta_reference", pk=pk)

    # 4. Tarik senarai fail untuk dipaparkan di jadual
    senarai_fail = contribution.files.all()

    return render(request, "bengkel/peserta_reference.html", {
        "bengkel": bengkel,
        "jemputan": jemputan,
        "senarai_fail": senarai_fail,
    })

@login_required
def peserta_spaf(request, pk):
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # PERISAI GATEKEEPER MAKRO (Langkah 2)
    if bengkel.blueprint_current_step < 2:
        messages.warning(request, "Hab Analitikal SPAF (Langkah 2) belum dibuka oleh Penganjur.")
        return redirect("bengkel:peserta_reference", pk=pk)
        
    return render(request, "bengkel/peserta_spaf.html", {
        "bengkel": bengkel,
    })

@login_required
def peserta_painpoint(request, pk):
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # 1. Tarik aduan yang telah direkodkan oleh user ini
    from .models import SpafPainPoint
    senarai_aduan = SpafPainPoint.objects.filter(user=request.user, bengkel=bengkel).order_by('-created_at')
    
    # 2. Tarik cetusan AI dari memori session (jika ada)
    cetusan_simpanan = request.session.get(f'cetusan_ai_{pk}', None)

    return render(request, "bengkel/peserta_painpoint.html", {
        "bengkel": bengkel,
        "senarai_aduan": senarai_aduan,
        "cetusan_simpanan": cetusan_simpanan
    })

@login_required
@require_POST
def jana_cetusan_ai(request, pk):
    """
    Endpoint AJAX untuk dipanggil oleh butang 'Jana Cetusan AI'.
    Menggunakan Enjin Dwi-Mod untuk kepantasan.
    """
    import re
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    jemputan = Jemputan.objects.filter(bengkel=bengkel, user=request.user, status="accepted").first()
    
    if not jemputan:
        return JsonResponse({"success": False, "error": "Akses ditolak."})
    
    teks_rujukan = ""
    # Cari fail rujukan yang di-upload oleh user ini
    contribution = getattr(jemputan, 'contribution', None)
    if not contribution:
        from .models import BengkelContribution
        contribution = BengkelContribution.objects.filter(bengkel=bengkel, jemputan=jemputan).first()

    if contribution:
        fail_peserta = contribution.files.all()
        for fp in fail_peserta:
            # --- PERUBAHAN HIBRID ---
            # Jangan guna fail fizikal (abs_path). Ambil teks dari database!
            teks_diekstrak = fp.teks_diekstrak or ""
            
            if teks_diekstrak.strip():
                teks_rujukan += teks_diekstrak + "\n\n"
                # Hentikan carian jika dah kumpul lebih 3000 aksara (Cukup untuk prompt AI)
                if len(teks_rujukan) > 3000:
                    break
                
        # Potong kepada 3000 aksara maksima untuk menjimatkan token Gemini
        teks_rujukan = teks_rujukan[:3000]

    if not teks_rujukan.strip():
        return JsonResponse({
            "success": False, 
            "error": "Tiada teks dijumpai. Sila pastikan anda telah memuat naik fail SOP/Dasar di Node 1."
        })
        
    from django.utils.translation import get_language
    lang = get_language()
    bahasa_sistem = "ENGLISH" if lang == 'en' else "BAHASA MELAYU"
        
    prompt_cetusan = f"""
    Anda adalah Fasilitator Kesihatan Digital.
    TUGASAN: Baca dokumen SOP/Rujukan di bawah dan hasilkan SATU (1) soalan provokatif.
    Soalan ini mesti merangsang pengguna untuk menceritakan masalah/kesusahan mereka berkaitan garis panduan ini dalam konteks pendigitalan kesihatan.
    
    DOKUMEN:
    {teks_rujukan}
    
    Kembalikan soalan tersebut secara terus. JANGAN MULA DENGAN 'Soalan:'. JANGAN berikan penerangan.
    """
    
    # MENGGUNAKAN HELPER FUNCTION DENGAN PAKSAAN BAHASA
    jawapan_ai = panggil_ai_dwi_enjin(prompt_cetusan, enforce_language=bahasa_sistem)
    
    if jawapan_ai:
        cetusan_ai = re.sub(r'^"|"$', '', jawapan_ai) # Buang quote marks
        request.session[f'cetusan_ai_{pk}'] = cetusan_ai 
        request.session.modified = True
        return JsonResponse({"success": True, "cetusan": cetusan_ai})
    else:
        return JsonResponse({"success": False, "error": "Sistem AI (OpenAI GPT) gagal berfungsi."})
    
@login_required
def peserta_situational(request, pk):
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # PERISAI GATEKEEPER NODE 3
    if bengkel.spaf_current_step < 3:
        messages.warning(request, "Modul Analisis Situasi belum dibuka oleh Penganjur.")
        return redirect("bengkel:peserta_spaf", pk=pk)
    
    # Ambil data sedia ada milik user untuk bengkel ini (jika ada)
    from .models import AnalisisSWOT, AnalisisPESTEL, AnalisisSOAR, Analisis5C
    
    swot_list = AnalisisSWOT.objects.filter(bengkel=bengkel, user=request.user)
    pestel_list = AnalisisPESTEL.objects.filter(bengkel=bengkel, user=request.user)
    soar_list = AnalisisSOAR.objects.filter(bengkel=bengkel, user=request.user)
    fivec_list = Analisis5C.objects.filter(bengkel=bengkel, user=request.user)
    
    # Gabungkan semua untuk dikira jumlahnya
    jumlah_hantar = swot_list.count() + pestel_list.count() + soar_list.count() + fivec_list.count()

    return render(request, "bengkel/peserta_situational.html", {
        "bengkel": bengkel,
        "swot_list": swot_list,
        "pestel_list": pestel_list,
        "soar_list": soar_list,
        "fivec_list": fivec_list,
        "jumlah_hantar": jumlah_hantar
    })

@login_required
def peserta_problem(request, pk):
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # Tarik Master Statements yang telah dijana oleh Penganjur (created_by)
    from .models import SpafProblemStatement
    senarai_master = SpafProblemStatement.objects.filter(user=bengkel.created_by, bengkel=bengkel).order_by('id')
    
    return render(request, "bengkel/peserta_problem.html", {
        "bengkel": bengkel,
        "senarai_master": senarai_master
    })

@login_required
def peserta_rca(request, pk):
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # PERISAI GATEKEEPER NODE 4
    if bengkel.spaf_current_step < 4:
        messages.warning(request, "Modul Analisis Punca (RCA) belum dibuka oleh Penganjur.")
        return redirect("bengkel:peserta_spaf", pk=pk)
    from .models import SpafRootCauseAnalysis, SpafProblemStatement
    
    # 1. Tarik data RCA milik pengguna untuk dipaparkan semula (Persistence)
    rca_list = SpafRootCauseAnalysis.objects.filter(user=request.user, bengkel=bengkel).order_by('-created_at')
    
    # 2. Tarik Master Statements HANYA KHAS UNTUK BENGKEL INI SAHAJA
    senarai_master = SpafProblemStatement.objects.filter(user=bengkel.created_by, bengkel=bengkel).order_by('id')
    
    return render(request, "bengkel/peserta_rca.html", {
        "bengkel": bengkel,
        "rca_list": rca_list,
        "senarai_master": senarai_master 
    })

@login_required
def peserta_rca_validation(request, pk):
    import json
    bengkel = get_object_or_404(Bengkel, pk=pk)
    from .models import SpafRootCauseAnalysis
    
    # 1. Tarik RCA sebenar yang dihantar oleh user untuk dipaparkan sebagai 'Penemuan'
    findings = SpafRootCauseAnalysis.objects.filter(user=request.user, bengkel=bengkel).order_by('created_at')
    
    # 2. Tarik status verifikasi dari memori session (untuk persistence)
    status_verifikasi = request.session.get(f"rca_validation_{pk}", {})
    
    return render(request, "bengkel/peserta_rca_validation.html", {
        "bengkel": bengkel,
        "findings": findings, # Hantar data RCA sebenar ke HTML
        "status_verifikasi_json": json.dumps(status_verifikasi)
    })

@login_required
def peserta_risk(request, pk):
    import json
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # PERISAI GATEKEEPER NODE 6 (MATI KAN LOOP HOLE)
    if bengkel.spaf_current_step < 6:
        messages.warning(request, "Modul Penilaian Risiko belum dibuka oleh Penganjur.")
        return redirect("bengkel:peserta_spaf", pk=pk)
        
    from .models import SpafRootCauseAnalysis
    
    # 1. Tarik RCA sebenar yang dihantar oleh user (kita jadikan ia sebagai Asas Risiko)
    findings = SpafRootCauseAnalysis.objects.filter(user=request.user, bengkel=bengkel).order_by('created_at')
    
    # 2. Tarik status penilaian dari memori session (untuk persistence)
    status_risiko = request.session.get(f"risk_assessment_{pk}", {})
    
    return render(request, "bengkel/peserta_risk.html", {
        "bengkel": bengkel,
        "findings": findings, # Hantar data ke HTML
        "status_risiko_json": json.dumps(status_risiko)
    })

# Pastikan anda mempunyai import ini di bahagian atas views.py jika belum ada
# from django.http import JsonResponse
# from .models import BlueprintTheme, ThemeVote, ThemeIdea

@login_required
def peserta_theme(request, pk):
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # PERISAI GATEKEEPER MAKRO (Langkah 3)
    if bengkel.blueprint_current_step < 3:
        messages.warning(request, "Modul Undian Tema (Langkah 3) belum dibuka oleh Penganjur.")
        return redirect("bengkel:peserta_overview", pk=pk)
        
    import math
    jemputan = get_object_or_404(Jemputan, bengkel=bengkel, user=request.user, status="accepted")
    
    from .models import BlueprintTheme, ThemeVote
    themes = BlueprintTheme.objects.filter(bengkel=bengkel).order_by('urutan')
    jumlah_tema = themes.count()
    
    # --- LOGIK BAHARU: Kebebasan Mengundi Kognitif ---
    user_voted_ids = list(ThemeVote.objects.filter(user=request.user, tema__bengkel=bengkel).values_list('tema_id', flat=True))
    
    # TAMBAHAN: Cari tema mana yang peserta sudah berikan idea/bantahan
    from .models import ThemeIdea
    user_idea_ids = list(ThemeIdea.objects.filter(user=request.user, tema__bengkel=bengkel).values_list('tema_id', flat=True))
    
    telah_undi = len(user_voted_ids)
    
    # Kita gantikan baki undi dengan status total yang telah disokong oleh user
    baki_undi = telah_undi 
    kuota_undi = jumlah_tema
    
    themes_data = []
    for t in themes:
        themes_data.append({
            'id': t.id,
            'urutan': t.urutan,
            'tema': t.tema,
            'penerangan': t.penerangan,
            'kata_kunci': t.senarai_kata_kunci,
            'total_votes': t.votes.count(),
            'is_voted': t.id in user_voted_ids,
            'has_idea': t.id in user_idea_ids # KUNCI: Tandakan jika dah ada idea!
        })
        
    return render(request, "bengkel/peserta_theme.html", {
        "bengkel": bengkel,
        "themes_data": themes_data,
        "kuota_undi": kuota_undi,
        "baki_undi": baki_undi
    })

@login_required
@require_POST
def undi_tema_ajax(request, pk):
    """Logik untuk peserta mengundi Tema secara Bebas (Tanpa Paksaan/Kuota)"""
    import json
    from .models import Bengkel, BlueprintTheme, ThemeVote
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    try:
        data = json.loads(request.body)
        tema_id = data.get("tema_id")
        tema = get_object_or_404(BlueprintTheme, pk=tema_id, bengkel=bengkel)
        
        # Semak jika undian sokongan sudah wujud
        undi_wujud = ThemeVote.objects.filter(user=request.user, tema=tema).first()
        
        if undi_wujud:
            # Jika sudah diundi, klik kali kedua akan membatalkan undian (Skip/Abaikan)
            undi_wujud.delete()
            status = "unvoted"
        else:
            # Jika belum diundi, cipta rekod sokongan rasmi
            ThemeVote.objects.create(user=request.user, tema=tema)
            status = "voted"
            
        return JsonResponse({
            "success": True, 
            "status": status, 
            "total_votes": tema.votes.count()
        })
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})

@login_required
@require_POST
def simpan_idea_tema_ajax(request, pk):
    """Logik untuk peserta memberi Cadangan Penambahbaikan berserta Saringan AI Berlapis"""
    import json
    from .models import Bengkel, BlueprintTheme, ThemeIdea
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    try:
        data = json.loads(request.body)
        tema_id = data.get("tema_id")
        idea_text = data.get("idea", "").strip()
        
        if not idea_text:
            return JsonResponse({"success": False, "error": "Sila masukkan cadangan anda."})
            
        tema = get_object_or_404(BlueprintTheme, pk=tema_id, bengkel=bengkel)
        
        is_bantahan = data.get("is_bantahan", False)
        prefix_dna = "[BANTAHAN STRATEGIK]" if is_bantahan else "[CADANGAN KOGNITIF]"
        jenis_teks = "Bantahan/Kritikan" if is_bantahan else "Idea Penambahbaikan"
        idea_penuh = f"{prefix_dna} {idea_text}"
        
        # 🛡️ LAPIS 1: SEKATAN AYAT BERTINDIH TEPAT (EXACT MATCH BLOCKING)
        if ThemeIdea.objects.filter(user=request.user, tema=tema, idea__iexact=idea_penuh).exists():
            return JsonResponse({"success": False, "error": "Sistem Anti-Spam: Anda telah pun menghantar idea ini. Sila sumbangkan idea baharu."})
        
        # 🛡️ KIRAAN LAPIS 2: SEMAK BERAPA BANYAK IDEA SUDAH DIHANTAR PADA KAD INI
        jumlah_idea_sedia_ada = ThemeIdea.objects.filter(user=request.user, tema=tema).count()
        
        # --- ENJIN AI: PENAPIS SAMPAH & GAMIFIKASI ---
        prompt = f"""
        Anda adalah Penilai Kualiti yang SANGAT TEGAS. Semak {jenis_teks} yang dihantar oleh pakar lapangan untuk mengulas sebuah Tema Nasional.
        Tema Asal AI: "{tema.tema}"
        Ulasan Peserta: "{idea_text}"
        
        TUGAS PENILAIAN:
        Nilaikan ulasan pakar dari skala 0.0 hingga 10.0 berdasarkan syarat ketat ini:
        1. Adakah komen ini relevan dengan tema asal dan logik untuk tujuan penambahbaikan kerangka nasional?
        2. Jika teks mengandungi perkataan harian merepek (cth: "ikan", "nasi", "ayam", "kucing", "test"), WAJIB BERI SKOR 0.0.
        3. Jika teks rawak/carutan ("asdasd", "123123"), WAJIB BERI SKOR 0.0.
        4. Jika ulasan bernas dan relevan, beri markah munasabah (6.0 - 10.0).
        
        KEMBALIKAN FORMAT JSON SAHAJA:
        {{
          "skor_relevansi": 8.5
        }}
        """
        
        skor_rel = 0.0
        jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
        if jawapan_ai:
            import re
            try:
                match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
                json_str = match.group(0) if match else jawapan_ai
                data_json = json.loads(json_str)
                skor_rel = float(data_json.get("skor_relevansi", 0.0))
            except Exception:
                pass
        
        # 🛡️ LAPIS 3: BLOK SAMPAH KETAT
        if skor_rel < 5.0:
            return JsonResponse({"success": True, "mata_diperoleh": 0, "status": "spam"})
        
        mata_diperoleh = int(10 * (skor_rel / 10.0))
        if mata_diperoleh <= 0: mata_diperoleh = 3
        
        # 🛡️ APLIKASI LAPIS 2: PENURUNAN KADAR MATA (DIMINISHING RETURNS)
        if jumlah_idea_sedia_ada == 1:
            mata_diperoleh = max(2, int(mata_diperoleh * 0.5)) # Idea ke-2 dapat 50%
        elif jumlah_idea_sedia_ada >= 2:
            mata_diperoleh = 2 # Idea ke-3 dan seterusnya hanya dapat 2 mata saguhati
        
        ThemeIdea.objects.create(
            user=request.user,
            tema=tema,
            idea=idea_penuh,
            skor_relevansi=skor_rel
        )
        
        return JsonResponse({"success": True, "mata_diperoleh": mata_diperoleh, "status": "lulus"})
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})

@login_required
def peserta_pillar(request, pk):
    bengkel = get_object_or_404(Bengkel, pk=pk)
    from .models import BlueprintPillar, PillarVote
    
    pillars = BlueprintPillar.objects.filter(bengkel=bengkel).order_by('urutan')
    user_voted_ids = list(PillarVote.objects.filter(user=request.user, pillar__bengkel=bengkel).values_list('pillar_id', flat=True))
    
    pillars_data = []
    from .models import PillarIdea
    for p in pillars:
        tema_list = [t.strip() for t in p.tema_dirangkumi.split(',')] if p.tema_dirangkumi else []
        
        # 🎯 KUNCI: Sedut kesemua idea yang pernah dihantar oleh USER INI khusus untuk TERAS INI
        my_ideas = list(PillarIdea.objects.filter(user=request.user, pillar=p).order_by('-created_at'))
        
        pillars_data.append({
            'id': p.id,
            'urutan': p.urutan,
            'teras': p.teras,
            'penerangan': p.penerangan,
            'tema_list': tema_list,
            'total_votes': p.votes.count(),
            'is_voted': p.id in user_voted_ids,
            'my_ideas': my_ideas # Hantar log ke HTML
        })
        
    return render(request, "bengkel/peserta_pillar.html", {
        "bengkel": bengkel,
        "pillars_data": pillars_data,
        "baki_undi": len(user_voted_ids) # LOGIK BAHARU: Jumlah yang telah disokong
    })

@login_required
@require_POST
def sokong_pillar_ajax(request, pk):
    """Logik untuk peserta menyokong Teras secara Bebas"""
    import json
    from .models import Bengkel, BlueprintPillar, PillarVote
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    try:
        data = json.loads(request.body)
        pillar_id = data.get("pillar_id")
        pillar = get_object_or_404(BlueprintPillar, pk=pillar_id, bengkel=bengkel)
        
        vote, created = PillarVote.objects.get_or_create(user=request.user, pillar=pillar)
        
        if not created:
            # Jika sudah diundi, klik kedua akan membatalkan sokongan
            vote.delete()
            status = "unvoted"
        else:
            # LOGIK BAHARU: Kita tidak lagi memadam undian teras lain. Bebas sokong banyak!
            status = "voted"
            
        return JsonResponse({"success": True, "status": status, "total_votes": pillar.votes.count()})
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})

@login_required
@require_POST
def simpan_idea_pillar_ajax(request, pk):
    """Logik untuk peserta menambah skop Teras berserta Saringan AI & Gamifikasi"""
    import json
    from .models import Bengkel, BlueprintPillar, PillarIdea
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    try:
        data = json.loads(request.body)
        pillar_id = data.get("pillar_id")
        idea_text = data.get("idea", "").strip()
        is_bantahan = data.get("is_bantahan", False)
        
        if not idea_text:
            return JsonResponse({"success": False, "error": "Sila masukkan cadangan skop anda."})
            
        pillar = get_object_or_404(BlueprintPillar, pk=pillar_id, bengkel=bengkel)
        
        prefix_dna = "[BANTAHAN STRATEGIK]" if is_bantahan else "[CADANGAN KOGNITIF]"
        jenis_teks = "Bantahan/Kritikan" if is_bantahan else "Idea Skop Tambahan"
        
        # --- ENJIN AI: PENAPIS SAMPAH & GAMIFIKASI ---
        prompt = f"""
        Anda adalah Penilai Kualiti yang SANGAT TEGAS. Semak {jenis_teks} yang dihantar oleh pakar lapangan untuk Teras Strategik.
        Teras Asal: "{pillar.teras}"
        Ulasan Peserta: "{idea_text}"
        
        TUGAS PENILAIAN:
        Nilaikan ulasan pakar dari skala 0.0 hingga 10.0 berdasarkan syarat ketat ini:
        1. Adakah komen ini logik untuk tujuan penambahbaikan skop teras jabatan/kementerian?
        2. Jika teks mengandungi perkataan harian merepek (cth: "ikan", "nasi", "ayam", "kucing", "test"), WAJIB BERI SKOR 0.0.
        3. Jika teks rawak/carutan ("asdasd", "123123"), WAJIB BERI SKOR 0.0.
        4. Jika ulasan bernas, logik dan membina, beri markah munasabah (6.0 - 10.0).
        
        KEMBALIKAN FORMAT JSON SAHAJA:
        {{
          "skor_relevansi": 8.5
        }}
        """
        
        skor_rel = 0.0
        jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
        if jawapan_ai:
            import re
            try:
                match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
                json_str = match.group(0) if match else jawapan_ai
                data_json = json.loads(json_str)
                skor_rel = float(data_json.get("skor_relevansi", 0.0))
            except Exception:
                pass
        
        # --- BLOK SAMPAH KETAT ---
        if skor_rel < 5.0:
            return JsonResponse({"success": True, "mata_diperoleh": 0, "status": "spam"})
        
        # Pengiraan Markah Gred (Max 10 mata)
        mata_diperoleh = int(10 * (skor_rel / 10.0))
        if mata_diperoleh <= 0: mata_diperoleh = 3
        
        PillarIdea.objects.create(
            user=request.user, 
            pillar=pillar, 
            idea=f"{prefix_dna} {idea_text}",
            skor_relevansi=skor_rel
        )
        
        return JsonResponse({"success": True, "mata_diperoleh": mata_diperoleh, "status": "lulus"})
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})

@login_required
@require_POST
def jana_pillar_penganjur_ajax(request, pk):
    """Penganjur mengarahkan AI mensintesis TEMA menjadi TERAS yang lebih besar"""
    import json, requests, re
    from .models import Bengkel, BlueprintTheme, BlueprintPillar
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    # Tarik Tema-tema yang telah diundi di Node 3
    themes = BlueprintTheme.objects.filter(bengkel=bengkel).order_by('urutan')
    if not themes.exists():
        return JsonResponse({"success": False, "error": "Tiada Tema dijumpai. Sila jana Tema di Langkah 3 terlebih dahulu."})

    teks_tema = "\n".join([f"- {t.tema}: {t.penerangan}" for t in themes])

    prompt = f"""
    Anda adalah Pakar Arkitek Strategi Nasional.
    TUGASAN: Gabungkan senarai Tema di bawah menjadi DUA (2) atau TIGA (3) TERAS STRATEGIK (Pillars) yang lebih besar sebagai payung utama.
    
    SENARAI TEMA UNTUK DIGABUNGKAN:
    {teks_tema}
    
    ARAHAN: 
    1. Setiap Teras mesti merangkumi sekurang-kurangnya satu atau lebih tema di atas yang berkaitan.
    2. Kembalikan dalam format JSON ARRAY SAHAJA. (Tanpa markdown).
    Contoh format:
    [
      {{"teras": "Tajuk Teras 1", "penerangan": "Huraian menyeluruh", "tema_dirangkumi": "Tema A, Tema B"}},
      {{"teras": "Tajuk Teras 2", "penerangan": "Huraian menyeluruh", "tema_dirangkumi": "Tema C"}}
    ]
    """

    # MENGGUNAKAN HELPER FUNCTION (DWI-ENJIN)
    jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
    
    if jawapan_ai:
        import re, json
        try:
            match = re.search(r'\[.*\]', jawapan_ai, re.DOTALL)
            if match:
                json_str = match.group(0)
            else:
                json_str = re.sub(r"^```json\s*|```\s*$", "", jawapan_ai, flags=re.MULTILINE).strip()
            
            data_json = json.loads(json_str)
            
            BlueprintPillar.objects.filter(bengkel=bengkel).delete()
            for i, item in enumerate(data_json, 1):
                BlueprintPillar.objects.create(
                    bengkel=bengkel, urutan=i,
                    teras=item.get("teras", "Teras Umum"),
                    penerangan=item.get("penerangan", ""),
                    tema_dirangkumi=item.get("tema_dirangkumi", "")
                )
            return JsonResponse({"success": True, "mesej": f"Berjaya merumuskan {len(data_json)} Teras Strategik."})
        except json.JSONDecodeError:
            return JsonResponse({"success": False, "error": "AI tidak memulangkan format JSON yang sah."})
        except Exception as e:
            return JsonResponse({"success": False, "error": str(e)})
    else:
        return JsonResponse({"success": False, "error": "Sistem AI (OpenAI GPT) gagal merespons."})

@login_required
def peserta_strategi(request, pk):
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # PERISAI GATEKEEPER MAKRO (Langkah 5)
    if bengkel.blueprint_current_step < 5:
        messages.warning(request, "Modul Semakan Strategi (Langkah 5) belum dibuka oleh Penganjur.")
        return redirect("bengkel:peserta_overview", pk=pk)
        
    from .models import BlueprintStrategy
    
    # Tarik semua strategi/fasa yang telah dijana AI (6 Fasa)
    strategies = BlueprintStrategy.objects.filter(bengkel=bengkel).order_by('urutan')
    
    strategies_data = []
    from .models import StrategyIdea
    for s in strategies:
        teras_list = [t.strip() for t in s.teras_berkaitan.split(',')] if s.teras_berkaitan else []
        
        # 🎯 KUNCI: Sedut kesemua idea yang pernah dihantar oleh USER INI untuk FASA INI
        my_ideas = list(StrategyIdea.objects.filter(user=request.user, strategi=s).order_by('-created_at'))
        
        strategies_data.append({
            'id': s.id,
            'urutan': s.urutan,
            'fasa': s.fasa,
            'tajuk': s.tajuk,
            'penerangan': s.penerangan,
            'teras_list': teras_list,
            'my_ideas': my_ideas # Hantar log ke HTML
        })
        
    return render(request, "bengkel/peserta_strategi.html", {
        "bengkel": bengkel,
        "strategies_data": strategies_data,
    })

@login_required
@require_POST
def simpan_idea_strategi_ajax(request, pk):
    """Logik untuk peserta menyumbang idea inisiatif/projek berserta Saringan AI"""
    import json
    from .models import Bengkel, BlueprintStrategy, StrategyIdea
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    try:
        data = json.loads(request.body)
        strategi_id = data.get("strategi_id")
        idea_text = data.get("idea", "").strip()
        
        if not idea_text:
            return JsonResponse({"success": False, "error": "Sila masukkan cadangan inisiatif/projek anda."})
            
        strategi = get_object_or_404(BlueprintStrategy, pk=strategi_id, bengkel=bengkel)
        
        # --- ENJIN AI: PENAPIS SAMPAH & GAMIFIKASI ---
        prompt = f"""
        Anda adalah Penilai Kualiti yang SANGAT TEGAS. Semak idea inisiatif/projek yang dihantar oleh pakar lapangan untuk Fasa Pelaksanaan (Roadmap).
        Fasa: "{strategi.tajuk}"
        Idea Projek Peserta: "{idea_text}"
        
        TUGAS PENILAIAN:
        Nilaikan cadangan projek dari skala 0.0 hingga 10.0 berdasarkan syarat ketat ini:
        1. Adakah ia merupakan satu idea inisiatif atau tindakan praktikal yang logik?
        2. Jika teks mengandungi perkataan harian merepek (cth: "ikan", "nasi", "ayam", "kucing", "test"), WAJIB BERI SKOR 0.0.
        3. Jika teks rawak/carutan ("asdasd", "123123"), WAJIB BERI SKOR 0.0.
        4. Jika cadangan logik dan membina, beri markah munasabah (6.0 - 10.0).
        
        KEMBALIKAN FORMAT JSON SAHAJA:
        {{
          "skor_relevansi": 8.5
        }}
        """
        
        skor_rel = 0.0
        jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
        if jawapan_ai:
            import re
            try:
                match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
                json_str = match.group(0) if match else jawapan_ai
                data_json = json.loads(json_str)
                skor_rel = float(data_json.get("skor_relevansi", 0.0))
            except Exception:
                pass
        
        # --- BLOK SAMPAH KETAT ---
        if skor_rel < 5.0:
            return JsonResponse({"success": True, "mata_diperoleh": 0, "status": "spam"})
        
        # Pengiraan Markah Gred (Max 10 mata)
        mata_diperoleh = int(10 * (skor_rel / 10.0))
        if mata_diperoleh <= 0: mata_diperoleh = 3
        
        StrategyIdea.objects.create(
            user=request.user, 
            strategi=strategi, 
            idea=idea_text,
            skor_relevansi=skor_rel
        )
        
        return JsonResponse({"success": True, "mata_diperoleh": mata_diperoleh, "status": "lulus"})
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})

@login_required
@require_POST
def jana_strategi_penganjur_ajax(request, pk):
    """Penganjur arahkan AI bina roadmap 30 tahun (6 fasa) berdasarkan Teras"""
    import json, requests, re
    from .models import Bengkel, BlueprintPillar, BlueprintStrategy
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    # Tarik Teras yang telah disokong
    pillars = BlueprintPillar.objects.filter(bengkel=bengkel).order_by('urutan')
    if not pillars.exists():
        return JsonResponse({"success": False, "error": "Tiada Teras dijumpai. Sila jana Teras di Langkah 4 terlebih dahulu."})

    teks_teras = "\n".join([f"- {p.teras}: {p.penerangan}" for p in pillars])

    # Prompt yang sangat ketat untuk memaksa AI keluarkan tepat 6 Fasa
    prompt = f"""
    Anda adalah Pakar Perancang Strategi Jangka Panjang.
    TUGASAN: Rangka satu Pelan Induk (Roadmap) 30 Tahun yang dibahagikan kepada ENAM (6) FASA (setiap fasa 5 tahun).
    Gunakan Teras Strategik berikut sebagai panduan:
    {teks_teras}
    
    ARAHAN PENTING:
    1. Hasilkan tepat 6 Fasa secara kronologi (cth: Fasa 1: 2026-2030, Fasa 2: 2031-2035, dan seterusnya).
    2. Susun pelaksanaan secara logik (Contoh: Infrastruktur dan Dasar mesti di Fasa awal, Kematangan AI di Fasa akhir).
    3. Kembalikan HANYA format JSON ARRAY (Tanpa markdown).
    
    Contoh output JSON yang dimahukan:
    [
      {{"urutan": 1, "fasa": "FASA 1 (2026-2030)", "tajuk": "Asas dan Standardisasi", "penerangan": "Membina infrastruktur awan dan...", "teras_berkaitan": "Teras A, Teras B"}},
      {{"urutan": 2, "fasa": "FASA 2 (2031-2035)", "tajuk": "Tajuk Fasa 2", "penerangan": "Huraian...", "teras_berkaitan": "Teras B"}}
    ]
    """

    # MENGGUNAKAN HELPER FUNCTION (DWI-ENJIN)
    jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
    
    if jawapan_ai:
        import re, json
        try:
            match = re.search(r'\[.*\]', jawapan_ai, re.DOTALL)
            if match:
                json_str = match.group(0)
            else:
                json_str = re.sub(r"^```json\s*|```\s*$", "", jawapan_ai, flags=re.MULTILINE).strip()
            
            data_json = json.loads(json_str)
            
            BlueprintStrategy.objects.filter(bengkel=bengkel).delete()
            for item in data_json:
                BlueprintStrategy.objects.create(
                    bengkel=bengkel, 
                    urutan=item.get("urutan", 0),
                    fasa=item.get("fasa", "FASA X"),
                    tajuk=item.get("tajuk", "Tajuk Fasa"),
                    penerangan=item.get("penerangan", ""),
                    teras_berkaitan=item.get("teras_berkaitan", "")
                )
                
            # --- AUTO-UNLOCK LANGKAH 5 MAKRO ---
            if bengkel.blueprint_current_step < 5:
                bengkel.blueprint_current_step = 5
                bengkel.save(update_fields=['blueprint_current_step'])
                
            return JsonResponse({"success": True, "mesej": f"Berjaya merangka Strategi dengan {len(data_json)} Fasa."})
            
        except json.JSONDecodeError:
            return JsonResponse({"success": False, "error": "AI tidak memulangkan format JSON yang sah."})
        except Exception as e:
            return JsonResponse({"success": False, "error": str(e)})
    else:
        return JsonResponse({"success": False, "error": "Sistem AI (OpenAI GPT) gagal merespons."})

@login_required
def peserta_action_plan(request, pk):
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    if bengkel.blueprint_current_step < 6:
        messages.warning(request, "Modul Pelan Tindakan (Langkah 6) belum dibuka oleh Penganjur.")
        return redirect("bengkel:peserta_overview", pk=pk)
        
    from .models import BlueprintStrategy, ActionPlanIdea
    strategies = BlueprintStrategy.objects.filter(bengkel=bengkel).prefetch_related('action_plans', 'ideas').order_by('urutan')
    
    # 🎯 KUNCI: Sedut kesemua idea KPI yang pernah dihantar oleh USER INI untuk tugasan ini
    for s in strategies:
        for task in s.action_plans.all():
            task.my_ideas = ActionPlanIdea.objects.filter(user=request.user, action_plan=task).order_by('-created_at')
            
    return render(request, "bengkel/peserta_action_plan.html", {
        "bengkel": bengkel,
        "strategies": strategies,
    })

@login_required
@require_POST
def simpan_idea_action_plan_ajax(request, pk):
    """Logik untuk peserta menyumbang idea KPI berserta Saringan AI"""
    import json
    from .models import Bengkel, BlueprintActionPlan, ActionPlanIdea
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    try:
        data = json.loads(request.body)
        action_plan_id = data.get("action_plan_id")
        idea_text = data.get("idea", "").strip()
        
        if not idea_text:
            return JsonResponse({"success": False, "error": "Sila masukkan cadangan KPI anda."})
            
        action_plan = get_object_or_404(BlueprintActionPlan, pk=action_plan_id, strategi__bengkel=bengkel)
        
        # --- ENJIN AI: PENAPIS SAMPAH & GAMIFIKASI ---
        prompt = f"""
        Anda adalah Penilai Kualiti yang profesional. Semak cadangan KPI/Indikator yang dihantar oleh pakar lapangan untuk Pelan Tindakan berikut.
        Tindakan Asal: "{action_plan.tajuk}"
        Idea/KPI Peserta: "{idea_text}"
        
        TUGAS PENILAIAN:
        Nilaikan cadangan dari skala 0.0 hingga 10.0 berdasarkan syarat ini:
        1. Adakah ia relevan dengan Tindakan Asal? Ia boleh jadi satu ukuran berangka (KPI), objektif, atau 'deliverable' (hasil kerja seperti kewujudan platform, forum, atau polisi).
        2. Jika teks mengandungi perkataan harian merepek yang TIDAK SESUAI dalam konteks profesional (cth: "ikan", "nasi", "ayam", "kucing", "test"), WAJIB BERI SKOR 0.0.
        3. Jika teks rawak/carutan ("asdasd", "123123"), WAJIB BERI SKOR 0.0.
        4. Jika cadangan logik, relevan, dan membina, beri markah munasabah (6.0 - 10.0). JANGAN tolak idea yang bagus walaupun ia tiada elemen peratusan/nombor.
        
        KEMBALIKAN FORMAT JSON SAHAJA:
        {{
          "skor_relevansi": 8.5
        }}
        """
        
        skor_rel = 0.0
        jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
        if jawapan_ai:
            import re
            try:
                match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
                json_str = match.group(0) if match else jawapan_ai
                data_json = json.loads(json_str)
                skor_rel = float(data_json.get("skor_relevansi", 0.0))
            except Exception:
                pass
        
        # --- BLOK SAMPAH KETAT ---
        if skor_rel < 5.0:
            return JsonResponse({"success": True, "mata_diperoleh": 0, "status": "spam"})
        
        # Pengiraan Markah Gred (Max 10 mata per idea)
        mata_diperoleh = int(10 * (skor_rel / 10.0))
        if mata_diperoleh <= 0: mata_diperoleh = 3
        
        ActionPlanIdea.objects.create(
            user=request.user, 
            action_plan=action_plan, 
            idea=idea_text,
            skor_relevansi=skor_rel
        )
        
        return JsonResponse({"success": True, "mata_diperoleh": mata_diperoleh, "status": "lulus"})
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})

@login_required
@require_POST
def jana_action_plan_penganjur_ajax(request, pk):
    """Penganjur arahkan AI pecahkan Strategi & Idea Peserta kepada Pelan Tindakan terperinci"""
    import json, requests, re
    from .models import Bengkel, BlueprintStrategy, BlueprintActionPlan
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    # Tarik semua Fasa Strategi
    strategies = BlueprintStrategy.objects.filter(bengkel=bengkel).order_by('urutan')
    if not strategies.exists():
        return JsonResponse({"success": False, "error": "Tiada Strategi dijumpai. Sila jana Roadmap di Langkah 5 terlebih dahulu."})

    # Susun maklumat Strategi dan Idea Peserta untuk disuap ke AI
    teks_input = ""
    for s in strategies:
        teks_input += f"\n[ID_FASA: {s.id}] {s.fasa} - {s.tajuk}\n"
        teks_input += f"Objektif: {s.penerangan}\n"
        
        # Tarik idea inisiatif yang Ali/peserta lain masukkan tadi!
        ideas = s.ideas.all()
        if ideas.exists():
            teks_input += "CADANGAN INISIATIF DARI PESERTA (SME):\n"
            for i in ideas:
                teks_input += f"- {i.idea}\n"
        teks_input += "-" * 30

    prompt = f"""
    Anda adalah Pengurus Projek Strategik.
    TUGASAN: Rangka Pelan Tindakan (Action Items) yang praktikal untuk Fasa-Fasa berikut. Serap cadangan inisiatif peserta ke dalam tindakan rasmi jika ia relevan.
    
    MAKLUMAT FASA & CADANGAN PESERTA:
    {teks_input}
    
    ARAHAN PENTING (WAJIB PATUH):
    1. Hasilkan 2 hingga 3 Item Tindakan bagi SETIAP Fasa.
    2. Tentukan 'tahun_pelaksanaan' yang sesuai (cth: "Tahun 1", "Tahun 2-3").
    3. Untuk medan "strategi_id", anda WAJIB menggunakan nombor ID_FASA yang sebenar dari senarai di atas. JANGAN teka nombor urutan (seperti 1, 2, 3), gunakan ID rujukan yang tepat.
    4. Kembalikan HANYA format JSON ARRAY (Tanpa markdown).
    
    Contoh output JSON yang dimahukan:
    [
      {{"strategi_id": [SILA GANTI DENGAN NOMBOR ID_FASA SEBENAR], "tajuk": "Sediakan Kertas Kerja Awan", "penerangan": "Mendraf...", "tahun_pelaksanaan": "Tahun 1"}},
      {{"strategi_id": [SILA GANTI DENGAN NOMBOR ID_FASA SEBENAR], "tajuk": "Naik Taraf Rangkaian", "penerangan": "Memasang...", "tahun_pelaksanaan": "Tahun 2-3"}}
    ]
    """

    # MENGGUNAKAN HELPER FUNCTION (DWI-ENJIN)
    jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
    
    if jawapan_ai:
        import json
        mula = jawapan_ai.find('[')
        tamat = jawapan_ai.rfind(']')
        
        if mula != -1 and tamat != -1 and tamat > mula:
            json_str = jawapan_ai[mula:tamat+1]
        else:
            json_str = jawapan_ai
            
        json_str = json_str.replace("```json", "").replace("```", "").replace("\n", " ").strip()
        
        try:
            data_json = json.loads(json_str)
            BlueprintActionPlan.objects.filter(strategi__bengkel=bengkel).delete()
            
            rekod_berjaya = 0
            # Ambil senarai strategi sedia ada susun mengikut urutan sebagai Fallback
            list_strategi = list(BlueprintStrategy.objects.filter(bengkel=bengkel).order_by('urutan'))
            
            for item in data_json:
                strat_id = item.get("strategi_id")
                strat_obj = None
                
                # CUBAAN 1: Cari guna ID sebenar yang dipulangkan AI
                try:
                    strat_obj = BlueprintStrategy.objects.get(id=strat_id, bengkel=bengkel)
                except (BlueprintStrategy.DoesNotExist, ValueError):
                    # CUBAAN 2 (FALLBACK): Jika AI beri ID 1, 2, 3.. kita padankan dengan indeks (1->index 0)
                    try:
                        idx = int(strat_id) - 1
                        if 0 <= idx < len(list_strategi):
                            strat_obj = list_strategi[idx]
                    except (ValueError, TypeError):
                        pass
                
                # Jika berjaya dijumpai, barulah simpan
                if strat_obj:
                    BlueprintActionPlan.objects.create(
                        strategi=strat_obj,
                        bengkel=bengkel,
                        tajuk=item.get("tajuk", "Tindakan"),
                        penerangan=item.get("penerangan", ""),
                        tahun_pelaksanaan=item.get("tahun_pelaksanaan", "")
                    )
                    rekod_berjaya += 1

            if rekod_berjaya > 0:
                # --- AUTO-UNLOCK LANGKAH 6 MAKRO ---
                if bengkel.blueprint_current_step < 6:
                    bengkel.blueprint_current_step = 6
                    bengkel.save(update_fields=['blueprint_current_step'])
                return JsonResponse({"success": True, "mesej": f"Berjaya merangka dan menyimpan {rekod_berjaya} Item Pelan Tindakan."})
            else:
                return JsonResponse({"success": False, "error": "AI menjana hasil, tetapi gagal dipetakan (mismatch) dengan mana-mana Fasa Strategi."})
                
        except json.JSONDecodeError:
            print("RALAT JSON AI (Action Plan):", json_str)
            return JsonResponse({"success": False, "error": "AI tersilap format JSON."})
        except Exception as e:
            return JsonResponse({"success": False, "error": str(e)})
    else:
        return JsonResponse({"success": False, "error": "Sistem AI (OpenAI GPT) gagal merespons."})

@login_required
def peserta_indikator(request, pk):
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    if bengkel.blueprint_current_step < 7:
        messages.warning(request, "Modul Sasaran KPI (Langkah 7) belum dibuka oleh Penganjur.")
        return redirect("bengkel:peserta_overview", pk=pk)
        
    from .models import BlueprintStrategy, BlueprintIndicatorCalibration
    
    strategies = BlueprintStrategy.objects.filter(bengkel=bengkel).prefetch_related(
        'ideas', 
        'action_plans__indicators'
    ).order_by('urutan')
    
    # 🎯 KUNCI: Tandakan mana KPI yang telah dikalibrasi oleh user ini
    calibrated_kpi_ids = BlueprintIndicatorCalibration.objects.filter(
        user=request.user, 
        kpi__action_plan__strategi__bengkel=bengkel
    ).values_list('kpi_id', flat=True)
    
    for s in strategies:
        for ap in s.action_plans.all():
            for kpi in ap.indicators.all():
                kpi.is_calibrated = kpi.id in calibrated_kpi_ids
    
    return render(request, "bengkel/peserta_indikator.html", {
        "bengkel": bengkel,
        "strategies": strategies,
    })

@login_required
@require_POST
def simpan_kpi_calibration_ajax(request, pk):
    """Logik untuk peserta mengesahkan KPI berserta Saringan AI"""
    import json
    from .models import Bengkel, BlueprintIndicator, BlueprintIndicatorCalibration
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    try:
        data = json.loads(request.body)
        kpi_id = data.get("kpi_id")
        penilaian = data.get("penilaian") # 'ideal', 'ambitious', 'low'
        cadangan = data.get("cadangan", "").strip()
        
        kpi = get_object_or_404(BlueprintIndicator, pk=kpi_id, action_plan__strategi__bengkel=bengkel)
        
        # HALANG DUPLIKASI (Peserta hanya boleh kalibrasi 1 kali sahaja per KPI)
        if BlueprintIndicatorCalibration.objects.filter(user=request.user, kpi=kpi).exists():
            return JsonResponse({"success": False, "error": "Anda telah pun membuat kalibrasi untuk KPI ini."})
        
        mata_diperoleh = 2 # Ganjaran asas yang kecil (hanya untuk klik 'Sesuai')
        
        # --- ENJIN AI: PENAPIS SAMPAH (Hanya jika peserta tidak setuju dan letak cadangan) ---
        if penilaian != 'ideal' and cadangan:
            prompt = f"""
            Anda adalah Penilai Kualiti profesional. Semak cadangan/bantahan sasaran KPI dari pakar.
            KPI Asal: "{kpi.kpi_tajuk}" dengan sasaran "{kpi.sasaran}"
            Ulasan Peserta: "{cadangan}"
            
            TUGAS PENILAIAN:
            1. Adakah cadangan ini merupakan satu ukuran KPI, objektif, atau angka yang lebih munasabah?
            2. Jika teks mengandungi perkataan harian merepek (cth: "ikan", "nasi", "ayam", "kucing", "test"), WAJIB BERI SKOR 0.0.
            3. Jika teks rawak/carutan ("asdasd", "123123"), WAJIB BERI SKOR 0.0.
            4. Jika cadangan logik, beri markah munasabah (6.0 - 10.0). JANGAN tolak cadangan deliverable/objektif.
            
            KEMBALIKAN FORMAT JSON SAHAJA:
            {{
              "skor_relevansi": 8.5
            }}
            """
            jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
            if jawapan_ai:
                import re
                try:
                    match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
                    json_str = match.group(0) if match else jawapan_ai
                    data_json = json.loads(json_str)
                    skor_rel = float(data_json.get("skor_relevansi", 0.0))
                    
                    if skor_rel < 5.0:
                        return JsonResponse({"success": True, "mata_diperoleh": 0, "status": "spam"})
                    
                    # Pengiraan Markah Maksimum 5 untuk usaha berfikir
                    mata_diperoleh = int(5 * (skor_rel / 10.0))
                    if mata_diperoleh <= 0: mata_diperoleh = 2
                except Exception:
                    pass
        
        BlueprintIndicatorCalibration.objects.create(
            user=request.user, 
            kpi=kpi,
            penilaian=penilaian, 
            cadangan_sasaran=cadangan,
            skor_relevansi=skor_rel if (penilaian != 'ideal' and cadangan) else 0.0
        )
        
        return JsonResponse({"success": True, "mata_diperoleh": mata_diperoleh, "status": "lulus"})
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})

@login_required
@require_POST
def simpan_peruntukan_calibration_ajax(request, pk):
    """Menyimpan maklum balas Peruntukan berserta saringan AI Gamifikasi"""
    import json
    from .models import Bengkel, BlueprintActionPlan, AllocationIdea
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    try:
        data = json.loads(request.body)
        ap_id = data.get("alloc_id") 
        penilaian = data.get("penilaian") # setuju, over_budget, under_budget
        cadangan_sme = data.get("cadangan", "").strip()
        
        ap = get_object_or_404(BlueprintActionPlan, pk=ap_id, strategi__bengkel=bengkel)
        
        # HALANG DUPLIKASI
        if AllocationIdea.objects.filter(user=request.user, action_plan=ap).exists():
            return JsonResponse({"success": False, "error": "Anda telah pun membuat kalibrasi sumber untuk tugas ini."})
        
        mata_diperoleh = 2
        skor_rel = 0.0
        
        # --- ENJIN AI: PENAPIS SAMPAH (Hanya jika peserta tidak setuju dan letak cadangan) ---
        if penilaian != 'setuju' and cadangan_sme:
            prompt = f"""
            Anda adalah Penilai Kualiti profesional. Semak cadangan bajet/sumber dari pakar.
            Tugasan Asal: "{ap.tajuk}"
            Ulasan Peserta: "{cadangan_sme}"
            
            TUGAS PENILAIAN:
            1. Adakah cadangan ini merupakan satu cadangan sumber (Kewangan/Fasiliti/Manusia) yang logik?
            2. Jika teks mengandungi perkataan harian merepek (cth: "ikan", "nasi", "ayam", "kucing", "test"), WAJIB BERI SKOR 0.0.
            3. Jika teks rawak/carutan ("asdasd", "123123"), WAJIB BERI SKOR 0.0.
            4. Jika logik, beri markah munasabah (6.0 - 10.0).
            
            KEMBALIKAN FORMAT JSON SAHAJA:
            {{
              "skor_relevansi": 8.5
            }}
            """
            jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
            if jawapan_ai:
                import re
                try:
                    match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
                    json_str = match.group(0) if match else jawapan_ai
                    data_json = json.loads(json_str)
                    skor_rel = float(data_json.get("skor_relevansi", 0.0))
                    
                    if skor_rel < 5.0:
                        return JsonResponse({"success": True, "mata_diperoleh": 0, "status": "spam"})
                    
                    mata_diperoleh = int(5 * (skor_rel / 10.0))
                    if mata_diperoleh <= 0: mata_diperoleh = 2
                except Exception:
                    pass
        
        tag_penilaian = "[SETUJU]"
        if penilaian == 'over_budget': tag_penilaian = "[TERLALU TINGGI]"
        elif penilaian == 'under_budget': tag_penilaian = "[TERLALU RENDAH]"
        
        teks_idea = f"{tag_penilaian}"
        if cadangan_sme: teks_idea += f" {cadangan_sme}"

        AllocationIdea.objects.create(
            user=request.user,
            action_plan=ap, 
            idea_sumber=teks_idea,
            skor_relevansi=skor_rel if cadangan_sme else 0.0
        )

        return JsonResponse({"success": True, "mata_diperoleh": mata_diperoleh, "status": "lulus"})
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})

@login_required
@require_POST
def murnikan_peruntukan_ai_ajax(request, pk):
    """Mengarahkan AI memurnikan Anggaran Sumber berdasarkan bantahan SME"""
    import json
    from .models import Bengkel, BlueprintAllocation
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    try:
        data = json.loads(request.body)
        alloc_id = data.get("alloc_id")
        alloc_obj = get_object_or_404(BlueprintAllocation, pk=alloc_id, bengkel=bengkel)
        
        ideas = alloc_obj.action_plan.allocation_ideas.all()
        if not ideas.exists():
            return JsonResponse({"success": False, "error": "Tiada bantahan/idea baharu untuk disintesis."})

        teks_idea = "\n".join([f"- {i.idea_sumber}" for i in ideas])

        prompt = f"""
        Anda Pakar Perolehan Sektor Awam. Profil peruntukan asal:
        - Kategori: {alloc_obj.kategori_sumber}
        - Anggaran Semasa: {alloc_obj.anggaran_keperluan}
        - Justifikasi Semasa: {alloc_obj.justifikasi}
        
        Terdapat bantahan/cadangan realiti dari pakar (SME):
        {teks_idea[:3000]}
        
        TUGAS: Rombak 'Anggaran' dan 'Justifikasi' asal untuk menyerap maklum balas SME di atas supaya lebih realistik.
        
        KEMBALIKAN OUTPUT JSON SAHAJA:
        {{
          "anggaran_baharu": "Skala (Besar/Sederhana/Kecil) dan ringkasan keperluan",
          "justifikasi_baharu": "Huraian ringkas yang telah dibaiki"
        }}
        """

        jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
        if jawapan_ai:
            import re
            try:
                match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
                json_str = match.group(0) if match else jawapan_ai
                data_json = json.loads(json_str)
                
                alloc_obj.anggaran_keperluan = data_json.get("anggaran_baharu", alloc_obj.anggaran_keperluan)
                alloc_obj.justifikasi = data_json.get("justifikasi_baharu", alloc_obj.justifikasi)
                alloc_obj.save()
                return JsonResponse({"success": True})
            except Exception:
                return JsonResponse({"success": False, "error": "Ralat format AI."})
        return JsonResponse({"success": False, "error": "AI tidak merespons."})
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})

@login_required
@require_POST
def jana_indikator_penganjur_ajax(request, pk):
    """Penganjur mengarahkan AI untuk menjana KPI bagi setiap Pelan Tindakan"""
    import json, requests, re
    from .models import Bengkel, BlueprintActionPlan, BlueprintIndicator
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    # Tarik semua Pelan Tindakan yang telah dijana
    action_plans = BlueprintActionPlan.objects.filter(strategi__bengkel=bengkel).order_by('strategi__urutan', 'id')
    
    if not action_plans.exists():
        return JsonResponse({"success": False, "error": "Tiada Pelan Tindakan dijumpai. Sila jana di Langkah 6 terlebih dahulu."})

    # Susun maklumat Pelan Tindakan dan Idea Peserta untuk disuap ke AI
    teks_input = ""
    for ap in action_plans:
        teks_input += f"\n[ID_TINDAKAN: {ap.id}] {ap.tajuk}\n"
        teks_input += f"Penerangan: {ap.penerangan}\n"
        
        # Tarik idea KPI yang Ali/peserta sumbangkan di Node 6
        ideas = ap.ideas.all()
        if ideas.exists():
            teks_input += "CADANGAN KPI DARI PESERTA (SME):\n"
            for i in ideas:
                teks_input += f"- {i.idea}\n"
        teks_input += "-" * 30

    prompt = f"""
    Anda adalah Pakar Pengukuran Prestasi (M&E) Sektor Awam.
    TUGASAN: Rangka Indikator Prestasi Utama (KPI) yang boleh diukur (measurable) bagi setiap Pelan Tindakan berikut.
    Gunakan cadangan peserta sebagai asas jika ia logik dan relevan.
    
    MAKLUMAT PELAN TINDAKAN & CADANGAN KPI:
    {teks_input}
    
    ARAHAN PENTING:
    1. Hasilkan tepat SATU atau DUA KPI bagi SETIAP Pelan Tindakan.
    2. Berikan sasaran (target) yang jelas (contoh: "100% Siap", "50,000 staf dilatih", "Q3 2026"). PASTIKAN teks sasaran SANGAT PENDEK (maksimum 15 patah perkataan).
    3. Tentukan kekerapan ukuran (contoh: "Suku Tahun", "Tahunan").
    4. Kembalikan HANYA format JSON ARRAY (Tanpa markdown). Pastikan 'action_plan_id' sepadan dengan ID_TINDAKAN.
    
    Contoh output JSON yang dimahukan:
    [
      {{"action_plan_id": 1, "kpi_tajuk": "Peratusan Hospital yang Siap Dinaik Taraf", "sasaran": "100%", "kekerapan_ukuran": "Tahunan"}},
      {{"action_plan_id": 2, "kpi_tajuk": "Bilangan Modul Siap Digubal", "sasaran": "5 Modul Utama", "kekerapan_ukuran": "Suku Tahun"}}
    ]
    """

    # MENGGUNAKAN HELPER FUNCTION (DWI-ENJIN)
    jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
    
    if jawapan_ai:
        import json, re
        data_json = []
        teks_bersih = re.sub(r'[\r\n\t]+', ' ', jawapan_ai).replace("'", '"').replace('`', '"')
        pattern = re.compile(r'\{[^{}]*"action_plan_id"[^{}]*\}')
        matches = pattern.findall(teks_bersih)
        
        for match in matches:
            try:
                item = json.loads(match)
                if "action_plan_id" in item and "kpi_tajuk" in item:
                    item["action_plan_id"] = int(item["action_plan_id"])
                    data_json.append(item)
            except Exception:
                continue 
        
        if not data_json:
            print("RALAT AI (GAGAL PARSE KPI):", jawapan_ai)
            return JsonResponse({"success": False, "error": "AI gagal memulangkan format JSON KPI."})
        
        BlueprintIndicator.objects.filter(action_plan__strategi__bengkel=bengkel).delete()
        rekod_berjaya = 0
        for item in data_json:
            try:
                ap_obj = BlueprintActionPlan.objects.get(id=item.get("action_plan_id"), strategi__bengkel=bengkel)
                BlueprintIndicator.objects.create(
                    bengkel=bengkel,
                    action_plan=ap_obj,
                    kpi_tajuk=str(item.get("kpi_tajuk", "KPI Umum")),
                    sasaran=str(item.get("sasaran", "TBD")),
                    kekerapan_ukuran=str(item.get("kekerapan_ukuran", "Tahunan"))
                )
                rekod_berjaya += 1
            except BlueprintActionPlan.DoesNotExist:
                continue
                
        if rekod_berjaya > 0:
            # --- AUTO-UNLOCK LANGKAH 7 MAKRO ---
            if bengkel.blueprint_current_step < 7:
                bengkel.blueprint_current_step = 7
                bengkel.save(update_fields=['blueprint_current_step'])
            return JsonResponse({"success": True, "mesej": f"Berjaya menjana {rekod_berjaya} Indikator KPI Utama."})
        else:
            return JsonResponse({"success": False, "error": "AI menjana KPI, tetapi ID Tindakan tidak sah."})
    else:
        return JsonResponse({"success": False, "error": "Sistem AI (OpenAI GPT) gagal merespons."})

@login_required
def peserta_peruntukan(request, pk):
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    if bengkel.blueprint_current_step < 8:
        messages.warning(request, "Modul Peruntukan Sumber (Langkah 8) belum dibuka oleh Penganjur.")
        return redirect("bengkel:peserta_overview", pk=pk)
        
    from .models import BlueprintStrategy, BlueprintAllocation, AllocationIdea
    
    strategies = BlueprintStrategy.objects.filter(bengkel=bengkel).prefetch_related('action_plans__allocations').order_by('urutan')
    ada_peruntukan = BlueprintAllocation.objects.filter(bengkel=bengkel).exists()
    
    # 🎯 KUNCI: Tandakan mana Peruntukan yang telah dikalibrasi (disumbang idea) oleh user ini
    calibrated_ap_ids = AllocationIdea.objects.filter(
        user=request.user, 
        action_plan__strategi__bengkel=bengkel
    ).values_list('action_plan_id', flat=True)
    
    for s in strategies:
        for ap in s.action_plans.all():
            ap.is_calibrated = ap.id in calibrated_ap_ids
            # Sedut log idea sumber jika ada
            ap.my_ideas = AllocationIdea.objects.filter(user=request.user, action_plan=ap).order_by('-created_at')
    
    return render(request, "bengkel/peserta_peruntukan.html", {
        "bengkel": bengkel,
        "strategies": strategies,
        "ada_peruntukan": ada_peruntukan,
    })

@login_required
@require_POST
def simpan_idea_peruntukan_ajax(request, pk):
    """Logik untuk peserta menyumbang idea keperluan sumber"""
    import json
    from .models import Bengkel, BlueprintActionPlan, AllocationIdea
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    try:
        data = json.loads(request.body)
        action_plan_id = data.get("action_plan_id")
        idea_text = data.get("idea", "").strip()
        
        if not idea_text:
            return JsonResponse({"success": False, "error": "Sila masukkan cadangan sumber anda."})
            
        action_plan = get_object_or_404(BlueprintActionPlan, pk=action_plan_id, strategi__bengkel=bengkel)
        
        # Simpan DNA Idea Sumber (Contoh: Bajet, Staf)
        AllocationIdea.objects.create(user=request.user, action_plan=action_plan, idea_sumber=idea_text)
        
        return JsonResponse({"success": True})
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})

@login_required
@require_POST
def jana_peruntukan_penganjur_ajax(request, pk):
    """Penganjur arahkan AI merangka Peruntukan Sumber (Bajet/Manusia) bagi setiap Tindakan"""
    import json, requests, re
    from .models import Bengkel, BlueprintActionPlan, BlueprintAllocation
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    action_plans = BlueprintActionPlan.objects.filter(strategi__bengkel=bengkel).order_by('strategi__urutan', 'id')
    if not action_plans.exists():
        return JsonResponse({"success": False, "error": "Tiada Pelan Tindakan dijumpai."})

    # Susun input untuk AI
    teks_input = ""
    for ap in action_plans:
        teks_input += f"\n[ID_TINDAKAN: {ap.id}] {ap.tajuk}\n"
        
        # Masukkan idea sumber dari peserta jika ada
        ideas = ap.allocation_ideas.all()
        if ideas.exists():
            teks_input += "CADANGAN SUMBER DARI PESERTA (SME):\n"
            for i in ideas:
                teks_input += f"- {i.idea_sumber}\n"
        teks_input += "-" * 30

    prompt = f"""
    Anda adalah Pakar Pengurusan Sumber dan Perolehan Sektor Awam.
    TUGASAN: Analisis cadangan sumber dari pakar lapangan (SME) bagi setiap Pelan Tindakan. 
    Rangka SATU profil peruntukan yang realistik tanpa membuat andaian nilai Ringgit (RM) yang semberono.
    
    MAKLUMAT PELAN TINDAKAN & CADANGAN PESERTA:
    {teks_input}
    
    ARAHAN PENTING (WAJIB PATUH):
    1. Hasilkan tepat SATU profil peruntukan utama bagi SETIAP Pelan Tindakan.
    2. 'kategori_sumber' MESTILAH salah satu dari ini: "Kewangan (Bajet/Geran)", "Tenaga Manusia (Perjawatan)", "Teknologi (Sistem/Lesen)", atau "Fasiliti (Infrastruktur)".
    3. 'anggaran_keperluan' JANGAN letak nilai RM. Gantikan dengan SKALA (Skala Besar / Skala Sederhana / Skala Kecil) beserta ringkasan pendek (Contoh: "Skala Besar: Kepakaran AI & Server Cloud").
    4. Kembalikan HANYA format JSON ARRAY (Tanpa markdown).
    
    Contoh output JSON:
    [
      {{"action_plan_id": 1, "kategori_sumber": "Teknologi (Sistem/Lesen)", "anggaran_keperluan": "Skala Sederhana: Sistem Pemantauan IoT", "justifikasi": "Fokus pada naik taraf perisian sedia ada berdasarkan cadangan SME."}},
      {{"action_plan_id": 2, "kategori_sumber": "Tenaga Manusia (Perjawatan)", "anggaran_keperluan": "Skala Besar: Pasukan Data Saintis", "justifikasi": "SME menekankan keperluan tenaga kerja teknikal berbanding bajet fizikal."}}
    ]
    """

    # MENGGUNAKAN HELPER FUNCTION (DWI-ENJIN)
    jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
    
    if jawapan_ai:
        import json, re
        data_json = []
        teks_bersih = re.sub(r'[\r\n\t]+', ' ', jawapan_ai).replace("'", '"').replace('`', '"')
        pattern = re.compile(r'\{[^{}]*"action_plan_id"[^{}]*\}')
        matches = pattern.findall(teks_bersih)
        
        for match in matches:
            try:
                item = json.loads(match)
                if "action_plan_id" in item:
                    item["action_plan_id"] = int(item["action_plan_id"])
                    data_json.append(item)
            except Exception:
                continue
                
        if not data_json:
            print("RALAT AI (PERUNTUKAN):", jawapan_ai)
            return JsonResponse({"success": False, "error": "AI gagal memberikan format JSON yang sah."})
        
        BlueprintAllocation.objects.filter(action_plan__strategi__bengkel=bengkel).delete()
        rekod_berjaya = 0
        for item in data_json:
            try:
                ap_obj = BlueprintActionPlan.objects.get(id=item.get("action_plan_id"), strategi__bengkel=bengkel)
                BlueprintAllocation.objects.create(
                    action_plan=ap_obj,
                    bengkel=bengkel,
                    kategori_sumber=str(item.get("kategori_sumber", "Lain-lain")),
                    anggaran_keperluan=str(item.get("anggaran_keperluan", "TBD")),
                    justifikasi=str(item.get("justifikasi", ""))
                )
                rekod_berjaya += 1
            except BlueprintActionPlan.DoesNotExist:
                continue
                
        if rekod_berjaya > 0:
            # --- AUTO-UNLOCK LANGKAH 8 MAKRO ---
            if bengkel.blueprint_current_step < 8:
                bengkel.blueprint_current_step = 8
                bengkel.save(update_fields=['blueprint_current_step'])
            return JsonResponse({"success": True, "mesej": f"Berjaya merangka {rekod_berjaya} Peruntukan Sumber."})
        else:
            return JsonResponse({"success": False, "error": "Tugas dijana tetapi ID tidak berpadanan."})
    else:
        return JsonResponse({"success": False, "error": "Sistem AI (OpenAI GPT) gagal merespons."})
#----------------------------------------------------------------------------

def overview_map(request, pk):
    bengkel = get_object_or_404(Bengkel, pk=pk)
    return render(request, "bengkel/overview_map.html", {"bengkel": bengkel})

# ========================================================
# VIEWS UNTUK PAPARAN ANALISIS PENGANJUR (COMMAND CENTER)
# ========================================================

@login_required
def analisis_korpus(request, pk):
    import json # Wajib import json untuk hantar data Array ke Chart.js
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # 1. Tarik semua fail peserta untuk bengkel ini
    fail_peserta = ContributionFile.objects.filter(contribution__bengkel=bengkel)
    
    # 2. Kira Jumlah Dokumen & Purata
    jumlah_dokumen = fail_peserta.count()
    purata_novelty = fail_peserta.aggregate(Avg('skor_novelty'))['skor_novelty__avg'] or 0.0
    purata_actionability = fail_peserta.aggregate(Avg('indeks_kebolehtindakan'))['indeks_kebolehtindakan__avg'] or 0
    
    # Kiraan Format Fail
    fail_pdf = fail_peserta.filter(original_name__icontains='.pdf').count()
    fail_docx = fail_peserta.filter(original_name__icontains='.doc').count()
    fail_xlsx = fail_peserta.filter(original_name__icontains='.xls').count()
    
    pct_pdf = int((fail_pdf / jumlah_dokumen) * 100) if jumlah_dokumen > 0 else 0
    pct_docx = int((fail_docx / jumlah_dokumen) * 100) if jumlah_dokumen > 0 else 0
    pct_xlsx = int((fail_xlsx / jumlah_dokumen) * 100) if jumlah_dokumen > 0 else 0

    # 3. KIRAAN UNTUK GRAF 1: Tema Wacana Dominan & Pemetaan Domain
    # Kumpulkan tema, kira jumlahnya, dan susun dari paling banyak ke sikit
    tema_qs = fail_peserta.exclude(tema_utama__isnull=True).exclude(tema_utama="").values('tema_utama').annotate(count=Count('id')).order_by('-count')[:6]
    
    tema_labels = [item['tema_utama'] for item in tema_qs] if tema_qs else ['Belum Ada Data']
    tema_data = [item['count'] for item in tema_qs] if tema_qs else [0]
    
    # Guna data yang sama (Top 5) untuk Radar Chart (Domain)
    domain_labels = [item['tema_utama'] for item in tema_qs[:5]] if tema_qs else ['Klinikal', 'IT', 'Polisi', 'Kewangan', 'Tadbir']
    domain_data = [item['count'] for item in tema_qs[:5]] if tema_qs else []

    # 4. KIRAAN UNTUK GRAF 2: Analisis Nada (Doughnut Chart)
    positif = fail_peserta.filter(nada_dokumen__icontains='Positif').count()
    negatif = fail_peserta.filter(nada_dokumen__icontains='Negatif').count()
    neutral = fail_peserta.filter(nada_dokumen__icontains='Neutral').count()
    nada_data = [positif, negatif, neutral]

    # Peratusan Nada untuk Modal Actionability
    total_nada = positif + negatif + neutral or 1
    pct_positif = int((positif / total_nada) * 100) if jumlah_dokumen > 0 else 0
    pct_negatif = int((negatif / total_nada) * 100) if jumlah_dokumen > 0 else 0

    # 5. KIRAAN UNTUK BAR PROGRES: Kompleksiti Kognitif
    holistik = fail_peserta.filter(kompleksiti_kognitif__icontains='Sistem').count()
    silo = fail_peserta.filter(kompleksiti_kognitif__icontains='Linear').count()
    konseptual = fail_peserta.filter(kompleksiti_kognitif__icontains='Berwawasan').count()
    total_kognitif = holistik + silo + konseptual or 1 # Elak error bahagi dengan sifar

    pct_holistik = int((holistik / total_kognitif) * 100) if jumlah_dokumen > 0 else 0
    pct_silo = int((silo / total_kognitif) * 100) if jumlah_dokumen > 0 else 0
    pct_konseptual = int((konseptual / total_kognitif) * 100) if jumlah_dokumen > 0 else 0

    # Teks Dinamik untuk Skor Novelty (Kalis Ralat)
    nilai_novelty = float(purata_novelty) if purata_novelty is not None else 0.0

    if nilai_novelty >= 8.0:
        teks_novelty = "dokumen ini mengandungi pengetahuan tempatan dan spesifik organisasi yang sangat khusus yang tidak diketahui oleh AI umum."
    elif nilai_novelty >= 5.0:
        teks_novelty = "dokumen ini mengandungi gabungan panduan umum dan sedikit maklumat khusus jabatan."
    else:
        teks_novelty = "dokumen ini bersifat generik (seperti definisi atau panduan awam) yang sudah difahami oleh model AI umum."

    # --- TAMBAHAN BARU: Status Pemprosesan & Dokumen Top ---
    dokumen_diproses = fail_peserta.exclude(skor_novelty=0.0).count()
    peratus_proses = int((dokumen_diproses / jumlah_dokumen) * 100) if jumlah_dokumen > 0 else 0
    dokumen_top = fail_peserta.exclude(skor_novelty=0.0).order_by('-indeks_kebolehtindakan')[:3]

    # Sediakan data untuk dihantar ke HTML
    context = {
        'dokumen_diproses': dokumen_diproses,
        'peratus_proses': peratus_proses,
        'dokumen_top': dokumen_top,
        'bengkel': bengkel,
        'jumlah_dokumen': jumlah_dokumen,
        'purata_novelty': round(purata_novelty, 1), 
        'purata_actionability': int(purata_actionability), 
        
        # Data untuk Graf (Ditukar ke format JSON String)
        'tema_labels': json.dumps(tema_labels),
        'tema_data': json.dumps(tema_data),
        'domain_labels': json.dumps(domain_labels),
        'domain_data': json.dumps(domain_data),
        'nada_data': json.dumps(nada_data),

        # Data untuk Bar Progres Kognitif
        'pct_holistik': pct_holistik,
        'pct_silo': pct_silo,
        'pct_konseptual': pct_konseptual,

        # Data untuk Modal Audit Format
        'fail_pdf': fail_pdf, 'pct_pdf': pct_pdf,
        'fail_docx': fail_docx, 'pct_docx': pct_docx,
        'fail_xlsx': fail_xlsx, 'pct_xlsx': pct_xlsx,

        # Data untuk Modal Actionability & Novelty
        'pct_positif': pct_positif,
        'pct_negatif': pct_negatif,
        'teks_novelty': teks_novelty,
    }
    
    return render(request, 'bengkel/analisis_korpus.html', context)

def analisis_spaf(request, pk):
    bengkel = get_object_or_404(Bengkel, pk=pk)
    # Tukar kepada analisis_spaf.html
    return render(request, "bengkel/analisis_spaf.html", {"bengkel": bengkel})

@login_required
def analisis_tema(request, pk):
    import json
    from django.db.models import Count
    from .models import Bengkel, BlueprintTheme, ThemeVote, ThemeIdea
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # Semak akses (Hanya penganjur dibenarkan)
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        messages.error(request, "Anda tidak mempunyai akses ke analitik ini.")
        return redirect("bengkel:dashboard")

    # 1. Tarik senarai Tema dari pangkalan data
    senarai_tema = BlueprintTheme.objects.filter(bengkel=bengkel).prefetch_related('votes', 'ideas').order_by('urutan')
    jumlah_tema = senarai_tema.count()

    # 2. Kira jumlah keseluruhan undian dan idea (Jejak DNA)
    jumlah_undi = ThemeVote.objects.filter(tema__bengkel=bengkel).count()
    jumlah_idea = ThemeIdea.objects.filter(tema__bengkel=bengkel).count()

    # 3. Sediakan data untuk graf & gelungan HTML
    graf_labels = []
    graf_data = []
    tema_data = []
    
    for t in senarai_tema:
        undi_count = t.votes.count()
        graf_labels.append(f"TEMA-0{t.urutan}") # Label pendek untuk graf
        graf_data.append(undi_count)
        
        tema_data.append({
            'obj': t,
            'undi': undi_count,
            'ideas': t.ideas.all().select_related('user').order_by('-created_at') # Tarik idea milik tema ini
        })

    # Data log penuh untuk Modal
    semua_idea = ThemeIdea.objects.filter(tema__bengkel=bengkel).select_related('user', 'tema').order_by('-created_at')

    context = {
        "bengkel": bengkel,
        "jumlah_tema": jumlah_tema,
        "jumlah_undi": jumlah_undi,
        "jumlah_idea": jumlah_idea,
        "tema_data": tema_data,
        "semua_idea": semua_idea,
        "graf_labels_json": json.dumps(graf_labels),
        "graf_data_json": json.dumps(graf_data),
    }
        
    return render(request, "bengkel/analisis_tema.html", context)

@login_required
def analisis_pillar(request, pk):
    import json
    from django.db.models import Count
    from .models import Bengkel, BlueprintPillar, PillarVote, PillarIdea
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # Semak akses (Hanya penganjur dibenarkan)
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        messages.error(request, "Anda tidak mempunyai akses ke analitik ini.")
        return redirect("bengkel:dashboard")

    # 1. Tarik senarai Teras Strategik dari pangkalan data
    senarai_pillar = BlueprintPillar.objects.filter(bengkel=bengkel).prefetch_related('votes', 'ideas').order_by('urutan')
    jumlah_pillar = senarai_pillar.count()

    # 2. Kira jumlah keseluruhan sokongan (undian) dan idea skop
    jumlah_sokongan = PillarVote.objects.filter(pillar__bengkel=bengkel).count()
    jumlah_idea = PillarIdea.objects.filter(pillar__bengkel=bengkel).count()

    # 3. Sediakan data untuk graf & struktur HTML
    graf_labels = []
    graf_data = []
    pillar_data = []
    
    for p in senarai_pillar:
        undi_count = p.votes.count()
        graf_labels.append(f"TERAS-0{p.urutan}")
        graf_data.append(undi_count)
        
        # Tema_dirangkumi selalunya disimpan sebagai string "Tema 1, Tema 2"
        # Kita pecahkan menjadi senarai (list) supaya cantik dipaparkan di kotak-kotak kecil UI
        tema_list = [t.strip() for t in p.tema_dirangkumi.split(',')] if p.tema_dirangkumi else []

        pillar_data.append({
            'obj': p,
            'undi': undi_count,
            'tema_list': tema_list,
            'ideas': p.ideas.all().select_related('user').order_by('-created_at')
        })

    # 4. Tarik semua log idea untuk paparan di Modal (Audit Trail)
    semua_idea = PillarIdea.objects.filter(pillar__bengkel=bengkel).select_related('user', 'pillar').order_by('-created_at')

    context = {
        "bengkel": bengkel,
        "jumlah_pillar": jumlah_pillar,
        "jumlah_sokongan": jumlah_sokongan,
        "jumlah_idea": jumlah_idea,
        "pillar_data": pillar_data,
        "semua_idea": semua_idea,
        "graf_labels_json": json.dumps(graf_labels),
        "graf_data_json": json.dumps(graf_data),
    }
        
    return render(request, "bengkel/analisis_pillar.html", context)

@login_required
def analisis_strategi(request, pk):
    import json
    from .models import Bengkel, BlueprintPillar, BlueprintStrategy, StrategyIdea
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # Semak akses (Hanya penganjur dibenarkan)
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        messages.error(request, "Anda tidak mempunyai akses ke analitik ini.")
        return redirect("bengkel:dashboard")

    # 1. Jumlah Teras (Pillars)
    jumlah_pillar = BlueprintPillar.objects.filter(bengkel=bengkel).count()

    # 2. Tarik Senarai Strategi Berfasa (Roadmap)
    senarai_strategi = BlueprintStrategy.objects.filter(bengkel=bengkel).prefetch_related('ideas').order_by('urutan')
    
    # 3. Jumlah Inisiatif (Idea Projek) yang disumbangkan oleh peserta
    jumlah_idea = StrategyIdea.objects.filter(strategi__bengkel=bengkel).count()

    # 4. Sediakan data untuk UI dan Graf
    strategi_data = []
    graf_labels = []
    graf_data = []

    for s in senarai_strategi:
        idea_count = s.ideas.count()
        # Pecahkan rentetan "Teras A, Teras B" kepada senarai
        teras_list = [t.strip() for t in s.teras_berkaitan.split(',')] if s.teras_berkaitan else []
        
        strategi_data.append({
            'obj': s,
            'teras_list': teras_list,
            'ideas': s.ideas.all().select_related('user').order_by('-created_at')
        })
        
        graf_labels.append(s.fasa[:15] + "...") # Ambil nama fasa pendek
        
        # Graf menunjukkan intensiti berdasarkan bilangan cadangan inisiatif peserta
        graf_data.append(idea_count * 15 + 20) # +20 sebagai 'baseline' supaya graf tidak cecah lantai

    # Jika graf kosong (tiada data), kita masukkan lengkung unjuran asas
    if sum(graf_data) == len(graf_data) * 20:
        graf_data = [40, 70, 95, 80, 65, 50] 

    semua_idea = StrategyIdea.objects.filter(strategi__bengkel=bengkel).select_related('user', 'strategi').order_by('-created_at')

    context = {
        "bengkel": bengkel,
        "jumlah_pillar": jumlah_pillar,
        "jumlah_idea": jumlah_idea,
        "strategi_data": strategi_data,
        "semua_idea": semua_idea,
        "graf_labels_json": json.dumps(graf_labels),
        "graf_data_json": json.dumps(graf_data),
    }
        
    return render(request, "bengkel/analisis_strategi.html", context)

@login_required
def analisis_action_plan(request, pk):
    import json
    from .models import Bengkel, BlueprintStrategy, BlueprintActionPlan, ActionPlanIdea
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # Semak akses (Hanya penganjur dibenarkan)
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        messages.error(request, "Anda tidak mempunyai akses ke analitik ini.")
        return redirect("bengkel:dashboard")

    # 1. Tarik Senarai Strategi beserta Pelan Tindakan dan Idea KPI Peserta
    senarai_strategi = BlueprintStrategy.objects.filter(bengkel=bengkel).prefetch_related(
        'action_plans__ideas'
    ).order_by('urutan')

    # 2. Pengiraan Statistik Utama
    jumlah_tindakan = BlueprintActionPlan.objects.filter(strategi__bengkel=bengkel).count()
    jumlah_idea_kpi = ActionPlanIdea.objects.filter(action_plan__strategi__bengkel=bengkel).count()

    # 3. Data Carta (Taburan Beban Kerja Mengikut Fasa)
    graf_labels = []
    graf_data = []
    
    for s in senarai_strategi:
        graf_labels.append(s.fasa)
        graf_data.append(s.action_plans.count())

    context = {
        "bengkel": bengkel,
        "senarai_strategi": senarai_strategi,
        "jumlah_tindakan": jumlah_tindakan,
        "jumlah_idea_kpi": jumlah_idea_kpi,
        "graf_labels_json": json.dumps(graf_labels),
        "graf_data_json": json.dumps(graf_data),
    }
        
    return render(request, "bengkel/analisis_action_plan.html", context)

@login_required
def analisis_indikator(request, pk):
    import json
    from .models import Bengkel, BlueprintIndicator, ActionPlanIdea
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # Semak akses (Hanya penganjur dibenarkan)
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        messages.error(request, "Anda tidak mempunyai akses ke analitik ini.")
        return redirect("bengkel:dashboard")

    # 1. Tarik Senarai KPI yang telah dijana AI
    senarai_kpi = BlueprintIndicator.objects.filter(
        action_plan__strategi__bengkel=bengkel
    ).select_related('action_plan__strategi').order_by('action_plan__strategi__urutan', 'id')
    
    jumlah_kpi = senarai_kpi.count()

    # 2. Tarik jumlah Idea Pengukuran (DNA KPI) dari peserta
    jumlah_idea_kpi = ActionPlanIdea.objects.filter(action_plan__strategi__bengkel=bengkel).count()

    # 3. Data Carta (Simulasi / Unjuran Pemantauan Masa Depan)
    graf_compliance_data = [86, 10, 4] 
    graf_tools_submitted = [12, 19, 15, 22, 30, 25]
    graf_tools_approved = [2, 5, 8, 11, 15, 18]

    context = {
        "bengkel": bengkel,
        "senarai_kpi": senarai_kpi,
        "jumlah_kpi": jumlah_kpi,
        "jumlah_idea_kpi": jumlah_idea_kpi,
        "graf_compliance_json": json.dumps(graf_compliance_data),
        "graf_tools_sub_json": json.dumps(graf_tools_submitted),
        "graf_tools_app_json": json.dumps(graf_tools_approved),
    }
        
    return render(request, "bengkel/analisis_indikator.html", context)

@login_required
def analisis_peruntukan(request, pk):
    import json
    from django.db.models import Count
    from .models import Bengkel, BlueprintStrategy, BlueprintAllocation, AllocationIdea
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        messages.error(request, "Anda tidak mempunyai akses ke analitik ini.")
        return redirect("bengkel:dashboard")

    senarai_strategi = BlueprintStrategy.objects.filter(bengkel=bengkel).prefetch_related(
        'action_plans__allocations', 'action_plans__allocation_ideas'
    ).order_by('urutan')

    jumlah_peruntukan = BlueprintAllocation.objects.filter(bengkel=bengkel).count()
    jumlah_idea_sumber = AllocationIdea.objects.filter(action_plan__strategi__bengkel=bengkel).count()

    # Data Graf Donut (Kategori Sumber)
    kategori_qs = BlueprintAllocation.objects.filter(bengkel=bengkel).values('kategori_sumber').annotate(jumlah=Count('id'))
    kategori_labels = [k['kategori_sumber'] or "Lain-lain" for k in kategori_qs]
    kategori_data = [k['jumlah'] for k in kategori_qs]
        
    if not kategori_labels:
        kategori_labels = ["Kewangan", "Tenaga Manusia", "Teknologi", "Fasiliti"]
        kategori_data = [0, 0, 0, 0]

    # --- TAMBAHAN BARU: Data Graf Bar (CAPEX & OPEX Projection) ---
    graf_phases_labels = []
    graf_capex_data = []
    graf_opex_data = []

    for s in senarai_strategi:
        # Ambil 2 huruf pertama fasa, cth: "FASA 1" jadi "P1" (Phase 1)
        nama_pendek = f"P{s.urutan}" if s.urutan else s.fasa[:2]
        graf_phases_labels.append(nama_pendek)
        
        # Unjuran dinamik: Makin banyak peruntukan dalam fasa ini, makin tinggi graf bar
        alloc_count = BlueprintAllocation.objects.filter(action_plan__strategi=s).count()
        
        # Formula unjuran bajet (dalam Juta)
        capex = (alloc_count * 45) + 150 if alloc_count > 0 else 100
        opex = (alloc_count * 20) + 80 if alloc_count > 0 else 50
        
        graf_capex_data.append(capex)
        graf_opex_data.append(opex)

    # Fallback jika tiada fasa
    if not graf_phases_labels:
        graf_phases_labels = ['P1', 'P2', 'P3', 'P4', 'P5', 'P6']
        graf_capex_data = [250, 350, 400, 300, 200, 100]
        graf_opex_data = [100, 150, 200, 150, 150, 150]

    context = {
        "bengkel": bengkel,
        "senarai_strategi": senarai_strategi,
        "jumlah_peruntukan": jumlah_peruntukan,
        "jumlah_idea_sumber": jumlah_idea_sumber,
        "kategori_labels_json": json.dumps(kategori_labels),
        "kategori_data_json": json.dumps(kategori_data),
        "graf_phases_labels_json": json.dumps(graf_phases_labels),
        "graf_capex_json": json.dumps(graf_capex_data),
        "graf_opex_json": json.dumps(graf_opex_data),
    }
        
    return render(request, "bengkel/analisis_peruntukan.html", context)

#----------------------------------------------------------------------------

@login_required
def analisis_painpoint(request, pk):
    import json
    from django.db.models import Count
    from .models import SpafPainPoint, SpafProblemStatement, Jemputan
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # 1. KIRAAN ADUAN MENTAH & UNIK
    # Tarik semua aduan (Pain Points) KHAS untuk bengkel ini
    aduan_list = SpafPainPoint.objects.filter(bengkel=bengkel)
    
    # Fallback jika tiada data (Untuk tujuan ujian Penganjur)
    if not aduan_list.exists() and (request.user.is_staff or request.user.is_superuser):
        aduan_list = SpafPainPoint.objects.filter(bengkel=bengkel) # Elak fallback ambil semua acara

    aduan_mentah_count = aduan_list.count()
    
    # Kira berapa banyak aduan yang 'unik' (berdasarkan ruangan keterangan)
    masalah_unik_count = aduan_list.values('keterangan').distinct().count()
    
    # Isu berulang adalah baki daripada aduan mentah tolak masalah unik
    isu_berulang_count = aduan_mentah_count - masalah_unik_count

    # 2. KIRAAN KLUSTER AI (Pernyataan Masalah Master)
    # Ini ditarik daripada jadual SpafProblemStatement milik Penganjur (yang dijana oleh butang AI Pukal)
    senarai_kluster = SpafProblemStatement.objects.filter(user=bengkel.created_by, bengkel=bengkel).order_by('id')
    kluster_count = senarai_kluster.count()

    # 3. DATA UNTUK CARTA (Kekerapan Kategori/Domain Aduan Tertinggi)
    # Kita kumpulkan aduan mengikut 'tajuk' (yang mewakili domain dalam sistem ini)
    graf_data_qs = aduan_list.values('tajuk').annotate(jumlah=Count('id')).order_by('-jumlah')[:6]
    
    graf_labels = [item['tajuk'] for item in graf_data_qs] if graf_data_qs else ['Tiada Data Rungutan']
    graf_data = [item['jumlah'] for item in graf_data_qs] if graf_data_qs else [0]

    context = {
        "bengkel": bengkel,
        "aduan_mentah_count": aduan_mentah_count,
        "isu_berulang_count": isu_berulang_count,
        "masalah_unik_count": masalah_unik_count,
        "kluster_count": kluster_count,
        "senarai_kluster": senarai_kluster,
        # Format JSON untuk JavaScript Chart.js
        "graf_labels_json": json.dumps(graf_labels),
        "graf_data_json": json.dumps(graf_data),
    }
    return render(request, "bengkel/analisis_painpoint.html", context)

@login_required
def analisis_problem(request, pk):
    import json
    from django.db.models import Count
    from .models import Bengkel, SpafProblemStatement, SpafPainPoint, Jemputan
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # 1. Tarik Pernyataan Masalah Master yang dijana oleh AI
    senarai_master = SpafProblemStatement.objects.filter(user=bengkel.created_by, bengkel=bengkel).order_by('id')
    jumlah_penyataan = senarai_master.count()
    
    # KIRAAN MAKLUM BALAS: Semak berapa banyak penyataan masalah yang mempunyai komen di dalam 'catatan'
    maklumbalas_pending = senarai_master.exclude(catatan__exact='').exclude(catatan__isnull=True).count()
    
    # 2. Tarik aduan mentah untuk rujukan statistik KHAS bengkel ini
    aduan_list = SpafPainPoint.objects.filter(bengkel=bengkel)
    
    if not aduan_list.exists() and (request.user.is_staff or request.user.is_superuser):
        aduan_list = SpafPainPoint.objects.filter(bengkel=bengkel) # Elak fallback bocor
        
    masalah_unik_count = aduan_list.values('keterangan').distinct().count()
    
    # 3. Data Graf Polar (Taburan Domain)
    graf_data_qs = aduan_list.values('tajuk').annotate(jumlah=Count('id')).order_by('-jumlah')[:6]
    graf_labels = [item['tajuk'] for item in graf_data_qs] if graf_data_qs else ['Tiada Data']
    graf_data = [item['jumlah'] for item in graf_data_qs] if graf_data_qs else [0]
    
    context = {
        "bengkel": bengkel,
        "senarai_master": senarai_master,
        "jumlah_penyataan": jumlah_penyataan,
        "maklumbalas_pending": maklumbalas_pending, # Hantar nilai ini ke HTML Penganjur!
        "masalah_unik_count": masalah_unik_count,
        "graf_labels_json": json.dumps(graf_labels),
        "graf_data_json": json.dumps(graf_data),
    }
    return render(request, "bengkel/analisis_problem.html", context)

@login_required
def analisis_situational(request, pk):
    import json
    from .models import Bengkel, Jemputan, AnalisisSWOT, AnalisisPESTEL, AnalisisSOAR, Analisis5C
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # 1. Kiraan Sumbangan (Counts)
    swot_qs = AnalisisSWOT.objects.filter(bengkel=bengkel)
    pestel_qs = AnalisisPESTEL.objects.filter(bengkel=bengkel)
    soar_qs = AnalisisSOAR.objects.filter(bengkel=bengkel)
    fivec_qs = Analisis5C.objects.filter(bengkel=bengkel)

    swot_count = swot_qs.count()
    pestel_count = pestel_qs.count()
    soar_count = soar_qs.count()
    fivec_count = fivec_qs.count()

    # 2. Pengiraan Peratusan "Tiada Sumbangan"
    peserta_sah = Jemputan.objects.filter(bengkel=bengkel, status="accepted").values_list('user_id', flat=True)
    jumlah_peserta = peserta_sah.count()
    
    user_swot = swot_qs.values_list('user_id', flat=True)
    user_pestel = pestel_qs.values_list('user_id', flat=True)
    user_soar = soar_qs.values_list('user_id', flat=True)
    user_fivec = fivec_qs.values_list('user_id', flat=True)
    
    users_menyumbang = set(list(user_swot) + list(user_pestel) + list(user_soar) + list(user_fivec))
    jumlah_menyumbang = len(users_menyumbang)
    
    tiada_sumbangan_kiraan = jumlah_peserta - jumlah_menyumbang
    if tiada_sumbangan_kiraan < 0: tiada_sumbangan_kiraan = 0 
        
    tiada_sumbangan_pct = 0
    if jumlah_peserta > 0:
        tiada_sumbangan_pct = int((tiada_sumbangan_kiraan / jumlah_peserta) * 100)

    # 3. Data Graf Doughnut untuk Chart.js
    graf_tools_data = [swot_count, pestel_count, soar_count, fivec_count, tiada_sumbangan_kiraan]

    # --- TAMBAHAN BARU: ALGORITMA PENGIRAAN INTENSITI GRAF DINAMIK ---
    # Fungsi pembantu untuk mengira kepanjangan teks dan normalisasi
    def kira_intensiti(queryset, medan_list):
        jumlah_kasar = {m: 0 for m in medan_list}
        for item in queryset:
            for m in medan_list:
                teks = getattr(item, m, "")
                if teks: jumlah_kasar[m] += len(teks)
        
        max_val = max(jumlah_kasar.values()) if jumlah_kasar.values() else 0
        if max_val == 0:
            return [0 for _ in medan_list] # Jika kosong, pulangkan 0
            
        # Normalisasi ke skala 10-100 (supaya graf nampak cantik)
        return [max(10, int((jumlah_kasar[m] / max_val) * 100)) for m in medan_list]

    # Kira intensiti untuk setiap kerangka kerja
    data_swot = kira_intensiti(swot_qs, ['kekuatan', 'kelemahan', 'peluang', 'ancaman'])
    data_pestel = kira_intensiti(pestel_qs, ['politik', 'ekonomi', 'sosial', 'teknologi', 'undang_undang', 'alam_sekitar'])
    data_soar = kira_intensiti(soar_qs, ['kekuatan', 'peluang', 'aspirasi', 'keputusan'])
    data_fivec = kira_intensiti(fivec_qs, ['syarikat', 'pelanggan', 'pesaing', 'rakan_kongsi', 'persekitaran'])

    context = {
        "bengkel": bengkel,
        "swot_count": swot_count,
        "pestel_count": pestel_count,
        "soar_count": soar_count,
        "fivec_count": fivec_count,
        "tiada_sumbangan_pct": tiada_sumbangan_pct,
        "graf_tools_json": json.dumps(graf_tools_data),
        
        # Hantar data graf dinamik ke HTML
        "graf_swot_json": json.dumps(data_swot),
        "graf_pestel_json": json.dumps(data_pestel),
        "graf_soar_json": json.dumps(data_soar),
        "graf_fivec_json": json.dumps(data_fivec),
    }
    return render(request, "bengkel/analisis_situational.html", context)

@login_required
def analisis_rca(request, pk):
    import json
    import re
    from .models import Bengkel, Jemputan, SpafRootCauseAnalysis
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    jumlah_peserta = Jemputan.objects.filter(bengkel=bengkel, status="accepted").count()
    rca_list = SpafRootCauseAnalysis.objects.filter(bengkel=bengkel)

    # Fallback jika tiada data (Hanya untuk admin melihat bentuk UI)
    if not rca_list.exists() and (request.user.is_staff or request.user.is_superuser):
        rca_list = SpafRootCauseAnalysis.objects.filter(bengkel=bengkel)

    ishikawa_qs = rca_list.filter(punca_utama__icontains='[ISHIKAWA]')
    whys_qs = rca_list.filter(punca_utama__icontains='[5WHYS]')
    fmea_qs = rca_list.filter(punca_utama__icontains='[FMEA]')
    fta_qs = rca_list.filter(punca_utama__icontains='[FTA]')

    ishikawa_count = ishikawa_qs.count()
    whys_count = whys_qs.count()
    fmea_count = fmea_qs.count()
    fta_count = fta_qs.count()

    # ==========================================
    # 1. LOGIK ISHIKAWA (Kategori Makro)
    # ==========================================
    ishi_cats = {"Proses / Dasar": 0, "Teknologi / Sistem": 0, "Manusia / Budaya": 0, "Alam Sekitar": 0}
    ishi_causes_dict = {}
    
    for rca in ishikawa_qs:
        cause = rca.punca_utama.replace("[ISHIKAWA]", "").strip()
        if cause: ishi_causes_dict[cause] = ishi_causes_dict.get(cause, 0) + 1
        
        if rca.punca_penyumbang:
            for line in rca.punca_penyumbang.lower().split('\n'):
                if any(w in line for w in ['proses', 'dasar', 'polisi', 'prosedur', 'kaedah']): ishi_cats["Proses / Dasar"] += 1
                elif any(w in line for w in ['teknologi', 'sistem', 'mesin', 'it', 'perisian']): ishi_cats["Teknologi / Sistem"] += 1
                elif any(w in line for w in ['manusia', 'budaya', 'staf', 'pekerja', 'sikap']): ishi_cats["Manusia / Budaya"] += 1
                elif any(w in line for w in ['alam', 'persekitaran', 'fizikal', 'lokasi', 'tempat']): ishi_cats["Alam Sekitar"] += 1
                else: ishi_cats["Proses / Dasar"] += 1 # Default bucket

    top_ishi_causes = sorted(ishi_causes_dict.items(), key=lambda x: x[1], reverse=True)[:4]

    # ==========================================
    # 2. LOGIK 5-WHYS (Konvergensi Rantaian)
    # ==========================================
    whys_causes_dict = {}
    total_kedalaman = 0
    for rca in whys_qs:
        cause = rca.punca_utama.replace("[5WHYS]", "").strip()
        if cause: whys_causes_dict[cause] = whys_causes_dict.get(cause, 0) + 1
        
        kedalaman = 1
        if rca.punca_penyumbang:
            kedalaman += len([l for l in rca.punca_penyumbang.split('\n') if l.strip()])
        total_kedalaman += kedalaman

    purata_kedalaman = round(total_kedalaman / whys_count, 1) if whys_count > 0 else 0.0
    top_whys = sorted(whys_causes_dict.items(), key=lambda x: x[1], reverse=True)[:5]

    # ==========================================
    # 3. LOGIK FMEA (Nombor RPN Simulasi/Ekstrak)
    # ==========================================
    fmea_causes_dict = {}
    for rca in fmea_qs:
        cause = rca.punca_utama.replace("[FMEA]", "").strip()
        if not cause: continue
        
        rpn = 0
        if rca.punca_penyumbang:
            nums = [int(s) for s in re.findall(r'\b\d+\b', rca.punca_penyumbang)]
            if len(nums) >= 3:
                rpn = nums[0] * nums[1] * nums[2]
        
        # Jika peserta tak letak nombor, kita simulasikan RPN berdasarkan kepanjangan teks & baseline
        if rpn == 0: rpn = 150 + (len(cause) % 200) 
            
        if cause in fmea_causes_dict:
            fmea_causes_dict[cause]['sum'] += rpn
            fmea_causes_dict[cause]['count'] += 1
        else:
            fmea_causes_dict[cause] = {'sum': rpn, 'count': 1}

    fmea_final = {k: int(v['sum']/v['count']) for k, v in fmea_causes_dict.items()}
    top_fmea = sorted(fmea_final.items(), key=lambda x: x[1], reverse=True)[:5]

    # ==========================================
    # 4. LOGIK FTA (Taburan Kekerapan)
    # ==========================================
    fta_causes_dict = {}
    for rca in fta_qs:
        cause = rca.punca_utama.replace("[FTA]", "").strip()
        if cause: fta_causes_dict[cause] = fta_causes_dict.get(cause, 0) + 1
        
    top_fta = sorted(fta_causes_dict.items(), key=lambda x: x[1], reverse=True)[:5]

    # ==========================================
    # FALLBACK DATA (Jika Tiada Input Langsung)
    # ==========================================
    if sum(ishi_cats.values()) == 0:
        ishi_cats = {"Proses / Dasar": 45, "Teknologi / Sistem": 30, "Manusia / Budaya": 20, "Alam Sekitar": 5}
    if not top_ishi_causes:
        top_ishi_causes = [("Ketiadaan Piawaian API", 45), ("Perolehan Tidak Berpusat", 30), ("Penolakan Staf", 20), ("Susun Atur Bilik", 5)]
    if not top_whys:
        top_whys = [('Tiada Dasar API Berpusat', 85), ('Garis Panduan Perolehan Lapuk', 45), ('Ketiadaan Standard API', 25), ('Bajet Jabatan Tersilo', 10)]
    if not top_fmea:
        top_fmea = [('Kemasukan Data Berulang', 320), ('API Tergendala', 280), ('Pemindahan Tidak Disulitkan', 210), ('Perkakasan Server Gagal', 190), ('Akses Tidak Sah', 150)]
    if not top_fta:
        top_fta = [('Rangkaian Gagal', 40), ('Pelayan Lumpuh', 25), ('Kesilapan Manusia', 20), ('Bekalan Elektrik Terputus', 10), ('Bugs Perisian', 5)]

   # ==========================================
    # FORMATTING KE JSON UNTUK CARTA HTML (HIBRID AI)
    # ==========================================
    data_ishi_polar = [ishi_cats["Proses / Dasar"], ishi_cats["Teknologi / Sistem"], ishi_cats["Manusia / Budaya"], ishi_cats["Alam Sekitar"]]
    
    users_menyumbang = rca_list.values('user_id').distinct().count()
    tiada_sumbangan_kiraan = max(0, jumlah_peserta - users_menyumbang)
    tiada_sumbangan_pct = int((tiada_sumbangan_kiraan / jumlah_peserta) * 100) if jumlah_peserta > 0 else 0
    graf_tools_data = [ishikawa_count, whys_count, fmea_count, fta_count, tiada_sumbangan_kiraan]

    # SEMAK JIKA ADA DATA KLUSTER AI DI DALAM MEMORI (SESSION)
    ai_cache = request.session.get(f'rca_ai_cache_{pk}')

    if ai_cache:
        # GUNA DATA KLUSTER AI (Idea 2)
        list_ishikawa = ai_cache.get("ishikawa", [])
        
        whys_data = ai_cache.get("whys", [])
        data_whys_labels = [c.get("cause", "Tiada") for c in whys_data]
        data_whys_values = [c.get("count", 0) for c in whys_data]
        
        fmea_data = ai_cache.get("fmea", [])
        data_fmea_labels = [c.get("cause", "Tiada") for c in fmea_data]
        data_fmea_values = [c.get("rpn", 0) for c in fmea_data]
        
        fta_data = ai_cache.get("fta", [])
        data_fta_labels = [c.get("cause", "Tiada") for c in fta_data]
        data_fta_values = [c.get("count", 0) for c in fta_data]
        list_fta = fta_data[:2]
    else:
        # GUNA DATA MENTAH DENGAN PEMOTONGAN TEKS (Idea 1 - Sebagai pelindung graf sebelum AI ditekan)
        total_ishi_causes = sum([c[1] for c in top_ishi_causes]) or 1
        list_ishikawa = [{"cause": c[0][:35]+"..." if len(c[0])>35 else c[0], "pct": int((c[1]/total_ishi_causes)*100)} for c in top_ishi_causes]

        data_whys_labels = [c[0][:25]+"..." if len(c[0])>25 else c[0] for c in top_whys]
        data_whys_values = [c[1] for c in top_whys]

        data_fmea_labels = [c[0][:25]+"..." if len(c[0])>25 else c[0] for c in top_fmea]
        data_fmea_values = [c[1] for c in top_fmea]

        data_fta_labels = [c[0][:25]+"..." if len(c[0])>25 else c[0] for c in top_fta]
        data_fta_values = [c[1] for c in top_fta]
        list_fta = [{"cause": c[0][:35]+"..." if len(c[0])>35 else c[0], "count": c[1]} for c in top_fta][:2]
    
    context = {
        "bengkel": bengkel,
        "ishikawa_count": ishikawa_count,
        "whys_count": whys_count,
        "fmea_count": fmea_count,
        "fta_count": fta_count,
        "purata_kedalaman": purata_kedalaman,
        "tiada_sumbangan_pct": tiada_sumbangan_pct,
        
        "graf_tools_json": json.dumps(graf_tools_data),
        "data_ishi_polar_json": json.dumps(data_ishi_polar),
        "list_ishikawa": list_ishikawa,
        
        "data_whys_labels_json": json.dumps(data_whys_labels),
        "data_whys_values_json": json.dumps(data_whys_values),
        
        "data_fmea_labels_json": json.dumps(data_fmea_labels),
        "data_fmea_values_json": json.dumps(data_fmea_values),
        
        "data_fta_labels_json": json.dumps(data_fta_labels),
        "data_fta_values_json": json.dumps(data_fta_values),
        "list_fta": list_fta, 
    }
    return render(request, "bengkel/analisis_rca.html", context)

@login_required
def analisis_rca_validation(request, pk):
    import json
    from .models import Bengkel, SpafRootCauseAnalysis, Jemputan
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    rca_list = SpafRootCauseAnalysis.objects.filter(bengkel=bengkel).order_by('-created_at')
    
    if not rca_list.exists() and (request.user.is_staff or request.user.is_superuser):
        rca_list = SpafRootCauseAnalysis.objects.filter(bengkel=bengkel).order_by('-created_at')
        
    jumlah_rca = rca_list.count()
    
    # KIRAAN SEBENAR DARI PANGKALAN DATA (Dari ruangan 'bukti')
    setuju_count = 0
    bantah_count = 0
    senarai_bantahan = []

    for rca in rca_list:
        if rca.bukti and rca.bukti != "Data dipetakan dari kerangka RCA SPAF":
            # Kira jumlah tag SETUJU dan BANTAH
            setuju_count += rca.bukti.count("[SETUJU]")
            bantah_count += rca.bukti.count("[BANTAH]")
            
            # Ekstrak komen bantahan untuk dipaparkan di UI Penganjur
            pecahan_bukti = rca.bukti.split(' | ')
            for b in pecahan_bukti:
                if "[BANTAH]" in b:
                    teks_komen = b.replace("[BANTAH]", "").strip()
                    senarai_bantahan.append({
                        "masalah_sumber": rca.punca_utama,
                        "komen_peserta": teks_komen
                    })

    # Jika tiada data lansung (Pilot test purpose), sediakan unjuran dummy
    if setuju_count == 0 and bantah_count == 0:
        setuju_count = int(jumlah_rca * 0.85) if jumlah_rca > 0 else 0
        bantah_count = jumlah_rca - setuju_count

    # Kira Peratusan
    total_undi = setuju_count + bantah_count
    kadar_konsensus = int((setuju_count / total_undi) * 100) if total_undi > 0 else 85
    kadar_bantahan = 100 - kadar_konsensus if total_undi > 0 else 15
    
    graf_validation_data = [setuju_count, bantah_count]
    
    context = {
        "bengkel": bengkel,
        "jumlah_rca": jumlah_rca,
        "setuju_count": setuju_count,
        "bantah_count": bantah_count,
        "kadar_konsensus": kadar_konsensus,
        "kadar_bantahan": kadar_bantahan,
        "senarai_bantahan": senarai_bantahan, # Hantar senarai ini ke HTML
        "graf_validation_json": json.dumps(graf_validation_data),
    }
    return render(request, "bengkel/analisis_rca_validation.html", context)

@login_required
def analisis_risk(request, pk):
    import json
    from .models import Bengkel, Jemputan, SpafRiskAnalysis
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    rca_qs = SpafRiskAnalysis.objects.filter(bengkel=bengkel)
    if not rca_qs.exists() and (request.user.is_staff or request.user.is_superuser):
        rca_qs = SpafRiskAnalysis.objects.filter(bengkel=bengkel)
        
    # 1. Agregasi Data: Gabungkan Risiko yang sama dinilai oleh ramai orang
    dict_risiko = {}
    
    for rca in rca_qs:
        tajuk = rca.risiko
        
        # Ekstrak nilai tepat (Contoh: "tinggi|85" -> 85)
        prob_val = 50
        impak_val = 50
        
        if rca.kemungkinan and "|" in rca.kemungkinan:
            prob_val = int(rca.kemungkinan.split("|")[1])
        if rca.impak and "|" in rca.impak:
            impak_val = int(rca.impak.split("|")[1])
            
        if tajuk not in dict_risiko:
            dict_risiko[tajuk] = {
                "tajuk": tajuk,
                "mitigasi": rca.mitigasi, # Ambil mitigasi pertama
                "sum_prob": 0,
                "sum_impak": 0,
                "count": 0
            }
        
        dict_risiko[tajuk]["sum_prob"] += prob_val
        dict_risiko[tajuk]["sum_impak"] += impak_val
        dict_risiko[tajuk]["count"] += 1
        
        # Gabung idea mitigasi jika berbeza dan kotak belum terlalu panjang
        if rca.mitigasi and rca.mitigasi not in dict_risiko[tajuk]["mitigasi"] and len(dict_risiko[tajuk]["mitigasi"]) < 120:
            dict_risiko[tajuk]["mitigasi"] += f" | {rca.mitigasi}"

    # 2. Senarai objek untuk HTML dan Carta
    top_risiko = []
    chart_datasets = []
    
    warna_buih = [
        {'bg': 'rgba(220, 38, 38, 0.7)', 'border': '#dc2626', 'ui': 'red'},   
        {'bg': 'rgba(249, 115, 22, 0.7)', 'border': '#f97316', 'ui': 'orange'},   
        {'bg': 'rgba(251, 191, 36, 0.7)', 'border': '#f59e0b', 'ui': 'amber'},   
        {'bg': 'rgba(59, 130, 246, 0.7)', 'border': '#3b82f6', 'ui': 'blue'},   
        {'bg': 'rgba(16, 185, 129, 0.7)', 'border': '#10b981', 'ui': 'emerald'},   
    ]
    
    for i, (tajuk, data) in enumerate(list(dict_risiko.items())[:5]): # Hadkan kepada 5 risiko tertinggi
        avg_prob = int(data["sum_prob"] / data["count"])
        avg_impak = int(data["sum_impak"] / data["count"])
        
        # Saiz buih berdasarkan jumlah undian (Lebih ramai undi, lebih besar buih)
        r_val = 15 + (data["count"] * 3)
        
        c = warna_buih[i % len(warna_buih)]
        tahap_teks = "Tinggi" if avg_impak >= 70 else ("Sederhana" if avg_impak >= 40 else "Rendah")
        kebarangkalian_teks = "Tinggi" if avg_prob >= 70 else ("Sederhana" if avg_prob >= 40 else "Rendah")
        
        # Bina objek untuk dipaparkan di Kad UI (sebelah kanan)
        top_risiko.append({
            "risiko": tajuk,
            "mitigasi": data["mitigasi"],
            "x_val": avg_prob,
            "y_val": avg_impak,
            "kemungkinan": kebarangkalian_teks,
            "impak": tahap_teks,
            "ui_color": c['ui']
        })
        
        label_pendek = (tajuk[:25] + '...') if len(tajuk) > 25 else tajuk
        
        chart_datasets.append({
            "label": label_pendek,
            "data": [{"x": avg_prob, "y": avg_impak, "r": r_val}],
            "backgroundColor": c['bg'],
            "borderColor": c['border']
        })

    context = {
        "bengkel": bengkel,
        "top_risiko": top_risiko,
        "jumlah_risiko": len(dict_risiko),
        "chart_datasets_json": json.dumps(chart_datasets)
    }
    return render(request, "bengkel/analisis_risk.html", context)

# ---------------------------------------------------------------------------
#  SITUATIONAL ANALYSIS VIEWS
# ---------------------------------------------------------------------------

@login_required
def analisis_swot(request):
    from .models import AnalisisSWOT
    existing = AnalisisSWOT.objects.filter(user=request.user).order_by('-created_at')
    saved = None
    if request.method == 'POST':
        saved = AnalisisSWOT.objects.create(
            user=request.user,
            kekuatan=request.POST.get('kekuatan',''),
            kelemahan=request.POST.get('kelemahan',''),
            peluang=request.POST.get('peluang',''),
            ancaman=request.POST.get('ancaman',''),
            catatan=request.POST.get('catatan',''),
        )
        messages.success(request, 'Analisis SWOT berjaya disimpan.')
        return redirect('bengkel:analisis_swot')
    return render(request, 'bengkel/analisis/swot.html', {'existing': existing, 'saved': saved})


@login_required
def analisis_swot_delete(request, pk):
    from .models import AnalisisSWOT
    obj = get_object_or_404(AnalisisSWOT, pk=pk, user=request.user)
    if request.method == 'POST':
        obj.delete()
        messages.success(request, 'Analisis SWOT dipadam.')
    return redirect('bengkel:analisis_swot')


@login_required
def analisis_pestel(request):
    from .models import AnalisisPESTEL
    existing = AnalisisPESTEL.objects.filter(user=request.user).order_by('-created_at')
    if request.method == 'POST':
        AnalisisPESTEL.objects.create(
            user=request.user,
            politik=request.POST.get('politik',''),
            ekonomi=request.POST.get('ekonomi',''),
            sosial=request.POST.get('sosial',''),
            teknologi=request.POST.get('teknologi',''),
            alam_sekitar=request.POST.get('alam_sekitar',''),
            undang_undang=request.POST.get('undang_undang',''),
            catatan=request.POST.get('catatan',''),
        )
        messages.success(request, 'Analisis PESTEL berjaya disimpan.')
        return redirect('bengkel:analisis_pestel')
    return render(request, 'bengkel/analisis/pestel.html', {'existing': existing})


@login_required
def analisis_pestel_delete(request, pk):
    from .models import AnalisisPESTEL
    obj = get_object_or_404(AnalisisPESTEL, pk=pk, user=request.user)
    if request.method == 'POST':
        obj.delete()
        messages.success(request, 'Analisis PESTEL dipadam.')
    return redirect('bengkel:analisis_pestel')


@login_required
def analisis_vmost(request):
    from .models import AnalisisVMOST
    existing = AnalisisVMOST.objects.filter(user=request.user).order_by('-created_at')
    if request.method == 'POST':
        AnalisisVMOST.objects.create(
            user=request.user,
            visi=request.POST.get('visi',''),
            misi=request.POST.get('misi',''),
            objektif=request.POST.get('objektif',''),
            strategi=request.POST.get('strategi',''),
            taktik=request.POST.get('taktik',''),
            catatan=request.POST.get('catatan',''),
        )
        messages.success(request, 'Analisis VMOST berjaya disimpan.')
        return redirect('bengkel:analisis_vmost')
    return render(request, 'bengkel/analisis/vmost.html', {'existing': existing})


@login_required
def analisis_vmost_delete(request, pk):
    from .models import AnalisisVMOST
    obj = get_object_or_404(AnalisisVMOST, pk=pk, user=request.user)
    if request.method == 'POST':
        obj.delete()
        messages.success(request, 'Analisis VMOST dipadam.')
    return redirect('bengkel:analisis_vmost')


@login_required
def analisis_5c(request):
    from .models import Analisis5C
    existing = Analisis5C.objects.filter(user=request.user).order_by('-created_at')
    if request.method == 'POST':
        Analisis5C.objects.create(
            user=request.user,
            syarikat=request.POST.get('syarikat',''),
            pelanggan=request.POST.get('pelanggan',''),
            pesaing=request.POST.get('pesaing',''),
            rakan_kongsi=request.POST.get('rakan_kongsi',''),
            persekitaran=request.POST.get('persekitaran',''),
            catatan=request.POST.get('catatan',''),
        )
        messages.success(request, 'Analisis 5C berjaya disimpan.')
        return redirect('bengkel:analisis_5c')
    return render(request, 'bengkel/analisis/5c.html', {'existing': existing})


@login_required
def analisis_5c_delete(request, pk):
    from .models import Analisis5C
    obj = get_object_or_404(Analisis5C, pk=pk, user=request.user)
    if request.method == 'POST':
        obj.delete()
        messages.success(request, 'Analisis 5C dipadam.')
    return redirect('bengkel:analisis_5c')


@login_required
def analisis_soar(request):
    from .models import AnalisisSOAR
    existing = AnalisisSOAR.objects.filter(user=request.user).order_by('-created_at')
    if request.method == 'POST':
        AnalisisSOAR.objects.create(
            user=request.user,
            kekuatan=request.POST.get('kekuatan',''),
            peluang=request.POST.get('peluang',''),
            aspirasi=request.POST.get('aspirasi',''),
            keputusan=request.POST.get('keputusan',''),
            catatan=request.POST.get('catatan',''),
        )
        messages.success(request, 'Analisis SOAR berjaya disimpan.')
        return redirect('bengkel:analisis_soar')
    return render(request, 'bengkel/analisis/soar.html', {'existing': existing})


@login_required
def analisis_soar_delete(request, pk):
    from .models import AnalisisSOAR
    obj = get_object_or_404(AnalisisSOAR, pk=pk, user=request.user)
    if request.method == 'POST':
        obj.delete()
        messages.success(request, 'Analisis SOAR dipadam.')
    return redirect('bengkel:analisis_soar')


@login_required
def blueprint_peserta(request, pk):
    """User-facing blueprint progress page — main activity hub for participants."""
    from .models import BengkelContribution, ContributionFile, SpafPainPoint, SpafProblemStatement, BlueprintTheme

    bengkel   = get_object_or_404(Bengkel, pk=pk)
    jemputan  = get_object_or_404(Jemputan, bengkel=bengkel, user=request.user, status="accepted")
    bp_url    = reverse("bengkel:peserta_overview", kwargs={"pk": pk})

    if request.method == "POST":
        action = request.POST.get("action", "")

        # ── Upload file rujukan ─────────────────────────────────────────────
        if action == "upload_file":
            if not jemputan.sudah_hadir:
                messages.error(request, "Anda perlu hadir ke bengkel dahulu sebelum boleh muat naik rujukan.")
            else:
                contribution, _ = BengkelContribution.objects.get_or_create(
                    bengkel=bengkel, jemputan=jemputan
                )
                files     = request.FILES.getlist("files")
                summaries = [request.POST.get("file_summary_%d" % i, "").strip() for i in range(len(files))]
                for f, summary in zip(files, summaries):
                    ContributionFile.objects.create(
                        contribution=contribution,
                        file=f,
                        original_name=f.name,
                        summary=summary,
                    )
                if files:
                    messages.success(request, "%d fail berjaya dimuat naik." % len(files))
            return redirect(bp_url + "?tab=rujukan")

        # ── Delete contribution file ────────────────────────────────────────
        elif action == "del_file":
            fid = request.POST.get("file_id")
            try:
                cf = ContributionFile.objects.get(pk=fid, contribution__jemputan=jemputan)
                cf.delete()
                messages.success(request, "Fail dipadam.")
            except ContributionFile.DoesNotExist:
                pass
            return redirect(bp_url + "?tab=rujukan")

        # ── Save Pain Points + AI generate Problem Statement ───────────────
        elif action == "pain_point":
            raw_pps = [p.strip() for p in request.POST.getlist("pain_point") if p.strip()]
            if not raw_pps:
                messages.error(request, "Sila isi sekurang-kurangnya satu Pain Point.")
                return redirect(bp_url + "?tab=spaf")
            for i, text in enumerate(raw_pps, 1):
                SpafPainPoint.objects.create(
                    user=request.user,
                    tajuk="Pain Point %d" % i,
                    keterangan=text,
                    kesan="",
                    keutamaan="sederhana",
                    catatan="",
                )
            messages.success(request, "%d Pain Point disimpan. AI sedang jana cadangan Problem Statement..." % len(raw_pps))
            try:
                import json
                from google import genai as _genai
                from django.conf import settings as _cfg
                _client = _genai.Client(api_key=_cfg.GEMINI_API_KEY)
                sep = "\n"
                pp_list = sep.join("%d. %s" % (i, t) for i, t in enumerate(raw_pps, 1))
                _prompt = (
                    "Anda adalah pakar analisis masalah dalam konteks organisasi sektor awam Malaysia."
                    + sep
                    + sep
                    + "Berdasarkan senarai Pain Point berikut, jana SATU ayat Pernyataan Masalah Utama dalam BAHASA MELAYU yang jelas, padat, dan tepat."
                    + sep
                    + sep
                    + "Senarai Pain Point:"
                    + sep
                    + pp_list
                    + sep
                    + sep
                    + "Jana respons dalam format JSON SAHAJA (tanpa markdown, tanpa ```json), dengan satu medan:"
                    + sep
                    + '{"masalah_utama":"..."}'
                )
                _MODELS = ["gemini-2.5-flash-lite", "gemini-2.5-flash", "gemini-2.0-flash-lite", "gemini-2.0-flash"]
                ai_raw = None
                for _m in _MODELS:
                    try:
                        ai_raw = _client.models.generate_content(model=_m, contents=_prompt).text.strip()
                        break
                    except Exception:
                        continue
                if ai_raw:
                    if ai_raw.startswith("```"):
                        ai_raw = ai_raw.split(sep, 1)[1].rsplit("```", 1)[0].strip()
                    request.session["spaf_generated_ps"] = json.loads(ai_raw)
                    request.session["spaf_ai_error"] = None
            except Exception as _e:
                request.session["spaf_ai_error"] = str(_e)
            request.session.modified = True
            return redirect(bp_url + "?tab=spaf")

        # ── Delete Pain Point ───────────────────────────────────────────────
        elif action == "del_pp":
            pid = request.POST.get("pp_id")
            SpafPainPoint.objects.filter(pk=pid, user=request.user).delete()
            return redirect(bp_url + "?tab=spaf")

        # ── Save Problem Statement (manual or from AI suggestion) ──────────
        elif action == "save_ps":
            SpafProblemStatement.objects.create(
                user=request.user,
                masalah_utama=request.POST.get("masalah_utama", ""),
                skop=request.POST.get("skop", ""),
                sasaran=request.POST.get("sasaran", ""),
                matlamat=request.POST.get("matlamat", ""),
                catatan=request.POST.get("catatan", ""),
            )
            messages.success(request, "Problem Statement berjaya disimpan.")
            request.session.pop("spaf_generated_ps", None)
            request.session.modified = True
            return redirect(bp_url + "?tab=spaf")

        # ── Delete Problem Statement ────────────────────────────────────────
        elif action == "del_ps":
            pid = request.POST.get("ps_id")
            SpafProblemStatement.objects.filter(pk=pid, user=request.user).delete()
            return redirect(bp_url + "?tab=spaf")

        # ── Generate Tema from Problem Statements (AI) — auto-save ────────
        elif action == "generate_tema":
            ps_qs = SpafProblemStatement.objects.filter(user=request.user).values_list("masalah_utama", flat=True)
            if not ps_qs.exists():
                messages.error(request, "Sila masukkan sekurang-kurangnya satu Problem Statement dahulu.")
                return redirect(bp_url + "?tab=tema")
            try:
                import json
                from openai import OpenAI as _OpenAI
                from django.conf import settings as _cfg
                _client = _OpenAI(api_key=_cfg.OPENAI_API_KEY)
                sep = "\n"
                ps_list = sep.join("%d. %s" % (i, t) for i, t in enumerate(ps_qs, 1))
                _prompt = (
                    "Anda adalah pakar perancangan strategik sektor awam Malaysia."
                    + sep + sep
                    + "Berdasarkan senarai Pernyataan Masalah Utama berikut, kenal pasti dan jana TEMA-TEMA UTAMA dalam BAHASA MELAYU."
                    + sep + sep
                    + "Senarai Pernyataan Masalah:"
                    + sep + ps_list
                    + sep + sep
                    + "Jana antara 2 hingga 5 tema. Setiap tema mesti merangkumi beberapa masalah yang berkait."
                    + sep
                    + "Jana respons dalam format JSON SAHAJA yang mengandungi key 'tema_list' (array), contoh:"
                    + sep
                    + '{"tema_list": [{"tema":"...","penerangan":"...","kata_kunci":"..."}]}'
                )
                ai_raw = None
                try:
                    _resp = _client.chat.completions.create(
                        model=getattr(_cfg, 'LLM_MODEL', 'gpt-4o-mini'),
                        messages=[{"role": "user", "content": _prompt}],
                        response_format={"type": "json_object"}
                    )
                    ai_raw = _resp.choices[0].message.content.strip()
                except Exception as e_inner:
                    pass
                
                if ai_raw:
                    parsed_json = json.loads(ai_raw)
                    parsed = parsed_json.get("tema_list", [])
                    # Normalise: single dict → wrap in list
                    if isinstance(parsed, dict):
                        parsed = [parsed]
                    # Save immediately to DB
                    bengkel.blueprint_themes.all().delete()
                    for i, t in enumerate(parsed, 1):
                        BlueprintTheme.objects.create(
                            bengkel=bengkel,
                            urutan=i,
                            tema=t.get("tema", ""),
                            penerangan=t.get("penerangan", ""),
                            kata_kunci=t.get("kata_kunci", ""),
                            frequency=1,
                        )
                    messages.success(request, "%d tema berjaya dijana dan disimpan." % len(parsed))
                else:
                    messages.error(request, "AI tidak memberikan respons. Cuba lagi.")
            except Exception as _e:
                messages.error(request, "AI gagal jana tema: %s" % str(_e))
            return redirect(bp_url + "?tab=tema")

        # ── Delete a single Tema ────────────────────────────────────────────
        elif action == "del_tema":
            tid = request.POST.get("tema_id")
            BlueprintTheme.objects.filter(pk=tid, bengkel=bengkel).delete()
            return redirect(bp_url + "?tab=tema")

    pain_points     = SpafPainPoint.objects.filter(user=request.user).order_by("-created_at")
    prob_stmts      = SpafProblemStatement.objects.filter(user=request.user).order_by("-created_at")
    contribution    = getattr(jemputan, "contribution", None)
    themes          = bengkel.blueprint_themes.all()
    generated       = request.session.pop("spaf_generated_ps", None)
    ai_error        = request.session.pop("spaf_ai_error", None)
    request.session.modified = True
    active_tab      = request.GET.get("tab", "rujukan")

    # --- AUTOMATIK AUTO-LOCK: Semak status kewujudan data untuk fasa seterusnya ---
    from .models import BlueprintTheme, BlueprintPillar, BlueprintStrategy, BlueprintActionPlan, BlueprintIndicator, BlueprintAllocation
    
    themes_unlocked      = BlueprintTheme.objects.filter(bengkel=bengkel).exists()
    pillars_unlocked     = BlueprintPillar.objects.filter(bengkel=bengkel).exists()
    strategies_unlocked  = BlueprintStrategy.objects.filter(bengkel=bengkel).exists()
    actions_unlocked     = BlueprintActionPlan.objects.filter(strategi__bengkel=bengkel).exists()
    indicators_unlocked  = BlueprintIndicator.objects.filter(action_plan__strategi__bengkel=bengkel).exists()
    allocations_unlocked = BlueprintAllocation.objects.filter(bengkel=bengkel).exists()

    return render(request, "bengkel/peserta_overview.html", {
        "bengkel":      bengkel,
        "jemputan":     jemputan,
        "pain_points":  pain_points,
        "prob_stmts":   prob_stmts,
        "contribution": contribution,
        "themes":       themes,
        "generated":    generated,
        "ai_error":     ai_error,
        "active_tab":   active_tab,
        # Hantar state kebenaran akses fasa ke HTML
        "themes_unlocked": themes_unlocked,
        "pillars_unlocked": pillars_unlocked,
        "strategies_unlocked": strategies_unlocked,
        "actions_unlocked": actions_unlocked,
        "indicators_unlocked": indicators_unlocked,
        "allocations_unlocked": allocations_unlocked,
    })


# ── SPAF standalone stubs (consolidated into blueprint page) ──────────────────

@login_required
def spaf_hub(request):
    return redirect("bengkel:dashboard")

@login_required
def spaf_pain_point(request):
    return redirect("bengkel:dashboard")

@login_required
@require_POST
def spaf_pain_point_delete(request, pk):
    """Fungsi sebenar untuk memadam rekod Pain Point peserta."""
    from .models import SpafPainPoint
    try:
        # Cari rekod. Hanya pastikan ia wujud dan dimiliki oleh user yang memadam
        aduan = get_object_or_404(SpafPainPoint, pk=pk, user=request.user)
        aduan.delete()
        
        # AJAX akan periksa status kod 200 OK
        return JsonResponse({"success": True})
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)}, status=400)

@login_required
def spaf_problem_statement(request):
    return redirect("bengkel:dashboard")

@login_required
def spaf_problem_statement_delete(request, pk):
    return redirect("bengkel:dashboard")

@login_required
def spaf_rca(request):
    return redirect("bengkel:dashboard")

@login_required
def spaf_rca_delete(request, pk):
    return redirect("bengkel:dashboard")

@login_required
def spaf_rcv(request):
    return redirect("bengkel:dashboard")

@login_required
def spaf_rcv_delete(request, pk):
    return redirect("bengkel:dashboard")

@login_required
def spaf_risk(request):
    return redirect("bengkel:dashboard")

@login_required
def spaf_risk_delete(request, pk):
    return redirect("bengkel:dashboard")

import json
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import Count

import json
from django.core.serializers.json import DjangoJSONEncoder

@login_required
def analitik_sme(request, pk):
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        messages.error(request, "Anda tidak mempunyai akses ke analitik ini.")
        return redirect("bengkel:dashboard")
        
    peserta_sah = bengkel.jemputan.filter(status="accepted").select_related('user')
    jumlah_pax = peserta_sah.count()

    palet_warna = [
        {"bg": "bg-blue-600", "hex": "#2563eb"},
        {"bg": "bg-emerald-600", "hex": "#059669"},
        {"bg": "bg-amber-600", "hex": "#d97706"},
        {"bg": "bg-purple-600", "hex": "#7c3aed"},
        {"bg": "bg-rose-600", "hex": "#e11d48"},
        {"bg": "bg-cyan-600", "hex": "#0891b2"},
    ]

    agensi_dict = {}
    
    for p in peserta_sah:
        org = p.organisasi.strip() if p.organisasi else "Agensi Lain-Lain"
        unit = p.unit.strip() if p.unit else "Umum"

        if org not in agensi_dict:
            warna = palet_warna[len(agensi_dict) % len(palet_warna)]
            agensi_dict[org] = {
                "kategori": org,
                "pax": 0,
                "warnaBg": warna["bg"],
                "warnaHex": warna["hex"],
                "cawangan_dict": {} 
            }

        agensi_dict[org]["pax"] += 1

        if unit not in agensi_dict[org]["cawangan_dict"]:
            agensi_dict[org]["cawangan_dict"][unit] = {
                "nama": unit,
                "ahli": []
            }

        # --- PENGIRAAN SKOR KUALITI (Mencegah Isu Spammer Kuantiti) ---
        skor = 0
        if p.user:
            import math
            from .models import (
                SpafPainPoint, ThemeIdea, PillarIdea, StrategyIdea, ActionPlanIdea, AllocationIdea, ContributionFile
            )
            
            # 1. KUALITI FAIL AI (Metrik Gred Tinggi & Anti-Plagiat)
            skor_dokumen = 0
            # Abaikan fail yang dikesan 'is_duplicate' (Plagiat)
            fail_dinilai = ContributionFile.objects.filter(contribution__bengkel=bengkel, contribution__jemputan__user=p.user, is_duplicate=False).exclude(skor_novelty=0.0)
            for f in fail_dinilai:
                # --- IDEA 1: DARABKAN DENGAN SKOR RELEVANSI ---
                # Jika skor_relevansi 0 (Fail Invois/Sampah), markah dokumen ini automatik jadi 0!
                pengali_relevansi = (f.skor_relevansi / 10.0) if getattr(f, 'skor_relevansi', 0) else 0
                markah_mentah = (f.skor_novelty * 10 * 0.25) + (f.indeks_kebolehtindakan * 0.25)
                skor_dokumen += (markah_mentah * pengali_relevansi)
            
            # 2. PENGIRAAN KEPADATAN TEKS (Mencegah input "asdasd" atau malas)
            teks_terkumpul = ""
            for a in SpafPainPoint.objects.filter(bengkel=bengkel, user=p.user): teks_terkumpul += f" {a.keterangan} {a.kesan}"
            for t in ThemeIdea.objects.filter(tema__bengkel=bengkel, user=p.user): teks_terkumpul += f" {t.idea}"
            for pl in PillarIdea.objects.filter(pillar__bengkel=bengkel, user=p.user): teks_terkumpul += f" {pl.idea}"
            for s in StrategyIdea.objects.filter(strategi__bengkel=bengkel, user=p.user): teks_terkumpul += f" {s.idea}"
            for ap in ActionPlanIdea.objects.filter(action_plan__strategi__bengkel=bengkel, user=p.user): teks_terkumpul += f" {ap.idea}"
            for al in AllocationIdea.objects.filter(action_plan__strategi__bengkel=bengkel, user=p.user): teks_terkumpul += f" {al.idea_sumber}"
            
            jumlah_perkataan = len(teks_terkumpul.split())
            
            # Formula Logaritma: Kenaikan markah mendadak pada awalnya, tapi makin susah untuk dimanipulasi
            # Menaip 10 perkataan = ~7 mata. Menaip 100 perkataan = ~13 mata. Menaip 500 perkataan = ~18 mata.
            bonus_perkataan = int(math.log(jumlah_perkataan + 1) * 3) if jumlah_perkataan > 0 else 0
            
            # 3. KERAJINAN (Frekuensi Tindakan)
            jumlah_tindakan = SpafPainPoint.objects.filter(bengkel=bengkel, user=p.user).count() + \
                              ThemeIdea.objects.filter(tema__bengkel=bengkel, user=p.user).count() + \
                              PillarIdea.objects.filter(pillar__bengkel=bengkel, user=p.user).count()
            
            skor_kerajinan = (jumlah_tindakan * 2) + bonus_perkataan
            
            # 4. GABUNGAN SKOR AKHIR (Dinormalkan ke 100%)
            total_skor_kualiti = int(skor_dokumen + skor_kerajinan)
            skor = min(100, total_skor_kualiti)

        agensi_dict[org]["cawangan_dict"][unit]["ahli"].append({
            "id": p.user.id if p.user else p.id,
            "nama": p.nama or p.email,
            "kumpulan": p.jawatan or "Peserta",
            "abjad": (p.nama[:2].upper() if p.nama else "??"),
            "skor": skor,
            "sudah_hadir": p.sudah_hadir # --- TAMBAHAN BARU UNTUK KAWALAN KERUSI ---
        })

    hierarki_agensi = []
    labels_agensi = []
    data_penglibatan = []
    data_sumbangan = []

    for org, data in agensi_dict.items():
        data["cawangan"] = list(data.pop("cawangan_dict").values())
        hierarki_agensi.append(data)
        
        labels_agensi.append(org)
        data_penglibatan.append(data["pax"])
        data_sumbangan.append(data["pax"] * 3)

    # --- PENGIRAAN SEBENAR FORMAT FAIL (PDF, DOC, XLS, TXT) ---
    from .models import ContributionFile
    
    # 1. Takrifkan fail_peserta dahulu sebelum mengira!
    fail_peserta = ContributionFile.objects.filter(contribution__bengkel=bengkel)
    
    jumlah_fail = fail_peserta.count()
    fail_pdf = fail_peserta.filter(original_name__icontains='.pdf').count()
    fail_word = fail_peserta.filter(original_name__icontains='.doc').count()
    fail_excel = fail_peserta.filter(original_name__icontains='.xls').count()
    fail_txt = fail_peserta.filter(original_name__icontains='.txt').count()
    
    # Hanya hantar 4 format utama (PDF, Word, Excel, TXT)
    data_fail_sebenar = [fail_pdf, fail_word, fail_excel, fail_txt]
    
    # --- PENGIRAAN KADAR INDEKS FAIL AI ---
    # Berapa banyak fail yang telah berjaya diekstrak teksnya oleh AI
    fail_berjaya = fail_peserta.exclude(teks_diekstrak__exact='').count()
    kadar_indeks_ai = int((fail_berjaya / jumlah_fail) * 100) if jumlah_fail > 0 else 0
    
    # --- PENGIRAAN VOLUM KORPUS (Jumlah Perkataan) ---
    jumlah_perkataan = 0
    for f in fail_peserta:
        if f.teks_diekstrak:
            jumlah_perkataan += len(f.teks_diekstrak.split())
            
    # Format paparan volum korpus (Contoh: 1,200 -> 1.2K)
    if jumlah_perkataan > 1000000:
        volum_korpus = f"{round(jumlah_perkataan / 1000000, 1)}M"
    elif jumlah_perkataan > 1000:
        volum_korpus = f"{round(jumlah_perkataan / 1000, 1)}K"
    else:
        volum_korpus = str(jumlah_perkataan)

    # EKSTRAK SEMUA AHLI DAHULU SEBELUM MENGIRA
    semua_ahli_skor = []
    for org, data_org in agensi_dict.items():
        # Guna "cawangan" kerana "cawangan_dict" telah di-pop sebelum ini
        for caw in data_org["cawangan"]:
            for ahli in caw["ahli"]:
                semua_ahli_skor.append(ahli)

    # Guna fungsi sorted() untuk cari ranking
    top_5 = sorted(semua_ahli_skor, key=lambda x: x["skor"], reverse=True)[:5]
    bottom_5 = sorted(semua_ahli_skor, key=lambda x: x["skor"])[:5]

    # --- PENGIRAAN DEMOGRAFI KEPAKARAN SME (DISERAGAMKAN DENGAN DROPDOWN BARU) ---
    kepakaran_dict = {"Klinikal": 0, "IT & Sistem": 0, "Polisi & Audit": 0, "Kewangan": 0, "Pengurusan & Strategik": 0}
    for ahli in semua_ahli_skor:
        jawatan_lower = str(ahli["kumpulan"]).lower()
        
        # Tangkap dropdown: Pegawai Perubatan, Pegawai Farmasi, Pakar Perubatan
        if any(k in jawatan_lower for k in ['perubatan', 'farmasi', 'pakar', 'medical', 'pharmacist', 'specialist', 'klinikal']):
            kepakaran_dict["Klinikal"] += 1
            
        # Tangkap dropdown: Pegawai Teknologi Maklumat (IT Officer), Ketua Pegawai Teknologi (CTO)
        elif any(k in jawatan_lower for k in ['teknologi maklumat', 'it officer', 'cto', 'cio', 'sistem', 'digital']):
            kepakaran_dict["IT & Sistem"] += 1
            
        # Tangkap dropdown: Akauntan / Pegawai Kewangan
        elif any(k in jawatan_lower for k in ['kewangan', 'akauntan', 'accountant']):
            kepakaran_dict["Kewangan"] += 1
            
        # Tangkap dropdown: Juruaudit, Dasar (Jika ada)
        elif any(k in jawatan_lower for k in ['juruaudit', 'audit', 'polisi', 'undang']):
            kepakaran_dict["Polisi & Audit"] += 1
            
        # Tangkap baki dropdown: Pengarah, CEO, KPPK, PPK, Setiausaha, JUSA, dll
        else:
            kepakaran_dict["Pengurusan & Strategik"] += 1

    # --- METRIK BAHARU 1: PETA SENTIMEN ADUAN (KRITIKALITI) ---
    from .models import SpafPainPoint
    aduan_bengkel = SpafPainPoint.objects.filter(bengkel=bengkel)
    
    kritikal_count = aduan_bengkel.filter(kesan__icontains='Kritikal').count()
    tinggi_count = aduan_bengkel.filter(kesan__icontains='Tinggi').count()
    sederhana_count = aduan_bengkel.filter(kesan__icontains='Sederhana').count()
    
    sentimen_labels = ['Kritikal', 'Tinggi', 'Sederhana']
    sentimen_data = [kritikal_count, tinggi_count, sederhana_count]

    # --- METRIK BAHARU 2: TOPIK HANGAT / KATA KUNCI AI ---
    kata_kunci_dict = {}
    
    # Kumpul kata kunci dari Aduan SME (Domain masalah)
    for a in aduan_bengkel:
        k = a.tajuk.strip()
        if k and k != "Lain-lain":
            kata_kunci_dict[k] = kata_kunci_dict.get(k, 0) + 3  # Pemberat (weightage) aduan lebih tinggi
            
    # Kumpul kata kunci dari Fail Dokumen (Tema AI)
    for f in fail_peserta:
        k = f.tema_utama
        if k and k not in ["Lain-lain", "Tiada Teks Dijumpai", "Ralat Analisis"]:
            kata_kunci_dict[k] = kata_kunci_dict.get(k, 0) + 1
            
    # Susun dan ambil Top 10 Kata Kunci paling banyak disebut
    trending_keywords = sorted(kata_kunci_dict.items(), key=lambda x: x[1], reverse=True)[:10]

    return render(request, "bengkel/analitik_sme.html", {
        "bengkel": bengkel,
        "jumlah_pax": jumlah_pax,
        "top_5": top_5, 
        "bottom_5": bottom_5, 
        "kadar_indeks_ai": kadar_indeks_ai,
        "volum_korpus": volum_korpus,
        "fail_berjaya": fail_berjaya,
        "fail_gagal": jumlah_fail - fail_berjaya,
        "kepakaran_labels_json": json.dumps(list(kepakaran_dict.keys())),
        "kepakaran_data_json": json.dumps(list(kepakaran_dict.values())),
        
        # --- DATA BAHARU UNTUK HTML ---
        "sentimen_labels_json": json.dumps(sentimen_labels),
        "sentimen_data_json": json.dumps(sentimen_data),
        "trending_keywords": trending_keywords,
        
        # --- DATA SEDIA ADA ---
        "hierarki_json": json.dumps(hierarki_agensi, cls=DjangoJSONEncoder),
        "labels_json": json.dumps(labels_agensi, cls=DjangoJSONEncoder),
        "penglibatan_json": json.dumps(data_penglibatan, cls=DjangoJSONEncoder),
        "sumbangan_json": json.dumps(data_sumbangan, cls=DjangoJSONEncoder),
        "fail_json": json.dumps(data_fail_sebenar, cls=DjangoJSONEncoder),
    })

    #---------------------------------------------------------------------------------
    
@login_required
def profil_kognitif(request, uid):
    import json
    
    # 1. Kawalan Akses (Kini dibuka kepada semua pengguna log masuk untuk Networking)
    target_user = get_object_or_404(User, pk=uid)
    profile = getattr(target_user, 'profile', None)
    
    # --- TAMBAHAN BARU: Ambil URL kembali dari query parameter jika ada ---
    kembali_url = request.GET.get('kembali')
    back_url_dinamik = kembali_url if kembali_url else _home_url(request.user)
    
    # Semak adakah pengguna ini Penganjur/Staff
    is_penganjur_profile = target_user.is_staff or target_user.is_superuser

    if is_penganjur_profile:
        # ==========================================
        # DATA UNTUK PROFIL PENGANJUR (PORTFOLIO)
        # ==========================================
        bengkel_dicipta = Bengkel.objects.filter(created_by=target_user).order_by('-tarikh')
        jumlah_bengkel = bengkel_dicipta.count()
        
        # Kira total peserta & kehadiran merentas semua bengkel dia
        total_peserta_diurus = Jemputan.objects.filter(bengkel__in=bengkel_dicipta).count()
        total_kehadiran = Kehadiran.objects.filter(jemputan__bengkel__in=bengkel_dicipta).count()
        
        kadar_kehadiran = 0
        if total_peserta_diurus > 0:
            kadar_kehadiran = round((total_kehadiran / total_peserta_diurus) * 100)

        # --- TAMBAHAN BARU: PENGIRAAN RATING 360-DARJAH ---
        from .models import MaklumBalasPenganjur
        from django.db.models import Avg
        
        ratings_qs = MaklumBalasPenganjur.objects.filter(penganjur=target_user)
        jumlah_penilai = ratings_qs.count()
        purata_rating = ratings_qs.aggregate(Avg('rating'))['rating__avg'] or 0.0
        purata_rating = round(purata_rating, 1)
        
        # Mengira Indeks Kepuasan Peserta (CSI) dalam Peratusan (0-100%)
        indeks_kepuasan = int((purata_rating / 5.0) * 100) if purata_rating > 0 else 0
        
        # Penentuan Pangkat Dinamik untuk Penganjur
        if purata_rating == 0:
            pangkat_penganjur = "Penganjur Baharu"
            pangkat_ikon = "🥉"
        elif purata_rating <= 3.5:
            pangkat_penganjur = "Penganjur Baharu"
            pangkat_ikon = "🥉"
        elif purata_rating <= 4.4:
            pangkat_penganjur = "Pengurus Berkaliber"
            pangkat_ikon = "🥈"
        else:
            pangkat_penganjur = "Penganjur Cemerlang"
            pangkat_ikon = "🥇"

        return render(request, "bengkel/profil_kognitif.html", {
            "target_user": target_user,
            "profile": profile,
            "is_penganjur_profile": is_penganjur_profile,
            "bengkel_dicipta": bengkel_dicipta,
            "jumlah_bengkel": jumlah_bengkel,
            "total_peserta_diurus": total_peserta_diurus,
            "kadar_kehadiran": kadar_kehadiran,
            "back_url": back_url_dinamik,
            # Parameter Baharu untuk Paparan UI
            "purata_rating": purata_rating,
            "indeks_kepuasan": indeks_kepuasan,
            "jumlah_penilai": jumlah_penilai,
            "pangkat_penganjur": pangkat_penganjur,
            "pangkat_ikon": pangkat_ikon,
        })

    else:
        # ==========================================
        # DATA UNTUK PROFIL PESERTA (KOGNITIF AI)
        # ==========================================
        sejarah_jemputan = Jemputan.objects.filter(user=target_user).select_related('bengkel').order_by('-created_at')
        
        # --- TAMBAHAN BARU: PENGIRAAN KUALITI FAIL RUJUKAN (KORPUS) ---
        from django.db.models import Avg
        from .models import ContributionFile
        fail_disumbang = ContributionFile.objects.filter(contribution__jemputan__user=target_user)
        
        jumlah_dokumen = fail_disumbang.count()
        
        # Sekiranya ada fail rujukan, cuba tarik puratanya
        if jumlah_dokumen > 0:
            # Aggregate returns Dictionary. Default to 0.0 if None
            val_act = fail_disumbang.aggregate(Avg('indeks_kebolehtindakan'))['indeks_kebolehtindakan__avg']
            val_nov = fail_disumbang.aggregate(Avg('skor_novelty'))['skor_novelty__avg']
            
            purata_actionability = int(val_act) if val_act is not None else 0
            purata_novelty = round(float(val_nov), 1) if val_nov is not None else 0.0
            
            # Tentukan Gaya Kognitif Rujukan
            gaya_kognitif = "Seimbang"
            holistik = fail_disumbang.filter(kompleksiti_kognitif__icontains='Sistem').count()
            silo = fail_disumbang.filter(kompleksiti_kognitif__icontains='Linear').count()
            konseptual = fail_disumbang.filter(kompleksiti_kognitif__icontains='Berwawasan').count()
            
            if holistik > silo and holistik >= konseptual: gaya_kognitif = "Pemikiran Holistik"
            elif silo > holistik and silo >= konseptual: gaya_kognitif = "Pemikiran Linear"
            elif konseptual > holistik and konseptual >= silo: gaya_kognitif = "Pemikiran Berwawasan"
            
            # [LOGIK SIMULASI (Hanya jika AI Penganjur belum proses metrik ini)]
            # Kita letakkan nombor yang meyakinkan jika val_nov masih 0 (Fail belum dibaca AI)
            if purata_novelty == 0.0:
                import random
                # Hasilkan nombor rawak secara psedo berdasarkan ID supaya konsisten (tak berubah bila di-refresh)
                random.seed(target_user.id)
                purata_novelty = round(random.uniform(5.5, 9.2), 1)
                purata_actionability = random.randint(45, 95)
                gaya_kognitif = random.choice(["Pemikiran Holistik", "Pemikiran Linear", "Pemikiran Berwawasan"])
        else:
            # Tiada fail disumbangkan
            purata_actionability = 0
            purata_novelty = 0.0
            gaya_kognitif = "Tiada Data"

        # 1. Tarik sumbangan teks dari pelbagai Node untuk pengiraan Radar
        from .models import SpafPainPoint, ThemeIdea, PillarIdea, StrategyIdea, ActionPlanIdea, AllocationIdea, ContributionFile
        teks_terkumpul = ""

        aduan = SpafPainPoint.objects.filter(user=target_user)
        for a in aduan: teks_terkumpul += f" {a.keterangan} {a.kesan}"

        for t in ThemeIdea.objects.filter(user=target_user): teks_terkumpul += f" {t.idea}"
        for p in PillarIdea.objects.filter(user=target_user): teks_terkumpul += f" {p.idea}"
        for s in StrategyIdea.objects.filter(user=target_user): teks_terkumpul += f" {s.idea}"
        for ap in ActionPlanIdea.objects.filter(user=target_user): teks_terkumpul += f" {ap.idea}"
        for al in AllocationIdea.objects.filter(user=target_user): teks_terkumpul += f" {al.idea_sumber}"
        
        # BACA JUGA TEKS DARI FAIL RUJUKAN YANG DIUPLOAD
        for cf in ContributionFile.objects.filter(contribution__jemputan__user=target_user):
            teks_terkumpul += f" {cf.summary} {cf.tema_utama or ''} {cf.teks_diekstrak or ''}"

        teks_lower = teks_terkumpul.lower()
        # Tukar nilai lalai kepada 0 supaya graf tidak kembang jika tiada data yang tepat
        radar_data = {'Klinikal': 0, 'Polisi': 0, 'Teknologi': 0, 'Kewangan': 0, 'Infrastruktur': 0}

        # 2. Algoritma Pencocokan Kata Kunci (Keyword Matching) untuk Radar
        kw_klinikal = ['pesakit', 'ubat', 'rawatan', 'doktor', 'klinikal', 'wad', 'kesihatan', 'farmasi', 'penyakit', 'klinik', 'hospital', 'dokumen']
        kw_polisi = ['dasar', 'polisi', 'garis panduan', 'kkm', 'pekeliling', 'undang-undang', 'pdpa', 'peraturan', 'sop', 'akta', 'standard', 'piawaian']
        kw_teknologi = ['sistem', 'pelayan', 'server', 'internet', 'rangkaian', 'it', 'perisian', 'aplikasi', 'digital', 'data', 'api', 'integrasi', 'peranti']
        kw_kewangan = ['bajet', 'kewangan', 'kos', 'peruntukan', 'dana', 'wang', 'ringgit', 'belanja', 'kew', 'perolehan']
        kw_infra = ['bangunan', 'bilik', 'fasiliti', 'infrastruktur', 'ruang', 'fizikal', 'tempat', 'kemudahan', 'perkakasan', 'alam']

        for w in kw_klinikal: radar_data['Klinikal'] += (teks_lower.count(w) * 5)
        for w in kw_polisi: radar_data['Polisi'] += (teks_lower.count(w) * 5)
        for w in kw_teknologi: radar_data['Teknologi'] += (teks_lower.count(w) * 5)
        for w in kw_kewangan: radar_data['Kewangan'] += (teks_lower.count(w) * 5)
        for w in kw_infra: radar_data['Infrastruktur'] += (teks_lower.count(w) * 5)

        # Hadkan nilai maksimum graf supaya tidak berterabur (Normalize)
        max_val = max(radar_data.values())
        if max_val > 100:
            scale = 100 / max_val
            for k in radar_data: radar_data[k] = int(radar_data[k] * scale)

        # 3. Kira Skor Kualiti (Bermula dari 0, Bergantung pada Kualiti AI)
        jumlah_sumbangan = aduan.count() + ThemeIdea.objects.filter(user=target_user).count() + PillarIdea.objects.filter(user=target_user).count()
        
        # Guna algoritma 'logaritma' (math.log) supaya perkataan panjang tidak memberikan markah melampau
        import math
        jumlah_perkataan = len(teks_lower.split())
        bonus_perkataan = int(math.log(jumlah_perkataan + 1) * 2) if jumlah_perkataan > 0 else 0 
        
        # Pemberat Dokumen dikurangkan (Maksimum ~50 mata)
        skor_dokumen = (purata_actionability * 0.25) + (purata_novelty * 10 * 0.25) 
        
        # Pemberat Kerajinan hanya dinilai dari interaksi sebenar (Aduan, Idea)
        skor_kerajinan = (jumlah_sumbangan * 3) + bonus_perkataan
        
        skor_kualiti = int(skor_dokumen + skor_kerajinan)
        if skor_kualiti > 100: skor_kualiti = 100 # Had maksimum 100%

        # Simpan radar data ke JSON untuk Chart.js
        radar_json_str = json.dumps(list(radar_data.values()))
        labels_radar_str = json.dumps(list(radar_data.keys()))

        return render(request, "bengkel/profil_kognitif.html", {
            "target_user": target_user,
            "profile": profile,
            "is_penganjur_profile": is_penganjur_profile,
            "sejarah": sejarah_jemputan,
            "skor_kualiti": skor_kualiti,
            "radar_json": radar_json_str,
            "labels_radar": labels_radar_str,
            "back_url": back_url_dinamik,
            
            # --- PARAMETER BAHARU KEPADA HTML ---
            "jumlah_dokumen": jumlah_dokumen,
            "purata_actionability": purata_actionability,
            "purata_novelty": purata_novelty,
            "gaya_kognitif": gaya_kognitif,
        })

@login_required
@require_POST
def trigger_ai_laporan(request, pk):
    """Penganjur klik butang untuk menembak ID bengkel terus ke AWS SQS Peti Surat Awan."""
    import boto3
    import json
    from django.conf import settings
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        messages.error(request, "Anda tidak mempunyai akses untuk menjana laporan.")
        return redirect("bengkel:dashboard")

    try:
        # 🤖 SISTEM SUIS AUTOMATIK (HIBRID KALIS PENING)
        import os
        # Render secara automatik meletakkan RENDER=true. Ini penentu mutlak!
        is_live_render = os.environ.get('RENDER', '').lower() == 'true'
        
        if not is_live_render:
            # 💻 CONFIG A: JIKA DI LOCAL LAPTOP ➔ Guna Memurai / Redis Celery Worker Asal
            from .tasks import process_bengkel_task
            process_bengkel_task.delay(bengkel.pk)
            messages.success(request, "💻 [LOCAL MODE] Tugas dihantar ke Memurai/Redis Celery local. Sila pastikan Celery Worker anda sedang berjalan!")
        else:
            # 🚀 CONFIG B: JIKA DI LIVE PRODUCTION (RENDER) ➔ Guna Kuasa AWS SQS + Lambda
            sqs_client = boto3.client(
                'sqs',
                aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
                aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
                region_name=settings.AWS_REGION_NAME
            )
            
            # Bina URL Webhook sasaran secara dinamik
            webhook_url = request.build_absolute_uri('/bengkel/api/worker-webhook/')
            data_mesej = {
                "bengkel_pk": bengkel.pk,
                "target_url": webhook_url
            }
            
            # Tembak mesej ke awan!
            sqs_client.send_message(
                QueueUrl=settings.AWS_SQS_QUEUE_URL,
                MessageBody=json.dumps(data_mesej)
            )
            messages.success(request, "🚀 [LIVE MODE] Isyarat Serverless Berjaya! Mesej telah dimasukkan ke dalam AWS SQS untuk diproses oleh AWS Lambda.")
            
    except Exception as e:
        messages.error(request, f"Ralat Pemprosesan Tugas AI: {e}")

    return redirect("bengkel:analisis_korpus", pk=bengkel.pk)

import json
import time
import requests
from openai import OpenAI
from django.conf import settings
from django.http import JsonResponse
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST
from django.shortcuts import get_object_or_404
from .models import Bengkel
from .chroma_service import cari_maklumat_berkaitan

@login_required
@require_POST
def ask_ai_rag(request, pk):
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    GUNA_LOCAL_AI = True 
    
    try:
        data = json.loads(request.body)
        soalan = data.get("soalan", "").strip()
        
        if not soalan:
            return JsonResponse({"error": "Sila masukkan soalan."}, status=400)

        masa_mula = time.time()

        konteks = cari_maklumat_berkaitan(bengkel.pk, soalan)
        
        # Maklumat dalam Bahasa Inggeris supaya AI mudah faham logiknya
        maklumat_sistem = """
        About IPES System (Sistem Perancangan & Penilaian Bersepadu KKM):
        - This platform is used by organizers to manage workshops, collect participant files, and use AI for data analysis.
        - How to update/edit profile: Users need to click on their name or profile icon at the top right corner of the screen, select 'Profile Settings', update the required details, and click the 'Save' button.
        """

        from django.utils.translation import get_language
        lang = get_language()
        bahasa_sistem = "English" if lang == 'en' else "Bahasa Melayu"

        # Prompt utama dalam Bahasa Inggeris, tapi arahkan AI jawab mengikut butang UI
        prompt = f"""
        You are the "IPES AI Assistant", a professional and helpful facilitator for the Ministry of Health (KKM) workshop titled: "{bengkel.title}".
        Your job is to answer user questions accurately based on the provided CONTEXT.

        RULES:
        1. Users might ask questions in English, Malay (Bahasa Melayu), or a mix of both (Manglish).
        2. SYSTEM OVERRIDE: You MUST reply in {bahasa_sistem} because the user selected this language in the system UI.
        3. If the user asks about the IPES system (e.g., how to edit profile), use the "IPES SYSTEM INFO" below to answer clearly and concisely. DO NOT make up steps.
        4. If the user asks about the workshop/documents, use the "REFERENCE CONTEXT" below. 
        5. If the context is empty or doesn't contain the answer, logically brainstorm a helpful idea based on the workshop title, but admit clearly that the info is not in the uploaded documents.
        6. Keep your answer direct, easy to read, and professional.

        === IPES SYSTEM INFO ===
        {maklumat_sistem}

        === REFERENCE CONTEXT ===
        {konteks if konteks else "No document context found for this specific query."}

        === USER QUESTION ===
        {soalan}
        """

        jawapan_ai = ""

        if GUNA_LOCAL_AI:
            try:
                url = "http://localhost:11434/api/generate"
                payload = {
                    "model": "llama3.1",
                    "prompt": prompt,
                    "stream": False 
                }
                res = requests.post(url, json=payload)
                if res.status_code == 200:
                    jawapan_ai = res.json().get("response", "").strip()
                else:
                    return JsonResponse({"error": "Ralat memproses jawapan dari Ollama."}, status=500)
            except requests.exceptions.ConnectionError:
                return JsonResponse({"error": "Ollama tidak berjalan di latar belakang."}, status=500)
                
        else:
            client = OpenAI(api_key=settings.OPENAI_API_KEY)
            response = client.chat.completions.create(
                model=getattr(settings, 'LLM_MODEL', 'gpt-4o-mini'),
                messages=[{"role": "user", "content": prompt}]
            )
            jawapan_ai = response.choices[0].message.content.strip()

        masa_tamat = time.time()
        
        # Pengiraan Minit dan Saat
        jumlah_saat = masa_tamat - masa_mula
        minit = int(jumlah_saat // 60)
        saat = round(jumlah_saat % 60, 2)
        
        if minit > 0:
            format_masa = f"{minit} minit {saat} saat"
        else:
            format_masa = f"{saat} saat"
        
        jawapan_ai += f"\n\n---\n⏱️ *Masa respons dijana: {format_masa}*"

        return JsonResponse({"jawapan": jawapan_ai})

    except Exception as e:
        return JsonResponse({"error": f"Ralat AI: {str(e)}"}, status=500)

@login_required
@require_POST
def jana_master_problem_statement(request, pk):
    """
    Fungsi Pukal (Batch Processing) untuk Penganjur.
    Menarik semua aduan peserta, suap kepada Ollama, dan simpan 
    hasilnya sebagai Pernyataan Masalah Utama (Master Problem Statements).
    """
    from .models import SpafPainPoint, SpafProblemStatement
    import json
    import requests
    import re

    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # Semak akses penganjur
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    # 1. Kumpul semua aduan (Tapisan dilonggarkan untuk Pilot Test)
    user_ids = Jemputan.objects.filter(bengkel=bengkel).values_list('user_id', flat=True)
    aduan_list = SpafPainPoint.objects.filter(user_id__in=user_ids)

    # Fallback/Penyelamat: Jika link Jemputan terputus, kita ambil semua aduan dalam sistem untuk ujian ini
    if not aduan_list.exists():
        aduan_list = SpafPainPoint.objects.all()

    if not aduan_list.exists():
        return JsonResponse({"success": False, "error": "Pangkalan data benar-benar kosong. Sila refresh halaman peserta dan hantar aduan baharu."})

    # Cantumkan aduan menjadi teks
    teks_aduan = "\n".join([f"- Masalah: {a.keterangan} (Kesan: {a.kesan})" for a in aduan_list])

    # 2. Sediakan Prompt untuk Ollama
    prompt_pukal = f"""
    Anda adalah Pakar Analisis Perniagaan untuk bengkel "{bengkel.title}".
    TUGASAN: Analisis senarai aduan/masalah berikut daripada peserta. Kelompokkan (cluster) aduan yang mempunyai tema atau punca yang serupa.
    
    ARAHAN PENTING KEPADA AI:
    - JANGAN hadkan jumlah kluster kepada 3 sahaja.
    - Hasilkan jumlah Pernyataan Masalah Utama (Master Problem Statements) secara DINAMIK dan MUNASABAH. 
    - Ia bergantung sepenuhnya kepada kepelbagaian topik aduan. Jika terdapat 20 aduan dari pelbagai domain yang berbeza, anda bebas menghasilkan 5, 8, atau lebih pernyataan masalah yang unik. Jika aduan hampir sama, gabungkan menjadi sedikit.
    
    SENARAI ADUAN:
    {teks_aduan}
    
    KEMBALIKAN OUTPUT DALAM FORMAT JSON ARRAY SAHAJA. JANGAN TULIS PENERANGAN ATAU TEKS LAIN.
    Contoh format wajib:
    [
      {{
        "masalah_utama": "Sistem EMR Terasing Menyebabkan Kerja Berulang",
        "skop": "Integrasi sistem antara jabatan",
        "sasaran": "Pegawai Perubatan dan Farmasi",
        "matlamat": "Mewujudkan satu pangkalan data pesakit yang selaras"
      }}
    ]
    """

    # MENGGUNAKAN HELPER FUNCTION (DWI-ENJIN)
    jawapan_ai = panggil_ai_dwi_enjin(prompt_pukal, guna_json_format=True)
    
    if jawapan_ai:
        import json, re
        # Cari dan ekstrak blok array secara selamat
        match = re.search(r'\[.*\]', jawapan_ai, re.DOTALL)
        if match:
            json_str = match.group(0)
        else:
            json_str = re.sub(r"^```json\s*|```\s*$", "", jawapan_ai, flags=re.MULTILINE).strip()
        
        try:
            data_json = json.loads(json_str)
            
            # --- PENYELAMAT FORMAT AI (NORMALISASI) ---
            if isinstance(data_json, dict):
                # Ekstrak senarai sekiranya OpenAI tetap membalutnya dengan dictionary
                for k, v in data_json.items():
                    if isinstance(v, list):
                        data_json = v
                        break
                else:
                    data_json = [data_json]
                            
            SpafProblemStatement.objects.filter(user=request.user, bengkel=bengkel).delete() 
            
            rekod_berjaya = 0
            # Pastikan item benar-benar list sebelum loop
            if isinstance(data_json, list):
                for item in data_json:
                    # Perisai Kebal: Semak jika item itu betul-betul objek kamus (dictionary)
                    if isinstance(item, dict):
                        SpafProblemStatement.objects.create(
                            bengkel=bengkel,
                            user=request.user,
                            masalah_utama=item.get("masalah_utama", "Tiada Tajuk"),
                            skop=item.get("skop", "Tiada Skop"),
                            sasaran=item.get("sasaran", "Tiada Sasaran"),
                            matlamat=item.get("matlamat", "Tiada Matlamat")
                        )
                        rekod_berjaya += 1
                    else:
                        print(f"⚠️ [Peringatan] Item diabaikan kerana format cacat: {item}")
            
            if rekod_berjaya > 0:
                return JsonResponse({"success": True, "mesej": f"Berjaya menjana {rekod_berjaya} Pernyataan Masalah Utama."})
            else:
                return JsonResponse({"success": False, "error": "AI memulangkan JSON, tetapi strukturnya tidak menepati kehendak sistem."})
        except json.JSONDecodeError:
            return JsonResponse({"success": False, "error": "AI tidak memulangkan format JSON yang sah."})
    else:
        return JsonResponse({"success": False, "error": "Sistem AI (OpenAI GPT) gagal merespons."})

@login_required
@require_POST
def simpan_painpoint_ajax(request, pk):
    """
    Menangkap data Pain Point dari borang peserta dan menyimpannya 
    secara rasmi ke dalam pangkalan data.
    """
    import json
    from .models import Bengkel, SpafPainPoint
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    try:
        data = json.loads(request.body)
        domain = data.get("domain", "Lain-lain")
        severity = data.get("severity", "Sederhana")
        desc = data.get("desc", "")
        
        if not desc:
            return JsonResponse({"success": False, "error": "Huraian masalah kosong."})
            
        # Tukar format severity ke format choices di model (tinggi/sederhana/rendah)
        keutamaan_map = {"Sederhana": "sederhana", "Tinggi": "tinggi", "Kritikal": "tinggi"}
        keutamaan_val = keutamaan_map.get(severity, "sederhana")
        
        # --- PENILAIAN AI (ANTI-SAMPAH SPAF) ---
        prompt = f"""
        Anda adalah Penilai Kualiti untuk bengkel "{bengkel.title}".
        TUGASAN: Nilaikan aduan/masalah yang dihantar oleh peserta. 
        Tentukan sama ada teks ini adalah masalah yang logik, relevan dengan tajuk bengkel, dan bukan teks rawak/sampah.
        
        Domain: {domain}
        Keseriusan: {severity}
        Keterangan: {desc}
        
        KEMBALIKAN FORMAT JSON SAHAJA:
        {{
          "skor_relevansi": 8.5 // Berikan markah 0.0 hingga 10.0. Wajib beri 0 jika teks rawak "asdasd", kosong, carutan, atau tidak masuk akal.
        }}
        """
        
        skor_rel = 0.0
        jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
        if jawapan_ai:
            import re
            try:
                match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
                json_str = match.group(0) if match else jawapan_ai
                data_json = json.loads(json_str)
                skor_rel = float(data_json.get("skor_relevansi", 0.0))
            except Exception as e:
                print("Ralat JSON SPAF AI:", e)

        # Simpan ke Database berserta skor AI
        SpafPainPoint.objects.create(
            bengkel=bengkel,
            user=request.user,
            tajuk=domain,
            keterangan=desc,
            kesan=severity,
            keutamaan=keutamaan_val,
            skor_relevansi=skor_rel,
            is_processed_by_ai=True
        )
        
        # --- KIRAAN MATA UNTUK FRONTEND ---
        # Formula: 10 mata max * (skor_rel / 10.0)
        mata_diperoleh = int(10 * (skor_rel / 10.0))
        
        return JsonResponse({
            "success": True, 
            "mata_diperoleh": mata_diperoleh,
            "skor_relevansi": skor_rel
        })
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})

@login_required
@require_POST
def simpan_komen_pembaikan_ajax(request, pk):
    import json
    from .models import Bengkel, SpafProblemStatement
    
    try:
        data = json.loads(request.body)
        statement_id = data.get("statement_id")
        komen = data.get("komen", "").strip()

        if not komen:
            return JsonResponse({"success": False, "error": "Komen pembaikan tidak boleh kosong."})

        statement = get_object_or_404(SpafProblemStatement, id=statement_id)

        # --- ENJIN AI (ANTI-SAMPAH & EKSTRAK KATA KUNCI) ---
        prompt = f"""
        Anda adalah Penilai Kualiti. Nilaikan komen peserta terhadap Penyataan Masalah AI.
        Penyataan Asal: "{statement.masalah_utama}"
        Komen Peserta: "{komen}"
        
        1. Adakah komen ini logik, membina, dan bukan carutan/teks rawak seperti "asdasd"?
        2. Ekstrak 1 hingga 3 perkataan sebagai kata kunci utama dari komen ini (Cth: "Integrasi Farmasi", "Latihan Kakitangan").
        
        KEMBALIKAN FORMAT JSON SAHAJA:
        {{
          "skor_relevansi": 10.0,
          "kata_kunci": "Kata kunci pendek"
        }}
        """
        
        skor_rel = 0.0
        kata_kunci = ""
        jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
        if jawapan_ai:
            import re
            try:
                match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
                json_str = match.group(0) if match else jawapan_ai
                data_json = json.loads(json_str)
                skor_rel = float(data_json.get("skor_relevansi", 0.0))
                kata_kunci = str(data_json.get("kata_kunci", "")).strip()
            except Exception:
                pass
                
        mata_diperoleh = int(10 * (skor_rel / 10.0))
        
        if mata_diperoleh == 0:
            return JsonResponse({"success": True, "mata_diperoleh": 0, "status": "spam"})

        nama_peserta = request.user.first_name or request.user.username
        komen_berformat = f"[{nama_peserta}]: {komen}\n"

        if statement.catatan:
            statement.catatan += komen_berformat
        else:
            statement.catatan = komen_berformat
            
        # --- KEMAS KINI METRIK PAPAN PEMUKA MINI PENGANJUR ---
        statement.jumlah_refine += 1
        if kata_kunci and kata_kunci.lower() not in ["tiada", "none", ""]:
            if statement.kata_kunci_refine:
                senarai_kw = statement.kata_kunci_refine.split(', ')
                if kata_kunci not in senarai_kw:
                    senarai_kw.append(kata_kunci)
                statement.kata_kunci_refine = ", ".join(senarai_kw[-2:]) 
            else:
                statement.kata_kunci_refine = kata_kunci

        statement.save()

        # 🎯 BARIS PENYELAMAT: Pulangkan respon kejayaan kepada Frontend!
        return JsonResponse({"success": True, "mata_diperoleh": mata_diperoleh, "status": "lulus"})

    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})


@login_required
@require_POST
def sintesis_semula_komen_ajax(request, pk):
    """
    LANGKAH 2 (PENGANJUR):
    Penganjur klik butang untuk mengarahkan AI (Gemini 2.5 Flash) menyintesis 
    semua komen pembaikan yang telah terkumpul di ruangan 'catatan'.
    """
    import json, re
    from .models import Bengkel, SpafProblemStatement
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # Semak akses (Hanya Penganjur dibenarkan trigger AI ini)
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    # Cari mana-mana penyataan masalah KHAS UNTUK BENGKEL INI yang ada komen terkumpul
    statements_pending = SpafProblemStatement.objects.filter(user=bengkel.created_by, bengkel=bengkel).exclude(catatan__exact='')

    if not statements_pending.exists():
        return JsonResponse({"success": False, "error": "Tiada komen baharu untuk disintesis."})

    rekod_berjaya = 0

    for statement in statements_pending:
        prompt = f"""
        Anda adalah Pakar Analisis Strategik KKM. 
        Terdapat satu Pernyataan Masalah yang telah dijana sebelum ini, tetapi beberapa peserta telah memberikan komen pembaikan.
        
        DATA ASAL:
        - Masalah Utama: "{statement.masalah_utama}"
        - Skop: "{statement.skop}"
        - Sasaran/Kesan: "{statement.sasaran}"
        - Matlamat: "{statement.matlamat}"

        KOMEN PEMBAIKAN TERKUMPUL (Dari Peserta): 
        "{statement.catatan}"

        TUGASAN: Baiki dan perhalusi DATA ASAL di atas dengan menyerap dan memurnikan niat dari KOMEN PESERTA. 
        Jika komen selari, gabungkan. Jika bercanggah, cari jalan tengah yang paling profesional.
        
        KEMBALIKAN OUTPUT DALAM FORMAT JSON SAHAJA (Tanpa tag markdown ```):
        {{
          "masalah_utama": "Pernyataan yang telah dibaiki",
          "skop": "Skop yang dibaiki",
          "sasaran": "Sasaran yang dibaiki",
          "matlamat": "Matlamat yang dibaiki"
        }}
        """

        # MENGGUNAKAN HELPER FUNCTION (Dwi-Enjin: Tetap pada Gemini 2.5 Flash)
        jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
        
        if jawapan_ai:
            match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
            json_str = match.group(0) if match else jawapan_ai 
                
            try:
                data_json = json.loads(json_str)
                statement.masalah_utama = data_json.get("masalah_utama", statement.masalah_utama)
                statement.skop = data_json.get("skop", statement.skop)
                statement.sasaran = data_json.get("sasaran", statement.sasaran)
                statement.matlamat = data_json.get("matlamat", statement.matlamat)
                
                # PENTING: Kosongkan ruangan komen selepas berjaya disintesis
                statement.catatan = "" 
                statement.save()
                rekod_berjaya += 1
            except json.JSONDecodeError:
                pass # Abaikan ralat JSON jika ada, teruskan ke statement lain

    if rekod_berjaya > 0:
        return JsonResponse({"success": True, "mesej": f"Berjaya memurnikan {rekod_berjaya} penyataan masalah."})
    else:
        return JsonResponse({"success": False, "error": "AI gagal memberikan format yang betul, tiada perubahan dibuat."})

@login_required
@require_POST
def simpan_status_semakan_ajax(request, pk):
    """Menyimpan status butang (Sokong/Refine) ke dalam memori Session"""
    import json
    try:
        data = json.loads(request.body)
        id_kad = str(data.get("id_kad"))
        status = data.get("status")
        
        session_key = f"status_semakan_{pk}"
        if session_key not in request.session:
            request.session[session_key] = {}
            
        if status:
            request.session[session_key][id_kad] = status
        else:
            request.session[session_key].pop(id_kad, None) # Batal sokongan
            
        request.session.modified = True
        return JsonResponse({"success": True})
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})

@login_required
def peserta_problem(request, pk):
    import json
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # SEKATAN GATEKEEPER
    if bengkel.spaf_current_step < 2:
        messages.warning(request, "Modul Pernyataan Masalah belum dibuka oleh Penganjur. Sila tunggu arahan selanjutnya.")
        return redirect("bengkel:peserta_spaf", pk=pk)
    from .models import SpafProblemStatement
    senarai_master = SpafProblemStatement.objects.filter(user=bengkel.created_by, bengkel=bengkel).order_by('id')
    
    # Tarik memori butang dari session
    status_semakan = request.session.get(f"status_semakan_{pk}", {})
    
    return render(request, "bengkel/peserta_problem.html", {
        "bengkel": bengkel,
        "senarai_master": senarai_master,
        "status_semakan_json": json.dumps(status_semakan) # Wajib ada baris ini
    })

@login_required
@require_POST
def simpan_situational_ajax(request, pk):
    """Menyimpan data Imbasan Persekitaran (SWOT/PESTEL dll) dari peserta"""
    import json
    from .models import Bengkel, AnalisisSWOT, AnalisisPESTEL, AnalisisSOAR, Analisis5C
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    try:
        data = json.loads(request.body)
        jenis = data.get("jenis", "").lower()
        inputs = data.get("inputs", [])
        
        # Ekstrak nilai berdasarkan susunan array dari HTML
        def get_val(idx):
            return inputs[idx] if len(inputs) > idx else ""

        # Himpunkan kesemua teks input untuk dibaca oleh AI serentak
        teks_gabungan = " | ".join([str(val) for val in inputs if val])
        
        # --- ENJIN AI: PENAPIS SAMPAH KETAT & GAMIFIKASI ---
        prompt = f"""
        Anda adalah Penilai Kualiti yang SANGAT TEGAS. Semak input Analisis Situasi ({jenis.upper()}) yang dihantar oleh peserta.
        Topik/Bengkel: "{bengkel.title}"
        Teks Peserta: "{teks_gabungan}"
        
        TUGAS PENILAIAN:
        Nilaikan dari skala 0.0 hingga 10.0 berdasarkan syarat ketat ini:
        1. Adakah ia RELEVAN dengan konteks profesional, pengurusan, teknologi, atau topik bengkel?
        2. Jika teks mengandungi perkataan harian yang langsung tidak masuk akal untuk analisis strategik (contoh: "ikan", "nasi", "tidur", "main"), WAJIB BERIKAN SKOR 0.0.
        3. Jika teks rawak/carutan ("asdasd", "123123"), WAJIB BERIKAN SKOR 0.0.
        4. Jika jawapan terlalu pendek dan semberono, berikan skor rendah (1.0 - 4.0).
        
        KEMBALIKAN FORMAT JSON SAHAJA:
        {{
          "skor_relevansi": 8.5 // Nilaikan dengan jujur
        }}
        """
        
        skor_rel = 0.0
        jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
        if jawapan_ai:
            import re
            try:
                match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
                json_str = match.group(0) if match else jawapan_ai
                data_json = json.loads(json_str)
                skor_rel = float(data_json.get("skor_relevansi", 0.0))
            except Exception as e:
                pass
                
        # --- BLOK SAMPAH KETAT ---
        # Tendang keluar jika AI bagi skor kurang dari 5.0 (Iaitu tidak relevan dengan konteks profesional)
        if skor_rel < 5.0:
            return JsonResponse({"success": True, "mata_diperoleh": 0, "status": "spam"})

        # Simpan ke pangkalan data jika lulus ujian
        if jenis == 'swot':
            AnalisisSWOT.objects.create(
                bengkel=bengkel, user=request.user,
                kekuatan=get_val(0), kelemahan=get_val(1),
                peluang=get_val(2), ancaman=get_val(3),
                skor_relevansi=skor_rel
            )
        elif jenis == 'pestel':
            AnalisisPESTEL.objects.create(
                bengkel=bengkel, user=request.user,
                politik=get_val(0), ekonomi=get_val(1),
                sosial=get_val(2), teknologi=get_val(3),
                undang_undang=get_val(4), alam_sekitar=get_val(5),
                skor_relevansi=skor_rel
            )
        elif jenis == 'soar':
            AnalisisSOAR.objects.create(
                bengkel=bengkel, user=request.user,
                kekuatan=get_val(0), peluang=get_val(1),
                aspirasi=get_val(2), keputusan=get_val(3),
                skor_relevansi=skor_rel
            )
        elif jenis == '5c':
            Analisis5C.objects.create(
                bengkel=bengkel, user=request.user,
                syarikat=get_val(0), rakan_kongsi=get_val(1),
                pelanggan=get_val(2), pesaing=get_val(3), persekitaran=get_val(4),
                skor_relevansi=skor_rel
            )
        else:
            return JsonResponse({"success": False, "error": "Jenis kerangka kerja tidak sah."})

        # Pengiraan markah Gred (Max 10 mata setiap hantaran kerangka)
        mata_diperoleh = int(10 * (skor_rel / 10.0))
        if mata_diperoleh <= 0: mata_diperoleh = 5 # Minimum 5 mata bagi galakan

        return JsonResponse({"success": True, "mata_diperoleh": mata_diperoleh, "status": "lulus"})
        
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})

@login_required
@require_POST
def simpan_rca_ajax(request, pk):
    """Menyimpan data Root Cause Analysis dari peserta"""
    import json
    from .models import Bengkel, SpafRootCauseAnalysis
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    try:
        data = json.loads(request.body)
        masalah = data.get("masalah", "")
        framework = data.get("framework", "")
        inputs = data.get("inputs", [])

        if not masalah or not inputs:
            return JsonResponse({"success": False, "error": "Data tidak lengkap."})

        # --- ENJIN AI: PENAPIS SAMPAH KETAT & GAMIFIKASI ---
        teks_gabungan = " | ".join([str(item['value']) for item in inputs if item['value']])
        
        prompt = f"""
        Anda adalah Penilai Kualiti yang SANGAT TEGAS. Semak input Analisis Punca (RCA - {framework.upper()}) yang dihantar oleh peserta.
        Masalah Utama: "{masalah}"
        Input Punca Peserta: "{teks_gabungan}"
        
        TUGAS PENILAIAN:
        Nilaikan dari skala 0.0 hingga 10.0 berdasarkan syarat ketat ini:
        1. Adakah ia RELEVAN dengan konteks profesional untuk mencari punca (root cause) kepada masalah tersebut?
        2. Jika teks mengandungi perkataan harian yang langsung tidak masuk akal untuk analisis korporat (contoh: "ikan", "nasi", "ayam", "makan"), WAJIB BERIKAN SKOR 0.0.
        3. Jika teks rawak/carutan ("asdasd", "123123"), WAJIB BERIKAN SKOR 0.0.
        4. Jika jawapan logik dan menjawab persoalan "Kenapa masalah ini berlaku?", berikan skor yang munasabah (6.0 - 10.0).
        
        KEMBALIKAN FORMAT JSON SAHAJA:
        {{
          "skor_relevansi": 8.5
        }}
        """
        
        skor_rel = 0.0
        jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
        if jawapan_ai:
            import re
            try:
                match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
                json_str = match.group(0) if match else jawapan_ai
                data_json = json.loads(json_str)
                skor_rel = float(data_json.get("skor_relevansi", 0.0))
            except Exception as e:
                pass
                
        # --- BLOK SAMPAH KETAT ---
        if skor_rel < 5.0:
            return JsonResponse({"success": True, "mata_diperoleh": 0, "status": "spam"})

        # Susun data untuk disimpan dalam database
        punca_utama = f"[{framework.upper()}] " + (inputs[0]['value'] if inputs else "Tiada Data")
        punca_penyumbang = "\n".join([f"{item['label']}: {item['value']}" for item in inputs[1:]])

        SpafRootCauseAnalysis.objects.create(
            bengkel=bengkel,
            user=request.user,
            masalah=masalah,
            punca_utama=punca_utama,
            punca_penyumbang=punca_penyumbang,
            bukti="Data dipetakan dari kerangka RCA SPAF",
            skor_relevansi=skor_rel # <--- 🎯 KUNCI PENYELAMAT: Simpan markah ke DB!
        )
        
        # Pengiraan Markah (Max 15 mata untuk RCA)
        mata_diperoleh = int(15 * (skor_rel / 10.0))
        if mata_diperoleh <= 0: mata_diperoleh = 5 # Minimum galakan jika lulus tapi kualiti rendah
        
        return JsonResponse({"success": True, "mata_diperoleh": mata_diperoleh, "status": "lulus"})
        
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})

@login_required
@require_POST
def simpan_rca_validation_ajax(request, pk):
    """Menyimpan keputusan ke Session dan Database beserta saringan AI Gamifikasi"""
    import json
    from .models import SpafRootCauseAnalysis, SpafRootCauseValidation
    
    try:
        data = json.loads(request.body)
        id_finding = str(data.get("id_finding"))
        keputusan = data.get("keputusan") # 'yes' atau 'no'
        komen = data.get("komen", "").strip()

        finding = SpafRootCauseAnalysis.objects.get(id=int(id_finding))
        
        # HALANG DUPLIKASI (Peserta tak boleh verify banyak kali/spam Edit)
        if SpafRootCauseValidation.objects.filter(user=request.user, punca=f"RCA ID #{id_finding}").exists():
            return JsonResponse({"success": False, "error": "Anda telah pun mengesahkan penemuan ini."})

        skor_rel = 5.0 # Markah lalai jika setuju tanpa komen
        
        # --- ENJIN AI: PENAPIS SAMPAH (Hanya jika peserta letak komen) ---
        if komen:
            prompt = f"""
            Anda adalah Penilai Kualiti SANGAT TEGAS. Semak ulasan pengesahan (validation) dari pakar lapangan.
            Penemuan AI Asal: "{finding.punca_utama}"
            Komen Pakar: "{komen}"
            
            TUGAS PENILAIAN:
            Nilaikan ulasan pakar dari skala 0.0 hingga 10.0 berdasarkan syarat ketat ini:
            1. Adakah komen ini relevan dengan penemuan asal dan memberi konteks teknikal/profesional?
            2. Jika teks mengandungi perkataan harian merepek (cth: "ikan", "nasi", "ayam", "kucing", "test"), WAJIB BERI SKOR 0.0.
            3. Jika teks rawak/carutan ("asdasd", "123123"), WAJIB BERI SKOR 0.0.
            4. Jika ulasan logik, beri markah munasabah (6.0 - 10.0).
            
            KEMBALIKAN FORMAT JSON SAHAJA:
            {{
              "skor_relevansi": 8.5
            }}
            """
            jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
            if jawapan_ai:
                import re
                try:
                    match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
                    json_str = match.group(0) if match else jawapan_ai
                    data_json = json.loads(json_str)
                    skor_rel = float(data_json.get("skor_relevansi", 0.0))
                except Exception:
                    pass
            
            # --- BLOK SAMPAH KETAT ---
            if skor_rel < 5.0:
                return JsonResponse({"success": True, "mata_diperoleh": 0, "status": "spam"})

        # Pengiraan markah Gred (Max 10 mata untuk RCV)
        mata_diperoleh = int(10 * (skor_rel / 10.0))
        if mata_diperoleh <= 0: mata_diperoleh = 3 # Minimum 3 mata fallback

        # 1. SIMPAN DI SESSION
        session_key = f"rca_validation_{pk}"
        if session_key not in request.session:
            request.session[session_key] = {}
        request.session[session_key][id_finding] = {
            "keputusan": keputusan,
            "komen": komen,
            "mata": mata_diperoleh
        }
        request.session.modified = True

        # 2. SIMPAN DI DATABASE (Untuk panel Penganjur baca komen)
        nama_peserta = request.user.first_name or request.user.username
        tag_keputusan = "[SETUJU]" if keputusan == 'yes' else "[BANTAH]"
        catatan_baru = f"{tag_keputusan} {nama_peserta}: {komen if komen else 'Tiada komen khusus'} | "
        
        if finding.bukti == "Data dipetakan dari kerangka RCA SPAF":
            finding.bukti = catatan_baru
        else:
            finding.bukti += catatan_baru
        finding.save()
        
        # 3. SIMPAN REKOD VALIDASI UNTUK JEJAK DNA/GAMIFIKASI
        SpafRootCauseValidation.objects.create(
            bengkel=finding.bengkel,
            user=request.user,
            punca=f"RCA ID #{id_finding}",
            kaedah=keputusan,
            dapatan=komen,
            kesimpulan=tag_keputusan,
            skor_relevansi=skor_rel
        )
        
        return JsonResponse({"success": True, "mata_diperoleh": mata_diperoleh, "status": "lulus"})
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})

@login_required
@require_POST
def simpan_risk_ajax(request, pk):
    """Menyimpan data penilaian risiko ke pangkalan data beserta saringan AI Gamifikasi"""
    import json
    from .models import SpafRiskAnalysis, SpafRootCauseAnalysis
    
    bengkel = get_object_or_404(Bengkel, pk=pk)

    try:
        data = json.loads(request.body)
        risk_id = str(data.get("risk_id")) 
        
        try:
            prob = int(data.get("prob", 50))
            impact = int(data.get("impact", 50))
        except ValueError:
            prob = 50
            impact = 50
            
        mitigasi = data.get("mitigasi", "").strip()

        tajuk_risiko = f"Risiko Sistemik #{risk_id}"
        try:
            rca = SpafRootCauseAnalysis.objects.get(id=int(risk_id), bengkel=bengkel)
            tajuk_risiko = f"{rca.punca_utama}"
        except Exception:
            pass

        # HALANG DUPLIKASI (Kunci DB supaya peserta tak boleh Edit / Spam)
        if SpafRiskAnalysis.objects.filter(bengkel=bengkel, user=request.user, risiko=tajuk_risiko).exists():
            return JsonResponse({"success": False, "error": "Anda telah pun menilai unjuran risiko ini."})

        skor_rel = 5.0
        
        # --- ENJIN AI: PENAPIS SAMPAH & GAMIFIKASI ---
        if mitigasi:
            prompt = f"""
            Anda adalah Penilai Kualiti SANGAT TEGAS. Semak ulasan mitigasi risiko dari pakar lapangan.
            Risiko/Ancaman: "{tajuk_risiko}"
            Cadangan Mitigasi: "{mitigasi}"
            
            TUGAS PENILAIAN:
            Nilaikan ulasan mitigasi dari skala 0.0 hingga 10.0 berdasarkan syarat ketat ini:
            1. Adakah ia RELEVAN dan logik untuk mengurangkan kebarangkalian/impak risiko tersebut?
            2. Jika teks mengandungi perkataan harian merepek (cth: "ikan", "nasi", "ayam", "kucing", "test"), WAJIB BERI SKOR 0.0.
            3. Jika teks rawak/carutan ("asdasd", "123123"), WAJIB BERI SKOR 0.0.
            4. Jika jawapan praktikal dan logik, beri markah munasabah (6.0 - 10.0).
            
            KEMBALIKAN FORMAT JSON SAHAJA:
            {{
              "skor_relevansi": 8.5
            }}
            """
            jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
            if jawapan_ai:
                import re
                try:
                    match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
                    json_str = match.group(0) if match else jawapan_ai
                    data_json = json.loads(json_str)
                    skor_rel = float(data_json.get("skor_relevansi", 0.0))
                except Exception:
                    pass
            
            # --- BLOK SAMPAH KETAT ---
            if skor_rel < 5.0:
                return JsonResponse({"success": True, "mata_diperoleh": 0, "status": "spam"})

        # Pengiraan markah Gred (Max 15 mata untuk Penilaian Risiko)
        mata_diperoleh = int(15 * (skor_rel / 10.0))
        if mata_diperoleh <= 0: mata_diperoleh = 5

        def nilai_ke_tahap(nilai):
            if nilai >= 70: return "tinggi"
            elif nilai >= 40: return "sederhana"
            else: return "rendah"

        prob_str = f"{nilai_ke_tahap(prob)}|{prob}"
        impact_str = f"{nilai_ke_tahap(impact)}|{impact}"

        SpafRiskAnalysis.objects.create(
            bengkel=bengkel,
            user=request.user,
            risiko=tajuk_risiko,
            kemungkinan=prob_str,
            impak=impact_str,
            mitigasi=mitigasi,
            skor_relevansi=skor_rel,
            is_processed_by_ai=True
        )

        session_key = f"risk_assessment_{pk}"
        if session_key not in request.session:
            request.session[session_key] = {}

        request.session[session_key][risk_id] = {
            "prob": prob,
            "impact": impact,
            "mitigasi": mitigasi,
            "mata": mata_diperoleh
        }
        request.session.modified = True

        return JsonResponse({"success": True, "mata_diperoleh": mata_diperoleh, "status": "lulus"})
        
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})

@login_required
@require_POST
def jana_tema_penganjur_ajax(request, pk):
    """Penganjur menarik SEMUA isi laci peserta (References + SPAF) untuk sintesis AI"""
    import json, requests, re
    from .models import Bengkel, SpafProblemStatement, ContributionFile, BlueprintTheme
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    # 1. Kumpul Realiti Masalah (SPAF - Pernyataan Masalah)
    ps_list = SpafProblemStatement.objects.filter(bengkel=bengkel)
    teks_spaf = "\n".join([f"- Isu: {p.masalah_utama} (Skop: {p.skop})" for p in ps_list])

    # 2. Kumpul Konteks Dasar (References - Dokumen Rujukan)
    fail_rujukan = ContributionFile.objects.all()
    teks_references = "\n".join([f"- Polisi/Standard: {f.summary}" for f in fail_rujukan if f.summary])

    if not teks_spaf and not teks_references:
        return JsonResponse({"success": False, "error": "Laci peserta masih kosong. Tiada data rujukan atau masalah untuk disintesis."})

    # 3. Prompt AI yang lebih 'Bijak'
    prompt = f"""
    Anda adalah Pakar Arkitek Strategi Nasional.
    TUGASAN: Sintesiskan dua sumber data di bawah menjadi TEMA NASIONAL yang komprehensif.
    
    KONTEKS DASAR (Apa yang sepatutnya mengikut fail rujukan peserta):
    {teks_references if teks_references else "Tiada dasar khusus dimuat naik."}
    
    REALITI LAPANGAN (Isu sebenar dari aduan peserta):
    {teks_spaf if teks_spaf else "Tiada aduan khusus direkodkan."}
    
    ARAHAN PENTING KEPADA AI:
    - JANGAN hadkan jumlah Tema kepada 3 sahaja!
    - Hasilkan jumlah Tema secara DINAMIK (Mungkin 5, 8, 10, atau 15 Tema) bergantung kepada keluasan dan kepelbagaian data yang diberikan di atas.
    - Tujuan Tema ini adalah untuk MENGELOMPOKKAN (Cluster) ratusan idea peserta menjadi "Payung-payung Kategori" yang besar.
    - Pastikan Tema tidak berulang maksudnya. Gabungkan idea yang selari.
    
    Kembalikan dalam format JSON ARRAY SAHAJA. (Tanpa markdown).
    Contoh:
    [
      {{"tema": "Tajuk Tema 1", "penerangan": "Huraian tema (gabungan isu X & Y)", "kata_kunci": "Katakunci1, Katakunci2"}},
      {{"tema": "Tajuk Tema 2", "penerangan": "Huraian tema (gabungan isu Z)", "kata_kunci": "Katakunci3, Katakunci4"}}
    ]
    """

    # Menggunakan Helper Function (Dwi-Enjin)
    jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
    
    if jawapan_ai:
        import re, json
        try:
            match = re.search(r'\[.*\]', jawapan_ai, re.DOTALL)
            if match:
                json_str = match.group(0)
            else:
                json_str = re.sub(r"^```json\s*|```\s*$", "", jawapan_ai, flags=re.MULTILINE).strip()
            
            data_json = json.loads(json_str)
            
            BlueprintTheme.objects.filter(bengkel=bengkel).delete()
            for i, item in enumerate(data_json, 1):
                BlueprintTheme.objects.create(
                    bengkel=bengkel, urutan=i,
                    tema=item.get("tema", "Tema Umum"),
                    penerangan=item.get("penerangan", ""),
                    kata_kunci=item.get("kata_kunci", "")
                )
            
            # --- AUTO-UNLOCK LANGKAH 3 MAKRO ---
            # Buka pintu Langkah 3 untuk peserta secara automatik jika AI Penganjur berjaya
            if bengkel.blueprint_current_step < 3:
                bengkel.blueprint_current_step = 3
                bengkel.save(update_fields=['blueprint_current_step'])

            return JsonResponse({"success": True, "mesej": f"Sintesis {len(data_json)} Tema berjaya!"})
            
        except json.JSONDecodeError:
            return JsonResponse({"success": False, "error": "AI tidak memulangkan format JSON yang sah."})
        except Exception as e:
            return JsonResponse({"success": False, "error": str(e)})
    else:
        return JsonResponse({"success": False, "error": "Sistem AI (OpenAI GPT) gagal merespons."})

@login_required
def laporan_eksekutif(request, pk):
    from .models import Bengkel, BlueprintTheme, BlueprintPillar, BlueprintStrategy, BlueprintActionPlan, BlueprintIndicator, BlueprintAllocation, SpafPainPoint, SpafProblemStatement, SpafRootCauseAnalysis, SpafRiskAnalysis
    from django.contrib.auth import get_user_model
    User = get_user_model()

    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    themes = BlueprintTheme.objects.filter(bengkel=bengkel).order_by('urutan')
    pillars = BlueprintPillar.objects.filter(bengkel=bengkel).order_by('urutan')
    
    strategies = BlueprintStrategy.objects.filter(bengkel=bengkel).prefetch_related(
        'action_plans__indicators',
        'action_plans__allocations'
    ).order_by('urutan')
    
    # KIRAAN METRIK S.P.A.F TERPERINCI
    jumlah_aduan = SpafPainPoint.objects.filter(bengkel=bengkel).count()
    jumlah_skop_ai = SpafProblemStatement.objects.filter(bengkel=bengkel, user=bengkel.created_by).count()
    jumlah_rca = SpafRootCauseAnalysis.objects.filter(bengkel=bengkel).count()
    jumlah_risiko = SpafRiskAnalysis.objects.filter(bengkel=bengkel).count()
    
    # Kira Peratusan Validasi (Secara Ringkas)
    rca_list = SpafRootCauseAnalysis.objects.filter(bengkel=bengkel)
    setuju_count = 0
    bantah_count = 0
    for rca in rca_list:
        if rca.bukti and rca.bukti != "Data dipetakan dari kerangka RCA SPAF":
            setuju_count += rca.bukti.count("[SETUJU]")
            bantah_count += rca.bukti.count("[BANTAH]")
    
    total_undi = setuju_count + bantah_count
    kadar_konsensus = int((setuju_count / total_undi) * 100) if total_undi > 0 else (85 if jumlah_rca > 0 else 0)

    stats = {
        'jumlah_strategi': strategies.count(),
        'jumlah_tindakan': BlueprintActionPlan.objects.filter(bengkel=bengkel).count(),
        'jumlah_kpi': BlueprintIndicator.objects.filter(bengkel=bengkel).count(),
        'jumlah_sumber': BlueprintAllocation.objects.filter(bengkel=bengkel).count(),
        
        # Tambah ke context HTML
        'jumlah_aduan': jumlah_aduan,
        'jumlah_skop_ai': jumlah_skop_ai,
        'jumlah_rca': jumlah_rca,
        'kadar_konsensus': kadar_konsensus,
        'jumlah_risiko': jumlah_risiko,
    }

    senarai_peserta = User.objects.filter(jemputan__bengkel=bengkel).distinct()

    context = {
        'bengkel': bengkel,
        'themes': themes,
        'pillars': pillars,
        'strategies': strategies,
        'stats': stats,
        'senarai_peserta': senarai_peserta, 
    }
    return render(request, 'bengkel/laporan_eksekutif.html', context)

@login_required
def peserta_lobi(request, pk):
    """Lobi acara untuk peserta melihat maklumat sebelum masuk ke ruang kerja Blueprint."""
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # Semak sama ada pengguna adalah penganjur/admin atau peserta yang sah
    is_organizer = request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user
    
    jemputan = None
    if not is_organizer:
        jemputan = Jemputan.objects.filter(bengkel=bengkel, user=request.user, status="accepted").first()
        if not jemputan:
            messages.error(request, "Anda tidak mempunyai akses atau belum mengesahkan kehadiran untuk acara ini.")
            return redirect("bengkel:dashboard")

    # Tarik senarai peserta yang telah sah (accepted) dan mempunyai akaun User
    # Tarik senarai peserta
    senarai_peserta = Jemputan.objects.filter(
        bengkel=bengkel, status="accepted"
    ).exclude(user__isnull=True).select_related('user').order_by('nama')
    
    # Tarik data forum thread beserta balasan (reply)
    from .models import ForumThread
    forum_threads = ForumThread.objects.filter(bengkel=bengkel).select_related('user').prefetch_related('replies__user').order_by('-created_at')

    return render(request, "bengkel/peserta_lobi.html", {
        "bengkel": bengkel,
        "jemputan": jemputan,
        "tentative_items": bengkel.tentative.all().order_by('urutan'),
        "senarai_peserta": senarai_peserta,
        "forum_threads": forum_threads, # <-- Hantar data forum
    })

@login_required
@require_POST
def cipta_forum_thread(request, pk):
    """Fungsi untuk mencipta Topik/Thread baharu di Lobi"""
    from .models import ForumThread
    bengkel = get_object_or_404(Bengkel, pk=pk)
    kandungan = request.POST.get('kandungan', '').strip()
    
    if kandungan:
        ForumThread.objects.create(bengkel=bengkel, user=request.user, kandungan=kandungan)
        messages.success(request, "Perbincangan baharu telah dimuat naik.")
    
    return redirect(reverse('bengkel:peserta_lobi', kwargs={'pk': pk}) + "#ruangan-forum")

@login_required
@require_POST
def balas_forum_thread(request, thread_id):
    """Fungsi untuk membalas/reply Thread sedia ada di Lobi"""
    from .models import ForumThread, ForumReply
    thread = get_object_or_404(ForumThread, pk=thread_id)
    kandungan = request.POST.get('kandungan', '').strip()
    
    if kandungan:
        ForumReply.objects.create(thread=thread, user=request.user, kandungan=kandungan)
        messages.success(request, "Balasan anda telah dihantar.")
        
    return redirect(reverse('bengkel:peserta_lobi', kwargs={'pk': thread.bengkel.pk}) + "#ruangan-forum")

@login_required
@require_POST
def jana_rumusan_situasi_ajax(request, pk):
    """
    Mengumpul semua data SWOT, PESTEL, SOAR, dan 5C daripada semua peserta,
    kemudian menghantarnya ke AI untuk rumusan strategik.
    """
    import json
    from .models import Bengkel, AnalisisSWOT, AnalisisPESTEL, AnalisisSOAR, Analisis5C
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # Semak akses penganjur
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    # 1. Kumpul Data Kualitatif (Teks) dari semua kerangka
    swot_qs = AnalisisSWOT.objects.filter(bengkel=bengkel)
    pestel_qs = AnalisisPESTEL.objects.filter(bengkel=bengkel)
    soar_qs = AnalisisSOAR.objects.filter(bengkel=bengkel)
    fivec_qs = Analisis5C.objects.filter(bengkel=bengkel)

    teks_input = "DATA ANALISIS SITUASI DARI PESERTA:\n\n"
    
    for s in swot_qs:
        teks_input += f"- SWOT: Kekuatan({s.kekuatan}), Kelemahan({s.kelemahan}), Peluang({s.peluang}), Ancaman({s.ancaman})\n"
    for p in pestel_qs:
        teks_input += f"- PESTEL: Politikal({p.politik}), Ekonomi({p.ekonomi}), Sosial({p.sosial}), Teknologi({p.teknologi})\n"
    for sr in soar_qs:
        teks_input += f"- SOAR: Kekuatan({sr.kekuatan}), Peluang({sr.peluang}), Aspirasi({sr.aspirasi}), Hasil({sr.keputusan})\n"
    for c in fivec_qs:
        teks_input += f"- 5C: Agensi({c.syarikat}), Pelanggan({c.pelanggan}), Pesaing({c.pesaing})\n"

    if not swot_qs.exists() and not pestel_qs.exists():
        return JsonResponse({"success": False, "error": "Tiada data analisis daripada peserta untuk dirumuskan."})

    # 2. Sediakan Prompt untuk Gemini
    prompt = f"""
    Anda adalah Pakar Arkitek Strategik Nasional.
    TUGASAN: Sintesiskan semua input analisis situasi daripada peserta bengkel "{bengkel.title}" di bawah menjadi satu rumusan eksekutif yang tajam.
    
    INPUT PESERTA:
    {teks_input[:5000]} 

    ARAHAN:
    1. Kenalpasti 'Kekuatan Utama' (Asas Sedia Ada) yang paling kerap disebut.
    2. Kenalpasti 'Kelemahan Utama' (Kekangan Semasa) yang paling kritikal.
    3. Berikan satu 'Kesimpulan Ejen AI' (Visi Strategik) dalam satu perenggan pendek.
    
    KEMBALIKAN OUTPUT DALAM FORMAT JSON SAHAJA:
    {{
      "kekuatan": "Ringkasan kekuatan dalam 2 ayat",
      "kelemahan": "Ringkasan kelemahan dalam 2 ayat",
      "kesimpulan": "Kesimpulan strategik yang provokatif dan membina"
    }}
    """

    # 3. Panggil Enjin AI (Tetap guna Gemini 2.5 Flash untuk kos rendah)
    jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
    
    if jawapan_ai:
        import re
        try:
            match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
            json_str = match.group(0) if match else jawapan_ai
            data_json = json.loads(json_str)
            return JsonResponse({"success": True, "data": data_json})
        except Exception:
            return JsonResponse({"success": False, "error": "AI memulangkan format yang tidak sah."})
    
    return JsonResponse({"success": False, "error": "Sistem AI gagal merespons."})

@login_required
@require_POST
def sintesis_bantahan_ai_ajax(request, pk):
    """
    Menyedut semua komen 'Bantah' dari pangkalan data, 
    dan meminta Gemini merumuskan punca utama kenapa peserta tidak bersetuju.
    """
    import json
    from .models import Bengkel, SpafRootCauseAnalysis, Jemputan
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # Semak akses penganjur
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    # Tarik semua RCA yang berkaitan dengan bengkel ini SAHAJA
    rca_list = SpafRootCauseAnalysis.objects.filter(bengkel=bengkel)
    
    if not rca_list.exists() and (request.user.is_staff or request.user.is_superuser):
        rca_list = SpafRootCauseAnalysis.objects.filter(bengkel=bengkel)

    # Kumpul semua teks bantahan
    senarai_bantahan = []
    for rca in rca_list:
        if rca.bukti and "[BANTAH]" in rca.bukti:
            pecahan = rca.bukti.split(' | ')
            for b in pecahan:
                if "[BANTAH]" in b:
                    komen = b.replace("[BANTAH]", "").strip()
                    senarai_bantahan.append(f"- Isu AI: {rca.punca_utama} | Komen Pakar: {komen}")

    if not senarai_bantahan:
        return JsonResponse({"success": False, "error": "Tiada bantahan direkodkan setakat ini."})

    teks_bantahan = "\n".join(senarai_bantahan)

    # Prompt Gemini
    prompt = f"""
    Anda adalah Penasihat Strategik KKM.
    TUGASAN: Baca senarai komen bantahan dari Pakar Lapangan (SME) di bawah yang menolak hasil penemuan AI sebelum ini.
    Rumuskan apakah TEMA atau SEBAB UTAMA mereka menolak dapatan tersebut.

    SENARAI BANTAHAN:
    {teks_bantahan[:6000]}

    KEMBALIKAN OUTPUT DALAM FORMAT JSON SAHAJA:
    {{
      "rumusan_bantahan": "Rumusan padat dalam 2 ayat tentang sebab utama SME menolak.",
      "cadangan_tindakan": "Apa yang perlu pihak pengurusan lakukan berdasarkan bantahan ini."
    }}
    """

    # Panggil Enjin AI
    jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
    
    if jawapan_ai:
        import re
        try:
            match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
            json_str = match.group(0) if match else jawapan_ai
            data_json = json.loads(json_str)
            return JsonResponse({"success": True, "data": data_json})
        except Exception:
            return JsonResponse({"success": False, "error": "AI memulangkan format tidak sah."})

    return JsonResponse({"success": False, "error": "Sistem AI gagal merespons."})

@login_required
@require_POST
def sintesis_mitigasi_ai_ajax(request, pk):
    """
    Menyedut kesemua idea mitigasi pelbagai peserta untuk setiap risiko, 
    kemudian meminta AI merumuskannya kepada satu Langkah Kawalan Utama.
    """
    import json
    from .models import Bengkel, Jemputan, SpafRiskAnalysis
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # Semak akses penganjur
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    rca_qs = SpafRiskAnalysis.objects.filter(bengkel=bengkel)
    
    if not rca_qs.exists() and (request.user.is_staff or request.user.is_superuser):
        rca_qs = SpafRiskAnalysis.objects.filter(bengkel=bengkel)

    # Kumpul dan gabung idea mitigasi mengikut Risiko
    dict_risiko = {}
    for rca in rca_qs:
        tajuk = rca.risiko
        if tajuk not in dict_risiko:
            dict_risiko[tajuk] = []
        if rca.mitigasi and rca.mitigasi not in dict_risiko[tajuk]:
            dict_risiko[tajuk].append(rca.mitigasi)

    # Bina teks input untuk disuap ke AI (Ambil Top 5 sahaja supaya pantas)
    teks_input = ""
    for tajuk, mitigasi_list in list(dict_risiko.items())[:5]:
        teks_input += f"Risiko: {tajuk}\nIdea Peserta: {' | '.join(mitigasi_list)}\n\n"

    if not teks_input.strip():
        return JsonResponse({"success": False, "error": "Tiada idea mitigasi peserta untuk dirumuskan."})

    # Prompt AI (Gemini)
    prompt = f"""
    Anda adalah Pakar Pengurusan Risiko Korporat.
    TUGASAN: Baca senarai Risiko dan Idea Mitigasi yang pelbagai daripada peserta di bawah.
    Bagi SETIAP Risiko, rumuskan idea-idea yang panjang itu menjadi SATU (1) ayat padat sebagai 'Langkah Kawalan Utama'.

    DATA RISIKO & IDEA:
    {teks_input[:6000]}

    KEMBALIKAN OUTPUT DALAM FORMAT JSON ARRAY SAHAJA. (Tanpa markdown)
    Contoh Format:
    [
      {{"risiko": "Tajuk Risiko A", "rumusan_mitigasi": "Ayat rumusan komprehensif..."}},
      {{"risiko": "Tajuk Risiko B", "rumusan_mitigasi": "Ayat rumusan komprehensif..."}}
    ]
    """

    # Panggil Enjin AI
    jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
    
    if jawapan_ai:
        import re
        try:
            match = re.search(r'\[.*\]', jawapan_ai, re.DOTALL)
            json_str = match.group(0) if match else jawapan_ai
            data_json = json.loads(json_str)
            return JsonResponse({"success": True, "data": data_json})
        except Exception:
            return JsonResponse({"success": False, "error": "AI memulangkan format tidak sah."})

    return JsonResponse({"success": False, "error": "Sistem AI gagal merespons."})

@login_required
@require_POST
def murnikan_tema_ai_ajax(request, pk):
    """
    Menyedut semua idea tambahan (DNA) dari peserta untuk SATU tema tertentu,
    dan meminta AI merombak semula ayat Tema tersebut supaya menyerap idea-idea tersebut.
    """
    import json
    from .models import Bengkel, BlueprintTheme
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    try:
        data = json.loads(request.body)
        tema_id = data.get("tema_id")
        tema_obj = get_object_or_404(BlueprintTheme, pk=tema_id, bengkel=bengkel)
        
        # Tarik semua idea peserta untuk tema ini
        ideas = tema_obj.ideas.all()
        if not ideas.exists():
            return JsonResponse({"success": False, "error": "Tiada idea tambahan/DNA disuntik oleh peserta untuk tema ini lagi."})

        # Gabungkan semua idea menjadi satu teks panjang
        teks_idea = "\n".join([f"- {i.idea}" for i in ideas])

        # Prompt AI untuk proses 'Refinement'
        prompt = f"""
        Anda adalah Pakar Penggubal Dasar KKM.
        Satu Tema Nasional telah dirangka sebelum ini. Namun, peserta di lapangan telah memberikan pandangan dan idea penambahbaikan.
        
        TEMA ASAL:
        Tajuk: {tema_obj.tema}
        Penerangan: {tema_obj.penerangan}
        
        IDEA TAMBAHAN DARI PESERTA (DNA):
        {teks_idea[:5000]}
        
        TUGASAN: 
        Rombak dan tulis semula Tema Asal di atas. Serapkan maksud dari 'Idea Tambahan Peserta' ke dalam tajuk dan penerangan supaya ia menjadi satu Tema Versi 2.0 yang lebih komprehensif, matang, dan mewakili suara majoriti.
        
        KEMBALIKAN OUTPUT DALAM FORMAT JSON SAHAJA:
        {{
          "tema_baharu": "Tajuk Tema yang mungkin dikekalkan atau dibaiki",
          "penerangan_baharu": "Penerangan komprehensif yang telah menyerap idea peserta"
        }}
        """

        # Panggil AI
        jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
        
        if jawapan_ai:
            import re
            try:
                match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
                json_str = match.group(0) if match else jawapan_ai
                data_json = json.loads(json_str)
                
                # Kemaskini Database dengan Tema Versi 2.0!
                tema_obj.tema = data_json.get("tema_baharu", tema_obj.tema)
                tema_obj.penerangan = data_json.get("penerangan_baharu", tema_obj.penerangan)
                tema_obj.save()
                
                # Boleh juga padam/arkib idea selepas disintesis untuk bersihkan log (Pilihan)
                # tema_obj.ideas.all().delete() 
                
                return JsonResponse({
                    "success": True, 
                    "tema_baharu": tema_obj.tema,
                    "penerangan_baharu": tema_obj.penerangan
                })
            except Exception:
                return JsonResponse({"success": False, "error": "AI memulangkan format tidak sah."})

        return JsonResponse({"success": False, "error": "Sistem AI gagal merespons."})
        
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})

@login_required
@require_POST
def murnikan_pillar_ai_ajax(request, pk):
    """
    Menyedut idea skop tambahan dari peserta untuk SATU Teras tertentu,
    dan meminta AI merombak semula ayat Teras tersebut.
    """
    import json
    from .models import Bengkel, BlueprintPillar
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    try:
        data = json.loads(request.body)
        pillar_id = data.get("pillar_id")
        pillar_obj = get_object_or_404(BlueprintPillar, pk=pillar_id, bengkel=bengkel)
        
        # Tarik semua idea skop tambahan untuk teras ini
        ideas = pillar_obj.ideas.all()
        if not ideas.exists():
            return JsonResponse({"success": False, "error": "Tiada idea skop tambahan disuntik oleh peserta untuk teras ini."})

        teks_idea = "\n".join([f"- {i.idea}" for i in ideas])

        prompt = f"""
        Anda adalah Pakar Arkitek Strategi Nasional.
        Satu Teras Strategik (Pillar) telah dirangka. Namun, peserta di lapangan telah memberikan idea skop tambahan.
        
        TERAS ASAL:
        Tajuk: {pillar_obj.teras}
        Penerangan: {pillar_obj.penerangan}
        
        IDEA SKOP TAMBAHAN DARI PESERTA:
        {teks_idea[:5000]}
        
        TUGASAN: 
        Rombak dan tulis semula Teras Asal di atas. Serapkan maksud dari 'Idea Skop Tambahan' ke dalam tajuk dan penerangan supaya ia menjadi satu Teras Versi 2.0 yang lebih utuh dan merangkumi skop yang dicadangkan.
        
        KEMBALIKAN OUTPUT DALAM FORMAT JSON SAHAJA:
        {{
          "teras_baharu": "Tajuk Teras yang mungkin dikekalkan atau dibaiki",
          "penerangan_baharu": "Penerangan komprehensif yang telah menyerap idea skop peserta"
        }}
        """

        # Panggil AI
        jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
        
        if jawapan_ai:
            import re
            try:
                match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
                json_str = match.group(0) if match else jawapan_ai
                data_json = json.loads(json_str)
                
                # Kemaskini Database dengan Teras Versi 2.0!
                pillar_obj.teras = data_json.get("teras_baharu", pillar_obj.teras)
                pillar_obj.penerangan = data_json.get("penerangan_baharu", pillar_obj.penerangan)
                pillar_obj.save()
                
                return JsonResponse({
                    "success": True, 
                    "teras_baharu": pillar_obj.teras,
                    "penerangan_baharu": pillar_obj.penerangan
                })
            except Exception:
                return JsonResponse({"success": False, "error": "AI memulangkan format tidak sah."})

        return JsonResponse({"success": False, "error": "Sistem AI gagal merespons."})
        
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})

@login_required
@require_POST
def murnikan_strategi_ai_ajax(request, pk):
    """
    Menyedut semua cadangan inisiatif/projek dari peserta untuk SATU Fasa Strategi,
    dan meminta AI merombak semula penerangan Fasa tersebut supaya menyerap idea-idea praktikal tersebut.
    """
    import json
    from .models import Bengkel, BlueprintStrategy
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    try:
        data = json.loads(request.body)
        strategi_id = data.get("strategi_id")
        strategi_obj = get_object_or_404(BlueprintStrategy, pk=strategi_id, bengkel=bengkel)
        
        # Tarik semua idea inisiatif projek untuk fasa ini
        ideas = strategi_obj.ideas.all()
        if not ideas.exists():
            return JsonResponse({"success": False, "error": "Tiada idea inisiatif disuntik oleh peserta untuk fasa ini."})

        teks_idea = "\n".join([f"- {i.idea}" for i in ideas])

        prompt = f"""
        Anda adalah Pakar Pengurusan Projek Strategik Nasional.
        Satu Fasa Pelaksanaan (Roadmap) telah dirangka. Namun, peserta di lapangan telah memberikan idea-idea inisiatif dan projek spesifik.
        
        FASA STRATEGI ASAL:
        Tajuk Fasa: {strategi_obj.tajuk}
        Penerangan Asal: {strategi_obj.penerangan}
        
        CADANGAN INISIATIF DARI PESERTA (SME):
        {teks_idea[:5000]}
        
        TUGASAN: 
        Rombak dan tulis semula Fasa Strategi Asal di atas. Serapkan inisiatif/projek praktikal yang dicadangkan oleh peserta ke dalam 'Penerangan' supaya ia menjadi satu pelan pelaksanaan yang konkrit dan holistik. Pastikan ayat kekal profesional dan tidak meleret.
        
        KEMBALIKAN OUTPUT DALAM FORMAT JSON SAHAJA:
        {{
          "tajuk_baharu": "Tajuk Fasa yang dikekalkan atau dibaiki agar lebih tepat",
          "penerangan_baharu": "Penerangan komprehensif yang telah menyerap nama-nama projek cadangan peserta"
        }}
        """

        # Panggil AI
        jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
        
        if jawapan_ai:
            import re
            try:
                match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
                json_str = match.group(0) if match else jawapan_ai
                data_json = json.loads(json_str)
                
                # Kemaskini Database dengan Fasa Strategi Versi 2.0!
                strategi_obj.tajuk = data_json.get("tajuk_baharu", strategi_obj.tajuk)
                strategi_obj.penerangan = data_json.get("penerangan_baharu", strategi_obj.penerangan)
                strategi_obj.save()
                
                return JsonResponse({
                    "success": True, 
                    "tajuk_baharu": strategi_obj.tajuk,
                    "penerangan_baharu": strategi_obj.penerangan
                })
            except Exception:
                return JsonResponse({"success": False, "error": "AI memulangkan format tidak sah."})

        return JsonResponse({"success": False, "error": "Sistem AI gagal merespons."})
        
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})
    
@login_required
def korpus_live_data_ajax(request, pk):
    """API ringan untuk memberikan data terkini (Live) kepada Dashboard Penganjur"""
    from django.db.models import Avg, Count
    from .models import Bengkel, ContributionFile
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # Tarik fail terkini
    fail_peserta = ContributionFile.objects.filter(contribution__bengkel=bengkel)
    jumlah_dokumen = fail_peserta.count()
    
    # Kira purata pantas
    purata_novelty = fail_peserta.aggregate(Avg('skor_novelty'))['skor_novelty__avg'] or 0.0
    purata_actionability = fail_peserta.aggregate(Avg('indeks_kebolehtindakan'))['indeks_kebolehtindakan__avg'] or 0
    
    # Sediakan data carta tema
    tema_qs = fail_peserta.exclude(tema_utama__isnull=True).exclude(tema_utama="").values('tema_utama').annotate(count=Count('id')).order_by('-count')[:6]
    tema_labels = [item['tema_utama'] for item in tema_qs] if tema_qs else ['Belum Ada Data']
    tema_data = [item['count'] for item in tema_qs] if tema_qs else [0]
    
    # Data Tambahan untuk Progress & Top Dokumen
    dokumen_diproses = fail_peserta.exclude(skor_novelty=0.0).count()
    peratus_proses = int((dokumen_diproses / jumlah_dokumen) * 100) if jumlah_dokumen > 0 else 0
    
    dokumen_top_qs = fail_peserta.exclude(skor_novelty=0.0).order_by('-indeks_kebolehtindakan')[:3]
    dokumen_top_list = []
    for d in dokumen_top_qs:
        ext = str(d.original_name).split('.')[-1].lower()
        ikon = "fa-file-pdf text-rose-500" if ext == 'pdf' else ("fa-file-word text-blue-500" if ext in ['doc', 'docx'] else "fa-file-alt text-slate-500")
        
        dokumen_top_list.append({
            "nama_fail": d.original_name,
            "penyumbang": d.contribution.jemputan.nama if d.contribution and d.contribution.jemputan else "Peserta",
            "skor": d.indeks_kebolehtindakan,
            "ikon": ikon
        })

    # Data tambahan untuk menyokong kemas kini live Carta Radar Domain
    domain_labels = [item['tema_utama'] for item in tema_qs[:5]] if tema_qs else ['Klinikal', 'IT', 'Polisi', 'Kewangan', 'Tadbir']
    domain_data = [item['count'] for item in tema_qs[:5]] if tema_qs else []

    return JsonResponse({
        "jumlah_dokumen": jumlah_dokumen,
        "purata_actionability": int(purata_actionability),
        "purata_novelty": round(purata_novelty, 1),
        "tema_labels": tema_labels,
        "tema_data": tema_data,
        "domain_labels": domain_labels,
        "domain_data": domain_data,
        "dokumen_diproses": dokumen_diproses,
        "peratus_proses": peratus_proses,
        "dokumen_top_list": dokumen_top_list
    })

@login_required
def rca_live_data_ajax(request, pk):
    """API ringan untuk memberikan data terkini (Live) Analitik RCA kepada Dashboard Penganjur"""
    from .models import Bengkel, Jemputan, SpafRootCauseAnalysis
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # Dapatkan jumlah peserta bengkel yang sah
    jumlah_peserta = Jemputan.objects.filter(bengkel=bengkel, status="accepted").count()
    rca_list = SpafRootCauseAnalysis.objects.filter(bengkel=bengkel)

    # Fallback Data (Jika pangkalan data kosong dan penganjur nak test)
    if not rca_list.exists() and (request.user.is_staff or request.user.is_superuser):
        rca_list = SpafRootCauseAnalysis.objects.filter(bengkel=bengkel)

    ishikawa_count = rca_list.filter(punca_utama__icontains='[ISHIKAWA]').count()
    whys_list = rca_list.filter(punca_utama__icontains='[5WHYS]')
    whys_count = whys_list.count()
    fmea_count = rca_list.filter(punca_utama__icontains='[FMEA]').count()
    fta_count = rca_list.filter(punca_utama__icontains='[FTA]').count()

    # Peratusan Tiada Sumbangan
    users_menyumbang = rca_list.values('user_id').distinct().count()
    tiada_sumbangan_kiraan = jumlah_peserta - users_menyumbang
    if tiada_sumbangan_kiraan < 0: tiada_sumbangan_kiraan = 0 
        
    tiada_sumbangan_pct = 0
    if jumlah_peserta > 0:
        tiada_sumbangan_pct = int((tiada_sumbangan_kiraan / jumlah_peserta) * 100)

    # Graf Doughnut (Tools)
    graf_tools_data = [ishikawa_count, whys_count, fmea_count, fta_count, tiada_sumbangan_kiraan]
    
    return JsonResponse({
        "ishikawa_count": ishikawa_count,
        "whys_count": whys_count,
        "fmea_count": fmea_count,
        "fta_count": fta_count,
        "tiada_sumbangan_pct": tiada_sumbangan_pct,
        "graf_tools_data": graf_tools_data
    })

@login_required
def rca_validation_live_data_ajax(request, pk):
    """API ringan untuk memberikan data terkini (Live) Pengesahan RCA kepada Penganjur"""
    from .models import Bengkel, SpafRootCauseAnalysis
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    rca_list = SpafRootCauseAnalysis.objects.filter(bengkel=bengkel).order_by('-created_at')
    if not rca_list.exists() and (request.user.is_staff or request.user.is_superuser):
        rca_list = SpafRootCauseAnalysis.objects.filter(bengkel=bengkel).order_by('-created_at')
        
    jumlah_rca = rca_list.count()
    setuju_count = 0
    bantah_count = 0
    senarai_bantahan = []

    for rca in rca_list:
        if rca.bukti and rca.bukti != "Data dipetakan dari kerangka RCA SPAF":
            setuju_count += rca.bukti.count("[SETUJU]")
            bantah_count += rca.bukti.count("[BANTAH]")
            
            pecahan_bukti = rca.bukti.split(' | ')
            for b in pecahan_bukti:
                if "[BANTAH]" in b:
                    teks_komen = b.replace("[BANTAH]", "").strip()
                    senarai_bantahan.append({
                        "masalah_sumber": rca.punca_utama,
                        "komen_peserta": teks_komen
                    })

    if setuju_count == 0 and bantah_count == 0:
        setuju_count = int(jumlah_rca * 0.85) if jumlah_rca > 0 else 0
        bantah_count = jumlah_rca - setuju_count

    total_undi = setuju_count + bantah_count
    kadar_konsensus = int((setuju_count / total_undi) * 100) if total_undi > 0 else 85
    kadar_bantahan = 100 - kadar_konsensus if total_undi > 0 else 15
    
    graf_validation_data = [setuju_count, bantah_count]

    return JsonResponse({
        "jumlah_rca": jumlah_rca,
        "kadar_konsensus": kadar_konsensus,
        "kadar_bantahan": kadar_bantahan,
        "graf_validation_data": graf_validation_data,
        "senarai_bantahan": senarai_bantahan
    })

@login_required
def risk_live_data_ajax(request, pk):
    """API ringan untuk memberikan data terkini (Live) Matriks Risiko kepada Penganjur"""
    from .models import Bengkel, SpafRiskAnalysis
    import json
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    rca_qs = SpafRiskAnalysis.objects.filter(bengkel=bengkel)
    if not rca_qs.exists() and (request.user.is_staff or request.user.is_superuser):
        rca_qs = SpafRiskAnalysis.objects.filter(bengkel=bengkel)
        
    dict_risiko = {}
    
    for rca in rca_qs:
        tajuk = rca.risiko
        prob_val = 50
        impak_val = 50
        
        if rca.kemungkinan and "|" in rca.kemungkinan:
            prob_val = int(rca.kemungkinan.split("|")[1])
        if rca.impak and "|" in rca.impak:
            impak_val = int(rca.impak.split("|")[1])
            
        if tajuk not in dict_risiko:
            dict_risiko[tajuk] = {
                "tajuk": tajuk,
                "mitigasi": rca.mitigasi,
                "sum_prob": 0,
                "sum_impak": 0,
                "count": 0
            }
        
        dict_risiko[tajuk]["sum_prob"] += prob_val
        dict_risiko[tajuk]["sum_impak"] += impak_val
        dict_risiko[tajuk]["count"] += 1
        
        if rca.mitigasi and rca.mitigasi not in dict_risiko[tajuk]["mitigasi"] and len(dict_risiko[tajuk]["mitigasi"]) < 120:
            dict_risiko[tajuk]["mitigasi"] += f" | {rca.mitigasi}"

    top_risiko = []
    chart_datasets = []
    
    warna_buih = [
        {'bg': 'rgba(220, 38, 38, 0.7)', 'border': '#dc2626', 'ui': 'red'},   
        {'bg': 'rgba(249, 115, 22, 0.7)', 'border': '#f97316', 'ui': 'orange'},   
        {'bg': 'rgba(251, 191, 36, 0.7)', 'border': '#f59e0b', 'ui': 'amber'},   
        {'bg': 'rgba(59, 130, 246, 0.7)', 'border': '#3b82f6', 'ui': 'blue'},   
        {'bg': 'rgba(16, 185, 129, 0.7)', 'border': '#10b981', 'ui': 'emerald'},   
    ]
    
    for i, (tajuk, data) in enumerate(list(dict_risiko.items())[:5]): 
        avg_prob = int(data["sum_prob"] / data["count"])
        avg_impak = int(data["sum_impak"] / data["count"])
        r_val = 15 + (data["count"] * 3)
        c = warna_buih[i % len(warna_buih)]
        tahap_teks = "Tinggi" if avg_impak >= 70 else ("Sederhana" if avg_impak >= 40 else "Rendah")
        kebarangkalian_teks = "Tinggi" if avg_prob >= 70 else ("Sederhana" if avg_prob >= 40 else "Rendah")
        
        top_risiko.append({
            "risiko": tajuk,
            "mitigasi": data["mitigasi"][:120] + "..." if len(data["mitigasi"]) > 120 else data["mitigasi"],
            "x_val": avg_prob,
            "y_val": avg_impak,
            "kemungkinan": kebarangkalian_teks,
            "impak": tahap_teks,
            "ui_color": c['ui']
        })
        
        label_pendek = (tajuk[:25] + '...') if len(tajuk) > 25 else tajuk
        chart_datasets.append({
            "label": label_pendek,
            "data": [{"x": avg_prob, "y": avg_impak, "r": r_val}],
            "backgroundColor": c['bg'],
            "borderColor": c['border']
        })

    return JsonResponse({
        "jumlah_risiko": len(dict_risiko),
        "top_risiko": top_risiko,
        "chart_datasets": chart_datasets
    })

@login_required
def tema_live_data_ajax(request, pk):
    """API ringan untuk memberikan data terkini (Live) Undian Tema & Idea kepada Penganjur"""
    from .models import Bengkel, BlueprintTheme, ThemeVote, ThemeIdea
    import json
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # 1. Kira jumlah keseluruhan undian dan idea
    jumlah_undi = ThemeVote.objects.filter(tema__bengkel=bengkel).count()
    jumlah_idea = ThemeIdea.objects.filter(tema__bengkel=bengkel).count()

    # 2. Sediakan data untuk graf dan kemas kini kad tema
    senarai_tema = BlueprintTheme.objects.filter(bengkel=bengkel).order_by('urutan')
    
    graf_labels = []
    graf_data = []
    tema_votes = {} # Simpan rekod undi setiap kad mengikut ID
    
    for t in senarai_tema:
        undi_count = t.votes.count()
        graf_labels.append(f"TEMA-0{t.urutan}")
        graf_data.append(undi_count)
        tema_votes[str(t.id)] = undi_count

    return JsonResponse({
        "jumlah_undi": jumlah_undi,
        "jumlah_idea": jumlah_idea,
        "graf_labels": graf_labels,
        "graf_data": graf_data,
        "tema_votes": tema_votes
    })

@login_required
def pillar_live_data_ajax(request, pk):
    """API ringan untuk memberikan data terkini (Live) Undian Teras & Idea Skop kepada Penganjur"""
    from .models import Bengkel, BlueprintPillar, PillarVote, PillarIdea
    import json
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # 1. Kira jumlah keseluruhan sokongan dan idea skop
    jumlah_sokongan = PillarVote.objects.filter(pillar__bengkel=bengkel).count()
    jumlah_idea = PillarIdea.objects.filter(pillar__bengkel=bengkel).count()

    # 2. Sediakan data untuk graf dan kemas kini kad teras
    senarai_pillar = BlueprintPillar.objects.filter(bengkel=bengkel).order_by('urutan')
    
    graf_labels = []
    graf_data = []
    pillar_votes = {} # Simpan rekod undi setiap kad mengikut ID
    
    for p in senarai_pillar:
        undi_count = p.votes.count()
        graf_labels.append(f"TERAS-0{p.urutan}")
        graf_data.append(undi_count)
        pillar_votes[str(p.id)] = undi_count

    return JsonResponse({
        "jumlah_sokongan": jumlah_sokongan,
        "jumlah_idea": jumlah_idea,
        "graf_labels": graf_labels,
        "graf_data": graf_data,
        "pillar_votes": pillar_votes
    })

@login_required
@require_POST
def sintesis_forum_ai_ajax(request, pk):
    """Konsep 4: AI membaca keseluruhan lobi dan merumuskan mood & isu hangat"""
    import json
    from .models import Bengkel, ForumThread
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    threads = ForumThread.objects.filter(bengkel=bengkel).prefetch_related('replies')
    if not threads.exists():
        return JsonResponse({"success": False, "error": "Tiada perbincangan di lobi untuk dianalisis oleh AI."})

    teks_forum = ""
    for t in threads:
        teks_forum += f"\nTopik oleh {t.user.username}: {t.kandungan}\n"
        for r in t.replies.all():
            teks_forum += f" - Balasan {r.user.username}: {r.kandungan}\n"

    prompt = f"""
    Anda adalah Penganalisis Tingkah Laku (Behavioral Analyst).
    TUGASAN: Baca perbincangan para pakar (SME) di lobi pra-acara di bawah.
    Rumuskan 'Mood/Sentimen' keseluruhan peserta dan senaraikan 3 Isu Utama yang sedang rancak dibincangkan.

    DATA PERBINCANGAN:
    {teks_forum[:6000]}

    KEMBALIKAN OUTPUT DALAM FORMAT JSON SAHAJA (Tanpa markdown):
    {{
      "mood_keseluruhan": "Teruja / Risau / Keliru dll beserta 1 ayat huraian",
      "isu_hangat": ["Isu 1", "Isu 2", "Isu 3"]
    }}
    """

    jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
    if jawapan_ai:
        import re
        try:
            match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
            json_str = match.group(0) if match else jawapan_ai
            data_json = json.loads(json_str)
            return JsonResponse({"success": True, "data": data_json})
        except Exception:
            return JsonResponse({"success": False, "error": "AI memulangkan format tidak sah."})

    return JsonResponse({"success": False, "error": "Sistem AI gagal merespons."})

@login_required
@require_POST
def jana_rumusan_rca_ajax(request, pk):
    """Mengarahkan AI membaca dan mengelompokkan punca masalah RCA yang panjang"""
    import json, re
    from .models import Bengkel, SpafRootCauseAnalysis

    bengkel = get_object_or_404(Bengkel, pk=pk)
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    rca_list = SpafRootCauseAnalysis.objects.filter(bengkel=bengkel)
    if not rca_list.exists():
        return JsonResponse({"success": False, "error": "Tiada data RCA untuk dirumuskan."})

    # Kumpul data mentah
    ishi_raw = [r.punca_utama.replace("[ISHIKAWA]", "").strip() for r in rca_list.filter(punca_utama__icontains='[ISHIKAWA]')]
    whys_raw = [r.punca_utama.replace("[5WHYS]", "").strip() for r in rca_list.filter(punca_utama__icontains='[5WHYS]')]
    fmea_raw = [r.punca_utama.replace("[FMEA]", "").strip() for r in rca_list.filter(punca_utama__icontains='[FMEA]')]
    fta_raw = [r.punca_utama.replace("[FTA]", "").strip() for r in rca_list.filter(punca_utama__icontains='[FTA]')]

    def get_sample(raw_list):
        from collections import Counter
        c = Counter(raw_list)
        return "\n".join([f"- {k} (Kekerapan: {v})" for k, v in c.most_common(20)])

    prompt = f"""
    Anda adalah Pakar Analisis Punca (Root Cause Analyst).
    TUGASAN: Baca senarai masalah mentah yang panjang daripada 4 kerangka RCA di bawah.
    Kelompokkan masalah-masalah yang serupa dan hasilkan LABEL PENDEK (Maksimum 5 patah perkataan) untuk paparan graf.
    Agihkan semula kekerapan mengikut kumpulan baru anda.

    DATA ISHIKAWA:
    {get_sample(ishi_raw) if ishi_raw else "Tiada Data"}

    DATA 5-WHYS:
    {get_sample(whys_raw) if whys_raw else "Tiada Data"}

    DATA FMEA:
    {get_sample(fmea_raw) if fmea_raw else "Tiada Data"}

    DATA FTA:
    {get_sample(fta_raw) if fta_raw else "Tiada Data"}

    KEMBALIKAN DALAM FORMAT JSON SAHAJA:
    {{
      "ishikawa": [{{"cause": "Label Pendek", "pct": 40}}],
      "whys": [{{"cause": "Label Pendek", "count": 25}}],
      "fmea": [{{"cause": "Label Pendek", "rpn": 300}}],
      "fta": [{{"cause": "Label Pendek", "count": 15}}]
    }}
    Nota: Untuk setiap kerangka, berikan maksimum 5 kumpulan tertinggi sahaja. Untuk Ishikawa, pastikan jumlah pct bernilai lebih kurang 100. Untuk FMEA, rpn adalah antara 100-400.
    """

    jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)

    if jawapan_ai:
        try:
            match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
            json_str = match.group(0) if match else jawapan_ai
            data_json = json.loads(json_str)

            # Simpan ke session supaya apabila halaman dimuat semula, ia akan menggunakan data ini!
            request.session[f'rca_ai_cache_{pk}'] = data_json
            request.session.modified = True

            return JsonResponse({"success": True})
        except Exception as e:
            return JsonResponse({"success": False, "error": f"Format AI tidak sah: {str(e)}"})

    return JsonResponse({"success": False, "error": "AI gagal merespons."})

@login_required
@require_POST
def jana_profil_kognitif_ajax(request, uid):
    """Mengarahkan AI membaca log aktiviti peserta dan menjana Profil Psikometrik Kognitif"""
    import json, re
    from django.utils import timezone
    from .models import UserProfile, SpafPainPoint, ThemeIdea, PillarIdea, StrategyIdea, ActionPlanIdea, AllocationIdea

    # Pastikan user ada kuasa Penganjur atau Superadmin (atau dia owner kepada diri sendiri)
    if not (request.user.is_staff or request.user.is_superuser or str(request.user.id) == str(uid)):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    target_user = get_object_or_404(User, pk=uid)
    profile, _ = UserProfile.objects.get_or_create(user=target_user)

    # Kumpul semua teks sumbangan peserta
    teks_terkumpul = ""
    for a in SpafPainPoint.objects.filter(user=target_user): teks_terkumpul += f"- Isu/Aduan dikesan: {a.keterangan}\n"
    for t in ThemeIdea.objects.filter(user=target_user): teks_terkumpul += f"- Idea penyelesaian: {t.idea}\n"
    for s in StrategyIdea.objects.filter(user=target_user): teks_terkumpul += f"- Cadangan Strategi: {s.idea}\n"
    for al in AllocationIdea.objects.filter(user=target_user): teks_terkumpul += f"- Keperluan Sumber: {al.idea_sumber}\n"
    
    # Masukkan ringkasan dokumen yang di-upload ke dalam log pemikiran AI
    from .models import ContributionFile
    for cf in ContributionFile.objects.filter(contribution__jemputan__user=target_user):
        if cf.summary or cf.tema_utama:
            teks_terkumpul += f"- Fail disumbang: {cf.summary} (Fokus Tema: {cf.tema_utama or 'Umum'})\n"

    if not teks_terkumpul.strip():
        return JsonResponse({"success": False, "error": "Peserta ini belum mempunyai sebarang sumbangan teks yang cukup untuk dianalisis oleh AI."})

    from django.utils.translation import get_language
    lang = get_language()
    bahasa_sistem = "ENGLISH" if lang == 'en' else "BAHASA MELAYU"

    prompt = f"""
    Anda adalah Pakar Psikologi Industri dan Pengurusan Bakat.
    TUGASAN: Baca rekod sumbangan input daripada peserta bernama "{target_user.get_full_name() or target_user.username}" di bawah.
    Analisis gaya pemikiran, fokus, dan kecenderungan beliau.

    DATA SUMBANGAN PESERTA (SME):
    {teks_terkumpul[:5000]}

    ARAHAN:
    1. Pilih SATU 'Persona Kognitif' utama dari senarai ini yang paling tepat menggambarkan peserta:
       - Pemikir Berwawasan (Visionary)
       - Pelaksana Teknikal (Executor)
       - Pakar Proses & Dasar (Process/Policy Expert)
       - Pengkritik / Penganalisis Risiko (Devil's Advocate)
       - Pemerhati Aktif (Active Observer)
    2. Tulis SATU perenggan (2-3 ayat sahaja) rumusan eksekutif yang menerangkan mengapa persona ini dipilih berdasarkan topik yang diketengahkannya.
    3. PENTING: Terjemahkan nama Persona dan Huraian Eksekutif ke dalam bahasa {bahasa_sistem}.

    KEMBALIKAN OUTPUT DALAM FORMAT JSON SAHAJA:
    {{
      "persona_ai": "Nama Persona (cth: Pelaksana Teknikal / Executor)",
      "huraian_kognitif": "Perenggan huraian profesional..."
    }}
    """

    jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True, enforce_language=bahasa_sistem)

    if jawapan_ai:
        try:
            match = re.search(r'\{.*\}', jawapan_ai, re.DOTALL)
            json_str = match.group(0) if match else jawapan_ai
            data_json = json.loads(json_str)

            # Simpan secara kekal ke pangkalan data
            profile.persona_ai = data_json.get("persona_ai", "Pemerhati Aktif")
            profile.huraian_kognitif = data_json.get("huraian_kognitif", "Analisis siap.")
            profile.ai_generated_at = timezone.now()
            profile.save()

            return JsonResponse({
                "success": True,
                "persona_ai": profile.persona_ai,
                "huraian_kognitif": profile.huraian_kognitif
            })
        except Exception as e:
            return JsonResponse({"success": False, "error": f"Ralat parse JSON AI: {str(e)}"})

    return JsonResponse({"success": False, "error": "Sistem AI gagal merespons."})

# ==============================================================================
# MASTER QR (SELF CHECK-IN)
# ==============================================================================
@login_required
def papar_master_qr(request, pk):
    """Penganjur memaparkan Master QR di skrin projektor untuk diimbas oleh peserta"""
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        messages.error(request, "Akses ditolak.")
        return redirect("bengkel:dashboard")

    # Ini adalah URL yang akan dimasukkan ke dalam QR Code
    check_in_url = request.build_absolute_uri(reverse("bengkel:peserta_self_check_in", kwargs={"master_token": bengkel.master_qr_token}))

    # Kita boleh gunakan templat 'open_reg_info.html' atau cipta satu templat baharu. 
    # Untuk kelancaran, kita cipta templat baharu.
    return render(request, "bengkel/papar_master_qr.html", {
        "bengkel": bengkel,
        "check_in_url": check_in_url,
    })

@login_required
def peserta_self_check_in(request, master_token):
    """Endpoint AJAX: Dipanggil oleh kamera bimbit peserta setelah mereka mengimbas Master QR"""
    if request.method != "POST":
        return JsonResponse({"success": False, "error": "Kaedah tidak sah."})

    # 1. Cari bengkel berdasarkan master_token tersebut
    try:
        bengkel = Bengkel.objects.get(master_qr_token=master_token)
    except Bengkel.DoesNotExist:
        return JsonResponse({"success": False, "error": "Kod QR tidak dikenali atau acara tidak wujud."})

    # 2. Semak sama ada peserta ini mempunyai jemputan SAH ke bengkel ini
    try:
        jemputan = Jemputan.objects.get(bengkel=bengkel, user=request.user, status="accepted")
    except Jemputan.DoesNotExist:
        return JsonResponse({"success": False, "error": "Anda tiada jemputan rasmi ke acara ini."})

    # 3. Semak jika sudah daftar masuk
    if jemputan.sudah_hadir:
        return JsonResponse({"success": False, "already": True, "error": "Kehadiran telah pun disahkan sebelum ini.", "bengkel_id": bengkel.pk})

    # 4. Rekod kehadiran!
    Kehadiran.objects.create(
        jemputan=jemputan,
        checked_in_by=request.user 
    )

    # Beri respon sukses supaya UI kamera peserta boleh tunjuk Mesej Hijau, lalu membawanya ke Lobi
    return JsonResponse({
        "success": True, 
        "mesej": f"Hadir! Mengalihkan ke lobi...",
        "bengkel_id": bengkel.pk
    })

@login_required
@require_POST
def simpan_maklumbalas_ajax(request, pk):
    """Logik AJAX untuk peserta memberikan rating 5-bintang dan ulasan kepada penganjur"""
    import json
    from .models import Bengkel, MaklumBalasPenganjur
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    try:
        data = json.loads(request.body)
        rating_val = int(data.get("rating", 5))
        komen_val = data.get("komen", "").strip()
        
        # Keselamatan: Pastikan nilai bintang adalah sah antara 1 hingga 5
        if not (1 <= rating_val <= 5):
            return JsonResponse({"success": False, "error": "Rating mestilah antara 1 hingga 5 bintang."})
            
        if not bengkel.created_by:
            return JsonResponse({"success": False, "error": "Acara ini tidak mempunyai penganjur rekod yang sah."})
            
        # Simpan atau kemaskini maklum balas (Overwrite jika peserta tukar fikiran)
        maklum_balas, created = MaklumBalasPenganjur.objects.get_or_create(
            bengkel=bengkel,
            peserta=request.user,
            defaults={
                'penganjur': bengkel.created_by,
                'rating': rating_val,
                'komen': komen_val
            }
        )
        
        if not created:
            maklum_balas.rating = rating_val
            maklum_balas.komen = komen_val
            maklum_balas.save()
            
        return JsonResponse({"success": True, "mesej": "Maklum balas anda berjaya direkodkan. Terima kasih!"})
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})
    
@login_required
@require_POST
def kemaskini_kapasiti_meja_ajax(request, pk):
    import json
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    try:
        data = json.loads(request.body)
        kapasiti = int(data.get("kapasiti", 10))
        
        # Simpan ke pangkalan data
        bengkel.kapasiti_semeja = kapasiti
        bengkel.save(update_fields=['kapasiti_semeja'])
        
        return JsonResponse({"success": True})
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})

@login_required
@require_POST
def kunci_bengkel_ajax(request, pk):
    """Penganjur mengesahkan Laporan Eksekutif dan menutup acara (Kunci Sistem)"""
    import json
    from .models import Bengkel
    
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    # Semak akses
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    try:
        # Tukar status kepada True (Terkunci)
        bengkel.is_locked = True
        bengkel.save()
        
        return JsonResponse({"success": True, "mesej": "Acara berjaya ditamatkan! Sistem kini dikunci sepenuhnya untuk peserta."})
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})


@login_required
@require_POST
def unlock_spaf_step_ajax(request, pk):
    """Fungsi Penganjur membuka kunci modul SPAF seterusnya untuk peserta"""
    import json
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    try:
        data = json.loads(request.body)
        next_step = int(data.get("step", 2))
        
        # Buka kunci jika langkah sasaran lebih tinggi dari sekarang
        if next_step > bengkel.spaf_current_step:
            bengkel.spaf_current_step = next_step
            bengkel.save(update_fields=['spaf_current_step'])
            
        return JsonResponse({"success": True, "mesej": f"Modul {next_step} berjaya dibuka kepada peserta!"})
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})
    
@login_required
@require_POST
def unlock_blueprint_step_ajax(request, pk):
    """Fungsi Penganjur membuka kunci modul makro Blueprint seterusnya untuk peserta"""
    import json
    bengkel = get_object_or_404(Bengkel, pk=pk)
    
    if not (request.user.is_staff or request.user.is_superuser or bengkel.created_by == request.user):
        return JsonResponse({"success": False, "error": "Akses ditolak."})

    try:
        data = json.loads(request.body)
        next_step = int(data.get("step", 2))
        
        # Buka kunci jika langkah sasaran lebih tinggi dari sekarang
        if next_step > bengkel.blueprint_current_step:
            bengkel.blueprint_current_step = next_step
            bengkel.save(update_fields=['blueprint_current_step'])
            
        return JsonResponse({"success": True, "mesej": f"Blueprint Langkah {next_step} berjaya dibuka!"})
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)})
    

from django.views.decorators.csrf import csrf_exempt
import json
import boto3
from django.conf import settings

@csrf_exempt
def worker_webhook(request):
    """
    🤖 SERVERLESS WEBHOOK WORKER (Kalis Timeout Render 30-saat)
    Menerima ID Bengkel, memproses 3 dokumen secara berperingkat,
    and mencetuskan tindak balas berantai sehingga PDF Laporan siap sepenuhnya.
    """
    if request.method != 'POST':
        return JsonResponse({"error": "Kaedah tidak dibenarkan"}, status=405)
    
    try:
        data = json.loads(request.body)
        bengkel_pk = data.get("bengkel_pk")
        bengkel = Bengkel.objects.get(pk=bengkel_pk)
        
        # Tarik fail-fail rujukan peserta yang belum dianalisis oleh AI (atau belum diekstrak teks)
        from .models import ContributionFile
        from django.db.models import Q
        
        fail_belum_proses = ContributionFile.objects.filter(
            Q(teks_diekstrak__isnull=True) | Q(teks_diekstrak="") | Q(tema_utama__isnull=True),
            contribution__bengkel=bengkel
        )
        
        if fail_belum_proses.exists():
            # 🚀 KUNCI KESELAMATAN: Proses 3 fail sahaja setiap pusingan
            batch_fail = fail_belum_proses[:3]
            
            for cf in batch_fail:
                text = cf.teks_diekstrak or ""
                
                # JIKA TEKS KOSONG, EKSTRAK SEKARANG DI BELAKANG TABIR
                if not text.strip():
                    import os
                    from django.conf import settings
                    # Pastikan kita boleh baca fail secara fizikal
                    try:
                        abs_path = os.path.join(settings.MEDIA_ROOT, cf.file.name)
                        # Kita panggil fungsi _extract dari views secara selamat
                        teks_baru = _extract_text_from_file(abs_path)
                        if teks_baru:
                            cf.teks_diekstrak = teks_baru
                            cf.save(update_fields=['teks_diekstrak'])
                            text = teks_baru
                        else:
                            cf.teks_diekstrak = "Gagal Mengekstrak Teks"
                            cf.save(update_fields=['teks_diekstrak'])
                    except Exception as e:
                        print(f"Ralat ekstrak fail {cf.original_name} di Celery: {e}")
                        continue
                
                if text.strip() and text != "Gagal Mengekstrak Teks":
                    from .tasks import analisis_fail_dengan_ai
                    hasil = analisis_fail_dengan_ai(text, bengkel.title)
                    if hasil:
                        cf.skor_novelty = hasil.get('novelty_score', 0)
                        cf.skor_relevansi = hasil.get('relevance_score', 0) # --- TANGKAP SKOR RELEVANSI DARI AI ---
                        cf.indeks_kebolehtindakan = hasil.get('actionability_index', 0)
                        cf.nada_dokumen = hasil.get('sentiment', 'Neutral')
                        cf.tema_utama = hasil.get('main_theme', 'Lain-lain')
                        cf.kompleksiti_kognitif = hasil.get('cognitive_complexity', 'Pemikiran Linear')
                        cf.analitik_data = hasil
                        cf.save()
                else:
                    cf.tema_utama = "Tiada Kandungan Teks"
                    cf.save()
            
            # 🔄 TINDAK BALAS BERANTAI: Tembak mesej baru ke SQS untuk panggil Lambda sambung baki fail
            sqs_client = boto3.client(
                'sqs',
                aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
                aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
                region_name=settings.AWS_REGION_NAME
            )
            
            # 🎯 Kekalkan rantaian URL dinamik
            target_url = request.build_absolute_uri('/bengkel/api/worker-webhook/')
            
            sqs_client.send_message(
                QueueUrl=settings.AWS_SQS_QUEUE_URL,
                MessageBody=json.dumps({
                    "bengkel_pk": bengkel_pk,
                    "target_url": target_url
                })
            )
            return JsonResponse({"status": "Pusingan batch berjaya. Mesej berantai dihantar ke SQS."})
            
        else:
            # 🏁 GRAND FINALE: Semua fail dah dianalisis! Sekarang jana Laporan Ringkasan Eksekutif & PDF
            from .models import BengkelLaporan
            
            # Pastikan laporan akhir belum wujud untuk elak gelung pemprosesan berulang
            if not BengkelLaporan.objects.filter(bengkel=bengkel, status="done").exists():
                from .tasks import process_bengkel_task
                
                # Panggil task asal abang (Ia akan skip gelung fail sebab semua dah ada data analitik, 
                # and terus lompat ke proses bina PDF. Sangat laju, sekitar 10 saat sahaja!)
                process_bengkel_task(bengkel.pk)
                return JsonResponse({"status": "Analisis selesai. Laporan PDF Eksekutif berjaya dijana!"})
                
            return JsonResponse({"status": "Sistem dalam keadaan idle. Semua laporan sedia ada."})
            
    except Exception as e:
        print(f"❌ [Webhook Error]: {e}")
        return JsonResponse({"error": str(e)}, status=500)

@login_required
@require_POST
def jana_tentatif_ai_ajax(request):
    """
    Menerima dokumen (PDF/Word), ekstrak teks, and arahkan Gemini
    menyusun aturcara dalam format JSON untuk dipaparkan di form.html
    """
    import json, re, os
    from django.core.files.storage import FileSystemStorage
    
    if 'dokumen' not in request.FILES:
        return JsonResponse({'success': False, 'error': 'Sila muat naik dokumen aturcara bengkel.'})
    
    fail = request.FILES['dokumen']
    
    # 1. Simpan fail sementara untuk diekstrak
    fs = FileSystemStorage()
    filename = fs.save(fail.name, fail)
    file_path = fs.path(filename)
    
    try:
        # 2. Guna fungsi sedia ada untuk sedut teks
        teks_dokumen = _extract_text_from_file(file_path)
        
        if not teks_dokumen.strip():
            return JsonResponse({'success': False, 'error': 'Gagal membaca teks. Sila pastikan ia bukan fail gambar kosong.'})
            
        # 3. Prompt AI untuk menstrukturkan data ke JSON
        prompt = f"""
        Anda adalah Pembantu Penjadualan Acara.
        TUGASAN: Ekstrak jadual tentatif dari teks di bawah dan tukarkan kepada format JSON ARRAY.
        Pastikan 'masa_mula' dan 'masa_tamat' dalam format 24-jam (HH:MM). Jika tiada masa tamat, buat anggaran munasabah atau biarkan kosong.
        
        TEKS DOKUMEN:
        {teks_dokumen[:5000]}
        
        KEMBALIKAN DALAM FORMAT JSON ARRAY SAHAJA. (Tanpa markdown)
        Contoh Wajib:
        [
          {{"masa_mula": "08:00", "masa_tamat": "09:00", "aktiviti": "Pendaftaran Peserta", "penerangan": "Pengedaran tag nama & minum pagi"}},
          {{"masa_mula": "09:00", "masa_tamat": "10:30", "aktiviti": "Ucaptama Oleh Pengarah", "penerangan": ""}}
        ]
        """
        
        # 4. Panggil Enjin Gemini
        jawapan_ai = panggil_ai_dwi_enjin(prompt, guna_json_format=True)
        
        if jawapan_ai:
            match = re.search(r'\[.*\]', jawapan_ai, re.DOTALL)
            json_str = match.group(0) if match else jawapan_ai
            data_json = json.loads(json_str)
            return JsonResponse({'success': True, 'data': data_json})
        else:
            return JsonResponse({'success': False, 'error': 'AI gagal memproses dokumen tersebut.'})
            
    except Exception as e:
        return JsonResponse({'success': False, 'error': str(e)})
    finally:
        # 5. Padam fail sementara untuk jimat ruang server
        if os.path.exists(file_path):
            os.remove(file_path)
