import React, { useEffect, useMemo, useRef, useState } from 'react'
import { Layout, Menu, theme, Avatar, Dropdown, Space, Modal, Form, Input, message, Breadcrumb, ConfigProvider, Tabs } from 'antd'
import { useNavigate, useLocation, useOutlet } from 'react-router-dom'
import { HomeOutlined, LogoutOutlined, TeamOutlined, SettingOutlined, AppstoreOutlined, KeyOutlined, UserOutlined, DashboardOutlined, FileOutlined, LockOutlined, MenuOutlined, PieChartOutlined, BarChartOutlined, TableOutlined, CalendarOutlined, FormOutlined, SearchOutlined, ToolOutlined, ShoppingCartOutlined, ShopOutlined, FolderOpenOutlined, FolderOutlined, CloudOutlined, DatabaseOutlined, ApiOutlined, CodeOutlined, BugOutlined, BellOutlined, PlusOutlined, EditOutlined, DeleteOutlined, UploadOutlined, DownloadOutlined, EyeOutlined, EyeInvisibleOutlined, StarOutlined, HeartOutlined, LikeOutlined, DislikeOutlined, SmileOutlined, FrownOutlined, PhoneOutlined, MailOutlined, EnvironmentOutlined, GlobalOutlined, AimOutlined, CompassOutlined, CameraOutlined, VideoCameraOutlined, SoundOutlined, WifiOutlined, RocketOutlined, ThunderboltOutlined, ExperimentOutlined, BulbOutlined, GiftOutlined, BankOutlined, WalletOutlined, MoneyCollectOutlined, BookOutlined, ReadOutlined, ProfileOutlined, CloudUploadOutlined, CloudDownloadOutlined, InboxOutlined, FolderAddOutlined, SlidersOutlined, FilterOutlined, AlertOutlined, ClockCircleOutlined, FieldTimeOutlined, HistoryOutlined, ContactsOutlined, SolutionOutlined, IdcardOutlined, QrcodeOutlined, ScanOutlined, SafetyOutlined, SecurityScanOutlined, UnlockOutlined, HddOutlined, CopyOutlined, ScissorOutlined, SnippetsOutlined, FileProtectOutlined, DesktopOutlined, LaptopOutlined, MobileOutlined, TabletOutlined, ClusterOutlined, AppstoreAddOutlined, PlusSquareOutlined, SyncOutlined, ReloadOutlined } from '@ant-design/icons'
import { clearToken, getUser as getUserLocal, setUser as setUserLocal } from '../utils/token'
import api from '../utils/axios'
import './layout.css'
import { PermissionProvider } from '../utils/permission'
import { APP_CONFIG } from '../utils/config'
const { Header, Sider, Content, Footer } = Layout

interface MenuItemResp { id: number; parent_id?: number | null; name: string; path?: string | null; component?: string | null; type: number; icon?: string | null; order_no: number; visible: boolean; status: number; perms?: string | null; keep_alive?: boolean }

import zhCN from 'antd/locale/zh_CN'
import enUS from 'antd/locale/en_US'

