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 sidebarSource = readFileSync(new URL("../src/features/shell/AppSidebar.tsx", import.meta.url), "utf8")
const mockApiSource = readFileSync(new URL("../src/lib/mock-desktop-api.ts", import.meta.url), "utf8")
const desktopApiSource = readFileSync(new URL("../src/lib/desktop-api.ts", import.meta.url), "utf8")
const automationStyles = readFileSync(new URL("../src/styles/automation.css", import.meta.url), "utf8")
const automationViewSource = readFileSync(new URL("../src/features/automation/AutomationTasksView.tsx", import.meta.url), "utf8")
const appIconsSource = readFileSync(new URL("../src/components/icons/AppIcons.tsx", import.meta.url), "utf8")

test("automation tasks view is available from the sidebar above settings", () => {
  assert.match(appSource, /AutomationTasksView/)
  assert.match(appSource, /viewMode === "automation"/)
  assert.match(sidebarSource, /id: "automation" as const, label: "自动化任务"/)
  assert.match(sidebarSource, /id: "settings" as const/)
  assert.ok(sidebarSource.indexOf('id: "automation"') < sidebarSource.indexOf('id: "settings"'))
})

test("mock desktop API exposes automation task methods for UI development", () => {
  assert.match(desktopApiSource, /mockDesktopApi/)
  assert.match(mockApiSource, /automationTasks:\s*\{/)
  assert.match(mockApiSource, /listRuns/)
  assert.match(mockApiSource, /runNow/)
})

test("automation task destructive and utility actions are explicit", () => {
  assert.match(automationViewSource, /确认删除自动化任务/)
  assert.match(automationViewSource, />刷新</)
  assert.match(automationViewSource, />新建任务</)
  assert.match(automationViewSource, />编辑</)
  assert.match(automationViewSource, />立即执行</)
  assert.match(automationViewSource, />删除</)
  assert.doesNotMatch(automationViewSource, /automation-button-toggle/)
  assert.doesNotMatch(automationViewSource, /aria-label="删除自动化任务" onClick=\{\(\) => void deleteTask/)
})

test("automation task view exposes lifecycle text without quick enable and disable controls", () => {
  assert.doesNotMatch(automationViewSource, /toggleTaskEnabled/)
  assert.doesNotMatch(automationViewSource, /automationTasks\.update\(task\.id,\s*\{\s*enabled: !task\.enabled\s*\}\)/s)
  assert.doesNotMatch(automationViewSource, /automation-task-row-actions/)
  assert.doesNotMatch(automationViewSource, /automation-row-toggle-button/)
  assert.match(automationViewSource, /已完成，已停用/)
  assert.match(automationViewSource, /已错过，已停用/)
  assert.match(automationViewSource, /已停用/)
})

test("automation task list truncates title and prompt source text", () => {
  assert.match(automationViewSource, /function truncateText\(text: string, maxLength: number\)/)
  assert.match(automationViewSource, /return text\.length > maxLength \? `\$\{text\.slice\(0, maxLength\)\}\.\.\.` : text/)
  assert.match(automationViewSource, /<strong>\{truncateText\(task\.title,\s*20\)\}<\/strong>/)
  assert.match(automationViewSource, /<small>\{truncateText\(task\.prompt,\s*20\)\}<\/small>/)
  assert.match(automationViewSource, /<p className="automation-detail-prompt">\{truncateText\(selectedTask\.prompt,\s*30\)\}<\/p>/)
  assert.doesNotMatch(automationViewSource, /下次执行：\{getTaskNextRunLabel\(task\)\}/)
  assert.doesNotMatch(automationViewSource, /<StatusChip tone=\{getTaskLifecycleTone\(task\)\}>\{getTaskLifecycleLabel\(task\)\}<\/StatusChip>/)
})

test("automation task edit can clear the selected expert", () => {
  assert.match(automationViewSource, /const expertId = form\.expertId \|\| \(form\.id \? null : undefined\)/)
  assert.match(automationViewSource, /const expertName = form\.expertId \? selectedExpert\?\.label : \(form\.id \? null : undefined\)/)
})

test("automation task default expert is labeled as the general assistant", () => {
  assert.match(automationViewSource, /expertId: "",/)
  assert.match(automationViewSource, /\{ id: "", label: "通用助手" \}/)
  assert.match(automationViewSource, /<span className="automation-kicker">\{selectedTask\.expertName \|\| "通用助手"\}<\/span>/)
  assert.doesNotMatch(automationViewSource, /千匠问天/)
  assert.doesNotMatch(mockApiSource, /expertName:\s*"千匠问天"/)
})

test("automation run replies use shared markdown rendering", () => {
  assert.match(automationViewSource, /renderChatMessageContent/)
  assert.match(automationViewSource, /markdown-body automation-run-markdown/)
  assert.match(automationViewSource, /className="automation-run-meta"/)
  assert.match(automationViewSource, /run\.replyText/)
  assert.doesNotMatch(automationViewSource, /\{run\.replyText\}<\/p>/)
  assert.match(automationStyles, /\.automation-run-list\s*\{[^}]*overflow:\s*auto/is)
  assert.doesNotMatch(automationStyles, /\.automation-run-item\s*>\s*div\s*\{/)
  assert.match(automationStyles, /\.automation-detail-pane\s*\{[^}]*grid-template-rows:\s*auto auto minmax\(0,\s*1fr\)/is)
  assert.match(automationStyles, /\.automation-runs\s*\{[^}]*grid-template-rows:\s*auto minmax\(0,\s*1fr\)/is)
  assert.match(automationStyles, /\.automation-page\s*\{[^}]*display:\s*grid[^}]*grid-template-rows:\s*auto minmax\(0,\s*1fr\)/is)
  assert.match(automationStyles, /\.automation-run-meta\s*\{[^}]*display:\s*flex/is)
})

test("run-now action uses WeChat green accent", () => {
  assert.match(automationStyles, /\.automation-button-accent\s*\{[^}]*#07C160/is)
})

test("automation task form actions use scoped DingTalk-style button colors", () => {
  assert.match(automationViewSource, /<Button[^]*className="automation-form-button-secondary"[^]*>取消<\/Button>/)
  assert.match(automationViewSource, /<Button[^]*className="automation-form-button-primary"[^]*>\{saving \? "保存中" : "保存"\}<\/Button>/)
  assert.match(automationStyles, /\.automation-form-button-secondary\s*\{[^}]*background:\s*#ffffff/is)
  assert.match(automationStyles, /\.automation-form-button-primary\s*\{[^}]*background:\s*#1677FF/is)
  assert.match(automationStyles, /\.automation-form-button-primary:hover:not\(:disabled\)[^{]*\{[^}]*background:\s*#0F63D8/is)
})

test("automation task action buttons use svg icons", () => {
  assert.match(appIconsSource, /export function PlusIcon\(\)/)
  assert.match(appIconsSource, /export function EditIcon\(\)/)
  assert.match(automationViewSource, /import \{ EditIcon, PlusIcon, RefreshIcon, TrashIcon \} from/)
  assert.match(automationViewSource, /<PlusIcon \/>\s*<span>新建任务<\/span>/)
  assert.match(automationViewSource, /<EditIcon \/>\s*<span>编辑<\/span>/)
})

test("automation task header omits the runtime scheduling helper copy", () => {
  assert.doesNotMatch(automationViewSource, /应用运行时执行，错过的计划会保留为记录。/)
})

test("automation task list scrolls inside the left pane", () => {
  assert.match(automationStyles, /\.automation-list-pane\s*\{[^}]*grid-template-rows:\s*auto minmax\(0,\s*1fr\)/is)
  assert.match(automationStyles, /\.automation-list-scroll\s*\{[^}]*min-height:\s*0[^}]*overflow:\s*hidden/is)
  assert.match(automationStyles, /\.automation-list-scroll \.scroll-area-content\s*\{[^}]*height:\s*100%[^}]*min-height:\s*0[^}]*overflow-y:\s*auto/is)
})

test("automation task rows use a fixed compact height", () => {
  assert.match(automationStyles, /\.automation-task-row\s*\{[^}]*height:\s*84px/is)
  assert.match(automationStyles, /\.automation-task-row\s*\{[^}]*overflow:\s*hidden/is)
  assert.match(automationStyles, /\.automation-task-select\s*\{[^}]*gap:\s*5px/is)
  assert.match(automationStyles, /\.automation-task-select strong\s*\{[^}]*text-overflow:\s*ellipsis[^}]*white-space:\s*nowrap/is)
  assert.match(automationStyles, /\.automation-task-select small\s*\{[^}]*text-overflow:\s*ellipsis[^}]*white-space:\s*nowrap/is)
})

test("run-now button derives selected task running state from optimistic state or active run records", () => {
  assert.match(automationViewSource, /const activeRunStatuses = new Set<AutomationTaskRun\["status"\]>\(\["queued", "running"\]\)/)
  assert.match(automationViewSource, /const \[optimisticRunningTaskIds,\s*setOptimisticRunningTaskIds\] = useState<Set<string>>\(\(\) => new Set\(\)\)/)
  assert.doesNotMatch(automationViewSource, /const \[runningTaskId,\s*setRunningTaskId\]/)
  assert.doesNotMatch(automationViewSource, /setRunningTaskId/)
  assert.match(automationViewSource, /setOptimisticRunningTaskIds\(\(current\) => new Set\(current\)\.add\(taskId\)\)/)
  assert.match(automationViewSource, /next\.delete\(taskId\)/)
  assert.match(automationViewSource, /const hasActiveSelectedRun = runs\.some\(\(run\) => run\.taskId === selectedTask\?\.id && activeRunStatuses\.has\(run\.status\)\)/)
  assert.match(automationViewSource, /const hasOptimisticSelectedRun = optimisticRunningTaskIds\.has\(selectedTask\?\.id \?\? ""\)/)
  assert.match(automationViewSource, /const isRunningSelectedTask = hasActiveSelectedRun \|\| hasOptimisticSelectedRun/)
  assert.match(automationViewSource, /<span className="automation-button-spinner" aria-hidden="true" \/>\s*<span>执行中<\/span>/)
  assert.match(automationViewSource, /disabled=\{saving \|\| isRunningSelectedTask\}/)
})

test("automation detail selection follows the filtered task list", () => {
  assert.match(automationViewSource, /const selectedTask = useMemo\(\s*\(\) => filteredTasks\.find\(\(task\) => task\.id === selectedTaskId\) \?\? filteredTasks\[0\]/s)
  assert.match(automationViewSource, /\[selectedTaskId, filteredTasks\]/)
  assert.doesNotMatch(automationViewSource, /tasks\.find\(\(task\) => task\.id === selectedTaskId\) \?\? tasks\[0\]/)
})

test("automation task runs poll while a selected task has optimistic state or active run records", () => {
  assert.match(automationViewSource, /const loadSelectedTaskRuns = useCallback/)
  assert.match(automationViewSource, /const selectedTaskIdRef = useRef\(""\)/)
  assert.match(automationViewSource, /const taskId = selectedTask\?\.id/)
  assert.match(automationViewSource, /desktopApi\.automationTasks\.listRuns\(taskId\)/)
  assert.match(automationViewSource, /if \(selectedTaskIdRef\.current === taskId\) \{\s*setRuns\(nextRuns\)\s*\}/s)
  assert.match(automationViewSource, /void loadSelectedTaskRuns\(\)/)
  assert.match(automationViewSource, /const hasActiveSelectedRun = runs\.some\(\(run\) => run\.taskId === selectedTask\?\.id && activeRunStatuses\.has\(run\.status\)\)/)
  assert.match(automationViewSource, /const shouldPollSelectedRuns = hasActiveSelectedRun \|\| hasOptimisticSelectedRun/)
  assert.match(automationViewSource, /if \(!shouldPollSelectedRuns\) \{/)
  assert.match(automationViewSource, /window\.setInterval\(\(\) => \{[^}]*void loadSelectedTaskRuns\(\)[^}]*void loadTasks\(\)[^}]*\},\s*2000\)/s)
  assert.match(automationViewSource, /window\.clearInterval\(pollTimer\)/)
})

test("run-now only prepends runs while the same task remains selected", () => {
  assert.match(automationViewSource, /const runTaskNow = async \(taskId: string\) => \{/)
  assert.match(automationViewSource, /const run = await desktopApi\.automationTasks\.runNow\(taskId\)/)
  assert.match(automationViewSource, /if \(selectedTaskIdRef\.current === taskId\) \{\s*setRuns\(\(current\) => \[run, \.\.\.current\.filter\(\(item\) => item\.id !== run\.id\)\]\)\s*\}/s)
  assert.match(automationViewSource, /await loadTasks\(\)/)
})

test("run-now spinner has accessible reduced-motion styling", () => {
  assert.match(automationStyles, /\.automation-button-spinner\s*\{[^}]*border-radius:\s*999px[^}]*animation:\s*automation-spin 0\.8s linear infinite/is)
  assert.match(automationStyles, /@keyframes automation-spin\s*\{[^}]*transform:\s*rotate\(360deg\)/is)
  assert.match(automationStyles, /@media \(prefers-reduced-motion:\s*reduce\)\s*\{[^}]*\.automation-button-spinner\s*\{[^}]*animation:\s*none/is)
})
