import { expect, test, type Locator, type Page } from '@playwright/test'
import fs from 'fs'
import path from 'path'
import { createFixtureVaultCopy, openFixtureVault, removeFixtureVaultCopy } from '../helpers/fixtureVault'
import { executeCommand, openCommandPalette } from './helpers'
import { RUNTIME_STYLE_NONCE } from '../../src/lib/runtimeStyleNonce'
import { APP_COMMAND_IDS } from '../../src/hooks/appCommandCatalog'
import { triggerMenuCommand, triggerShortcutCommand } from './testBridge'

let tempVaultDir: string

const FIRST_DIAGRAM = [
  '```mermaid',
  'flowchart LR',
  '  A[Draft] --> B[Saved]',
  '```',
].join('\n')
const UPDATED_FIRST_DIAGRAM = FIRST_DIAGRAM.replace('B[Saved]', 'C[Published]')
const SECOND_DIAGRAM = [
  '```mermaid',
  'sequenceDiagram',
  '  Alice->>Bob: Hello',
  '```',
].join('\n')
const REPORTED_THEME_DIAGRAM = [
  '```mermaid',
  "%%{init: {'theme':'base','themeVariables':{'primaryColor':'#dbe7ff','primaryTextColor':'#0b1220','primaryBorderColor':'#1f3a8a','lineColor':'#1f3a8a','secondaryColor':'#fff4d6','tertiaryColor':'#ffe0e0','fontSize':'14px'}}}%%",
  'flowchart TD',
  '    A(["Employee clocks in"]) --> B{"Linked to a planned shift?"}',
  '',
  '    B -- "Yes" --> C["Other path<br/>for scheduled shift"]',
  '    C --> D["Contract path<br/>for scheduled shift"]',
  '',
  '    B -- "No (unscheduled)" --> E["other path<br/>for unscheduled clocking"]',
  '',
  '    classDef path fill:#fff4d6,stroke:#7a5b00,stroke-width:2px,color:#3a2c00;',
  '    class C,D,E,F path;',
  '```',
].join('\n')
const REPORTED_DATABASE_DIAGRAM = [
  '```mermaid',
  'flowchart TB',
  '  app["app<br/>api"] --> db[("DB Type<br/>rows")]',
  '```',
].join('\n')
const REPORTED_GANTT_DIAGRAM = [
  '```mermaid',
  'gantt',
  '    title Project Plan',
  '    dateFormat YYYY-MM-DD',
  '    section Phase 1',
  '    Requirements Analysis:a1, 2026-06-24, 7d',
  '    Design:a2, after a1, 5d',
  '    section Phase 2',
  '    Development:b1, after a2, 14d',
  '    Testing & Deployment:b2, after b1, 7d',
  '```',
].join('\n')
const SYSTEM_OVERVIEW_DIAGRAM = [
  '```mermaid',
  'flowchart TD',
  '    subgraph TW["Tauri v2 Window"]',
  '        subgraph FE["React Frontend"]',
  '            App["App.tsx (orchestrator)"]',
  '            WS["WelcomeScreen\\n(onboarding)"]',
  '            SB["Sidebar\\n(navigation + filters + types)"]',
  '            NL["NoteList / PulseView\\n(filtered list / activity)"]',
  '            ED["Editor\\n(BlockNote + diff + raw)"]',
  '            IN["Inspector\\n(metadata + relationships)"]',
  '            AIP["AiPanel\\n(selected CLI agent + tools)"]',
  '            SP["SearchPanel\\n(keyword search)"]',
  '            ST["StatusBar\\n(vault picker + sync + version)"]',
  '            CP["CommandPalette\\n(Cmd+K launcher)"]',
  '',
  '            App --> WS & SB & NL & ED & SP & ST & CP',
  '            ED --> IN & AIP',
  '        end',
  '',
  '        subgraph RB["Rust Backend"]',
  '            LIB["lib.rs → Tauri commands"]',
  '            VAULT["vault/"]',
  '            FM["frontmatter/"]',
  '            GIT["git/\\n(commit, sync, clone)"]',
  '            SETTINGS["settings.rs"]',
  '            SEARCH["search.rs"]',
  '            CLI["ai_agents.rs\\n+ claude_cli.rs"]',
  '        end',
  '',
  '        subgraph EXT["External Services"]',
  '            CCLI["Claude / Codex / OpenCode / Pi CLI\\n(agent subprocesses)"]',
  '            MCP["MCP Server\\n(ws://9710, 9711)"]',
  '            GCLI["git CLI\\n(system executable)"]',
  '            REMOTE["Git remotes\\n(GitHub/GitLab/Gitea/etc.)"]',
  '        end',
  '',
  '        FE -->|"Tauri IPC"| RB',
  '        CLI -->|"spawn subprocess"| CCLI',
  '        LIB -->|"register / monitor"| MCP',
  '        GIT -->|"clone / fetch / push / pull"| GCLI',
  '        GCLI -->|"network auth via user config"| REMOTE',
  '    end',
  '',
  '    style FE fill:#e8f4fd,stroke:#2196f3,color:#000',
  '    style RB fill:#fff8e1,stroke:#ff9800,color:#000',
  '    style EXT fill:#f3e5f5,stroke:#9c27b0,color:#000',
  '```',
].join('\n')
const INVALID_DIAGRAM = [
  '```mermaid',
  'not a diagram',
  '```',
].join('\n')
const REPORTED_INVALID_DIAGRAM = [
  '```mermaid',
  'flowchart TD',
  '    A[Christmas] -->|Get money| B(Go shopping)',
  '    B --> C{Let me think}',
  '    C -->|One| D[Laptop]',
  '    C -->|Two| E[iPhone]',
  '    C -->|Three| F[fa:fa-car Car]',
  '## ABC',
  '```',
].join('\n')

