// [S190 · WQ1-FE YC-030 Q1] PANEL 1 của màn "HĐ của tôi" (GĐ3) — mirror-ADAPT
// `components/khkk/KhkkListPanel.tsx` (GĐ2): khối filter trên cùng + thân CÂY-hoặc-PHẲNG
// cuộn riêng + chân đếm. Anh BÁC bản hoán-đổi W2 (cây trái ⟂ danh-sách phải) và chốt
// "Q1 như KHKK" ⇒ hai khối đó gộp làm MỘT cột, đổi nhau theo bộ lọc.
//
// 🔴 fe-user-ONLY CÓ CHỦ ĐÍCH — KHÔNG có cặp mirror ở fe-admin, đừng đi tìm rồi tưởng ai đó
//    quên `cp`: `ls fe-admin/src/pages/contracts/` = ContractCreatePage · ContractDetailPage ·
//    ContractsListPage · HardCopiesPage — KHÔNG có `MyContractsPage`. Bên admin đối ứng là
//    `ContractsListPage.tsx` (khác file, khác vai: quản-trị toàn bộ HĐ, không phải "HĐ của tôi").
//    Đây là chỗ 2 app LỆCH THẬT, khác với 4 cặp component/hook đang SHA-identical.
//
// ══ HAI CHẾ ĐỘ, MỘT CỘT ═══════════════════════════════════════════════════════════════════
//   CÂY (mặc định)  `PipelineTreePanel currentStage={3}` — Năm › Dự án › Hạng mục › … › HĐ.
//   PHẲNG           khi CÓ bất kỳ trục lọc nào: từ khoá · trạng thái · `pendingMe` · `deleted`.
//
// 🔴 [M-1 — bê nguyên từ `KhkkListPanel.tsx:14`] `!canPe` cũng ép PHẲNG. Cây dựng trên xương
//    sống phiếu Duyệt NCC (`usePipelineStages.ts:83` + index `enabled: canPe`); ai KHÔNG có
//    quyền Duyệt NCC thì cây dựng ra RỖNG trong khi `GET /contracts` vẫn cho họ đọc HĐ của
//    chính mình (mặt còn lại của gotcha #82). Bỏ vế này = họ mất trắng danh sách, hỏng CÂM.
//
// 🔴 [HĐ NGOÀI GÓI — khai lại, W2 `MyContractsPage.tsx:9-10` đã ghi và vẫn ĐÚNG] Cây chỉ mọc
//    từ phiếu Duyệt NCC ⇒ HĐ tạo TRỰC TIẾP (không qua gói thầu) KHÔNG BAO GIỜ hiện trên cây.
//    Trước đợt này danh-sách là một cột thường-trực nên luôn thấy; nay nó là một CHẾ ĐỘ ⇒
//    đường thấy = bật bất kỳ bộ lọc nào (chọn trạng thái là đủ). Vì đường đó không tự lộ ra,
//    có DÒNG CHỮ HIỆN ở cuối khối filter khi đang ở chế độ cây — KHÔNG giấu sau tooltip
//    (bài S182: `title` trên control disabled bị Chromium nuốt; và thông tin PHÂN BIỆT thì
//    phải nhìn thấy được).
import { useQuery } from '@tanstack/react-query'
import { FileText, Search } from 'lucide-react'
import { EmptyState } from '@/components/EmptyState'
import { PhaseBadge } from '@/components/PhaseBadge'
import { SlaTimer } from '@/components/SlaTimer'
import { PipelineTreePanel } from '@/components/pipeline/PipelineTreePanel'
import { Input } from '@/components/ui/Input'
import { Select } from '@/components/ui/Select'
import { usePipelineStages } from '@/hooks/usePipelineStages'
import { api } from '@/lib/api'
import { cn } from '@/lib/cn'
import { ContractPhaseLabel, type ContractListItem } from '@/types/contracts'
import { ContractTypeLabel } from '@/types/forms'
import type { Paged } from '@/types/master'

