// [S171 KHKK 3-panel] PANEL 1 của màn "Kế hoạch ký kết HĐ" (GĐ2) — mirror bố-cục
// Duyệt NCC (GĐ1) / Hợp đồng (GĐ3): khối filter trên cùng + thân danh sách cuộn riêng.
// File MIRROR SHA256 identical với fe-admin counterpart.
//
// 🔴 KHỐI Ý-KIẾN / SƠ-ĐỒ DUYỆT là bản thứ 3 của cùng một hình (PE `PeDetailTabs.tsx:705-774`
// · HĐ `components/contracts/WorkflowHistoryPanel.tsx`). Gộp được khi và chỉ khi BE 3 module
// thống-nhất shape `status` precompute. Trước đó, gộp = đẻ `if (module === …)`.
//
// ══ OWNER CHỐT 2026-08-04 (QĐ-1): panel 1 = CÂY TOÀN TRÌNH `PipelineTreePanel currentStage={2}`.
// Chọn HÌNH ≠ chấp-nhận VỠ ⇒ 4 mitigation dưới đây là BẮT BUỘC, không phải tuỳ chọn:
//   M-1  `canPe` (`usePipelineStages.ts:83`) khoá cây bằng 3 key của Duyệt NCC. Người CHỈ có
//        quyền KHKK (vd người duyệt nhóm N5) sẽ thấy cây trống trong khi API
//        `GET /contract-signing-plans` (policy `KeHoachKyKet.Read`) VẪN cho họ đọc — đúng mặt
//        còn lại của gotcha #82. ⇒ `!canPe` FALLBACK sang DANH-SÁCH PHẲNG cùng ô này.
//        Cây là lớp LÀM GIÀU, KHÔNG phải điều-kiện để thấy phiếu.
//   M-2  Leaf/card KHKK trên cây KHÔNG `navigate('/khkk/:id')` nữa mà gọi `onSelect` của host
//        (`usePipelineStages.ts` `onOpenKhkk`) — không thì bấm 1 cái là văng khỏi màn 3-panel.
//   B1   Truyền `stage2Content` PHẢI kèm `stage2Count` (`PipelineStageFolders.tsx:95-97`).
//        Trade-off khai thẳng: trong cây của trang này, folder GĐ2 hiện CARD PHIẾU thay cho
//        8 ngăn N1-N8; lối vào nhóm giữ ở `Select` nhóm ngay bên trên.
//   R-11 Cây chỉ tải 200 phiếu PE mới nhất (`PipelineTreePanel.tsx:22`) và KHÔNG có "trang 2".
//        ⇒ ô tìm + `Select` trạng-thái đi SERVER-SIDE thẳng `GET /contract-signing-plans`
//        (bật chế-độ danh-sách phẳng), luôn có đường tới phiếu cũ; và dòng cảnh-báo
//        "Cây tải 200 phiếu mới nhất…" của cây KHÔNG bị nuốt.
import { useQuery } from '@tanstack/react-query'
import { FileCheck, Inbox, Search } from 'lucide-react'
import { Input } from '@/components/ui/Input'
import { Select } from '@/components/ui/Select'
import { PipelineTreePanel } from '@/components/pipeline/PipelineTreePanel'
import { usePipelineStages } from '@/hooks/usePipelineStages'
import { api } from '@/lib/api'
import { cn } from '@/lib/cn'
import {
  KHKK_PHASE_BADGE,
  KHKK_PHASE_LABELS,
  KhkkPhase,
  type KhkkListItemDto,
  type KhkkPhaseValue,
  type PagedResult,
} from '@/types/khkk'

const PAGE_SIZE = 20

function formatVnd(n: number | null | undefined): string {
  if (n === null || n === undefined) return '—'
  return n.toLocaleString('vi-VN') + ' đ'
}

function formatDateTime(iso: string): string {
  return new Date(iso).toLocaleString('vi-VN', {
    day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit',
  })
}

/**
 * [F-2] Chip "⚑ Kết thúc · {Bước}" — khuôn `components/pe/PeFinalizeChip.tsx` (S89/S97),
 * dựng lại ở đây thay vì import vì nguồn cờ khác module (KHKK đọc
 * `endedByLevelFinalize` + `finalizeStepName`, PE đọc `endsBeforeCeo`).
 */
