import type { ComponentType } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { NavLink, Outlet, useLocation, useNavigate } from 'react-router-dom';
import { useAuthStore } from '../../store/authStore';
import type { Track } from '../../lib/roleTrack';
import { useThemeStore } from '../../store/themeStore';
import { useMdiStore } from '../../store/mdiStore';
import { useUiStore, isMobileViewport } from '../../store/uiStore';
import { useIsAdmin } from '../../screens/admin/AdminGuard';
import { MdiTabBar } from './MdiTabBar';
import { Breadcrumb } from './Breadcrumb';
import { ShellFooter } from './ShellFooter';
import { SettingsCustomizer } from './SettingsCustomizer';
import { labelForPath } from './mdiLabels';
import { useResolvedEventId } from '../../hooks/useResolvedEventId';
import { DdayChip } from '../ui/Badge';
import { LanguageSwitcher } from '../ui/LanguageSwitcher';
import {
  IconAnalytics,
  IconBell,
  IconCalendar,
  IconCheckCircle,
  IconChevronDown,
  IconClose,
  IconDashboard,
  IconDocument,
  IconExhibitors,
  IconFloorplan,
  IconGrid,
  IconHome,
  IconLogOut,
  IconMenu,
  IconMoon,
  IconOperations,
  IconSearch,
  IconSettings,
  IconSettlement,
  IconSun,
  IconUser,
  IconUsers,
  type IconProps,
} from '../ui/icons';
import './app-shell.css';

/**
 * 사이드바 항목. `to`가 있으면 react-router NavLink(활성 표시).
 * `scoped: true` 항목(대시보드·플로어플랜·참가업체)은 행사 스코프(:eventId) 라우트라
 * 정적 to 로 배선할 수 없다 → 클릭 시 useResolvedEventId 로 대상 행사를 해소해 계산된 경로로 이동한다.
 */
interface NavItem {
  key: string;
  /** 링크 라벨(또는 section=true 인 경우 소그룹 구분 헤더 텍스트) — i18n 미해소 시 폴백. */
  label: string;
  /** i18n 키(shell.menu.*). 없으면 label 을 그대로 사용. */
  labelKey?: string;
  /** 서브항목은 아이콘 대신 점(dot)으로 렌더 — Icon 은 메타(선택). */
  Icon?: ComponentType<IconProps>;
  to?: string;
  scoped?: boolean;
  /** true → 클릭 불가한 소그룹 구분 헤더(시스템관리 20항목 정리용). */
  section?: boolean;
  /** 노출 허용 트랙(미지정=전 트랙). plan §2-3 항목 단위 필터(예: 플로어플랜=agency 숨김). */
  tracks?: Track[];
}
interface NavGroupDef {
  id: string;
  label: string;
  labelKey?: string;
  Icon: ComponentType<IconProps>;
  items: NavItem[];
  /** 관리자 전용 그룹 — useIsAdmin() 시에만 노출. */
  admin?: boolean;
  /** 노출 허용 트랙(미지정=전 트랙). plan §2-3 그룹 노출 매트릭스. */
  tracks?: Track[];
}

/** plan §2-3 노출 매트릭스 — 트랙 상수(가독성용). system 은 admin 플래그로 별도 게이트. */
const BIZ_OPS_ADMIN: Track[] = ['business', 'ops', 'admin'];
const WORK_TRACKS: Track[] = ['business', 'ops', 'agency', 'admin'];

/** tracks 미지정(전 트랙 허용) 또는 현재 트랙 포함 시 노출. */
function trackAllows(tracks: Track[] | undefined, active: Track): boolean {
  return !tracks || tracks.includes(active);
}

/** 홈 — 로그인 후 랜딩(단일 최상위 링크, 아코디언 밖). */
const HOME_ITEM: NavItem = { key: 'home', label: '홈', labelKey: 'shell.menu.home', Icon: IconHome, to: '/home' };

/**
 * 전면 IA 재구성(소유자 지시 ③) — 평면 나열 폐기, 의미 그룹으로 재편.
 * 전 라우트 누락 없이 재배치. WISE menuTree 처럼 대분류 그룹 아코디언.
 * 라벨은 shell.menu.* i18n(4개 언어) 로 해소하고, label 은 폴백(ko).
 */
