<template>
  <div class="sessions-view">
    <!-- Stats Cards -->
    <div class="stats-row">
      <div class="stat-card">
        <p class="stat-lbl">活跃会话集群</p>
        <h3 class="stat-val">{{ activeCount }}/{{ total }}</h3>
      </div>
      <div class="stat-card">
        <p class="stat-lbl">当前筛选调用量</p>
        <h3 class="stat-val accent">{{ filteredCalls.toLocaleString() }} <span class="stat-unit">次</span></h3>
      </div>
      <div class="stat-card">
        <p class="stat-lbl">引擎分布情况</p>
        <div class="engine-dots">
          <span v-for="(c, eng) in engineDist" :key="eng" class="engine-dot" :style="{ background: getEngineMeta(eng).color }" :title="`${getEngineMeta(eng).label}: ${c}`">{{ getEngineMeta(eng).label }}({{ c }})</span>
        </div>
      </div>
      <div class="stat-card">
        <p class="stat-lbl">异常/中断率</p>
        <h3 class="stat-val success">{{ errorRate }}%</h3>
      </div>
    </div>

    <!-- Search + Filter -->
    <div class="search-bar">
      <div class="search-wrap">
        <span class="search-ico">🔍</span>
        <input v-model="query" class="search-input" placeholder="输入摘要、会话 ID、文件路径关键字检索..." @keyup.enter="doSearch" />
        <button v-if="query" class="search-clear" @click="query = ''; doSearch()">✕</button>
      </div>
      <div class="filter-group">
        <span class="filter-lbl">状态过滤:</span>
        <button v-for="opt in statusOpts" :key="opt.key" class="filter-btn" :class="{ active: statusFilter === opt.key }" @click="statusFilter = opt.key; doSearch()">{{ opt.label }}</button>
      </div>
      <div class="filter-group">
        <button class="filter-btn" :class="{ active: favoriteOnly }" @click="onFavoriteOnlyChange()" title="只看收藏会话"
          aria-label="只看收藏会话" :aria-pressed="String(favoriteOnly)">⭐ 只看收藏</button>
      </div>
    </div>

    <!-- Session Cards -->
    <div class="session-list">
      <div class="list-header" v-if="sessions.length">
        <span class="list-header-text">已过滤出 {{ displayedSessions.length }} 个智能体会话监控流 (Engine: {{ selectedEngine.toUpperCase() }})</span>
      </div>

      <div v-if="loading" class="loading-state">加载中...</div>
      <div v-else-if="!displayedSessions.length" class="empty-state">
        <p>{{ favoriteOnly ? '暂无收藏的会话。' : '无符合当前引擎或状态过滤条件的会话实例。' }}</p>
      </div>
      <div v-else class="card-grid">
        <div
          v-for="s in displayedSessions"
          :key="s.key || s.id"
          class="session-card"
          :style="{ borderLeftColor: getEngineMeta(s.engine).color }"
          @click="openDrawer(s)"
        >
          <div class="card-main">
            <div class="card-tags">
              <span class="engine-badge" :style="{ background: getEngineMeta(s.engine).bg, color: getEngineMeta(s.engine).color, borderColor: getEngineMeta(s.engine).border }">
                {{ s.engine_label || getEngineMeta(s.engine).label }}
              </span>
              <span class="status-badge" :class="'status-' + (s.status || 'unknown')">
                <span class="status-dot-card" :class="'dot-' + (s.status || 'unknown')"></span>
                {{ statusLabel(s.status) }}
              </span>
              <span class="id-preview mono">会话 ID: <span class="id-highlight">{{ (s.id || '').substring(0, 15) }}...</span></span>
            </div>
            <h4 class="card-summary">{{ s.custom_name || s.summary || '(无消息)' }}</h4>
            <div v-if="s.custom_name" class="card-original-summary">{{ s.summary }}</div>
            <div class="card-path">
              <span class="path-ico">📁</span>
              <span class="path-text">{{ s.cwd || '-' }}</span>
            </div>
          </div>
          <div class="card-side">
            <div class="card-meta">
              <div class="meta-col">
                <span class="meta-col-label">对应版本</span>
                <span class="meta-col-val">{{ s.cli_version || s.model || '-' }}</span>
              </div>
              <div class="meta-col">
                <span class="meta-col-label">工具调用</span>
                <span class="meta-col-val calls" :class="callsClass(s.tool_call_count)">{{ s.tool_call_count || 0 }} 次</span>
              </div>
              <div class="meta-col">
                <span class="meta-col-label">开始时间</span>
                <span class="meta-col-val time">{{ formatTimestamp(s.created_at) }}</span>
              </div>
              <div class="meta-col">
                <button class="detail-btn" @click.stop="goToDetailPage(s)" title="进入详情页">详情 →</button>
              </div>
            </div>
            <div class="card-actions">
              <button class="action-btn" @click.stop="openNameEditor(s)" title="编辑名称" aria-label="编辑自定义名称">
                <span class="icon">✏️</span><span class="text">编辑</span>
              </button>
              <button
                class="action-btn fav"
                :class="{ 'is-fav': s.is_favorite }"
                :disabled="isFavoritePending(s)"
                @click.stop="toggleFavorite(s)"
                :title="s.is_favorite ? '取消收藏' : '收藏'"
                :aria-label="s.is_favorite ? '取消收藏此会话' : '收藏此会话'"
                :aria-pressed="String(s.is_favorite)"
              >
                <span class="icon">{{ s.is_favorite ? '★' : '☆' }}</span>
                <span class="text">{{ isFavoritePending(s) ? '处理中' : (s.is_favorite ? '已收藏' : '收藏') }}</span>
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>

    <!-- Pagination -->
    <div class="pagination" v-if="total > pageSize">
      <span class="page-info">共 {{ total }} 条</span>
      <div class="page-btns">
        <button class="page-btn" :disabled="page <= 1" @click="goPage(page - 1)">‹</button>
        <button v-for="p in pageNums" :key="p" v-if="p !== '...'" class="page-btn" :class="{ active: p === page }" @click="goPage(p)">{{ p }}</button>
        <span v-else class="page-ellipsis">...</span>
        <button class="page-btn" :disabled="page >= maxPage" @click="goPage(page + 1)">›</button>
      </div>
    </div>

    <!-- Detail Drawer -->
    <div v-if="drawerSession" class="drawer-overlay" @click.self="closeDrawer">
      <div class="drawer">
        <div class="drawer-head">
          <div class="drawer-title-bar">
            <span class="drawer-badge" :style="{ background: getEngineMeta(drawerSession.engine).bg, color: getEngineMeta(drawerSession.engine).color, borderColor: getEngineMeta(drawerSession.engine).border }">
              {{ getEngineMeta(drawerSession.engine).label }} · 运行诊断
            </span>
            <button class="drawer-close" @click="closeDrawer">✕</button>
          </div>
          <h3 class="drawer-summary">{{ drawerSession.custom_name || drawerSession.summary || '(无消息)' }}</h3>
          <div v-if="drawerSession.custom_name" class="drawer-original-summary">{{ drawerSession.summary }}</div>
          <div class="drawer-name-row">
            <p class="drawer-id mono">完整会话 UUID: {{ drawerSession.id }}</p>
            <button class="name-edit-btn" @click.stop="openNameEditor(drawerSession)">✏️ 命名</button>
            <button class="name-edit-btn" @click.stop="generateDrawerTitle" :disabled="drawerAiLoading">🤖 {{ drawerAiLoading ? '生成中...' : 'AI 生成' }}</button>
            <button v-if="drawerSession.custom_name" class="name-edit-btn" @click.stop="clearCustomName(drawerSession)">🗑 清除</button>
            <button
              class="fav-btn-drawer"
              :class="{ 'is-fav': drawerSession.is_favorite }"
              :disabled="isFavoritePending(drawerSession)"
              @click.stop="toggleFavorite(drawerSession)"
              :title="drawerSession.is_favorite ? '取消收藏' : '收藏'"
              :aria-label="drawerSession.is_favorite ? '取消收藏此会话' : '收藏此会话'"
              :aria-pressed="String(drawerSession.is_favorite)"
            >{{ drawerSession.is_favorite ? '★' : '☆' }} {{ drawerSession.is_favorite ? '取消收藏' : '收藏' }}</button>
          </div>
        </div>

        <div class="drawer-body" v-if="drawerDetail">
          <!-- detail content unchanged -->
          <div class="drawer-meta-box">
            <div class="meta-row"><span class="meta-lbl">宿主执行用户:</span><span class="meta-val">{{ drawerSession.model || '-' }}</span></div>
            <div class="meta-row"><span class="meta-lbl">引擎运行时版本:</span><span class="meta-val">{{ drawerSession.cli_version || '-' }}</span></div>
            <div class="meta-row"><span class="meta-lbl">工作物理路径:</span><span class="meta-val accent mono">{{ drawerSession.cwd || '-' }}</span></div>
            <div class="meta-row"><span class="meta-lbl">运行总时长:</span><span class="meta-val">{{ drawerSession.duration_ms ? Math.round(drawerSession.duration_ms / 60000) + ' 分钟' : '暂无数据' }}</span></div>
            <div class="meta-row"><span class="meta-lbl">诊断执行状态:</span><span class="meta-val">
              <span class="status-tag-drawer" :class="'tag-' + (drawerSession.status || 'unknown')">{{ statusLabel(drawerSession.status) }}</span>
            </span></div>
          </div>

          <div class="drawer-section">
            <h4 class="drawer-section-title">交互式智能体思考环与工具调用 Trace 链路</h4>
            <div v-if="!drawerDetail.messages?.length && drawerSession?.engine === 'agy' && drawerSession?.has_detail_db === false" class="trace-empty">该会话的详细 Trace 数据存储在 per-conversation protobuf 中，本地详情缓存不可用。</div>
            <div v-else-if="!drawerDetail.messages?.length" class="trace-empty">该历史会话没有持久化的 Trace 调试详情数据。</div>
            <div v-else class="trace-list">
              <div v-for="(msg, i) in drawerDetail.messages.slice(0, 30)" :key="i" class="trace-item">
                <span class="trace-dot" :class="traceDot(msg.type)"></span>
                <div class="trace-content">
                  <span class="trace-tag" :class="'trace-tag-' + traceTag(msg.type)">{{ traceLabel(msg.type) }}</span>
                  <span class="trace-msg">{{ msg.message || msg.name || msg.output_preview || '' }}</span>
                </div>
              </div>
            </div>
          </div>
        </div>
        <div v-else-if="drawerError" class="drawer-body">
          <div class="drawer-error-box">
            <p class="drawer-error-text">{{ drawerError }}</p>
            <button class="drawer-retry-btn" @click="retryDrawer">重试</button>
          </div>
        </div>
        <div v-else-if="!drawerDetail && !drawerError" class="drawer-body">
          <div class="loading-state">加载中...</div>
        </div>

        <div class="drawer-footer">
          <button class="footer-btn-copy" @click="copyResume(drawerSession)">
            📋 复制恢复命令
          </button>
          <button class="footer-btn-uuid" @click="copyUUID(drawerSession.id)">
            📋 复制 UUID 句柄
          </button>
          <button class="footer-btn-detail" @click="goToDetailPage(drawerSession)">
            → 进入详情页
          </button>
          <button v-if="drawerSession.can_delete" class="footer-btn-delete" @click="showDeleteConfirm(drawerSession)">
            🗑 删除会话
          </button>
        </div>
      </div>
    </div>

    <!-- Name Editor Dialog -->
    <div v-if="editNameVisible" class="dialog-overlay" @click.self="editNameVisible = false">
      <div class="dialog">
        <div class="dialog-head">
          <h3>自定义名称</h3>
          <button class="dialog-close" @click="editNameVisible = false">✕</button>
        </div>
        <div class="dialog-body">
          <p class="dialog-hint">为本会话设置一个易记的名称，仅在本地使用。</p>
          <input
            v-model="editNameValue"
            class="dialog-input"
            placeholder="输入自定义名称..."
            maxlength="100"
            @keyup.enter="saveCustomName"
          />
          <p class="dialog-counter">{{ editNameValue.length }}/100</p>
        </div>
        <div class="dialog-foot">
          <button class="dialog-btn" @click="editNameVisible = false">取消</button>
          <button class="dialog-btn primary" @click="saveCustomName">保存</button>
        </div>
      </div>
    </div>

    <!-- Delete Confirmation Dialog -->
    <div v-if="deleteConfirmVisible" class="dialog-overlay" @click.self="deleteConfirmVisible = false">
      <div class="dialog">
        <div class="dialog-head">
          <h3>确认删除会话</h3>
          <button class="dialog-close" @click="deleteConfirmVisible = false">✕</button>
        </div>
        <div class="dialog-body">
          <p class="dialog-hint">此操作将从 OpenCode 数据库中永久删除该会话，不可恢复。</p>
          <p class="dialog-id mono">{{ deleteTarget?.id }}</p>
        </div>
        <div class="dialog-foot">
          <button class="dialog-btn" @click="deleteConfirmVisible = false" :disabled="deleting">取消</button>
          <button class="dialog-btn danger" @click="confirmDelete" :disabled="deleting">{{ deleting ? '删除中...' : '确认删除' }}</button>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted, inject, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { getSessions, getSessionDetail, getGlobalTools, formatTimestamp, getEngineMeta, ENGINE_META, setCustomName, deleteCustomName, generateTitle, deleteSession, favoriteSession, unfavoriteSession } from '../api/index.js'