test.beforeEach(async ({ page }, testInfo) => {
  testInfo.setTimeout(90_000)
  tempVaultDir = createFixtureVaultCopy()
  fs.writeFileSync(
    path.join(tempVaultDir, 'note', 'mermaid-reported.md'),
    [
      '---',
      'Status: Active',
      'Date: 2026-04-29T00:00:00',
      '---',
      '',
      '# Mermaid Reported',
      '',
      REPORTED_THEME_DIAGRAM,
      '',
    ].join('\n'),
  )
  fs.writeFileSync(
    path.join(tempVaultDir, 'note', 'mermaid-database.md'),
    [
      '---',
      'Status: Active',
      'Date: 2026-06-21T00:00:00',
      '---',
      '',
      '# Mermaid Database',
      '',
      REPORTED_DATABASE_DIAGRAM,
      '',
    ].join('\n'),
  )
  fs.writeFileSync(
    path.join(tempVaultDir, 'note', 'mermaid-gantt.md'),
    [
      '---',
      'Status: Active',
      'Date: 2026-06-24T00:00:00',
      '---',
      '',
      '# Mermaid Gantt',
      '',
      REPORTED_GANTT_DIAGRAM,
      '',
    ].join('\n'),
  )
  await openFixtureVault(page, tempVaultDir, { installedAiAgents: ['claude_code'] })
})

test.afterEach(async () => {
  removeFixtureVaultCopy(tempVaultDir)
})

async function openNote(page: Page, title: string): Promise<void> {
  await page.locator('[data-testid="note-list-container"]').getByText(title, { exact: true }).click()
  await expect(page.locator('.bn-editor')).toBeVisible({ timeout: 5_000 })
}

async function toggleRawMode(page: Page, visibleSelector: '.bn-editor' | '.cm-content'): Promise<void> {
  await openCommandPalette(page)
  await executeCommand(page, 'Toggle Raw')
  await expect(page.locator(visibleSelector)).toBeVisible({ timeout: 5_000 })
}