const GROUPS: NavGroupDef[] = [
  {
    id: 'ops',
    label: '전시 운영',
    labelKey: 'shell.menu.groups.ops',
    Icon: IconOperations,
    tracks: BIZ_OPS_ADMIN, // agency·visitor ✕
    items: [
      { key: 'dashboard', label: '대시보드', labelKey: 'shell.menu.items.dashboard', Icon: IconDashboard, scoped: true },
      { key: 'exhibitors', label: '참가업체 관리', labelKey: 'shell.menu.items.exhibitors', Icon: IconExhibitors, to: '/exhibitors' },
      { key: 'schedule', label: '전시 일정', labelKey: 'shell.menu.items.schedule', Icon: IconCalendar, to: '/schedule' },
      { key: 'halls', label: '홀 배정', labelKey: 'shell.menu.items.halls', Icon: IconGrid, to: '/halls' },
      { key: 'booth-sales', label: '부스 판매', labelKey: 'shell.menu.items.boothSales', Icon: IconSettlement, to: '/booth-sales' },
      { key: 'operations', label: '현장운영', labelKey: 'shell.menu.items.operations', Icon: IconOperations, to: '/ops/operations' },
    ],
  },
  {
    id: 'design',
    label: '설계·시공',
    labelKey: 'shell.menu.groups.design',
    Icon: IconFloorplan,
    tracks: WORK_TRACKS, // business·ops·admin ● + agency 부분(항목 단위)
    items: [
      // 플로어플랜 편집은 agency 숨김(주최자·홀매니저·관리자 전용).
      { key: 'floorplan', label: '플로어플랜 스튜디오', labelKey: 'shell.menu.items.floorplan', Icon: IconFloorplan, scoped: true, tracks: BIZ_OPS_ADMIN },
      { key: 'documents', label: '서류·마일스톤', labelKey: 'shell.menu.items.documents', Icon: IconDocument, to: '/docs' },
      { key: 'auctions', label: '공사 옥션', labelKey: 'shell.menu.items.auctions', Icon: IconSettlement, to: '/auctions' },
      { key: 'contractor', label: '수주 부스', labelKey: 'shell.menu.items.contractor', Icon: IconExhibitors, to: '/contractor/dashboard' },
      { key: 'logistics', label: '반입·반출', labelKey: 'shell.menu.items.logistics', Icon: IconOperations, to: '/logistics' },
    ],
  },
  {
    id: 'visitor',
    label: '관람객·마케팅',
    labelKey: 'shell.menu.groups.visitor',
    Icon: IconUsers,
    tracks: BIZ_OPS_ADMIN, // agency·visitor ✕
    items: [
      { key: 'visitors', label: '관람객', labelKey: 'shell.menu.items.visitors', Icon: IconUsers, to: '/visitors' },
      { key: 'checkin', label: '체크인 데스크', labelKey: 'shell.menu.items.checkin', Icon: IconCheckCircle, to: '/visitors/checkin' },
      { key: 'leads', label: '리드', labelKey: 'shell.menu.items.leads', Icon: IconExhibitors, to: '/leads' },
      { key: 'campaigns', label: '마케팅', labelKey: 'shell.menu.items.campaigns', Icon: IconBell, to: '/campaigns' },
      { key: 'sponsorship', label: '스폰서십', labelKey: 'shell.menu.items.sponsorship', Icon: IconSettlement, to: '/sponsorship' },
      { key: 'cms', label: '콘텐츠 CMS', labelKey: 'shell.menu.items.cms', Icon: IconDocument, to: '/cms' },
    ],
  },
  {
    id: 'finance',
    label: '정산·분석',
    labelKey: 'shell.menu.groups.finance',
    Icon: IconAnalytics,
    tracks: BIZ_OPS_ADMIN, // agency·visitor ✕
    items: [
      { key: 'settlement', label: '정산', labelKey: 'shell.menu.items.settlement', Icon: IconSettlement, to: '/settlement' },
      { key: 'analytics', label: '경영분석', labelKey: 'shell.menu.items.analytics', Icon: IconAnalytics, to: '/analytics' },
    ],
  },
  {
    id: 'work',
    label: '업무 공통',
    labelKey: 'shell.menu.groups.work',
    Icon: IconDocument,
    tracks: WORK_TRACKS, // 전 업무 트랙 ● (visitor ✕)
    items: [
      { key: 'worklog', label: '업무일지', labelKey: 'shell.menu.items.worklog', Icon: IconDocument, to: '/work/worklog' },
      { key: 'work-schedule', label: '일정', labelKey: 'shell.menu.items.workSchedule', Icon: IconCalendar, to: '/work/schedule' },
      { key: 'message', label: '쪽지', labelKey: 'shell.menu.items.message', Icon: IconUsers, to: '/work/message' },
      { key: 'notice', label: '공지', labelKey: 'shell.menu.items.notice', Icon: IconBell, to: '/work/notice' },
      { key: 'meeting', label: '회의록', labelKey: 'shell.menu.items.meeting', Icon: IconOperations, to: '/work/meeting' },
      { key: 'report', label: '업무보고', labelKey: 'shell.menu.items.report', Icon: IconAnalytics, to: '/work/report' },
      { key: 'approval', label: '결재', labelKey: 'shell.menu.items.approval', Icon: IconCheckCircle, to: '/work/approval' },
      { key: 'opinion', label: '의견접수', labelKey: 'shell.menu.items.opinion', Icon: IconCheckCircle, to: '/work/opinion' },
      { key: 'search', label: '통합검색', labelKey: 'shell.menu.items.search', Icon: IconGrid, to: '/work/search' },
      { key: 'app-qr', label: '모바일 앱 설치', labelKey: 'shell.menu.items.appqr', Icon: IconGrid, to: '/app-qr' },
    ],
  },
  {
    id: 'system',
    label: '시스템관리',
    labelKey: 'shell.menu.groups.system',
    Icon: IconSettings,
    admin: true,
    items: [
      { key: 'admin', label: '관리 대시보드', labelKey: 'shell.menu.items.admin', Icon: IconDashboard, to: '/admin' },
      // 사용자·조직
      { key: 'sec-userorg', label: '사용자·조직', labelKey: 'shell.menu.sections.userOrg', section: true },
      { key: 'admin-users', label: '사용자 관리', labelKey: 'shell.menu.items.users', Icon: IconUsers, to: '/admin/users' },
      { key: 'admin-roles', label: '역할/권한', labelKey: 'shell.menu.items.roles', Icon: IconCheckCircle, to: '/admin/roles' },
      { key: 'admin-depts', label: '조직 관리', labelKey: 'shell.menu.items.depts', Icon: IconUsers, to: '/admin/depts' },
      { key: 'admin-companies', label: '회사 관리', labelKey: 'shell.menu.items.companies', Icon: IconExhibitors, to: '/admin/companies' },
      // 메뉴·프로그램·코드
      { key: 'sec-menuauth', label: '메뉴·프로그램·코드', labelKey: 'shell.menu.sections.menuAuth', section: true },
      { key: 'admin-menus', label: '메뉴 관리', labelKey: 'shell.menu.items.menus', Icon: IconDocument, to: '/admin/menus' },
      { key: 'admin-programs', label: '프로그램 관리', labelKey: 'shell.menu.items.programs', Icon: IconGrid, to: '/admin/programs' },
      { key: 'admin-role-menus', label: '역할-메뉴 매핑', labelKey: 'shell.menu.items.roleMenus', Icon: IconCheckCircle, to: '/admin/role-menus' },
      { key: 'admin-codes', label: '공통코드', labelKey: 'shell.menu.items.codes', Icon: IconGrid, to: '/admin/codes' },
      // 정책·이력·로그
      { key: 'sec-policylog', label: '정책·이력·로그', labelKey: 'shell.menu.sections.policyLog', section: true },
      { key: 'admin-auth-policy', label: '인증 정책', labelKey: 'shell.menu.items.authPolicy', Icon: IconSettings, to: '/admin/auth-policy' },
      { key: 'admin-login-history', label: '로그인 이력', labelKey: 'shell.menu.items.loginHistory', Icon: IconDocument, to: '/admin/login-history' },
      { key: 'admin-audit', label: '감사로그', labelKey: 'shell.menu.items.audit', Icon: IconDocument, to: '/admin/audit' },
      { key: 'admin-error-log', label: '오류 로그', labelKey: 'shell.menu.items.errorLog', Icon: IconDocument, to: '/admin/error-log' },
      // 상태·설정
      { key: 'sec-statusconfig', label: '상태·설정', labelKey: 'shell.menu.sections.statusConfig', section: true },
      { key: 'admin-system-health', label: '시스템 상태', labelKey: 'shell.menu.items.systemHealth', Icon: IconAnalytics, to: '/admin/system-health' },
      { key: 'admin-settings', label: '시스템설정', labelKey: 'shell.menu.items.settings', Icon: IconSettings, to: '/admin/settings' },
      { key: 'admin-mail-config', label: '메일 설정', labelKey: 'shell.menu.items.mailConfig', Icon: IconBell, to: '/admin/mail-config' },
      { key: 'admin-notify-config', label: '알림 설정', labelKey: 'shell.menu.items.notifyConfig', Icon: IconBell, to: '/admin/notify-config' },
      { key: 'admin-messages', label: '메시지 관리', labelKey: 'shell.menu.items.msgTemplates', Icon: IconDocument, to: '/admin/messages' },
      // 마스터·기타
      { key: 'sec-masteretc', label: '마스터·기타', labelKey: 'shell.menu.sections.masterEtc', section: true },
      { key: 'admin-tenants', label: '테넌트', labelKey: 'shell.menu.items.tenants', Icon: IconExhibitors, to: '/admin/tenants' },
      { key: 'admin-rulesets', label: '룰셋', labelKey: 'shell.menu.items.rulesets', Icon: IconOperations, to: '/admin/masterdata/rulesets' },
      { key: 'admin-holidays', label: '공휴일 관리', labelKey: 'shell.menu.items.holidays', Icon: IconCalendar, to: '/admin/holidays' },
      { key: 'admin-slides', label: '로그인 슬라이드', labelKey: 'shell.menu.items.slides', Icon: IconBell, to: '/admin/login-slides' },
    ],
  },
];

