import test from "node:test"
import assert from "node:assert/strict"
import { readFileSync } from "node:fs"

const appSource = readFileSync(new URL("../src/App.tsx", import.meta.url), "utf8")
const appSidebarSource = readFileSync(new URL("../src/features/shell/AppSidebar.tsx", import.meta.url), "utf8")
const sidebarSource = readFileSync(new URL("../src/features/shell/Sidebar.tsx", import.meta.url), "utf8")
const iconSource = readFileSync(new URL("../src/components/icons/AppIcons.tsx", import.meta.url), "utf8")
const knowledgeStylesSource = readFileSync(new URL("../src/styles/knowledge.css", import.meta.url), "utf8")
const shellStylesSource = readFileSync(new URL("../src/styles/shell.css", import.meta.url), "utf8")
const chatStylesSource = readFileSync(new URL("../src/styles/chat.css", import.meta.url), "utf8")
const themeStylesSource = readFileSync(new URL("../src/styles/theme-openclaw.css", import.meta.url), "utf8")

function cssBlock(source: string, selector: string): string {
  const start = source.indexOf(`${selector} {`)
  assert.notEqual(start, -1, `Missing CSS selector: ${selector}`)
  const end = source.indexOf("\n}", start)
  assert.notEqual(end, -1, `Missing CSS block end: ${selector}`)
  return source.slice(start, end + 2)
}

test("app shell wires a collapsible sidebar state into the sidebar components", () => {
  assert.match(appSource, /const \[isSidebarCollapsed, setIsSidebarCollapsed\] = useState\(false\)/)
  assert.match(appSource, /sidebar-collapsed/)
  assert.match(appSource, /isCollapsed=\{isSidebarCollapsed\}/)
  assert.match(appSource, /onToggleCollapsed=\{\(\) => setIsSidebarCollapsed\(\(current\) => !current\)\}/)
  assert.match(appSidebarSource, /\bisCollapsed:\s*boolean/)
  assert.match(appSidebarSource, /\bonToggleCollapsed\(\):\s*void/)
  assert.match(appSidebarSource, /<Sidebar[\s\S]*?isCollapsed=\{isCollapsed\}[\s\S]*?onToggleCollapsed=\{onToggleCollapsed\}/)
})

test("sidebar nav omits plugins and keeps the new conversation action visible when expanded", () => {
  const navItems = appSidebarSource.match(/\{\s*id:\s*"chat"[\s\S]*?\]\.(filter[\s\S]*?\)\.)?map/)?.[0] ?? ""
  assert.match(navItems, /id:\s*"chat"/)
  assert.match(navItems, /id:\s*"tasks"/)
  assert.match(navItems, /id:\s*"knowledge"/)
  assert.match(navItems, /id:\s*"settings"/)
  assert.doesNotMatch(navItems, /id:\s*"plugins"/)
  assert.match(appSidebarSource, /\{!showBindEntry \? <div className="conversation-sidebar-action">\{sidebarNewSessionAction\}<\/div> : null\}/)
})

test("sidebar renders the brand row and hides non-icon content in collapsed mode", () => {
  assert.match(sidebarSource, /brandIcon/)
  assert.match(sidebarSource, /sidebar-brand/)
  assert.match(sidebarSource, /sidebar-collapse-button/)
  assert.match(shellStylesSource, /\.shell\.sidebar-collapsed\s*\{[\s\S]*?grid-template-columns:\s*64px minmax\(0, 1fr\);/m)
  assert.match(themeStylesSource, /\.shell\.openclaw-theme\.sidebar-collapsed \.sidebar-header-content,\s*\n\.shell\.openclaw-theme\.sidebar-collapsed \.sidebar-brand-logo/)
  assert.match(themeStylesSource, /\.shell\.openclaw-theme\.sidebar-collapsed \.sidebar-brand\s*\{[\s\S]*?width:\s*44px;[\s\S]*?justify-content:\s*center;/m)
  assert.match(themeStylesSource, /\.shell\.openclaw-theme\.sidebar-collapsed \.sidebar-collapse-button\s*\{[\s\S]*?width:\s*38px;[\s\S]*?height:\s*38px;/m)
  assert.match(themeStylesSource, /\.shell\.openclaw-theme\.sidebar-collapsed \.sidebar-top\s*\{[\s\S]*?align-content:\s*start;/m)
  assert.match(themeStylesSource, /\.shell\.openclaw-theme\.sidebar-collapsed \.nav-item-label/)
  assert.match(themeStylesSource, /\.shell\.openclaw-theme\.sidebar-collapsed \.sidebar-bottom/)
})

test("sidebar keeps brand, navigation, and lower content in fixed grid rows", () => {
  assert.match(cssBlock(shellStylesSource, ".sidebar"), /grid-template-rows:\s*auto auto minmax\(0, 1fr\);/)
  assert.match(cssBlock(chatStylesSource, ".conversation-sidebar-layout"), /grid-template-rows:\s*auto auto minmax\(0, 1fr\);/)
  assert.match(cssBlock(themeStylesSource, ".shell.openclaw-theme .conversation-sidebar-layout"), /grid-template-rows:\s*auto auto minmax\(0, 1fr\);/)
  assert.match(cssBlock(themeStylesSource, ".shell.openclaw-theme .sidebar-bottom"), /grid-template-rows:\s*minmax\(0, 56fr\) minmax\(150px, 44fr\);[\s\S]*?overflow:\s*hidden;/)
})

test("new conversation action matches the sidebar navigation control size", () => {
  assert.match(cssBlock(themeStylesSource, ".shell.openclaw-theme .nav-item"), /min-height:\s*42px;[\s\S]*?border-radius:\s*14px;/)
  assert.match(cssBlock(themeStylesSource, ".shell.openclaw-theme .sidebar-new-session.conversation-new-session"), /width:\s*100%;[\s\S]*?min-height:\s*44px;[\s\S]*?justify-content:\s*center;[\s\S]*?border-radius:\s*14px;/)
})

test("knowledge navigation uses a book icon instead of the old document icon", () => {
  const knowledgeIconCase = iconSource.match(/case "knowledge":[\s\S]*?case "settings":/)?.[0] ?? ""
  assert.match(knowledgeIconCase, /M5\.25 5\.7/)
  assert.match(knowledgeIconCase, /M12 6\.65/)
  assert.doesNotMatch(knowledgeIconCase, /M6 4\.5A2\.5 2\.5/)
})

test("knowledge page uses the OpenClaw blue-white theme instead of purple glass styling", () => {
  assert.doesNotMatch(knowledgeStylesSource, /#7C3AED|#A78BFA|backdrop-filter|rgba\(255,\s*255,\s*255,\s*0\.0[58]\)/)
  assert.match(knowledgeStylesSource, /\.upload-card\s*\{[\s\S]*?background:\s*#ffffff;/m)
  assert.match(knowledgeStylesSource, /\.upload-card\s*\{[\s\S]*?border:\s*1px dashed rgba\(59, 130, 246, 0\.34\);/m)
  assert.match(knowledgeStylesSource, /\.knowledge-title\s*\{[\s\S]*?color:\s*#12355f;/m)
  assert.match(knowledgeStylesSource, /\.upload-button\s*\{[\s\S]*?background:\s*linear-gradient\(135deg, #2563eb, #0f6eea\);/m)
})
