// [W2 KHKK — S161 2026-07-29] Chi tiết "Kế hoạch ký kết HĐ" (GĐ2).
// Khuôn `pages/office/ProposalDetailPage.tsx` (SectionCard + Field idiom PURO).
// File MIRROR SHA256 identical với fe-admin counterpart.
//
// 4 section: (1) Thông tin + ghi chú/link hồ sơ sửa được khi nháp · (2) Dòng giá
// per-NCC trúng thầu (READ-ONLY ở W2 — `PeReferenceAmount` là SNAPSHOT lúc lập kế
// hoạch) · (3) Căn cứ hồ sơ b.8-9 (CRUD chỉ mở ở DangSoanThao|TraLai) · (4) File
// đính kèm (mở MỌI phase — triết lý PE S147).
// [W3 S161] +panel duyệt `KhkkWorkflowPanel` NGAY DƯỚI header (trên "Thông tin kế hoạch"):
// sơ đồ Bước→Cấp + banner đến-lượt + Gửi duyệt / Duyệt / Trả lại / Từ chối.
import { useState, type ReactNode } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { useNavigate, useParams } from 'react-router-dom'
import {
  ArrowLeft, ClipboardList, FileCheck, FileSignature, Paperclip, Pencil, Plus, Save, Trash2,
  Upload, Users,
} from 'lucide-react'
import { toast } from 'sonner'
import { PageHeader } from '@/components/ui/PageHeader'
import { Button } from '@/components/ui/Button'
import { Dialog } from '@/components/ui/Dialog'
import { Input } from '@/components/ui/Input'
import { Label } from '@/components/ui/Label'
import { Select } from '@/components/ui/Select'
import { Textarea } from '@/components/ui/Textarea'
import { usePermission } from '@/hooks/usePermission'
import { api } from '@/lib/api'
import { getErrorMessage } from '@/lib/apiError'
import { cn } from '@/lib/cn'
import { ContractTypeLabel } from '@/types/forms'
import {
  DOSSIER_KIND_LABELS, DOSSIER_STATUS_BADGE, DOSSIER_STATUS_LABELS,
  DossierItemKind, DossierItemStatus, KHKK_APPROVAL_GROUP_LABELS,
  KHKK_PHASE_BADGE, KHKK_PHASE_LABELS,
  KhkkAttachmentPurpose, KhkkPhase,
  type CreateContractFromKhkkInput, type CreateContractFromKhkkResult,
  type DossierItemKindValue, type DossierItemStatusValue, type KhkkAttachmentDto,
  type KhkkDetailDto, type KhkkDossierItemDto, type KhkkLineDto, type KhkkPhaseValue,
  type UpsertKhkkDossierItemInput,
} from '@/types/khkk'
import { KhkkWorkflowPanel } from './KhkkWorkflowPanel'

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

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

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

// Section card — .card-accent shell + .icon-chip header (Hồ sơ-NS idiom).
function SectionCard({
  title, icon, accent, head, chipBg, chipFg, count, actions, children,
}: {
  title: string
  icon: ReactNode
  accent: string
  head: string
  chipBg: string
  chipFg: string
  count?: number
  actions?: ReactNode
  children: ReactNode
}) {
  return (
    <section className="card-accent" style={{ ['--accent' as string]: accent }}>
      <header className="flex items-center gap-2 border-b border-slate-100 px-5 py-3">
        <span
          className="icon-chip"
          style={{ height: '1.75rem', width: '1.75rem', ['--chip-bg' as string]: chipBg, ['--chip-fg' as string]: chipFg }}
          aria-hidden
        >
          {icon}
        </span>
        <h3 className={cn('text-sm font-semibold tracking-tight', head)}>{title}</h3>
        {count != null && (
          <span className="rounded-full bg-slate-100 px-1.5 py-0.5 text-[10px] font-semibold tabular-nums text-slate-500">
            {count}
          </span>
        )}
        {actions && <span className="ml-auto">{actions}</span>}
      </header>
      <div className="p-5">{children}</div>
    </section>
  )
}

function Field({
  label, value, mono, full, children,
}: {
  label: string
  value?: ReactNode
  mono?: boolean
  full?: boolean
  children?: ReactNode
}) {
  const empty = value == null || value === ''
  return (
    <div className={cn('min-w-0 text-sm', full && 'sm:col-span-2')}>
      <div className="text-[11px] font-semibold uppercase tracking-wide text-brand-600">{label}</div>
      <div className={cn('mt-0.5 break-words text-sm', empty && !children ? 'text-slate-300' : 'font-medium text-brand-800', mono && !empty && 'font-mono')}>
        {children ?? (empty ? '—' : value)}
      </div>
    </div>
  )
}

const EMPTY_ITEM: UpsertKhkkDossierItemInput = {
  kind: DossierItemKind.MauVatLieu,
  name: '',
  status: DossierItemStatus.ChuaNop,
  tvgsName: '',
  note: '',
}