export default function MainLayout() {
  const navigate = useNavigate()
  const loc = useLocation()
  const {
    token: { colorBgContainer, borderRadiusLG },
  } = theme.useToken()

  // 简易语言切换（存储到 localStorage）
  const [lang, setLang] = useState<'zh' | 'en'>(() => (localStorage.getItem('lang') === 'en' ? 'en' : 'zh'))
  const localeObj = lang === 'zh' ? zhCN : enUS
  const [user, setUser] = useState(() => getUserLocal())
  const [profileOpen, setProfileOpen] = useState(false)
  const [form] = Form.useForm()

  const [rawMenus, setRawMenus] = useState([] as MenuItemResp[])
  const [menuItems, setMenuItems] = useState([] as any[])

  // 从菜单提取权限编码集合（后端通过菜单返回 perms，包含页面/按钮权限）
  const permissionCodes = useMemo(() => {
    const set = new Set<string>()
    rawMenus.forEach((m: MenuItemResp) => {
      const p = m.perms
      if (p && typeof p === 'string') {
        p.split(',').map(s => s.trim()).filter(Boolean).forEach(code => set.add(code))
      }
    })
    return set
  }, [rawMenus])

  const iconMap: Record<string, any> = useMemo(() => ({
    HomeOutlined: <HomeOutlined />,
    UserOutlined: <UserOutlined />,
    TeamOutlined: <TeamOutlined />,
    SettingOutlined: <SettingOutlined />,
    AppstoreOutlined: <AppstoreOutlined />,
    KeyOutlined: <KeyOutlined />,
    DashboardOutlined: <DashboardOutlined />,
    FileOutlined: <FileOutlined />,
    LockOutlined: <LockOutlined />,
    MenuOutlined: <MenuOutlined />,
    PieChartOutlined: <PieChartOutlined />,
    BarChartOutlined: <BarChartOutlined />,
    TableOutlined: <TableOutlined />,
    CalendarOutlined: <CalendarOutlined />,
    FormOutlined: <FormOutlined />,
    SearchOutlined: <SearchOutlined />,
    ToolOutlined: <ToolOutlined />,
    ShoppingCartOutlined: <ShoppingCartOutlined />,
    ShopOutlined: <ShopOutlined />,
    FolderOpenOutlined: <FolderOpenOutlined />,
    FolderOutlined: <FolderOutlined />,
    CloudOutlined: <CloudOutlined />,
    DatabaseOutlined: <DatabaseOutlined />,
    ApiOutlined: <ApiOutlined />,
    CodeOutlined: <CodeOutlined />,
    BugOutlined: <BugOutlined />,
    BellOutlined: <BellOutlined />,
    // 新增常用图标映射
    PlusOutlined: <PlusOutlined />,
    EditOutlined: <EditOutlined />,
    DeleteOutlined: <DeleteOutlined />,
    UploadOutlined: <UploadOutlined />,
    DownloadOutlined: <DownloadOutlined />,
    EyeOutlined: <EyeOutlined />,
    EyeInvisibleOutlined: <EyeInvisibleOutlined />,
    StarOutlined: <StarOutlined />,
    HeartOutlined: <HeartOutlined />,
    LikeOutlined: <LikeOutlined />,
    DislikeOutlined: <DislikeOutlined />,
    SmileOutlined: <SmileOutlined />,
    FrownOutlined: <FrownOutlined />,
    PhoneOutlined: <PhoneOutlined />,
    MailOutlined: <MailOutlined />,
    EnvironmentOutlined: <EnvironmentOutlined />,
    GlobalOutlined: <GlobalOutlined />,
    AimOutlined: <AimOutlined />,
    CompassOutlined: <CompassOutlined />,
    CameraOutlined: <CameraOutlined />,
    VideoCameraOutlined: <VideoCameraOutlined />,
    SoundOutlined: <SoundOutlined />,
    WifiOutlined: <WifiOutlined />,
    RocketOutlined: <RocketOutlined />,
    ThunderboltOutlined: <ThunderboltOutlined />,
    ExperimentOutlined: <ExperimentOutlined />,
    BulbOutlined: <BulbOutlined />,
    GiftOutlined: <GiftOutlined />,
    BankOutlined: <BankOutlined />,
    WalletOutlined: <WalletOutlined />,
    MoneyCollectOutlined: <MoneyCollectOutlined />,
    BookOutlined: <BookOutlined />,
    ReadOutlined: <ReadOutlined />,
    ProfileOutlined: <ProfileOutlined />,
    CloudUploadOutlined: <CloudUploadOutlined />,
    CloudDownloadOutlined: <CloudDownloadOutlined />,
    InboxOutlined: <InboxOutlined />,
    FolderAddOutlined: <FolderAddOutlined />,
    SlidersOutlined: <SlidersOutlined />,
    FilterOutlined: <FilterOutlined />,
    AlertOutlined: <AlertOutlined />,
    ClockCircleOutlined: <ClockCircleOutlined />,
    FieldTimeOutlined: <FieldTimeOutlined />,
    HistoryOutlined: <HistoryOutlined />,
    ContactsOutlined: <ContactsOutlined />,
    SolutionOutlined: <SolutionOutlined />,
    IdcardOutlined: <IdcardOutlined />,
    QrcodeOutlined: <QrcodeOutlined />,
    ScanOutlined: <ScanOutlined />,
    SafetyOutlined: <SafetyOutlined />,
    SecurityScanOutlined: <SecurityScanOutlined />,
    UnlockOutlined: <UnlockOutlined />,
    HddOutlined: <HddOutlined />,
    CopyOutlined: <CopyOutlined />,
    ScissorOutlined: <ScissorOutlined />,
    SnippetsOutlined: <SnippetsOutlined />,
    FileProtectOutlined: <FileProtectOutlined />,
    DesktopOutlined: <DesktopOutlined />,
    LaptopOutlined: <LaptopOutlined />,
    MobileOutlined: <MobileOutlined />,
    TabletOutlined: <TabletOutlined />,
    ClusterOutlined: <ClusterOutlined />,
    AppstoreAddOutlined: <AppstoreAddOutlined />,
    PlusSquareOutlined: <PlusSquareOutlined />,
    SyncOutlined: <SyncOutlined />,
    ReloadOutlined: <ReloadOutlined />,
  }), [])

  useEffect(() => {
    const fetchMenus = async () => {
      try {
        const { data } = await api.get('/auth/menus')
        if (data?.code === 0) {
          const list: MenuItemResp[] = data.data || []
          setRawMenus(list)
        }
      } catch (e) {
        // ignore
      }
    }
    fetchMenus()
  }, [])

  useEffect(() => {
    // 前端移除不再需要的“权限”相关菜单项，并仅展示可见菜单项
    const filtered = rawMenus
      .filter((m: MenuItemResp) => m.path !== '/permissions')
      .filter((m: MenuItemResp) => m.visible === true)

    // build tree items from filtered（仅包含可见且非按钮项）
    const map = new Map<number, any>()
    const byId = new Map<number, MenuItemResp>()
    filtered.forEach((m: MenuItemResp) => byId.set(m.id, m))
    filtered
      .filter((m: MenuItemResp) => m.type !== 3) // skip buttons
      .forEach((m: MenuItemResp) => {
        const key = m.path && m.path.startsWith('/') ? m.path : `m-${m.id}`
        const item: any = {
          key,
          label: m.name,
          icon: m.icon ? iconMap[m.icon] : undefined,
          // NOTE: 不要预先放 children: []，否则在 antd Menu 中会被当成可展开项
        }
        map.set(m.id, item)
      })
    const roots: any[] = []
    filtered
      .filter((m: MenuItemResp) => m.type !== 3)
      .forEach((m: MenuItemResp) => {
        const node = map.get(m.id)
        const pid = m.parent_id || undefined
        // 只有当父级存在且不是按钮时，才允许挂载；若父级不可见或不存在则提升为根
        if (pid && map.has(pid) && byId.get(pid)?.type !== 3) {
          const parent = map.get(pid)
          if (!parent.children) parent.children = []
          parent.children.push(node)
        } else {
          roots.push(node)
        }
      })
    // 完全依赖后端返回的菜单树（不再追加本地兜底项）
    setMenuItems(roots)
  }, [rawMenus, iconMap])

  const handleLogout = async () => {
    try {
      await api.post('/auth/logout')
    } catch (_) {}
    clearToken()
    navigate('/login', { replace: true })
  }

  const openProfile = () => {
    const u = getUserLocal()
    setUser(u)
    form.setFieldsValue({ username: u?.username, nickname: u?.nickname, avatar: (u as any)?.avatar || '' })
    setProfileOpen(true)
  }

  const handleSaveProfile = async () => {
    try{
      const values = await form.validateFields()
      const uid = (user as any)?.id
      if (uid) {
        const { data } = await api.put(`/users/${uid}`, { nickname: values.nickname })
        if (data?.code !== 0) throw new Error(data?.message || '更新失败')
      }
      const merged = { ...(user as any), nickname: values.nickname, avatar: values.avatar }
      setUserLocal(merged)
      setUser(merged)
      message.success('资料已更新')
      setProfileOpen(false)
    }catch(e: any){ if(e?.errorFields) return; message.error(e?.message || '更新失败') }
  }

  const onMenuClick = (info: any) => {
    const key = info?.key as string
    if (key && key.startsWith('/')) navigate(key)
  }

  const selectedKeys = useMemo(() => {
    const k = loc.pathname
    if (k.startsWith('/flows/')) return ['/flows']
    return [k]
  }, [loc.pathname])

  // 根据当前路径计算需要展开的父级菜单 keys（不包含叶子自身）
  const [openKeys, setOpenKeys] = useState<string[]>(() => {
    try {
      const v = localStorage.getItem('layout:menuOpenKeys')
      const arr = v ? JSON.parse(v) : []
      return Array.isArray(arr) ? (arr as string[]) : []
    } catch { return [] }
  })
  // 持久化用户手动展开/收起的子菜单状态，刷新后保持
  useEffect(() => {
    try { localStorage.setItem('layout:menuOpenKeys', JSON.stringify(openKeys)) } catch {}
  }, [openKeys])
  // 确保当前路由对应的父级菜单也处于展开状态，但不覆盖用户手动展开的其他菜单（合并而非替换）
  const currentAncestors = useMemo(() => {
    const list = rawMenus.filter((m: MenuItemResp) => m.type !== 3 && m.path !== '/permissions')
    const idMap = new Map<number, MenuItemResp>()
    const pathToId = new Map<string, number>()
    list.forEach((m: MenuItemResp) => { idMap.set(m.id, m); if (m.path) pathToId.set(m.path, m.id) })

    const currentPath = loc.pathname
    let foundId: number | undefined = pathToId.get(currentPath)
    if (!foundId) {
      const candidates = list.filter((m: MenuItemResp) => m.path && currentPath.startsWith(m.path))
      if (candidates.length) {
        candidates.sort((a: MenuItemResp, b: MenuItemResp) => (b.path?.length || 0) - (a.path?.length || 0))
        foundId = candidates[0].id
      }
    }
    if (!foundId) return [] as string[]

    const chain: MenuItemResp[] = []
    let cur: MenuItemResp | undefined = idMap.get(foundId)
    let guard = 0
    while (cur && guard++ < 50) {
      chain.push(cur)
      const pid = cur.parent_id || undefined
      cur = pid ? idMap.get(pid) : undefined
    }
    chain.reverse()
    const ancestors = chain.slice(0, Math.max(0, chain.length - 1))
    return ancestors.map((m: MenuItemResp) => (m.path && m.path.startsWith('/') ? m.path : `m-${m.id}`))
  }, [rawMenus, loc.pathname])
  useEffect(() => {
    setOpenKeys(prev => {
      const s = new Set(prev)
      currentAncestors.forEach(k => s.add(k))
      return Array.from(s)
    })
  }, [currentAncestors])

  const dropdownMenuItems = useMemo(() => ([
    { key: 'profile', label: '个人信息', icon: <UserOutlined /> },
    { type: 'divider' as any },
    { key: 'logout', label: '退出登录', icon: <LogoutOutlined /> },
  ]), [])

  // 基于菜单树与当前路径计算面包屑
  const breadcrumbItems = useMemo(() => {
    // 过滤掉不需要显示的菜单与按钮（type 3）
    const list = rawMenus.filter((m: MenuItemResp) => m.type !== 3 && m.path !== '/permissions')
    const idMap = new Map<number, MenuItemResp>()
    const pathToId = new Map<string, number>()
    list.forEach((m: MenuItemResp) => { idMap.set(m.id, m); if (m.path) pathToId.set(m.path, m.id) })

    const currentPath = loc.pathname
    let foundId: number | undefined
    if (pathToId.has(currentPath)) {
      foundId = pathToId.get(currentPath)!
    } else {
      // 兜底：取路径前缀最长匹配（例如 /users/detail/123 匹配 /users）
      const candidates = list.filter((m: MenuItemResp) => m.path && currentPath.startsWith(m.path))
      if (candidates.length) {
        candidates.sort((a: MenuItemResp, b: MenuItemResp) => (b.path?.length || 0) - (a.path?.length || 0))
        foundId = candidates[0].id
      }
    }
    if (!foundId) return [] as any[]

    const chain: MenuItemResp[] = []
    let cur: MenuItemResp | undefined = idMap.get(foundId)
    let guard = 0
    while (cur && guard++ < 50) {
      chain.push(cur)
      const pid = cur.parent_id || undefined
      cur = pid ? idMap.get(pid) : undefined
    }
    chain.reverse()

    return chain.map((m: MenuItemResp, idx: number) => ({
      title: (m.path && idx !== chain.length - 1)
        ? <a onClick={(e: any) => { e.preventDefault(); navigate(m.path!) }}>{m.name}</a>
        : m.name
    }))
  }, [rawMenus, loc.pathname, navigate])

  // 路由页签与缓存（放在 return 之前的顶层）
  type TabItem = { key: string; title: string; keepAlive: boolean }
  const [tabs, setTabs] = useState<TabItem[]>([])
  const [cache, setCache] = useState<Record<string, React.ReactNode>>({})
  // 记录每个标签页是否有未保存更改（脏标记）
  const dirtyRef = useRef<Record<string, boolean>>({})
  const [dirtyVersion, setDirtyVersion] = useState(0) // 仅用于触发重渲染（展示脏标记）
  const outlet = useOutlet()
  // 右侧菜单折叠状态（默认收起）
  // 左侧菜单折叠状态：持久化到 localStorage，刷新不丢失
  const [leftCollapsed, setLeftCollapsed] = useState<boolean>(() => {
    try {
      const v = localStorage.getItem('layout:leftCollapsed')
      return v ? v === '1' : false
    } catch { return false }
  })
  // 断点状态：仅用于小屏时临时强制折叠，不写入持久化
  const [isBroken, setIsBroken] = useState(false)

  useEffect(() => {
    try { localStorage.setItem('layout:leftCollapsed', leftCollapsed ? '1' : '0') } catch {}
  }, [leftCollapsed])

  // 取消自动展开：尊重用户的折叠偏好（刷新后保持折叠/展开状态）
  // 取消自动展开：尊重用户的折叠偏好（刷新后保持折叠/展开状态）

  // path -> menu 映射，便于取标题与 keep_alive
  const pathToMenu = useMemo(() => {
    const map = new Map<string, MenuItemResp>()
    rawMenus.forEach((m: MenuItemResp) => { if (m.path) map.set(m.path, m) })
    return map
  }, [rawMenus])

  // 用于标题兜底：当精确路径无菜单时，使用“最长前缀匹配”的菜单（例如 /flows/editor 命中 /flows）
  const menusForLookup = useMemo(() => {
    return rawMenus
      .filter((m: MenuItemResp) => m.type !== 3 && m.path && m.path.startsWith('/'))
      .sort((a: MenuItemResp, b: MenuItemResp) => (b.path!.length - a.path!.length))
  }, [rawMenus])
  const resolveMenuByPath = (pathname: string): MenuItemResp | undefined => {
    const exact = pathToMenu.get(pathname)
    if (exact) return exact
    for (const m of menusForLookup) {
      if (pathname.startsWith(m.path!)) return m
    }
    return undefined
  }

  // 当前路由激活 key（包含查询参数，支持同一路由多开不同 query 的标签页）
  const activeKey = useMemo(() => (loc.pathname + (loc.search || '')), [loc.pathname, loc.search])

  // 根据菜单变更，修正已存在 tabs 的标题与 keepAlive（解决刷新后标题变为路径的问题）
  useEffect(() => {
    setTabs(prev => {
      let changed = false
      const next = prev.map(t => {
        if (t.key === '/') {
          const nextTitle = '首页'
          const nextKeep = true
          if (t.title !== nextTitle || t.keepAlive !== nextKeep) {
            changed = true
            return { ...t, title: nextTitle, keepAlive: nextKeep }
          }
          return t
        }
        const pathname = t.key.split('?')[0]
        const m = resolveMenuByPath(pathname)
        let nextTitle = m?.name || t.title
        // 对 /flows/editor 提供更高优先级的标题覆盖：即使存在 /flows 的前缀匹配，也使用“流程设计”
        if (pathname === '/flows/editor' && m?.path !== '/flows/editor') {
          nextTitle = '流程设计'
        }
        const nextKeep = (m && typeof m.keep_alive === 'boolean') ? !!m.keep_alive : t.keepAlive
        if (t.title !== nextTitle || t.keepAlive !== nextKeep) {
          changed = true
          return { ...t, title: nextTitle, keepAlive: nextKeep }
        }
        return t
      })
      return changed ? next : prev
    })
  }, [pathToMenu, menusForLookup])

  // 根据当前路由补齐 tabs（key 含 query，title 从“精确或前缀匹配”的菜单名获取），并为 /flows/editor 覆盖标题为“流程设计”
  useEffect(() => {
    const curPath = loc.pathname
    if (!curPath.startsWith('/')) return
    const key = curPath + (loc.search || '')
    const m = resolveMenuByPath(curPath)
    let title = m?.name || (curPath === '/' ? '首页' : curPath)
    // 对 /flows/editor 提供更高优先级的标题覆盖：即使存在 /flows 的前缀匹配，也使用“流程设计”
    if (curPath === '/flows/editor' && m?.path !== '/flows/editor') {
      title = '流程设计'
    }
    const keepAlive = (m && typeof m.keep_alive === 'boolean') ? !!m.keep_alive : true
    setTabs(prev => (prev.some(t => t.key === key) ? prev : [...prev, { key, title, keepAlive }]))
  }, [loc.pathname, loc.search, pathToMenu, menusForLookup])

  // 确保缓存当前激活页（若 keepAlive 打开）
  useEffect(() => {
    const cur = tabs.find(t => t.key === activeKey)
    if (!cur) return
    if (cur.keepAlive && outlet && !cache[activeKey]) {
      setCache(prev => ({ ...prev, [activeKey]: outlet }))
    }
  }, [activeKey, outlet, tabs, cache])

  const onTabChange = (key: string) => { if (key && key !== activeKey) navigate(key) }
  const onTabEdit = (targetKey: any, action: 'add' | 'remove') => {
    if (action === 'remove') {
      const tKey = String(targetKey)
      if (tKey === '/') return
      // 如果关闭的是流程设计页，且存在未保存更改，则进行二次确认
      const isFlowsEditor = tKey.startsWith('/flows/editor')
      if (isFlowsEditor && dirtyRef.current[tKey]) {
        Modal.confirm({
          title: '未保存的更改',
          content: '当前流程设计有未保存的更改，确定要关闭吗？',
          okText: '确定',
          cancelText: '取消',
          onOk: () => {
            // 确认后继续原有关闭逻辑
            setTabs(prev => {
              const idx = prev.findIndex(t => t.key === tKey)
              if (idx === -1) return prev
              const next = prev.filter(t => t.key !== tKey)
              setCache(c => { const n = { ...c }; delete n[tKey]; return n })
              delete dirtyRef.current[tKey]
              if (tKey === activeKey) {
                const fallback = next[idx - 1] || next[idx] || { key: '/' }
                if (fallback.key) navigate(fallback.key)
              }
              return next
            })
          },
        })
        return
      }
      setTabs(prev => {
        const idx = prev.findIndex(t => t.key === tKey)
        if (idx === -1) return prev
        const next = prev.filter(t => t.key !== tKey)
        // 清理缓存
        setCache(c => { const n = { ...c }; delete n[tKey]; return n })
        delete dirtyRef.current[tKey]
        // 如果关闭的是当前激活页，跳转到相邻页
        if (tKey === activeKey) {
          const fallback = next[idx - 1] || next[idx] || { key: '/' }
          if (fallback.key) navigate(fallback.key)
        }
        return next
      })
    }
  }

  // 监听流程编辑器的脏状态事件 flows:doc-dirty
  useEffect(() => {
    const handler = (e: Event) => {
      const detail = (e as CustomEvent<{ key: string; dirty: boolean }>).detail
      if (!detail || !detail.key) return
      // 仅记录 /flows/editor 路由的脏状态
      if (!detail.key.startsWith('/flows/editor')) return
      dirtyRef.current[detail.key] = !!detail.dirty
      setDirtyVersion(v => v + 1)
    }
    window.addEventListener('flows:doc-dirty', handler as any)
    return () => window.removeEventListener('flows:doc-dirty', handler as any)
  }, [])

  // 在浏览器刷新/关闭时提示未保存的流程设计
  useEffect(() => {
    const beforeUnload = (e: BeforeUnloadEvent) => {
      const hasDirty = Object.entries(dirtyRef.current).some(([k, v]) => k.startsWith('/flows/editor') && v)
      if (hasDirty) {
        e.preventDefault()
        const msg = '当前页面存在未保存的更改，确定要离开吗？'
        e.returnValue = msg
        return msg
      }
    }
    window.addEventListener('beforeunload', beforeUnload)
    return () => window.removeEventListener('beforeunload', beforeUnload)
  }, [])
  // 确保“首页”始终存在且固定（仅初始化时补齐一次）
  useEffect(() => {
    setTabs(prev => (prev.some(t => t.key === '/') ? prev : [{ key: '/', title: '首页', keepAlive: true }, ...prev]))
  }, [])

  return (
      <PermissionProvider codes={permissionCodes}>
        <ConfigProvider locale={localeObj}>
          <Layout style={{ minHeight: '100vh' }}>
            {/* 其余内容保持不变 */}
      <Sider
      collapsible
      collapsed={isBroken ? true : leftCollapsed}
      onCollapse={(collapsed, type) => {
        // 仅在用户点击触发器时更新用户偏好；响应式折叠不写入
        if (type === 'clickTrigger') {
          setLeftCollapsed(collapsed)
        }
      }}
      breakpoint="lg"
      onBreakpoint={(broken) => setIsBroken(broken)}
      trigger={null}
      collapsedWidth={56}
      width={220}
      style={{ background: colorBgContainer }}
      >
        <div
          style={{ 
            height: 64, 
            display: 'flex', 
            alignItems: 'center', 
            justifyContent: 'center', 
            fontWeight: 600, 
            fontSize: leftCollapsed ? '16px' : '18px',
            cursor: 'pointer', 
            userSelect: 'none' 
          }}
          onClick={() => setLeftCollapsed(c => !c)}
          title={leftCollapsed ? '展开菜单' : '收起菜单'}
          >
            {leftCollapsed ? APP_CONFIG.SITE_NAME_SHORT : APP_CONFIG.SITE_NAME}
        </div>
         <Menu
           mode="inline"
           selectedKeys={selectedKeys}
           openKeys={openKeys}
           onOpenChange={(keys) => setOpenKeys(keys as string[])}
           inlineCollapsed={isBroken ? true : leftCollapsed}
           items={menuItems}
           onClick={onMenuClick}
           style={{ borderInlineEnd: 0 }}
         />
       </Sider>
       <Layout>
         <Header style={{ padding: '8px 16px', background: colorBgContainer }}>
           <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
            <div style={{ flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', gap: 12 }}>
              {/* 用户要求：隐藏展开/收起图标，保留名称点击收起/展开 */}
               {tabs.length > 0 && (
                 <Tabs
                   className="top-tabs"
                   hideAdd
                   type="editable-card"
                   items={tabs.map(t => ({ key: t.key, label: (t.key.startsWith('/flows/editor') && dirtyRef.current[t.key]) ? `${t.title} *` : t.title, closable: t.key !== '/' }))}
                    activeKey={activeKey}
                    onChange={onTabChange}
                    onEdit={onTabEdit as any}
                  />
               )}
             </div>
             <Space size={16} align="center">
               <Dropdown
                 menu={{
                   items: [
                     { key: 'zh', label: 'CN' },
                     { key: 'en', label: 'EN' },
                   ],
                   onClick: ({ key }) => { const v = key as 'zh'|'en'; setLang(v); localStorage.setItem('lang', v); window.dispatchEvent(new CustomEvent('app:lang-changed', { detail: v })); }
                 }}
                 placement="bottomLeft"
               >
                 <Space style={{ cursor: 'pointer', userSelect: 'none' }}>
                   <GlobalOutlined />
                   <span>{lang === 'zh' ? 'CN' : 'EN'}</span>
                 </Space>
               </Dropdown>
               <Dropdown
                 menu={{ items: dropdownMenuItems as any, onClick: (e: { key: string }) => { if (e.key === 'logout') handleLogout(); if (e.key === 'profile') openProfile() } }}
                 placement="bottomRight"
               >
                 <Space style={{ cursor: 'pointer' }}>
                   <Avatar size={32} icon={<UserOutlined />} />
                   <span>{(user as any)?.nickname || (user as any)?.username || '用户'}</span>
                 </Space>
               </Dropdown>
             </Space>
           </div>
         </Header>
         <Content style={{ margin: '16px', padding: 0, background: colorBgContainer, borderRadius: borderRadiusLG, display: 'flex', flexDirection: 'column' }}>
           <div style={{ flex: 1, minHeight: 0, position: 'relative', padding: 16 }}>
             {/* 缓存的标签页：非激活隐藏（激活页若缓存为空则回退渲染 outlet） */}
             {tabs.filter(t => t.keepAlive).map(t => (
               <div key={t.key} style={{ display: t.key === activeKey ? 'block' : 'none', height: '100%' }}>
                 {t.key === activeKey ? (cache[t.key] ?? outlet) : cache[t.key]}
               </div>
             ))}
             {/* 未开启缓存：只渲染当前路由 */}
             {(() => {
               const cur = tabs.find(t => t.key === activeKey)
               if (!cur) return null
               if (cur.keepAlive) return null
               return <div key={activeKey} style={{ height: '100%' }}>{outlet}</div>
             })()}
           </div>
         </Content>
         <Footer style={{ textAlign: 'center' }}>{APP_CONFIG.FOOTER_TEXT.replace('{year}', String(new Date().getFullYear()))}</Footer>
       </Layout>
     </Layout>

      <Modal title="个人信息" open={profileOpen} onOk={handleSaveProfile} onCancel={() => setProfileOpen(false)} okText="保存" width={840}>
        <Form form={form} layout="horizontal" labelCol={{ span: 2 }} wrapperCol={{ span: 22 }} labelAlign="right">
          <Form.Item name="username" label="用户名">
            <Input disabled />
          </Form.Item>
          <Form.Item name="nickname" label="昵称" rules={[{ required: true, message: '请输入昵称' }]}> 
            <Input placeholder="请输入昵称" />
          </Form.Item>
          <Form.Item name="avatar" label="头像 URL">
            <Input placeholder="请输入头像图片地址（URL）" />
          </Form.Item>
        </Form>
      </Modal>
        </ConfigProvider>
      </PermissionProvider>
   )
 }
 // 说明：菜单完全依赖后端返回的路径，若需要本地添加“调度任务管理”菜单，请在后端创建菜单项 path: '/schedule-jobs'，前端会自动展示。