"use client";

import { useState, Suspense, useEffect, useCallback, useRef } from "react";
import { useRouter, usePathname, useSearchParams } from "next/navigation";
import { Button } from "@/components/ui/button";
import {
  Shield,
  Menu,
  Home,
  Settings,
  BarChart3,
  FileText,
  Users,
  Package,
  ChevronDown,
  ChevronRight,
  UserCheck,
  LogOut,
  User,
  Building2,
  FileCheck,
  Monitor,
  Plus,
  Edit3,
  Zap,
  Save,
  SlidersHorizontal,
  Sun,
  Moon,
  Bell,
  Star,
} from "lucide-react";
import { useDashboardStore } from "@/stores/dashboardStore";
import { useControlMode } from "@/components/control/hooks/useControlMode";
import { insertDashboard, getDashboardList, updateDashboard } from "@/lib/api/dashMenu";
import { CreateDashboardModal } from "@/components/dash/CreateDashboardModal";
import { MenuItemActions } from "@/components/layout/MenuItemActions";
import { useMenu } from "@/contexts/MenuContext";
import { useFavorites } from "@/contexts/FavoritesContext";
import { useAuth } from "@/hooks/useAuth";
import { useProfile } from "@/hooks/useProfile";
import { MenuItem, menuApi } from "@/lib/api/menu";
import { menuScreenApi } from "@/lib/api/screen";
import { apiClient } from "@/lib/api/client";
import { toast } from "sonner";
import { ProfileModal } from "./ProfileModal";
import { SettingsModal } from "./SettingsModal";
import { Logo } from "./Logo";
import { SideMenu } from "./SideMenu";
import { TabBar } from "./TabBar";
import { TabContent } from "./TabContent";
import { useTabStore } from "@/stores/tabStore";
import { ThemeToggle } from "./ThemeToggle";
import { TopNavBar } from "./TopNavBar";
import { animatedNavOrientationChange } from "@/lib/navOrientationTransition";
import { useTheme } from "next-themes";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogDescription,
} from "@/components/ui/dialog";
import { CompanySwitcher } from "@/components/admin/CompanySwitcher";
import { getIconComponent } from "@/components/admin/MenuIconPicker";
import { animatedThemeChange } from "@/lib/themeTransition";

// MANAGEMENT_ONLY_MENU_URLS — DB 컬럼 IS_SOLUTION_ONLY 로 이전 (PR #D).
// 백엔드 /api/admin/user-menus 가 Host 헤더 기반으로 SQL 단계에서 필터하므로 프론트 Set 불필요.

interface ExtendedUserInfo {
  user_id: string;
  user_name: string;
  userNameEng?: string;
  userNameCn?: string;
  deptCode?: string;
  dept_name?: string;
  positionCode?: string;
  position_name?: string;
  email?: string;
  tel?: string;
  cellPhone?: string;
  user_type?: string;
  userTypeName?: string;
  authName?: string;
  partnerCd?: string;
  isAdmin: boolean;
  sabun?: string;
  photo?: string | null;
  company_code?: string;
  locale?: string;
}

interface AppLayoutProps {
  children: React.ReactNode;
}

const getMenuIcon = (menuName: string, dbIconName?: string | null) => {
  if (dbIconName) {
    const DbIcon = getIconComponent(dbIconName);
    if (DbIcon) return <DbIcon className="h-4 w-4" />;
  }

  const name = menuName.toLowerCase();
  if (name.includes("대시보드") || name.includes("dashboard")) return <Home className="h-4 w-4" />;
  if (name.includes("관리자") || name.includes("admin")) return <Shield className="h-4 w-4" />;
  if (name.includes("사용자") || name.includes("user")) return <Users className="h-4 w-4" />;
  if (name.includes("프로젝트") || name.includes("project")) return <BarChart3 className="h-4 w-4" />;
  if (name.includes("제품") || name.includes("product")) return <Package className="h-4 w-4" />;
  if (name.includes("설정") || name.includes("setting")) return <Settings className="h-4 w-4" />;
  if (name.includes("로그") || name.includes("log")) return <FileText className="h-4 w-4" />;
  if (name.includes("메뉴") || name.includes("menu")) return <Menu className="h-4 w-4" />;
  if (name.includes("화면관리") || name.includes("screen")) return <FileText className="h-4 w-4" />;
  return <FileText className="h-4 w-4" />;
};

const convertMenuToUI = (menus: MenuItem[], userInfo: ExtendedUserInfo | null, parentId: string = "0", parentPath: string = ""): any[] => {
  const filteredMenus = menus
    .filter((menu) => String(menu.parent_obj_id ?? menu.PARENT_OBJ_ID ?? "0") === String(parentId ?? "0"))
    .filter((menu) => String(menu.status ?? menu.STATUS ?? "").toLowerCase() === "active")
    .sort((a, b) => Number(a.seq ?? a.SEQ ?? 0) - Number(b.seq ?? b.SEQ ?? 0));

  if (parentId === "0") {
    const allMenus: any[] = [];

    for (const menu of filteredMenus) {
      const menuName = (menu.menu_name_kor || menu.MENU_NAME_KOR || "").toLowerCase();

      if (menuName.includes("사용자") || menuName.includes("관리자")) {
        const childMenus = convertMenuToUI(menus, userInfo, menu.objid ?? menu.OBJID, "");
        allMenus.push(...childMenus);
      } else {
        allMenus.push(convertSingleMenu(menu, menus, userInfo, ""));
      }
    }

    return allMenus;
  }

  return filteredMenus.map((menu) => convertSingleMenu(menu, menus, userInfo, parentPath));
};

const convertSingleMenu = (menu: MenuItem, allMenus: MenuItem[], userInfo: ExtendedUserInfo | null, parentPath: string = ""): any => {
  const menuId = menu.objid ?? menu.OBJID;

  const getDisplayText = (m: MenuItem) => {
    if (m.translated_name || m.TRANSLATED_NAME) {
      return m.translated_name || m.TRANSLATED_NAME;
    }

    const baseName = m.menu_name_kor || m.MENU_NAME_KOR || "메뉴명 없음";
    const userLocale = userInfo?.locale || "ko";

    if (userLocale === "EN") {
      const translations: { [key: string]: string } = {
        관리자: "Administrator",
        사용자: "User Management",
        메뉴: "Menu Management",
        대시보드: "Dashboard",
        권한: "Permission Management",
        코드: "Code Management",
        설정: "Settings",
        로그: "Log Management",
        프로젝트: "Project Management",
        제품: "Product Management",
      };

      for (const [korean, english] of Object.entries(translations)) {
        if (baseName.includes(korean)) {
          return baseName.replace(korean, english);
        }
      }
    } else if (userLocale === "JA") {
      const translations: { [key: string]: string } = {
        관리자: "管理者",
        사용자: "ユーザー管理",
        메뉴: "メニュー管理",
        대시보드: "ダッシュボード",
        권한: "権限管理",
        코드: "コード管理",
        설정: "設定",
        로그: "ログ管理",
        프로젝트: "プロジェクト管理",
        제품: "製品管理",
      };

      for (const [korean, japanese] of Object.entries(translations)) {
        if (baseName.includes(korean)) {
          return baseName.replace(korean, japanese);
        }
      }
    } else if (userLocale === "ZH") {
      const translations: { [key: string]: string } = {
        관리자: "管理员",
        사용자: "用户管理",
        메뉴: "菜单管理",
        대시보드: "仪表板",
        권한: "权限管理",
        코드: "代码管理",
        설정: "设置",
        로그: "日志管理",
        프로젝트: "项目管理",
        제품: "产品管理",
      };

      for (const [korean, chinese] of Object.entries(translations)) {
        if (baseName.includes(korean)) {
          return baseName.replace(korean, chinese);
        }
      }
    }

    return baseName;
  };

  const displayName = getDisplayText(menu);
  const tabTitle = parentPath ? `${parentPath} - ${displayName}` : displayName;

  const children = convertMenuToUI(allMenus, userInfo, menuId, tabTitle);

  const menuUrl = menu.menu_url || menu.MENU_URL || "#";
  const screenCode = menu.screen_code || menu.SCREEN_CODE || null;
  const menuType = String(menu.menu_type ?? menu.MENU_TYPE ?? "");

  let screenId: number | null = null;
  const screensMatch = menuUrl.match(/^\/screens\/(\d+)/);
  if (screensMatch) {
    screenId = parseInt(screensMatch[1]);
  }

  return {
    id: menuId,
    objid: menuId,
    name: displayName,
    tabTitle,
    icon: getMenuIcon(menu.menu_name_kor || menu.MENU_NAME_KOR || "", menu.menu_icon || menu.MENU_ICON),
    url: menuUrl,
    screenCode,
    screen_id: screenId,
    menu_type: menuType,
    children: children.length > 0 ? children : undefined,
    hasChildren: children.length > 0,
  };
};