import { useRouter } from 'vue-router'

const props = defineProps({ selectedEngine: { type: String, default: 'all' } })

const sessions = ref([])
const total = ref(0)
const loading = ref(false)
const query = ref('')
const statusFilter = ref('all')
const page = ref(1)
const pageSize = 50
const drawerSession = ref(null)
const drawerDetail = ref(null)
const drawerError = ref(null)
const drawerAiLoading = ref(false)
const router = useRouter()
const activeSessions = ref(0)
const filteredCalls = ref(0)

const statusOpts = [
  { key: 'all', label: '全部' },
  { key: 'active', label: '执行中' },
  { key: 'success', label: '已完成' },
  { key: 'failed', label: '已中断' },
]

const favoriteOnly = ref(false)
const favoritePendingMap = ref({})

function isFavoritePending(s) {
  return !!favoritePendingMap.value[`${s.engine}:${s.id}`]
}
function setFavoritePending(s, val) {
  if (val) {
    favoritePendingMap.value[`${s.engine}:${s.id}`] = true
  } else {
    delete favoritePendingMap.value[`${s.engine}:${s.id}`]
  }
}

const engineDist = computed(() => {
  const dist = {}
  for (const s of sessions.value) {
    dist[s.engine] = (dist[s.engine] || 0) + 1
  }
  return dist
})

