// [S162 — 2026-07-30] Cây 4 folder GIAI ĐOẠN, đặt DƯỚI TỪNG GÓI THẦU.
// Owner 30-07 (screenshot khoanh đỏ dải 4 GĐ trỏ xuống cây trái trang Duyệt NCC):
// "Tạo folder: Duyệt NCC / Kế hoạch Hợp đồng / Duyệt Hợp đồng / Hợp đồng cứng",
// AskUser chốt (1) folder nằm DƯỚI TỪNG GÓI THẦU · (2) áp CẢ các trang GĐ khác.
//
// 🔴 Nhãn folder VERBATIM lời owner — GĐ2 CỐ Ý khác nhãn dải strip
// (`components/pe/PePipelineStrip.tsx` = "Kế hoạch ký kết HĐ"): lời-mới-nhất-thắng.
// KHÔNG sửa strip, KHÔNG sửa nhãn menu.
//
// File MIRROR SHA256 identical giữa 2 app (fe-user ⟂ fe-admin) — sửa 1 bên PHẢI copy sang bên kia.
//
// Component này THUẦN TRÌNH BÀY: nhận `stages` đã dựng sẵn (xem
// `hooks/usePipelineStages.ts`) rồi vẽ. GĐ1 trên trang Duyệt NCC truyền `content`
// = đúng khối leaf phiếu CŨ ⇒ mọi behavior (chọn phiếu / chip GẤP / nút mở rộng)
// giữ nguyên, chỉ được RE-PARENT vào trong folder.
import { useState, type ReactNode } from 'react'
import { ChevronRight, ClipboardCheck, FileCheck, FileSignature, Lock, Stamp } from 'lucide-react'
import { cn } from '@/lib/cn'

export type PipelineStageNo = 1 | 2 | 3 | 4

export const PIPELINE_STAGE_LABELS: Record<PipelineStageNo, string> = {
  1: 'Duyệt NCC',
  2: 'Kế hoạch Hợp đồng',
  3: 'Duyệt Hợp đồng',
  4: 'Hợp đồng cứng',
}

const STAGE_ICON: Record<PipelineStageNo, typeof ClipboardCheck> = {
  1: ClipboardCheck,
  2: FileCheck,
  3: FileSignature,
  4: Stamp,
}

// Pattern 14 (Tailwind JIT): class dạng LITERAL đầy đủ, không nội suy mảnh.
// Palette `@theme` (index.css, khối `ACCENT PALETTE` — neo theo TÊN, KHÔNG theo số dòng:
//   `grep -n 'ACCENT PALETTE' fe-admin/src/index.css`; số dòng cũ ":29-51" đã thối 2 lần @S179)
// chỉ có stop 50/100/500/600/700 cho teal /
// violet / amberx ⇒ head dùng -700 (riêng brand có tới -900).
const STAGE_STYLE: Record<PipelineStageNo, { chip: string; head: string; count: string; code: string }> = {
  1: { chip: 'bg-brand-50 text-brand-600', head: 'text-brand-800', count: 'bg-brand-50 text-brand-700', code: 'text-brand-700' },
  2: { chip: 'bg-violet-50 text-violet-600', head: 'text-violet-700', count: 'bg-violet-50 text-violet-700', code: 'text-violet-700' },
  3: { chip: 'bg-amberx-50 text-amberx-600', head: 'text-amberx-700', count: 'bg-amberx-50 text-amberx-700', code: 'text-amberx-700' },
  4: { chip: 'bg-teal-50 text-teal-600', head: 'text-teal-700', count: 'bg-teal-50 text-teal-700', code: 'text-teal-700' },
}

export type PipelineLeaf = {
  id: string
  /** Dòng 1 — mã (font-mono). */
  primary: string
  /** Dòng 2 — tên gói thầu / tên HĐ. */
  secondary?: string | null
  badgeLabel?: string | null
  /** Class ĐẦY ĐỦ cho badge (Pattern 14). */
  badgeClass?: string | null
  onOpen?: () => void
}

/**
 * [K5 S166] Ngăn con TRONG một folder giai đoạn (GĐ2 = 8 nhóm duyệt KHKK).
 * 🔴 `leaves` ở đây là LEAVES THẬT (chính các phiếu con), KHÔNG phải một đường
 * `content` riêng — nhờ vậy tổng số phiếu của các ngăn luôn bằng số phiếu của
 * folder cha theo CẤU TRÚC, không phải nhờ ai đó nhớ cộng cho khớp (vá-9).
 */