async function getRawEditorContent(page: Page): Promise<string> {
  return page.evaluate(() => {
    type CodeMirrorHost = Element & {
      cmTile?: {
        view?: {
          state: {
            doc: {
              toString(): string
            }
          }
        }
      }
    }

    const host = document.querySelector('.cm-content') as CodeMirrorHost | null
    return host?.cmTile?.view?.state.doc.toString() ?? host?.textContent ?? ''
  })
}

async function setRawEditorContent(page: Page, content: string): Promise<void> {
  await page.evaluate((nextContent) => {
    type CodeMirrorHost = Element & {
      cmTile?: {
        view?: {
          state: {
            doc: {
              length: number
            }
          }
          dispatch(update: {
            changes: {
              from: number
              to: number
              insert: string
            }
          }): void
        }
      }
    }

    const host = document.querySelector('.cm-content') as CodeMirrorHost | null
    const view = host?.cmTile?.view
    if (!view) return

    view.dispatch({
      changes: { from: 0, to: view.state.doc.length, insert: nextContent },
    })
  }, content)
}

async function expectRenderedDiagramCount(page: Page, count: number): Promise<void> {
  await expect(page.locator('[data-testid="mermaid-diagram-viewport"] svg')).toHaveCount(count, { timeout: 15_000 })
}

async function reloadVault(page: Page): Promise<void> {
  await triggerMenuCommand(page, APP_COMMAND_IDS.vaultReload)
  await expect(page.getByText(/Vault reloaded \(\d+ entries\)/).last()).toBeVisible({
    timeout: 5_000,
  })
}

async function countLargeBlackFilledShapes(page: Page, diagramIndex: number): Promise<number> {
  return page.locator('[data-testid="mermaid-diagram-viewport"] svg').nth(diagramIndex).evaluate((svg) => {
    const shapes = Array.from(svg.querySelectorAll<SVGGraphicsElement>('rect,path,polygon'))

    return shapes.filter((shape) => {
      const box = shape.getBoundingClientRect()
      return getComputedStyle(shape).fill === 'rgb(0, 0, 0)'
        && box.width > 12
        && box.height > 8
    }).length
  })
}

function mermaidSvg(page: Page, diagramIndex: number): Locator {
  return page.locator('[data-testid="mermaid-diagram-viewport"] svg').nth(diagramIndex)
}

function mermaidBodyArtifacts(page: Page): Locator {
  return page.locator([
    'body > [data-tolaria-mermaid-render-host]',
    'body > [id^="tolaria-mermaid-"]',
    'body > [id^="dtolaria-mermaid-"]',
    'body > [id^="itolaria-mermaid-"]',
  ].join(', '))
}

function mermaidNode(page: Page, diagramIndex: number, text: string): Locator {
  return mermaidSvg(page, diagramIndex).locator('.node').filter({ hasText: text }).first()
}

async function computedCss(locator: Locator, property: 'background-color' | 'color' | 'fill' | 'stroke'): Promise<string> {
  return locator.evaluate((element, styleProperty) => (
    getComputedStyle(element).getPropertyValue(styleProperty)
  ), property)
}

async function switchToResolvedTheme(page: Page, mode: 'dark' | 'light'): Promise<void> {
  const root = page.locator('html')
  if (await root.getAttribute('data-theme') !== mode) {
    await page.getByTestId('status-theme-mode').click()
  }
  await expect(root).toHaveAttribute('data-theme', mode)
}

async function setDocumentZoom(page: Page, percent: number): Promise<void> {
  await page.evaluate((zoomPercent) => {
    const factor = zoomPercent / 100
    document.documentElement.style.setProperty('zoom', `${zoomPercent}%`)
    document.documentElement.style.setProperty('--tolaria-overlay-zoom-factor', String(factor))
    document.documentElement.style.setProperty('--tolaria-overlay-zoom-inverse', String(1 / factor))
    window.dispatchEvent(new Event('laputa-zoom-change'))
  }, percent)
}