const errorCount = computed(() => sessions.value.filter(s => s.status === 'error' || s.status === 'interrupted').length)
const errorRate = computed(() => sessions.value.length ? Math.round(errorCount.value / sessions.value.length * 100) : 0)
const activeCount = computed(() => sessions.value.filter(s => s.status === 'active').length)

const displayedSessions = computed(() => {
  let list = sessions.value
  if (statusFilter.value === 'success') list = list.filter(s => s.status === 'success')
  else if (statusFilter.value === 'failed') list = list.filter(s => s.status === 'error' || s.status === 'interrupted')
  else if (statusFilter.value === 'active') list = list.filter(s => s.status === 'active')
  return list
})

const maxPage = computed(() => Math.ceil(total.value / pageSize))
const pageNums = computed(() => {
  const p = [], tp = maxPage.value
  if (tp <= 7) { for (let i = 1; i <= tp; i++) p.push(i) }
  else {
    p.push(1)
    if (page.value > 3) p.push('...')
    for (let i = Math.max(2, page.value - 1); i <= Math.min(tp - 1, page.value + 1); i++) p.push(i)
    if (page.value < tp - 2) p.push('...')
    p.push(tp)
  }
  return p
})

function statusLabel(status) {
  return { success: '已成功', error: '已中断', interrupted: '已中断', active: '执行中', unknown: '未知' }[status] || status
}

