'use client'

/**
 * Knowledge Base Page - 知識殿堂
 * ==============================
 * KB Phase 1: 左側分類導航 + 搜尋 + 知識條目列表
 *
 * 建立時間: 2026-04-02 (台北時區)
 * 建立者: Claude Code (Knowledge Base Phase 1)
 *
 * API: /api/v1/knowledge
 */

import { useState, useEffect, useCallback, useMemo } from 'react'
import { AppLayout } from '@/components/layout'
import { useTranslations } from 'next-intl'
import Link from 'next/link'
import { cn } from '@/lib/utils'
import {
  Search, BookOpen, FileText, Shield, Cpu,
  Server, Eye, Bot, ChevronRight, Plus, Sparkles, ClipboardList, Tag, TriangleAlert,
  GitBranch, CheckCircle2, Clock3, Link2, FileSearch, ListChecks, ArrowRight, Users,
  PlayCircle, Database, RotateCw,
} from 'lucide-react'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'

// =============================================================================
// Types
// =============================================================================

interface KnowledgeEntry {
  id: string
  title: string
  content: string
  // 2026-04-04 Claude Code: Phase 25 P1 新增 auto_runbook + anti_pattern
  entry_type: 'incident_case' | 'runbook' | 'best_practice' | 'postmortem' | 'auto_runbook' | 'anti_pattern'
  category: string
  tags: string[]
  source: 'ai_extracted' | 'human'
  status: 'draft' | 'review' | 'approved' | 'archived'
  related_incident_id: string | null
  related_playbook_id: string | null
  view_count: number
  created_by: string | null
  created_at: string
  updated_at: string
}

interface CategoryCount {
  category: string
  count: number
}

interface ListResponse {
  items: KnowledgeEntry[]
  total: number
  categories: CategoryCount[]
}

interface KnowledgeStaleCandidate {
  entry_id: string
  title: string
  priority_tier: string
  priority_score: number
  stale_days: number
  recommended_action: string
  owner_review_dispatch_id?: string | null
  owner_review_status?: string | null
  owner_review_stage?: string | null
  related_incident_id?: string | null
  related_playbook_id?: string | null
}

interface KnowledgeStaleCandidatesResponse {
  total_stale: number
  returned: number
  threshold_days: number
  writes_on_read: boolean
  manual_review_required: boolean
  items: KnowledgeStaleCandidate[]
}

interface KnowledgeStaleOwnerReviewInboxResponse {
  total: number
  returned: number
  writes_on_read: boolean
  manual_review_required: boolean
}

interface KnowledgeStaleOwnerReviewBurnDownResponse {
  burn_down_status: string
  entries_to_threshold: number
  pending_owner_reviews: number
  completed_owner_reviews: number
  completion_audit_total?: number
  stale_ratio_recheck_total?: number
  current_snapshot?: {
    stale_count: number
    total_count: number
    stale_ratio: number
    threshold: number
    stale_days: number
  } | null
}

interface KnowledgeStaleOwnerReviewCompletionQueueResponse {
  total: number
  pending_count: number
  ready_count: number
  blocked_count: number
  completed_count: number
  failed_count: number
  writes_on_read: boolean
  manual_review_required: boolean
  batch_writes_allowed: boolean
}

interface KnowledgeGovernanceTelemetry {
  staleCandidates: KnowledgeStaleCandidatesResponse | null
  ownerReviews: KnowledgeStaleOwnerReviewInboxResponse | null
  burnDown: KnowledgeStaleOwnerReviewBurnDownResponse | null
  completionQueue: KnowledgeStaleOwnerReviewCompletionQueueResponse | null
}

// =============================================================================
// Category Config
// =============================================================================

const CATEGORY_ICONS: Record<string, React.ReactNode> = {
  infrastructure: <Server className="w-4 h-4" />,
  application: <FileText className="w-4 h-4" />,
  ai_system: <Bot className="w-4 h-4" />,
  security: <Shield className="w-4 h-4" />,
}

const CATEGORIES = ['infrastructure', 'application', 'ai_system', 'security'] as const
const KNOWN_CATEGORY_KEYS = new Set([
  'AI治理',
  'alert_handling',
  'application',
  'ai_system',
  'auto_repair',
  'backup_failure',
  'database',
  'devops_tool',
  'external_site',
  'flywheel_health',
  'general',
  'host_resource',
  'incident_postmortem',
  'infrastructure',
  'kubernetes',
  'postmortem',
  'security',
  'ssl_cert',
])

const CATEGORY_OVERVIEW_LIMIT = 8
const KNOWN_TAG_KEYS = new Set([
  'ai_extracted',
  'auto_runbook',
  'critical',
  'execution',
  'execution_failed',
  'failure',
  'human_approved',
  'human_intervention',
  'incident',
  'postmortem',
  'telegram',
  'warning',
])

const REDUNDANT_SIGNAL_TAGS = new Set([
  'AI治理',
  'alert_handling',
  'application',
  'ai_system',
  'database',
  'general',
  'host_resource',
  'incident_case',
  'infrastructure',
  'postmortem',
  'security',
])

const KNOWLEDGE_PROJECT_ID = 'awoooi'

const appendKnowledgeProject = (url: string) => {
  const separator = url.includes('?') ? '&' : '?'
  return `${url}${separator}project_id=${encodeURIComponent(KNOWLEDGE_PROJECT_ID)}`
}

// =============================================================================
// Type Badge Colors
// =============================================================================

const TYPE_COLORS: Record<string, string> = {
  incident_case: 'bg-status-warning/10 text-status-warning border-status-warning/20',
  runbook: 'bg-claw-blue/10 text-claw-blue border-claw-blue/20',
  best_practice: 'bg-status-healthy/10 text-status-healthy border-status-healthy/20',
  postmortem: 'bg-status-critical/10 text-status-critical border-status-critical/20',
  // 2026-04-04 Claude Code: Phase 25 P1 — Auto-Harvesting 新增類型
  auto_runbook: 'bg-purple-100 text-purple-600 border-purple-200',
  anti_pattern: 'bg-red-100 text-red-600 border-red-200',
}

const STATUS_COLORS: Record<string, string> = {
  draft: 'bg-nothing-gray-100 text-nothing-gray-500',
  review: 'bg-status-warning/10 text-status-warning',
  approved: 'bg-status-healthy/10 text-status-healthy',
}

const CATEGORY_BAR_COLORS: Record<string, string> = {
  AI治理: 'bg-claw-blue',
  alert_handling: 'bg-status-warning',
  infrastructure: 'bg-claw-blue',
  application: 'bg-status-healthy',
  ai_system: 'bg-purple-500',
  security: 'bg-status-warning',
  general: 'bg-nothing-gray-400',
  postmortem: 'bg-status-critical',
  __other__: 'bg-nothing-gray-300',
}

const TAG_TONES: Record<string, string> = {
  critical: 'border-status-critical/20 bg-status-critical/10 text-status-critical',
  execution_failed: 'border-status-critical/20 bg-status-critical/10 text-status-critical',
  failure: 'border-status-critical/20 bg-status-critical/10 text-status-critical',
  human_intervention: 'border-status-warning/20 bg-status-warning/10 text-status-warning',
  warning: 'border-status-warning/20 bg-status-warning/10 text-status-warning',
  human_approved: 'border-status-healthy/20 bg-status-healthy/10 text-status-healthy',
  ai_extracted: 'border-claw-blue/20 bg-claw-blue/10 text-claw-blue',
  auto_runbook: 'border-purple-200 bg-purple-100 text-purple-600',
  incident: 'border-nothing-gray-200 bg-white text-secondary',
  postmortem: 'border-status-critical/20 bg-white text-status-critical',
  telegram: 'border-claw-blue/20 bg-white text-claw-blue',
}

const humanizeCategory = (category: string) => {
  if (!category.includes('_')) return category
  return category
    .split('_')
    .filter(Boolean)
    .map(part => {
      const upper = part.toUpperCase()
      if (['AI', 'DB', 'SSL', 'SLO', 'API'].includes(upper)) return upper
      return `${part.charAt(0).toUpperCase()}${part.slice(1)}`
    })
    .join(' ')
}

