"use client";

import { useCallback, useEffect, useMemo, useState } from "react";
import * as XLSX from "xlsx";
import {
  ArrowDownToLine,
  ArrowUpToLine,
  Building2,
  CheckCircle2,
  ChevronDown,
  ChevronRight,
  ChevronUp,
  ChevronsDownUp,
  ChevronsUpDown,
  Eye,
  EyeOff,
  FileDown,
  Folder,
  FolderOpen,
  FolderTree,
  Globe,
  History,
  Info,
  MoreVertical,
  Play,
  Plus,
  Search,
  Star,
  Trash2,
  Undo2,
  Upload,
  Users,
  X,
  XCircle,
} from "lucide-react";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import { Checkbox } from "@/components/ui/checkbox";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useToast } from "@/hooks/use-toast";
import { useAuth } from "@/hooks/useAuth";
import { cn } from "@/lib/utils";
import * as departmentAPI from "@/lib/api/department";
import { getCompanyList } from "@/lib/api/company";
import { DepartmentPicker } from "@/components/departments/DepartmentPicker";
import type { Department, DepartmentMember } from "@/types/department";
import type { Company } from "@/types/company";

/**
 * dept_info 테이블 스키마에 1:1 매핑되는 Draft (V019 정리 후).
 *
 * 필드 매핑:
 *   dept_code        : 부서코드 (PK)
 *   parent_dept_code : 상위부서코드
 *   dept_name        : 부서명
 *   location         : 위치코드 (UI hide, V2 매핑용 컬럼만 유지)
 *   status           : 사용여부 (active/inactive)
 *   company_name     : 회사명 (조회용)
 *   company_code     : 회사코드
 *   short_name       : 부서약칭
 *   dept_type        : 부서유형 (dept|team|temp)
 *   org_system       : 조직체계
 *   approval_manager : 결재 관리자
 *   dept_manager     : 부서 관리자
 *   zipcode          : 우편번호 (UI hide)
 *   address1         : 주소1 (UI hide)
 *   address2         : 주소2 (UI hide)
 *   start_date       : 시작일 (UI hide)
 *   end_date         : 종료일 (UI hide)
 *   sort_order       : 정렬순서
 */
interface DeptDetailDraft {
  dept_code: string;
  parent_dept_code: string | null;
  dept_name: string;
  location: string;
  status: "active" | "inactive";
  company_code: string;
  short_name: string;
  dept_type: string;
  org_system: string;
  approval_manager: string;
  dept_manager: string;
  zipcode: string;
  address1: string;
  address2: string;
  start_date: string;
  end_date: string;
  sort_order: number;
  // 다중 관리자 (chip UI 용)
  approval_managers: string[];
  dept_managers: string[];
  org_leaders: string[];
}

const emptyDraft = (companyCode = ""): DeptDetailDraft => ({
  dept_code: "",
  parent_dept_code: null,
  dept_name: "",
  location: "",
  status: "active",
  company_code: companyCode,
  short_name: "",
  dept_type: "dept",
  org_system: "",
  approval_manager: "",
  dept_manager: "",
  zipcode: "",
  address1: "",
  address2: "",
  start_date: "",
  end_date: "",
  sort_order: 10,
  approval_managers: [],
  dept_managers: [],
  org_leaders: [],
});

