// [S174 owner] PANEL 1 (danh sách) của màn "Kế hoạch ký kết HĐ — Thao tác" — 1 cột, 2 TẦNG:
//   · tầng TRÊN  = phiếu Duyệt NCC ĐÃ DUYỆT (có NCC trúng thầu) mà CHƯA lập kế hoạch ký kết,
//                  gom theo DỰ ÁN — "việc còn phải làm"; bấm = nạp thẳng vào form tạo;
//   · tầng DƯỚI  = kế hoạch ký kết ĐÃ TẠO — "việc đã làm rồi"; bấm = XEM tại chỗ (read-only).
// File MIRROR SHA256 identical với fe-user counterpart.
//
// 🔴 LỊCH SỬ ĐẶT SAI CHỖ — ghi để không tái phạm: bản đầu S174 gắn panel này thành cột 3 của
// màn *Duyệt NCC — Thao tác* (`PurchaseEvaluationWorkspacePage`). Anh bác: **"chỗ duyệt NCC
// đã golive và đã tốt rồi"** — ý anh từ đầu là làm màn Thao tác của KHKK **GIỐNG GIAO DIỆN**
// Thao tác của Duyệt NCC, chứ không phải chèn thêm thứ vào màn Duyệt NCC. Đã revert
// `PurchaseEvaluationWorkspacePage` về đúng byte của `1b3bcaa`. Bài: "làm giống màn X" ≠
// "sửa màn X".
//
// 🔴 0 ENDPOINT MỚI — dùng lại đúng 2 cửa đã sống:
//   · `GET /contract-signing-plans/approved-pe-awaiting-plan` (chính cửa `KhkkCreatePage`
//     đang gọi) → `KhkkPeCandidateDto[]`; "chưa có kế hoạch" = `existingGroups` RỖNG.
//     🔸 Từ K2 S164 cửa này KHÔNG còn loại PE đã có kế hoạch (nó trả cả PE đã dùng vài
//     nhóm, kèm `existingGroups`) ⇒ lọc rỗng là việc của FE, đừng tưởng BE lọc hộ.
//   · `GET /contract-signing-plans` → `PagedResult<KhkkListItemDto>`.
//
// 🔴 GATE QUYỀN (gotcha #85 — mặt đã cắn thật ở S162): màn đang đứng là *Duyệt NCC*, nhưng
// CẢ HAI cửa trên gác bằng policy `KeHoachKyKet.Read` (`ContractSigningPlansController.cs:36`
// + `MenuPermissionHandler.cs:40` so khớp CHÍNH XÁC 1 key — KHÔNG kế thừa cha, KHÔNG OR key
// con). Người chỉ có quyền PE mà không có KHKK sẽ ăn 403 rải khắp cột. ⇒ gate bằng ĐÚNG key
// của endpoint, và khi thiếu quyền thì NÓI RA thay vì để cột trắng trông như "chưa có gì".
import { useQuery } from '@tanstack/react-query'
import { ChevronRight, FileCheck, FileSignature, FolderTree, Lock, Pencil } from 'lucide-react'
import { useState } from 'react'
import { usePermission } from '@/hooks/usePermission'
import { api } from '@/lib/api'
import { cn } from '@/lib/cn'
import {
  KHKK_PHASE_BADGE,
  KHKK_PHASE_LABELS,
  KhkkPhase,
  type KhkkListItemDto,
  type KhkkPeCandidateDto,
  type PagedResult,
} from '@/types/khkk'

const KHKK_TIER_PAGE_SIZE = 30

function formatDate(iso: string): string {
  return new Date(iso).toLocaleDateString('vi-VN', { day: '2-digit', month: '2-digit', year: 'numeric' })
}

/** Vỏ 1 tầng — `min-h-0 flex-1` ở CẢ HAI tầng là thứ tạo ra tỉ lệ 50/50 thật; thiếu
 *  `min-h-0` thì con cuộn được sẽ đẩy tầng phình theo nội dung, hỏng tỉ lệ. */
