// [S171 KHKK 3-panel] PANEL 3 (phải) — quy trình duyệt + lịch sử của "Kế hoạch ký kết HĐ".
// DỜI từ thư mục trang KHKK (file cùng tên) sang `components/khkk/` (cùng lớp với
// `components/pe/PeWorkflowPanel.tsx` của Duyệt NCC — panel dùng ở NHIỀU host thì không
// thuộc về một trang). File MIRROR SHA256 identical với fe-admin counterpart.
//
// 🔴 Khối này 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 chỉ đẻ `if (module === …)`.
//
// 7 khối theo bố-cục đích (mirror Ảnh 2 của Duyệt NCC):
//   1 "Quy trình duyệt" + meta {code} v{NN} · {name}
//   2 Banner kết-thúc-sớm ("không qua CEO")
//   3 Sơ đồ Bước → Cấp → NV (chip phòng · ⚑ Duyệt thay CEO · mờ nhánh không chạy · liệt-kê NV)
//   4 Nút Duyệt / Trả lại / Từ chối + Dialog + nút Xóa phiếu
//   5 📎 File đính kèm khi duyệt (upload THẬT — owner chốt QĐ-3)
//   6 Lịch sử duyệt (N)
//   7 Lịch sử thay đổi
//
// 🔴 KHÁC PE Ở CHỖ ĐẮT NHẤT: PE nhận `approvalFlow` đã có `status` Done/Current/Pending do
// BE precompute. KHKK BE trả CÂY THÔ — panel này TỰ SUY từ cặp con-trỏ:
//   • `currentWorkflowStepIndex` = INDEX 0-based vào mảng `workflowSteps` (đã sort Order)
//   • `currentApprovalLevelOrder` = GIÁ TRỊ `level.order`
// Luật so khớp OR-of-N canonical (BE `ContractSigningPlanFeatures.cs:259-260`):
// `steps[idx].Levels.Any(l => l.Order == cur && l.ApproverUserId == uid)` — mirror bằng
// `.some()` vì N Cấp CÙNG Order = OR-of-N, chỉ 1 người ký là tiến.
//
// [R-8] KHKK KHÔNG có phiếu V1-legacy ⇒ KHÔNG port nhánh fallback V1 của PE (code chết).
// [ĐƠN-GIẢN-HOÁ CÓ CHỦ ĐÍCH] KHÔNG price-picker (KHKK chốt giá per-Line ở choke-point BE
// `ApplyApprovedValuesOnFinalize`), KHÔNG badge "✎ NS PRO/CCM" (khái niệm riêng PE, Mig 50),
// và đợt này giữ 1 return-mode (về Bước 1 · Cấp 1) — 4 mode của PE là món riêng, còn treo.
// 🔴 [F-1 S166 · RATIFIED @S167] Ô-tích "Cấp này KẾT THÚC" = OPT-IN theo khuôn PE-LIVE S97
// (`PeWorkflowPanel.tsx:61` useState(false)). Default KHÔNG tick ⇒ trình tiếp cấp sau.
import { useMemo, useRef, useState } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner'
import { Download, Eye, History, Paperclip } from 'lucide-react'
import { AttachmentPreviewDialog, isPreviewable } from '@/components/pe/AttachmentPreviewDialog'
import { Button } from '@/components/ui/Button'
import { Dialog } from '@/components/ui/Dialog'
import { Label } from '@/components/ui/Label'
import { Textarea } from '@/components/ui/Textarea'
import { api } from '@/lib/api'
import { getErrorMessage } from '@/lib/apiError'
import { cn } from '@/lib/cn'
import { useAuth } from '@/contexts/AuthContext'
import {
  KHKK_CHANGELOG_ACTION_LABELS, KHKK_CHANGELOG_ENTITY_TYPE_LABELS,
  KHKK_PHASE_LABELS, KhkkApprovalDecision, KhkkAttachmentPurpose, KhkkPhase, KhkkTransitionAction,
  type KhkkApprovalDto, type KhkkAttachmentDto, type KhkkChangelogDto, type KhkkDetailDto,
  type KhkkPhaseValue, type KhkkTransitionActionValue, type KhkkTransitionInput,
  type KhkkTransitionResult, type KhkkWorkflowLevelDto,
} from '@/types/khkk'

type NodeStatus = 'Done' | 'Current' | 'Pending'

function formatDateTime(iso: string): string {
  return new Date(iso).toLocaleString('vi-VN')
}

function formatSize(b: number): string {
  return b > 1024 * 1024 ? `${(b / 1024 / 1024).toFixed(1)} MB` : `${Math.round(b / 1024)} KB`
}

// Pattern 14 (Tailwind JIT): chuỗi class ĐẦY ĐỦ, KHÔNG nội suy mảnh (`bg-${x}-50` bị purge).
const STATUS_DOT: Record<NodeStatus, string> = {
  Done: 'bg-emerald-500 text-white',
  Current: 'bg-brand-600 text-white',
  Pending: 'bg-slate-200 text-slate-500',
}
const STATUS_BOX: Record<NodeStatus, string> = {
  Done: 'border-emerald-200 bg-emerald-50/40',
  Current: 'border-brand-300 bg-brand-50/50',
  Pending: 'border-slate-200 bg-white',
}
const STATUS_ICON: Record<NodeStatus, string> = {
  Done: '✓',
  Current: '●',
  Pending: '○',
}

const DECISION_LABEL: Record<number, string> = {
  0: 'Gửi duyệt',
  1: 'Duyệt',
  2: 'Trả lại / Từ chối',
  3: 'Tự động duyệt',
}