export default function DeptMngListPage() {
  const { toast } = useToast();
  const { user } = useAuth();

  // ── 회사 선택 / 기준일 ────────────────────────────────
  const [companies, setCompanies] = useState<Company[]>([]);
  const [selectedCompanyCode, setSelectedCompanyCode] = useState<string>("");
  const [periodMode, setPeriodMode] = useState<"all" | "date">("date");
  const [baseDate, setBaseDate] = useState<string>(new Date().toISOString().slice(0, 10));
  const [searchKeyword, setSearchKeyword] = useState("");

  // ── 부서 트리 ─────────────────────────────────────────
  const [departments, setDepartments] = useState<Department[]>([]);
  const [expandedSet, setExpandedSet] = useState<Set<string>>(new Set());
  // 회사/사업장 루트 노드 펼침 토글 (1:1 가정 — 단일 노드)
  const [siteOpen, setSiteOpen] = useState(true);
  const [selectedCode, setSelectedCode] = useState<string | null>(null);
  const [isTreeLoading, setIsTreeLoading] = useState(false);
  // V1: 삭제된 부서 표시 토글 (휴지통)
  const [showDeleted, setShowDeleted] = useState(false);

  // ── 상세정보 ─────────────────────────────────────────
  const [draft, setDraft] = useState<DeptDetailDraft>(() => emptyDraft());
  const [originalDraft, setOriginalDraft] = useState<DeptDetailDraft | null>(null);
  const [isNewMode, setIsNewMode] = useState(false);
  const [activeTab, setActiveTab] = useState<"info" | "members">("info");
  const [members, setMembers] = useState<DepartmentMember[]>([]);
  const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
  const [pendingDeleteDept, setPendingDeleteDept] = useState<{ code: string; name: string } | null>(null);

  // ── 일괄등록 / 일괄업데이트 모달 ─────────────────────
  const [bulkOpen, setBulkOpen] = useState(false);
  const [bulkTab, setBulkTab] = useState<"create" | "update">("create");
  const [bulkUpdateMode, setBulkUpdateMode] = useState<"department" | "manager">("department");
  const [bulkRows, setBulkRows] = useState<Record<string, any>[]>([]);
  const [bulkPreviewRows, setBulkPreviewRows] = useState<departmentAPI.BulkPreviewRow[]>([]);
  const [bulkSelected, setBulkSelected] = useState<Set<number>>(new Set());
  const [bulkBusy, setBulkBusy] = useState(false);
  const [bulkFileName, setBulkFileName] = useState<string>("");

  // ── 트리 ⋮ 메뉴: 이동/삭제 대상 ───────────────────────
  const [moveTargetDept, setMoveTargetDept] = useState<Department | null>(null);
  const [contextDeleteDept, setContextDeleteDept] = useState<Department | null>(null);

  const selectedCompany = useMemo(
    () => companies.find((c) => c.company_code === selectedCompanyCode) || null,
    [companies, selectedCompanyCode],
  );

  // 헤더 breadcrumb 용 ancestor 체인 — root 부터 직속 부모까지 (자기 자신 제외)
  // 사이클 데이터에서도 무한 루프 안 걸리게 visited 가드.
  const ancestors = useMemo(() => {
    const list: Department[] = [];
    if (!draft.parent_dept_code) return list;
    const visited = new Set<string>();
    if (draft.dept_code) visited.add(draft.dept_code);
    let cur: string | null = draft.parent_dept_code;
    while (cur && !visited.has(cur)) {
      visited.add(cur);
      const p = departments.find((d) => d.dept_code === cur);
      if (!p) break;
      list.unshift(p);
      cur = p.parent_dept_code ?? null;
    }
    return list;
  }, [draft.parent_dept_code, draft.dept_code, departments]);

  // ── 회사 목록 로드 (SUPER_ADMIN 은 전체, 그 외엔 본인 회사) ──
  useEffect(() => {
    (async () => {
      try {
        const list = await getCompanyList();
        setCompanies(list);
        const userCompany = (user as any)?.company_code;
        if (userCompany && userCompany !== "*") {
          setSelectedCompanyCode(userCompany);
        } else if (list.length > 0) {
          setSelectedCompanyCode(list[0].company_code);
        }
      } catch (err) {
        console.error("회사 목록 로드 실패", err);
      }
    })();
  }, [user]);

  // ── 부서 목록 로드 ───────────────────────────────────
  const loadDepartments = useCallback(async () => {
    if (!selectedCompanyCode) return;
    setIsTreeLoading(true);
    try {
      const res = await departmentAPI.getDepartments(selectedCompanyCode, {
        includeDeleted: showDeleted,
        baseDate: periodMode === "date" ? baseDate : undefined,
      });
      if (res.success && (res as any).data) {
        setDepartments((res as any).data);
      } else {
        setDepartments([]);
      }
    } finally {
      setIsTreeLoading(false);
    }
  }, [selectedCompanyCode, showDeleted, periodMode, baseDate]);

  useEffect(() => {
    loadDepartments();
  }, [loadDepartments]);

  // ── 부서원 로드 ──────────────────────────────────────
  useEffect(() => {
    if (activeTab !== "members" || !selectedCode || isNewMode) {
      setMembers([]);
      return;
    }
    let cancelled = false;
    (async () => {
      const res = await departmentAPI.getDepartmentMembers(selectedCode);
      if (cancelled) return;
      if (res.success && (res as any).data) setMembers((res as any).data);
    })();
    return () => {
      cancelled = true;
    };
  }, [activeTab, selectedCode, isNewMode]);

  // ── 트리 구성 ────────────────────────────────────────
  const filteredDepts = useMemo(() => {
    if (!searchKeyword.trim()) return departments;
    const kw = searchKeyword.toLowerCase();
    // 1차: 직접 매칭된 부서들
    const directMatches = new Set(
      departments
        .filter((d) => d.dept_name?.toLowerCase().includes(kw) || d.dept_code?.toLowerCase().includes(kw))
        .map((d) => d.dept_code),
    );
    // 2차: 매칭된 노드의 모든 조상도 포함 (트리 구조 유지)
    const visible = new Set<string>(directMatches);
    const byCode = new Map(departments.map((d) => [d.dept_code, d]));
    for (const code of directMatches) {
      let cur: string | null | undefined = byCode.get(code)?.parent_dept_code ?? null;
      const visited = new Set<string>([code]);
      while (cur && !visited.has(cur)) {
        visited.add(cur);
        visible.add(cur);
        cur = byCode.get(cur)?.parent_dept_code ?? null;
      }
    }
    return departments.filter((d) => visible.has(d.dept_code));
  }, [departments, searchKeyword]);

  const childrenOf = useCallback(
    (parent: string | null) =>
      filteredDepts
        .filter((d) => (d.parent_dept_code ?? null) === parent)
        .sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0) || (a.dept_name || "").localeCompare(b.dept_name || "")),
    [filteredDepts],
  );

  const expandAll = () => {
    setExpandedSet(new Set(departments.map((d) => d.dept_code)));
  };
  const collapseAll = () => setExpandedSet(new Set());

  const toggleExpand = (code: string) => {
    setExpandedSet((prev) => {
      const next = new Set(prev);
      next.has(code) ? next.delete(code) : next.add(code);
      return next;
    });
  };

  // ── 선택 시 상세정보 채우기 (dept_info 스키마 1:1) ────
  const handleSelectDepartment = (dept: Department) => {
    setSelectedCode(dept.dept_code);
    setIsNewMode(false);
    const d = dept as any;
    const loaded: DeptDetailDraft = {
      ...emptyDraft(selectedCompanyCode),
      dept_code: dept.dept_code,
      parent_dept_code: dept.parent_dept_code ?? null,
      dept_name: dept.dept_name,
      location: d.location ?? "",
      company_code: dept.company_code ?? selectedCompanyCode,
      short_name: dept.short_name ?? "",
      dept_type: dept.dept_type ?? "dept",
      org_system: dept.org_system ?? "",
      approval_manager: dept.approval_manager ?? "",
      dept_manager: dept.dept_manager ?? "",
      zipcode: dept.zipcode ?? "",
      address1: dept.address1 ?? "",
      address2: dept.address2 ?? "",
      start_date: (dept.start_date ?? "").slice(0, 10),
      end_date: (dept.end_date ?? "").slice(0, 10),
      sort_order: dept.sort_order ?? 10,
      status: (dept.status as "active" | "inactive") ?? "active",
      approval_managers: (() => {
        const arr = ((dept as any).approval_managers || []).map((m: any) => m.user_id).filter(Boolean);
        // 신규 매핑이 비어있고 옛날 단일 컬럼에 값 있으면 seed (PR #19 이전 데이터 호환)
        if (arr.length === 0 && dept.approval_manager) return [dept.approval_manager];
        return arr;
      })(),
      dept_managers: (() => {
        const arr = ((dept as any).dept_managers || []).map((m: any) => m.user_id).filter(Boolean);
        if (arr.length === 0 && dept.dept_manager) return [dept.dept_manager];
        return arr;
      })(),
      org_leaders: ((dept as any).org_leaders || []).map((m: any) => m.user_id).filter(Boolean),
    };
    setDraft(loaded);
    setOriginalDraft(loaded);
  };

  const handleAddNew = (parentCode: string | null = null) => {
    setSelectedCode(null);
    setIsNewMode(true);
    setActiveTab("info");
    setDraft({ ...emptyDraft(selectedCompanyCode), parent_dept_code: parentCode });
    setOriginalDraft(null);
  };

  const handleClearDetail = () => {
    setSelectedCode(null);
    setIsNewMode(false);
    setDraft(emptyDraft(selectedCompanyCode));
    setOriginalDraft(null);
  };

  // ── 트리 ⋮ 메뉴 helpers / 핸들러 ───────────────────────

  /** dept 객체를 update payload (snake_case) 로 변환 — 일부 필드만 override 가능 */
  const toUpdatePayload = useCallback((d: any, overrides: Record<string, any> = {}) => ({
    dept_name: d.dept_name,
    parent_dept_code: d.parent_dept_code ?? null,
    short_name: d.short_name ?? "",
    dept_type: d.dept_type ?? "dept",
    org_system: d.org_system || null,
    approval_manager: d.approval_manager ?? "",
    dept_manager: d.dept_manager ?? "",
    zipcode: d.zipcode ?? "",
    address1: d.address1 ?? "",
    address2: d.address2 ?? "",
    start_date: d.start_date ? String(d.start_date).slice(0, 10) : null,
    end_date: d.end_date ? String(d.end_date).slice(0, 10) : null,
    sort_order: d.sort_order ?? 10,
    status: d.status ?? "active",
    location: d.location ?? "",
    // 다중 관리자 보존 (서버 응답 형식 그대로 다시 전달)
    approval_managers: (d.approval_managers || []).map((m: any) => ({ user_id: typeof m === 'string' ? m : m.user_id })),
    dept_managers: (d.dept_managers || []).map((m: any) => ({ user_id: typeof m === 'string' ? m : m.user_id })),
    org_leaders: (d.org_leaders || []).map((m: any) => ({ user_id: typeof m === 'string' ? m : m.user_id })),
    ...overrides,
  }), []);

  /** 자기 자신 + 모든 자손 dept_code 들 — picker 의 excludeCodes 용 */
  const collectAllDescendants = useCallback((rootCode: string): string[] => {
    const result = new Set<string>([rootCode]);
    const dfs = (code: string) => {
      for (const child of departments.filter((d) => d.parent_dept_code === code)) {
        if (!result.has(child.dept_code)) {
          result.add(child.dept_code);
          dfs(child.dept_code);
        }
      }
    };
    dfs(rootCode);
    return [...result];
  }, [departments]);

  /** 트리에서 ⋮ → "다른 부서 아래로 이동" 후 picker 에서 새 부모 확정 */
  const handleConfirmMoveTo = async (newParentCode: string | null) => {
    const src = moveTargetDept;
    if (!src) return;
    if ((src.parent_dept_code ?? null) === newParentCode) {
      setMoveTargetDept(null);
      return;
    }
    try {
      const payload = toUpdatePayload(src, { parent_dept_code: newParentCode });
      const res = await departmentAPI.updateDepartment(src.dept_code, payload);
      if (res.success) {
        toast({ title: `"${src.dept_name}" 부서를 이동했습니다` });
        await loadDepartments();
        if (selectedCode === src.dept_code) {
          const refreshed = (res as any).data as Department | undefined;
          if (refreshed) handleSelectDepartment(refreshed);
        }
      } else {
        toast({ title: "이동 실패", description: (res as any).error, variant: "destructive" });
      }
    } catch (err: any) {
      toast({ title: "이동 오류", description: err?.message, variant: "destructive" });
    } finally {
      setMoveTargetDept(null);
    }
  };

  /**
   * 형제 사이 정렬 변경 — 4가지 동작.
   * "맨 위로 / 한 칸 위 / 한 칸 아래 / 맨 아래로"
   *
   * 동작: siblings 를 새 순서로 재배치 후 sort_order 를 (i+1)*10 으로 normalize.
   * - 매 호출마다 sort_order 가 정상화되어 다음 동작도 정확하게 1칸 이동.
   * - sibling 5~20개 가정: N번 update 호출 OK.
   */
  const handleMove = async (dept: Department, action: "top" | "up" | "down" | "bottom") => {
    const siblings = departments
      .filter((d) => (d.parent_dept_code ?? null) === (dept.parent_dept_code ?? null) && !(d as any).deleted_at)
      .sort(
        (a, b) =>
          (a.sort_order || 0) - (b.sort_order || 0) ||
          (a.dept_name || "").localeCompare(b.dept_name || ""),
      );
    const idx = siblings.findIndex((s) => s.dept_code === dept.dept_code);
    if (idx === -1 || siblings.length < 2) return;

    let newIdx: number;
    switch (action) {
      case "top": newIdx = 0; break;
      case "up": newIdx = Math.max(0, idx - 1); break;
      case "down": newIdx = Math.min(siblings.length - 1, idx + 1); break;
      case "bottom": newIdx = siblings.length - 1; break;
    }
    if (newIdx === idx) return;

    const reordered = [...siblings];
    const [moved] = reordered.splice(idx, 1);
    reordered.splice(newIdx, 0, moved);

    try {
      const results = await Promise.all(
        reordered.map((s, i) =>
          departmentAPI.updateDepartment(
            s.dept_code,
            toUpdatePayload(s, { sort_order: (i + 1) * 10 }),
          ),
        ),
      );
      const failed = results.find((r) => !r.success);
      if (failed) throw new Error((failed as any).error || "정렬 변경 실패");
      await loadDepartments();
    } catch (err: any) {
      toast({ title: "정렬 변경 실패", description: err?.message, variant: "destructive" });
      await loadDepartments(); // 부분 업데이트 가능성 있어 DB 상태로 동기화
    }
  };

  /** 컨텍스트 삭제 확인 */
  const handleConfirmDeleteContext = async () => {
    const d = contextDeleteDept;
    if (!d) return;
    try {
      const res = await departmentAPI.deleteDepartment(d.dept_code);
      if (res.success) {
        const softDeleted = (res as any).data?.soft_deleted;
        toast({
          title: softDeleted ? "부서 삭제됨 (복구 가능)" : "부서가 삭제되었습니다",
          description: softDeleted
            ? `"${d.dept_name}" 부서가 휴지통으로 이동했습니다.`
            : undefined,
        });
        await loadDepartments();
        if (selectedCode === d.dept_code) handleClearDetail();
      } else {
        toast({ title: "삭제 실패", description: (res as any).error, variant: "destructive" });
      }
    } finally {
      setContextDeleteDept(null);
    }
  };

  // ── 저장 ─────────────────────────────────────────────
  const handleSave = async () => {
    if (!draft.dept_name.trim()) {
      toast({ title: "부서명을 입력해주세요", variant: "destructive" });
      return;
    }
    if (!selectedCompanyCode) {
      toast({ title: "회사를 선택해주세요", variant: "destructive" });
      return;
    }
    // 시작일/종료일 정합성 검증
    if (draft.start_date && draft.end_date && draft.start_date > draft.end_date) {
      toast({
        title: "시작일은 종료일보다 빠르거나 같아야 합니다",
        variant: "destructive",
      });
      return;
    }

    // 기본정보 탭 전체 필드를 payload 로 전달 — dept_info 스키마와 1:1 (V019 정리 후)
    const payload = {
      dept_name: draft.dept_name,
      parent_dept_code: draft.parent_dept_code,
      short_name: draft.short_name,
      dept_type: draft.dept_type,
      org_system: draft.org_system || null,
      approval_manager: draft.approval_manager,
      dept_manager: draft.dept_manager,
      zipcode: draft.zipcode,
      address1: draft.address1,
      address2: draft.address2,
      start_date: draft.start_date || null,
      end_date: draft.end_date || null,
      sort_order: draft.sort_order,
      status: draft.status,
      // dept_info 추가 필드 (location 코드만 유지)
      location: draft.location,
      // 다중 관리자 — backend 가 {user_id} 객체 배열 받음
      approval_managers: draft.approval_managers.map((uid) => ({ user_id: uid })),
      dept_managers: draft.dept_managers.map((uid) => ({ user_id: uid })),
      org_leaders: draft.org_leaders.map((uid) => ({ user_id: uid })),
    } as any;

    try {
      if (isNewMode) {
        const res = await departmentAPI.createDepartment(selectedCompanyCode, payload);
        if (res.success) {
          toast({ title: "부서가 생성되었습니다" });
          await loadDepartments();
          const created = (res as any).data as Department | undefined;
          if (created) handleSelectDepartment(created);
          else handleClearDetail();
        } else {
          toast({ title: "생성 실패", description: (res as any).error, variant: "destructive" });
        }
      } else if (selectedCode) {
        const res = await departmentAPI.updateDepartment(selectedCode, payload);
        if (res.success) {
          toast({ title: "부서가 수정되었습니다" });
          await loadDepartments();
          setOriginalDraft(draft);
        } else {
          toast({ title: "수정 실패", description: (res as any).error, variant: "destructive" });
        }
      }
    } catch (err: any) {
      toast({ title: "오류", description: err?.message, variant: "destructive" });
    }
  };

  // ── 삭제 ─────────────────────────────────────────────
  const handleDelete = async () => {
    const target = pendingDeleteDept;
    if (!target) return;
    try {
      const res = await departmentAPI.deleteDepartment(target.code);
      if (res.success) {
        const softDeleted = (res as any).data?.soft_deleted;
        toast({
          title: softDeleted ? "부서 삭제됨 (복구 가능)" : "부서가 삭제되었습니다",
          description: softDeleted
            ? `"${target.name}" 부서가 휴지통으로 이동했습니다. 상단 '삭제 보기' 토글에서 복구할 수 있습니다.`
            : undefined,
        });
        await loadDepartments();
        handleClearDetail();
      } else {
        toast({ title: "삭제 실패", description: (res as any).error, variant: "destructive" });
      }
    } finally {
      setDeleteConfirmOpen(false);
      setPendingDeleteDept(null);
    }
  };

  // ── 복구 (V1) ────────────────────────────────────────
  const handleRestoreDepartment = async (deptCode: string, deptName: string) => {
    try {
      const response = await departmentAPI.restoreDepartment(deptCode);
      if (response.success) {
        await loadDepartments();
        toast({
          title: "부서 복구 완료",
          description: `"${deptName}" 부서가 복구되었습니다.`,
          variant: "default",
        });
      } else {
        toast({
          title: "복구 불가",
          description: (response as any).error || "부서 복구에 실패했습니다.",
          variant: "destructive",
        });
      }
    } catch (error) {
      console.error("부서 복구 실패:", error);
      toast({
        title: "부서 복구 실패",
        description: "복구 중 오류가 발생했습니다.",
        variant: "destructive",
      });
    }
  };

  // ─────────────────────────────────────────────────────
  // 일괄등록 / 일괄업데이트 helpers
  // ─────────────────────────────────────────────────────

  const BULK_HEADERS_CREATE: Record<string, string> = {
    "부서명": "dept_name",
    "상위부서코드": "parent_dept_code",
    "부서유형": "dept_type",
    "약칭": "short_name",
    "조직체계": "org_system",
    "정렬순서": "sort_order",
    "사용여부": "status",
    "시작일": "start_date",
    "종료일": "end_date",
    "결재관리자": "approval_managers",
    "부서관리자": "dept_managers",
    "조직장": "org_leaders",
  };
  const BULK_HEADERS_UPDATE_DEPT: Record<string, string> = {
    "부서코드": "dept_code",
    "부서명": "dept_name",
    "상위부서코드": "parent_dept_code",
    "부서유형": "dept_type",
    "약칭": "short_name",
    "조직체계": "org_system",
    "정렬순서": "sort_order",
    "사용여부": "status",
    "시작일": "start_date",
    "종료일": "end_date",
  };
  const BULK_HEADERS_UPDATE_MGR: Record<string, string> = {
    "부서코드": "dept_code",
    "결재관리자": "approval_managers",
    "부서관리자": "dept_managers",
    "조직장": "org_leaders",
  };
  const MANAGER_KEYS = new Set(["approval_managers", "dept_managers", "org_leaders"]);

  const currentHeaderMap = () =>
    bulkTab === "create"
      ? BULK_HEADERS_CREATE
      : bulkUpdateMode === "department"
      ? BULK_HEADERS_UPDATE_DEPT
      : BULK_HEADERS_UPDATE_MGR;

  const currentBulkAction = (): departmentAPI.BulkAction =>
    bulkTab === "create"
      ? "create"
      : bulkUpdateMode === "department"
      ? "update_department"
      : "update_manager";

  const resetBulkData = useCallback(() => {
    setBulkRows([]);
    setBulkPreviewRows([]);
    setBulkSelected(new Set());
    setBulkFileName("");
  }, []);

  const openBulkModal = () => {
    if (!selectedCompanyCode) {
      toast({ title: "회사를 먼저 선택하세요", variant: "destructive" });
      return;
    }
    setBulkTab("create");
    setBulkUpdateMode("department");
    resetBulkData();
    setBulkOpen(true);
  };

  /** 엑셀 템플릿 다운로드 — action 별 컬럼 다름. 예시 row 1개 포함 */
  const downloadBulkTemplate = () => {
    const action = currentBulkAction();
    const headerMap =
      action === "create"
        ? BULK_HEADERS_CREATE
        : action === "update_department"
        ? BULK_HEADERS_UPDATE_DEPT
        : BULK_HEADERS_UPDATE_MGR;
    const columns = Object.keys(headerMap);
    const example: Record<string, any> = {};
    columns.forEach((c) => {
      const snake = headerMap[c];
      if (snake === "dept_name") example[c] = "경영지원본부";
      else if (snake === "dept_code") example[c] = "DEPT_1";
      else if (snake === "dept_type") example[c] = "dept";
      else if (snake === "status") example[c] = "active";
      else if (snake === "sort_order") example[c] = 10;
      else if (MANAGER_KEYS.has(snake)) example[c] = action === "update_manager" ? "user001,user002" : "";
      else example[c] = "";
    });
    const ws = XLSX.utils.json_to_sheet([example], { header: columns });
    ws["!cols"] = columns.map(() => ({ wch: 16 }));
    const wb = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(wb, ws, "부서");
    const fileName =
      action === "create"
        ? "부서_일괄등록_템플릿.xlsx"
        : action === "update_department"
        ? "부서정보_일괄업데이트_템플릿.xlsx"
        : "부서관리자_일괄업데이트_템플릿.xlsx";
    XLSX.writeFile(wb, fileName);
  };

  /** 업로드된 xlsx → 한글 헤더를 snake_case 로 매핑 + 매니저 필드는 CSV 분해 */
  const handleBulkFile = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;
    setBulkFileName(file.name);
    try {
      const buf = await file.arrayBuffer();
      const wb = XLSX.read(buf, { type: "array" });
      const ws = wb.Sheets[wb.SheetNames[0]];
      const raw = XLSX.utils.sheet_to_json<Record<string, any>>(ws, { defval: "" });
      const headerMap = currentHeaderMap();
      const rows = raw
        .map((row) => {
          const out: Record<string, any> = {};
          for (const [korean, snake] of Object.entries(headerMap)) {
            const v = row[korean];
            if (v === undefined || v === null || v === "") continue;
            if (MANAGER_KEYS.has(snake)) {
              const ids = String(v).split(/[,;]/).map((s) => s.trim()).filter(Boolean);
              if (ids.length > 0) out[snake] = ids;
            } else if (snake === "sort_order") {
              const n = Number(v);
              if (!Number.isNaN(n)) out[snake] = n;
            } else {
              out[snake] = String(v).trim();
            }
          }
          return out;
        })
        .filter((r) => Object.keys(r).length > 0);
      setBulkRows(rows);
      setBulkPreviewRows([]);
      setBulkSelected(new Set());
      toast({ title: `${rows.length}건 로드됨`, description: "[미리보기] 를 눌러 검증하세요." });
    } catch (err: any) {
      toast({ title: "파일 읽기 실패", description: err.message || String(err), variant: "destructive" });
    } finally {
      // 동일 파일 재선택 가능하도록
      e.target.value = "";
    }
  };

  const handleBulkPreview = async () => {
    if (bulkRows.length === 0) return;
    setBulkBusy(true);
    try {
      const res = await departmentAPI.bulkPreviewDepartments(selectedCompanyCode, currentBulkAction(), bulkRows);
      if (res.success && (res as any).data) {
        const rows: departmentAPI.BulkPreviewRow[] = (res as any).data.rows;
        setBulkPreviewRows(rows);
        // 기본: ok 인 row 만 선택
        setBulkSelected(new Set(rows.filter((r) => r.result === "ok").map((r) => r.row_index)));
      } else {
        toast({
          title: "미리보기 실패",
          description: (res as any).error || (res as any).message || "오류",
          variant: "destructive",
        });
      }
    } finally {
      setBulkBusy(false);
    }
  };

  const handleBulkApply = async () => {
    const okSelected = bulkPreviewRows.filter(
      (r) => bulkSelected.has(r.row_index) && r.result === "ok",
    );
    if (okSelected.length === 0) {
      toast({ title: "반영할 정상 행이 없습니다", variant: "destructive" });
      return;
    }
    const payload = okSelected.map((r) => {
      const { row_index, result, error_detail, ...rest } = r as any;
      return rest as Record<string, any>;
    });
    setBulkBusy(true);
    try {
      const res =
        bulkTab === "create"
          ? await departmentAPI.bulkCreateDepartments(selectedCompanyCode, payload)
          : await departmentAPI.bulkUpdateDepartments(selectedCompanyCode, bulkUpdateMode, payload);
      if (res.success) {
        const count =
          (res as any).data?.inserted ?? (res as any).data?.updated ?? payload.length;
        toast({
          title: bulkTab === "create" ? "일괄등록 완료" : "일괄업데이트 완료",
          description: `${count}건 처리됨`,
        });
        setBulkOpen(false);
        resetBulkData();
        await loadDepartments();
      } else {
        toast({
          title: bulkTab === "create" ? "일괄등록 실패" : "일괄업데이트 실패",
          description: (res as any).error || (res as any).message || "오류",
          variant: "destructive",
        });
      }
    } finally {
      setBulkBusy(false);
    }
  };

  const previewColumns = useMemo(() => {
    if (bulkTab === "create") {
      return [
        { key: "dept_name", label: "부서명" },
        { key: "parent_dept_code", label: "상위부서코드" },
        { key: "dept_type", label: "유형" },
        { key: "sort_order", label: "순서" },
        { key: "approval_managers", label: "결재관리자", manager: true },
        { key: "dept_managers", label: "부서관리자", manager: true },
        { key: "org_leaders", label: "조직장", manager: true },
      ];
    }
    if (bulkUpdateMode === "department") {
      return [
        { key: "dept_code", label: "부서코드" },
        { key: "dept_name", label: "부서명" },
        { key: "parent_dept_code", label: "상위부서코드" },
        { key: "dept_type", label: "유형" },
        { key: "sort_order", label: "순서" },
      ];
    }
    return [
      { key: "dept_code", label: "부서코드" },
      { key: "approval_managers", label: "결재관리자", manager: true },
      { key: "dept_managers", label: "부서관리자", manager: true },
      { key: "org_leaders", label: "조직장", manager: true },
    ];
  }, [bulkTab, bulkUpdateMode]);

  const bulkOkCount = bulkPreviewRows.filter((r) => r.result === "ok").length;
  const bulkErrCount = bulkPreviewRows.length - bulkOkCount;
  const allOkSelected =
    bulkOkCount > 0 &&
    bulkPreviewRows
      .filter((r) => r.result === "ok")
      .every((r) => bulkSelected.has(r.row_index));

  const isDirty = originalDraft
    ? JSON.stringify(originalDraft) !== JSON.stringify(draft)
    : isNewMode && (draft.dept_name.trim() !== "" || draft.parent_dept_code !== null);

  // ─────────────────────────────────────────────────────
  // 렌더
  // ─────────────────────────────────────────────────────
  return (
    <div className="flex h-full min-h-0 w-full flex-col bg-background text-sm">
      {/* 상단 타이틀 바 */}
      <div className="flex items-center justify-between border-b px-5 py-3">
        <div className="flex items-center gap-2">
          <h1 className="text-xl font-bold tracking-tight">부서관리</h1>
          <Button size="icon" variant="ghost" className="h-7 w-7 text-sky-600">
            <Info className="h-4 w-4" />
          </Button>
          <Button size="icon" variant="ghost" className="h-7 w-7 text-sky-600">
            <Play className="h-4 w-4" />
          </Button>
        </div>
        <div className="flex items-center gap-2">
          <Button
            variant="outline"
            size="sm"
            className="h-8 gap-1.5 text-xs"
            onClick={openBulkModal}
          >
            <Upload className="h-3.5 w-3.5" />
            일괄등록
          </Button>
          <Button variant="outline" size="sm" className="h-8 gap-1.5 text-xs" onClick={() => handleAddNew(null)}>
            <Plus className="h-3.5 w-3.5" />
            추가
          </Button>
          <Button
            variant="outline"
            size="sm"
            className="h-8 gap-1.5 text-xs opacity-60"
            disabled
            title="변경이력 기능은 준비 중입니다"
          >
            <History className="h-3.5 w-3.5" />
            변경이력
          </Button>
          <Button variant="ghost" size="icon" className="h-8 w-8">
            <Star className="h-4 w-4" />
          </Button>
        </div>
      </div>

      {/* 안내 배너 */}
      <div className="flex items-center gap-2 border-b bg-sky-50/60 px-5 py-2 text-xs text-sky-800 dark:bg-sky-950/30 dark:text-sky-200">
        <Info className="h-3.5 w-3.5 shrink-0" />
        <span>회사별 조직도(부서)를 등록할 수 있으며, '부서/팀/임시' 유형을 선택하여 등록할 수 있습니다.</span>
        <Button variant="ghost" size="icon" className="ml-auto h-5 w-5">
          <ChevronDown className="h-3 w-3" />
        </Button>
      </div>

      {/* 본문 */}
      <div className="flex min-h-0 flex-1">
        {/* 좌측 트리 패널 */}
        <aside className="flex w-[340px] shrink-0 flex-col border-r">
          {/* 기준일 / 회사 / 검색 */}
          <div className="space-y-3 border-b p-3">
            <div className="flex items-center gap-3">
              <Label className="w-[60px] shrink-0 text-xs font-semibold">사용기간</Label>
              <RadioGroup
                value={periodMode}
                onValueChange={(v) => setPeriodMode(v as "all" | "date")}
                className="flex items-center gap-3"
              >
                <div className="flex items-center gap-1">
                  <RadioGroupItem value="all" id="period-all" className="h-3.5 w-3.5" />
                  <Label htmlFor="period-all" className="text-xs">전체</Label>
                </div>
                <div className="flex items-center gap-1">
                  <RadioGroupItem value="date" id="period-date" className="h-3.5 w-3.5" />
                  <Label htmlFor="period-date" className="text-xs">기준일</Label>
                </div>
              </RadioGroup>
              <Input
                type="date"
                value={baseDate}
                onChange={(e) => setBaseDate(e.target.value)}
                disabled={periodMode !== "date"}
                className="h-7 flex-1 text-xs"
              />
            </div>

            <Select
              value={selectedCompanyCode}
              onValueChange={(v) => {
                setSelectedCompanyCode(v);
                setSelectedCode(null);
                setIsNewMode(false);
                setDraft(emptyDraft(v));
                setOriginalDraft(null);
              }}
            >
              <SelectTrigger className="h-8 text-xs">
                <SelectValue placeholder="회사 선택" />
              </SelectTrigger>
              <SelectContent>
                {companies.map((c) => (
                  <SelectItem key={c.company_code} value={c.company_code}>
                    {c.company_code}. {c.company_name}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>

            <div className="relative">
              <Search className="absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" />
              <Input
                value={searchKeyword}
                onChange={(e) => setSearchKeyword(e.target.value)}
                placeholder="코드/사업장/부서명을 입력하세요."
                className="h-8 pl-7 text-xs"
              />
            </div>

            <div className="flex items-center justify-start gap-0.5">
              <Button
                variant="ghost"
                size="icon"
                className="h-7 w-7 text-muted-foreground hover:text-foreground"
                onClick={expandedSet.size > 0 ? collapseAll : expandAll}
                title={expandedSet.size > 0 ? "전체 접기" : "전체 펼치기"}
              >
                {expandedSet.size > 0 ? (
                  <ChevronsDownUp className="h-3.5 w-3.5" />
                ) : (
                  <ChevronsUpDown className="h-3.5 w-3.5" />
                )}
              </Button>
              <Button
                variant="ghost"
                size="icon"
                className={cn(
                  "h-7 w-7",
                  showDeleted ? "text-sky-600 hover:text-sky-700" : "text-muted-foreground hover:text-foreground",
                )}
                onClick={() => setShowDeleted((v) => !v)}
                title={showDeleted ? "삭제된 부서 숨기기" : "삭제된 부서 보기"}
              >
                {showDeleted ? <EyeOff className="h-3.5 w-3.5" /> : <Eye className="h-3.5 w-3.5" />}
              </Button>
            </div>
          </div>

          {/* 트리 */}
          <div className="flex-1 overflow-auto p-2">
            {isTreeLoading ? (
              <div className="py-6 text-center text-xs text-muted-foreground">로딩 중...</div>
            ) : !selectedCompany ? (
              <div className="py-6 text-center text-xs text-muted-foreground">회사를 선택하세요</div>
            ) : (
              <>
                {/* 회사/사업장 루트 (1:1 가정으로 단일 표시) — 토글 가능 */}
                <button
                  type="button"
                  onClick={() => setSiteOpen((v) => !v)}
                  className="flex w-full items-center gap-1.5 rounded px-1.5 py-1 text-left text-xs font-bold text-sky-700 hover:bg-sky-50 dark:text-sky-300 dark:hover:bg-sky-900/20"
                >
                  {siteOpen ? <ChevronDown className="h-3.5 w-3.5" /> : <ChevronRight className="h-3.5 w-3.5" />}
                  <Building2 className="h-3.5 w-3.5" />
                  <span className="truncate">{selectedCompany.company_code}. {selectedCompany.company_name}</span>
                </button>

                {siteOpen && (
                  <div className="ml-3">
                    <DeptTree
                      items={childrenOf(null)}
                      allDepts={filteredDepts}
                      expanded={expandedSet}
                      selectedCode={selectedCode}
                      handlers={{
                        onToggle: toggleExpand,
                        onSelect: handleSelectDepartment,
                        onRestore: handleRestoreDepartment,
                        onAddSub: (parent) => handleAddNew(parent.dept_code),
                        onMoveTo: (d) => setMoveTargetDept(d),
                        onMoveTop: (d) => handleMove(d, "top"),
                        onMoveUp: (d) => handleMove(d, "up"),
                        onMoveDown: (d) => handleMove(d, "down"),
                        onMoveBottom: (d) => handleMove(d, "bottom"),
                        onContextDelete: (d) => setContextDeleteDept(d),
                      }}
                    />
                    {childrenOf(null).length === 0 && (
                      <div className="px-2 py-3 text-xs text-muted-foreground">등록된 부서가 없습니다.</div>
                    )}
                  </div>
                )}
              </>
            )}
          </div>
        </aside>

        {/* 우측 상세 패널 */}
        <section className="flex min-w-0 flex-1 flex-col">
          {/* 상세 헤더 */}
          <div className="flex items-center justify-between border-b px-5 py-2.5">
            <div className="flex min-w-0 items-center gap-2 text-sm font-semibold">
              <span className="inline-block h-1.5 w-1.5 shrink-0 rounded-full bg-sky-500" />
              <span className="shrink-0">상세정보</span>
              {isNewMode ? (
                <span className="ml-1 rounded bg-sky-100 px-1.5 py-0.5 text-xs font-medium text-sky-700 dark:bg-sky-900/40 dark:text-sky-300">
                  신규 부서
                </span>
              ) : selectedCode ? (
                <span className="ml-1 flex min-w-0 items-center gap-1 text-foreground">
                  {ancestors.map((a) => (
                    <span key={a.dept_code} className="flex shrink-0 items-center gap-1 text-xs font-normal text-muted-foreground">
                      <span className="max-w-[120px] truncate">{a.dept_name}</span>
                      <ChevronRight className="h-3 w-3 opacity-60" />
                    </span>
                  ))}
                  <span className="truncate">{draft.dept_name || "(이름 없음)"}</span>
                  <span className="shrink-0 rounded bg-muted px-1.5 py-0.5 font-mono text-[10px] font-medium text-muted-foreground">
                    {selectedCode}
                  </span>
                </span>
              ) : (
                <span className="ml-1 text-xs font-normal text-muted-foreground">
                  좌측 트리에서 부서를 선택하거나 추가하세요
                </span>
              )}
            </div>
            {(selectedCode || isNewMode) && (
              <div className="flex items-center gap-1.5">
                <Button
                  size="sm"
                  className="h-7 bg-sky-500 text-xs text-white hover:bg-sky-600"
                  onClick={handleSave}
                >
                  저장
                </Button>
                <Button
                  size="sm"
                  variant="outline"
                  className="h-7 text-xs"
                  onClick={() => {
                    if (selectedCode) {
                      setPendingDeleteDept({ code: selectedCode, name: draft.dept_name });
                      setDeleteConfirmOpen(true);
                    }
                  }}
                  disabled={isNewMode}
                >
                  삭제
                </Button>
                <Button
                  size="icon"
                  variant="ghost"
                  className="h-7 w-7"
                  onClick={() => {
                    if (isDirty && !window.confirm("저장되지 않은 변경사항이 있습니다. 폐기하시겠습니까?")) return;
                    handleClearDetail();
                  }}
                  title="상세 닫기"
                >
                  <X className="h-4 w-4" />
                </Button>
              </div>
            )}
          </div>

          {(selectedCode || isNewMode) ? (
            <>
              {/* 탭 */}
              <div className="flex items-center gap-5 border-b px-5">
                <button
                  className={cn(
                    "relative py-2.5 text-sm font-medium transition-colors",
                    activeTab === "info" ? "text-sky-600 dark:text-sky-400" : "text-muted-foreground hover:text-foreground",
                  )}
                  onClick={() => setActiveTab("info")}
                >
                  기본정보
                  {activeTab === "info" && <span className="absolute inset-x-0 -bottom-px h-0.5 bg-sky-500" />}
                </button>
                <button
                  className={cn(
                    "relative py-2.5 text-sm font-medium transition-colors",
                    activeTab === "members" ? "text-sky-600 dark:text-sky-400" : "text-muted-foreground hover:text-foreground",
                  )}
                  onClick={() => setActiveTab("members")}
                  disabled={isNewMode}
                >
                  부서원 정보
                  {activeTab === "members" && <span className="absolute inset-x-0 -bottom-px h-0.5 bg-sky-500" />}
                </button>
              </div>

              {/* 탭 바디 */}
              <div
                key={isNewMode ? "__new__" : selectedCode ?? "__empty__"}
                className="min-h-0 flex-1 animate-in fade-in slide-in-from-bottom-1 overflow-auto p-6 duration-200"
              >
                {activeTab === "info" ? (
                  <BasicInfoForm
                    draft={draft}
                    setDraft={setDraft}
                    companyLabel={
                      selectedCompany ? `${selectedCompany.company_code}. ${selectedCompany.company_name}` : ""
                    }
                  />
                ) : (
                  <MembersPanel
                    members={members}
                    deptCode={selectedCode!}
                    companyCode={selectedCompany?.company_code ?? ""}
                    onChanged={async () => {
                      if (selectedCode) {
                        const res = await departmentAPI.getDepartmentMembers(selectedCode);
                        if (res.success && (res as any).data) setMembers((res as any).data);
                      }
                      await loadDepartments();
                    }}
                  />
                )}
              </div>
            </>
          ) : (
            <div className="flex flex-1 items-center justify-center p-8">
              <div className="flex flex-col items-center gap-3 text-center">
                <Building2 className="h-12 w-12 text-muted-foreground/30" />
                <div className="text-sm text-muted-foreground">
                  좌측 트리에서 부서를 선택하거나
                  <br />
                  상단의 <span className="font-semibold text-foreground">+ 추가</span> 버튼으로 새 부서를 만드세요
                </div>
              </div>
            </div>
          )}
        </section>
      </div>

      {/* 삭제 확인 */}
      <Dialog
        open={deleteConfirmOpen}
        onOpenChange={(o) => {
          setDeleteConfirmOpen(o);
          if (!o) setPendingDeleteDept(null);
        }}
      >
        <DialogContent className="max-w-[420px]">
          <DialogHeader>
            <DialogTitle>부서 삭제</DialogTitle>
          </DialogHeader>
          <p className="text-sm">
            <span className="font-semibold">{pendingDeleteDept?.name ?? draft.dept_name}</span> 부서를 삭제하시겠습니까?
          </p>
          <p className="text-xs text-muted-foreground">부서원은 보존됩니다. 휴지통(상단 '삭제 보기' 토글)에서 복구할 수 있습니다.</p>
          <DialogFooter>
            <Button variant="outline" onClick={() => { setDeleteConfirmOpen(false); setPendingDeleteDept(null); }}>취소</Button>
            <Button variant="destructive" onClick={handleDelete}>삭제</Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* 트리 ⋮ 메뉴 — 컨텍스트 삭제 확인 */}
      <Dialog open={!!contextDeleteDept} onOpenChange={(o) => !o && setContextDeleteDept(null)}>
        <DialogContent className="max-w-[420px]">
          <DialogHeader>
            <DialogTitle>부서 삭제</DialogTitle>
          </DialogHeader>
          <p className="text-sm">
            <span className="font-semibold">{contextDeleteDept?.dept_name}</span> 부서를 삭제하시겠습니까?
          </p>
          <p className="text-xs text-muted-foreground">부서원은 보존됩니다. 휴지통(상단 '삭제 보기' 토글)에서 복구할 수 있습니다.</p>
          <DialogFooter>
            <Button variant="outline" onClick={() => setContextDeleteDept(null)}>취소</Button>
            <Button variant="destructive" onClick={handleConfirmDeleteContext}>삭제</Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

      {/* 트리 ⋮ 메뉴 — 다른 부서 아래로 이동 picker */}
      <DepartmentPicker
        companyCode={moveTargetDept?.company_code ?? selectedCompanyCode}
        mode="single"
        allowRoot
        value={moveTargetDept?.parent_dept_code || ""}
        open={!!moveTargetDept}
        onSelect={(code) =>
          handleConfirmMoveTo(typeof code === "string" && code ? code : null)
        }
        onClose={() => setMoveTargetDept(null)}
        excludeCodes={moveTargetDept ? collectAllDescendants(moveTargetDept.dept_code) : []}
        title={moveTargetDept ? `"${moveTargetDept.dept_name}" — 새 상위 부서 선택` : "부서 선택"}
      />

      {/* 일괄등록 / 일괄업데이트 */}
      <Dialog open={bulkOpen} onOpenChange={setBulkOpen}>
        <DialogContent className="flex max-h-[88vh] max-w-[1040px] flex-col">
          <DialogHeader>
            <DialogTitle>부서 일괄등록 / 일괄업데이트</DialogTitle>
          </DialogHeader>

          <Tabs
            value={bulkTab}
            onValueChange={(v) => {
              setBulkTab(v as "create" | "update");
              resetBulkData();
            }}
            className="flex min-h-0 flex-1 flex-col"
          >
            <TabsList className="mb-2">
              <TabsTrigger value="create">일괄등록</TabsTrigger>
              <TabsTrigger value="update">일괄업데이트</TabsTrigger>
            </TabsList>

            <TabsContent value="create" className="m-0 space-y-2">
              <div className="rounded-md border bg-muted/30 p-3 text-xs leading-relaxed">
                <p className="mb-1 font-semibold">신규 조직도를 일괄 등록합니다</p>
                <ul className="list-inside list-disc space-y-0.5 text-muted-foreground">
                  <li>[엑셀 템플릿] 을 다운로드해 작성 후 업로드하세요.</li>
                  <li>업로드 → [미리보기] 로 검증 → 정상 행만 선택해 [반영].</li>
                  <li>부서코드는 저장 시 자동 부여됩니다 (DEPT_n).</li>
                  <li>관리자 컬럼은 user_id 를 쉼표 (,) 로 구분해 입력하세요. 최대 10명/role.</li>
                  <li>한 번에 최대 1000건까지 처리 가능.</li>
                </ul>
              </div>
            </TabsContent>

            <TabsContent value="update" className="m-0 space-y-2">
              <div className="rounded-md border bg-muted/30 p-3 text-xs leading-relaxed">
                <p className="mb-1 font-semibold">기존 부서 정보 또는 관리자 매핑을 일괄 수정합니다</p>
                <ul className="list-inside list-disc space-y-0.5 text-muted-foreground">
                  <li>각 행에 <code className="rounded bg-background px-1 font-mono">부서코드(dept_code)</code> 필수.</li>
                  <li><b>부서 정보</b>: 부서명/유형/순서/날짜 등 변경. 빈 셀은 기존값 유지.</li>
                  <li><b>관리자 정보</b>: 결재/부서/조직장 매핑만 변경. 명시된 role 만 동기화.</li>
                </ul>
              </div>
              <div className="flex items-center gap-3 px-1">
                <Label className="text-xs font-semibold">업로드 항목 선택</Label>
                <RadioGroup
                  value={bulkUpdateMode}
                  onValueChange={(v) => {
                    setBulkUpdateMode(v as "department" | "manager");
                    resetBulkData();
                  }}
                  className="flex items-center gap-4"
                >
                  <div className="flex items-center gap-1.5">
                    <RadioGroupItem value="department" id="bulk-mode-dept" className="h-3.5 w-3.5" />
                    <Label htmlFor="bulk-mode-dept" className="cursor-pointer text-xs">부서 정보</Label>
                  </div>
                  <div className="flex items-center gap-1.5">
                    <RadioGroupItem value="manager" id="bulk-mode-mgr" className="h-3.5 w-3.5" />
                    <Label htmlFor="bulk-mode-mgr" className="cursor-pointer text-xs">관리자 정보</Label>
                  </div>
                </RadioGroup>
              </div>
            </TabsContent>

            {/* 회사 + 파일 선택 (탭 공통) */}
            <div className="mt-2 space-y-2 rounded-md border p-3">
              <div className="grid grid-cols-[100px_1fr_auto] items-center gap-3">
                <Label className="text-xs font-semibold">회사 선택</Label>
                <div className="text-xs">
                  <span className="font-mono">{selectedCompanyCode}</span>
                  {selectedCompany?.company_name && (
                    <span className="ml-2 text-muted-foreground">{selectedCompany.company_name}</span>
                  )}
                </div>
                <Button variant="outline" size="sm" className="h-7 gap-1.5 text-xs" onClick={downloadBulkTemplate}>
                  <FileDown className="h-3.5 w-3.5" />
                  엑셀 템플릿
                </Button>
              </div>
              <div className="grid grid-cols-[100px_1fr] items-center gap-3">
                <Label className="text-xs font-semibold">파일 선택</Label>
                <div className="flex items-center gap-2">
                  <Input
                    type="file"
                    accept=".xlsx,.xls,.csv"
                    onChange={handleBulkFile}
                    className="h-8 cursor-pointer text-xs file:mr-2 file:rounded file:border-0 file:bg-muted file:px-2 file:py-1 file:text-xs"
                  />
                  {bulkFileName && (
                    <span className="shrink-0 text-xs text-muted-foreground">
                      {bulkRows.length}건 로드됨
                    </span>
                  )}
                </div>
              </div>
            </div>

            {/* 미리보기 테이블 */}
            <div className="mt-3 flex min-h-0 flex-1 flex-col overflow-hidden rounded-md border">
              <div className="flex items-center justify-between bg-muted/40 px-3 py-1.5 text-xs">
                <span className="font-semibold">
                  반영할 부서 목록 ({bulkSelected.size}/{bulkPreviewRows.length})
                </span>
                {bulkPreviewRows.length > 0 && (
                  <span className="text-muted-foreground">
                    <span className="text-emerald-600 dark:text-emerald-400">정상 {bulkOkCount}건</span>
                    {" / "}
                    <span className="text-destructive">오류 {bulkErrCount}건</span>
                  </span>
                )}
              </div>
              <div className="min-h-[200px] flex-1 overflow-auto">
                {bulkPreviewRows.length === 0 ? (
                  <div className="flex h-full min-h-[200px] flex-col items-center justify-center gap-2 text-xs text-muted-foreground">
                    <FileDown className="h-6 w-6 opacity-30" />
                    <p>{bulkRows.length === 0 ? "엑셀 파일을 업로드하세요" : "[미리보기] 버튼을 눌러 검증하세요"}</p>
                  </div>
                ) : (
                  <table className="w-full text-xs">
                    <thead className="sticky top-0 z-10 bg-muted/60">
                      <tr>
                        <th className="w-9 px-2 py-1.5">
                          <Checkbox
                            checked={allOkSelected}
                            onCheckedChange={(c) => {
                              if (c) {
                                setBulkSelected(
                                  new Set(
                                    bulkPreviewRows.filter((r) => r.result === "ok").map((r) => r.row_index),
                                  ),
                                );
                              } else {
                                setBulkSelected(new Set());
                              }
                            }}
                          />
                        </th>
                        {previewColumns.map((c) => (
                          <th key={c.key} className="px-2 py-1.5 text-left font-semibold">{c.label}</th>
                        ))}
                        <th className="w-16 px-2 py-1.5 text-left font-semibold">결과</th>
                        <th className="px-2 py-1.5 text-left font-semibold">오류상세</th>
                      </tr>
                    </thead>
                    <tbody className="divide-y">
                      {bulkPreviewRows.map((r) => {
                        const isErr = r.result === "error";
                        return (
                          <tr
                            key={r.row_index}
                            className={cn("hover:bg-muted/30", isErr && "bg-destructive/5")}
                          >
                            <td className="px-2 py-1.5">
                              <Checkbox
                                disabled={isErr}
                                checked={bulkSelected.has(r.row_index)}
                                onCheckedChange={(c) => {
                                  setBulkSelected((prev) => {
                                    const next = new Set(prev);
                                    if (c) next.add(r.row_index);
                                    else next.delete(r.row_index);
                                    return next;
                                  });
                                }}
                              />
                            </td>
                            {previewColumns.map((c) => {
                              const v = (r as any)[c.key];
                              const display = (c as any).manager
                                ? Array.isArray(v) && v.length > 0 ? v.join(", ") : "-"
                                : v != null && v !== "" ? String(v) : "-";
                              return (
                                <td key={c.key} className="max-w-[180px] truncate px-2 py-1.5" title={display}>
                                  {display}
                                </td>
                              );
                            })}
                            <td className="px-2 py-1.5">
                              {isErr ? (
                                <Badge variant="destructive" className="gap-1 text-[10px]">
                                  <XCircle className="h-3 w-3" />
                                  오류
                                </Badge>
                              ) : (
                                <Badge className="gap-1 border-emerald-500/30 bg-emerald-500/15 text-[10px] text-emerald-700 hover:bg-emerald-500/20 dark:text-emerald-300">
                                  <CheckCircle2 className="h-3 w-3" />
                                  정상
                                </Badge>
                              )}
                            </td>
                            <td
                              className="max-w-[300px] truncate px-2 py-1.5 text-destructive"
                              title={r.error_detail || ""}
                            >
                              {r.error_detail || ""}
                            </td>
                          </tr>
                        );
                      })}
                    </tbody>
                  </table>
                )}
              </div>
            </div>
          </Tabs>

          <DialogFooter className="gap-2">
            <Button variant="outline" onClick={() => setBulkOpen(false)} disabled={bulkBusy}>
              취소
            </Button>
            <Button
              variant="outline"
              onClick={handleBulkPreview}
              disabled={bulkBusy || bulkRows.length === 0}
            >
              {bulkBusy ? "검증 중..." : "미리보기"}
            </Button>
            <Button
              onClick={handleBulkApply}
              disabled={bulkBusy || bulkSelected.size === 0}
            >
              {bulkBusy ? "처리 중..." : `반영 (${bulkSelected.size}건)`}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>

    </div>
  );
}

// ───────────────────────────────────────────────────────
// 트리 (재귀)
// ───────────────────────────────────────────────────────
type DeptTreeHandlers = {
  onToggle: (code: string) => void;
  onSelect: (d: Department) => void;
  onRestore: (code: string, name: string) => void;
  onAddSub: (parent: Department) => void;
  onMoveTo: (d: Department) => void;
  onMoveTop: (d: Department) => void;
  onMoveUp: (d: Department) => void;
  onMoveDown: (d: Department) => void;
  onMoveBottom: (d: Department) => void;
  onContextDelete: (d: Department) => void;
};

function DeptTree({
  items,
  allDepts,
  expanded,
  selectedCode,
  handlers,
}: {
  items: Department[];
  allDepts: Department[];
  expanded: Set<string>;
  selectedCode: string | null;
  handlers: DeptTreeHandlers;
}) {
  return (
    <div>
      {items.map((dept) => {
        const sub = allDepts.filter((d) => d.parent_dept_code === dept.dept_code);
        const hasSub = sub.length > 0;
        const isOpen = expanded.has(dept.dept_code);
        const isActive = selectedCode === dept.dept_code;
        const isDeleted = !!(dept as any).deleted_at;


        return (
          <div key={dept.dept_code} className="group/node">
            <div
              className={cn(
                "flex cursor-pointer items-center gap-1 rounded px-1.5 py-1 text-xs",
                isActive
                  ? "bg-sky-100 font-semibold text-sky-700 dark:bg-sky-900/40 dark:text-sky-300"
                  : "hover:bg-muted",
                isDeleted && "text-muted-foreground line-through opacity-60",
              )}
              onClick={() => !isDeleted && handlers.onSelect(dept)}
            >
              {hasSub ? (
                <button
                  onClick={(e) => {
                    e.stopPropagation();
                    handlers.onToggle(dept.dept_code);
                  }}
                  className="flex h-3.5 w-3.5 items-center justify-center"
                >
                  {isOpen ? <ChevronDown className="h-3 w-3" /> : <ChevronRight className="h-3 w-3" />}
                </button>
              ) : (
                <span className="inline-block h-3.5 w-3.5" />
              )}
              {hasSub && isOpen ? (
                <FolderOpen className="h-3.5 w-3.5 text-amber-500" />
              ) : (
                <Folder className="h-3.5 w-3.5 text-amber-500" />
              )}
              <span className="truncate">
                {dept.dept_code}. {dept.dept_name}
              </span>

              {isDeleted ? (
                <>
                  <span className="ml-1 text-[10px] uppercase tracking-wider text-muted-foreground">
                    삭제됨
                  </span>
                  <Button
                    variant="ghost"
                    size="icon"
                    className="h-5 w-5 ml-auto text-primary"
                    title="복구"
                    onClick={(e) => {
                      e.stopPropagation();
                      handlers.onRestore(dept.dept_code, dept.dept_name);
                    }}
                  >
                    <Undo2 className="h-3 w-3" />
                  </Button>
                </>
              ) : (
                <div className="ml-auto flex shrink-0 items-center gap-1">
                  {typeof dept.member_count === "number" && dept.member_count > 0 && (
                    <span className="flex items-center gap-0.5 text-[10px] text-muted-foreground">
                      <Users className="h-2.5 w-2.5" /> {dept.member_count}
                    </span>
                  )}
                  <DropdownMenu>
                    <DropdownMenuTrigger asChild>
                      <button
                        type="button"
                        onClick={(e) => e.stopPropagation()}
                        className="flex h-5 w-5 shrink-0 items-center justify-center rounded text-muted-foreground/60 transition-colors hover:bg-foreground/10 hover:text-foreground data-[state=open]:bg-foreground/10 data-[state=open]:text-foreground"
                        title="부서 메뉴"
                      >
                        <MoreVertical className="h-3 w-3" />
                      </button>
                    </DropdownMenuTrigger>
                    <DropdownMenuContent align="end" className="w-48">
                      <DropdownMenuItem
                        onClick={(e) => {
                          e.stopPropagation();
                          handlers.onAddSub(dept);
                        }}
                      >
                        <Plus className="mr-2 h-3.5 w-3.5" />
                        하위 부서 추가
                      </DropdownMenuItem>
                      <DropdownMenuItem
                        onClick={(e) => {
                          e.stopPropagation();
                          handlers.onMoveTo(dept);
                        }}
                      >
                        <FolderTree className="mr-2 h-3.5 w-3.5" />
                        다른 부서 아래로 이동...
                      </DropdownMenuItem>
                      <DropdownMenuSeparator />
                      <DropdownMenuItem
                        onClick={(e) => {
                          e.stopPropagation();
                          handlers.onMoveTop(dept);
                        }}
                      >
                        <ArrowUpToLine className="mr-2 h-3.5 w-3.5" />
                        맨 위로
                      </DropdownMenuItem>
                      <DropdownMenuItem
                        onClick={(e) => {
                          e.stopPropagation();
                          handlers.onMoveUp(dept);
                        }}
                      >
                        <ChevronUp className="mr-2 h-3.5 w-3.5" />
                        한 칸 위
                      </DropdownMenuItem>
                      <DropdownMenuItem
                        onClick={(e) => {
                          e.stopPropagation();
                          handlers.onMoveDown(dept);
                        }}
                      >
                        <ChevronDown className="mr-2 h-3.5 w-3.5" />
                        한 칸 아래
                      </DropdownMenuItem>
                      <DropdownMenuItem
                        onClick={(e) => {
                          e.stopPropagation();
                          handlers.onMoveBottom(dept);
                        }}
                      >
                        <ArrowDownToLine className="mr-2 h-3.5 w-3.5" />
                        맨 아래로
                      </DropdownMenuItem>
                      <DropdownMenuSeparator />
                      <DropdownMenuItem
                        className="text-destructive focus:text-destructive"
                        onClick={(e) => {
                          e.stopPropagation();
                          handlers.onContextDelete(dept);
                        }}
                      >
                        <Trash2 className="mr-2 h-3.5 w-3.5" />
                        삭제
                      </DropdownMenuItem>
                    </DropdownMenuContent>
                  </DropdownMenu>
                </div>
              )}
            </div>
            {hasSub && isOpen && (
              <div className="ml-4">
                <DeptTree
                  items={sub}
                  allDepts={allDepts}
                  expanded={expanded}
                  selectedCode={selectedCode}
                  handlers={handlers}
                />
              </div>
            )}
          </div>
        );
      })}
    </div>
  );
}

// ───────────────────────────────────────────────────────
// 기본정보 폼
// ───────────────────────────────────────────────────────
function BasicInfoForm({
  draft,
  setDraft,
  companyLabel,
}: {
  draft: DeptDetailDraft;
  setDraft: React.Dispatch<React.SetStateAction<DeptDetailDraft>>;
  companyLabel: string;
}) {
  const update = <K extends keyof DeptDetailDraft>(key: K, value: DeptDetailDraft[K]) =>
    setDraft((prev) => ({ ...prev, [key]: value }));

  return (
    <div className="mx-auto max-w-4xl">
      <div className="grid grid-cols-[120px_minmax(0,1fr)] gap-x-4 gap-y-0 rounded-md border bg-card">
        <Row label="회사">
          <div className="py-1 text-sm">{companyLabel || "-"}</div>
        </Row>
        <Row label="사업장">
          <div className="py-1 text-sm">{companyLabel || "-"}</div>
        </Row>

        <Row label="부서코드">
          {draft.dept_code ? (
            <span className="font-mono text-sm">{draft.dept_code}</span>
          ) : (
            <span className="text-muted-foreground text-sm">저장 시 자동 부여됩니다 (DEPT_n)</span>
          )}
        </Row>

        <Row label="부서유형">
          <div className="flex flex-wrap items-center gap-2">
            <Select value={draft.dept_type} onValueChange={(v) => update("dept_type", v)}>
              <SelectTrigger className="h-8 w-[100px] shrink-0 text-sm">
                <SelectValue />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="dept">부서</SelectItem>
                <SelectItem value="team">팀</SelectItem>
                <SelectItem value="temp">임시</SelectItem>
              </SelectContent>
            </Select>
            <Select value={draft.org_system || "none"} onValueChange={(v) => update("org_system", v === "none" ? "" : v)}>
              <SelectTrigger className="h-8 min-w-[140px] flex-1 text-sm">
                <SelectValue placeholder="조직체계 선택" />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="none">조직체계 선택</SelectItem>
                <SelectItem value="hr">인사조직</SelectItem>
                <SelectItem value="sales">영업조직</SelectItem>
              </SelectContent>
            </Select>
          </div>
        </Row>

        <Row label="부서명" required>
          <div className="flex items-center gap-1">
            <Input
              value={draft.dept_name}
              onChange={(e) => update("dept_name", e.target.value)}
              className="h-8 flex-1 bg-rose-50/40 text-sm dark:bg-rose-950/20"
              placeholder="부서명을 입력하세요"
            />
            <Button variant="outline" size="icon" className="h-8 w-8" type="button" title="다국어">
              <Globe className="h-3.5 w-3.5" />
            </Button>
          </div>
        </Row>

        <Row label="부서약칭">
          <Input
            value={draft.short_name}
            onChange={(e) => update("short_name", e.target.value)}
            className="h-8 text-sm"
          />
        </Row>

        <Row label="결재 관리자" hint>
          <ManagerChipsField
            userIds={draft.approval_managers}
            onChange={(ids) => update("approval_managers", ids)}
            companyCode={draft.company_code}
            max={10}
          />
        </Row>
        <Row label="부서 관리자">
          <ManagerChipsField
            userIds={draft.dept_managers}
            onChange={(ids) => update("dept_managers", ids)}
            companyCode={draft.company_code}
            max={10}
          />
        </Row>
        <Row label="조직장" hint>
          <ManagerChipsField
            userIds={draft.org_leaders}
            onChange={(ids) => update("org_leaders", ids)}
            companyCode={draft.company_code}
            max={10}
          />
        </Row>

        {/* TODO V2: 부서주소 — 다지점 운영 회사 V2 에서 살릴 수도. 컬럼은 DEPT_INFO.ZIPCODE/ADDRESS1/ADDRESS2 유지 */}
        {false && (
          <Row label="부서주소">
            <div className="space-y-1">
              <div className="flex gap-1">
                <Input
                  value={draft.zipcode}
                  onChange={(e) => update("zipcode", e.target.value)}
                  className="h-8 w-[100px] text-sm"
                />
                <Button variant="outline" size="sm" className="h-8 text-xs" type="button">우편번호</Button>
              </div>
              <Input
                value={draft.address1}
                onChange={(e) => update("address1", e.target.value)}
                className="h-8 text-sm"
              />
              <Input
                value={draft.address2}
                onChange={(e) => update("address2", e.target.value)}
                className="h-8 text-sm"
              />
            </div>
          </Row>
        )}

        <Row label="사용여부">
          <RadioGroup
            value={draft.status}
            onValueChange={(v) => update("status", v as "active" | "inactive")}
            className="flex items-center gap-4"
          >
            <div className="flex items-center gap-1">
              <RadioGroupItem value="active" id="status-active" className="h-3.5 w-3.5" />
              <Label htmlFor="status-active" className="text-sm">사용</Label>
            </div>
            <div className="flex items-center gap-1">
              <RadioGroupItem value="inactive" id="status-inactive" className="h-3.5 w-3.5" />
              <Label htmlFor="status-inactive" className="text-sm">미사용</Label>
            </div>
          </RadioGroup>
        </Row>

        <Row label="시작일">
          <div className="grid grid-cols-2 gap-3">
            <Input
              type="date"
              value={draft.start_date}
              onChange={(e) => update("start_date", e.target.value)}
              className="h-8 text-sm"
            />
            <div className="flex items-center gap-2">
              <Label className="w-[50px] text-xs">종료일</Label>
              <Input
                type="date"
                value={draft.end_date}
                onChange={(e) => update("end_date", e.target.value)}
                className="h-8 flex-1 text-sm"
              />
            </div>
          </div>
        </Row>

        <Row label="정렬">
          <Input
            type="number"
            value={draft.sort_order}
            onChange={(e) => update("sort_order", Number(e.target.value) || 0)}
            className="h-8 text-sm"
          />
        </Row>

        {/* TODO V2: 위치 — 향후 매핑 가능성. 컬럼은 DEPT_INFO.LOCATION 유지 */}
        {false && (
          <Row label="위치코드">
            <Input
              value={draft.location}
              onChange={(e) => update("location", e.target.value)}
              className="h-8 text-sm"
              placeholder="위치코드"
            />
          </Row>
        )}
      </div>
    </div>
  );
}

function Row({
  label,
  children,
  required,
  hint,
}: {
  label: string;
  children: React.ReactNode;
  required?: boolean;
  hint?: boolean;
}) {
  return (
    <>
      <div className="flex items-center gap-1 border-b bg-muted/30 px-3 py-2.5 text-xs font-medium last:border-b-0">
        {hint && <Info className="h-3 w-3 text-sky-500" />}
        <span>{label}</span>
        {required && <span className="text-destructive">*</span>}
      </div>
      <div className="flex items-center border-b px-3 py-1.5 last:border-b-0">{children}</div>
    </>
  );
}

function PickerField({
  value,
  onChange,
  placeholder,
}: {
  value: string;
  onChange: (v: string) => void;
  placeholder?: string;
}) {
  return (
    <div className="flex items-center gap-1">
      <Input
        value={value}
        onChange={(e) => onChange(e.target.value)}
        className="h-8 flex-1 text-sm"
        placeholder={placeholder}
      />
      <Button variant="outline" size="icon" className="h-8 w-8" type="button">
        <Users className="h-3.5 w-3.5" />
      </Button>
    </div>
  );
}

function ManagerChipsField({
  userIds,
  onChange,
  companyCode,
  max,
}: {
  userIds: string[];
  onChange: (ids: string[]) => void;
  companyCode: string;
  max: number;
}) {
  const [pickerOpen, setPickerOpen] = useState(false);
  const [resolvedNames, setResolvedNames] = useState<Record<string, string>>({});

  useEffect(() => {
    const unknown = userIds.filter((id) => !resolvedNames[id]);
    if (unknown.length === 0 || !companyCode) return;
    let cancelled = false;
    (async () => {
      const updates: Record<string, string> = {};
      for (const id of unknown) {
        try {
          const res = await departmentAPI.searchUsers(companyCode, id);
          if (res.success && Array.isArray((res as any).data)) {
            const found = (res as any).data.find((u: any) => u.user_id === id);
            if (found) updates[id] = found.user_name || id;
          }
        } catch { /* ignore */ }
      }
      if (!cancelled && Object.keys(updates).length > 0) {
        setResolvedNames((prev) => ({ ...prev, ...updates }));
      }
    })();
    return () => { cancelled = true; };
  }, [userIds, companyCode]);

  const handleRemove = (id: string) => onChange(userIds.filter((x) => x !== id));
  const handleAdd = (id: string) => {
    if (userIds.includes(id)) return;
    if (userIds.length >= max) return;
    onChange([...userIds, id]);
  };

  return (
    <>
      <div className="flex flex-wrap items-center gap-1.5">
        {userIds.map((id) => (
          <div
            key={id}
            className="flex items-center gap-1 rounded-md border bg-muted/50 px-2 py-0.5 text-xs"
          >
            <span className="font-medium">{resolvedNames[id] || id}</span>
            <span className="text-muted-foreground text-[10px]">({id})</span>
            <button
              type="button"
              onClick={() => handleRemove(id)}
              className="text-muted-foreground hover:text-destructive ml-0.5"
              title="제거"
            >
              <X className="h-3 w-3" />
            </button>
          </div>
        ))}
        {userIds.length < max && (
          <Button
            type="button"
            variant="outline"
            size="sm"
            className="h-7 gap-1 text-xs"
            onClick={() => setPickerOpen(true)}
            disabled={!companyCode}
          >
            <Plus className="h-3 w-3" />
            추가
          </Button>
        )}
        {userIds.length >= max && (
          <span className="text-muted-foreground text-[10px]">최대 {max}명</span>
        )}
      </div>
      <UserSearchModal
        open={pickerOpen}
        companyCode={companyCode}
        existingMemberIds={new Set(userIds)}
        onAdd={async (userId) => {
          handleAdd(userId);
        }}
        onClose={() => setPickerOpen(false)}
      />
    </>
  );
}

// ───────────────────────────────────────────────────────
// 사용자 검색 모달
// ───────────────────────────────────────────────────────
function UserSearchModal({
  open,
  companyCode,
  existingMemberIds,
  onAdd,
  onClose,
}: {
  open: boolean;
  companyCode: string;
  existingMemberIds: Set<string>;
  onAdd: (userId: string) => Promise<void>;
  onClose: () => void;
}) {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState<Record<string, any>[]>([]);
  const [searching, setSearching] = useState(false);
  const [addingId, setAddingId] = useState<string | null>(null);

  useEffect(() => {
    if (!open) {
      setQuery("");
      setResults([]);
    }
  }, [open]);

  useEffect(() => {
    if (!query.trim()) {
      setResults([]);
      return;
    }
    const timer = setTimeout(async () => {
      setSearching(true);
      try {
        const res = await departmentAPI.searchUsers(companyCode, query.trim());
        if (res.success && (res as any).data) setResults((res as any).data);
        else setResults([]);
      } finally {
        setSearching(false);
      }
    }, 300);
    return () => clearTimeout(timer);
  }, [query, companyCode]);

  const handleAdd = async (userId: string) => {
    setAddingId(userId);
    try {
      await onAdd(userId);
      onClose();
    } finally {
      setAddingId(null);
    }
  };

  return (
    <Dialog open={open} onOpenChange={(o) => !o && onClose()}>
      <DialogContent className="max-w-[480px]">
        <DialogHeader>
          <DialogTitle>부서원 추가</DialogTitle>
        </DialogHeader>
        <div className="flex flex-col gap-3">
          <Input
            placeholder="이름 또는 아이디로 검색..."
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            className="h-8 text-sm"
            autoFocus
          />
          <div className="max-h-72 overflow-y-auto rounded-md border bg-card">
            {searching ? (
              <div className="py-8 text-center text-xs text-muted-foreground">검색 중...</div>
            ) : results.length === 0 && query.trim() ? (
              <div className="py-8 text-center text-xs text-muted-foreground">사용자를 찾을 수 없음</div>
            ) : results.length === 0 ? (
              <div className="py-8 text-center text-xs text-muted-foreground">검색어를 입력하세요</div>
            ) : (
              <div className="divide-y">
                {results.map((u) => {
                  const alreadyMember = existingMemberIds.has(u.user_id);
                  return (
                    <div
                      key={u.user_id}
                      className={cn(
                        "flex items-center justify-between px-4 py-2.5",
                        alreadyMember ? "opacity-50" : "cursor-pointer hover:bg-muted/50",
                      )}
                      onClick={() => !alreadyMember && !addingId && handleAdd(u.user_id)}
                    >
                      <div>
                        <div className="flex items-center gap-2 text-sm">
                          <span className="font-medium">{u.user_name}</span>
                          <span className="text-xs text-muted-foreground">({u.user_id})</span>
                        </div>
                        <div className="mt-0.5 flex gap-3 text-[11px] text-muted-foreground">
                          {u.position_name && <span>{u.position_name}</span>}
                          {u.email && <span>{u.email}</span>}
                        </div>
                      </div>
                      {alreadyMember ? (
                        <span className="text-[11px] text-muted-foreground">이미 부서원</span>
                      ) : (
                        <Button
                          size="sm"
                          className="h-6 px-2 text-[11px]"
                          disabled={addingId === u.user_id}
                          onClick={(e) => {
                            e.stopPropagation();
                            handleAdd(u.user_id);
                          }}
                        >
                          추가
                        </Button>
                      )}
                    </div>
                  );
                })}
              </div>
            )}
          </div>
        </div>
        <DialogFooter>
          <Button variant="outline" size="sm" onClick={onClose}>
            닫기
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  );
}

// ───────────────────────────────────────────────────────
// 부서원 패널
// ───────────────────────────────────────────────────────
function MembersPanel({
  members,
  deptCode,
  companyCode,
  onChanged,
}: {
  members: DepartmentMember[];
  deptCode: string;
  companyCode: string;
  onChanged: () => Promise<void>;
}) {
  const { toast } = useToast();
  const [addModalOpen, setAddModalOpen] = useState(false);
  const existingMemberIds = useMemo(() => new Set(members.map((m) => m.user_id)), [members]);

  const handleAdd = async (userId: string) => {
    const res = await departmentAPI.addDepartmentMember(deptCode, userId);
    if (res.success) {
      toast({ title: "부서원이 추가되었습니다" });
      await onChanged();
    } else {
      toast({ title: "추가 실패", description: (res as any).error ?? "오류가 발생했습니다", variant: "destructive" });
    }
  };

  const handleRemove = async (userId: string, userName: string) => {
    if (!window.confirm(`${userName}을(를) 부서에서 제거하시겠습니까?`)) return;
    const res = await departmentAPI.removeDepartmentMember(deptCode, userId);
    if (res.success) {
      toast({ title: "부서원이 제거되었습니다" });
      await onChanged();
    } else {
      toast({ title: "제거 실패", description: (res as any).error ?? "오류가 발생했습니다", variant: "destructive" });
    }
  };

  const handleSetPrimary = async (userId: string) => {
    const res = await departmentAPI.setPrimaryDepartment(deptCode, userId);
    if (res.success) {
      toast({ title: "주부서로 설정되었습니다" });
      await onChanged();
    } else {
      toast({ title: "주부서 설정 실패", description: (res as any).error ?? "오류가 발생했습니다", variant: "destructive" });
    }
  };

  return (
    <div className="mx-auto max-w-4xl">
      <div className="mb-3 flex items-center justify-between">
        <div className="text-sm text-muted-foreground">부서원 {members.length}명</div>
        <Button size="sm" className="h-7 gap-1.5 text-xs" onClick={() => setAddModalOpen(true)}>
          <Plus className="h-3.5 w-3.5" />
          부서원 추가
        </Button>
      </div>
      <div className="divide-y rounded-md border bg-card">
        {members.length === 0 ? (
          <div className="py-10 text-center text-xs text-muted-foreground">부서원이 없습니다.</div>
        ) : (
          members.map((m) => (
            <div key={m.user_id} className="flex items-center justify-between px-4 py-2.5">
              <div>
                <div className="flex items-center gap-2 text-sm">
                  <span className="font-medium">{m.user_name}</span>
                  <span className="text-xs text-muted-foreground">({m.user_id})</span>
                  {m.is_primary && (
                    <Badge variant="default" className="h-4 gap-0.5 px-1.5 text-[10px]">
                      <Star className="h-2.5 w-2.5" />주부서
                    </Badge>
                  )}
                </div>
                <div className="mt-0.5 flex gap-3 text-[11px] text-muted-foreground">
                  {m.position_name && <span>{m.position_name}</span>}
                  {m.email && <span>{m.email}</span>}
                  {m.phone && <span>{m.phone}</span>}
                </div>
              </div>
              <div className="flex items-center gap-1">
                {!m.is_primary && (
                  <Button
                    size="sm"
                    variant="outline"
                    className="h-6 gap-1 px-2 text-[11px]"
                    onClick={() => handleSetPrimary(m.user_id)}
                    title="주부서로 설정"
                  >
                    <Star className="h-3 w-3" />
                    주부서로 설정
                  </Button>
                )}
                <Button
                  size="icon"
                  variant="ghost"
                  className="h-6 w-6 text-muted-foreground hover:text-destructive"
                  onClick={() => handleRemove(m.user_id, m.user_name)}
                  title="부서원 제거"
                >
                  <Trash2 className="h-3.5 w-3.5" />
                </Button>
              </div>
            </div>
          ))
        )}
      </div>

      <UserSearchModal
        open={addModalOpen}
        companyCode={companyCode}
        existingMemberIds={existingMemberIds}
        onAdd={handleAdd}
        onClose={() => setAddModalOpen(false)}
      />
    </div>
  );
}