/**
 * 행사 스코프 항목 → 실제 경로 빌더(App.tsx 라우트와 정합).
 * 플로어플랜 홀은 워크스페이스 hallLabel 에서 파싱(예: "제2전시장 홀7" → H7), 실패 시 'H7' 폴백.
 * (#41: 참가업체 관리는 전용 라우트 `/exhibitors` 신설 — 더 이상 스코프 폴백 대상 아님.)
 */
const EVENT_SCOPED_PATH: Record<string, (eventId: string, hallId: string) => string> = {
  dashboard: (id) => `/events/${id}/dashboard`,
  floorplan: (id, hallId) => `/events/${id}/halls/${hallId}/layout`,
};

/** hallLabel("제2전시장 홀7"·"홀 10" 등) → 홀 id("H7"). 파싱 불가 시 기본 H7. */
function hallIdFromLabel(label: string | null | undefined): string {
  const m = /홀\s*(\d+)/.exec(label ?? '');
  return m ? `H${m[1]}` : 'H7';
}

/**
 * 메뉴 검색(WISE NavSearch/filterMenuTree 패턴 이식) — 검색어로 그룹을 prune.
 * 매칭: 항목 라벨(i18n 해소값) 부분일치(대소문자 무시). section 헤더는 결과에서 제외.
 * 매칭 항목을 보유한 그룹만, 매칭 항목만 담아 반환(WISE 는 매칭 그룹 force-open 렌더).
 * resolveLabel 로 i18n 해소값을 넘겨 4개 언어 라벨 모두 검색 가능.
 */