function Tier({
  title, count, icon, accent, children,
}: {
  title: string
  count: number | null
  icon: React.ReactNode
  accent: string
  children: React.ReactNode
}) {
  return (
    <div className="flex min-h-0 flex-1 flex-col">
      <div className="flex shrink-0 items-center gap-2 border-b border-slate-200 bg-slate-50/80 px-3 py-2">
        <span className={cn('inline-flex h-5 w-5 items-center justify-center rounded', accent)} aria-hidden>
          {icon}
        </span>
        <span className="min-w-0 flex-1 truncate text-[11px] font-bold uppercase tracking-wide text-slate-600">
          {title}
        </span>
        {count !== null && (
          <span className="shrink-0 rounded-full bg-slate-100 px-2 py-0.5 text-[10px] font-medium text-slate-600">
            {count}
          </span>
        )}
      </div>
      <div className="min-h-0 flex-1 overflow-y-auto">{children}</div>
    </div>
  )
}

export function KhkkWorkListPanel({
  onPickPe,
  onPickPlan,
  selectedPeId,
  selectedPlanId,
  onEditPlan,
  group,
}: {
  /** Bấm PE ở tầng trên → host nạp phiếu đó vào form tạo (Ở LẠI màn, không điều hướng). */
  onPickPe: (peId: string) => void
  /** Bấm KHKK ở tầng dưới → host mở phiếu đó ở panel 2, CHỈ XEM. Anh chốt: "click vào phiếu
   *  nào thì cho hiện phiếu đó luôn, chỉ view là đc, ko re-direct về phiếu gốc". */
  onPickPlan: (planId: string) => void
  selectedPeId?: string | null
  selectedPlanId?: string | null
  /** [S175 · O7] Bấm bút chì trên card KHKK → host mở phiếu ở chế-độ SỬA. Không truyền ⇒
   *  không vẽ bút chì (cột giữ đúng hình cũ). */
  onEditPlan?: (planId: string) => void
  /** [S175 - anh: "Loc theo quy trinh luon"] Nhom duyet cua leaf menu dang dung.
   *  Tang duoi CHI hien ke hoach thuoc dung nhom nay - dung o leaf N2 ma thay phieu
   *  N1/N7 la doc nham "nhom nay dang co viec". `null` = khong loc. */
  group?: number | null
}) {
  // [S175 — anh: "Cho hiển thị theo cây nằm dự án hạng mục"] Tập KHOÁ đang GẬP.
  // Dùng tập-gập (không phải tập-mở) để mặc-định là MỞ HẾT: dự án/hạng mục mới xuất hiện sau
  // này tự mở, không bị ẩn im lặng vì chưa ai bấm mở bao giờ.
  const [collapsed, setCollapsed] = useState<Set<string>>(new Set())
  const { can } = usePermission()
  // 🔴 ĐÚNG key policy của endpoint — KHÔNG dùng `Khkk_*` (đó là key MENU, tầng hiển thị).
  const canKhkk = can('KeHoachKyKet', 'Read')

  const candidates = useQuery({
    // Key dùng CHUNG với `KhkkCreatePage` là CỐ Ý: tạo kế hoạch xong quay lại đây thì
    // cột tự tươi, không phải nhớ invalidate thêm một key thứ hai.
    queryKey: ['khkk-pe-candidates'],
    queryFn: async () =>
      (await api.get<KhkkPeCandidateDto[]>('/contract-signing-plans/approved-pe-awaiting-plan')).data,
    enabled: canKhkk,
  })

  const plans = useQuery({
    queryKey: ['khkk-list', { side: true, page: 1, pageSize: KHKK_TIER_PAGE_SIZE }],
    queryFn: async () =>
      (await api.get<PagedResult<KhkkListItemDto>>('/contract-signing-plans', {
        params: { page: 1, pageSize: KHKK_TIER_PAGE_SIZE },
      })).data,
    enabled: canKhkk,
  })

  // "Chưa được tạo phiếu KHKK" = chưa dùng nhóm duyệt NÀO. `?? []` phòng payload cũ còn
  // trong cache React Query (DTO trước K2 không có field này) — thiếu guard thì `.length` nổ.
  const awaiting = (candidates.data ?? []).filter((p) => (p.existingGroups ?? []).length === 0)

  // [S175 · O8] CÂY 2 CẤP **Dự án › Hạng mục** — anh chốt "cho cây thư mục dự án hạng mục
  // ra luôn nhé". Trước đây chỉ gom 1 cấp theo dự án, nên một dự án nhiều gói thầu đổ ra
  // một danh sách phẳng dài, không thấy được ranh giới hạng mục.
  // 🔴 0 ENDPOINT MỚI: `workItemName` ĐÃ có sẵn trong `KhkkPeCandidateDto` (`khkk.ts:423`)
  // ⇒ cấp 2 là gom lại trên dữ-liệu đang có, không phải gọi thêm cửa nào.
  // `Map` giữ thứ tự chèn ở CẢ HAI cấp nên dự án/hạng mục có phiếu mới nhất nổi lên trước
  // (BE đã trả sẵn theo thứ tự).
  const byProject = new Map<string, Map<string, KhkkPeCandidateDto[]>>()
  for (const pe of awaiting) {
    const pKey = pe.projectName ?? '(chưa gán dự án)'
    const wKey = pe.workItemName ?? '(chưa gán hạng mục)'
    let byWorkItem = byProject.get(pKey)
    if (!byWorkItem) { byWorkItem = new Map(); byProject.set(pKey, byWorkItem) }
    const bucket = byWorkItem.get(wKey)
    if (bucket) bucket.push(pe)
    else byWorkItem.set(wKey, [pe])
  }
  /** Đếm phiếu của cả một dự án (cộng mọi hạng mục con) — số ở header cấp 1. */
  const countProject = (byWorkItem: Map<string, KhkkPeCandidateDto[]>) =>
    [...byWorkItem.values()].reduce((s, arr) => s + arr.length, 0)

  // [S174 owner] "chỗ thao tác chỉ hiển trả lại và nháp, từ chối thôi nhé" ⇒ DANH-SÁCH-TRẮNG
  // 3 trạng thái, KHÔNG phải loại-trừ. 🔴 Đổi từ loại-trừ sang danh-sách-trắng là CÓ CHỦ ĐÍCH:
  // loại-trừ (`!== DaDuyet`) sẽ tự động NHẬN mọi phase mới thêm về sau; danh-sách-trắng thì
  // phase mới phải được khai TƯỜNG MINH mới hiện. Ở màn Thao tác — nơi chỉ nên có việc CÒN
  // SỬA ĐƯỢC — im lặng nhận thêm là hỏng, nên chọn vế chặt.
  // 🔸 `ChoDuyet` CŨNG bị loại (khác lượt trước chỉ bỏ `DaDuyet`): phiếu đang trình duyệt thì
  //    người soạn không thao tác gì được nữa.
  // 🔴 KHAI GIỚI-HẠN (lọc ở FE): endpoint chỉ nhận `phase` ĐƠN, không nhận nhiều ⇒ (1) KHÔNG
  //    dùng `plans.data.total` làm số hiển thị (nó đếm MỌI phase) — đếm từ mảng đã lọc;
  //    (2) cửa-sổ 30 phiếu mới nhất có thể toàn phiếu đã duyệt ⇒ tầng dưới rỗng dù còn nháp
  //    cũ hơn. Chấp nhận ở UAT; muốn chuẩn thì BE nhận `phases[]` rồi lọc SERVER-SIDE.
  const OPEN_PHASES: number[] = [KhkkPhase.DangSoanThao, KhkkPhase.TraLai, KhkkPhase.TuChoi]
  const planItems = (plans.data?.items ?? [])
    .filter((k) => OPEN_PHASES.includes(k.phase))
    // [S175] +loc NHOM. `group == null` => khong loc (giu duong cu cho host khong truyen).
    .filter((k) => group == null || k.approvalGroup === group)

  return (
    <aside className="flex min-h-0 flex-col overflow-hidden border-r border-slate-200 bg-white">
      {!canKhkk ? (
        // Cột trắng đọc thành "chưa có gì" — phải nói ĐÚNG lý do là thiếu quyền.
        <div className="flex flex-1 flex-col items-center justify-center gap-2 px-4 text-center">
          <Lock className="h-5 w-5 text-slate-300" />
          <p className="text-[11px] text-slate-500">
            Bạn chưa có quyền xem Kế hoạch ký kết HĐ nên cột này không dựng được. Việc duyệt NCC
            ở hai panel bên trái không bị ảnh hưởng.
          </p>
        </div>
      ) : (
        <>
          <Tier
            title="Đã duyệt · chưa lập KHKK"
            count={awaiting.length}
            accent="bg-amber-50 text-amber-600"
            icon={<FileCheck className="h-3 w-3" />}
          >
            {candidates.isLoading && <div className="p-3 text-[11px] text-slate-500">Đang tải…</div>}
            {candidates.isError && (
              <div className="space-y-1.5 p-3">
                <p className="text-[11px] text-slate-500">Không tải được danh sách.</p>
                <button
                  type="button"
                  onClick={() => candidates.refetch()}
                  className="rounded-md border border-slate-300 px-2 py-1 text-[10px] font-semibold text-slate-600 hover:border-brand-300 hover:bg-brand-50"
                >
                  Thử lại
                </button>
              </div>
            )}
            {!candidates.isLoading && !candidates.isError && awaiting.length === 0 && (
              <p className="p-3 text-[11px] italic text-slate-500">
                Mọi phiếu đã duyệt đều đã có kế hoạch ký kết.
              </p>
            )}
            {/* [S175 · O8] CẤP 1 = Dự án (sticky, `z-20`) · CẤP 2 = Hạng mục (sticky dưới nó,
                `z-10` + thụt lề + vạch trái). 🔴 Hai `z` khác nhau là BẮT BUỘC: cùng `z` thì
                header hạng mục trượt ĐÈ lên header dự án khi cuộn, mất mất cấp cha. */}
            {[...byProject.entries()].map(([projectName, byWorkItem]) => {
              const pCollapsed = collapsed.has(projectName)
              return (
              <div key={projectName}>
                {/* [S175 — anh: "Cho hiển thị theo cây nằm dự án hạng mục"] Header CẤP 1 nay là
                    NÚT gập/mở. Mặc-định MỞ: gập sẵn thì mở panel ra thấy toàn thư-mục đóng,
                    người dùng tưởng chưa có việc nào. */}
                <button
                  type="button"
                  onClick={() => setCollapsed(prev => {
                    const next = new Set(prev)
                    if (next.has(projectName)) next.delete(projectName); else next.add(projectName)
                    return next
                  })}
                  aria-expanded={!pCollapsed}
                  className="sticky top-0 z-20 flex w-full items-center gap-1.5 border-b border-slate-200 bg-slate-50/95 px-3 py-1 text-left backdrop-blur transition hover:bg-slate-100"
                >
                  <ChevronRight className={cn('h-3 w-3 shrink-0 text-slate-400 transition-transform', !pCollapsed && 'rotate-90')} aria-hidden />
                  <FolderTree className="h-3 w-3 shrink-0 text-slate-400" aria-hidden />
                  <span className="min-w-0 flex-1 truncate text-[10px] font-semibold uppercase tracking-wide text-slate-600">
                    {projectName}
                  </span>
                  <span className="shrink-0 text-[10px] tabular-nums text-slate-500">{countProject(byWorkItem)}</span>
                </button>
                {!pCollapsed && [...byWorkItem.entries()].map(([workItemName, items]) => {
                  const wKey = `${projectName}${workItemName}`
                  const wCollapsed = collapsed.has(wKey)
                  return (
                  <div key={workItemName}>
                    <button
                      type="button"
                      onClick={() => setCollapsed(prev => {
                        const next = new Set(prev)
                        if (next.has(wKey)) next.delete(wKey); else next.add(wKey)
                        return next
                      })}
                      aria-expanded={!wCollapsed}
                      className="sticky top-[1.375rem] z-10 flex w-full items-center gap-1.5 border-b border-slate-100 bg-white/95 py-1 pl-5 pr-3 text-left backdrop-blur transition hover:bg-slate-50"
                    >
                      <ChevronRight className={cn('h-3 w-3 shrink-0 text-slate-300 transition-transform', !wCollapsed && 'rotate-90')} aria-hidden />
                      <span className="min-w-0 flex-1 truncate text-[10px] font-medium text-slate-500">
                        {workItemName}
                      </span>
                      <span className="shrink-0 text-[10px] tabular-nums text-slate-400">{items.length}</span>
                    </button>
                    {!wCollapsed && (
                    <ul className="divide-y divide-slate-100 border-l-2 border-slate-100 pl-1">
                      {items.map((pe) => (
                        <li key={pe.id}>
                          <button
                            type="button"
                            // Bấm = nạp THẲNG phiếu này vào form tạo bên panel 2. Không điều hướng,
                            // không mở lại màn khác — đúng "click vào cột này để tạo luôn KHKK".
                            onClick={() => onPickPe(pe.id)}
                            title="Lập kế hoạch ký kết cho phiếu này"
                            className={cn(
                              'block w-full px-3 py-2 text-left transition hover:bg-amber-50/60',
                              selectedPeId === pe.id && 'bg-amber-50 ring-1 ring-inset ring-amber-300',
                            )}
                          >
                            <div className="flex items-center gap-1.5">
                              <span className="font-mono text-[10px] text-brand-700">{pe.maPhieu ?? '—'}</span>
                              <span className="text-[10px] text-slate-400">·</span>
                              <span className="truncate text-[10px] text-slate-500">{formatDate(pe.createdAt)}</span>
                            </div>
                            <div className="truncate text-[12px] font-medium text-slate-800">{pe.tenGoiThau}</div>
                            {pe.winnerSupplierNames.length > 0 && (
                              <div className="mt-0.5 truncate text-[10px] text-emerald-700">
                                ✓ {pe.winnerSupplierNames.join(' · ')}
                              </div>
                            )}
                          </button>
                        </li>
                      ))}
                    </ul>
                    )}
                  </div>
                  )
                })}
              </div>
              )
            })}
          </Tier>

          <div className="shrink-0 border-t-2 border-slate-200" />

          <Tier
            // Tên nói ĐÚNG tập đang hiện, không nói chung chung — người đọc phải biết ngay
            // vì sao phiếu họ vừa gửi duyệt "biến mất" khỏi đây.
            title="KHKK · nháp / trả lại / từ chối"
            // Đếm từ mảng ĐÃ LỌC, KHÔNG lấy `total` của server (xem khối lọc phía trên).
            count={plans.data ? planItems.length : null}
            accent="bg-violet-50 text-violet-600"
            icon={<FileSignature className="h-3 w-3" />}
          >
            {plans.isLoading && <div className="p-3 text-[11px] text-slate-500">Đang tải…</div>}
            {plans.isError && (
              <div className="space-y-1.5 p-3">
                <p className="text-[11px] text-slate-500">Không tải được danh sách.</p>
                <button
                  type="button"
                  onClick={() => plans.refetch()}
                  className="rounded-md border border-slate-300 px-2 py-1 text-[10px] font-semibold text-slate-600 hover:border-brand-300 hover:bg-brand-50"
                >
                  Thử lại
                </button>
              </div>
            )}
            {!plans.isLoading && !plans.isError && planItems.length === 0 && (
              <p className="p-3 text-[11px] italic text-slate-500">
                Không còn kế hoạch nào đang xử lý. Phiếu đã duyệt không hiện ở đây — xem tại
                menu Kế hoạch ký kết HĐ.
              </p>
            )}
            <ul className="divide-y divide-slate-100">
              {planItems.map((k) => (
                <li key={k.id} className="relative">
                  <button
                    type="button"
                    // 🔴 CHỈ XEM tại chỗ — KHÔNG `navigate('/khkk/:id')`. Anh chốt: "cho hiện
                    // phiếu đó luôn, chỉ view là đc, ko re-direct về phiếu gốc".
                    onClick={() => onPickPlan(k.id)}
                    title="Xem kế hoạch ký kết này (chỉ xem)"
                    className={cn(
                      'block w-full px-3 py-2 text-left transition hover:bg-violet-50/60',
                      selectedPlanId === k.id && 'bg-violet-50 ring-1 ring-inset ring-violet-300',
                      // [S175 · O7] Chừa chỗ cho bút chì để tên gói thầu dài không chui xuống dưới nó.
                      onEditPlan && 'pr-9',
                    )}
                  >
                    <div className="flex items-center gap-1.5">
                      <span className="font-mono text-[10px] text-violet-700">{k.maKeHoach ?? '(chưa cấp mã)'}</span>
                      {k.approvalGroup != null && (
                        <span className="rounded border border-violet-200 bg-violet-50 px-1 text-[9px] font-semibold text-violet-700">
                          N{k.approvalGroup}
                        </span>
                      )}
                      <span
                        className={cn(
                          'ml-auto shrink-0 rounded border px-1.5 py-0.5 text-[9px] font-medium',
                          KHKK_PHASE_BADGE[k.phase],
                        )}
                      >
                        {KHKK_PHASE_LABELS[k.phase]}
                      </span>
                    </div>
                    <div className="truncate text-[12px] font-medium text-slate-800">
                      {k.peTenGoiThau ?? '(chưa có tên gói thầu)'}
                    </div>
                  </button>
                  {/* [S175 · O7] Bút chì — khuôn `PeListPanel.tsx:208-232`. LUÔN hiện (không
                      chỉ khi hover) rồi MỜ ĐI khi không sửa được: nút chỉ-hiện-khi-hover thì
                      trên máy cảm-ứng không ai tìm ra, còn ẩn hẳn khi phiếu khoá thì người
                      dùng tưởng chức năng không tồn tại thay vì hiểu là phiếu đã đi duyệt.
                      🔴 Nút NGOÀI thẻ <button> của card — lồng button trong button là HTML
                      không hợp lệ, React sẽ cảnh báo và bấm sẽ kích cả hai. */}
                  {onEditPlan && (() => {
                    const editable = k.phase === KhkkPhase.DangSoanThao || k.phase === KhkkPhase.TraLai
                    return (
                      <button
                        type="button"
                        onClick={() => editable && onEditPlan(k.id)}
                        disabled={!editable}
                        title={editable
                          ? 'Sửa kế hoạch này'
                          : 'Kế hoạch đã gửi duyệt / đã duyệt — không sửa được'}
                        aria-label={editable ? `Sửa kế hoạch ${k.maKeHoach ?? ''}` : undefined}
                        className={cn(
                          'absolute right-2 top-2 rounded p-1.5 transition',
                          editable
                            ? 'cursor-pointer text-violet-600 hover:bg-violet-100 hover:shadow-sm'
                            : 'cursor-not-allowed text-slate-300',
                        )}
                      >
                        <Pencil className="h-3.5 w-3.5" />
                      </button>
                    )
                  })()}
                </li>
              ))}
            </ul>
            {/* 🔴 KHÔNG in "x/total" nữa: `total` gồm cả phiếu đã duyệt vừa bị lọc ⇒ tỉ số đó
                sẽ đọc thành "đang cắt bớt" trong khi thật ra là "đang lọc bỏ". Nói đúng việc
                đang làm, và chỉ nói khi thật sự có cái bị giấu. */}
            {plans.data && plans.data.items.length > planItems.length && (
              <p className="border-t border-slate-100 px-3 py-1.5 text-[10px] italic text-slate-500">
                Đã ẩn kế hoạch ở trạng thái “Đã duyệt” — xem đủ ở menu Kế hoạch ký kết HĐ.
              </p>
            )}
          </Tier>
        </>
      )}

      {/* 🧊 [S174] KHÔNG có nút "+ Thêm mới" ở đáy — anh chốt bỏ. Lý do đứng vững: màn này
          KHÔNG có đường "tạo từ số 0". Một kế hoạch LUÔN sinh ra từ một phiếu Duyệt NCC, mà
          các phiếu đủ điều kiện đã nằm nguyên ở tầng trên ⇒ nút chỉ xoá form chứ không tạo
          thêm được gì. Đây là chỗ khác CÓ CHỦ ĐÍCH với `PeListPanel` (bên đó tạo phiếu PE
          mới là việc có thật). Ai định thêm lại: phải trả lời được "thêm mới CÁI GÌ". */}
    </aside>
  )
}