const humanizeSignalTag = (tag: string) => {
  if (!tag.includes('_')) return tag
  return tag
    .split('_')
    .filter(Boolean)
    .map(part => {
      const upper = part.toUpperCase()
      if (['AI', 'CPU', 'DB', 'DNS', 'HTTP', 'IP', 'KB', 'P0', 'P1', 'P2', 'P3', 'SSL', 'SLO', 'SSH', 'TYPE'].includes(upper)) return upper
      return `${part.charAt(0).toUpperCase()}${part.slice(1)}`
    })
    .join(' ')
}

const getSignalTags = (entry: KnowledgeEntry) => {
  const seen = new Set<string>()
  return entry.tags
    .filter(tag => tag && tag !== entry.category && tag !== entry.entry_type && tag !== entry.source)
    .filter(tag => !REDUNDANT_SIGNAL_TAGS.has(tag))
    .filter(tag => {
      if (seen.has(tag)) return false
      seen.add(tag)
      return true
    })
}

// =============================================================================
// Component
// =============================================================================

export default function KnowledgeBasePage({
  params,
}: {
  params: { locale: string }
}) {
  const t = useTranslations('knowledgeBase')
  const tCommon = useTranslations('common')

  const [entries, setEntries] = useState<KnowledgeEntry[]>([])
  const [total, setTotal] = useState(0)
  const [categories, setCategories] = useState<CategoryCount[]>([])
  const [loading, setLoading] = useState(true)
  const [entryFetchError, setEntryFetchError] = useState<string | null>(null)
  const [governanceTelemetry, setGovernanceTelemetry] = useState<KnowledgeGovernanceTelemetry>({
    staleCandidates: null,
    ownerReviews: null,
    burnDown: null,
    completionQueue: null,
  })
  const [governanceLoading, setGovernanceLoading] = useState(true)

  // Filters
  const [selectedCategory, setSelectedCategory] = useState<string | null>(null)
  const [selectedType, setSelectedType] = useState<string | null>(null)
  const [searchQuery, setSearchQuery] = useState('')
  const [semanticMode, setSemanticMode] = useState(false)
  const [semanticResults, setSemanticResults] = useState<(KnowledgeEntry & { score: number })[]>([])
  const [semanticLoading, setSemanticLoading] = useState(false)
  const [selectedEntry, setSelectedEntry] = useState<KnowledgeEntry | null>(null)

  // KB-D: approve/archive state (2026-04-03 ogt)
  const [actionLoading, setActionLoading] = useState(false)

  const fetchEntries = useCallback(async () => {
    setLoading(true)
    setEntryFetchError(null)
    try {
      const params = new URLSearchParams()
      params.set('project_id', KNOWLEDGE_PROJECT_ID)
      if (selectedCategory) params.set('category', selectedCategory)
      if (selectedType) params.set('entry_type', selectedType)
      if (searchQuery) params.set('q', searchQuery)
      params.set('limit', '50')

      const apiBase = process.env.NEXT_PUBLIC_API_URL ?? ''
      const res = await fetch(`${apiBase}/api/v1/knowledge?${params}`)
      if (res.ok) {
        const data: ListResponse = await res.json()
        setEntries(data.items)
        setTotal(data.total)
        setCategories(data.categories)
        return
      }
      const errorBody = await res.json().catch(() => null) as { detail?: string; message?: string } | null
      setEntries([])
      setTotal(0)
      setCategories([])
      setEntryFetchError(errorBody?.detail ?? errorBody?.message ?? `${res.status} ${res.statusText}`)
    } catch (err) {
      console.error('Failed to fetch knowledge entries', err)
      setEntries([])
      setTotal(0)
      setCategories([])
      setEntryFetchError(err instanceof Error ? err.message : String(err))
    } finally {
      setLoading(false)
    }
  }, [selectedCategory, selectedType, searchQuery])

  useEffect(() => {
    if (!semanticMode) fetchEntries()
  }, [fetchEntries, semanticMode])

  const fetchGovernanceTelemetry = useCallback(async () => {
    setGovernanceLoading(true)
    try {
      const apiBase = process.env.NEXT_PUBLIC_API_URL ?? ''
      const [staleCandidates, ownerReviews, burnDown, completionQueue] = await Promise.all([
        fetch(`${apiBase}/api/v1/ai/governance/km-stale-candidates?project_id=awoooi&limit=5`)
          .then(res => res.ok ? res.json() : null)
          .catch(() => null),
        fetch(`${apiBase}/api/v1/ai/governance/km-stale-owner-reviews?project_id=awoooi&dispatch_status=pending&limit=5`)
          .then(res => res.ok ? res.json() : null)
          .catch(() => null),
        fetch(`${apiBase}/api/v1/ai/governance/km-stale-owner-review-burndown?project_id=awoooi&limit=5`)
          .then(res => res.ok ? res.json() : null)
          .catch(() => null),
        fetch(`${apiBase}/api/v1/ai/governance/km-stale-owner-review-completion-queue?project_id=awoooi&status_bucket=all&limit=5`)
          .then(res => res.ok ? res.json() : null)
          .catch(() => null),
      ])

      setGovernanceTelemetry({
        staleCandidates,
        ownerReviews,
        burnDown,
        completionQueue,
      })
    } catch (err) {
      console.error('Failed to fetch knowledge governance telemetry', err)
    } finally {
      setGovernanceLoading(false)
    }
  }, [])

  useEffect(() => {
    fetchGovernanceTelemetry()
  }, [fetchGovernanceTelemetry])

  // KB Phase 2: 語意搜尋 (2026-04-04 Claude Code)
  const fetchSemanticResults = useCallback(async (q: string) => {
    if (!q.trim()) { setSemanticResults([]); return }
    setSemanticLoading(true)
    setEntryFetchError(null)
    try {
      const apiBase = process.env.NEXT_PUBLIC_API_URL ?? ''
      const res = await fetch(`${apiBase}/api/v1/knowledge/semantic-search?q=${encodeURIComponent(q)}&limit=20&project_id=${encodeURIComponent(KNOWLEDGE_PROJECT_ID)}`)
      if (res.ok) {
        const data = await res.json()
        setSemanticResults(data)
        return
      }
      const errorBody = await res.json().catch(() => null) as { detail?: string; message?: string } | null
      setSemanticResults([])
      setEntryFetchError(errorBody?.detail ?? errorBody?.message ?? `${res.status} ${res.statusText}`)
    } catch (err) {
      console.error('Semantic search failed', err)
      setSemanticResults([])
      setEntryFetchError(err instanceof Error ? err.message : String(err))
    } finally {
      setSemanticLoading(false)
    }
  }, [])

  useEffect(() => {
    if (semanticMode && searchQuery) {
      const timer = setTimeout(() => fetchSemanticResults(searchQuery), 500)
      return () => clearTimeout(timer)
    } else if (semanticMode && !searchQuery) {
      setSemanticResults([])
    }
  }, [semanticMode, searchQuery, fetchSemanticResults])

  // KB-D: Approve / Archive handlers (2026-04-03 ogt)
  const handleApprove = useCallback(async () => {
    if (!selectedEntry || actionLoading) return
    setActionLoading(true)
    try {
      const apiBase = process.env.NEXT_PUBLIC_API_URL ?? ''
      const res = await fetch(appendKnowledgeProject(`${apiBase}/api/v1/knowledge/${selectedEntry.id}/approve`), {
        method: 'POST',
      })
      if (res.ok) {
        setSelectedEntry({ ...selectedEntry, status: 'approved' })
        fetchEntries()
      }
    } catch (err) {
      console.error('Approve failed', err)
    } finally {
      setActionLoading(false)
    }
  }, [selectedEntry, actionLoading, fetchEntries])

  const handleArchive = useCallback(async () => {
    if (!selectedEntry || actionLoading) return
    setActionLoading(true)
    try {
      const apiBase = process.env.NEXT_PUBLIC_API_URL ?? ''
      const res = await fetch(appendKnowledgeProject(`${apiBase}/api/v1/knowledge/${selectedEntry.id}`), {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ status: 'archived' }),
      })
      if (res.ok) {
        setSelectedEntry({ ...selectedEntry, status: 'archived' })
        fetchEntries()
      }
    } catch (err) {
      console.error('Archive failed', err)
    } finally {
      setActionLoading(false)
    }
  }, [selectedEntry, actionLoading, fetchEntries])

  const displayedEntries = semanticMode ? semanticResults : entries
  const totalCount = categories.reduce((sum, c) => sum + c.count, 0)
  const isKnowledgeListUnavailable = Boolean(entryFetchError)
  const localeCode = params.locale === 'en' ? 'en-US' : 'zh-TW'
  const formatCount = useCallback(
    (value: number) => value.toLocaleString(localeCode),
    [localeCode],
  )
  const formatCategoryLabel = useCallback(
    (category: string) => {
      if (KNOWN_CATEGORY_KEYS.has(category)) {
        return t(`category.${category}`)
      }
      return humanizeCategory(category)
    },
    [t],
  )
  const formatDate = useCallback(
    (value: string) => new Date(value).toLocaleDateString(localeCode),
    [localeCode],
  )
  const formatSignalTag = useCallback(
    (tag: string) => {
      if (KNOWN_TAG_KEYS.has(tag)) {
        return t(`tag.${tag}`)
      }
      return humanizeSignalTag(tag)
    },
    [t],
  )

  const visibleSummary = useMemo(() => {
    const loaded = displayedEntries.length
    const aiExtracted = displayedEntries.filter(entry => entry.source === 'ai_extracted').length
    const approved = displayedEntries.filter(entry => entry.status === 'approved').length
    const approvedRate = loaded > 0 ? Math.round((approved / loaded) * 100) : 0
    return { loaded, aiExtracted, approved, approvedRate }
  }, [displayedEntries])

  const categoryRows = useMemo(() => {
    const sourceRows = categories.length > 0
      ? [...categories].sort((a, b) => b.count - a.count)
      : CATEGORIES.map(category => ({ category, count: 0 }))

    const visibleRows = sourceRows.slice(0, CATEGORY_OVERVIEW_LIMIT).map(row => {
      const pct = totalCount > 0 ? Math.round((row.count / totalCount) * 100) : 0
      return { category: row.category, count: row.count, pct }
    })
    const remainingCount = sourceRows
      .slice(CATEGORY_OVERVIEW_LIMIT)
      .reduce((sum, row) => sum + row.count, 0)

    if (remainingCount <= 0) return visibleRows

    return [
      ...visibleRows,
      {
        category: '__other__',
        count: remainingCount,
        pct: totalCount > 0 ? Math.round((remainingCount / totalCount) * 100) : 0,
      },
    ]
  }, [categories, totalCount])

  const qualityRows = useMemo(() => {
    const loaded = displayedEntries.length
    const now = Date.now()
    const freshWindowMs = 7 * 24 * 60 * 60 * 1000
    const pct = (count: number) => loaded > 0 ? Math.round((count / loaded) * 100) : 0
    const reviewBacklog = displayedEntries.filter(entry => entry.status === 'draft' || entry.status === 'review').length
    const freshWithin7d = displayedEntries.filter(entry => {
      const updatedAt = Date.parse(entry.updated_at)
      return Number.isFinite(updatedAt) && now - updatedAt <= freshWindowMs
    }).length
    const incidentLinked = displayedEntries.filter(entry => Boolean(entry.related_incident_id)).length
    const playbookLinked = displayedEntries.filter(entry => Boolean(entry.related_playbook_id)).length
    const signalRich = displayedEntries.filter(entry => getSignalTags(entry).length > 0).length

    return [
      { key: 'reviewBacklog', count: reviewBacklog, pct: pct(reviewBacklog), tone: 'bg-status-warning' },
      { key: 'freshWithin7d', count: freshWithin7d, pct: pct(freshWithin7d), tone: 'bg-status-healthy' },
      { key: 'incidentLinked', count: incidentLinked, pct: pct(incidentLinked), tone: 'bg-claw-blue' },
      { key: 'signalRich', count: signalRich, pct: pct(signalRich), tone: 'bg-purple-500' },
      { key: 'playbookLinked', count: playbookLinked, pct: pct(playbookLinked), tone: 'bg-nothing-gray-400' },
    ] as const
  }, [displayedEntries])

  const lineageNodes = useMemo(() => {
    const loaded = displayedEntries.length
    const pct = (count: number) => loaded > 0 ? Math.round((count / loaded) * 100) : 0
    const aiExtracted = displayedEntries.filter(entry => entry.source === 'ai_extracted').length
    const humanCreated = displayedEntries.filter(entry => entry.source === 'human').length
    const incidentLinked = displayedEntries.filter(entry => Boolean(entry.related_incident_id)).length
    const playbookLinked = displayedEntries.filter(entry => Boolean(entry.related_playbook_id)).length
    const approved = displayedEntries.filter(entry => entry.status === 'approved').length

    return [
      { key: 'source', count: aiExtracted, pct: pct(aiExtracted), icon: Bot, tone: 'border-purple-200 bg-purple-50 text-purple-600', detail: t('lineage.sourceDetail', { ai: formatCount(aiExtracted), human: formatCount(humanCreated) }) },
      { key: 'knowledge', count: loaded, pct: loaded > 0 ? 100 : 0, icon: BookOpen, tone: 'border-claw-blue/20 bg-claw-blue/8 text-claw-blue', detail: t('lineage.knowledgeDetail', { count: formatCount(loaded) }) },
      { key: 'incident', count: incidentLinked, pct: pct(incidentLinked), icon: TriangleAlert, tone: 'border-status-warning/20 bg-status-warning/10 text-status-warning', detail: t('lineage.countDetail', { count: formatCount(incidentLinked), total: formatCount(loaded) }) },
      { key: 'playbook', count: playbookLinked, pct: pct(playbookLinked), icon: ClipboardList, tone: 'border-nothing-gray-200 bg-white text-secondary', detail: t('lineage.countDetail', { count: formatCount(playbookLinked), total: formatCount(loaded) }) },
      { key: 'review', count: approved, pct: pct(approved), icon: CheckCircle2, tone: 'border-status-healthy/20 bg-status-healthy/10 text-status-healthy', detail: t('lineage.countDetail', { count: formatCount(approved), total: formatCount(loaded) }) },
    ] as const
  }, [displayedEntries, formatCount, t])

  const freshnessQueueRows = useMemo(() => {
    const loaded = displayedEntries.length
    const now = Date.now()
    const staleWindowMs = 7 * 24 * 60 * 60 * 1000
    const pct = (count: number) => loaded > 0 ? Math.round((count / loaded) * 100) : 0
    const stale = displayedEntries.filter(entry => {
      const updatedAt = Date.parse(entry.updated_at)
      return !Number.isFinite(updatedAt) || now - updatedAt > staleWindowMs
    }).length
    const missingIncident = displayedEntries.filter(entry => !entry.related_incident_id).length
    const missingPlaybook = displayedEntries.filter(entry => !entry.related_playbook_id).length
    const reviewBacklog = displayedEntries.filter(entry => entry.status === 'draft' || entry.status === 'review').length

    return [
      { key: 'stale', count: stale, pct: pct(stale), icon: Clock3, tone: 'bg-status-critical' },
      { key: 'missingIncident', count: missingIncident, pct: pct(missingIncident), icon: Link2, tone: 'bg-status-warning' },
      { key: 'missingPlaybook', count: missingPlaybook, pct: pct(missingPlaybook), icon: FileSearch, tone: 'bg-claw-blue' },
      { key: 'reviewBacklog', count: reviewBacklog, pct: pct(reviewBacklog), icon: Shield, tone: 'bg-purple-500' },
    ] as const
  }, [displayedEntries])

  const governanceSummary = useMemo(() => {
    const snapshot = governanceTelemetry.burnDown?.current_snapshot ?? null
    const ratio = snapshot ? Math.round(snapshot.stale_ratio * 1000) / 10 : null
    const threshold = snapshot ? Math.round(snapshot.threshold * 1000) / 10 : null
    const topCandidate = governanceTelemetry.staleCandidates?.items?.[0] ?? null
    const ownerReady = governanceTelemetry.completionQueue?.ready_count ?? 0
    const ownerBlocked = governanceTelemetry.completionQueue?.blocked_count ?? 0
    const ownerPending = governanceTelemetry.ownerReviews?.total ?? governanceTelemetry.completionQueue?.pending_count ?? 0
    const completed = governanceTelemetry.completionQueue?.completed_count ?? governanceTelemetry.burnDown?.completed_owner_reviews ?? 0
    const workItemsHref = topCandidate?.owner_review_dispatch_id
      ? `/awooop/work-items?project_id=awoooi&work_item_id=${encodeURIComponent(topCandidate.owner_review_dispatch_id)}`
      : '/awooop/work-items?project_id=awoooi'

    return {
      snapshot,
      ratio,
      threshold,
      topCandidate,
      ownerReady,
      ownerBlocked,
      ownerPending,
      completed,
      workItemsHref,
      staleTotal: governanceTelemetry.staleCandidates?.total_stale ?? snapshot?.stale_count ?? null,
      entriesToThreshold: governanceTelemetry.burnDown?.entries_to_threshold ?? null,
      burnDownStatus: governanceTelemetry.burnDown?.burn_down_status ?? null,
      writesOnRead: [
        governanceTelemetry.staleCandidates?.writes_on_read,
        governanceTelemetry.ownerReviews?.writes_on_read,
        governanceTelemetry.completionQueue?.writes_on_read,
      ].some(Boolean),
    }
  }, [governanceTelemetry])

  const governanceFlowNodes = useMemo(() => {
    const staleRatioText = governanceSummary.ratio === null ? '--' : `${governanceSummary.ratio}%`
    const thresholdText = governanceSummary.threshold === null ? '--' : `${governanceSummary.threshold}%`
    const recheckTotal = governanceTelemetry.burnDown?.stale_ratio_recheck_total ?? 0
    const auditTotal = governanceTelemetry.burnDown?.completion_audit_total ?? governanceSummary.completed
    const nodeTone = (state: 'warning' | 'ready' | 'waiting' | 'done' | 'blocked') => {
      switch (state) {
        case 'warning':
          return 'border-status-warning/25 bg-status-warning/10 text-status-warning'
        case 'ready':
          return 'border-claw-blue/25 bg-claw-blue/8 text-claw-blue'
        case 'done':
          return 'border-status-healthy/25 bg-status-healthy/10 text-status-healthy'
        case 'blocked':
          return 'border-status-critical/25 bg-status-critical/10 text-status-critical'
        default:
          return 'border-nothing-gray-200 bg-white text-secondary'
      }
    }

    return [
      {
        key: 'detected',
        icon: TriangleAlert,
        value: governanceSummary.staleTotal === null ? '--' : formatCount(governanceSummary.staleTotal),
        state: governanceSummary.burnDownStatus === 'above_threshold' ? 'warning' : 'done',
        detail: t('workItems.flow.detail.detected', { ratio: staleRatioText, threshold: thresholdText }),
      },
      {
        key: 'ownerReview',
        icon: Users,
        value: formatCount(governanceSummary.ownerPending),
        state: governanceSummary.ownerPending > 0 ? 'warning' : 'done',
        detail: t('workItems.flow.detail.ownerReview', { count: formatCount(governanceSummary.ownerPending) }),
      },
      {
        key: 'dryRun',
        icon: PlayCircle,
        value: formatCount(governanceSummary.ownerReady),
        state: governanceSummary.ownerReady > 0 ? 'ready' : 'waiting',
        detail: t('workItems.flow.detail.dryRun', {
          ready: formatCount(governanceSummary.ownerReady),
          blocked: formatCount(governanceSummary.ownerBlocked),
        }),
      },
      {
        key: 'ownerConfirm',
        icon: Shield,
        value: formatCount(governanceSummary.ownerReady),
        state: governanceSummary.ownerBlocked > 0 ? 'blocked' : governanceSummary.ownerReady > 0 ? 'ready' : 'waiting',
        detail: t('workItems.flow.detail.ownerConfirm'),
      },
      {
        key: 'writeback',
        icon: Database,
        value: formatCount(auditTotal),
        state: auditTotal > 0 ? 'done' : 'waiting',
        detail: t('workItems.flow.detail.writeback', { count: formatCount(auditTotal) }),
      },
      {
        key: 'recheck',
        icon: RotateCw,
        value: formatCount(recheckTotal),
        state: recheckTotal > 0 ? 'done' : 'waiting',
        detail: t('workItems.flow.detail.recheck', {
          count: formatCount(recheckTotal),
          remaining: governanceSummary.entriesToThreshold === null ? '--' : formatCount(governanceSummary.entriesToThreshold),
        }),
      },
    ].map(node => ({
      ...node,
      tone: nodeTone(node.state as 'warning' | 'ready' | 'waiting' | 'done' | 'blocked'),
    }))
  }, [formatCount, governanceSummary, governanceTelemetry.burnDown, t])

  const content = (
      <div className="flex min-h-[calc(100vh-64px)] flex-col lg:h-[calc(100vh-64px)] lg:flex-row">

        {/* 左側分類導航 */}
        <aside className="w-full flex-shrink-0 border-b border-nothing-gray-200/50 bg-white/50 p-3 lg:w-52 lg:border-b-0 lg:border-r">
          <h2 className="font-heading text-xs font-semibold text-tertiary uppercase tracking-widest mb-3 px-2">
            {t('title')}
          </h2>

          {/* All */}
          <button
            onClick={() => setSelectedCategory(null)}
            className={cn(
              'w-full flex items-center gap-2 px-2 py-1.5 rounded text-sm font-body transition-colors',
              !selectedCategory
                ? 'bg-claw-blue/8 text-claw-blue font-medium'
                : 'text-secondary hover:bg-nothing-gray-100'
            )}
          >
            <BookOpen className="w-4 h-4" />
            <span className="flex-1 text-left">{t('allCategories')}</span>
            <span className="text-xs text-muted">{totalCount}</span>
          </button>

          {/* Category items */}
          <div className="mt-1 grid grid-cols-2 gap-0.5 lg:block lg:space-y-0.5">
            {CATEGORIES.map(cat => {
              const count = categories.find(c => c.category === cat)?.count ?? 0
              return (
                <button
                  key={cat}
                  onClick={() => setSelectedCategory(cat === selectedCategory ? null : cat)}
                  className={cn(
                    'w-full flex items-center gap-2 px-2 py-1.5 rounded text-sm font-body transition-colors',
                    selectedCategory === cat
                      ? 'bg-claw-blue/8 text-claw-blue font-medium'
                      : 'text-secondary hover:bg-nothing-gray-100'
                  )}
                >
                  {CATEGORY_ICONS[cat]}
                  <span className="flex-1 text-left">{formatCategoryLabel(cat)}</span>
                  <span className="text-xs text-muted">{count}</span>
                </button>
              )
            })}
          </div>
        </aside>

        {/* 主區域 */}
        <main className="flex-1 flex flex-col min-w-0 min-h-0">

          {/* 頂部搜尋 + 篩選 */}
          <div className="flex flex-wrap items-center gap-3 px-4 py-3 border-b border-nothing-gray-200/50">
            {/* Search + Semantic Toggle */}
            <div className="flex min-w-[220px] flex-1 items-center gap-2 max-w-lg">
              <div className="relative flex-1">
                {semanticMode
                  ? <Sparkles className="absolute left-2.5 top-1/2 -translate-y-1/2 w-4 h-4 text-claw-blue" />
                  : <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 w-4 h-4 text-muted" />
                }
                <input
                  type="text"
                  value={searchQuery}
                  onChange={(e) => setSearchQuery(e.target.value)}
                  placeholder={semanticMode ? t('semanticSearchPlaceholder') : t('searchPlaceholder')}
                  className={cn(
                    "w-full pl-8 pr-3 py-1.5 text-sm font-body bg-nothing-gray-50 border rounded-md focus:outline-none transition-colors",
                    semanticMode
                      ? "border-claw-blue/50 focus:border-claw-blue"
                      : "border-nothing-gray-200 focus:border-claw-blue/50"
                  )}
                />
              </div>
              <button
                onClick={() => { setSemanticMode(m => !m); setSearchQuery('') }}
                title={semanticMode ? t('switchToKeyword') : t('switchToSemantic')}
                className={cn(
                  "flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-label rounded-md border transition-colors shrink-0",
                  semanticMode
                    ? "bg-claw-blue text-white border-claw-blue"
                    : "bg-nothing-gray-50 text-secondary border-nothing-gray-200 hover:border-claw-blue/50"
                )}
              >
                <Sparkles className="w-3 h-3" />
                {semanticMode ? t('semanticOn') : t('semanticOff')}
              </button>
            </div>

            {/* Type filter */}
            <select
              value={selectedType ?? ''}
              onChange={(e) => setSelectedType(e.target.value || null)}
              className="text-xs font-body bg-nothing-gray-50 border border-nothing-gray-200 rounded-md px-2 py-1.5 text-secondary focus:outline-none"
            >
              <option value="">{t('filterByType')}</option>
              {['incident_case', 'runbook', 'best_practice', 'postmortem', 'auto_runbook', 'anti_pattern'].map(tp => (
                <option key={tp} value={tp}>{t(`type.${tp}`)}</option>
              ))}
            </select>

            {/* Count */}
            <span className="text-xs text-muted font-body">
              {isKnowledgeListUnavailable ? '--' : total} {t('entries')}
            </span>

            {/* Create button */}
            <button className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-body font-medium bg-claw-blue text-white rounded-md hover:bg-claw-blue/90 transition-colors">
              <Plus className="w-3.5 h-3.5" />
              {t('createEntry')}
            </button>
          </div>

          {entryFetchError && (
            <div className="border-b border-status-warning/20 bg-status-warning/10 px-4 py-3">
              <div className="flex flex-col gap-2 rounded-md border border-status-warning/25 bg-white/75 px-3 py-2 sm:flex-row sm:items-center sm:justify-between">
                <div className="min-w-0">
                  <div className="flex items-center gap-2 text-xs font-label text-status-warning">
                    <TriangleAlert className="h-4 w-4 shrink-0" aria-hidden="true" />
                    <span>{t('dataChain.errorTitle')}</span>
                  </div>
                  <p className="mt-1 text-xs font-body leading-5 text-secondary">
                    {t('dataChain.errorDescription', { reason: entryFetchError })}
                  </p>
                </div>
                <button
                  onClick={() => {
                    if (semanticMode && searchQuery) {
                      fetchSemanticResults(searchQuery)
                    } else {
                      fetchEntries()
                    }
                  }}
                  className="inline-flex shrink-0 items-center justify-center gap-1.5 rounded-md border border-status-warning/25 bg-white px-3 py-1.5 text-xs font-label text-status-warning transition-colors hover:bg-status-warning/10"
                >
                  <RotateCw className="h-3.5 w-3.5" aria-hidden="true" />
                  {t('dataChain.retry')}
                </button>
              </div>
            </div>
          )}

          <section className="border-b border-nothing-gray-200/50 px-4 py-3 bg-white/35">
            <div className="grid grid-cols-1 gap-4 xl:grid-cols-[minmax(0,1fr)_280px]">
              <div className="grid grid-cols-2 gap-2 lg:grid-cols-4">
                {[
                  { label: t('overview.metricTotal'), value: formatCount(total), sub: t('overview.scopeFiltered'), tone: 'text-claw-blue' },
                  { label: t('overview.metricLoaded'), value: formatCount(visibleSummary.loaded), sub: t('overview.scopeCurrent'), tone: 'text-primary' },
                  { label: t('overview.metricAiExtracted'), value: formatCount(visibleSummary.aiExtracted), sub: t('overview.scopeCurrent'), tone: 'text-purple-600' },
                  { label: t('overview.metricApproved'), value: `${formatCount(visibleSummary.approved)} / ${visibleSummary.approvedRate}%`, sub: t('overview.scopeCurrent'), tone: 'text-status-healthy' },
                ].map(metric => (
                  <div key={metric.label} className="rounded-md border border-nothing-gray-200 bg-white/70 px-3 py-2">
                    <p className="text-[10px] font-label uppercase tracking-wider text-muted">{metric.label}</p>
                    <p className={cn('mt-1 text-xl font-heading font-semibold tabular-nums', metric.tone)}>
                      {isKnowledgeListUnavailable ? '--' : metric.value}
                    </p>
                    <p className="mt-0.5 text-[10px] font-body text-muted">{metric.sub}</p>
                  </div>
                ))}
              </div>

              <div className="rounded-md border border-nothing-gray-200 bg-white/70 px-3 py-2">
                <div className="mb-2 flex items-center justify-between">
                  <p className="text-[10px] font-label uppercase tracking-wider text-muted">{t('overview.categoryDistribution')}</p>
                  <span className="text-[10px] font-body text-muted">{formatCount(totalCount)}</span>
                </div>
                <div className="space-y-1.5">
                  {categoryRows.map(row => (
                    <div key={row.category} className="grid grid-cols-[96px_1fr_42px] items-center gap-2">
                      <span className="truncate text-[10px] font-body text-secondary">
                        {row.category === '__other__' ? t('overview.categoryOther') : formatCategoryLabel(row.category)}
                      </span>
                      <div className="h-1.5 overflow-hidden rounded-full bg-nothing-gray-100">
                        <div
                          className={cn('h-full rounded-full', CATEGORY_BAR_COLORS[row.category] ?? 'bg-nothing-gray-300')}
                          style={{ width: `${row.pct}%` }}
                        />
                      </div>
                      <span className="text-right text-[10px] font-label tabular-nums text-muted">{row.pct}%</span>
                    </div>
                  ))}
                </div>
              </div>
            </div>

            <div className="mt-3 rounded-md border border-nothing-gray-200 bg-white/70 px-3 py-2">
              <div className="mb-2 flex items-center justify-between gap-2">
                <p className="text-[10px] font-label uppercase tracking-wider text-muted">{t('quality.title')}</p>
                <span className="shrink-0 text-[10px] font-body text-muted">{t('quality.scope')}</span>
              </div>
              <div className="grid grid-cols-1 gap-2 sm:grid-cols-2 xl:grid-cols-5">
                {qualityRows.map(row => (
                  <div key={row.key} className="min-w-0">
                    <div className="mb-1 flex items-center justify-between gap-2">
                      <span className="truncate text-[10px] font-body text-secondary">{t(`quality.${row.key}`)}</span>
                      <span className="text-[10px] font-label tabular-nums text-muted">
                        {t('quality.countOfLoaded', { count: formatCount(row.count), total: formatCount(visibleSummary.loaded) })}
                      </span>
                    </div>
                    <div className="h-1.5 overflow-hidden rounded-full bg-nothing-gray-100">
                      <div
                        className={cn('h-full rounded-full', row.tone)}
                        style={{ width: `${row.pct}%` }}
                      />
                    </div>
                  </div>
                ))}
              </div>
            </div>

            <div className="mt-3 grid grid-cols-1 gap-3 xl:grid-cols-[minmax(0,1.25fr)_minmax(260px,0.75fr)]">
              <div className="rounded-md border border-nothing-gray-200 bg-white/70 px-3 py-2">
                <div className="mb-3 flex items-center justify-between gap-2">
                  <div className="min-w-0">
                    <p className="text-[10px] font-label uppercase tracking-wider text-muted">{t('lineage.title')}</p>
                    <p className="mt-0.5 truncate text-[10px] font-body text-muted">{t('lineage.scope')}</p>
                  </div>
                  <GitBranch className="h-4 w-4 shrink-0 text-claw-blue" aria-hidden="true" />
                </div>
                <div className="grid grid-cols-1 gap-2 sm:grid-cols-5">
                  {lineageNodes.map((node, index) => {
                    const Icon = node.icon
                    return (
                      <div key={node.key} className="relative min-w-0">
                        {index > 0 && (
                          <div className="pointer-events-none absolute -left-2 top-5 hidden h-px w-4 bg-nothing-gray-200 sm:block" />
                        )}
                        <div className={cn('h-full rounded-md border px-2.5 py-2', node.tone)}>
                          <div className="flex items-center justify-between gap-2">
                            <Icon className="h-4 w-4 shrink-0" aria-hidden="true" />
                            <span className="text-[10px] font-label tabular-nums">{node.pct}%</span>
                          </div>
                          <p className="mt-2 truncate text-[10px] font-label uppercase tracking-wider">{t(`lineage.${node.key}`)}</p>
                          <p className="mt-1 text-lg font-heading font-semibold tabular-nums">{formatCount(node.count)}</p>
                          <p className="mt-0.5 truncate text-[10px] font-body opacity-80">{node.detail}</p>
                        </div>
                      </div>
                    )
                  })}
                </div>
              </div>

              <div className="rounded-md border border-nothing-gray-200 bg-white/70 px-3 py-2">
                <div className="mb-2 flex items-center justify-between gap-2">
                  <div className="min-w-0">
                    <p className="text-[10px] font-label uppercase tracking-wider text-muted">{t('freshness.title')}</p>
                    <p className="mt-0.5 truncate text-[10px] font-body text-muted">{t('freshness.scope')}</p>
                  </div>
                  <Clock3 className="h-4 w-4 shrink-0 text-status-warning" aria-hidden="true" />
                </div>
                <div className="space-y-2">
                  {freshnessQueueRows.map(row => {
                    const Icon = row.icon
                    return (
                      <div key={row.key} className="min-w-0">
                        <div className="mb-1 flex items-center justify-between gap-2">
                          <span className="flex min-w-0 items-center gap-1.5 text-[10px] font-body text-secondary">
                            <Icon className="h-3 w-3 shrink-0 text-muted" aria-hidden="true" />
                            <span className="truncate">{t(`freshness.${row.key}`)}</span>
                          </span>
                          <span className="shrink-0 text-[10px] font-label tabular-nums text-muted">
                            {t('quality.countOfLoaded', { count: formatCount(row.count), total: formatCount(visibleSummary.loaded) })}
                          </span>
                        </div>
                        <div className="h-1.5 overflow-hidden rounded-full bg-nothing-gray-100">
                          <div className={cn('h-full rounded-full', row.tone)} style={{ width: `${row.pct}%` }} />
                        </div>
                        <p className="mt-0.5 truncate text-[10px] font-body text-muted">{t(`freshness.${row.key}Action`)}</p>
                      </div>
                    )
                  })}
                </div>
              </div>
            </div>

            <div className="mt-3 rounded-md border border-nothing-gray-200 bg-white/70 px-3 py-3">
              <div className="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
                <div className="min-w-0">
                  <div className="flex items-center gap-1.5 text-[10px] font-label uppercase tracking-wider text-muted">
                    <ListChecks className="h-3.5 w-3.5 text-claw-blue" aria-hidden="true" />
                    {t('workItems.title')}
                  </div>
                  <p className="mt-1 max-w-3xl text-xs font-body text-secondary">
                    {t('workItems.subtitle')}
                  </p>
                </div>
                <Link
                  href={governanceSummary.workItemsHref as never}
                  className="inline-flex shrink-0 items-center justify-center gap-1.5 rounded-md border border-claw-blue/20 bg-claw-blue/8 px-3 py-1.5 text-xs font-label text-claw-blue transition-colors hover:bg-claw-blue/12"
                >
                  {t('workItems.open')}
                  <ArrowRight className="h-3.5 w-3.5" aria-hidden="true" />
                </Link>
              </div>

              <div className="mt-3 grid grid-cols-2 gap-2 lg:grid-cols-5">
                {[
                  {
                    key: 'staleRatio',
                    value: governanceSummary.ratio === null ? '--' : `${governanceSummary.ratio}%`,
                    sub: governanceSummary.threshold === null ? t('workItems.loading') : t('workItems.threshold', { value: `${governanceSummary.threshold}%` }),
                    tone: 'text-status-warning',
                  },
                  {
                    key: 'staleTotal',
                    value: governanceSummary.staleTotal === null ? '--' : formatCount(governanceSummary.staleTotal),
                    sub: governanceSummary.entriesToThreshold === null ? t('workItems.loading') : t('workItems.entriesToThreshold', { count: formatCount(governanceSummary.entriesToThreshold) }),
                    tone: 'text-primary',
                  },
                  {
                    key: 'ownerPending',
                    value: formatCount(governanceSummary.ownerPending),
                    sub: t('workItems.ownerPendingSub'),
                    tone: 'text-claw-blue',
                  },
                  {
                    key: 'readyBlocked',
                    value: `${formatCount(governanceSummary.ownerReady)} / ${formatCount(governanceSummary.ownerBlocked)}`,
                    sub: t('workItems.readyBlockedSub'),
                    tone: 'text-purple-600',
                  },
                  {
                    key: 'completed',
                    value: formatCount(governanceSummary.completed),
                    sub: governanceSummary.burnDownStatus ? t('workItems.burndownStatus', { status: t(`workItems.status.${governanceSummary.burnDownStatus}` as never) }) : t('workItems.loading'),
                    tone: 'text-status-healthy',
                  },
                ].map(metric => (
                  <div key={metric.key} className="rounded-md border border-nothing-gray-200 bg-white/70 px-3 py-2">
                    <p className="text-[10px] font-label uppercase tracking-wider text-muted">{t(`workItems.metric.${metric.key}`)}</p>
                    <p className={cn('mt-1 text-xl font-heading font-semibold tabular-nums', metric.tone)}>{governanceLoading ? '--' : metric.value}</p>
                    <p className="mt-0.5 truncate text-[10px] font-body text-muted">{metric.sub}</p>
                  </div>
                ))}
              </div>

              <div className="mt-3 overflow-hidden rounded-md border border-nothing-gray-200 bg-white/60">
                <div className="flex flex-wrap items-center justify-between gap-2 border-b border-nothing-gray-100 px-3 py-2">
                  <div className="min-w-0">
                    <p className="text-[10px] font-label uppercase tracking-wider text-muted">{t('workItems.flow.title')}</p>
                    <p className="mt-0.5 truncate text-[10px] font-body text-muted">{t('workItems.flow.scope')}</p>
                  </div>
                  <span className="shrink-0 text-[10px] font-label text-status-healthy">
                    {governanceSummary.writesOnRead ? t('workItems.unexpectedWrites') : t('workItems.noWritesOnRead')}
                  </span>
                </div>
                <div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-6">
                  {governanceFlowNodes.map((node, index) => {
                    const Icon = node.icon
                    return (
                      <div key={node.key} className="relative min-w-0 border-t border-nothing-gray-100 px-3 py-2 first:border-t-0 sm:border-l sm:first:border-l-0 xl:border-t-0">
                        {index > 0 && (
                          <ArrowRight className="absolute -left-2 top-5 hidden h-3.5 w-3.5 rounded-full bg-white text-nothing-gray-300 xl:block" aria-hidden="true" />
                        )}
                        <div className="flex items-start justify-between gap-2">
                          <div className={cn('flex h-7 w-7 shrink-0 items-center justify-center rounded-md border', node.tone)}>
                            <Icon className="h-3.5 w-3.5" aria-hidden="true" />
                          </div>
                          <span className="rounded-full border border-nothing-gray-200 bg-white px-2 py-0.5 text-[10px] font-label uppercase tracking-wider text-muted">
                            {t(`workItems.flow.state.${node.state}` as never)}
                          </span>
                        </div>
                        <p className="mt-2 truncate text-[10px] font-label uppercase tracking-wider text-muted">
                          {t(`workItems.flow.node.${node.key}` as never)}
                        </p>
                        <p className="mt-1 text-lg font-heading font-semibold tabular-nums text-primary">
                          {governanceLoading ? '--' : node.value}
                        </p>
                        <p className="mt-0.5 line-clamp-2 text-[10px] font-body leading-4 text-muted">
                          {node.detail}
                        </p>
                      </div>
                    )
                  })}
                </div>
              </div>

              <div className="mt-3 grid grid-cols-1 gap-2 lg:grid-cols-[minmax(0,1fr)_220px]">
                <div className="rounded-md border border-nothing-gray-200 bg-white/60 px-3 py-2">
                  <div className="flex items-center justify-between gap-2">
                    <p className="truncate text-[10px] font-label uppercase tracking-wider text-muted">{t('workItems.topCandidate')}</p>
                    <span className="shrink-0 text-[10px] font-body text-muted">{t('workItems.readOnly')}</span>
                  </div>
                  {governanceSummary.topCandidate ? (
                    <div className="mt-2 min-w-0">
                      <div className="flex flex-wrap items-center gap-1.5">
                        <span className="rounded-full border border-status-warning/20 bg-status-warning/10 px-2 py-0.5 text-[10px] font-label text-status-warning">
                          {governanceSummary.topCandidate.priority_tier}
                        </span>
                        <span className="rounded-full border border-nothing-gray-200 bg-white px-2 py-0.5 text-[10px] font-label text-muted">
                          {t('workItems.staleDays', { days: governanceSummary.topCandidate.stale_days })}
                        </span>
                        <span className="rounded-full border border-claw-blue/20 bg-claw-blue/8 px-2 py-0.5 text-[10px] font-label text-claw-blue">
                          {t('workItems.score', { score: governanceSummary.topCandidate.priority_score })}
                        </span>
                      </div>
                      <p className="mt-2 truncate text-xs font-body font-semibold text-primary">
                        {governanceSummary.topCandidate.title}
                      </p>
                      <p className="mt-1 truncate text-[10px] font-body text-muted">
                        {t('workItems.topCandidateRefs', {
                          incident: governanceSummary.topCandidate.related_incident_id ?? '--',
                          playbook: governanceSummary.topCandidate.related_playbook_id ?? '--',
                        })}
                      </p>
                    </div>
                  ) : (
                    <p className="mt-2 text-xs font-body text-muted">{t('workItems.noCandidate')}</p>
                  )}
                </div>
                <div className="rounded-md border border-nothing-gray-200 bg-white/60 px-3 py-2">
                  <div className="flex items-center gap-1.5 text-[10px] font-label uppercase tracking-wider text-muted">
                    <Users className="h-3.5 w-3.5" aria-hidden="true" />
                    {t('workItems.ownerSplit')}
                  </div>
                  <div className="mt-2 space-y-1 text-[10px] font-body text-secondary">
                    <p>{t('workItems.hermes')}</p>
                    <p>{t('workItems.openclaw')}</p>
                    <p>{t('workItems.owner')}</p>
                  </div>
                  {governanceSummary.writesOnRead ? (
                    <p className="mt-2 text-[10px] font-label text-status-critical">{t('workItems.unexpectedWrites')}</p>
                  ) : (
                    <p className="mt-2 text-[10px] font-label text-status-healthy">{t('workItems.noWritesOnRead')}</p>
                  )}
                </div>
              </div>
            </div>
          </section>

          {/* 結果列表 */}
          <div className="flex-1 min-h-0 overflow-y-auto">
            {(semanticMode ? semanticLoading : loading) ? (
              <div className="flex items-center justify-center h-40">
                <span className="text-sm text-muted font-body">{tCommon('loading')}</span>
              </div>
            ) : semanticMode && !searchQuery ? (
              <div className="flex flex-col items-center justify-center h-60 gap-2">
                <Sparkles className="w-10 h-10 text-claw-blue/30" />
                <p className="text-sm text-secondary font-body">{t('semanticSearchHint')}</p>
              </div>
            ) : entryFetchError ? (
              <div className="flex flex-col items-center justify-center h-60 gap-2 px-4 text-center">
                <TriangleAlert className="w-10 h-10 text-status-warning/60" />
                <p className="text-sm font-body font-medium text-primary">{t('dataChain.errorTitle')}</p>
                <p className="max-w-lg text-xs font-body leading-5 text-muted">
                  {t('dataChain.errorDescription', { reason: entryFetchError })}
                </p>
                <button
                  onClick={() => {
                    if (semanticMode && searchQuery) {
                      fetchSemanticResults(searchQuery)
                    } else {
                      fetchEntries()
                    }
                  }}
                  className="mt-1 inline-flex items-center gap-1.5 rounded-md border border-nothing-gray-200 bg-white px-3 py-1.5 text-xs font-label text-secondary transition-colors hover:border-claw-blue/30 hover:text-claw-blue"
                >
                  <RotateCw className="h-3.5 w-3.5" aria-hidden="true" />
                  {t('dataChain.retry')}
                </button>
              </div>
            ) : semanticMode && semanticResults.length === 0 ? (
              <div className="flex flex-col items-center justify-center h-60 gap-2">
                <BookOpen className="w-10 h-10 text-nothing-gray-300" />
                <p className="text-sm text-secondary font-body">{t('empty')}</p>
              </div>
            ) : !semanticMode && entries.length === 0 ? (
              <div className="flex flex-col items-center justify-center h-60 gap-2">
                <BookOpen className="w-10 h-10 text-nothing-gray-300" />
                <p className="text-sm text-secondary font-body">{t('empty')}</p>
                <p className="text-xs text-muted font-body">{t('emptyDescription')}</p>
              </div>
            ) : (
              <div className="divide-y divide-nothing-gray-100">
                {displayedEntries.map(entry => {
                  const signalTags = getSignalTags(entry)
                  const visibleSignals = signalTags.slice(0, 3)
                  const hiddenSignalCount = Math.max(0, signalTags.length - visibleSignals.length)

                  return (
                  <button
                    key={entry.id}
                    onClick={() => setSelectedEntry(entry)}
                    className={cn(
                      'w-full text-left px-4 py-3 hover:bg-nothing-gray-50/50 transition-colors',
                      selectedEntry?.id === entry.id && 'bg-claw-blue/4'
                    )}
                  >
                    <div className="flex items-start gap-3">
                      <div className="flex-1 min-w-0">
                        {/* Title row */}
                        <div className="flex items-center gap-2 mb-1">
                          <span className={cn(
                            'text-[10px] font-body px-1.5 py-0.5 rounded border',
                            TYPE_COLORS[entry.entry_type] ?? 'bg-nothing-gray-100 text-nothing-gray-500'
                          )}>
                            {t(`type.${entry.entry_type}`)}
                          </span>
                          <span className={cn(
                            'text-[10px] font-body px-1.5 py-0.5 rounded',
                            STATUS_COLORS[entry.status] ?? ''
                          )}>
                            {t(`status.${entry.status}`)}
                          </span>
                          <h3 className="text-sm font-body font-medium text-primary truncate">
                            {entry.title}
                          </h3>
                        </div>

                        {/* Meta row */}
                        <div className="flex items-center gap-2 text-xs text-muted font-body">
                          <span>{formatCategoryLabel(entry.category)}</span>
                          <span>·</span>
                          <span>{t(`source.${entry.source}`)}</span>
                          <span>·</span>
                          <span>{formatDate(entry.created_at)}</span>
                        </div>

                        {/* Evidence signals */}
                        {visibleSignals.length > 0 && (
                          <div className="mt-2 flex min-w-0 flex-wrap items-center gap-1.5">
                            <span className="flex items-center gap-1 text-[10px] font-label uppercase tracking-wider text-muted">
                              <Tag className="h-3 w-3" aria-hidden="true" />
                              {t('signals.label')}
                            </span>
                            {visibleSignals.map(tag => (
                              <span
                                key={tag}
                                className={cn(
                                  'max-w-[160px] truncate rounded-full border px-1.5 py-0.5 text-[10px] font-body',
                                  TAG_TONES[tag] ?? 'border-nothing-gray-200 bg-nothing-gray-50 text-muted',
                                )}
                                title={tag}
                              >
                                {formatSignalTag(tag)}
                              </span>
                            ))}
                            {hiddenSignalCount > 0 && (
                              <span className="rounded-full border border-nothing-gray-200 bg-white px-1.5 py-0.5 text-[10px] font-label text-muted">
                                {t('signals.more', { count: hiddenSignalCount })}
                              </span>
                            )}
                          </div>
                        )}
                      </div>

                      {/* Right: similarity score (semantic) or view count + chevron */}
                      <div className="flex items-center gap-2 flex-shrink-0">
                        {semanticMode && 'score' in entry ? (
                          <div className="flex items-center gap-1 text-xs text-claw-blue font-label">
                            <Sparkles className="w-3 h-3" />
                            <span>{Math.round((entry as { score: number }).score * 100)}%</span>
                          </div>
                        ) : (
                          <div className="flex items-center gap-1 text-xs text-muted">
                            <Eye className="w-3 h-3" />
                            <span>{entry.view_count}</span>
                          </div>
                        )}
                        <ChevronRight className="w-4 h-4 text-nothing-gray-300" />
                      </div>
                    </div>
                  </button>
                  )
                })}
              </div>
            )}
          </div>
        </main>

        {/* 右側詳情 Panel */}
        {selectedEntry && (
          <aside className="w-full flex-shrink-0 border-t border-nothing-gray-200/50 bg-white/50 overflow-y-auto lg:w-96 lg:border-l lg:border-t-0">
            <div className="p-4">
              {/* Header */}
              <div className="flex items-start justify-between mb-3">
                <div>
                  <div className="flex items-center gap-2 mb-1">
                    <span className={cn(
                      'text-[10px] font-body px-1.5 py-0.5 rounded border',
                      TYPE_COLORS[selectedEntry.entry_type]
                    )}>
                      {t(`type.${selectedEntry.entry_type}`)}
                    </span>
                    <span className={cn(
                      'text-[10px] font-body px-1.5 py-0.5 rounded',
                      STATUS_COLORS[selectedEntry.status]
                    )}>
                      {t(`status.${selectedEntry.status}`)}
                    </span>
                  </div>
                  <h2 className="text-base font-heading font-semibold text-primary">
                    {selectedEntry.title}
                  </h2>
                </div>
                <button
                  onClick={() => setSelectedEntry(null)}
                  className="text-muted hover:text-primary text-sm"
                >
                  ×
                </button>
              </div>

              {/* Meta */}
              <div className="flex items-center gap-3 text-xs text-muted font-body mb-4 pb-3 border-b border-nothing-gray-100">
                <span>{formatCategoryLabel(selectedEntry.category)}</span>
                <span>·</span>
                <span>{t(`source.${selectedEntry.source}`)}</span>
                <span>·</span>
                <span><Eye className="w-3 h-3 inline" /> {selectedEntry.view_count} {t('viewCount')}</span>
                <span>·</span>
                <span>{formatDate(selectedEntry.updated_at)}</span>
              </div>

              {/* Content — react-markdown (KB-D 2026-04-03 ogt) */}
              <div className="prose prose-sm prose-neutral max-w-none font-body text-sm text-secondary mb-4
                [&_h1]:text-base [&_h1]:font-semibold [&_h1]:font-heading [&_h1]:text-primary [&_h1]:mt-4 [&_h1]:mb-2
                [&_h2]:text-xs [&_h2]:font-semibold [&_h2]:font-heading [&_h2]:text-primary [&_h2]:uppercase [&_h2]:tracking-wider [&_h2]:mt-4 [&_h2]:mb-1.5
                [&_li]:marker:text-claw-blue [&_code]:bg-nothing-gray-100 [&_code]:px-1 [&_code]:rounded [&_code]:text-[11px] [&_code]:font-mono
                [&_p]:leading-relaxed [&_ul]:pl-4 [&_ol]:pl-4">
                <ReactMarkdown remarkPlugins={[remarkGfm]}>
                  {selectedEntry.content}
                </ReactMarkdown>
              </div>

              {/* Evidence signals */}
              {getSignalTags(selectedEntry).length > 0 && (
                <div className="mb-4 rounded-md border border-nothing-gray-200 bg-white/70 p-3">
                  <div className="mb-2 flex items-center gap-1.5 text-[10px] font-label uppercase tracking-wider text-muted">
                    <Tag className="h-3 w-3" aria-hidden="true" />
                    {t('signals.detailTitle')}
                  </div>
                  <div className="flex flex-wrap gap-1.5">
                    {getSignalTags(selectedEntry).map(tag => (
                      <span
                        key={tag}
                        className={cn(
                          'max-w-full truncate rounded-full border px-2 py-0.5 text-[10px] font-body',
                          TAG_TONES[tag] ?? 'border-nothing-gray-200 bg-nothing-gray-50 text-muted',
                        )}
                        title={tag}
                      >
                        {formatSignalTag(tag)}
                      </span>
                    ))}
                  </div>
                </div>
              )}

              <div className="mb-4 rounded-md border border-nothing-gray-200 bg-white/70 p-3">
                <div className="mb-2 flex items-center gap-1.5 text-[10px] font-label uppercase tracking-wider text-muted">
                  <GitBranch className="h-3 w-3" aria-hidden="true" />
                  {t('entryLineage.title')}
                </div>
                <div className="space-y-1.5">
                  {[
                    {
                      key: 'source',
                      icon: selectedEntry.source === 'ai_extracted' ? Bot : FileText,
                      ok: true,
                      value: t(`source.${selectedEntry.source}`),
                    },
                    {
                      key: 'incident',
                      icon: TriangleAlert,
                      ok: Boolean(selectedEntry.related_incident_id),
                      value: selectedEntry.related_incident_id ?? t('entryLineage.missing'),
                    },
                    {
                      key: 'playbook',
                      icon: ClipboardList,
                      ok: Boolean(selectedEntry.related_playbook_id),
                      value: selectedEntry.related_playbook_id ?? t('entryLineage.missing'),
                    },
                    {
                      key: 'review',
                      icon: selectedEntry.status === 'approved' ? CheckCircle2 : Clock3,
                      ok: selectedEntry.status === 'approved',
                      value: t(`status.${selectedEntry.status}`),
                    },
                  ].map(item => {
                    const Icon = item.icon
                    return (
                      <div key={item.key} className="grid grid-cols-[18px_86px_minmax(0,1fr)] items-center gap-2">
                        <span className={cn(
                          'flex h-4 w-4 items-center justify-center rounded-full border',
                          item.ok ? 'border-status-healthy/30 bg-status-healthy/10 text-status-healthy' : 'border-status-warning/30 bg-status-warning/10 text-status-warning',
                        )}>
                          <Icon className="h-2.5 w-2.5" aria-hidden="true" />
                        </span>
                        <span className="truncate text-[10px] font-label uppercase tracking-wider text-muted">{t(`entryLineage.${item.key}`)}</span>
                        <span className="truncate text-[10px] font-body text-secondary" title={item.value}>{item.value}</span>
                      </div>
                    )
                  })}
                </div>
              </div>

              {/* Raw technical tags */}
              {selectedEntry.tags.length > 0 && (
                <details className="mb-4 rounded-md border border-nothing-gray-200 bg-white/50 p-3">
                  <summary className="cursor-pointer text-[10px] font-label uppercase tracking-wider text-muted">
                    {t('signals.rawTitle')}
                  </summary>
                  <div className="mt-2 flex flex-wrap gap-1">
                    {selectedEntry.tags.map(tag => (
                      <span key={tag} className="rounded-full bg-nothing-gray-100 px-2 py-0.5 font-mono text-[10px] text-muted">
                        {tag}
                      </span>
                    ))}
                  </div>
                </details>
              )}

              {/* Related links */}
              {selectedEntry.related_playbook_id && (
                <div className="flex items-center gap-1.5 text-xs text-claw-blue font-body mb-2">
                  <ClipboardList className="h-3.5 w-3.5" aria-hidden="true" />
                  <span>{t('relatedPlaybook')}: {selectedEntry.related_playbook_id}</span>
                </div>
              )}
              {selectedEntry.related_incident_id && (
                <div className="flex items-center gap-1.5 text-xs text-status-warning font-body mb-4">
                  <TriangleAlert className="h-3.5 w-3.5" aria-hidden="true" />
                  <span>{t('relatedIncident')}: {selectedEntry.related_incident_id}</span>
                </div>
              )}

              {/* Actions (KB-D wired 2026-04-03 ogt) */}
              {selectedEntry.status !== 'approved' && selectedEntry.status !== 'archived' && (
                <div className="flex gap-2 pt-3 border-t border-nothing-gray-100">
                  <button
                    onClick={handleApprove}
                    disabled={actionLoading}
                    className="flex-1 text-xs font-body font-medium py-1.5 rounded bg-status-healthy/10 text-status-healthy hover:bg-status-healthy/20 transition-colors disabled:opacity-50"
                  >
                    {actionLoading ? t('approving') : t('approve')}
                  </button>
                  <button
                    onClick={handleArchive}
                    disabled={actionLoading}
                    className="flex-1 text-xs font-body font-medium py-1.5 rounded bg-nothing-gray-100 text-muted hover:bg-nothing-gray-200 transition-colors disabled:opacity-50"
                  >
                    {actionLoading ? t('archiving') : t('archive')}
                  </button>
                </div>
              )}
            </div>
          </aside>
        )}
      </div>
  )

  return (
    <AppLayout locale={params.locale}>
      {content}
    </AppLayout>
  )
}