async function readDialogViewportBounds(page: Page) {
  return page.getByTestId('mermaid-diagram-dialog-viewport').evaluate((viewport) => {
    const rect = viewport.getBoundingClientRect()
    return {
      width: rect.width,
      height: rect.height,
      windowWidth: window.innerWidth,
      windowHeight: window.innerHeight,
    }
  })
}

async function dialogViewportFitsWindow(page: Page): Promise<boolean> {
  const bounds = await readDialogViewportBounds(page)
  const horizontallyContained = bounds.width <= bounds.windowWidth - 16
  const verticallyContained = bounds.height <= bounds.windowHeight - 16
  const stillFullscreenWidth = bounds.width >= bounds.windowWidth - 96
  const stillFullscreenHeight = bounds.height >= bounds.windowHeight - 96
  return horizontallyContained && verticallyContained && stillFullscreenWidth && stillFullscreenHeight
}

function mermaidNodeLabel(node: Locator): Locator {
  return node.locator('.nodeLabel, text').first()
}

async function readStyleNonce(svg: Locator): Promise<string | null> {
  return svg.locator('style').first().evaluate((style) => {
    const nonce = (style as HTMLElement).nonce
    return nonce.length > 0 ? nonce : style.getAttribute('nonce')
  })
}

async function labelFitsNode(node: Locator): Promise<boolean> {
  return node.evaluate((element) => {
    const shape = element.querySelector<SVGGraphicsElement>('rect, polygon, circle, ellipse, path')!
    const label = element.querySelector<SVGGraphicsElement | HTMLElement>('.nodeLabel, text')!
    const shapeBox = shape.getBoundingClientRect()
    const labelBox = label.getBoundingClientRect()
    return [
      labelBox.width <= shapeBox.width + 2,
      labelBox.height <= shapeBox.height + 2,
    ].every(Boolean)
  })
}

async function labelIsHorizontallyCenteredInNode(node: Locator): Promise<boolean> {
  return node.evaluate((element) => {
    const shape = element.querySelector<SVGGraphicsElement>('rect, polygon, circle, ellipse, path')!
    const label = element.querySelector<SVGGraphicsElement | HTMLElement>('.nodeLabel, text')!
    const shapeBox = shape.getBoundingClientRect()
    const labelBox = label.getBoundingClientRect()
    const shapeCenter = shapeBox.left + shapeBox.width / 2
    const labelCenter = labelBox.left + labelBox.width / 2
    return Math.abs(shapeCenter - labelCenter) <= 2
  })
}

async function readReportedDiagramMetrics(page: Page, diagramIndex: number) {
  const svg = mermaidSvg(page, diagramIndex)
  const scheduledNode = mermaidNode(page, diagramIndex, 'Other path')
  const scheduledShape = scheduledNode.locator('rect, polygon, circle, ellipse, path').first()
  const scheduledLabel = mermaidNodeLabel(scheduledNode)

  return {
    connectorStroke: await computedCss(svg.locator('.flowchart-link').first(), 'stroke'),
    markerFill: await computedCss(svg.locator('marker path').first(), 'fill'),
    scheduledFill: await computedCss(scheduledShape, 'fill'),
    scheduledStroke: await computedCss(scheduledShape, 'stroke'),
    scheduledTextFill: await computedCss(scheduledLabel, 'fill'),
    foreignObjectCount: await svg.locator('foreignObject').count(),
    labelsFit: [
      await labelFitsNode(scheduledNode),
      await labelFitsNode(mermaidNode(page, diagramIndex, 'Contract path')),
      await labelFitsNode(mermaidNode(page, diagramIndex, 'unscheduled clocking')),
    ].every(Boolean),
    styleNonce: await readStyleNonce(svg),
    text: await svg.evaluate((element) => element.textContent ?? ''),
  }
}