interface FilteredGroup {
  group: NavGroupDef;
  items: NavItem[];
}
function filterGroups(
  groups: NavGroupDef[],
  query: string,
  resolveLabel: (item: NavItem) => string,
): FilteredGroup[] {
  const nq = query.trim().toLowerCase();
  if (!nq) return [];
  const out: FilteredGroup[] = [];
  for (const group of groups) {
    const items = group.items.filter(
      (item) => !item.section && resolveLabel(item).toLowerCase().includes(nq),
    );
    if (items.length) out.push({ group, items });
  }
  return out;
}

/** 라벨을 검색어 매칭 구간에서 분할(하이라이트용). 미매칭 시 [{text,hit:false}] 단일. */
function highlightParts(label: string, query: string): { text: string; hit: boolean }[] {
  const nq = query.trim().toLowerCase();
  if (!nq) return [{ text: label, hit: false }];
  const parts: { text: string; hit: boolean }[] = [];
  const lower = label.toLowerCase();
  let i = 0;
  while (i < label.length) {
    const idx = lower.indexOf(nq, i);
    if (idx < 0) {
      parts.push({ text: label.slice(i), hit: false });
      break;
    }
    if (idx > i) parts.push({ text: label.slice(i, idx), hit: false });
    parts.push({ text: label.slice(idx, idx + nq.length), hit: true });
    i = idx + nq.length;
  }
  return parts;
}