export type PipelineGroup = {
  key: string
  label: string
  leaves: PipelineLeaf[]
  /** Bấm vào TÊN ngăn ⇒ mở trang danh sách đã lọc sẵn ngăn đó. */
  onOpenAll?: () => void
}

export type PipelineStage = {
  n: PipelineStageNo
  /** Danh sách PHẲNG, là nguồn duy nhất để đếm. `groups` chỉ chia lại chính nó. */
  leaves: PipelineLeaf[]
  /**
   * Có mặt ⇒ thân folder vẽ theo NGĂN thay vì danh sách phẳng. Ngăn RỖNG vẫn
   * hiện (mirror tiền lệ folder giai đoạn chưa có gì: chip "Sắp") — người dùng
   * phải thấy đủ khung 8 nhóm để biết cái nào trống, chứ không phải đoán.
   */
  groups?: PipelineGroup[]
  /** Không có quyền xem module GĐ này ⇒ folder khoá + "Sắp", KHÔNG gọi API. */
  locked?: boolean
  /** Query GĐ này lỗi ⇒ đếm "—", cây KHÔNG vỡ. */
  failed?: boolean
  loading?: boolean
  /** Nội dung tự vẽ (GĐ1 trang Duyệt NCC dùng để giữ nguyên leaf phiếu cũ). */
  content?: ReactNode
  /** Bắt buộc khi dùng `content` (component không đếm hộ được). */
  count?: number
  /** Index nguồn chạm trần cửa sổ 200 (`PagedResult.cs:20`) ⇒ đếm CÓ THỂ thiếu. */
  truncated?: boolean
}

function stageCount(stage: PipelineStage): number {
  return stage.content !== undefined ? (stage.count ?? 0) : stage.leaves.length
}

// 🧊 [S174 owner O2] `SoonChip()` ĐÃ GỠ — owner: "Bỏ hết đi đổi thành 0". Lane L4 đo được
// KHÔNG giai đoạn nào còn đúng nghĩa "sắp" (cả 4 đều fetch API thật), và `SoonChip` KHÔNG
// export (8 hit nội bộ) ⇒ 0 host ngoài phụ thuộc, gỡ an toàn.
// 🔴 Gỡ KHÔNG đồng nghĩa "thay hết bằng 0": 2 ca đếm-được thì in số, ca KHOÁ-QUYỀN thì bỏ
// trắng (xem chú thích tại chỗ) — in 0 cho ca khoá là nói dối về dữ-liệu.

type StageStyle = (typeof STAGE_STYLE)[PipelineStageNo]

/** Danh sách leaf phẳng — dùng chung cho thân folder LẪN thân từng ngăn (K5). */
function LeafList({ leaves, style }: { leaves: PipelineLeaf[]; style: StageStyle }) {
  return (
    <ul className="divide-y divide-slate-100">
      {leaves.map(leaf => (
        <li key={leaf.id}>
          <button
            type="button"
            onClick={leaf.onOpen}
            disabled={!leaf.onOpen}
            className={cn(
              'block w-full px-3 py-2 text-left transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-500/60',
              leaf.onOpen ? 'hover:bg-slate-50' : 'cursor-default',
            )}
          >
            <div className="flex items-center gap-1.5">
              {/* Mã to hơn dòng phụ 1 nấc: font-mono 11px NHÌN nhỏ ngang
                  sans 11px nên 2 dòng bẹt như nhau (soi FD2 r1-D). */}
              <span className={cn('min-w-0 flex-1 truncate font-mono text-[12px] font-medium', style.code)}>
                {leaf.primary}
              </span>
              {leaf.badgeLabel && (
                <span
                  className={cn(
                    'shrink-0 rounded px-1.5 py-0.5 text-[10px] font-medium',
                    leaf.badgeClass ?? 'bg-slate-100 text-slate-600',
                  )}
                >
                  {leaf.badgeLabel}
                </span>
              )}
            </div>
            {leaf.secondary && <div className="truncate text-[11px] text-slate-500">{leaf.secondary}</div>}
          </button>
        </li>
      ))}
    </ul>
  )
}

/**
 * [K5 S166] Một ngăn trong folder giai đoạn (GĐ2 = 1 nhóm duyệt).
 * 🔴 Hai thao tác TÁCH BẠCH trên cùng một dòng: bấm vùng dòng = mở/đóng ngăn
 * (hành vi mặc định của <summary>), bấm ĐÚNG TÊN ngăn = sang trang danh sách đã
 * lọc sẵn nhóm đó. Nút tên phải chặn nổi bọt, nếu không mỗi lần đi trang lại
 * kèm một lần đóng/mở ngăn nhấp nháy phía sau.
 * Mặc định ĐÓNG: 8 ngăn mở sẵn là một bức tường chữ, mà cây này nằm ở cột trái.
 */