async function readDatabaseDiagramMetrics(page: Page, diagramIndex: number) {
  const svg = mermaidSvg(page, diagramIndex)
  const appNode = mermaidNode(page, diagramIndex, 'app')
  const databaseNode = mermaidNode(page, diagramIndex, 'DB Type')

  return {
    rectangleLabelCentered: await labelIsHorizontallyCenteredInNode(appNode),
    databaseLabelCentered: await labelIsHorizontallyCenteredInNode(databaseNode),
    databaseLabelFits: await labelFitsNode(databaseNode),
    foreignObjectCount: await svg.locator('foreignObject').count(),
    text: await svg.evaluate((element) => element.textContent ?? ''),
  }
}

async function readGanttDiagramMetrics(page: Page, diagramIndex: number) {
  const svg = mermaidSvg(page, diagramIndex)
  return svg.evaluate((element) => {
    const viewBox = element.getAttribute('viewBox')?.split(/\s+/u).map(Number) ?? []
    const [, , width, height] = viewBox
    return {
      roleDescription: element.getAttribute('aria-roledescription'),
      width,
      height,
      text: element.textContent ?? '',
      taskCount: element.querySelectorAll('.task').length,
    }
  })
}

function readNoteBFile(): string {
  return fs.readFileSync(path.join(tempVaultDir, 'note', 'note-b.md'), 'utf8')
}

test('Mermaid diagrams render when opening saved notes directly', async ({ page }) => {
  await openNote(page, 'Mermaid Reported')
  await expectRenderedDiagramCount(page, 1)
  await expect.poll(() => readReportedDiagramMetrics(page, 0)).toMatchObject({
    connectorStroke: 'rgb(31, 58, 138)',
    markerFill: 'rgb(31, 58, 138)',
    scheduledFill: 'rgb(255, 244, 214)',
    scheduledStroke: 'rgb(122, 91, 0)',
    scheduledTextFill: 'rgb(58, 44, 0)',
    foreignObjectCount: 0,
    labelsFit: true,
    styleNonce: RUNTIME_STYLE_NONCE,
    text: expect.stringContaining('Linked to a planned shift?'),
  })
})

test('Mermaid database node labels stay centered inside cylinders', async ({ page }) => {
  await openNote(page, 'Mermaid Database')
  await expectRenderedDiagramCount(page, 1)
  await expect.poll(() => readDatabaseDiagramMetrics(page, 0)).toMatchObject({
    rectangleLabelCentered: true,
    databaseLabelCentered: true,
    databaseLabelFits: true,
    foreignObjectCount: 0,
    text: expect.stringContaining('DB Type'),
  })
})

test('Mermaid Gantt diagrams render with a nonzero timeline width', async ({ page }) => {
  await openNote(page, 'Mermaid Gantt')
  await expectRenderedDiagramCount(page, 1)
  await expect.poll(() => readGanttDiagramMetrics(page, 0)).toMatchObject({
    roleDescription: 'gantt',
    width: expect.any(Number),
    height: expect.any(Number),
    taskCount: 4,
    text: expect.stringContaining('Testing & Deployment'),
  })
  expect((await readGanttDiagramMetrics(page, 0)).width).toBeGreaterThan(0)
})

test('fullscreen Mermaid diagrams keep the active Tolaria surface in dark mode', async ({ page }) => {
  await openNote(page, 'Mermaid Reported')
  await expectRenderedDiagramCount(page, 1)
  await switchToResolvedTheme(page, 'dark')

  const inlineViewport = page.getByTestId('mermaid-diagram-viewport').first()
  await expect.poll(() => computedCss(inlineViewport, 'background-color')).not.toBe('rgb(255, 255, 255)')
  const inlineBackground = await computedCss(inlineViewport, 'background-color')

  await page.getByTestId('mermaid-diagram').first().hover()
  await page.getByRole('button', { name: 'Open Mermaid diagram' }).first().click()

  const dialogViewport = page.getByTestId('mermaid-diagram-dialog-viewport')
  await expect(dialogViewport).toBeVisible()
  await expect.poll(() => computedCss(dialogViewport, 'background-color')).toBe(inlineBackground)
})