function callsClass(c) {
  if (c > 100) return 'calls-danger'
  if (c > 50) return 'calls-warn'
  return 'calls-normal'
}

function traceTag(type) {
  const map = { user_message: 'primary', function_call: 'warning', function_call_output: 'info', assistant_message: 'success' }
  return map[type] || ''
}
function traceDot(type) {
  const map = { user_message: 'dot-primary', function_call: 'dot-warning', function_call_output: 'dot-info', assistant_message: 'dot-success' }
  return map[type] || 'dot-default'
}
function traceLabel(type) {
  const map = { user_message: '用户', function_call: '工具', function_call_output: '输出', assistant_message: 'AI' }
  return map[type] || type
}

async function loadSessions() {
  loading.value = true
  try {
    // Map frontend status values to backend status values
    let apiStatus = statusFilter.value;
    if (apiStatus === 'all') apiStatus = null;
    // 'failed' is handled by backend as error+interrupted

    const data = await getSessions({
      engine: props.selectedEngine,
      query: query.value,
      status: apiStatus,
      limit: pageSize,
      offset: (page.value - 1) * pageSize,
      favorite: favoriteOnly.value ? '1' : undefined,
    })
    sessions.value = data.sessions || []
    total.value = data.total || 0
    // Also fetch stats for calls
    const tools = await getGlobalTools({ engine: props.selectedEngine })
    filteredCalls.value = tools.tool_usage?.reduce((a, b) => a + b.total_calls, 0) || 0
  } catch (err) {
    ElMessage.error('加载失败: ' + err.message)
  } finally {
    loading.value = false
  }
}

function doSearch() { page.value = 1; loadSessions() }
function goPage(p) { if (p >= 1 && p <= maxPage.value) { page.value = p; loadSessions() } }

async function openDrawer(s) {
  drawerSession.value = s
  drawerDetail.value = null
  drawerError.value = null
  try {
    const data = await getSessionDetail(s.engine, s.id)
    drawerDetail.value = data
  } catch (err) {
    drawerError.value = '加载详情失败: ' + (err.message || '')
  }
}

function retryDrawer() {
  if (drawerSession.value) openDrawer(drawerSession.value)
}

function closeDrawer() { drawerSession.value = null; drawerDetail.value = null; drawerError.value = null }

function copyResume(s) {
  if (!s.resume_command) return
  navigator.clipboard.writeText(s.resume_command).then(() => {
    ElMessage.success(`已复制: ${s.resume_command}`)
  }).catch(() => { ElMessage.success(`手动复制: ${s.resume_command}`) })
}

function copyUUID(id) {
  navigator.clipboard.writeText(id).then(() => ElMessage.success('UUID 已复制'))
}

// Custom name editing
const editNameVisible = ref(false)
const editNameValue = ref('')
const editNameTarget = ref(null)

function openNameEditor(session) {
  editNameTarget.value = session
  editNameValue.value = session.custom_name || ''
  editNameVisible.value = true
}

async function saveCustomName() {
  const s = editNameTarget.value
  if (!s) return
  try {
    const result = await setCustomName(s.engine, s.id, editNameValue.value)
    // Update local state
    s.custom_name = result.custom_name || null
    // Also update drawer session if same
    if (drawerSession.value?.id === s.id && drawerSession.value?.engine === s.engine) {
      drawerSession.value.custom_name = s.custom_name
    }
    editNameVisible.value = false
    ElMessage.success('名称已保存')
  } catch (err) {
    ElMessage.error('保存失败: ' + err.message)
  }
}