export function KhkkFinalizeChip({
  endedByLevelFinalize,
  finalizeStepName,
  phase,
}: {
  endedByLevelFinalize: boolean
  finalizeStepName: string | null
  phase?: number
}) {
  if (!endedByLevelFinalize) {
    if (phase === KhkkPhase.DaDuyet) {
      return (
        <span
          className="inline-flex shrink-0 items-center rounded bg-emerald-100 px-1.5 py-0.5 text-[10px] font-semibold text-emerald-800"
          title="Kế hoạch đi HẾT quy trình (không kết thúc sớm ở cấp dưới)"
        >
          ✅ Duyệt hết quy trình
        </span>
      )
    }
    return null
  }
  return (
    <span
      className="inline-flex shrink-0 items-center rounded bg-violet-100 px-1.5 py-0.5 text-[10px] font-semibold text-violet-800"
      title={`Quy trình kết thúc tại ${finalizeStepName ?? 'cấp này'} — không trình tiếp cấp sau`}
    >
      {finalizeStepName ? `⚑ Kết thúc · ${finalizeStepName}` : '⚑ Kết thúc sớm'}
    </span>
  )
}

/**
 * Card 3 hàng — thay cho bảng 8 cột + 6 KpiCard của bản cũ (bảng "cột nào chết, chuyển
 * đi đâu" nằm ở `sub-implementer-frontend-s171.md` §4).
 * [M-8] Σ tiền in MỘT số, suy từ `phase === DaDuyet` (đúng như BE suy `budgetFrozen`) —
 * KHÔNG thêm field vào `KhkkListItemDto`, vì `budgetFrozen` chỉ có ở DetailDto.
 */
export function KhkkPlanCard({
  plan,
  selected,
  onSelect,
  finalize,
}: {
  plan: KhkkListItemDto
  selected: boolean
  onSelect: (id: string) => void
  /** Chỉ có khi phiếu ĐANG được chọn (detail đã tải) — list payload không mang cờ này. */
  finalize?: { endedByLevelFinalize: boolean; finalizeStepName: string | null }
}) {
  const frozen = plan.phase === KhkkPhase.DaDuyet
  const amount = frozen ? (plan.totalApprovedAmount ?? plan.totalProposedAmount) : plan.totalProposedAmount
  return (
    <button
      type="button"
      onClick={() => onSelect(plan.id)}
      className={cn(
        'block w-full px-3 py-2.5 text-left transition hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-500/60',
        selected && 'bg-brand-50 ring-1 ring-inset ring-brand-200',
      )}
    >
      <div className="flex items-start justify-between gap-2">
        <div className="min-w-0 flex-1">
          <div className="flex flex-wrap items-center gap-1">
            {finalize && (
              <KhkkFinalizeChip
                endedByLevelFinalize={finalize.endedByLevelFinalize}
                finalizeStepName={finalize.finalizeStepName}
                phase={plan.phase}
              />
            )}
            <span className="min-w-0 flex-1 truncate text-[13px] font-medium text-slate-900">
              {plan.peTenGoiThau ?? '(chưa có tên gói thầu)'}
            </span>
          </div>
        </div>
        <span
          className={cn(
            'shrink-0 rounded-md border px-1.5 py-0.5 text-[10px] font-medium',
            KHKK_PHASE_BADGE[plan.phase],
          )}
        >
          {KHKK_PHASE_LABELS[plan.phase]}
        </span>
      </div>
      <div className="mt-0.5 flex items-center gap-1.5 text-[11px] text-slate-500">
        <span className="font-mono">{plan.maKeHoach ?? '(chưa cấp mã)'}</span>
        {plan.approvalGroup != null && (
          <span className="rounded border border-violet-200 bg-violet-50 px-1 text-[9px] font-semibold text-violet-700">
            N{plan.approvalGroup}
          </span>
        )}
        <span>·</span>
        <span className="truncate">{formatDateTime(plan.createdAt)}</span>
      </div>
      <div className="mt-0.5 flex items-center justify-between gap-2 text-[11px] text-slate-500">
        <span className="min-w-0 truncate">👤 {plan.drafterFullName ?? '—'}</span>
        <span className={cn('shrink-0 tabular-nums', frozen ? 'text-emerald-700' : 'text-brand-800')}>
          {formatVnd(amount)}
        </span>
      </div>
    </button>
  )
}