test('fullscreen Mermaid diagrams stay within the viewport while the app is zoomed', async ({ page }) => {
  await openNote(page, 'Mermaid Reported')
  await expectRenderedDiagramCount(page, 1)
  await setDocumentZoom(page, 120)

  await page.getByTestId('mermaid-diagram').first().hover()
  await page.getByRole('button', { name: 'Open Mermaid diagram' }).first().click()
  await expect(page.getByTestId('mermaid-diagram-dialog-viewport')).toBeVisible()

  await expect.poll(() => dialogViewportFitsWindow(page)).toBe(true)
})

test('Mermaid diagrams stay mounted after property edits refresh frontmatter', async ({ page }) => {
  const pageErrors: string[] = []
  page.on('pageerror', error => pageErrors.push(error.message))

  await openNote(page, 'Mermaid Reported')
  await expectRenderedDiagramCount(page, 1)
  await triggerShortcutCommand(page, APP_COMMAND_IDS.viewToggleProperties)
  await expect(page.getByTestId('add-property-row')).toBeVisible()

  const dateRow = page.getByTestId('editable-property').filter({ hasText: 'Date' })
  await dateRow.getByTestId('date-display').click()
  const nextDateLabel = await page.evaluate(() => new Date(2026, 3, 30).toLocaleDateString())
  await page.locator(`button[data-day="${nextDateLabel}"]`).first().click()

  await expect.poll(() => (
    fs.readFileSync(path.join(tempVaultDir, 'note', 'mermaid-reported.md'), 'utf8')
  )).toMatch(/Date: "?2026-04-30"?/)
  await expectRenderedDiagramCount(page, 1)
  await expect(page.locator('[data-testid="mermaid-diagram-viewport"]').first()).toContainText('Linked to a planned shift?')
  expect(pageErrors).toEqual([])
})

test('Mermaid diagram clicks stay stable while a vault reload settles', async ({ page }) => {
  const pageErrors: string[] = []
  page.on('pageerror', error => pageErrors.push(error.message))

  await openNote(page, 'Mermaid Reported')
  await expectRenderedDiagramCount(page, 1)
  await reloadVault(page)
  await mermaidSvg(page, 0).click()

  await expectRenderedDiagramCount(page, 1)
  await expect(page.locator('#tolaria-fatal-render-error')).toHaveCount(0)
  await expect(mermaidSvg(page, 0)).toContainText('Linked to a planned shift?')
  expect(pageErrors).toEqual([])
})