function GroupFolder({ group, style }: { group: PipelineGroup; style: StageStyle }) {
  const count = group.leaves.length
  const empty = count === 0
  return (
    <details className="group/grp">
      <summary className="flex cursor-pointer items-center gap-1.5 px-3 py-1 hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-500/60 [&::-webkit-details-marker]:hidden">
        <ChevronRight
          aria-hidden
          className="h-3 w-3 shrink-0 text-slate-400 transition-transform group-open/grp:rotate-90 motion-reduce:transition-none"
        />
        {group.onOpenAll ? (
          <button
            type="button"
            onClick={e => {
              e.preventDefault()
              e.stopPropagation()
              group.onOpenAll?.()
            }}
            title={`Mở danh sách: ${group.label}`}
            // Ngăn rỗng vẫn bấm sang trang được (để lập phiếu mới cho nhóm đó)
            // ⇒ vẫn là chữ đang hoạt động: slate-500 giữ đủ tương phản AA.
            className={cn(
              'min-w-0 flex-1 truncate rounded text-left text-[11px] font-medium underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500/60',
              empty ? 'text-slate-500' : style.head,
            )}
          >
            {group.label}
          </button>
        ) : (
          <span className={cn('min-w-0 flex-1 truncate text-[11px] font-medium', empty ? 'text-slate-500' : style.head)}>
            {group.label}
          </span>
        )}
        {/* [S174 owner O2] "Bỏ hết đi đổi thành 0" — luôn in SỐ, kể cả 0. Chữ "SẮP" cũ nói
            "giai đoạn chưa xây"; nay CẢ 4 giai đoạn đều gọi API thật (lane L4 đo) ⇒ 0 nghĩa
            là "chưa có phiếu", không phải "chưa có tính năng". */}
        <span className={cn('shrink-0 rounded px-1.5 py-0.5 text-[10px] font-medium', style.count)}>{count}</span>
      </summary>
      <div className="ml-3 border-l border-slate-200">
        {empty ? (
          <div className="px-3 py-1.5 text-[11px] italic text-slate-400">Chưa có phiếu ở nhóm này.</div>
        ) : (
          <LeafList leaves={group.leaves} style={style} />
        )}
      </div>
    </details>
  )
}