export function KhkkWorkflowPanel({
  plan,
  onChanged,
  onDeleted,
}: {
  plan: KhkkDetailDto
  /** gọi sau khi transition thành công — caller refetch detail/list theo id của nó. */
  onChanged?: () => void
  /** gọi sau khi XÓA phiếu — host 3-panel bỏ chọn, trang chi tiết quay về danh sách. */
  onDeleted?: () => void
}) {
  const qc = useQueryClient()
  const { user } = useAuth()
  const [action, setAction] = useState<KhkkTransitionActionValue | null>(null)
  const [comment, setComment] = useState('')
  const [applyLevelFinalize, setApplyLevelFinalize] = useState(false)
  // [QĐ-3 / BE-7] File người duyệt đính kèm ngay trong dialog duyệt.
  const [pendingFiles, setPendingFiles] = useState<File[]>([])
  const fileInputRef = useRef<HTMLInputElement>(null)
  const [confirmDelete, setConfirmDelete] = useState(false)
  // [S175 · B1] File đang xem-trước inline (khuôn PE `PeWorkflowPanel.tsx:60`).
  const [previewAtt, setPreviewAtt] = useState<KhkkAttachmentDto | null>(null)

  const phase = plan.phase as KhkkPhaseValue
  const steps = plan.workflowSteps ?? []
  const approvals = plan.approvals ?? []

  const isWaiting = phase === KhkkPhase.ChoDuyet
  const isApproved = phase === KhkkPhase.DaDuyet
  // 🧊 [S175] `isDraftLike` / `canSubmit` / `canDeletePlan` ĐÃ GỠ cùng 2 nút "Gửi duyệt" +
  // "Xóa phiếu" — panel này nay chỉ lo màn ĐANG DUYỆT (Duyệt / Trả lại).

  const curStepIdx = plan.currentWorkflowStepIndex
  const curLevelOrder = plan.currentApprovalLevelOrder

  // Chữ ký thật: tra theo `approvalWorkflowLevelId` của các hàng ĐÃ DUYỆT trong lịch sử duyệt
  // (BE đã lọc `Pending`). Dùng nguồn này thay vì ý-kiến vì ý-kiến sống ở panel 2 (D-3).
  const signedByLevelId = useMemo(() => {
    const m = new Map<string, KhkkApprovalDto>()
    for (const a of approvals) {
      if (a.approvalWorkflowLevelId && a.decision !== KhkkApprovalDecision.Reject) {
        if (!m.has(a.approvalWorkflowLevelId)) m.set(a.approvalWorkflowLevelId, a)
      }
    }
    return m
  }, [approvals])

  const stepStatus = (idx: number): NodeStatus => {
    if (isApproved) return 'Done'
    if (!isWaiting || curStepIdx == null) return 'Pending'
    if (idx < curStepIdx) return 'Done'
    if (idx === curStepIdx) return 'Current'
    return 'Pending'
  }

  const levelStatus = (idx: number, order: number): NodeStatus => {
    const s = stepStatus(idx)
    if (s !== 'Current' || curLevelOrder == null) return s
    if (order < curLevelOrder) return 'Done'
    if (order === curLevelOrder) return 'Current'
    return 'Pending'
  }

  // 🔴 OR-of-N: mọi Cấp CÙNG `order` trong Bước hiện tại đều là người được duyệt lượt này.
  const currentLevels: KhkkWorkflowLevelDto[] =
    isWaiting && curStepIdx != null && curStepIdx >= 0 && curStepIdx < steps.length && curLevelOrder != null
      ? steps[curStepIdx].levels.filter(l => l.order === curLevelOrder)
      : []
  const currentStep = isWaiting && curStepIdx != null && curStepIdx >= 0 && curStepIdx < steps.length
    ? steps[curStepIdx]
    : null

  const isAdmin = user?.roles?.includes('Admin') ?? false
  const actorIsCurrentApprover = !!user?.id && currentLevels.some(l => l.approverUserId === user.id)
  const actorInLevel = isAdmin || actorIsCurrentApprover
  const blockedByLevel = isWaiting && !actorInLevel

  // Ô-tích hiện theo cờ của ĐÚNG level BE sẽ chấm — mirror `ResolveActingLevel`
  // (`ContractSigningPlanWorkflowService.cs:455-469`): own-level TRƯỚC, Admin không có own
  // ⇒ level ĐẦU nhóm cùng-order. Soi cờ của level KHÁC là hiện ô-tích nói dối.
  const actingLevel = currentLevels.find(l => l.approverUserId === user?.id)
    ?? (isAdmin ? currentLevels[0] : undefined)
  const approverFinalizeEligible = actingLevel?.allowApproverFinalize === true

  // Nút "Gửi duyệt": guard THẬT ở BE (`CreatedBy ∨ DeptManager ∨ Admin`). FE KHÔNG suy được
  // vế DeptManager ⇒ CỐ Ý không ẩn nút theo vai (ẩn = giấu nút với đúng người có quyền, im
  // lặng — bug-class gotcha #44). Chỉ chặn ca chắc-chắn-hỏng: chưa pin quy trình.

  const approvalFiles: KhkkAttachmentDto[] =
    (plan.attachments ?? []).filter(a => a.purpose === KhkkAttachmentPurpose.ApprovalAttachment)

  // [BE-5] Lịch sử thay đổi.
  // 🔴 [S175 · B1] Khối này TRƯỚC ĐÂY gập lại và query `enabled: showChangelog` (lazy —
  // tiết-kiệm 1 request/phiếu). Đợt này bỏ gập theo khuôn PE ⇒ **BẮT BUỘC bỏ luôn cờ
  // `enabled`**: giữ lại thì khối luôn hiện nhưng query KHÔNG BAO GIỜ chạy, và nó hỏng
  // dưới dạng "Chưa có thay đổi nào được ghi." — tức trông y hệt phiếu chưa từng sửa,
  // KHÔNG phải trông như lỗi. Đổi lấy: +1 request mỗi lần mở phiếu, đúng giá PE đang trả.
  const changelogs = useQuery({
    queryKey: ['khkk-changelogs', plan.id],
    queryFn: async () =>
      (await api.get<KhkkChangelogDto[]>(`/contract-signing-plans/${plan.id}/changelogs`, {
        params: { take: 200 },
      })).data,
  })

  const transition = useMutation({
    mutationFn: async (a: KhkkTransitionActionValue) => {
      const body: KhkkTransitionInput = {
        action: a,
        comment: comment.trim() || null,
        ...(a === KhkkTransitionAction.Approve
          ? { applyLevelFinalize: approverFinalizeEligible ? applyLevelFinalize : true }
          : {}),
      }
      const res = await api.post<KhkkTransitionResult>(`/contract-signing-plans/${plan.id}/transitions`, body)
      // [QĐ-3] File đính kèm khi duyệt — upload SAU khi transition thành công (transition
      // hỏng thì không đẻ file mồ côi). BE tự ép `purpose=ApprovalAttachment`, FE KHÔNG gửi
      // số enum lên (hợp-đồng lane BE §5).
      for (const f of pendingFiles) {
        const fd = new FormData()
        fd.append('file', f)
        await api.post(`/contract-signing-plans/${plan.id}/approval-attachments`, fd, {
          headers: { 'Content-Type': 'multipart/form-data' },
        })
      }
      return res.data
    },
    onSuccess: (res) => {
      const label = KHKK_PHASE_LABELS[res.phase as KhkkPhaseValue] ?? `Phase ${res.phase}`
      toast.success(`Đã cập nhật — trạng thái: ${label}.`)
      setAction(null)
      setComment('')
      setPendingFiles([])
      qc.invalidateQueries({ queryKey: ['khkk-detail', plan.id] })
      qc.invalidateQueries({ queryKey: ['khkk-list'] })
      qc.invalidateQueries({ queryKey: ['khkk-changelogs', plan.id] })
      qc.invalidateQueries({ queryKey: ['pipeline-khkk-index'] })
      onChanged?.()
    },
    onError: (e) => toast.error(getErrorMessage(e)),
  })

  const removePlan = useMutation({
    mutationFn: async () => api.delete(`/contract-signing-plans/${plan.id}`),
    onSuccess: () => {
      toast.success('Đã xóa kế hoạch')
      setConfirmDelete(false)
      qc.invalidateQueries({ queryKey: ['khkk-list'] })
      qc.invalidateQueries({ queryKey: ['pipeline-khkk-index'] })
      onDeleted?.()
    },
    onError: (e) => toast.error(getErrorMessage(e)),
  })

  const dialogTitle =
    action === KhkkTransitionAction.Submit ? '➤ Gửi duyệt kế hoạch ký kết'
      : action === KhkkTransitionAction.Approve ? '✓ Duyệt kế hoạch ký kết'
        : action === KhkkTransitionAction.Return ? '← Trả lại người soạn sửa'
          : '✗ Từ chối kế hoạch ký kết'
  const commentRequired =
    action === KhkkTransitionAction.Return || action === KhkkTransitionAction.Reject
  const confirmDisabled = transition.isPending || (commentRequired && !comment.trim())

  // [S174 · A1 — LỖI THẬT, không phải style] Trước đây tải file bằng `<a href="/api/…">` TRẦN.
  // 🔴 JWT chỉ được gắn bởi interceptor axios (`lib/api.ts:18`); thẻ `<a>` KHÔNG đi qua axios
  // ⇒ request tới `[Authorize(Policy="KeHoachKyKet.Read")]`
  // (`ContractSigningPlansController.cs:222-223`) đi TAY KHÔNG ⇒ **401, nút không tải được**.
  // Bê đúng khuôn PE (`PeWorkflowPanel.tsx:327-342`): axios lấy blob → objectURL → click ngầm.
  // Phát hiện bởi lane L1 của ensemble; lead verify bằng đối chứng `responseType:'blob'` ở PE.
  async function downloadAttachment(a: KhkkAttachmentDto) {
    try {
      const r = await api.get(
        `/contract-signing-plans/${plan.id}/attachments/${a.id}/download`,
        { responseType: 'blob' },
      )
      const url = window.URL.createObjectURL(r.data as Blob)
      const link = document.createElement('a')
      link.href = url
      link.download = a.fileName
      link.click()
      window.URL.revokeObjectURL(url)
    } catch (e) {
      toast.error(getErrorMessage(e))
    }
  }

  return (
    // [S175 · B1 · X2] Panel TRẦN khuôn PE (`PeWorkflowPanel.tsx:345`). Trước đây bọc
    // `card-accent` ⇒ thẻ-trong-thẻ, lệch đập vào mắt trước mọi mục owner liệt kê.
    // 🔴 GỠ HẲN CLASS, KHÔNG đè `border-0`/`shadow-none`: `.card-accent` (`index.css:118`)
    // nằm NGOÀI `@layer` nên thắng utility Tailwind v4 THƯỜNG ⇒ đè kiểu đó vô hiệu (gotcha #66).
    // 🔸 Chính-xác hoá @S179 (bản cũ viết "đè là vô hiệu" — OVERCLAIM): `!important` VẪN thắng
    // (`text-white!`) — đó đúng là fix mà #66 tự đề-xuất. Ở ĐÂY vẫn GỠ CLASS chứ không dùng
    // `!important`, vì lý do THIẾT-KẾ (thẻ-trong-thẻ là sai hình), không phải vì bất-khả-thi.
    <div className="space-y-4">
      {/* ── 1. Header + meta {code} v{NN} · {name} ───────────────────────────── */}
      <div>
        <h3 className="text-sm font-semibold text-slate-900">Quy trình duyệt</h3>
        {plan.workflowCode && (
          <p className="mt-0.5 font-mono text-[11px] text-slate-500">
            {plan.workflowCode}
            {plan.workflowVersion != null && ` v${String(plan.workflowVersion).padStart(2, '0')}`}
            {plan.workflowName && <> · <span className="font-sans">{plan.workflowName}</span></>}
          </p>
        )}
      </div>

      {/* ── 2. Banner kết-thúc-sớm ──────────────────────────────────────────── */}
      {/* [S175 · B1] violet → EMERALD khuôn PE (`:360-377`). PE dành violet RIÊNG cho
          "⚑ Duyệt thay CEO"; dùng violet cả ở đây là trộn 2 nghĩa vào một màu. */}
      {plan.endedByLevelFinalize && (
        <div className="rounded-md border border-emerald-300 bg-emerald-50 px-3 py-2 text-[12px] text-emerald-800">
          {isApproved ? (
            <span>
              ✅ Kế hoạch đã kết thúc tại{' '}
              <span className="font-semibold">{plan.finalizeStepName ?? 'cấp duyệt'}</span>
              {plan.finalizeLevelName ? <> · {plan.finalizeLevelName}</> : null} — không qua CEO.
            </span>
          ) : (
            <span>
              ⚑ Quy trình rút gọn: duyệt đến{' '}
              <span className="font-semibold">{plan.finalizeStepName ?? 'cấp này'}</span>
              {plan.finalizeLevelName ? <> · {plan.finalizeLevelName}</> : null} là KẾT THÚC,
              không trình CEO. Các cấp trước vẫn duyệt như thường.
            </span>
          )}
        </div>
      )}

        {/* ── 3. Sơ đồ Bước → Cấp → NV ───────────────────────────────────────── */}
        {steps.length > 0 ? (
          <ol className="space-y-2">
            {steps.map((step, idx) => {
              const st = stepStatus(idx)
              // Gom Cấp CÙNG `order` thành MỘT dòng — OR-of-N: N người, chỉ 1 chữ ký là tiến.
              const orders = [...new Set(step.levels.map(l => l.order))].sort((a, b) => a - b)
              return (
                <li key={step.id} className={cn('rounded-md border p-2', STATUS_BOX[st])}>
                  <div className="flex flex-wrap items-center gap-1.5 text-xs font-medium">
                    <span className={cn('flex h-5 w-5 shrink-0 items-center justify-center rounded-full font-bold', STATUS_DOT[st])}>
                      {STATUS_ICON[st]}
                    </span>
                    <span className="text-slate-800">Bước {step.order} — {step.name}</span>
                    {/* Chip PHÒNG của Bước (BE-4 join `Departments`).
                        [S175 · B1] viền-xám-nền-trắng → EMERALD đặc khuôn PE (`:406-410`). */}
                    {step.departmentName && (
                      <span className="rounded bg-emerald-50 px-1.5 py-0.5 text-[10px] font-medium text-emerald-700">
                        {step.departmentName}
                      </span>
                    )}
                  </div>
                  {orders.length > 0 && (
                    <ul className="ml-7 mt-1.5 space-y-1 border-l-2 border-violet-200 pl-2">
                      {orders.map(order => {
                        const group = step.levels.filter(l => l.order === order)
                        const signedRows = group.map(l => signedByLevelId.get(l.id)).filter(Boolean) as KhkkApprovalDto[]
                        const hasSigned = signedRows.length > 0
                        const finalizeHere = group.some(l => l.allowApproverFinalize)
                        // Nhánh KHÔNG CHẠY: phiếu đã duyệt bằng đường kết-thúc-sớm ⇒ các cấp
                        // sau đó không ai ký. Vẽ MỜ + trạng thái ○ thay vì bịa ✓ cho cả cây.
                        const skipped = isApproved && plan.endedByLevelFinalize && !hasSigned
                        const ls: NodeStatus = skipped ? 'Pending' : levelStatus(idx, order)
                        return (
                          <li key={`${step.id}-${order}`} className={cn('text-[11px]', skipped && 'opacity-50')}>
                            <div className="flex items-start gap-1.5">
                              <span className={cn('mt-0.5 inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full text-[9px] font-bold', STATUS_DOT[ls])}>
                                {STATUS_ICON[ls]}
                              </span>
                              <div className="min-w-0 flex-1">
                                <div className="font-medium text-slate-700">
                                  {group[0]?.name || `Cấp ${order}`}
                                  {ls === 'Current' && <span className="ml-1.5 text-[10px] font-normal text-brand-700">đang chờ</span>}
                                  {ls === 'Done' && <span className="ml-1.5 text-[10px] font-normal text-emerald-600">đã duyệt</span>}
                                  {skipped && <span className="ml-1.5 text-[10px] font-normal text-slate-500">không phải qua</span>}
                                  {finalizeHere && (
                                    <span
                                      className="ml-1.5 inline-flex items-center rounded bg-violet-100 px-1.5 py-0.5 text-[9px] font-semibold text-violet-800"
                                      title="Cấp này duyệt là KẾT THÚC quy trình — duyệt THAY CEO, không trình lên Ban Giám đốc"
                                    >
                                      ⚑ Duyệt thay CEO
                                    </span>
                                  )}
                                </div>
                                {/* [F-3] Liệt-kê NV của cấp: người ĐÃ KÝ in đậm, NV cùng cấp còn
                                    lại `/ Tên` mờ — mirror `PeWorkflowPanel.tsx:453-465`. */}
                                <div className="flex flex-wrap items-center gap-x-1 gap-y-0.5 text-slate-500">
                                  {group.length === 0
                                    ? '(chưa cấu hình)'
                                    : group.map((l, i) => {
                                      const sign = signedByLevelId.get(l.id)
                                      return (
                                        <span key={l.id} className="inline-flex items-center gap-1">
                                          {i > 0 && <span className="text-slate-300">/</span>}
                                          <span className={cn(sign ? 'font-semibold text-slate-800' : 'text-slate-400')}>
                                            {l.approverFullName ?? '(chưa cấu hình)'}
                                          </span>
                                          {sign && (
                                            <span className="text-[10px] text-emerald-600">✓ {formatDateTime(sign.approvedAt)}</span>
                                          )}
                                        </span>
                                      )
                                    })}
                                </div>
                              </div>
                            </div>
                          </li>
                        )
                      })}
                    </ul>
                  )}
                </li>
              )
            })}
          </ol>
        ) : (
          <div className="rounded border border-slate-200 bg-slate-50 px-3 py-2 text-[11px] text-slate-600">
            Quy trình duyệt chưa cấu hình Bước/Cấp — liên hệ Admin để dựng quy trình loại
            &ldquo;Kế hoạch ký kết HĐ&rdquo; trước khi trình duyệt.
          </div>
        )}

        {/* Banner "đến lượt bạn" — khuôn PeWorkflowPanel:489-508. */}
        {isWaiting && currentStep && (
          <div className={cn(
            'rounded border px-3 py-2 text-[11px]',
            actorInLevel ? 'border-emerald-200 bg-emerald-50 text-emerald-800' : 'border-amber-300 bg-amber-50 text-amber-800',
          )}>
            <div className="font-medium">
              Đang chờ Bước {currentStep.order} ({currentStep.name}) — Cấp {curLevelOrder}
            </div>
            <div className="mt-0.5">
              Người duyệt: {currentLevels.map(l => l.approverFullName ?? '(chưa rõ)').join(' / ') || '(chưa có)'}
            </div>
            {actorInLevel
              ? <div className="mt-0.5 font-medium">✓ Đến lượt bạn duyệt</div>
              : <div className="mt-0.5">⚠ Không phải lượt bạn — chỉ người trên mới thao tác cấp này</div>}
          </div>
        )}

        {phase === KhkkPhase.TraLai && (
          <div className="rounded border border-orange-300 bg-orange-50 px-3 py-2 text-[11px] text-orange-800">
            ⚠ Kế hoạch bị TRẢ LẠI — sửa nội dung rồi bấm &ldquo;Gửi duyệt&rdquo;, quy trình chạy lại từ Bước 1 · Cấp 1.
          </div>
        )}
        {phase === KhkkPhase.TuChoi && (
          <div className="rounded border border-red-300 bg-red-50 px-3 py-2 text-[11px] text-red-800">
            ✗ Kế hoạch đã bị TỪ CHỐI — không thao tác được nữa. Lập kế hoạch mới nếu cần làm lại.
          </div>
        )}

        {/* ── 4. Hành động + nút Xóa ─────────────────────────────────────────── */}
        {/* 🧊 [S175 — anh: "Bỏ hết đi" + "chỗ menu đang duyệt → Duyệt/Trả lại vậy thôi"]
            "Gửi duyệt" và "Xóa phiếu" ĐÃ RỜI khỏi đây → nay nằm ở THANH NÚT ĐÁY của
            `KhkkDetailContent`. Panel này từ nay CHỈ còn **Duyệt / Trả lại** — đúng phạm vi
            "màn đang duyệt". Máy `KhkkTransitionAction.Submit` vẫn nguyên ở BE và ở thanh đáy;
            gỡ ở đây chỉ là gỡ ĐƯỜNG BẤM TRÙNG, không phải bỏ chức năng. */}
        {isWaiting && (
          <div>
            <Label className="text-xs">Hành động:</Label>
            <div className="mt-1 flex flex-wrap gap-1.5">
              {isWaiting && (
                <>
                  <button
                    type="button"
                    disabled={blockedByLevel}
                    onClick={() => {
                      if (!blockedByLevel) {
                        setComment('')
                        setPendingFiles([])
                        // reset về default KHÔNG-tick (opt-IN, khuôn PE S97) mỗi lần mở dialog.
                        setApplyLevelFinalize(false)
                        setAction(KhkkTransitionAction.Approve)
                      }
                    }}
                    title={blockedByLevel ? 'Cấp hiện tại chỉ người được phân công mới thao tác được.' : 'Duyệt cấp hiện tại'}
                    className={cn(
                      'rounded border px-2 py-1 text-[11px] font-bold transition',
                      blockedByLevel ? 'cursor-not-allowed border-slate-200 bg-slate-50 text-slate-400' : 'border-emerald-300 text-emerald-700 hover:bg-emerald-50',
                    )}
                  >
                    ✓ Duyệt
                  </button>
                  <button
                    type="button"
                    disabled={blockedByLevel}
                    onClick={() => { if (!blockedByLevel) { setComment(''); setPendingFiles([]); setAction(KhkkTransitionAction.Return) } }}
                    title={blockedByLevel ? 'Cấp hiện tại chỉ người được phân công mới thao tác được.' : 'Trả lại người soạn sửa'}
                    className={cn(
                      'rounded border px-2 py-1 text-[11px] font-bold transition',
                      blockedByLevel ? 'cursor-not-allowed border-slate-200 bg-slate-50 text-slate-400' : 'border-amber-300 text-amber-700 hover:bg-amber-50',
                    )}
                  >
                    ← Trả lại
                  </button>
                  {/* 🧊 [S174 owner] Nút "TỪ CHỐI" ĐÃ GỠ — anh chốt "vậy thì bỏ luôn từ chối
                      ~ tương tự PE". Duyệt NCC có code từ-chối nhưng THỰC TẾ không hiện
                      (BE gỡ khỏi `nextPhases` + FE lọc, UAT S60) ⇒ KHKK còn hiện là lệch.
                      🔴 Chỉ gỡ ĐƯỜNG BẤM ở UI. Máy BE (`KhkkTransitionAction.Reject`,
                      phase `TuChoi=99`) GIỮ NGUYÊN: phiếu cũ đã từ-chối vẫn hiển thị đúng,
                      và mở lại sau này chỉ là thêm nút. Gỡ cả máy = mất dữ-liệu lịch sử. */}
                </>
              )}
            </div>
          </div>
        )}

        {/* ── 5. 📎 File đính kèm khi duyệt — TỰ ẨN khi rỗng (khuôn PE :928-967) ── */}
        {/* [S175 · B1] +`border-t pt-4` +`<h3 text-sm>` +nút `<Eye>` xem trước.
            Route BE ĐÃ CÓ `ContractSigningPlansController.cs:231` (`/view`, cùng hình với
            PE `:317`) ⇒ chỉ cần truyền `basePath` cho dialog dùng chung.
            🔴 Nhãn "Tải xuống" chuyển vào `title=` chứ KHÔNG bỏ: giữ đúng chuỗi đó trong
            bundle để phép verify prod còn control-dương soi được. */}
        {approvalFiles.length > 0 && (
          <div className="border-t border-slate-200 pt-4">
            <h3 className="text-sm font-semibold text-slate-900">📎 File đính kèm khi duyệt</h3>
            <p className="mt-0.5 text-[11px] text-slate-500">File do người duyệt tải lên trong quá trình duyệt.</p>
            <div className="mt-2 space-y-1">
              {approvalFiles.map(a => (
                <div key={a.id} className="flex items-center gap-1.5 rounded bg-slate-50 px-1.5 py-1 text-[11px]">
                  <Paperclip className="h-3 w-3 shrink-0 text-slate-400" />
                  <span className="min-w-0 flex-1 truncate text-slate-700" title={a.fileName}>{a.fileName}</span>
                  <span className="shrink-0 text-[10px] text-slate-400">{formatSize(a.fileSize)}</span>
                  {isPreviewable(a.fileName) && (
                    <button
                      type="button"
                      onClick={() => setPreviewAtt(a)}
                      className="shrink-0 rounded px-1 text-violet-600 hover:bg-violet-50"
                      title="Xem trước"
                    >
                      <Eye className="h-3 w-3" />
                    </button>
                  )}
                  <button
                    type="button"
                    onClick={() => downloadAttachment(a)}
                    className="shrink-0 rounded px-1 text-brand-600 hover:bg-brand-50"
                    title="Tải xuống"
                  >
                    <Download className="h-3 w-3" />
                  </button>
                </div>
              ))}
            </div>
            {previewAtt && (
              <AttachmentPreviewDialog
                open
                basePath="/contract-signing-plans"
                evaluationId={plan.id}
                attachmentId={previewAtt.id}
                fileName={previewAtt.fileName}
                onClose={() => setPreviewAtt(null)}
              />
            )}
          </div>
        )}

        {/* ── 6. Lịch sử duyệt (N) ───────────────────────────────────────────── */}
        {/* [B4/D-11] BE ĐÃ LỌC hàng `decision = Pending` (hành-vi GỬI duyệt) ⇒ số (N) ở đây
            CÙNG NGHĨA với (N) của Duyệt NCC. FE KHÔNG dedupe lại; sự kiện "Gửi duyệt" vẫn
            còn nguyên ở "Lịch sử thay đổi" bên dưới. */}
        {/* [S175 · B1] Bỏ pill đếm → `(n)` trong ngoặc · thẻ `bg-slate-50/60 text-[11px]`
            → `bg-white p-3 text-sm` · mốc-giờ ĐẨY PHẢI bằng `justify-between`.
            🔴 Badge 2-ngả → 3-NGẢ: trước đây MỌI thứ không phải `Reject` đều tô emerald
            ⇒ "Trả lại" (amber ở PE) bị vẽ xanh như đã-duyệt — đọc ngược nghĩa.
            `toPhase` đã có sẵn (`khkk.ts:307`) nên phân được TraLai ⟂ TuChoi ⟂ Duyệt. */}
        <div className="border-t border-slate-200 pt-4">
          <h3 className="text-sm font-semibold text-slate-900">Lịch sử duyệt ({approvals.length})</h3>
          {approvals.length === 0 ? (
            <p className="mt-1.5 text-[11px] italic text-slate-400">Chưa có lượt duyệt nào.</p>
          ) : (
            <ul className="mt-2 space-y-1.5">
              {approvals.map(a => {
                const isReject = a.toPhase === KhkkPhase.TuChoi
                const isReturn = a.toPhase === KhkkPhase.TraLai
                return (
                  <li key={a.id} className="rounded border border-slate-200 bg-white p-3 text-sm">
                    <div className="flex flex-wrap items-center justify-between gap-x-2 gap-y-1 text-slate-700">
                      <div className="flex flex-wrap items-center gap-x-1.5">
                        <span className={cn(
                          'rounded px-1.5 py-0.5 text-[10px] font-semibold',
                          isReject ? 'bg-rose-100 text-rose-800'
                            : isReturn ? 'bg-amber-100 text-amber-800'
                              : 'bg-emerald-100 text-emerald-800',
                        )}>
                          {DECISION_LABEL[a.decision] ?? 'Chuyển trạng thái'}
                        </span>
                        <span className="font-medium">{a.approvedByFullName ?? '(hệ thống)'}</span>
                      </div>
                      <span className="text-[11px] text-slate-500">{formatDateTime(a.approvedAt)}</span>
                    </div>
                    <div className="mt-1 text-[12px] text-slate-600">
                      {a.stepName ? `Bước ${a.stepOrder} — ${a.stepName}` : 'Cấp duyệt'}
                      {a.levelOrder != null && ` · Cấp ${a.levelOrder}`}
                      {a.levelName ? ` (${a.levelName})` : ''}
                      <span className="text-slate-400">
                        {' '}· {KHKK_PHASE_LABELS[a.fromPhase]} → {KHKK_PHASE_LABELS[a.toPhase]}
                      </span>
                    </div>
                    {a.comment?.trim() && (
                      <div className="mt-1 whitespace-pre-wrap text-[12px] text-slate-600">{a.comment}</div>
                    )}
                  </li>
                )
              })}
            </ul>
          )}
        </div>

        {/* ── 7. Lịch sử thay đổi ────────────────────────────────────────────── */}
        {/* [S175 · B1] BỎ GẬP — PE render thẳng (`PeDetailTabs.tsx:787-794`). Nội-dung
            đã tải sẵn qua query; giấu sau 1 cú bấm chỉ thêm ma-sát, không tiết-kiệm gì. */}
        <div className="border-t border-slate-200 pt-4">
          <h3 className="flex items-center gap-1.5 text-sm font-semibold text-slate-900">
            <History className="h-3.5 w-3.5 text-slate-400" />
            Lịch sử thay đổi
          </h3>
          <div className="mt-2">
              {changelogs.isLoading && <p className="text-[11px] text-slate-400">Đang tải…</p>}
              {changelogs.isError && <p className="text-[11px] text-red-600">Không tải được lịch sử thay đổi.</p>}
              {changelogs.data && changelogs.data.length === 0 && (
                <p className="text-[11px] italic text-slate-400">Chưa có thay đổi nào được ghi.</p>
              )}
              <ul className="space-y-1">
                {(changelogs.data ?? []).map(c => (
                  <li key={c.id} className="rounded border border-slate-100 bg-white px-2.5 py-1.5 text-[11px]">
                    <div className="flex flex-wrap items-center gap-x-1.5 text-slate-600">
                      <span className="font-medium text-slate-700">
                        {KHKK_CHANGELOG_ACTION_LABELS[c.action] ?? 'Thay đổi'}
                      </span>
                      <span className="text-slate-400">·</span>
                      <span>{KHKK_CHANGELOG_ENTITY_TYPE_LABELS[c.entityType] ?? 'Khác'}</span>
                      <span className="text-slate-400">·</span>
                      <span>{c.userName ?? '(hệ thống)'}</span>
                      <span className="text-slate-400">·</span>
                      <span className="text-slate-500">{formatDateTime(c.createdAt)}</span>
                    </div>
                    {(c.summary || c.contextNote) && (
                      <div className="mt-0.5 whitespace-pre-wrap text-slate-600">
                        {c.summary}
                        {c.contextNote ? ` — ${c.contextNote}` : ''}
                      </div>
                    )}
                  </li>
                ))}
              </ul>
          </div>
        </div>

      {action !== null && (
        <Dialog
          open
          onClose={() => setAction(null)}
          title={dialogTitle}
          footer={<>
            <Button variant="outline" onClick={() => setAction(null)}>Hủy</Button>
            <Button onClick={() => transition.mutate(action)} disabled={confirmDisabled}>
              {transition.isPending ? 'Đang gửi...' : 'Xác nhận'}
            </Button>
          </>}
        >
          {action === KhkkTransitionAction.Submit && (
            <div className="mb-3 rounded border border-brand-200 bg-brand-50 px-3 py-2 text-[11px] text-brand-800">
              Kế hoạch chuyển sang &ldquo;Chờ duyệt&rdquo; và trình lên Bước 1 · Cấp 1 của quy trình
              {plan.workflowName ? ` "${plan.workflowName}"` : ''}. Sau khi trình, nội dung nháp không sửa được nữa.
            </div>
          )}
          {action === KhkkTransitionAction.Return && (
            <div className="mb-3 rounded border border-amber-200 bg-amber-50 px-3 py-2 text-[11px] text-amber-800">
              Kế hoạch về trạng thái &ldquo;Trả lại&rdquo;. Người soạn sửa xong gửi lại thì quy trình chạy
              từ Bước 1 · Cấp 1.
            </div>
          )}
          {action === KhkkTransitionAction.Reject && (
            <div className="mb-3 rounded border border-red-200 bg-red-50 px-3 py-2 text-[11px] text-red-800">
              ⚠ Kế hoạch bị khoá hoàn toàn (không sửa / không duyệt tiếp). Người soạn phải lập kế hoạch mới.
            </div>
          )}
          {action === KhkkTransitionAction.Approve && approverFinalizeEligible && (
            <div className="mb-3 rounded border border-emerald-300 bg-emerald-50 px-3 py-2 text-[12px] text-emerald-800">
              <label className="flex cursor-pointer items-start gap-2">
                <input type="checkbox" className="mt-0.5" checked={applyLevelFinalize} onChange={e => setApplyLevelFinalize(e.target.checked)} />
                <span>
                  <span className="font-medium">Cấp này KẾT THÚC quy trình</span>
                  <span className="mt-0.5 block text-[11px] text-emerald-700/80">
                    {applyLevelFinalize
                      ? 'Đã tích — duyệt xong là kế hoạch "Đã duyệt", KẾT THÚC tại cấp này, KHÔNG trình tiếp cấp sau.'
                      : 'Mặc định: duyệt xong kế hoạch TRÌNH TIẾP lên cấp sau của quy trình. TÍCH vào nếu muốn KẾT THÚC ngay tại cấp này.'}
                  </span>
                </span>
              </label>
            </div>
          )}
          {/* [S175 · B1 micro-copy] Nhãn Ô NHẬP theo PE (`PeWorkflowPanel.tsx:869` "Ghi chú
              (tùy chọn)"). 🔴 CHỈ đổi tên TRƯỜNG — chuỗi "Ý kiến cấp duyệt" ở dòng trợ-giúp
              bên dưới là TÊN THẬT của mục đích đến trong phiếu (Section 5, Mig 26), đổi nó
              là trỏ người dùng tới một mục không tồn tại. */}
          <Label>
            Ghi chú{commentRequired ? <span className="text-red-500"> *</span> : ' (tùy chọn)'}
          </Label>
          <Textarea
            value={comment}
            onChange={e => setComment(e.target.value)}
            rows={3}
            maxLength={2000}
            placeholder={commentRequired ? 'Nêu lý do trả lại / từ chối...' : 'Ghi chú của bạn (ghi vào phiếu)...'}
          />

          {/* [QĐ-3 / BE-7] File đính kèm khi duyệt — upload THẬT qua đường riêng
              `POST {id}/approval-attachments` (BE ép `purpose=ApprovalAttachment`). */}
          {action !== KhkkTransitionAction.Submit && (
            <div className="mt-3">
              <Label className="text-xs">File đính kèm (tùy chọn)</Label>
              <div className="mt-1 flex flex-wrap items-center gap-2">
                <input
                  ref={fileInputRef}
                  type="file"
                  multiple
                  className="hidden"
                  onChange={e => {
                    setPendingFiles(Array.from(e.target.files ?? []))
                    e.target.value = ''
                  }}
                />
                <button
                  type="button"
                  onClick={() => fileInputRef.current?.click()}
                  className="inline-flex items-center gap-1 rounded border border-slate-300 px-2 py-1 text-[11px] font-medium text-slate-700 hover:bg-slate-50"
                >
                  <Paperclip className="h-3 w-3" /> Chọn file
                </button>
                {pendingFiles.length === 0
                  ? <span className="text-[11px] text-slate-400">Chưa chọn file nào</span>
                  : (
                    <span className="text-[11px] text-slate-600">
                      {pendingFiles.map(f => f.name).join(', ')}
                      <button type="button" onClick={() => setPendingFiles([])} className="ml-2 text-red-600 underline">bỏ chọn</button>
                    </span>
                  )}
              </div>
              <p className="mt-1 text-[11px] text-slate-500">
                File được tải lên SAU khi thao tác duyệt thành công và hiện ở mục &ldquo;File đính kèm khi duyệt&rdquo;.
              </p>
            </div>
          )}

          {action === KhkkTransitionAction.Approve && (
            <p className="mt-1 text-[11px] text-slate-500">
              Ghi chú sẽ được ghi vào mục &ldquo;Ý kiến cấp duyệt&rdquo; của kế hoạch (1 ô/Cấp — duyệt lại
              thì ghi đè ô cũ).
            </p>
          )}
        </Dialog>
      )}

      {confirmDelete && (
        <Dialog
          open
          onClose={() => setConfirmDelete(false)}
          title="🗑 Xóa kế hoạch ký kết"
          footer={<>
            <Button variant="outline" onClick={() => setConfirmDelete(false)}>Hủy</Button>
            <Button onClick={() => removePlan.mutate()} disabled={removePlan.isPending}>
              {removePlan.isPending ? 'Đang xóa...' : 'Xóa phiếu'}
            </Button>
          </>}
        >
          <div className="rounded border border-red-200 bg-red-50 px-3 py-2 text-[12px] text-red-800">
            Phiếu <span className="font-mono font-semibold">{plan.maKeHoach ?? '(chưa cấp mã)'}</span> sẽ bị
            xóa mềm: biến khỏi danh sách và khỏi cây toàn trình, chỉ còn xem được ở mục &ldquo;Đã xóa&rdquo;.
          </div>
        </Dialog>
      )}
    </div>
  )
}