/**
 * 헤더 pop-out 툴 그룹 — show=false 전환 시 closing 클래스 후 320ms 뒤 unmount.
 * Ref: INVYONE Design System / ui_kits/app/dashboard-user.jsx (AnimatedHtoolGroup).
 */
function AnimatedHtoolGroup({ show, children }: { show: boolean; children: React.ReactNode }) {
  const [rendered, setRendered] = useState(show);
  const [closing, setClosing] = useState(false);
  useEffect(() => {
    if (show) {
      setRendered(true);
      setClosing(false);
    } else if (rendered) {
      setClosing(true);
      const t = window.setTimeout(() => { setRendered(false); setClosing(false); }, 320);
      return () => window.clearTimeout(t);
    }
  }, [show, rendered]);
  if (!rendered) return null;
  return <span className={`ud-htool-group${closing ? " closing" : ""}`}>{children}</span>;
}

function AppLayoutInner({ children }: AppLayoutProps) {
  const router = useRouter();
  const pathname = usePathname();
  const searchParams = useSearchParams();
  const { user, logout, refreshUserData } = useAuth();
  const { user_menus: userMenus, admin_menus: adminMenus, loading, refreshMenus } = useMenu();
  const { isFavorite, toggle: toggleFavorite } = useFavorites();
  const [sidebarOpen, setSidebarOpen] = useState(true);
  const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
  const [tabsCollapsed, setTabsCollapsed] = useState(false);
  const [flyoutMenu, setFlyoutMenu] = useState<{ menu: any; rect: DOMRect } | null>(null);
  const [modeTransition, setModeTransition] = useState<"idle" | "out" | "in">("idle");
  const [expandedMenus, setExpandedMenus] = useState<Set<string>>(new Set());
  const [isMobile, setIsMobile] = useState(false);
  const [showCompanySwitcher, setShowCompanySwitcher] = useState(false);
  const [currentCompanyName, setCurrentCompanyName] = useState<string>("");
  const [settingsOpen, setSettingsOpen] = useState(false);

  // isMgmtSite / MANAGEMENT_ONLY_MENU_URLS — DB IS_SOLUTION_ONLY 컬럼으로 이전 (PR #D).
  // 백엔드가 Host 헤더 기반으로 SQL 단계에서 필터하므로 프론트 상태 불필요.
  const tweaksAnchorRef = useRef<HTMLButtonElement>(null);
  const { theme, setTheme: rawSetTheme } = useTheme();

  // 메뉴 방향 (vertical: 사이드바 / horizontal: 헤더 내 TopNav). localStorage 유지.
  const [navOrientation, setNavOrientationRaw] = useState<"vertical" | "horizontal">("vertical");
  const navOrientationRef = useRef<"vertical" | "horizontal">("vertical");
  navOrientationRef.current = navOrientation;
  useEffect(() => {
    try {
      const saved = localStorage.getItem("invyone-nav-orientation");
      if (saved === "horizontal" || saved === "vertical") setNavOrientationRaw(saved);
    } catch {}
  }, []);
  const setNavOrientation = useCallback((next: "vertical" | "horizontal") => {
    if (navOrientationRef.current === next) return;
    // Ghost-slide transition: 나가는 nav 를 축 방향으로 밀어내며 페이드, 새 nav 는 자체 enter 애니 실행
    animatedNavOrientationChange(next, () => {
      setNavOrientationRaw(next);
      try { localStorage.setItem("invyone-nav-orientation", next); } catch {}
    });
  }, []);

  // 대시보드 생성 (전역) + 제어/편집 (대시보드 페이지에서만 조건부 노출)
  const dashCreateOpen = useDashboardStore((s) => s.createOpen);
  const openDashCreate = useDashboardStore((s) => s.openCreate);
  const closeDashCreate = useDashboardStore((s) => s.closeCreate);
  const setDashboardsList = useDashboardStore((s) => s.setDashboards);
  const dashEditTarget = useDashboardStore((s) => s.editTarget);
  const closeDashEdit = useDashboardStore((s) => s.closeEdit);
  const dashEditMode = useDashboardStore((s) => s.editMode);
  const toggleDashEditMode = useDashboardStore((s) => s.toggleEditMode);
  const setDashEditMode = useDashboardStore((s) => s.setEditMode);
  const openDashLib = useDashboardStore((s) => s.openLib);
  const dashControlActive = useControlMode((s) => s.active);
  const toggleDashControlMode = useControlMode((s) => s.toggleControlMode);
  const [dashCreateSubmitting, setDashCreateSubmitting] = useState(false);

  const handleDashCreateSubmit = useCallback(async (payload: { name: string; icon: string; is_personal: boolean }) => {
    setDashCreateSubmitting(true);
    try {
      const result = await insertDashboard(payload);
      try { await refreshMenus(); } catch { /* ignore */ }
      try {
        const list = await getDashboardList();
        setDashboardsList(list?.list ?? []);
      } catch { /* ignore */ }
      const newUrl = result?.menu_url ?? result?.MENU_URL;
      closeDashCreate();
      toast.success(`"${payload.name}" 대시보드를 만들었습니다`);
      if (newUrl) router.push(newUrl);
    } catch (err) {
      toast.error("대시보드 생성 실패");
    } finally {
      setDashCreateSubmitting(false);
    }
  }, [refreshMenus, closeDashCreate, router, setDashboardsList]);

  // 등록/수정 통합 — editTarget 이 있으면 수정, 없으면 신규 생성.
  // 공유 범위(is_personal) 는 backend updateDashboard 에서 무시됨 (모달도 disabled)
  const handleDashModalSubmit = useCallback(async (payload: { name: string; icon: string; is_personal: boolean }) => {
    if (dashEditTarget) {
      setDashCreateSubmitting(true);
      try {
        await updateDashboard(dashEditTarget.id, { name: payload.name, icon: payload.icon });
        try { await refreshMenus(); } catch { /* ignore */ }
        try {
          const list = await getDashboardList();
          setDashboardsList(list?.list ?? []);
        } catch { /* ignore */ }
        closeDashEdit();
        toast.success("대시보드가 수정되었습니다");
      } catch (err) {
        console.error("[AppLayout] 대시보드 수정 실패", err);
        toast.error("수정 실패");
      } finally {
        setDashCreateSubmitting(false);
      }
    } else {
      await handleDashCreateSubmit(payload);
    }
  }, [dashEditTarget, refreshMenus, setDashboardsList, closeDashEdit, handleDashCreateSubmit]);

  const handleDashModalClose = useCallback(() => {
    closeDashCreate();
    closeDashEdit();
  }, [closeDashCreate, closeDashEdit]);

  // 사이드바 메뉴(=대시보드) 의 ⋮ 액션이 dashboards store 에서 menu_url 매칭으로
  // objid 를 찾기 때문에 AppLayout 마운트 시 한 번 채워둔다. DashboardLayout 페이지가
  // 같은 fetch 를 또 하지만 zustand 가 덮어쓰니 무해.
  useEffect(() => {
    if (!user) return;
    let cancelled = false;
    (async () => {
      try {
        const result = await getDashboardList();
        if (!cancelled) setDashboardsList(result?.list ?? []);
      } catch (err) {
        console.warn("[AppLayout] dashboard list 로드 실패", err);
      }
    })();
    return () => { cancelled = true; };
  }, [user, setDashboardsList]);

  // 테마 전환 — 클릭 위치에서 원형으로 새 테마가 reveal (View Transitions API)
  const setNextTheme = useCallback((t: "light" | "dark", e?: React.MouseEvent) => {
    if (theme === t) return;
    animatedThemeChange(t, rawSetTheme, e ? { x: e.clientX, y: e.clientY } : undefined);
  }, [theme, rawSetTheme]);

  // URL 직접 접근 시 탭 자동 열기
  useEffect(() => {
    const store = useTabStore.getState();
    const currentModeTabs = store[store.mode].tabs;
    if (currentModeTabs.length > 0) return;

    const screenMatch = pathname.match(/^\/screens\/(\d+)/);
    if (screenMatch) {
      const screenId = parseInt(screenMatch[1]);
      const menu_objid = searchParams.get("menuObjid") ? parseInt(searchParams.get("menuObjid")!) : undefined;
      store.openTab({ type: "screen", title: `화면 ${screenId}`, screen_id: screenId, menu_objid });
      return;
    }

    if (pathname.startsWith("/admin") && pathname !== "/admin") {
      store.setMode("admin");
      // menu API 가 실패하는 환경 (SUPER_ADMIN cross-tenant 등) 에서도 한글 라벨 유지
      const ADMIN_PATH_LABELS: Record<string, string> = {
        "/admin/userMng/deptMngList": "부서관리",
      };
      const fallbackTitle = ADMIN_PATH_LABELS[pathname] || pathname.split("/").pop() || "관리자";
      store.openTab({ type: "admin", title: fallbackTitle, admin_url: pathname });
    }
  }, []);

  // 현재 회사명 조회 (SUPER_ADMIN 전용)
  useEffect(() => {
    const fetchCurrentCompanyName = async () => {
      if ((user as ExtendedUserInfo)?.user_type === "SUPER_ADMIN") {
        const companyCode = (user as ExtendedUserInfo)?.company_code;

        if (companyCode === "*") {
          setCurrentCompanyName("Invyone (최고 관리자)");
        } else if (companyCode) {
          try {
            const response = await apiClient.get("/admin/companies/db");
            if (response.data.success) {
              const company = response.data.data.find((c: any) => c.company_code === companyCode);
              setCurrentCompanyName(company?.company_name || companyCode);
            }
          } catch {
            setCurrentCompanyName(companyCode);
          }
        }
      }
    };

    fetchCurrentCompanyName();
  }, [(user as ExtendedUserInfo)?.company_code, (user as ExtendedUserInfo)?.user_type]);

  // 화면 크기 감지 및 사이드바 초기 상태 설정
  useEffect(() => {
    const checkIsMobile = () => {
      const mobile = window.innerWidth < 768;
      setIsMobile(mobile);
      if (mobile) {
        setSidebarOpen(false);
      } else {
        setSidebarOpen(true);
      }
    };

    checkIsMobile();
    window.addEventListener("resize", checkIsMobile);
    return () => window.removeEventListener("resize", checkIsMobile);
  }, []);

  // 프로필 관련 로직
  const {
    isModalOpen,
    formData,
    selectedImage,
    isSaving,
    departments,
    alertModal,
    closeAlert,
    openProfileModal,
    closeProfileModal,
    updateFormData,
    selectImage,
    removeImage,
    saveProfile,
  } = useProfile(user, refreshUserData, refreshMenus);

  const tabMode = useTabStore((s) => s.mode);
  const setTabMode = useTabStore((s) => s.setMode);
  const isAdminMode = tabMode === "admin";

  const isPreviewMode = searchParams.get("preview") === "true";

  const currentMenus = isAdminMode ? adminMenus : userMenus;

  const currentTabs = useTabStore((s) => s[s.mode].tabs);
  const currentActiveTabId = useTabStore((s) => s[s.mode].active_tab_id);
  const activeTab = currentTabs.find((t) => t.id === currentActiveTabId);

  const toggleMenu = (menuId: string) => {
    const newExpanded = new Set(expandedMenus);
    if (newExpanded.has(menuId)) {
      newExpanded.delete(menuId);
    } else {
      newExpanded.add(menuId);
    }
    setExpandedMenus(newExpanded);
  };

  const { openTab } = useTabStore();

  const handleMenuClick = async (menu: any) => {
    if (menu.hasChildren) {
      toggleMenu(menu.id);
      return;
    }

    const menuName = menu.tabTitle || menu.label || menu.name || "메뉴";
    if (typeof window !== "undefined") {
      localStorage.setItem("currentMenuName", menuName);
    }

    const menuObjid = parseInt((menu.objid || menu.id)?.toString() || "0");
    const isAdminMenu = menu.menu_type === "0";

    console.log("[handleMenuClick] 메뉴 클릭:", {
      menuName,
      menuObjid,
      menu_type: menu.menu_type,
      isAdminMenu,
      screen_id: menu.screen_id,
      screenCode: menu.screenCode,
      url: menu.url,
      fullMenu: menu,
    });

    // 관리자 메뉴 (menu_type = 0): URL 직접 입력 → admin 탭
    if (isAdminMenu) {
      if (menu.url && menu.url !== "#") {
        console.log("[handleMenuClick] → admin 탭:", menu.url);
        openTab({ type: "admin", title: menuName, admin_url: menu.url });
        if (isMobile) setSidebarOpen(false);
      } else {
        toast.warning("이 메뉴에는 연결된 페이지가 없습니다.");
      }
      return;
    }

    // 사용자 메뉴 (menu_type = 1, 2): 화면/대시보드 할당
    // 1) screenId가 메뉴 URL에서 추출된 경우 바로 screen 탭
    if (menu.screen_id) {
      console.log("[handleMenuClick] → screen 탭 (URL에서 screenId 추출):", menu.screen_id);
      openTab({ type: "screen", title: menuName, screen_id: menu.screen_id, menu_objid: menuObjid });
      if (isMobile) setSidebarOpen(false);
      return;
    }

    // 2) screen_menu_assignments 테이블 조회
    if (menuObjid) {
      try {
        console.log("[handleMenuClick] → screen_menu_assignments 조회 시도, menuObjid:", menuObjid);
        const assignedScreens = await menuScreenApi.getScreensByMenu(menuObjid);
        console.log("[handleMenuClick] → 조회 결과:", assignedScreens);
        if (assignedScreens.length > 0) {
          const assignedScreenId = assignedScreens[0].screen_id;
          console.log("[handleMenuClick] → screen 탭 (assignments):", assignedScreenId);
          openTab({ type: "screen", title: menuName, screen_id: assignedScreenId, menu_objid: menuObjid });
          if (isMobile) setSidebarOpen(false);
          return;
        }
      } catch (err) {
        console.error("[handleMenuClick] 할당된 화면 조회 실패:", err);
      }
    }

    // 3) 대시보드 (사용자 메뉴) → 자동 부여된 /숫자 URL 로 이동
    //    - 회사별 시퀀스가 URL 그 자체. "/{seq}" 형태만 대시보드로 인식
    if (menu.url && /^\/\d+$/.test(menu.url)) {
      console.log("[handleMenuClick] → 대시보드 페이지:", menu.url);
      router.push(menu.url);
      if (isMobile) setSidebarOpen(false);
      return;
    }

    // 4) 커스텀 페이지 URL (React 직접 구현 페이지) → admin 탭으로 렌더링
    if (menu.url && menu.url !== "#" && !menu.url.startsWith("/screen/") && !menu.url.startsWith("/screens/")) {
      console.log("[handleMenuClick] → 커스텀 페이지 탭:", menu.url);
      openTab({ type: "admin", title: menuName, admin_url: menu.url });
      if (isMobile) setSidebarOpen(false);
      return;
    }

    console.warn("[handleMenuClick] 어떤 조건에도 매칭 안 됨:", { menuName, menu_type: menu.menu_type, url: menu.url, screen_id: menu.screen_id });
    toast.warning("이 메뉴에 할당된 화면이 없습니다. 메뉴 설정을 확인해주세요.");
  };

  const handleModeSwitch = useCallback((e?: React.MouseEvent<HTMLButtonElement>) => {
    if (modeTransition !== "idle") return;

    const goingToAdmin = !isAdminMode;
    setModeTransition("out");

    // (b) sidebar items morph-out — stagger
    const oldItems = Array.from(document.querySelectorAll<HTMLElement>(".v5-side .v5-si"));
    oldItems.forEach((it, i) => {
      it.style.animationDelay = `${i * 35}ms`;
      it.classList.add("mode-morph-out");
    });

    // (c) header glow flash
    const hdrGlow = document.querySelector<HTMLElement>(".v5-hdr-glow");
    if (hdrGlow) {
      hdrGlow.classList.remove("mode-flash");
      void hdrGlow.offsetWidth;
      hdrGlow.classList.add("mode-flash");
    }

    // (d) 토글 버튼 burst — ring 1 + radial particle 10
    const targetEl = e?.currentTarget as HTMLElement | undefined;
    const rect = targetEl?.getBoundingClientRect();
    const bx = rect ? rect.left + rect.width / 2 : (e?.clientX ?? window.innerWidth - 80);
    const by = rect ? rect.top + rect.height / 2 : (e?.clientY ?? 25);
    const burst = document.createElement("div");
    burst.className = `v5-mode-burst${goingToAdmin ? " admin" : ""}`;
    burst.style.left = `${bx}px`;
    burst.style.top = `${by}px`;
    const ring = document.createElement("span");
    ring.className = "burst-ring";
    burst.appendChild(ring);
    const N = 10;
    for (let i = 0; i < N; i++) {
      const p = document.createElement("span");
      p.className = "burst-particle";
      const angle = (i / N) * Math.PI * 2;
      const dist = 36 + Math.random() * 22;
      p.style.setProperty("--tx", `${Math.cos(angle) * dist}px`);
      p.style.setProperty("--ty", `${Math.sin(angle) * dist}px`);
      p.style.animationDelay = `${i * 8}ms`;
      burst.appendChild(p);
    }
    document.body.appendChild(burst);
    setTimeout(() => burst.remove(), 1100);

    // (d2) 헤더 하단 좌→우 sweep
    const hdrEl = document.querySelector<HTMLElement>(".v5-hdr");
    if (hdrEl) {
      const sweep = document.createElement("div");
      sweep.className = "v5-mode-sweep";
      sweep.setAttribute("data-mode", goingToAdmin ? "admin" : "user");
      hdrEl.appendChild(sweep);
      setTimeout(() => sweep.remove(), 900);
    }

    // (e) breadcrumb swap-out
    const bc = document.querySelector<HTMLElement>(".v5-hdr-bc");
    bc?.classList.remove("mode-swap-in");
    bc?.classList.add("mode-swap-out");

    setTimeout(() => {
      setTabMode(isAdminMode ? "user" : "admin");
      setModeTransition("in");

      requestAnimationFrame(() => {
        const newItems = Array.from(document.querySelectorAll<HTMLElement>(".v5-side .v5-si"));
        newItems.forEach((it, i) => {
          it.style.animationDelay = `${i * 45}ms`;
          it.classList.add("mode-morph-in");
        });

        const newBc = document.querySelector<HTMLElement>(".v5-hdr-bc");
        newBc?.classList.remove("mode-swap-out");
        newBc?.classList.add("mode-swap-in");
      });

      setTimeout(() => {
        setModeTransition("idle");
        document.querySelectorAll<HTMLElement>(".v5-side .v5-si").forEach((it) => {
          it.classList.remove("mode-morph-in", "mode-morph-out");
          it.style.animationDelay = "";
        });
        document.querySelector<HTMLElement>(".v5-hdr-bc")?.classList.remove("mode-swap-in", "mode-swap-out");
      }, 600);
    }, 350);
  }, [isAdminMode, setTabMode, modeTransition]);

  const handleLogout = async () => {
    try {
      await logout();
      router.push("/login");
    } catch {
      // 로그아웃 실패
    }
  };

  const handleMenuDragStart = (e: React.DragEvent, menu: any) => {
    if (menu.hasChildren) {
      e.preventDefault();
      return;
    }
    e.dataTransfer.effectAllowed = "copy";
    const menuName = menu.tabTitle || menu.label || menu.name || "메뉴";
    const menuObjid = menu.objid || menu.id;
    const dragPayload = JSON.stringify({ menuName, menuObjid, url: menu.url });
    e.dataTransfer.setData("application/tab-menu-pending", dragPayload);
    e.dataTransfer.setData("text/plain", menuName);
  };

  // POP 모드 진입 핸들러
  const handlePopModeClick = async () => {
    try {
      const response = await menuApi.getPopMenus();
      if (response.success && response.data) {
        const { childMenus, landingMenu } = response.data;

        if (landingMenu?.menu_url) {
          router.push(landingMenu.menu_url);
        } else if (childMenus.length === 0) {
          toast.info("설정된 POP 화면이 없습니다");
        } else if (childMenus.length === 1) {
          router.push(childMenus[0].menu_url);
        } else {
          router.push("/pop");
        }
      } else {
        toast.info("설정된 POP 화면이 없습니다");
      }
    } catch (error) {
      toast.error("POP 메뉴 조회 중 오류가 발생했습니다");
    }
  };

  // 활성 메뉴 판별: activeTab 이 있으면 그 탭 기준으로만 매칭한다.
  // 이전에는 pathname 매칭과 activeTab 매칭을 OR 로 결합해 사이드바에서
  // 두 메뉴(이전 URL + 현재 활성 탭)가 동시에 active 표시되는 버그가 있었다.
  const isMenuActive = useCallback(
    (menu: any): boolean => {
      if (activeTab) {
        const menuObjid = parseInt((menu.objid || menu.id)?.toString() || "0");
        if (activeTab.type === "admin" && activeTab.admin_url) {
          return menu.url === activeTab.admin_url;
        }
        if (activeTab.type === "screen") {
          if (activeTab.menu_objid != null && menuObjid === activeTab.menu_objid) return true;
          const { screen_id: activeTabScreenId } = activeTab;
          if (activeTabScreenId != null && menu.screen_id === activeTabScreenId) return true;
        }
        return false;
      }
      // activeTab 이 없을 때(예: /main 첫 진입)만 URL 직접 매칭
      return pathname === menu.url;
    },
    [pathname, activeTab],
  );

  // 플라이아웃 닫기 타이머 — hover out 후 작은 딜레이로 닫아 마우스가 버튼 ↔ 플라이아웃 이동 시 끊기지 않게 함
  const flyoutCloseTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

  const cancelFlyoutClose = useCallback(() => {
    if (flyoutCloseTimerRef.current) {
      clearTimeout(flyoutCloseTimerRef.current);
      flyoutCloseTimerRef.current = null;
    }
  }, []);

  const scheduleFlyoutClose = useCallback(() => {
    cancelFlyoutClose();
    flyoutCloseTimerRef.current = setTimeout(() => setFlyoutMenu(null), 150);
  }, [cancelFlyoutClose]);

  // 접힌 사이드바에서 부모 메뉴 hover → 플라이아웃 오픈
  const handleCollapsedMenuHover = useCallback((menu: any, e: React.MouseEvent) => {
    if (!sidebarCollapsed || !menu.hasChildren) return;
    cancelFlyoutClose();
    const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
    setFlyoutMenu({ menu, rect });
  }, [sidebarCollapsed, cancelFlyoutClose]);

  // 접힌 사이드바에서 부모 메뉴 클릭 → 이미 hover 로 열려있으므로 swallow (leaf 만 동작)
  const handleCollapsedMenuClick = useCallback((menu: any, e: React.MouseEvent) => {
    if (!sidebarCollapsed || !menu.hasChildren) return false;
    e.stopPropagation();
    return true;
  }, [sidebarCollapsed]);

  // 플라이아웃에서 메뉴 선택
  const handleFlyoutSelect = useCallback((child: any) => {
    cancelFlyoutClose();
    setFlyoutMenu(null);
    handleMenuClick(child);
  }, [cancelFlyoutClose]);

  // 언마운트 시 타이머 정리
  useEffect(() => {
    return () => cancelFlyoutClose();
  }, [cancelFlyoutClose]);

  // 메뉴 트리 렌더링 (v5 glassmorphism)
  const renderMenu = (menu: any, level: number = 0) => {
    const isExpanded = expandedMenus.has(menu.id);
    const isLeaf = !menu.hasChildren;

    return (
      <div
        key={menu.id}
        style={{ position: "relative" }}
        onMouseEnter={(e) => handleCollapsedMenuHover(menu, e)}
        onMouseLeave={() => {
          if (sidebarCollapsed && menu.hasChildren) scheduleFlyoutClose();
        }}
      >
        <MenuItemActions
          menuUrl={isLeaf && !sidebarCollapsed ? menu.url : undefined}
          menuName={menu.name?.trim() || "(이름 없음)"}
        >
          <div
            draggable={isLeaf && !sidebarCollapsed}
            onDragStart={(e) => handleMenuDragStart(e, menu)}
            className={`v5-si ${isMenuActive(menu) ? "on" : ""} ${level > 0 ? "ml-6" : ""}`}
            title={menu.name}
            onClick={(e) => {
              if (handleCollapsedMenuClick(menu, e)) return;
              handleMenuClick(menu);
            }}
          >
            <span className="ic">{menu.icon}</span>
            <span className="truncate" title={menu.name || ""}>{menu.name?.trim() || "(이름 없음)"}</span>
            {isLeaf && !sidebarCollapsed && (
              <button
                type="button"
                onClick={(e) => {
                  e.stopPropagation();
                  toggleFavorite(menu.id);
                }}
                className="ml-auto rounded p-0.5 transition-opacity hover:bg-[var(--v5-surface-hover)]"
                title={isFavorite(menu.id) ? "즐겨찾기 해제" : "즐겨찾기 추가"}
                style={{
                  opacity: isFavorite(menu.id) ? 1 : 0.35,
                  color: isFavorite(menu.id) ? "var(--v5-primary)" : "var(--v5-text-muted)",
                }}
              >
                <Star
                  className="h-3.5 w-3.5"
                  fill={isFavorite(menu.id) ? "currentColor" : "none"}
                />
              </button>
            )}
            {menu.hasChildren && !sidebarCollapsed && (
              <span className="ml-auto">
                {isExpanded ? <ChevronDown className="h-3.5 w-3.5" /> : <ChevronRight className="h-3.5 w-3.5" />}
              </span>
            )}
          </div>
        </MenuItemActions>

        {/* 플라이아웃 (접힌 상태에서만) */}
        {sidebarCollapsed && flyoutMenu?.menu.id === menu.id && menu.hasChildren && (
          <div
            className="v5-side-flyout open"
            style={{ top: 0 }}
            onMouseEnter={cancelFlyoutClose}
            onMouseLeave={scheduleFlyoutClose}
          >
            <div className="fly-title">{menu.name}</div>
            {menu.children?.map((child: any) => (
              <div
                key={child.id}
                className={`fly-item ${isMenuActive(child) ? "on" : ""}`}
                onClick={() => handleFlyoutSelect(child)}
              >
                <span className="ic">{child.icon}</span>
                <span>{child.name}</span>
              </div>
            ))}
          </div>
        )}

        {/* 하위 메뉴 — 항상 렌더링, CSS로 높이 제어. inner div 로 감싸야 grid 0fr trick 이 동작함 */}
        {!sidebarCollapsed && menu.hasChildren && (
          <div className={`v5-submenu ${isExpanded ? "expanded" : ""}`}>
            <div className="v5-submenu-inner">
              {menu.children?.map((child: any, idx: number) => (
                <MenuItemActions
                  key={child.id}
                  menuUrl={!child.hasChildren ? child.url : undefined}
                  menuName={child.name}
                >
                  <div
                    draggable={!child.hasChildren}
                    onDragStart={(e) => handleMenuDragStart(e, child)}
                    className={`v5-si v5-sub-item ${isMenuActive(child) ? "on" : ""}`}
                    style={{ transitionDelay: isExpanded ? `${idx * 30}ms` : "0ms" }}
                    onClick={() => handleMenuClick(child)}
                  >
                    <span className="ic">{child.icon}</span>
                    <span className="truncate" title={child.name}>{child.name}</span>
                    {!child.hasChildren && (
                      <button
                        type="button"
                        onClick={(e) => {
                          e.stopPropagation();
                          toggleFavorite(child.id);
                        }}
                        className="ml-auto rounded p-0.5 transition-opacity hover:bg-[var(--v5-surface-hover)]"
                        title={isFavorite(child.id) ? "즐겨찾기 해제" : "즐겨찾기 추가"}
                        style={{
                          opacity: isFavorite(child.id) ? 1 : 0.35,
                          color: isFavorite(child.id) ? "var(--v5-primary)" : "var(--v5-text-muted)",
                        }}
                      >
                        <Star
                          className="h-3.5 w-3.5"
                          fill={isFavorite(child.id) ? "currentColor" : "none"}
                        />
                      </button>
                    )}
                  </div>
                </MenuItemActions>
              ))}
            </div>
          </div>
        )}
      </div>
    );
  };

  if (isPreviewMode) {
    return (
      <div className="bg-background h-screen w-full overflow-auto p-4">{children}</div>
    );
  }

  // 솔루션 전용 메뉴 필터는 백엔드 IS_SOLUTION_ONLY 컬럼 + Host 헤더 기반 SQL 필터로 위임 (PR #D).
  const uiMenus = user ? convertMenuToUI(currentMenus, user as ExtendedUserInfo) : [];

  // 활성 탭이 바뀔 때 한 번만 부모 메뉴 자동 확장.
  // expandedMenus 를 의존성에 넣으면 사용자가 수동으로 닫은 즉시 다시 펼쳐져 "닫히지 않는" 버그가 남.
  const autoExpandedForTabRef = useRef<string | number | null>(null);
  useEffect(() => {
    if (!activeTab || uiMenus.length === 0) return;
    if (autoExpandedForTabRef.current === activeTab.id) return;
    autoExpandedForTabRef.current = activeTab.id;

    const toExpand: string[] = [];
    for (const menu of uiMenus) {
      if (menu.hasChildren && menu.children) {
        const hasActiveChild = menu.children.some((child: any) => isMenuActive(child));
        if (hasActiveChild) toExpand.push(menu.id);
      }
    }
    if (toExpand.length > 0) {
      setExpandedMenus((prev) => {
        const next = new Set(prev);
        toExpand.forEach((id) => next.add(id));
        return next;
      });
    }
  }, [activeTab, uiMenus, isMenuActive]);

  // URL 직접 진입 / sessionStorage 복원 시 admin 탭의 영어 path-segment title 을
  // menu_name_kor (uiMenus 의 tabTitle/label/name) 로 갱신.
  // menu API 가 실패한 환경 (SUPER_ADMIN cross-tenant) 에서도 동작하도록 hardcoded map 도 같이 검사.
  useEffect(() => {
    const ADMIN_PATH_LABELS: Record<string, string> = {
      "/admin/userMng/deptMngList": "부서관리",
    };
    const store = useTabStore.getState();
    for (const tab of store.admin.tabs) {
      if (tab.type !== "admin" || !tab.admin_url) continue;
      const matched = uiMenus.find((m: any) => m.url === tab.admin_url);
      const koreanTitle: string | undefined =
        matched?.tabTitle || matched?.label || matched?.name || ADMIN_PATH_LABELS[tab.admin_url];
      if (koreanTitle && tab.title !== koreanTitle) {
        store.updateTabTitle(tab.id, koreanTitle);
      }
    }
  }, [uiMenus]);

  if (!user) {
    return (
      <div className="flex h-screen items-center justify-center">
        <div className="flex flex-col items-center">
          <div className="border-primary mb-4 h-8 w-8 animate-spin rounded-full border-4 border-t-transparent"></div>
          <p>로딩중...</p>
        </div>
      </div>
    );
  }

  // Admin permission check
  const isAdmin =
    (user as ExtendedUserInfo)?.isAdmin ||
    (user as ExtendedUserInfo)?.user_type === "SUPER_ADMIN" ||
    (user as ExtendedUserInfo)?.user_type === "COMPANY_ADMIN" ||
    (user as ExtendedUserInfo)?.user_type === "ADMIN" ||
    (user as ExtendedUserInfo)?.user_type === "admin";

  // Breadcrumb from active tab
  const breadcrumbText = activeTab?.title || "대시보드";

  return (
    <>
      {/* Theme fade overlay */}
      <div className="v5-theme-fade" id="v5-theme-fade" />

      {/* V5 Shell */}
      <div className={`v5-shell ${isAdminMode ? "v5-admin-mode" : ""}`}>
        {/* ===== Glass Header ===== */}
        <header className="v5-hdr">
          <div className="v5-hdr-l">
            {/* Mobile hamburger — 가로 모드(horizontal nav)에서는 데스크톱일 때 사이드바 자체가 없어 숨김 */}
            {(() => {
              const toggleHidden = !isMobile && navOrientation === "horizontal";
              return (
                <button
                  className="v5-mobile-toggle"
                  onClick={() => {
                    if (isMobile) setSidebarOpen(!sidebarOpen);
                    else setSidebarCollapsed(!sidebarCollapsed);
                  }}
                  title={isMobile ? (sidebarOpen ? "사이드바 닫기" : "사이드바 열기") : (sidebarCollapsed ? "사이드바 펼치기" : "사이드바 접기")}
                  aria-label="사이드바 토글"
                  aria-hidden={toggleHidden}
                  tabIndex={toggleHidden ? -1 : undefined}
                  style={{
                    opacity: toggleHidden ? 0 : 1,
                    transform: toggleHidden ? "scale(.7) translateX(-8px)" : "scale(1) translateX(0)",
                    width: toggleHidden ? 0 : undefined,
                    minWidth: toggleHidden ? 0 : undefined,
                    paddingLeft: toggleHidden ? 0 : undefined,
                    paddingRight: toggleHidden ? 0 : undefined,
                    marginRight: toggleHidden ? 0 : undefined,
                    borderWidth: toggleHidden ? 0 : undefined,
                    overflow: "hidden",
                    pointerEvents: toggleHidden ? "none" : undefined,
                    transition: "opacity .28s ease, transform .28s cubic-bezier(.22,1,.36,1), width .28s ease, min-width .28s ease, padding .28s ease, margin .28s ease, border-width .28s ease",
                  }}
                >
                  <Menu size={16} />
                </button>
              );
            })()}
            <div className="v5-hdr-logo">Invy.one</div>
            {navOrientation === "vertical" && (
              <>
                <div className="v5-hdr-bc">
                  {isAdminMode ? "관리자" : "홈"} &rsaquo; <b>{breadcrumbText}</b>
                </div>
                <div className="v5-admin-badge">
                  <div className="badge-dot" />
                  관리자 모드
                </div>
              </>
            )}
            {navOrientation === "horizontal" && (
              <TopNavBar
                menus={uiMenus}
                isMenuActive={isMenuActive}
                onSelect={handleMenuClick}
              />
            )}
          </div>
          {/* mode transition 헤더 glow 라인 — 평소엔 opacity 0, mode change 시에만 flash */}
          <div className="v5-hdr-glow" />
          <div className="v5-hdr-r">
            {/* 헤더 도구군 — 대시보드 페이지(/숫자)에서만 노출, 그 외는 전부 숨김.
                - 사용자 + 대시보드 페이지: [+ 대시보드 | 편집 | 제어] + 외부 구분선
                - 사용자 + 생 main (대시보드 아님): 숨김
                - 관리자 모드: 숨김 */}
            {!isAdminMode && pathname && /^\/\d+$/.test(pathname) && (
              <>
                <div className="ud-htools">
                  <button
                    className="ud-htool"
                    onClick={openDashCreate}
                    title="새 대시보드 만들기"
                  >
                    <Plus size={11} />
                    <span>대시보드</span>
                  </button>
                  {/* 편집 ON 시 왼쪽으로 펼쳐지는 [템플릿][저장] pop-out */}
                  <AnimatedHtoolGroup show={dashEditMode && !dashControlActive}>
                    <span className="v5-hdr-sep" aria-hidden="true" />
                    <button
                      className="ud-htool"
                      onClick={() => openDashLib()}
                      title="템플릿 추가"
                    >
                      <Plus size={11} />
                      <span>템플릿</span>
                    </button>
                    <span className="v5-hdr-sep" aria-hidden="true" />
                    <button
                      className="ud-htool"
                      onClick={() => window.dispatchEvent(new CustomEvent("dash:save"))}
                      title="레이아웃 저장"
                    >
                      <Save size={11} />
                      <span>저장</span>
                    </button>
                  </AnimatedHtoolGroup>
                  <span className="v5-hdr-sep" aria-hidden="true" />
                  <button
                    className={`ud-htool${dashEditMode ? " on" : ""}`}
                    onClick={toggleDashEditMode}
                    disabled={dashControlActive}
                    title={dashControlActive ? "제어 모드 중에는 편집 불가" : (dashEditMode ? "편집 종료" : "편집 모드")}
                  >
                    <Edit3 size={11} />
                    <span>{dashEditMode ? "편집중" : "편집"}</span>
                  </button>
                  <span className="v5-hdr-sep" aria-hidden="true" />
                  <button
                    className={`ud-htool${dashControlActive ? " on" : ""}`}
                    data-mode="ctrl"
                    onClick={() => {
                      if (!dashControlActive) setDashEditMode(false);
                      toggleDashControlMode();
                    }}
                    title={dashControlActive ? "제어 종료" : "제어 모드 — 데이터 흐름 시각화"}
                  >
                    <Zap size={11} />
                    <span>{dashControlActive ? "제어중" : "제어"}</span>
                  </button>
                </div>
                {/* 외부 구분선 — 부모 .v5-hdr-r 의 flex gap(.65rem) 이 양쪽에 붙는 걸
                    음수 margin 으로 상쇄해 내부 구분선과 폭 맞춤 */}
                <span
                  className="v5-hdr-sep"
                  aria-hidden="true"
                  style={{ margin: "0 -0.3rem" }}
                />
              </>
            )}

            {/* Theme toggle — single sun/moon icon (디자인시스템 준수, 2026-04-21) */}
            <button
              className="v5-hdr-icon"
              onClick={(e) => setNextTheme(theme === "dark" ? "light" : "dark", e)}
              title={theme === "dark" ? "라이트 모드로" : "다크 모드로"}
              aria-label="테마 전환"
            >
              {theme === "dark" ? <Sun size={16} /> : <Moon size={16} />}
            </button>

            {/* Mini tab icon (visible when tabs collapsed) */}
            {tabsCollapsed && (
              <button
                className="v5-tab-mini visible"
                onClick={() => setTabsCollapsed(false)}
                title="탭 펼치기"
              >
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="9" x2="21" y2="9"/><line x1="9" y1="3" x2="9" y2="9"/></svg>
                <span className="tab-count">{currentTabs.length}</span>
              </button>
            )}

            {/* Bell / Notifications */}
            <button className="v5-hdr-icon" title="알림" aria-label="알림">
              <Bell size={16} />
              <span className="v5-hdr-icon-dot" />
            </button>

            {/* Tweaks — 색상/테마 빠른 접근 (SettingsModal 오픈) */}
            <button
              ref={tweaksAnchorRef}
              className={`v5-hdr-icon${settingsOpen ? " on" : ""}`}
              onClick={() => setSettingsOpen((v) => !v)}
              title="Tweaks — 테마 / 색상"
              aria-label="설정"
            >
              <SlidersHorizontal size={16} />
            </button>

            {isAdmin && <span className="v5-hdr-sep" aria-hidden="true" />}

            {/* Admin toggle (gear ↔ home) */}
            {isAdmin && (
              <button
                className="v5-hdr-icon v5-mode-toggle"
                onClick={(e) => handleModeSwitch(e)}
                title={isAdminMode ? "홈으로" : "관리자 모드"}
                aria-label={isAdminMode ? "홈으로" : "관리자 모드"}
              >
                {isAdminMode ? <Home size={16} /> : <Shield size={16} />}
              </button>
            )}

            {/* Avatar dropdown */}
            <div className="v5-avatar-w">
              <DropdownMenu modal={false}>
                <DropdownMenuTrigger asChild>
                  <div className="v5-avatar">
                    {user.user_name?.substring(0, 1)?.toUpperCase() || "U"}
                  </div>
                </DropdownMenuTrigger>
                <DropdownMenuContent className="v5-avatar-dd-content w-56" align="end">
                  <DropdownMenuLabel className="font-normal">
                    <div className="flex items-center space-x-3">
                      <div className="bg-gradient-to-br from-[var(--v5-primary)] to-[var(--v5-cyan)] flex h-10 w-10 shrink-0 items-center justify-center rounded-full text-sm font-bold text-white">
                        {user.user_name?.substring(0, 1)?.toUpperCase() || "U"}
                      </div>
                      <div className="flex flex-col space-y-1">
                        <p className="text-sm leading-none font-medium">{user.user_name || "사용자"}</p>
                        <p className="text-muted-foreground text-xs leading-none">{user.email || user.user_id}</p>
                      </div>
                    </div>
                  </DropdownMenuLabel>
                  <DropdownMenuSeparator />
                  <DropdownMenuItem onClick={openProfileModal}>
                    <User className="mr-2 h-4 w-4" />
                    <span>내 정보</span>
                  </DropdownMenuItem>
                  <DropdownMenuItem onClick={() => setSettingsOpen(true)}>
                    <Settings className="mr-2 h-4 w-4" />
                    <span>설정</span>
                  </DropdownMenuItem>
                  <DropdownMenuItem onClick={() => router.push("/admin/approvalBox")}>
                    <FileCheck className="mr-2 h-4 w-4" />
                    <span>결재함</span>
                  </DropdownMenuItem>
                  <DropdownMenuItem onClick={handlePopModeClick}>
                    <Monitor className="mr-2 h-4 w-4" />
                    <span>POP 모드</span>
                  </DropdownMenuItem>
                  <DropdownMenuSeparator />
                  <DropdownMenuItem onClick={handleLogout} className="text-red-500 focus:text-red-500">
                    <LogOut className="mr-2 h-4 w-4" />
                    <span>로그아웃</span>
                  </DropdownMenuItem>
                </DropdownMenuContent>
              </DropdownMenu>
            </div>
          </div>
        </header>

        {/* ===== Tab Bar ===== */}
        <TabBar collapsed={tabsCollapsed} onToggleCollapse={() => setTabsCollapsed(!tabsCollapsed)} modeTransition={modeTransition} />

        {/* Mobile overlay */}
        {sidebarOpen && isMobile && (
          <div className="v5-side-overlay open" onClick={() => setSidebarOpen(false)} />
        )}

        {/* ===== Body (sidebar + content) ===== */}
        <div className={`v5-body ${navOrientation === "horizontal" ? "v5-body-horizontal" : ""}`}>
          {/* Sidebar — horizontal 모드에서는 Mobile 용(햄버거) 로만 살리고 데스크톱 표시는 숨김 */}
          {(navOrientation === "vertical" || isMobile) && (
          <aside className={`v5-side v5-side-anim ${isAdminMode ? "v5-admin-side" : ""} ${sidebarCollapsed ? "collapsed" : ""} ${isMobile ? (sidebarOpen ? "mobile-open" : "") : ""} ${modeTransition === "out" ? "slide-out" : modeTransition === "in" ? "slide-in" : ""}`}
            style={isMobile ? { position: "fixed", left: 0, top: 0, bottom: 0, zIndex: 30, paddingTop: "60px", width: "260px", transform: sidebarOpen ? "none" : "translateX(-100%)" } : undefined}
          >
            {/* SUPER_ADMIN company info (slim, borderless) */}
            {(user as ExtendedUserInfo)?.user_type === "SUPER_ADMIN" && !sidebarCollapsed && (
              <div className="flex items-center gap-2 px-2.5 py-1.5" title={currentCompanyName || ""}>
                <Building2 size={12} style={{ color: "var(--v5-primary)", flexShrink: 0 }} />
                <p className="truncate font-medium min-w-0 flex-1" style={{ fontSize: ".78rem", color: "var(--v5-text)" }}>
                  {currentCompanyName || "로딩 중..."}
                </p>
              </div>
            )}

            {/* Menu items */}
            <div className={`flex-1 ${sidebarCollapsed ? "overflow-visible" : "overflow-y-auto"}`} style={{ padding: "0" }}>
              <nav style={{ display: "flex", flexDirection: "column", gap: "1px", overflow: sidebarCollapsed ? "visible" : undefined }}>
                {loading ? (
                  <div className="animate-pulse space-y-2 p-3">
                    {[...Array(5)].map((_, i) => (
                      <div key={i} className="h-8 rounded" style={{ background: "var(--v5-surface)" }} />
                    ))}
                  </div>
                ) : (
                  <>
                    {(() => {
                      // 즐겨찾기 섹션: uiMenus 의 모든 leaf 중 즐겨찾기로 등록된 것만.
                      // uiMenus 기반이라 권한 없는 메뉴는 자동 제외되고 handleMenuClick 도 그대로 재사용.
                      const flattenLeaves = (nodes: any[]): any[] => {
                        const out: any[] = [];
                        for (const n of nodes) {
                          if (n.hasChildren && n.children?.length) out.push(...flattenLeaves(n.children));
                          else out.push(n);
                        }
                        return out;
                      };
                      const favMenus = flattenLeaves(uiMenus).filter((m) => isFavorite(m.id));
                      if (favMenus.length === 0) return null;
                      return (
                        <>
                          {!sidebarCollapsed && (
                            <div
                              className="flex items-center gap-1.5 px-2.5 py-1.5"
                              style={{ fontSize: "0.62rem", fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--v5-text-muted)" }}
                            >
                              <Star size={11} fill="currentColor" style={{ color: "var(--v5-primary)" }} />
                              <span>즐겨찾기</span>
                            </div>
                          )}
                          {favMenus.map((m) => (
                            <MenuItemActions key={`fav-${m.id}`} menuUrl={m.url} menuName={m.name}>
                              <div
                                className={`v5-si ${isMenuActive(m) ? "on" : ""}`}
                                title={m.name}
                                onClick={() => handleMenuClick(m)}
                              >
                                <span className="ic">{m.icon}</span>
                                {!sidebarCollapsed && (
                                  <>
                                    <span className="truncate" title={m.name}>{m.name}</span>
                                    <button
                                      type="button"
                                      onClick={(e) => {
                                        e.stopPropagation();
                                        toggleFavorite(m.id);
                                      }}
                                      className="ml-auto rounded p-0.5 transition-opacity hover:bg-[var(--v5-surface-hover)]"
                                      title="즐겨찾기 해제"
                                      style={{ color: "var(--v5-primary)" }}
                                    >
                                      <Star className="h-3.5 w-3.5" fill="currentColor" />
                                    </button>
                                  </>
                                )}
                              </div>
                            </MenuItemActions>
                          ))}
                          <div
                            style={{
                              height: "1px",
                              margin: "6px 8px",
                              background: "var(--v5-border)",
                              opacity: 0.6,
                            }}
                          />
                        </>
                      );
                    })()}
                    {uiMenus.map((menu) => renderMenu(menu))}
                  </>
                )}
              </nav>
            </div>

          </aside>
          )}

          {/* Content area */}
          {/* ★ INVYONE 신규 페이지는 children 직접 렌더, 기존 VEX 페이지는 탭 시스템 */}
          <main className="v5-content flex min-w-0 flex-1 flex-col overflow-hidden">
            {pathname && !isAdminMode && (
              pathname.startsWith('/dashboard') ||
              pathname.startsWith('/dash') ||
              pathname.startsWith('/admin/builder') ||
              pathname.startsWith('/test-fc') ||
              /^\/\d+$/.test(pathname)  // /숫자 = 신규 대시보드(메뉴 시퀀스) 페이지
            ) ? (
              // ★ flex 컨테이너로 만들어서 안쪽 dash-shell이 height:100% 잘 먹도록
              <div className="relative flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
                {children}
              </div>
            ) : (
              <TabContent />
            )}
          </main>
        </div>
      </div>

      <ProfileModal
        isOpen={isModalOpen}
        user={user}
        formData={formData}
        selectedImage={selectedImage || ""}
        isSaving={isSaving}
        departments={departments}
        alertModal={alertModal}
        onClose={closeProfileModal}
        onFormChange={updateFormData}
        onImageSelect={selectImage}
        onImageRemove={removeImage}
        onSave={saveProfile}
        onAlertClose={closeAlert}
      />

      <Dialog open={showCompanySwitcher} onOpenChange={setShowCompanySwitcher}>
        <DialogContent className="max-w-[95vw] sm:max-w-[600px]">
          <DialogHeader>
            <DialogTitle className="text-base sm:text-lg">회사 선택</DialogTitle>
            <DialogDescription className="text-xs sm:text-sm">
              관리할 회사를 선택하면 해당 회사의 관점에서 시스템을 사용할 수 있습니다.
            </DialogDescription>
          </DialogHeader>
          <div className="mt-4">
            <CompanySwitcher onClose={() => setShowCompanySwitcher(false)} isOpen={showCompanySwitcher} />
          </div>
        </DialogContent>
      </Dialog>

      <SettingsModal
        open={settingsOpen}
        onOpenChange={setSettingsOpen}
        anchorRef={tweaksAnchorRef}
        sidebarCollapsed={sidebarCollapsed}
        onSidebarCollapsedChange={setSidebarCollapsed}
        navOrientation={navOrientation}
        onNavOrientationChange={setNavOrientation}
      />

      {/* 전역 대시보드 생성/수정 통합 모달 — 헤더 "+ 대시보드" 버튼 + 우클릭 컨텍스트의 "수정" 둘 다 사용 */}
      <CreateDashboardModal
        open={dashCreateOpen || !!dashEditTarget}
        onClose={handleDashModalClose}
        onSubmit={handleDashModalSubmit}
        mode={dashEditTarget ? "edit" : "create"}
        defaultName={dashEditTarget?.name ?? "새 대시보드"}
        defaultIcon={dashEditTarget?.icon}
        defaultIsPersonal={dashEditTarget?.is_personal ?? false}
        submitting={dashCreateSubmitting}
      />
    </>
  );
}

export function AppLayout({ children }: AppLayoutProps) {
  return (
    <Suspense
      fallback={
        <div className="flex h-screen items-center justify-center">
          <div className="flex flex-col items-center">
            <div className="border-primary mb-4 h-8 w-8 animate-spin rounded-full border-4 border-t-transparent"></div>
            <p>로딩중...</p>
          </div>
        </div>
      }
    >
      <AppLayoutInner>{children}</AppLayoutInner>
    </Suspense>
  );
}