function StageFolder({ stage, currentStage }: { stage: PipelineStage; currentStage: PipelineStageNo }) {
  // Folder của TRANG hiện tại mở sẵn; 3 folder kia đóng. Vẫn collapse/expand được.
  const [open, setOpen] = useState(stage.n === currentStage)
  const label = PIPELINE_STAGE_LABELS[stage.n]
  const style = STAGE_STYLE[stage.n]
  const Icon = STAGE_ICON[stage.n]
  const count = stageCount(stage)
  const muted = !!stage.locked || (!stage.failed && count === 0)

  // Khoá quyền: KHÔNG render <details> (không có gì để mở, và cũng không được
  // phép gọi API để biết) — chỉ 1 dòng khoá + chip "Sắp".
  // 🔴 Giữ ĐÚNG 3 khoang như dòng folder thường (khoang chevron ⇢ ổ khoá · icon-chip
  // · nhãn) để 4 dòng thẳng hàng tuyệt đối; bỏ khoang là lệch 26px (soi FD2 r1-D).
  if (stage.locked) {
    return (
      <div
        className="flex items-center gap-1.5 px-3 py-1.5"
        title="Bạn chưa có quyền xem giai đoạn này"
      >
        <Lock aria-hidden className="h-3 w-3 shrink-0 text-slate-400" />
        <span className="inline-flex h-5 w-5 shrink-0 items-center justify-center rounded bg-slate-100 text-slate-400">
          <Icon aria-hidden className="h-3 w-3" />
        </span>
        {/* [S174 owner O2] KHÔNG in "0" ở đây. Đây là ca "chưa có QUYỀN xem", không phải
            "có 0 phiếu" — in 0 là NÓI DỐI về dữ-liệu (thực tế có thể đầy phiếu, chỉ là mình
            không được xem, và cũng không được phép gọi API để biết). Ổ khoá + `title` đã
            mang trọn nghĩa; bỏ chip là đúng, không thay bằng số. */}
        <span className="min-w-0 flex-1 truncate text-[12px] font-medium text-slate-500">{label}</span>
      </div>
    )
  }

  return (
    <details
      open={open}
      onToggle={e => setOpen((e.currentTarget as HTMLDetailsElement).open)}
      className="group/stage"
    >
      <summary className="flex cursor-pointer items-center gap-1.5 px-3 py-1.5 hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-brand-500/60 [&::-webkit-details-marker]:hidden">
        <ChevronRight
          aria-hidden
          className="h-3 w-3 shrink-0 text-slate-400 transition-transform group-open/stage:rotate-90 motion-reduce:transition-none"
        />
        <span
          className={cn(
            'inline-flex h-5 w-5 shrink-0 items-center justify-center rounded',
            muted ? 'bg-slate-100 text-slate-400' : style.chip,
          )}
        >
          <Icon aria-hidden className="h-3 w-3" />
        </span>
        {/* Folder rỗng VẪN bấm mở được ⇒ nhãn là chữ ĐANG HOẠT ĐỘNG: slate-400
            (#94a3b8) chỉ 2.8:1 — dưới sàn AA 4.5:1 ⇒ dùng slate-500 (4.8:1).
            Chip "Sắp"/icon giữ slate-400 theo token dải strip (trạng thái trơ). */}
        <span className={cn('min-w-0 flex-1 truncate text-[12px] font-medium', muted ? 'text-slate-500' : style.head)}>
          {label}
        </span>
        {stage.failed ? (
          <span
            className="shrink-0 rounded bg-slate-100 px-1.5 py-0.5 text-[10px] font-medium text-slate-400"
            title="Không tải được dữ liệu giai đoạn này"
          >
            —
          </span>
        ) : (
          /* [S174 owner O2] luôn in SỐ (kể cả 0). Nhánh `—` ở trên GIỮ: đó là ca
             "không tải được dữ-liệu", khác hẳn "có 0 phiếu" — đừng gộp hai thứ. */
          <span className={cn('shrink-0 rounded px-1.5 py-0.5 text-[10px] font-medium', style.count)}>{count}</span>
        )}
      </summary>

      <div className="ml-3 border-l border-slate-200">
        {stage.failed ? (
          <div className="px-3 py-2 text-[11px] text-slate-400">Không tải được dữ liệu giai đoạn này.</div>
        ) : stage.loading ? (
          <div className="space-y-1.5 px-3 py-2">
            <div className="h-3 animate-pulse rounded bg-slate-100 motion-reduce:animate-none" />
            <div className="h-3 w-2/3 animate-pulse rounded bg-slate-100 motion-reduce:animate-none" />
          </div>
        ) : stage.content !== undefined ? (
          stage.content
        ) : stage.groups !== undefined ? (
          // [K5 S166] Có ngăn ⇒ vẽ theo ngăn. Ngăn rỗng KHÔNG bị lọc bỏ ở đây:
          // việc "hiện đủ 8 nhóm hay không" do bên dựng quyết (xem builder), chỗ
          // này chỉ vẽ đúng thứ nhận được.
          <div>
            {stage.groups.map(g => (
              <GroupFolder key={g.key} group={g} style={style} />
            ))}
          </div>
        ) : stage.leaves.length === 0 ? (
          <div className="px-3 py-2 text-[11px] italic text-slate-400">Chưa có dữ liệu ở giai đoạn này.</div>
        ) : (
          <LeafList leaves={stage.leaves} style={style} />
        )}
        {stage.truncated && !stage.failed && !stage.loading && (
          <div className="border-t border-slate-100 px-3 py-1 text-[10px] italic text-slate-400">
            Chỉ mục tải 200 bản ghi mới nhất — số đếm có thể thiếu.
          </div>
        )}
      </div>
    </details>
  )
}

export function PipelineStageFolders({
  stages,
  currentStage,
  hideEmpty = false,
  className,
}: {
  stages: PipelineStage[]
  currentStage: PipelineStageNo
  /** Đang lọc (search / trạng thái) ⇒ chỉ hiện folder CÓ nội dung khớp. */
  hideEmpty?: boolean
  className?: string
}) {
  // [F-6 S162] Đang lọc vẫn PHẢI thấy folder lỗi/đang tải — ẩn dấu "—" đi là
  // nói dối trạng thái; chỉ ẩn folder RỖNG THẬT (ok/locked, count 0).
  const visible = hideEmpty ? stages.filter(s => stageCount(s) > 0 || s.failed || s.loading) : stages
  if (visible.length === 0) return null
  return (
    <div className={className}>
      {visible.map(s => (
        <StageFolder key={s.n} stage={s} currentStage={currentStage} />
      ))}
    </div>
  )
}