export function KhkkDetailPage() {
  const { id } = useParams<{ id: string }>()
  const navigate = useNavigate()
  const qc = useQueryClient()

  const [editHeader, setEditHeader] = useState(false)
  const [ghiChu, setGhiChu] = useState('')
  const [hoSoLink, setHoSoLink] = useState('')
  const [itemForm, setItemForm] = useState<UpsertKhkkDossierItemInput | null>(null)
  const [bridgeOpen, setBridgeOpen] = useState(false)

  // [K7 S167 — vá-1 vế FE] HỘI 2 KHÓA, đúng bằng server, KHÔNG lỏng hơn:
  //   · `KeHoachKyKet.Read`  — cửa đọc chính phiếu này (`ContractSigningPlansController.cs:36`,
  //     `MenuPermissionHandler.cs:40` so khớp CHÍNH XÁC 1 key ⇒ KHÔNG OR `Khkk_*`, gotcha #85);
  //   · `Contracts.Create`   — cửa của endpoint bắc cầu: tài nguyên SINH RA là Contract nên key
  //     phải là của endpoint đích, không phải của màn đang đứng.
  // Chữ ký hook là `can(menuKey, action)` (`hooks/usePermission.ts:17`) — không có dạng
  // `usePermission('X','Y')`.
  const { can } = usePermission()
  const canReadPlan = can('KeHoachKyKet', 'Read')
  const canCreateContract = can('Contracts', 'Create')

  const plan = useQuery({
    queryKey: ['khkk-detail', id],
    queryFn: async () => (await api.get<KhkkDetailDto>(`/contract-signing-plans/${id}`)).data,
    enabled: !!id,
  })

  const invalidate = () => {
    qc.invalidateQueries({ queryKey: ['khkk-detail', id] })
    qc.invalidateQueries({ queryKey: ['khkk-list'] })
  }

  const saveHeader = useMutation({
    mutationFn: async () => {
      await api.put(`/contract-signing-plans/${id}`, {
        ghiChu: ghiChu.trim() || null,
        hoSoLink: hoSoLink.trim() || null,
      })
    },
    onSuccess: () => {
      toast.success('Đã lưu')
      setEditHeader(false)
      invalidate()
    },
    onError: (e) => toast.error(getErrorMessage(e)),
  })

  // [S168 K8-blocker — Lines-editor] Gán hạng mục SP-002 cho dòng: options lọc ĐÚNG nhóm
  // duyệt của phiếu (BE fail-fast tầng-gán + submit-guard chốt cuối). Chỉ tải khi phiếu
  // còn sửa được (Nháp/Trả lại) — tránh 1 request thừa trên phiếu đã trình/duyệt.
  const planDraftEditable =
    plan.data?.phase === KhkkPhase.DangSoanThao || plan.data?.phase === KhkkPhase.TraLai
  const catalogOptions = useQuery({
    queryKey: ['contract-catalog', plan.data?.approvalGroup],
    queryFn: async () =>
      (
        await api.get<{ id: string; code: string; tenVi: string }[]>('/catalogs/contract-catalog', {
          params: { approvalGroup: plan.data!.approvalGroup },
        })
      ).data,
    enabled: planDraftEditable && plan.data?.approvalGroup != null,
  })
  const assignLine = useMutation({
    mutationFn: async (p: { lineId: string; catalogEntryId: string }) =>
      api.put(`/contract-signing-plans/${id}/lines/${p.lineId}`, { catalogEntryId: p.catalogEntryId }),
    onSuccess: () => {
      toast.success('Đã gán hạng mục')
      invalidate()
    },
    onError: (e) => toast.error(getErrorMessage(e)),
  })

  const upsertItem = useMutation({
    mutationFn: async (body: UpsertKhkkDossierItemInput) => {
      if (!body.name.trim()) throw new Error('Vui lòng nhập tên căn cứ')
      // [F-3 S161] BE verify id-path == cmd.ContractSigningPlanId. [F-4] POST ép Id=null
      // = LUÔN insert ⇒ bấm "Sửa" phải đi PUT, không thì đẻ bản sao im lặng.
      const payload = {
        ...body,
        contractSigningPlanId: id,
        name: body.name.trim(),
        tvgsName: body.tvgsName?.trim() || null,
        note: body.note?.trim() || null,
      }
      if (body.id) await api.put(`/contract-signing-plans/${id}/dossier-items/${body.id}`, payload)
      else await api.post(`/contract-signing-plans/${id}/dossier-items`, payload)
    },
    onSuccess: () => {
      toast.success('Đã lưu căn cứ')
      setItemForm(null)
      invalidate()
    },
    onError: (e) => toast.error(getErrorMessage(e)),
  })

  const deleteItem = useMutation({
    mutationFn: async (itemId: string) =>
      api.delete(`/contract-signing-plans/${id}/dossier-items/${itemId}`),
    onSuccess: () => {
      toast.success('Đã xóa căn cứ')
      invalidate()
    },
    onError: (e) => toast.error(getErrorMessage(e)),
  })

  const upload = useMutation({
    mutationFn: async (file: File) => {
      const fd = new FormData()
      fd.append('file', file)
      fd.append('purpose', String(KhkkAttachmentPurpose.DossierScan))
      return api.post(`/contract-signing-plans/${id}/attachments`, fd, {
        headers: { 'Content-Type': 'multipart/form-data' },
      })
    },
    onSuccess: () => {
      toast.success('Đã tải lên')
      invalidate()
    },
    onError: (e) => toast.error(getErrorMessage(e)),
  })

  const deleteAttachment = useMutation({
    mutationFn: async (attId: string) => api.delete(`/contract-signing-plans/${id}/attachments/${attId}`),
    onSuccess: () => {
      toast.success('Đã xóa file')
      invalidate()
    },
    onError: (e) => toast.error(getErrorMessage(e)),
  })

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

  async function download(att: KhkkAttachmentDto) {
    try {
      const res = await api.get(`/contract-signing-plans/${id}/attachments/${att.id}/download`, {
        responseType: 'blob',
      })
      const url = window.URL.createObjectURL(res.data as Blob)
      const a = document.createElement('a')
      a.href = url
      a.download = att.fileName
      a.click()
      window.URL.revokeObjectURL(url)
    } catch (e) {
      toast.error(getErrorMessage(e))
    }
  }

  async function onPickFiles(e: React.ChangeEvent<HTMLInputElement>) {
    const files = Array.from(e.target.files ?? [])
    e.target.value = ''
    for (const f of files) {
      try { await upload.mutateAsync(f) } catch { /* lỗi đã toast ở onError */ }
    }
  }

  if (plan.isLoading) {
    return (
      <div className="space-y-5">
        <PageHeader eyebrow="Giai đoạn 2" title="Đang tải..." icon={<FileCheck className="h-5 w-5" />} />
      </div>
    )
  }

  if (plan.isError || !plan.data) {
    return (
      <div className="space-y-5">
        <PageHeader
          eyebrow="Giai đoạn 2"
          title="Lỗi"
          icon={<FileCheck className="h-5 w-5" />}
          actions={
            <Button variant="outline" onClick={() => navigate('/khkk/list')}>
              <ArrowLeft className="mr-2 h-4 w-4" />
              Quay lại
            </Button>
          }
        />
        <div className="rounded-lg border bg-red-50 p-4 text-sm text-red-800">
          Không tải được dữ liệu kế hoạch ký kết.
        </div>
      </div>
    )
  }

  const k = plan.data
  const phase = k.phase as KhkkPhaseValue
  const isDraft = phase === KhkkPhase.DangSoanThao || phase === KhkkPhase.TraLai
  const canDeletePlan = phase === KhkkPhase.DangSoanThao || phase === KhkkPhase.TuChoi

  // [K7 S167] Dòng bắc cầu được = CHƯA có HĐ ∧ ĐÃ có giá duyệt chốt — đúng 2 guard BE
  // (`§K7.1` guard 4 + 5). Chặn sớm ở FE để người dùng không bấm rồi mới ăn 409.
  const bridgeableLines = k.lines.filter(l => l.contractId == null && l.approvedAmount != null)
  // Nút chỉ có nghĩa khi phiếu ĐÃ DUYỆT (giá mới chốt) và người dùng đọc được phiếu.
  const showBridge = phase === KhkkPhase.DaDuyet && canReadPlan
  // Thiếu vế nào thì NÓI RA vế đó thay vì ẩn nút: người dùng đang đứng trên phiếu, ẩn đi họ
  // chỉ đoán "hệ thống không có chức năng". Khuôn nút-khoá-kèm-title `PeWorkflowPanel.tsx:547-561`.
  const bridgeBlockReason = !canCreateContract
    ? 'Bạn chưa có quyền "Tạo hợp đồng" (Contracts.Create) — nhờ quản trị cấp quyền rồi thử lại.'
    : k.lines.length === 0
      ? 'Phiếu chưa có dòng nhà cung cấp nào.'
      : bridgeableLines.length === 0
        ? (k.lines.every((l) => l.contractId != null)
            ? 'Mọi dòng của phiếu đã được đưa vào hợp đồng.'
            : 'Chưa dòng nào có giá duyệt chốt — phiếu phải qua bước chốt giá trước khi đưa vào HĐ.')
        : null

  const startEditHeader = () => {
    setGhiChu(k.ghiChu ?? '')
    setHoSoLink(k.hoSoLink ?? '')
    setEditHeader(true)
  }

  return (
    <div className="space-y-5">
      <PageHeader
        eyebrow={k.maKeHoach ?? 'Kế hoạch ký kết'}
        title={k.peTenGoiThau ?? 'Kế hoạch ký kết HĐ'}
        icon={<FileCheck className="h-5 w-5" />}
        accent="brand"
        actions={
          <>
            {/* [K2 S164] Badge NHÓM DUYỆT — đứng TRƯỚC badge phase vì nhóm là thứ quyết
                phiếu đi đường nào (8 quy trình khác nhau), phase chỉ là chỗ đang đứng.
                `!= null` guard: phiếu nạp từ cache trước K2 chưa có field ⇒ đừng in
                "Nundefined" (bài "vắng-mặt trông giống ổn"). */}
            {k.approvalGroup != null && (
              <span
                className="inline-flex items-center rounded-md border border-violet-300 bg-violet-100 px-2.5 py-1 text-xs font-semibold text-violet-800"
                title={KHKK_APPROVAL_GROUP_LABELS[k.approvalGroup] ?? `Nhóm ${k.approvalGroup}`}
              >
                N{k.approvalGroup}
              </span>
            )}
            <span
              className={cn(
                'inline-flex items-center rounded-md border px-2.5 py-1 text-xs font-medium',
                KHKK_PHASE_BADGE[phase],
              )}
            >
              {KHKK_PHASE_LABELS[phase]}
            </span>
            {/* [K7 S167 — vá-1 vế FE] Nút bắc cầu KHKK → HĐ. `<span title>` bọc ngoài vì
                `Button` có `disabled:pointer-events-none` (Button.tsx:12) ⇒ title đặt thẳng
                trên nút đã khoá sẽ không hiện, người dùng mất luôn câu giải thích. */}
            {showBridge && (
              <span title={bridgeBlockReason ?? 'Tạo hợp đồng từ một dòng của kế hoạch này'}>
                <Button
                  variant="outline"
                  disabled={bridgeBlockReason != null}
                  onClick={() => setBridgeOpen(true)}
                >
                  <FileSignature className="mr-2 h-4 w-4" />
                  Đưa vào HĐ
                </Button>
              </span>
            )}
            {canDeletePlan && (
              <Button
                variant="outline"
                onClick={() => {
                  if (window.confirm('Xóa kế hoạch ký kết này?')) deletePlan.mutate()
                }}
                disabled={deletePlan.isPending}
              >
                <Trash2 className="mr-2 h-4 w-4" />
                Xóa
              </Button>
            )}
            <Button variant="outline" onClick={() => navigate('/khkk/list')}>
              <ArrowLeft className="mr-2 h-4 w-4" />
              Danh sách
            </Button>
          </>
        }
      />

      {/* [W3 S161] Panel duyệt — đặt NGAY dưới header để người duyệt thấy việc-phải-làm
          trước nội dung phiếu (mirror thứ tự PE: workflow panel là thứ đầu tiên trong
          màn Duyệt). `onChanged` dùng `invalidate` của trang để refetch theo id URL. */}
      <KhkkWorkflowPanel plan={k} onChanged={invalidate} />

      {/* Section 1: Thông tin */}
      <SectionCard
        title="Thông tin kế hoạch"
        icon={<FileCheck className="h-4 w-4" />}
        accent="var(--color-brand-500)"
        head="text-brand-800"
        chipBg="var(--color-brand-50)"
        chipFg="var(--color-brand-600)"
        actions={
          isDraft && !editHeader ? (
            <Button variant="outline" size="sm" onClick={startEditHeader}>
              <Pencil className="mr-2 h-3.5 w-3.5" />
              Sửa
            </Button>
          ) : undefined
        }
      >
        <div className="grid grid-cols-1 gap-x-6 gap-y-4 md:grid-cols-2">
          <Field label="Mã kế hoạch" value={k.maKeHoach ?? '—'} mono />
          <Field label="Phiếu Duyệt NCC nguồn">
            <span className="font-mono text-sm">{k.peMaPhieu ?? '—'}</span>
          </Field>
          <Field label="Dự án" value={k.projectName ?? '—'} />
          <Field label="Phòng ban" value={k.departmentName ?? '—'} />
          <Field label="Người soạn" value={k.drafterFullName ?? '—'} />
          <Field label="Quy trình">
            {k.workflowCode ? (
              <span>
                <span className="font-mono">{k.workflowCode}</span> - {k.workflowName}
              </span>
            ) : (
              <span className="text-slate-300">— Chưa chọn —</span>
            )}
          </Field>
          <Field label="Ngày tạo" value={formatDateTime(k.createdAt)} />
          <Field label="Hạn xử lý (tham khảo)" value={k.slaDeadline ? formatDateTime(k.slaDeadline) : '—'} />
        </div>

        {editHeader ? (
          <div className="mt-4 space-y-3 rounded-lg border border-slate-200 bg-slate-50/60 p-4">
            <div>
              <Label htmlFor="hoSoLink">Link hồ sơ (NAS)</Label>
              <Input
                id="hoSoLink"
                value={hoSoLink}
                onChange={(e) => setHoSoLink(e.target.value)}
                maxLength={1000}
                placeholder="\\\\nas\\du-an\\..."
              />
            </div>
            <div>
              <Label htmlFor="ghiChu">Ghi chú</Label>
              <Textarea
                id="ghiChu"
                value={ghiChu}
                onChange={(e) => setGhiChu(e.target.value)}
                maxLength={2000}
                rows={4}
              />
              <div className="mt-1 text-xs text-muted-foreground">{ghiChu.length}/2000</div>
            </div>
            <div className="flex justify-end gap-2">
              <Button variant="outline" size="sm" onClick={() => setEditHeader(false)}>
                Huỷ
              </Button>
              <Button size="sm" onClick={() => saveHeader.mutate()} disabled={saveHeader.isPending}>
                <Save className="mr-2 h-3.5 w-3.5" />
                {saveHeader.isPending ? 'Đang lưu...' : 'Lưu'}
              </Button>
            </div>
          </div>
        ) : (
          <div className="mt-4 grid grid-cols-1 gap-x-6 gap-y-4 md:grid-cols-2">
            <Field label="Link hồ sơ" full>
              {k.hoSoLink ? (
                <span className="break-all font-normal text-slate-700">{k.hoSoLink}</span>
              ) : (
                <span className="text-slate-300">—</span>
              )}
            </Field>
            <Field label="Ghi chú" full>
              {k.ghiChu ? (
                <span className="whitespace-pre-wrap font-normal text-slate-700">{k.ghiChu}</span>
              ) : (
                <span className="text-slate-300">—</span>
              )}
            </Field>
          </div>
        )}
      </SectionCard>

      {/* Section 2: Dòng giá per-NCC trúng thầu */}
      <SectionCard
        title="Giá đề xuất theo NCC trúng thầu"
        icon={<Users className="h-4 w-4" />}
        accent="var(--color-teal-500)"
        head="text-teal-700"
        chipBg="var(--color-teal-50)"
        chipFg="var(--color-teal-700)"
        count={k.lines.length}
        actions={
          // [K2 S164 — QĐ-9] `budgetFrozen` = cờ HIỂN THỊ do BE dẫn xuất từ phase
          // (`DetailDto:166`). Số đã đóng băng ở tầng dữ liệu bởi `ApprovedAmount ??=`
          // tại choke-point finalize — badge này chỉ nói cho người dùng biết điều đó.
          k.budgetFrozen ? (
            <span className="inline-flex items-center rounded-md border border-amber-300 bg-amber-100 px-2 py-0.5 text-[11px] font-semibold normal-case tracking-normal text-amber-800">
              🔒 Giá đã chốt tại thời điểm duyệt
            </span>
          ) : undefined
        }
      >
        {k.lines.length === 0 ? (
          <div className="text-sm text-muted-foreground">Phiếu nguồn chưa có NCC trúng thầu.</div>
        ) : (
          <table className="w-full text-sm">
            <thead className="border-b border-slate-200 bg-slate-50/70">
              <tr>
                <th className="label-eyebrow px-3 py-2 text-left">Nhà cung cấp</th>
                {/* [K2 S164 → S168] Hạng mục SP-002 của dòng — đường GÁN (Lines-editor) land
                    @S168 (mảnh hẹn-K4b bị rơi, dry-run K8 bắt): Nháp/Trả lại gán tại bảng,
                    dòng trống KHÔNG trình duyệt được (submit-guard BE). */}
                <th className="label-eyebrow px-3 py-2 text-left">Hạng mục</th>
                <th className="label-eyebrow px-3 py-2 text-right">Giá tham chiếu (phiếu NCC)</th>
                <th className="label-eyebrow px-3 py-2 text-right">Giá đề xuất</th>
                <th className="label-eyebrow px-3 py-2 text-right">Giá duyệt</th>
                <th className="label-eyebrow px-3 py-2 text-left">Ghi chú</th>
              </tr>
            </thead>
            <tbody>
              {k.lines.map((l) => (
                <tr key={l.id} className="border-b border-slate-100 last:border-0">
                  <td className="px-3 py-2 font-medium text-brand-800">
                    {l.supplierName ?? '—'}
                    {/* [K7 S167] Dòng đã bắc cầu — khuôn dấu "✓ HĐ" của danh sách PE
                        (`PurchaseEvaluationsListPage.tsx:599`). Thấy ngay ở bảng chứ không
                        phải mở dialog mới biết dòng nào đã xong. */}
                    {l.contractId && (
                      <a
                        href={`/contracts/${l.contractId}`}
                        className="ml-2 text-[10px] font-medium text-brand-600 hover:underline"
                        title="Dòng này đã được đưa vào hợp đồng — bấm để mở HĐ"
                      >
                        ✓ HĐ
                      </a>
                    )}
                  </td>
                  <td className="px-3 py-2 text-xs text-slate-700">
                    {/* Dòng đã bắc cầu HĐ thì khoá (đổi hạng mục sau khi có HĐ = đổi lịch sử). */}
                    {isDraft && !l.contractId ? (
                      <>
                        <select
                          value={l.catalogEntryId ?? ''}
                          onChange={(e) =>
                            e.target.value &&
                            assignLine.mutate({ lineId: l.id, catalogEntryId: e.target.value })
                          }
                          disabled={assignLine.isPending}
                          aria-label={`Gán hạng mục SP-002 cho dòng ${l.supplierName ?? l.id}`}
                          className="h-8 w-full min-w-[12rem] rounded-md border border-slate-200 bg-white px-2 text-xs text-slate-700 focus-visible:border-brand-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500/30"
                        >
                          <option value="">— Chọn hạng mục —</option>
                          {/* [gate-lineeditor F-2] Hạng mục ĐÃ LƯU mà không có trong options
                              (đang tải / query lỗi / entry hết hiệu lực) ⇒ chèn option đại diện
                              từ dữ liệu đã lưu — không thì select rơi về placeholder và dòng
                              đã gán TRÔNG như chưa gán (vắng-mặt trông giống ổn). */}
                          {l.catalogEntryId &&
                            !(catalogOptions.data ?? []).some((c) => c.id === l.catalogEntryId) && (
                              <option value={l.catalogEntryId}>
                                {l.tenHangMuc ?? '(hạng mục đã gán)'}
                              </option>
                            )}
                          {(catalogOptions.data ?? []).map((c) => (
                            <option key={c.id} value={c.id}>
                              {c.code} — {c.tenVi}
                            </option>
                          ))}
                        </select>
                        {catalogOptions.isError && (
                          <div className="mt-1 text-[11px] text-red-600">
                            Không tải được danh mục hạng mục —{' '}
                            <button type="button" className="underline" onClick={() => catalogOptions.refetch()}>
                              thử lại
                            </button>
                          </div>
                        )}
                        {catalogOptions.isSuccess && (catalogOptions.data?.length ?? 0) === 0 && (
                          <div className="mt-1 text-[11px] text-amber-700">
                            Nhóm duyệt N{k.approvalGroup} chưa có hạng mục đang hiệu lực — liên hệ quản trị danh mục.
                          </div>
                        )}
                      </>
                    ) : l.tenHangMuc ? (
                      l.tenHangMuc
                    ) : (
                      <span className="text-slate-300">—</span>
                    )}
                  </td>
                  <td className="px-3 py-2 text-right tabular-nums text-slate-600">{formatVnd(l.peReferenceAmount)}</td>
                  <td className="px-3 py-2 text-right tabular-nums text-brand-800">{formatVnd(l.proposedAmount)}</td>
                  <td className="px-3 py-2 text-right tabular-nums text-emerald-700">{formatVnd(l.approvedAmount)}</td>
                  <td className="px-3 py-2 text-xs text-slate-600">{l.note ?? '—'}</td>
                </tr>
              ))}
            </tbody>
          </table>
        )}
        <div className="mt-3 text-xs text-muted-foreground">
          Giá tham chiếu là SNAPSHOT tổng báo giá được chọn của NCC trên phiếu Duyệt NCC tại thời điểm lập kế hoạch.
          {' '}Cột Hạng mục gán ngay tại bảng khi phiếu ở Nháp/Trả lại — mọi dòng phải gán xong mới trình duyệt được.
        </div>
      </SectionCard>

      {/* Section 3: Căn cứ hồ sơ b.8-9 */}
      <SectionCard
        title="Căn cứ hồ sơ (mẫu vật liệu / shopdrawing)"
        icon={<ClipboardList className="h-4 w-4" />}
        accent="var(--color-violet-500)"
        head="text-violet-700"
        chipBg="var(--color-violet-50)"
        chipFg="var(--color-violet-700)"
        count={k.dossierItems.length}
        actions={
          isDraft ? (
            <Button variant="outline" size="sm" onClick={() => setItemForm({ ...EMPTY_ITEM })}>
              <Plus className="mr-2 h-3.5 w-3.5" />
              Thêm căn cứ
            </Button>
          ) : undefined
        }
      >
        {k.dossierItems.length === 0 ? (
          <div className="text-sm text-muted-foreground">Chưa khai căn cứ nào.</div>
        ) : (
          <table className="w-full text-sm">
            <thead className="border-b border-slate-200 bg-slate-50/70">
              <tr>
                <th className="label-eyebrow px-3 py-2 text-left">Loại</th>
                <th className="label-eyebrow px-3 py-2 text-left">Tên căn cứ</th>
                <th className="label-eyebrow px-3 py-2 text-left">Trạng thái</th>
                <th className="label-eyebrow px-3 py-2 text-left">TVGS</th>
                <th className="label-eyebrow px-3 py-2 text-left">Ghi chú</th>
                {isDraft && <th className="label-eyebrow px-3 py-2 text-right">Thao tác</th>}
              </tr>
            </thead>
            <tbody>
              {k.dossierItems.map((d: KhkkDossierItemDto) => (
                <tr key={d.id} className="border-b border-slate-100 last:border-0">
                  <td className="px-3 py-2 text-xs text-slate-600">{DOSSIER_KIND_LABELS[d.kind] ?? '—'}</td>
                  <td className="px-3 py-2 font-medium text-brand-800">{d.name}</td>
                  <td className="px-3 py-2">
                    <span
                      className={cn(
                        'inline-flex items-center rounded-md border px-2 py-0.5 text-xs font-medium',
                        DOSSIER_STATUS_BADGE[d.status],
                      )}
                    >
                      {DOSSIER_STATUS_LABELS[d.status]}
                    </span>
                  </td>
                  <td className="px-3 py-2 text-xs text-slate-600">
                    {d.tvgsName ?? '—'}
                    {d.tvgsResultAt && (
                      <span className="ml-1 text-[11px] text-slate-400">({formatDateTime(d.tvgsResultAt)})</span>
                    )}
                  </td>
                  <td className="px-3 py-2 text-xs text-slate-600">{d.note ?? '—'}</td>
                  {isDraft && (
                    <td className="px-3 py-2 text-right">
                      <div className="flex justify-end gap-1">
                        <button
                          type="button"
                          onClick={() =>
                            setItemForm({
                              id: d.id,
                              kind: d.kind,
                              supplierId: d.supplierId,
                              name: d.name,
                              status: d.status,
                              tvgsName: d.tvgsName ?? '',
                              note: d.note ?? '',
                            })
                          }
                          className="rounded px-1.5 py-1 text-violet-600 hover:bg-violet-50"
                          title="Sửa"
                        >
                          <Pencil className="h-3.5 w-3.5" />
                        </button>
                        <button
                          type="button"
                          onClick={() => {
                            if (window.confirm(`Xóa căn cứ "${d.name}"?`)) deleteItem.mutate(d.id)
                          }}
                          className="rounded px-1.5 py-1 text-red-600 hover:bg-red-50"
                          title="Xóa"
                        >
                          <Trash2 className="h-3.5 w-3.5" />
                        </button>
                      </div>
                    </td>
                  )}
                </tr>
              ))}
            </tbody>
          </table>
        )}
      </SectionCard>

      {/* Section 4: File đính kèm — mở mọi phase */}
      <SectionCard
        title="File đính kèm"
        icon={<Paperclip className="h-4 w-4" />}
        accent="var(--color-teal-500)"
        head="text-teal-700"
        chipBg="var(--color-teal-50)"
        chipFg="var(--color-teal-700)"
        count={k.attachments.length}
        actions={
          <label className="inline-flex cursor-pointer items-center rounded-md border border-slate-300 px-2.5 py-1.5 text-xs font-medium text-slate-700 hover:bg-slate-50">
            <Upload className="mr-2 h-3.5 w-3.5" />
            {upload.isPending ? 'Đang tải...' : 'Tải file lên'}
            <input type="file" multiple className="hidden" onChange={onPickFiles} />
          </label>
        }
      >
        {k.attachments.length === 0 ? (
          <div className="text-sm text-muted-foreground">Chưa có file đính kèm.</div>
        ) : (
          <ul className="space-y-2">
            {k.attachments.map((a) => (
              <li key={a.id} className="flex items-center justify-between rounded-lg border border-slate-200 p-2.5 text-sm">
                <div className="min-w-0">
                  <div className="truncate font-medium text-brand-800">{a.fileName}</div>
                  <div className="text-xs text-muted-foreground">
                    {formatSize(a.fileSize)} · {formatDateTime(a.createdAt)}
                  </div>
                </div>
                <div className="flex shrink-0 gap-2">
                  <button type="button" onClick={() => download(a)} className="text-xs text-primary hover:underline">
                    Tải xuống
                  </button>
                  <button
                    type="button"
                    onClick={() => {
                      if (window.confirm(`Xóa file "${a.fileName}"?`)) deleteAttachment.mutate(a.id)
                    }}
                    className="text-xs text-red-600 hover:underline"
                  >
                    Xóa
                  </button>
                </div>
              </li>
            ))}
          </ul>
        )}
      </SectionCard>

      {/* Dialog thêm/sửa căn cứ */}
      <Dialog
        open={!!itemForm}
        onClose={() => setItemForm(null)}
        title={itemForm?.id ? 'Sửa căn cứ hồ sơ' : 'Thêm căn cứ hồ sơ'}
      >
        {itemForm && (
          <div className="space-y-3">
            <div>
              <Label htmlFor="item-kind">Loại căn cứ</Label>
              <select
                id="item-kind"
                value={itemForm.kind}
                onChange={(e) => setItemForm({ ...itemForm, kind: Number(e.target.value) as DossierItemKindValue })}
                className="flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm"
              >
                {Object.entries(DOSSIER_KIND_LABELS).map(([v, label]) => (
                  <option key={v} value={v}>
                    {label}
                  </option>
                ))}
              </select>
            </div>
            <div>
              <Label htmlFor="item-name">
                Tên căn cứ <span className="text-red-500">*</span>
              </Label>
              <Input
                id="item-name"
                value={itemForm.name}
                onChange={(e) => setItemForm({ ...itemForm, name: e.target.value })}
                maxLength={300}
                placeholder="vd. Mẫu thép D10 Hoà Phát"
              />
            </div>
            <div>
              <Label htmlFor="item-status">Trạng thái</Label>
              <select
                id="item-status"
                value={itemForm.status}
                onChange={(e) => setItemForm({ ...itemForm, status: Number(e.target.value) as DossierItemStatusValue })}
                className="flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm"
              >
                {Object.entries(DOSSIER_STATUS_LABELS).map(([v, label]) => (
                  <option key={v} value={v}>
                    {label}
                  </option>
                ))}
              </select>
            </div>
            <div>
              <Label htmlFor="item-tvgs">Tên TVGS xử lý</Label>
              <Input
                id="item-tvgs"
                value={itemForm.tvgsName ?? ''}
                onChange={(e) => setItemForm({ ...itemForm, tvgsName: e.target.value })}
                maxLength={200}
              />
            </div>
            <div>
              <Label htmlFor="item-note">Ghi chú</Label>
              <Textarea
                id="item-note"
                value={itemForm.note ?? ''}
                onChange={(e) => setItemForm({ ...itemForm, note: e.target.value })}
                maxLength={1000}
                rows={3}
              />
            </div>
            <div className="flex justify-end gap-2 pt-2">
              <Button variant="outline" onClick={() => setItemForm(null)}>
                Huỷ
              </Button>
              <Button
                onClick={() => upsertItem.mutate(itemForm)}
                disabled={upsertItem.isPending || !itemForm.name.trim()}
              >
                {upsertItem.isPending ? 'Đang lưu...' : 'Lưu'}
              </Button>
            </div>
          </div>
        )}
      </Dialog>

      {/* [K7 S167 — vá-16] Dialog bắc cầu. Render có điều kiện (không dùng prop `open`) để
          mỗi lần mở là state mới — khuôn dialog Xóa-phiếu `PeWorkflowPanel.tsx:879`. */}
      {bridgeOpen && (
        <CreateContractFromKhkkDialog plan={k} onClose={() => setBridgeOpen(false)} />
      )}
    </div>
  )
}