/** 현재 경로가 속한 대분류 그룹 id(활성 그룹 자동 펼침용). 정적 to 항목만 매칭. */
function activeGroupId(pathname: string): string | null {
  for (const g of GROUPS) {
    for (const it of g.items) {
      if (it.to && (pathname === it.to || pathname.startsWith(`${it.to}/`))) return g.id;
    }
  }
  return null;
}

/**
 * 웹 인증 셸 (design.md §2-2 · WISE AppLayout 구조 이식) —
 * 좌측 사이드바(브랜드 로고 + 그룹 아코디언 + 하단 사용자) + 상단 바(햄버거·브레드크럼·D-데이·알림).
 * 사이드바는 nav-collapsed 토글로 접힘(데스크톱 숨김 / <1024px 오버레이 드로어 + 백드롭).
 */
export function AppShell() {
  const user = useAuthStore((s) => s.user);
  const workspace = useAuthStore((s) => s.currentWorkspace());
  const logout = useAuthStore((s) => s.logout);
  const themeResolved = useThemeStore((s) => s.resolved);
  const toggleTheme = useThemeStore((s) => s.toggle);
  const navCollapsed = useUiStore((s) => s.navCollapsed);
  const toggleNav = useUiStore((s) => s.toggleNav);
  const setNavCollapsed = useUiStore((s) => s.setNavCollapsed);
  const navigate = useNavigate();
  const location = useLocation();
  const openTab = useMdiStore((s) => s.openTab);
  const resolvedEventId = useResolvedEventId();
  const isAdmin = useIsAdmin();
  const activeTrack = useAuthStore((s) => s.activeTrack());
  const isDark = themeResolved === 'dark';
  const { t } = useTranslation();

  /** 메뉴 라벨 해소 — shell.menu.* i18n(4개 언어), 미해소 시 ko 폴백(label). */
  const navLabel = (labelKey: string | undefined, fallback: string) =>
    labelKey ? t(labelKey, { defaultValue: fallback }) : fallback;

  // 단일-열림 아코디언(소유자 지시 ②) — 한 번에 대분류 하나만 펼침. WISE LeftNav 동작 그대로.
  const [openGroup, setOpenGroup] = useState<string | null>(() => activeGroupId(location.pathname));

  // 메뉴 검색(WISE NavSearch 패턴) — 검색어는 셸 로컬 state(신규 store 없음).
  const [menuQuery, setMenuQuery] = useState('');
  const searching = menuQuery.trim().length > 0;
  const clearMenuQuery = () => setMenuQuery('');

  // 설정 아이콘 → 우측 레이아웃 커스터마이저(#43) offcanvas 열림.
  const [customizerOpen, setCustomizerOpen] = useState(false);

  /**
   * 라우트 → MDI 탭 동기화(§2.7) + 활성 그룹 자동 펼침.
   * 화면 이동 시 활성 대분류를 펼친다(라우트=단일 출처). 비메뉴 경로는 현 상태 유지.
   */
  useEffect(() => {
    openTab(location.pathname, labelForPath(location.pathname));
    const active = activeGroupId(location.pathname);
    if (active) setOpenGroup(active);
  }, [location.pathname, openTab]);

  const toggleGroup = (id: string) => setOpenGroup((prev) => (prev === id ? null : id));

  /** 링크 이동 시 모바일이면 드로어 닫기(WISE maybeCloseOnMobile). */
  const closeOnMobile = () => {
    if (isMobileViewport()) setNavCollapsed(true);
  };

  /**
   * 행사 스코프 항목 진입 — 대상 행사 해소 시 계산 경로로.
   * 참여 행사가 없으면 홈(워크스페이스 섹션·초대 안내)으로 보낸다 — 인증 사용자를 /login 으로 되돌리지 않는다(2026-07-12 소유자 링크 오류 지적).
   */
  function goScoped(key: string) {
    const build = EVENT_SCOPED_PATH[key];
    if (resolvedEventId && build) {
      const ws = useAuthStore.getState().workspaces.find((w) => w.eventId === resolvedEventId);
      navigate(build(resolvedEventId, hallIdFromLabel(ws?.hallLabel)));
    } else {
      navigate('/home');
    }
    closeOnMobile();
  }

  /**
   * 리프 항목 렌더(링크 또는 스코프 버튼). highlight=true 시 검색어 매칭 구간을 <mark>로 강조.
   * 검색 결과 클릭 시 검색어를 지워 아코디언으로 복귀(WISE onNavigate=clearQuery).
   */
  const renderLeaf = (item: NavItem, highlight: boolean) => {
    const label = navLabel(item.labelKey, item.label);
    const body = highlight
      ? highlightParts(label, menuQuery).map((p, i) =>
          p.hit ? (
            <mark key={i} className="kx-shell__nav-hl">{p.text}</mark>
          ) : (
            <span key={i}>{p.text}</span>
          ),
        )
      : label;
    const onNav = () => {
      if (highlight) clearMenuQuery();
      closeOnMobile();
    };
    return item.to ? (
      <NavLink
        to={item.to}
        className={({ isActive }) => `kx-shell__nav-link ${isActive ? 'is-active' : ''}`}
        onClick={onNav}
      >
        <span className="kx-shell__nav-dot" aria-hidden="true" />
        <span>{body}</span>
      </NavLink>
    ) : (
      <button
        type="button"
        className="kx-shell__nav-link kx-shell__nav-link--btn"
        onClick={() => {
          if (highlight) clearMenuQuery();
          goScoped(item.key);
        }}
      >
        <span className="kx-shell__nav-dot" aria-hidden="true" />
        <span>{body}</span>
      </button>
    );
  };

  // 관리자 그룹은 isAdmin 게이트 유지 + 트랙 노출 매트릭스(plan §2-3). 미지정 그룹=전 트랙.
  const visibleGroups = GROUPS.filter(
    (g) => (!g.admin || isAdmin) && trackAllows(g.tracks, activeTrack),
  );

  /**
   * 검색 결과 — 전체 대분류 횡단(global), 항목 라벨(i18n) 부분일치.
   * 항목 단위 트랙 게이트(trackAllows) 유지 → 노출 규칙 회귀 없음.
   * (visibleGroups·menuQuery·activeTrack·언어 변경 시 재계산)
   */
  const searchResults = useMemo(
    () =>
      filterGroups(visibleGroups, menuQuery, (item) => navLabel(item.labelKey, item.label)).map(
        ({ group, items }) => ({
          group,
          items: items.filter((item) => trackAllows(item.tracks, activeTrack)),
        }),
      ).filter(({ items }) => items.length > 0),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [visibleGroups, menuQuery, activeTrack, t],
  );

  return (
    <div className={`kx-shell ${navCollapsed ? 'is-nav-collapsed' : ''}`}>
      <a className="kx-skip-link" href="#kx-main-content">
        본문 바로가기
      </a>

      <nav className="kx-shell__sidebar" aria-label="주 메뉴">
        {/* 브랜드 — 실제 KINTEX CI 워드마크(소유자 지시 ①, 다크 대비 화이트 플레이트) */}
        <NavLink to="/home" className="kx-shell__brand" onClick={closeOnMobile} title="KINTEX AI">
          <img
            src="/brand/kintex-wordmark.jpg"
            alt="KINTEX"
            className="kx-shell__brand-logo"
            draggable={false}
          />
          <span className="kx-shell__brand-subs">
            <small className="kx-shell__brand-sub">AI 전시·행사시스템</small>
            <small className="kx-shell__brand-sub kx-shell__brand-sub--en">
              AI Exhibition &amp; Event System
            </small>
          </span>
        </NavLink>

        {workspace && (
          <button className="kx-shell__event-switch" type="button">
            <span className="kx-shell__event-name">{workspace.eventName}</span>
            <span className="kx-shell__event-meta">{workspace.hallLabel}</span>
          </button>
        )}

        {/* 메뉴 검색(WISE NavSearch 이식) — 브랜드/행사 아래 고정(스크롤 영역 위, flex:none). */}
        <div className="kx-shell__nav-search" role="search">
          <IconSearch size={16} className="kx-shell__nav-search-icon" aria-hidden="true" />
          <input
            type="text"
            className="kx-shell__nav-search-input"
            value={menuQuery}
            placeholder={t('shell.menu.search.placeholder', { defaultValue: '메뉴 검색' })}
            aria-label={t('shell.menu.search.placeholder', { defaultValue: '메뉴 검색' })}
            onChange={(e) => setMenuQuery(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === 'Escape' && menuQuery) {
                e.preventDefault();
                clearMenuQuery();
              }
            }}
          />
          {menuQuery && (
            <button
              type="button"
              className="kx-shell__nav-search-clear"
              aria-label={t('shell.menu.search.clear', { defaultValue: '검색어 지우기' })}
              onClick={clearMenuQuery}
            >
              <IconClose size={15} aria-hidden="true" />
            </button>
          )}
        </div>

        <div className="kx-shell__nav-scroll">
          {searching ? (
            /* 검색 — 전체 대분류 횡단(global) 매칭 항목만, 매칭 그룹 force-open(WISE 패턴). 하이라이트. */
            searchResults.length === 0 ? (
              <div className="kx-shell__nav-empty">
                {t('shell.menu.search.noResult', { defaultValue: '일치하는 메뉴가 없습니다' })}
              </div>
            ) : (
              searchResults.map(({ group, items }) => (
                <div className="kx-shell__group" key={group.id}>
                  <div className="kx-shell__group-title kx-shell__group-title--static">
                    <span className="kx-shell__nav-icon"><group.Icon size={18} /></span>
                    <span className="kx-shell__group-label">{navLabel(group.labelKey, group.label)}</span>
                  </div>
                  <ul className="kx-shell__nav kx-shell__nav--sub">
                    {items.map((item) => (
                      <li key={item.key}>{renderLeaf(item, true)}</li>
                    ))}
                  </ul>
                </div>
              ))
            )
          ) : (
          <>
          {/* 홈 — 최상위 단일 링크 */}
          <ul className="kx-shell__nav">
            <li>
              <NavLink
                to={HOME_ITEM.to!}
                className={({ isActive }) =>
                  `kx-shell__nav-link kx-shell__nav-link--top ${isActive ? 'is-active' : ''}`
                }
                onClick={closeOnMobile}
              >
                <span className="kx-shell__nav-icon">{HOME_ITEM.Icon && <HOME_ITEM.Icon size={18} />}</span>
                {navLabel(HOME_ITEM.labelKey, HOME_ITEM.label)}
              </NavLink>
            </li>
          </ul>

          {/* 대분류 그룹 아코디언 */}
          {visibleGroups.map((group) => {
            const expanded = openGroup === group.id;
            return (
              <div className="kx-shell__group" key={group.id}>
                <button
                  type="button"
                  className="kx-shell__group-title"
                  aria-expanded={expanded}
                  onClick={() => toggleGroup(group.id)}
                >
                  <span className="kx-shell__nav-icon"><group.Icon size={18} /></span>
                  <span className="kx-shell__group-label">{navLabel(group.labelKey, group.label)}</span>
                  <IconChevronDown
                    size={15}
                    className={`kx-shell__group-caret ${expanded ? 'is-open' : ''}`}
                  />
                </button>
                {expanded && (
                  <ul className="kx-shell__nav kx-shell__nav--sub">
                    {group.items
                      .filter((item) => trackAllows(item.tracks, activeTrack))
                      .map((item) =>
                      item.section ? (
                        // 소그룹 구분 헤더(클릭 불가) — 시스템관리 20항목 정리(WISE 구분선 패턴).
                        <li key={item.key} className="kx-shell__nav-section" aria-hidden="true">
                          {navLabel(item.labelKey, item.label)}
                        </li>
                      ) : (
                        <li key={item.key}>{renderLeaf(item, false)}</li>
                      ),
                    )}
                  </ul>
                )}
              </div>
            );
          })}
          </>
          )}
        </div>

        {/* 하단 사용자 영역 — 반응형 재정렬(소유자 지적 3): 프로필 행 + 액션 아이콘 행. */}
        <div className="kx-shell__user">
          <button
            className="kx-shell__user-profile"
            type="button"
            aria-label="마이페이지"
            onClick={() => { navigate('/me'); closeOnMobile(); }}
          >
            <span className="kx-shell__avatar" aria-hidden="true">
              {user?.displayName?.[0] ?? <IconUser size={16} />}
            </span>
            <span className="kx-shell__user-name">{user?.displayName ?? '사용자'}</span>
          </button>
          <div className="kx-shell__user-actions">
            <button
              className="kx-shell__user-action"
              type="button"
              aria-label={isDark ? '라이트 모드로 전환' : '다크 모드로 전환'}
              aria-pressed={isDark}
              title={isDark ? '라이트 모드로 전환' : '다크 모드로 전환'}
              onClick={toggleTheme}
            >
              {isDark ? <IconSun size={18} /> : <IconMoon size={18} />}
            </button>
            <LanguageSwitcher variant="light" className="kx-shell__user-lang" />
            <button
              className="kx-shell__user-action"
              type="button"
              aria-label="로그아웃"
              title="로그아웃"
              onClick={logout}
            >
              <IconLogOut size={18} />
            </button>
          </div>
        </div>
      </nav>

      {/* 모바일 드로어 백드롭 — nav 열림 시에만(데스크톱은 CSS 로 미표시). */}
      {!navCollapsed && (
        <div
          className="kx-shell__backdrop"
          onClick={() => setNavCollapsed(true)}
          aria-hidden="true"
        />
      )}

      <div className="kx-shell__main">
        <header className="kx-shell__topbar">
          <div className="kx-shell__topbar-left">
            <button
              type="button"
              className="kx-shell__nav-toggle"
              aria-label={navCollapsed ? '메뉴 열기' : '메뉴 닫기'}
              aria-expanded={!navCollapsed}
              onClick={toggleNav}
            >
              <IconMenu size={20} />
            </button>
            <div className="kx-shell__crumb">
              <span className="kx-shell__crumb-root">{workspace?.eventName ?? '워크스페이스'}</span>
              <span className="kx-shell__crumb-sep">/</span>
              <span className="kx-shell__crumb-cur">{labelForPath(location.pathname)}</span>
            </div>
          </div>
          <div className="kx-shell__topbar-right">
            {workspace && <DdayChip dday={workspace.dday} />}
            <button
              className="kx-shell__icon-btn"
              type="button"
              aria-label="알림"
              title="알림"
              onClick={() => navigate('/notifications')}
            >
              <IconBell size={20} />
            </button>
            <button
              className="kx-shell__icon-btn"
              type="button"
              aria-label="화면 설정"
              title="화면 설정"
              aria-haspopup="dialog"
              aria-expanded={customizerOpen}
              onClick={() => setCustomizerOpen(true)}
            >
              <IconSettings size={20} />
            </button>
          </div>
        </header>

        <MdiTabBar />

        {/* 브레드크럼 — 콘텐츠 영역 상단(홈>섹션>현재, 조각 클릭 이동 · 4.4). */}
        <Breadcrumb />

        <main className="kx-shell__content" id="kx-main-content">
          <Outlet />
        </main>

        {/* 전 셸 화면 공통 하단 상태바 (design.md §2-2) */}
        <ShellFooter />
      </div>

      {/* 설정 아이콘 → 우측 레이아웃 커스터마이저(테마·레이아웃·강조색, #43) */}
      <SettingsCustomizer open={customizerOpen} onClose={() => setCustomizerOpen(false)} />
    </div>
  );
}