async function clearCustomName(session) {
  try {
    await deleteCustomName(session.engine, session.id)
    session.custom_name = null
    if (drawerSession.value?.id === session.id && drawerSession.value?.engine === session.engine) {
      drawerSession.value.custom_name = null
    }
    ElMessage.success('名称已清除')
  } catch (err) {
    ElMessage.error('清除失败: ' + err.message)
  }
}

const deleteConfirmVisible = ref(false)
const deleteTarget = ref(null)
const deleting = ref(false)
const refreshDashboard = inject('refreshDashboard', () => {})

function showDeleteConfirm(session) {
  deleteTarget.value = session
  deleteConfirmVisible.value = true
}

async function confirmDelete() {
  const session = deleteTarget.value
  if (!session || deleting.value) return
  deleting.value = true
  try {
    await deleteSession(session.engine, session.id)
    ElMessage.success('会话已删除')
    deleteConfirmVisible.value = false
    deleteTarget.value = null
    closeDrawer()
    loadSessions()
    await refreshDashboard()
  } catch (err) {
    ElMessage.error('删除失败: ' + (err.message || ''))
  } finally {
    deleting.value = false
  }
}

function goToDetailPage(session) {
  if (!session) return
  closeDrawer()
  router.push({ name: 'session-detail', params: { id: session.id }, query: { engine: session.engine } })
}

async function generateDrawerTitle() {
  const s = drawerSession.value
  if (!s) return
  drawerAiLoading.value = true
  try {
    const result = await generateTitle(s.engine, s.id)
    if (result.title) {
      editNameTarget.value = s
      editNameValue.value = result.title
      editNameVisible.value = true
      ElMessage.success('标题已生成，可点击保存确认')
    } else {
      ElMessage.warning(result.error || '生成失败')
    }
  } catch (err) {
    ElMessage.error('生成失败: ' + err.message)
  } finally {
    drawerAiLoading.value = false
  }
}

function toggleFavorite(s) {
  if (!s || isFavoritePending(s)) return
  setFavoritePending(s, true)

  const action = s.is_favorite ? unfavoriteSession : favoriteSession
  action(s.engine, s.id)
    .then((result) => {
      // Use API response as immediate source of truth
      s.is_favorite = result.is_favorite
      s.favorited_at = result.favorited_at
      // Sync drawer immediately if it matches the acted-on session
      if (drawerSession.value && drawerSession.value.id === s.id && drawerSession.value.engine === s.engine) {
        drawerSession.value.is_favorite = result.is_favorite
        drawerSession.value.favorited_at = result.favorited_at
      }
      if (favoriteOnly.value && !result.is_favorite) {
        const idx = sessions.value.findIndex(x => x.id === s.id && x.engine === s.engine)
        if (idx !== -1) {
          sessions.value.splice(idx, 1)
          total.value = Math.max(0, total.value - 1)
          if (displayedSessions.value.length === 0 && page.value > 1) {
            page.value--
            return loadSessions()
          }
        }
      }
    })
    .catch((err) => {
      ElMessage.error('收藏操作失败: ' + (err.message || ''))
    })
    .finally(() => {
      setFavoritePending(s, false)
    })
}

function onFavoriteOnlyChange() {
  favoriteOnly.value = !favoriteOnly.value
}

watch(() => props.selectedEngine, () => { page.value = 1; loadSessions() })
watch(favoriteOnly, () => { page.value = 1; loadSessions() })
onMounted(loadSessions)
</script>

<style scoped>
.sessions-view { width: 100%; }