// ===================== K7 S167 — cầu KHKK → Hợp đồng =================================
// [vá-16] 0 KHUÔN MỚI — ghép 2 khuôn đang sống:
//   · vỏ dialog + khối "nói thẳng hệ quả" = nút Xóa-phiếu S155 (`PeWorkflowPanel.tsx:879-918`);
//   · ô chọn Quy trình duyệt = picker `ContractCreatePage.tsx:333-343`
//     (GET `/approval-workflows-v2?applicableType=3` → lọc theo cờ).
// [OG-1 "máy rộng, UI hẹp"] Máy BE nhận `lineIds[]` (gộp được N dòng vào 1 HĐ); ô chọn ở
// đây CỐ Ý 1-1 — mỗi lần bấm đúng 1 dòng. Mở gộp sau = đổi radio thành checkbox, KHÔNG
// đụng hợp-đồng API.
type BridgeWorkflowOption = {
  id: string
  code: string
  version: number
  name: string
  isActive: boolean
  isUserSelectable: boolean
}

function CreateContractFromKhkkDialog({ plan, onClose }: { plan: KhkkDetailDto; onClose: () => void }) {
  const navigate = useNavigate()
  const qc = useQueryClient()

  const bridgeable = plan.lines.filter((l) => l.contractId == null && l.approvedAmount != null)
  const [lineId, setLineId] = useState(() => bridgeable[0]?.id ?? '')
  const [contractType, setContractType] = useState(1)
  const [workflowId, setWorkflowId] = useState('')
  // null = "đang dùng tên mặc định theo dòng đang chọn". Giữ null thay vì đồng bộ bằng
  // useEffect: đổi dòng thì tên gợi ý đổi theo NGAY trong render, không có nhịp lệch.
  const [tenHopDongOverride, setTenHopDongOverride] = useState<string | null>(null)

  const selected: KhkkLineDto | null = plan.lines.find((l) => l.id === lineId) ?? null
  const defaultName = selected
    ? `${plan.peTenGoiThau ?? 'Kế hoạch ký kết'} — ${selected.tenHangMuc ?? selected.supplierName ?? ''}`.trim()
    : ''
  const tenHopDong = tenHopDongOverride ?? defaultName

  const workflows = useQuery({
    // Query key RIÊNG (không dùng lại `approval-workflows-v2-contract` của ContractCreatePage):
    // cùng URL nhưng LỌC KHÁC ⇒ dùng chung ô cache là để trang này ăn dữ liệu trang kia một
    // cách câm (bài S159).
    queryKey: ['khkk-bridge-contract-workflows'],
    queryFn: async () => {
      const res = await api.get<{ types: { applicableType: number; history: BridgeWorkflowOption[] }[] }>(
        '/approval-workflows-v2',
        { params: { applicableType: 3 } },
      )
      const bucket = res.data.types.find((t) => t.applicableType === 3)
      // 🔴 LỌC ĐỦ 2 CỜ (spec cụm-3 vá-2): `isUserSelectable` (admin ghim cho user chọn) ∧
      // `isActive` (bản còn hiệu lực). Picker cũ `ContractCreatePage.tsx:341` chỉ lọc 1 cờ ⇒
      // mời chọn được bản đã retire rồi ăn 409 từ guard BE. Ở đây lọc đủ ngay từ nguồn.
      return (bucket?.history ?? []).filter((w) => w.isUserSelectable && w.isActive)
    },
  })

  const create = useMutation({
    mutationFn: async () => {
      const body: CreateContractFromKhkkInput = {
        lineIds: [lineId],
        contractType,
        approvalWorkflowId: workflowId,
        tenHopDong: tenHopDong.trim() || null,
      }
      return (
        await api.post<CreateContractFromKhkkResult>(
          `/contract-signing-plans/${plan.id}/create-contract`,
          body,
        )
      ).data
    },
    onSuccess: (data) => {
      toast.success(data.maHopDong ? `Đã tạo hợp đồng ${data.maHopDong}` : 'Đã tạo hợp đồng từ kế hoạch')
      qc.invalidateQueries({ queryKey: ['khkk-detail', plan.id] })
      qc.invalidateQueries({ queryKey: ['khkk-list'] })
      // Cây 4 folder đọc 2 index riêng (`usePipelineStages.ts`) với `staleTime` 60s — không
      // gọi 2 dòng này thì HĐ vừa tạo cả phút sau mới hiện trên cây.
      qc.invalidateQueries({ queryKey: ['pipeline-khkk-index'] })
      qc.invalidateQueries({ queryKey: ['pipeline-contract-index'] })
      onClose()
      navigate(`/contracts/${data.contractId}`)
    },
    onError: (e) => toast.error(getErrorMessage(e)),
  })

  const noWorkflow = !workflows.isLoading && (workflows.data?.length ?? 0) === 0

  return (
    <Dialog
      open
      size="lg"
      onClose={onClose}
      title="Đưa dòng kế hoạch vào Hợp đồng"
      footer={
        <>
          <Button variant="ghost" onClick={onClose}>
            Huỷ
          </Button>
          <Button
            onClick={() => create.mutate()}
            disabled={create.isPending || !lineId || !workflowId}
          >
            {create.isPending ? 'Đang tạo...' : 'Tạo hợp đồng'}
          </Button>
        </>
      }
    >
      <div className="mb-3 rounded border border-amber-200 bg-amber-50 px-3 py-2 text-[12px] text-amber-900">
        <div className="font-semibold">Tạo hợp đồng từ kế hoạch &ldquo;{plan.maKeHoach ?? 'chưa cấp mã'}&rdquo;</div>
        <ul className="mt-1.5 list-disc space-y-1 pl-4 text-[11px]">
          <li>Mỗi lần bấm tạo <strong>1 hợp đồng cho 1 dòng</strong> (1 nhà cung cấp). Liên danh nhiều NCC thì làm lần lượt.</li>
          <li>Giá trị HĐ lấy đúng <strong>giá duyệt đã chốt</strong> của dòng — không lấy giá đề xuất.</li>
          <li>HĐ mới ở trạng thái <strong>Nháp</strong> và đi theo quy trình duyệt bạn chọn dưới đây.</li>
          <li>Dòng đã đưa vào HĐ thì <strong>không đưa lần hai</strong> — cần sửa thì xử lý ở chính hợp đồng đó.</li>
        </ul>
      </div>

      <div className="space-y-3">
        <div>
          <Label>Chọn dòng đưa vào hợp đồng</Label>
          {plan.lines.length === 0 ? (
            <div className="text-sm text-muted-foreground">Phiếu chưa có dòng nhà cung cấp nào.</div>
          ) : (
            <ul className="mt-1 space-y-1.5">
              {plan.lines.map((l) => {
                const done = l.contractId != null
                const noPrice = l.approvedAmount == null
                const disabled = done || noPrice
                return (
                  <li key={l.id}>
                    <label
                      className={cn(
                        'flex cursor-pointer items-center gap-2 rounded-lg border px-3 py-2 text-sm',
                        disabled && 'cursor-not-allowed border-slate-200 bg-slate-50 text-slate-400',
                        !disabled && lineId === l.id && 'border-brand-400 bg-brand-50',
                        !disabled && lineId !== l.id && 'border-slate-200 hover:bg-slate-50',
                      )}
                      title={
                        done
                          ? 'Dòng này đã có hợp đồng.'
                          : noPrice
                            ? 'Dòng chưa có giá duyệt chốt — phiếu phải qua bước chốt giá.'
                            : undefined
                      }
                    >
                      <input
                        type="radio"
                        name="khkk-bridge-line"
                        checked={lineId === l.id}
                        disabled={disabled}
                        onChange={() => {
                          setLineId(l.id)
                          setTenHopDongOverride(null)
                        }}
                      />
                      <span className="min-w-0 flex-1">
                        <span className="font-medium">{l.tenHangMuc ?? '(chưa gán hạng mục)'}</span>
                        <span className="text-slate-500"> · {l.supplierName ?? '—'}</span>
                      </span>
                      <span className="shrink-0 tabular-nums">{formatVnd(l.approvedAmount)}</span>
                      {done && (
                        <a
                          href={`/contracts/${l.contractId}`}
                          className="shrink-0 text-[11px] font-medium text-brand-600 hover:underline"
                        >
                          Đã có HĐ
                        </a>
                      )}
                      {!done && noPrice && (
                        <span className="shrink-0 text-[11px]">Chưa có giá chốt</span>
                      )}
                    </label>
                  </li>
                )
              })}
            </ul>
          )}
        </div>

        <div>
          <Label htmlFor="bridge-type">Loại hợp đồng</Label>
          <Select
            id="bridge-type"
            value={contractType}
            onChange={(e) => setContractType(Number(e.target.value))}
          >
            {Object.entries(ContractTypeLabel).map(([v, label]) => (
              <option key={v} value={v}>
                {label}
              </option>
            ))}
          </Select>
        </div>

        <div>
          <Label htmlFor="bridge-workflow">Quy trình duyệt hợp đồng</Label>
          <Select
            id="bridge-workflow"
            value={workflowId}
            onChange={(e) => setWorkflowId(e.target.value)}
            disabled={workflows.isLoading || noWorkflow}
          >
            <option value="">— Chọn quy trình —</option>
            {(workflows.data ?? []).map((w) => (
              <option key={w.id} value={w.id}>
                {w.code} v{w.version} — {w.name}
              </option>
            ))}
          </Select>
          {noWorkflow && (
            // Rỗng-vì-chưa-cấu-hình phải khác câu với rỗng-vì-đang-tải: người dùng cần biết
            // đây là việc của quản trị, không phải chờ thêm.
            <p className="mt-1 text-[11px] text-red-700">
              Chưa có quy trình duyệt hợp đồng nào được ghim cho người dùng chọn (cần đang hiệu lực
              và được ghim). Nhờ quản trị mở trong màn Quy trình duyệt rồi quay lại.
            </p>
          )}
        </div>

        <div>
          <Label htmlFor="bridge-name">Tên hợp đồng</Label>
          <Input
            id="bridge-name"
            value={tenHopDong}
            onChange={(e) => setTenHopDongOverride(e.target.value)}
            maxLength={500}
          />
          <p className="mt-1 text-[11px] text-slate-500">
            Bỏ trống thì hệ thống tự đặt theo tên gói thầu và hạng mục của dòng.
          </p>
        </div>
      </div>
    </Dialog>
  )
}