// Cửa sổ tải của chế độ PHẲNG. Giữ nguyên số của bản W2/W7 (`pageSize: 100`, trang 1, KHÔNG
// có UI phân trang) — đợt này chỉ đổi CHỖ ĐỨNG của danh sách, không đổi cách phân trang.
// Chân cột khai thẳng "hiển thị x/total" khi bị cắt, thay vì cắt im lặng.
const PAGE_SIZE = 100

const fmtMoney = (v: number) => v.toLocaleString('vi-VN')

/**
 * Card 3 hàng — khuôn `KhkkPlanCard` (`KhkkListPanel.tsx:103-191`), thay 5 ô của HĐ vào:
 * tên · mã · NCC · chip phase · giá (+ đồng hồ SLA vốn có của màn này).
 * Markup giữ NGUYÊN của bản W2 (`MyContractsPage.tsx:333-359` trước đợt này) — dời chỗ chứ
 * không vẽ lại, để không đổi hình một cách âm thầm.
 */
export function ContractRowCard({
  contract: c,
  selected,
  onSelect,
  disabled,
}: {
  contract: ContractListItem
  selected: boolean
  onSelect: (id: string) => void
  /** Chế độ "Đã xóa": chỉ liệt kê, không mở (detail endpoint không IgnoreQueryFilters ⇒ 404). */
  disabled?: boolean
}) {
  return (
    <button
      type="button"
      onClick={() => { if (!disabled) onSelect(c.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',
        disabled && 'cursor-default opacity-70 hover:bg-transparent',
      )}
    >
      <div className="flex items-start justify-between gap-2">
        <div className="min-w-0 flex-1">
          <div className="truncate text-[13px] font-medium text-slate-900">
            {c.tenHopDong ?? '(chưa đặt tên)'}
          </div>
          <div className="mt-0.5 flex items-center gap-1.5 text-[11px] text-slate-500">
            <span className="font-mono">{c.maHopDong ?? '—'}</span>
            <span>·</span>
            <span className="truncate">{c.supplierName}</span>
          </div>
        </div>
        <PhaseBadge phase={c.phase} className="shrink-0 text-[10px]" />
      </div>
      <div className="mt-1 flex items-center justify-between text-[11px] text-slate-500">
        <span>{fmtMoney(c.giaTri)}</span>
        <SlaTimer deadline={c.slaDeadline} createdAt={c.createdAt} />
      </div>
    </button>
  )
}

export function ContractListPanel({
  selectedId,
  onSelect,
  search,
  onSearchChange,
  type,
  onTypeChange,
  phase,
  onPhaseChange,
  pendingMe,
  deletedView,
}: {
  selectedId: string | null
  onSelect: (id: string) => void
  search: string
  onSearchChange: (v: string) => void
  /** Loại HĐ ĐANG ÁP = `?type=` của leaf menu, có thể bị `Select` bên dưới đè (host giải). */
  type: number | null
  onTypeChange: (v: number | null) => void
  /** Trạng thái ĐANG ÁP = `?phase=` của leaf, có thể bị `Select` đè (host giải). */
  phase: number | null
  onPhaseChange: (v: number | null) => void
  pendingMe: boolean
  deletedView: boolean
}) {
  const { canPe } = usePipelineStages()
  const term = search.trim()

  // 🔴 Thứ tự vế KHÔNG tuỳ tiện: `!canPe` đứng đầu vì nó là lưới an toàn (M-1), 4 vế sau là
  // "người dùng đang lọc" ⇒ lọc thì phải thấy DANH SÁCH, cây không diễn tả được bộ lọc.
  const flatMode = !canPe || term.length > 0 || phase !== null || pendingMe || deletedView

  const list = useQuery({
    // Trục key giữ ĐÚNG 6 trục của bản W3 (`MyContractsPage.tsx:87`) — mỗi trục đổi request
    // hoặc đổi hẳn endpoint, thiếu một trục là dùng chung ô cache sai CÂM. Khác duy nhất:
    // dùng `term` (đã trim) thay `search` thô ⇒ "  abc" và "abc" về chung một ô, cùng payload.
    queryKey: ['my-contracts', type, phase, deletedView, term, pendingMe],
    queryFn: async () => {
      // [S190 W3 — khuôn PE `PurchaseEvaluationsListPage.tsx:77-99`] Nhánh "Đã xóa" đứng
      // TRƯỚC `pendingMe`: 2 view LOẠI TRỪ nhau, và HĐ đã xóa chỉ thấy được qua endpoint
      // riêng `/contracts/deleted`. Đảo thứ tự = URL `?deleted=1&pendingMe=1` rơi vào inbox
      // (inbox không bao giờ trả HĐ đã xóa) ⇒ màn "Đã xóa" rỗng THẦM LẶNG.
      if (pendingMe && !deletedView) {
        // 🔴 ĐỔI ENDPOINT, KHÔNG gửi thêm param: `GET /contracts` KHÔNG có param `pendingMe`
        // và ASP.NET NUỐT param lạ IM LẶNG ⇒ gửi lên sẽ ra Y HỆT danh sách thường.
        // `GET /contracts/inbox` = 0 THAM SỐ, trả `List<ContractListItemDto>` (không phân
        // trang) → lọc loại ở CLIENT + ép shape.
        // [carry:w7b-inbox-param] Nợ W7 vẫn treo: `GetMyInboxQuery` là record 0 tham số và
        // còn `Take(100)` cứng ⇒ chưa lọc/tìm được phía máy chủ. Nâng BE = việc đợt khác.
        const res = await api.get<ContractListItem[]>('/contracts/inbox')
        const items = type != null ? res.data.filter(c => c.type === type) : res.data
        return { items, total: items.length }
      }
      const res = await api.get<Paged<ContractListItem>>(
        deletedView ? '/contracts/deleted' : '/contracts',
        {
          params: {
            page: 1,
            pageSize: PAGE_SIZE,
            // [W7 S187] Lọc loại + [W2 S190] tìm kiếm ĐỀU ĐI SERVER (param có sẵn ở CẢ 2
            // endpoint: `ContractsController.cs:19,25` và `:34,36`; BE lọc TRƯỚC phân trang
            // `ContractFeatures.cs:325` trước `Skip/Take:340`). Lọc client sau khi đã cắt
            // 100 dòng = loại/mã hiếm biến mất IM LẶNG — đúng bệnh đã vá 2 đợt trước.
            // 🔴 ĐỔI SO VỚI W2: `type` ở đây là loại ĐANG ÁP (đã tính cả `Select`), không
            // còn là `?type=` trần của URL. `Select` loại nay lọc CẢ cây LẪN danh sách —
            // để nó chỉ lọc cây thì ở chế độ phẳng nó thành nút chết.
            type: type ?? undefined,
            search: term || undefined,
            ...(!deletedView && phase != null ? { phase } : {}),
          },
        },
      )
      return { items: res.data.items, total: res.data.total }
    },
    // Chế độ CÂY không đọc endpoint này ⇒ đừng gọi. (Khuôn `KhkkListPanel.tsx:260`.)
    enabled: flatMode,
  })

  const rows = list.data?.items ?? []
  const total = list.data?.total ?? 0
  const typeLabel = type != null ? ContractTypeLabel[type] : null

  // "Rỗng vì LỌC" ⟂ "rỗng vì CHƯA CÓ GÌ" ⟂ "rỗng vì LỖI" phải khác câu (bài S190 W3): dùng
  // chung một câu thì người đứng ở mục "Đang duyệt" đọc thành "loại HĐ này chưa có dữ liệu".
  const emptyTitle = pendingMe && !deletedView ? 'Không có HĐ chờ bạn duyệt' : 'Không có HĐ'
  const emptyDescription = deletedView
    ? 'Chưa có HĐ nào bị xóa.'
    : pendingMe
      ? typeLabel
        ? `Không HĐ "${typeLabel}" nào đang đến lượt bạn duyệt.`
        : 'Không có HĐ nào đang đến lượt bạn duyệt.'
      : term || phase !== null
        ? 'Không HĐ nào khớp bộ lọc đang bật.'
        : typeLabel
          ? 'Loại HĐ này chưa có dữ liệu.'
          : 'Tạo HĐ mới để bắt đầu.'

  return (
    <aside className="flex flex-col overflow-hidden border-r border-slate-200 bg-white">
      {/* Khối filter — CHỖ ĐẶT + thứ tự giống KHKK/PE (trên cùng panel 1).
          Q2 anh chốt: ô chọn ở đây CHỈ có "theo loại HĐ" (không đẻ trục chọn thứ hai). */}
      <div className="space-y-2 border-b border-slate-200 p-3">
        <Select
          value={type ?? ''}
          aria-label="Lọc loại HĐ"
          onChange={e => onTypeChange(e.target.value === '' ? null : Number(e.target.value))}
        >
          {/* Ca URL trần (`/my-contracts` không `?type=`) phải có lựa chọn hợp lệ. */}
          <option value="">Tất cả loại HĐ</option>
          {Object.entries(ContractTypeLabel).map(([value, label]) => (
            <option key={value} value={value}>{label}</option>
          ))}
        </Select>

        <div className="relative">
          <Search className="pointer-events-none absolute left-2.5 top-2.5 h-4 w-4 text-slate-400" />
          <Input
            value={pendingMe ? '' : search}
            onChange={e => onSearchChange(e.target.value)}
            disabled={pendingMe}
            placeholder="Tìm theo mã / tên / NCC…"
            className="pl-8"
          />
        </div>

        {/* [S176-KHKK khuôn `KhkkListPanel.tsx:347-350`] Thùng rác ⇒ THAY dropdown bằng banner.
            Ẩn dropdown là CÓ LÝ DO: HĐ đã xóa giữ NGUYÊN phase lúc bị xóa, `IsDeleted` mới là
            thứ phân loại ⇒ lọc trạng thái ở đây là nút chết (và `queryFn` cũng cố ý KHÔNG gửi
            `phase` cho `/contracts/deleted`). */}
        {deletedView ? (
          <div className="rounded border border-red-200 bg-red-50 px-2 py-1.5 text-[11px] text-red-700">
            🗑 <strong>HĐ đã xóa</strong> — chỉ xem. Không khôi phục, không thao tác.
          </div>
        ) : pendingMe ? (
          // [S190 W3] Nói VÌ SAO bằng CHỮ HIỆN, KHÔNG bằng `title` (tooltip trên control
          // `disabled` bị Chromium nuốt — bài S182). Ô tìm ở trên cũng khoá vì cùng một lý do:
          // `/contracts/inbox` là record 0 tham số, không nhận `search` lẫn `phase`.
          <p className="text-[11px] leading-snug text-slate-500">
            Chế độ “Đang duyệt” đọc hộp thư đến — chưa hỗ trợ tìm kiếm / lọc trạng thái phía máy chủ.
          </p>
        ) : (
          <Select
            value={phase ?? ''}
            aria-label="Lọc trạng thái"
            onChange={e => onPhaseChange(e.target.value === '' ? null : Number(e.target.value))}
          >
            <option value="">Tất cả trạng thái</option>
            {Object.entries(ContractPhaseLabel).map(([value, label]) => (
              <option key={value} value={value}>{label}</option>
            ))}
          </Select>
        )}

        {/* Chữ HIỆN cho đường tới HĐ ngoài gói (xem khối đầu file). Chỉ hiện ở chế độ cây —
            đang ở danh sách phẳng rồi thì câu này thành thừa. */}
        {!flatMode && (
          <p className="text-[11px] italic leading-snug text-slate-500">
            Cây dựng từ phiếu Duyệt NCC — HĐ tạo trực tiếp (ngoài gói thầu) không nằm trên cây.
            Chọn một trạng thái hoặc gõ ô tìm để chuyển sang danh sách phẳng.
          </p>
        )}
        {/* [M-1] Không có quyền Duyệt NCC ⇒ nói thẳng vì sao không thấy cây, thay vì để người
            dùng đoán. Danh sách phẳng bên dưới vẫn đầy đủ. */}
        {!canPe && (
          <p className="text-[10px] italic text-slate-500">
            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 HĐ vẫn liệt kê đầy đủ ở dưới.
          </p>
        )}
      </div>

      <div className="flex-1 overflow-y-auto">
        {!flatMode && (
          // [X-3 / SF-26 — bài S168 F-1] Cột này TỰ CUỘN ⇒ thân cây PHẢI bỏ trần `max-h` mặc
          // định của khuôn, nếu không là 2 thanh cuộn lồng nhau.
          <PipelineTreePanel
            currentStage={3}
            hideHeader
            hideSearch
            bodyMaxHClass="lg:max-h-none"
            stage2Options={{
              // Bấm leaf HĐ ⇒ chọn vào Panel 2 qua `?id=`; URL Ở LẠI `/my-contracts`.
              onOpenContract: onSelect,
              // `null` ⇒ `undefined` ⇒ hook coi như KHÔNG lọc (đủ mọi loại).
              stage3TypeFilter: type ?? undefined,
            }}
          />
        )}

        {flatMode && list.isLoading && (
          <div className="space-y-2 p-3">
            {Array.from({ length: 6 }).map((_, i) => (
              <div key={i} className="h-16 animate-pulse rounded-md bg-slate-100 motion-reduce:animate-none" />
            ))}
          </div>
        )}
        {/* Rỗng-vì-LỖI phải khác rỗng-vì-không-có: bản W2 không có nhánh này nên lỗi mạng đọc
            thành "Không có HĐ" — sai nghĩa hẳn. Khuôn `KhkkListPanel.tsx:416-427`. */}
        {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 HĐ.</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 && rows.length === 0 && (
          <div className="p-6">
            <EmptyState icon={FileText} title={emptyTitle} description={emptyDescription} />
          </div>
        )}
        {flatMode && rows.length > 0 && (
          <ul className="divide-y divide-slate-100">
            {rows.map(c => (
              <li key={c.id}>
                <ContractRowCard
                  contract={c}
                  selected={selectedId === c.id}
                  onSelect={onSelect}
                  disabled={deletedView}
                />
              </li>
            ))}
          </ul>
        )}
      </div>

      {/* Chân cột — số đếm dời từ badge ở header trang xuống đây (khuôn `KhkkListPanel.tsx:457`).
          🔴 Nói THẬT về chỗ bị cắt: cửa sổ là 100 dòng và trang này chưa có UI phân trang, nên
          `total > rows.length` phải hiện thành chữ, không im lặng. */}
      {flatMode && !list.isLoading && !list.isError && rows.length > 0 && (
        <div className="flex shrink-0 flex-wrap items-center justify-between gap-1 border-t border-slate-200 px-3 py-2 text-[11px] text-slate-500">
          <span>
            <FileText className="mr-1 inline h-3 w-3" />
            {total > rows.length ? `${rows.length} / ${total} HĐ` : `${rows.length} HĐ`}
          </span>
          {total > rows.length && (
            <span className="text-amber-700">Chỉ tải {PAGE_SIZE} dòng đầu — thu hẹp bộ lọc để thấy phần còn lại.</span>
          )}
        </div>
      )}
    </aside>
  )
}