test('Mermaid diagrams render, fall back, and round-trip through raw mode', async ({ page }) => {
  await openNote(page, 'Note B')
  await toggleRawMode(page, '.cm-content')

  const originalContent = await getRawEditorContent(page)
  const nextContent = `${originalContent.trimEnd()}

${FIRST_DIAGRAM}

${REPORTED_THEME_DIAGRAM}

${INVALID_DIAGRAM}

${SECOND_DIAGRAM}

${SYSTEM_OVERVIEW_DIAGRAM}
`

  await setRawEditorContent(page, nextContent)
  await expect.poll(readNoteBFile).toContain(FIRST_DIAGRAM)

  await toggleRawMode(page, '.bn-editor')
  await expectRenderedDiagramCount(page, 4)
  await expect.poll(() => countLargeBlackFilledShapes(page, 3)).toBe(0)
  await expect.poll(() => readReportedDiagramMetrics(page, 1)).toMatchObject({
    connectorStroke: 'rgb(31, 58, 138)',
    markerFill: 'rgb(31, 58, 138)',
    scheduledFill: 'rgb(255, 244, 214)',
    scheduledStroke: 'rgb(122, 91, 0)',
    scheduledTextFill: 'rgb(58, 44, 0)',
    foreignObjectCount: 0,
    labelsFit: true,
    styleNonce: RUNTIME_STYLE_NONCE,
    text: expect.stringContaining('Linked to a planned shift?'),
  })
  await expect(page.locator('[data-testid="mermaid-diagram-error"]')).toHaveCount(1)
  await expect(page.locator('[data-testid="mermaid-diagram-error"]')).toContainText('not a diagram')

  await page.locator('[data-testid="mermaid-diagram"]').nth(3).hover()
  await page.getByRole('button', { name: 'Open Mermaid diagram' }).nth(3).click()
  await expect(page.locator('[data-testid="mermaid-diagram-dialog-viewport"] svg')).toBeVisible()
  await page.keyboard.press('Escape')

  await toggleRawMode(page, '.cm-content')
  const rawAfterRichMode = await getRawEditorContent(page)
  expect(rawAfterRichMode).toContain(FIRST_DIAGRAM)
  expect(rawAfterRichMode).toContain(REPORTED_THEME_DIAGRAM)
  expect(rawAfterRichMode).toContain(INVALID_DIAGRAM)
  expect(rawAfterRichMode).toContain(SECOND_DIAGRAM)
  expect(rawAfterRichMode).toContain(SYSTEM_OVERVIEW_DIAGRAM)

  await setRawEditorContent(page, rawAfterRichMode.replace(FIRST_DIAGRAM, UPDATED_FIRST_DIAGRAM))
  await expect.poll(readNoteBFile).toContain(UPDATED_FIRST_DIAGRAM)

  await toggleRawMode(page, '.bn-editor')
  await expectRenderedDiagramCount(page, 4)
  await expect(page.locator('[data-testid="mermaid-diagram-viewport"]').first()).toContainText('Published')

  await openNote(page, 'Note C')
  await openNote(page, 'Note B')
  await toggleRawMode(page, '.cm-content')

  const reopenedRaw = await getRawEditorContent(page)
  expect(reopenedRaw).toContain(UPDATED_FIRST_DIAGRAM)
  expect(reopenedRaw).toContain(REPORTED_THEME_DIAGRAM)
  expect(reopenedRaw).toContain(INVALID_DIAGRAM)
  expect(reopenedRaw).toContain(SECOND_DIAGRAM)
  expect(reopenedRaw).toContain(SYSTEM_OVERVIEW_DIAGRAM)
})

test('invalid reported Mermaid syntax recovers across source and AI panel rerenders', async ({ page }) => {
  await openNote(page, 'Note B')
  await toggleRawMode(page, '.cm-content')

  const originalContent = await getRawEditorContent(page)
  await setRawEditorContent(page, `${originalContent.trimEnd()}

${REPORTED_INVALID_DIAGRAM}
`)
  await expect.poll(readNoteBFile).toContain(REPORTED_INVALID_DIAGRAM)

  await toggleRawMode(page, '.bn-editor')
  await expect(page.locator('[data-testid="mermaid-diagram-error"]')).toContainText('## ABC')
  await expect(mermaidBodyArtifacts(page)).toHaveCount(0)
  await expect(page.locator('#tolaria-fatal-render-error')).toHaveCount(0)

  await page.getByRole('button', { name: 'Open the AI panel' }).click()
  await expect(page.getByTestId('ai-panel')).toBeVisible({ timeout: 5_000 })
  await toggleRawMode(page, '.cm-content')
  await toggleRawMode(page, '.bn-editor')
  await expect(page.locator('[data-testid="mermaid-diagram-error"]')).toContainText('## ABC')
  await expect(mermaidBodyArtifacts(page)).toHaveCount(0)

  const input = page.getByTestId('agent-input')
  await input.fill('Confirm the note is still usable.')
  await page.getByTestId('agent-send').click()
  await expect(page.getByTestId('ai-message').last()).toBeVisible({ timeout: 5_000 })
  await expect(mermaidBodyArtifacts(page)).toHaveCount(0)
  await expect(page.locator('#tolaria-fatal-render-error')).toHaveCount(0)
})