export function KhkkListPanel({
  selectedId,
  onSelect,
  search,
  onSearchChange,
  phase,
  onPhaseChange,
  group,
  deletedView,
  page,
  onPageChange,
  selectedFinalize,
}: {
  selectedId: string | null
  onSelect: (id: string) => void
  search: string
  onSearchChange: (v: string) => void
  phase: KhkkPhaseValue | null
  onPhaseChange: (v: KhkkPhaseValue | null) => void
  // [S174 owner] CHỈ còn đọc — `Select` nhóm đã gỡ khỏi UI. Nhóm nay đến DUY NHẤT từ URL
  // (`?group=n`, tức 8 leaf nhóm ở menu trái). Máy lọc giữ NGUYÊN (query + `stage2Content`);
  // chỉ bỏ đường đổi nhóm bằng tay — vào từ leaf nhóm rồi thì ô đó là thừa.
  group: number | null
  deletedView: boolean
  page: number
  onPageChange: (p: number) => void
  selectedFinalize?: { endedByLevelFinalize: boolean; finalizeStepName: string | null }
}) {
  const { canPe } = usePipelineStages()
  const term = search.trim()

  // [R-11] Lọc đi SERVER-SIDE, KHÔNG lọc trên cây: có từ khoá / có trạng-thái / xem thùng
  // rác ⇒ chuyển sang danh-sách phẳng có phân-trang thật (`total` từ server) nên phiếu nằm
  // ngoài 200 phiếu mới nhất VẪN tới được.
  // [M-1] `!canPe` ⇒ cũng dùng đúng danh-sách phẳng này — cây khoá không được phép làm
  // người dùng mất trắng phiếu của chính họ.
  const flatMode = !canPe || deletedView || term.length > 0 || phase !== null

  const list = useQuery({
    queryKey: ['khkk-list', { group, phase, search: term, page, deletedView }],
    queryFn: async () => {
      const url = deletedView ? '/contract-signing-plans/deleted' : '/contract-signing-plans'
      return (await api.get<PagedResult<KhkkListItemDto>>(url, {
        params: {
          phase: deletedView ? undefined : (phase ?? undefined),
          group: group ?? undefined,
          search: term || undefined,
          page,
          pageSize: PAGE_SIZE,
        },
      })).data
    },
    enabled: flatMode,
  })

  const items = list.data?.items ?? []
  const total = list.data?.total ?? 0
  const totalPages = list.data?.totalPages ?? 1

  // [B1] Thân folder GĐ2 của cây = CARD phiếu của chính trang này; `Select` nhóm lọc luôn
  // trên cây (client-side, an toàn: đây chỉ là chia lại đúng tập phiếu cây đã có).
  const stage2Content = (plans: KhkkListItemDto[]) => {
    const shown = group === null ? plans : plans.filter(k => k.approvalGroup === group)
    if (shown.length === 0) {
      return (
        <p className="px-3 py-2 text-[11px] italic text-slate-400">
          {group === null ? 'Gói này chưa có kế hoạch ký kết.' : `Gói này chưa có phiếu nhóm N${group}.`}
        </p>
      )
    }
    return (
      <ul className="divide-y divide-slate-100">
        {shown.map(k => (
          <li key={k.id}>
            <KhkkPlanCard
              plan={k}
              selected={selectedId === k.id}
              onSelect={onSelect}
              finalize={selectedId === k.id ? selectedFinalize : undefined}
            />
          </li>
        ))}
      </ul>
    )
  }
  const stage2Count = (plans: KhkkListItemDto[]) =>
    (group === null ? plans : plans.filter(k => k.approvalGroup === group)).length

  return (
    <aside className="flex flex-col overflow-hidden border-r border-slate-200 bg-white">
      {/* Khối filter — CHỖ ĐẶT giống PE/GĐ3 (trên cùng panel 1). `Select` nhóm N1-N8 là
          chỗ KHKK khác nguồn dữ-liệu chứ không khác bố-trí (P1-4). */}
      <div className="space-y-2 border-b border-slate-200 p-3">
        <div className="relative">
          <Search className="pointer-events-none absolute left-2.5 top-2.5 h-4 w-4 text-slate-400" />
          <Input
            value={search}
            onChange={e => onSearchChange(e.target.value)}
            placeholder="Tìm mã kế hoạch / mã phiếu / gói thầu…"
            className="pl-8"
          />
        </div>
        <Select
          value={phase ?? ''}
          aria-label="Lọc trạng thái"
          onChange={e => onPhaseChange(e.target.value === '' ? null : (Number(e.target.value) as KhkkPhaseValue))}
        >
          <option value="">Tất cả trạng thái</option>
          {(Object.keys(KHKK_PHASE_LABELS) as unknown as number[]).map(p => (
            <option key={p} value={p}>{KHKK_PHASE_LABELS[Number(p) as KhkkPhaseValue]}</option>
          ))}
        </Select>
        {flatMode && !deletedView && canPe && (
          <p className="text-[10px] italic text-slate-400">
            Đang lọc — danh sách lấy thẳng từ máy chủ (không giới hạn 200 phiếu của cây).
          </p>
        )}
        {!canPe && (
          <p className="text-[10px] italic text-slate-400">
            Bạn chưa có quyền xem phiếu Duyệt NCC nên cây toàn trình không dựng được — danh
            sách kế hoạch ký kết vẫn liệt kê đầy đủ ở dưới.
          </p>
        )}
      </div>

      <div className="flex-1 overflow-y-auto">
        {!flatMode && (
          <PipelineTreePanel
            currentStage={2}
            className="border-0 shadow-none"
            hideHeader
            bodyMaxHClass="max-h-[28rem] lg:max-h-none"
            stage2Options={{ stage2Content, stage2Count, onOpenKhkk: onSelect }}
          />
        )}

        {flatMode && list.isLoading && (
          <div className="space-y-2 p-3">
            {Array.from({ length: 8 }).map((_, i) => (
              <div key={i} className="h-16 animate-pulse rounded-md bg-slate-100 motion-reduce:animate-none" />
            ))}
          </div>
        )}
        {flatMode && list.isError && (
          <div className="space-y-2 px-3 py-4">
            <p className="text-[12px] text-slate-500">Không tải được danh sách kế hoạch ký kết.</p>
            <button
              type="button"
              onClick={() => list.refetch()}
              className="rounded-md border border-slate-300 px-2 py-1 text-[11px] font-semibold text-slate-600 transition hover:border-brand-300 hover:bg-brand-50 hover:text-brand-700"
            >
              Thử lại
            </button>
          </div>
        )}
        {flatMode && !list.isLoading && !list.isError && items.length === 0 && (
          <div className="p-6 text-center text-sm text-slate-500">
            <span
              className="icon-chip mx-auto mb-2 flex"
              style={{ ['--chip-bg' as string]: '#f1f5f9', ['--chip-fg' as string]: '#94a3b8' }}
              aria-hidden
            >
              <Inbox className="h-4 w-4" />
            </span>
            {deletedView ? 'Không có phiếu đã xóa.' : 'Chưa có kế hoạch ký kết nào khớp bộ lọc.'}
          </div>
        )}
        {flatMode && items.length > 0 && (
          <ul className="divide-y divide-slate-100">
            {items.map(k => (
              <li key={k.id} className={cn(deletedView && 'opacity-70')}>
                <KhkkPlanCard
                  plan={k}
                  selected={selectedId === k.id}
                  onSelect={deletedView ? () => { /* phiếu đã xóa: chỉ liệt kê, không mở */ } : onSelect}
                  finalize={selectedId === k.id ? selectedFinalize : undefined}
                />
              </li>
            ))}
          </ul>
        )}
      </div>

      {flatMode && totalPages > 1 && (
        <div className="flex shrink-0 items-center justify-between border-t border-slate-200 px-3 py-2 text-[11px]">
          <span className="text-slate-500">
            <FileCheck className="mr-1 inline h-3 w-3" />
            {total} kế hoạch — Trang {page} / {totalPages}
          </span>
          <div className="flex gap-1">
            <button
              type="button"
              onClick={() => onPageChange(Math.max(1, page - 1))}
              disabled={page <= 1}
              className="rounded border border-slate-300 px-2 py-0.5 text-slate-600 hover:bg-slate-50 disabled:opacity-40"
            >
              ←
            </button>
            <button
              type="button"
              onClick={() => onPageChange(page + 1)}
              disabled={page >= totalPages}
              className="rounded border border-slate-300 px-2 py-0.5 text-slate-600 hover:bg-slate-50 disabled:opacity-40"
            >
              →
            </button>
          </div>
        </div>
      )}
    </aside>
  )
}