/* Stats */
.stats-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 16px; }
.stat-card { padding: 16px 20px; border-radius: 12px; border: 1px solid var(--border); background: var(--bg-card); }
.stat-lbl { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; }
.stat-val { font-size: 22px; font-weight: 800; font-family: 'SF Mono', monospace; color: var(--text-primary); }
.stat-val.accent { color: var(--accent-light); }
.stat-val.success { color: var(--success); }
.stat-unit { font-size: 13px; font-weight: 400; color: var(--text-muted); }
.engine-dots { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.engine-dot { font-size: 10px; padding: 1px 6px; border-radius: 4px; color: #fff; font-family: 'SF Mono', monospace; }

/* Search */
.search-bar { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin-bottom: 16px; }
.search-wrap { position: relative; flex: 1; min-width: 200px; max-width: 480px; }
.search-ico { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); font-size: 14px; opacity: 0.5; }
.search-input { width: 100%; padding: 9px 32px 9px 36px; border-radius: 10px; border: 1px solid var(--border); background: var(--bg-input); color: var(--text-primary); font-size: 13px; outline: none; }
.search-input:focus { border-color: var(--accent); }
.search-clear { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 12px; }
.filter-group { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.filter-lbl { font-size: 11px; color: var(--text-muted); }
.filter-btn { padding: 5px 12px; border-radius: 8px; border: 1px solid var(--border); background: transparent; color: var(--text-secondary); font-size: 11px; cursor: pointer; }
.filter-btn:hover { background: var(--accent-bg); }
.filter-btn.active { background: var(--accent-bg); border-color: var(--accent); color: var(--accent-light); font-weight: 600; }
.filter-group + .filter-group .filter-btn { min-height: 44px; }

/* Session Cards */
.session-list { }
.list-header { margin-bottom: 12px; }
.list-header-text { font-size: 12px; color: var(--text-muted); font-weight: 500; }

.loading-state { padding: 48px; text-align: center; color: var(--text-muted); font-size: 14px; }
.empty-state { padding: 48px; text-align: center; color: var(--text-muted); border-radius: 12px; border: 1px solid var(--border); background: var(--bg-card); font-size: 13px; }

.card-grid { display: flex; flex-direction: column; gap: 12px; }
.session-card {
  display: flex; justify-content: space-between; align-items: center;
  padding: 16px 20px; border-radius: 12px; border: 1px solid var(--border);
  background: var(--bg-card); cursor: pointer; transition: all 0.12s;
  border-left: 4px solid var(--accent);
}
.session-card:hover { border-color: var(--text-muted); background: var(--bg-card-hover); }
.card-main { flex: 1; min-width: 0; padding-right: 16px; }
.card-tags { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; flex-wrap: wrap; }
.engine-badge { padding: 2px 8px; border-radius: 4px; font-size: 9px; font-weight: 700; letter-spacing: 0.5px; border: 1px solid; font-family: 'SF Mono', monospace; }
.status-badge { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border-radius: 4px; font-size: 9px; font-weight: 600; }
.status-badge.status-success { background: rgba(16,185,129,0.1); color: var(--success); }
.status-badge.status-error { background: rgba(239,68,68,0.1); color: var(--danger); }
.status-badge.status-interrupted { background: rgba(245,158,11,0.1); color: var(--warning); }
.status-badge.status-active { background: rgba(59,130,246,0.1); color: #3b82f6; animation: pulse 1.5s infinite; }
.status-badge.status-unknown { background: rgba(100,116,139,0.1); color: var(--text-muted); }
.status-dot-card { width: 6px; height: 6px; border-radius: 50%; }
.dot-success { background: var(--success); }
.dot-error { background: var(--danger); }
.dot-interrupted { background: var(--warning); }
.dot-active { background: #3b82f6; }
.dot-unknown { background: var(--text-muted); }
.id-preview { font-size: 10px; color: var(--text-muted); }
.id-highlight { color: var(--accent-light); }
.name-btn-card { background: none; border: none; font-size: 12px; cursor: pointer; opacity: 0.4; padding: 0 2px; }
.name-btn-card:hover { opacity: 1; }
.card-summary { font-size: 1rem; font-weight: 700; color: var(--text-primary); line-height: 1.4; margin-bottom: 6px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.card-original-summary { font-size: 11px; color: var(--text-muted); margin-bottom: 6px; display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden; }
.card-path { display: flex; align-items: center; gap: 4px; font-size: 10px; color: var(--text-muted); }
.path-ico { font-size: 12px; }
.path-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: 'SF Mono', monospace; }

.card-side { display: flex; align-items: center; gap: 16px; flex-shrink: 0; }
.card-actions { display: flex; gap: 8px; flex-shrink: 0; align-items: center; }
.action-btn { display: inline-flex; align-items: center; justify-content: center; gap: 4px; min-width: 44px; min-height: 44px; padding: 4px 12px; border-radius: 8px; font-size: 13px; font-weight: 500; cursor: pointer; background: transparent; transition: all 0.12s; border: 1px solid var(--border); color: var(--text-secondary); }
.action-btn:hover { border-color: var(--accent); color: var(--accent-light); }
.action-btn:focus { outline: 2px solid var(--accent); outline-offset: 2px; }
.action-btn.fav { border-color: var(--border); }
.action-btn.fav:hover { border-color: #f59e0b; color: #f59e0b; }
.action-btn.fav.is-fav { border-color: #f59e0b; color: #f59e0b; background: rgba(245,158,11,0.1); }
.action-btn:disabled { opacity: 0.5; cursor: not-allowed; border-color: var(--border) !important; color: var(--text-muted) !important; background: transparent !important; }

.card-meta { display: flex; align-items: center; gap: 16px; flex-shrink: 0; }
.meta-col { text-align: center; }
.meta-col-label { display: block; font-size: 9px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 2px; }
.meta-col-val { font-size: 11px; font-family: 'SF Mono', monospace; color: var(--text-secondary); }
.meta-col-val.time { font-size: 10px; }
.meta-col-val.calls { font-weight: 600; padding: 1px 8px; border-radius: 10px; }
.calls-danger { color: var(--danger); background: rgba(239,68,68,0.1); }
.calls-warn { color: var(--warning); background: rgba(245,158,11,0.1); }
.calls-normal { color: var(--text-secondary); background: rgba(100,116,139,0.1); }
.arrow { font-size: 18px; color: var(--text-muted); }

/* Pagination */
.pagination { display: flex; align-items: center; justify-content: center; gap: 16px; margin-top: 16px; padding: 12px; }
.page-info { font-size: 12px; color: var(--text-muted); }
.page-btns { display: flex; gap: 4px; align-items: center; }
.page-btn { min-width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; border-radius: 6px; border: 1px solid var(--border); background: var(--bg-input); color: var(--text-secondary); font-size: 12px; font-family: 'SF Mono', monospace; cursor: pointer; }
.page-btn:hover { border-color: var(--accent); color: var(--accent-light); }
.page-btn.active { background: var(--accent); border-color: var(--accent); color: #fff; }
.page-btn:disabled { opacity: 0.4; }
.page-ellipsis { padding: 0 4px; color: var(--text-muted); }

/* Drawer */
.drawer-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 500; display: flex; justify-content: flex-end; }
.drawer { width: 100%; max-width: 560px; height: 100%; background: var(--bg-card); border-left: 1px solid var(--border); display: flex; flex-direction: column; animation: slideIn 0.25s ease; }
@keyframes slideIn { from { transform: translateX(100%); } to { transform: translateX(0); } }
.drawer-head { padding: 20px; border-bottom: 1px solid var(--border); background: rgba(15,23,42,0.3); }
.drawer-title-bar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.drawer-badge { padding: 3px 10px; border-radius: 4px; font-size: 10px; font-weight: 700; border: 1px solid; font-family: 'SF Mono', monospace; }
.drawer-close { background: none; border: none; color: var(--text-muted); font-size: 18px; cursor: pointer; }
.drawer-close:hover { color: var(--text-primary); }
.drawer-summary { font-size: 14px; font-weight: 600; color: var(--text-primary); line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.drawer-id { font-size: 10px; color: var(--text-muted); margin-top: 4px; }

.drawer-body { flex: 1; overflow-y: auto; padding: 16px 20px; }
.drawer-meta-box { padding: 12px 16px; border-radius: 10px; border: 1px solid var(--border); background: rgba(15,23,42,0.2); margin-bottom: 16px; }
.meta-row { display: grid; grid-template-columns: 100px 1fr; gap: 8px; padding: 5px 0; font-size: 11px; }
.meta-lbl { color: var(--text-muted); }
.meta-val { color: var(--text-primary); font-weight: 500; }
.meta-val.accent { color: var(--accent-light); }
.status-tag-drawer { display: inline-block; padding: 1px 8px; border-radius: 4px; font-size: 9px; font-weight: 700; }
.tag-success { background: rgba(16,185,129,0.1); color: var(--success); }
.tag-error { background: rgba(239,68,68,0.1); color: var(--danger); }
.tag-interrupted { background: rgba(245,158,11,0.1); color: var(--warning); }
.tag-active { background: rgba(59,130,246,0.1); color: #3b82f6; }
.tag-unknown { background: rgba(100,116,139,0.1); color: var(--text-muted); }

.drawer-section { margin-bottom: 16px; }
.drawer-section-title { font-size: 12px; font-weight: 600; color: var(--text-secondary); margin-bottom: 8px; }
.drawer-error-box { padding: 32px; text-align: center; }
.drawer-error-text { font-size: 13px; color: var(--danger); margin-bottom: 12px; }
.drawer-retry-btn { padding: 6px 16px; border-radius: 6px; border: 1px solid var(--accent); background: var(--accent-bg); color: var(--accent-light); cursor: pointer; font-size: 12px; }
.trace-empty { padding: 24px; text-align: center; color: var(--text-muted); font-size: 12px; border: 1px dashed var(--border); border-radius: 8px; }
.trace-list { }
.trace-item { display: flex; gap: 8px; padding: 4px 0; align-items: flex-start; }
.trace-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; margin-top: 4px; }
.dot-primary { background: var(--accent); }
.dot-success { background: var(--success); }
.dot-warning { background: var(--warning); }
.dot-info { background: var(--text-muted); }
.dot-default { background: var(--text-muted); }
.trace-content { flex: 1; min-width: 0; }
.trace-tag { display: inline-block; padding: 0 6px; border-radius: 3px; font-size: 9px; font-weight: 600; margin-right: 6px; }
.trace-tag-primary { background: var(--accent-bg); color: var(--accent-light); }
.trace-tag-success { background: rgba(16,185,129,0.1); color: var(--success); }
.trace-tag-warning { background: rgba(245,158,11,0.1); color: var(--warning); }
.trace-tag-info { background: rgba(100,116,139,0.1); color: var(--text-muted); }
.trace-msg { font-size: 10px; color: var(--text-secondary); }

.drawer-footer { display: flex; gap: 8px; padding: 16px 20px; border-top: 1px solid var(--border); background: rgba(15,23,42,0.2); }
.footer-btn-copy, .footer-btn-uuid { flex: 1; display: flex; align-items: center; justify-content: center; gap: 6px; padding: 10px; border-radius: 8px; font-size: 12px; font-weight: 600; cursor: pointer; min-height: 44px; min-width: 44px; }
.footer-btn-copy { background: var(--accent); border: none; color: #fff; }
.footer-btn-copy:hover { opacity: 0.9; }
.footer-btn-uuid { border: 1px solid var(--border); background: var(--bg-input); color: var(--text-secondary); }
.footer-btn-uuid:hover { background: var(--bg-card-hover); }

/* Name editor */
.drawer-original-summary { font-size: 11px; color: var(--text-muted); margin-top: 2px; }
.drawer-name-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.name-edit-btn { background: none; border: 1px solid var(--border); padding: 2px 8px; border-radius: 4px; font-size: 10px; color: var(--text-secondary); cursor: pointer; }
.name-edit-btn:hover { border-color: var(--accent); color: var(--accent-light); }
.detail-btn { background: var(--accent-bg); border: 1px solid var(--accent-border); padding: 4px 10px; border-radius: 4px; font-size: 11px; font-weight: 600; color: var(--accent-light); cursor: pointer; white-space: nowrap; }
.detail-btn:hover { background: var(--accent); color: #fff; }
.dialog-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 600; display: flex; align-items: center; justify-content: center; }
.dialog { background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; width: 90%; max-width: 420px; }
.dialog-head { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid var(--border); }
.dialog-head h3 { font-size: 15px; font-weight: 600; color: var(--text-primary); }
.dialog-close { background: none; border: none; color: var(--text-muted); font-size: 18px; cursor: pointer; }
.dialog-body { padding: 16px 20px; }
.dialog-hint { font-size: 12px; color: var(--text-muted); margin-bottom: 8px; }
.dialog-input { width: 100%; padding: 9px 12px; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-input); color: var(--text-primary); font-size: 13px; outline: none; }
.dialog-input:focus { border-color: var(--accent); }
.dialog-counter { text-align: right; font-size: 11px; color: var(--text-muted); margin-top: 4px; }
.dialog-foot { display: flex; gap: 8px; padding: 12px 20px; border-top: 1px solid var(--border); justify-content: flex-end; }
.dialog-btn { padding: 8px 16px; border-radius: 8px; border: 1px solid var(--border); background: transparent; color: var(--text-secondary); font-size: 12px; cursor: pointer; min-height: 44px; min-width: 44px; }
.dialog-btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.dialog-btn.danger { background: var(--danger); border-color: var(--danger); color: #fff; }
.dialog-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.dialog-id { font-size: 11px; color: var(--text-muted); font-family: 'SF Mono', monospace; word-break: break-all; }
.dialog-btn:hover { opacity: 0.9; }

@media (max-width: 768px) {
  .stats-row { grid-template-columns: repeat(2, 1fr); }
  .session-card { flex-direction: column; align-items: flex-start; gap: 8px; }
  .card-main { padding-right: 0; width: 100%; }
  .card-tags { gap: 4px; }
  .id-preview { flex-shrink: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
  .card-side { flex-direction: column; align-items: flex-end; width: 100%; gap: 12px; }
  .card-meta { width: 100%; justify-content: space-around; }
  .card-actions { width: 100%; justify-content: flex-end; }
  .drawer { max-width: 100%; }
}
.footer-btn-detail {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: 6px;
  padding: 10px; border-radius: 8px; font-size: 12px; font-weight: 600; cursor: pointer;
  border: 1px solid var(--accent-border); background: var(--accent-bg);
  color: var(--accent-light); transition: all 0.12s;
  min-height: 44px;
  min-width: 44px;
}
.footer-btn-detail:hover { background: var(--accent); color: #fff; }
.footer-btn-delete {
  flex: 1; display: flex; align-items: center; justify-content: center; gap: 6px;
  padding: 10px; border-radius: 8px; font-size: 12px; font-weight: 600; cursor: pointer;
  border: 1px solid rgba(239,68,68,0.3); background: rgba(239,68,68,0.08);
  color: var(--danger); transition: all 0.12s;
  min-height: 44px;
  min-width: 44px;
}
.footer-btn-delete:hover { background: var(--danger); color: #fff; }

@media (max-width: 480px) {
  .drawer-footer { flex-wrap: wrap; }
  .footer-btn-copy, .footer-btn-uuid, .footer-btn-detail { flex: 1 1 100%; }
}

/* Favorite Buttons */
.fav-btn-card {
  background: none; border: none; font-size: 14px; cursor: pointer;
  padding: 0; transition: transform 0.12s; line-height: 1;
  color: var(--text-muted);
  min-width: 44px; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
}
.fav-btn-card:hover { transform: scale(1.2); }
.fav-btn-card.is-fav { color: #f59e0b; }
.fav-btn-card:disabled { opacity: 0.4; cursor: not-allowed; transform: none; }

.fav-btn-drawer {
  background: none; border: 1px solid var(--border); padding: 2px 8px;
  border-radius: 4px; font-size: 10px; color: var(--text-secondary); cursor: pointer;
  transition: all 0.12s;
  min-width: 44px; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
}
.fav-btn-drawer:hover { border-color: #f59e0b; color: #f59e0b; }
.fav-btn-drawer.is-fav { border-color: #f59e0b; color: #f59e0b; background: rgba(245,158,11,0.1); }
.fav-btn-drawer:disabled { opacity: 0.4; cursor: not-allowed; }
</style>
