import { expect, test, type Page } from '@playwright/test'
import fs from 'fs'
import path from 'path'
import {
  createFixtureVaultCopy,
  openFixtureVault,
  removeFixtureVaultCopy,
} from '../helpers/fixtureVault'
import { executeCommand, openCommandPalette, sendShortcut } from './helpers'

const CODE_COPY_NOTE_RELATIVE_PATH = path.join('note', 'fenced-code-copy.md')
const CODE_COPY_NOTE_TITLE = 'Fenced Code Copy'
const JSON_SNIPPET = '{\n  "id": "Demo",\n  "enabled": true\n}'
const TYPESCRIPT_SNIPPET = 'const answer = 42\nconsole.log(answer)'
const CJK_SNIPPET = 'const label = "中文測試"\nconsole.log(label)'
const RICH_CODE_SELECTOR = '.bn-block-content[data-content-type="codeBlock"] pre code'

function writeCodeCopyFixtureNote(tempVaultDir: string) {
  const notePath = path.join(tempVaultDir, CODE_COPY_NOTE_RELATIVE_PATH)
  fs.mkdirSync(path.dirname(notePath), { recursive: true })
  fs.writeFileSync(notePath, `---
Is A: Note
Status: Active
---

# ${CODE_COPY_NOTE_TITLE}

Copy this JSON exactly:

\`\`\`json
${JSON_SNIPPET}
\`\`\`

Copy this TypeScript exactly:

\`\`\`ts
${TYPESCRIPT_SNIPPET}
\`\`\`

Copy this CJK text exactly:

\`\`\`ts
${CJK_SNIPPET}
\`\`\`
`)
}

async function clearClipboard(page: Page) {
  await page.evaluate(() => navigator.clipboard.writeText(''))
}

async function readClipboard(page: Page) {
  return page.evaluate(() => navigator.clipboard.readText())
}

async function copySelectedText(page: Page) {
  await clearClipboard(page)
  await sendShortcut(page, 'c', ['Control'])
  return readClipboard(page)
}

async function expectRichCodeBlockButtonCopy(page: Page, blockIndex: number, expectedText: string) {
  await clearClipboard(page)
  const codeBlock = page.locator('.bn-block-content[data-content-type="codeBlock"]').nth(blockIndex)
  await expect(codeBlock).toBeVisible()
  await codeBlock.hover()
  const copyButton = page.getByRole('button', { name: 'Copy code to clipboard' })
  await expect(copyButton).toBeVisible()
  await copyButton.click()
  await expect.poll(() => readClipboard(page)).toBe(expectedText)
}

async function selectRichCodeBlock(page: Page, blockIndex: number) {
  await page.locator(RICH_CODE_SELECTOR).nth(blockIndex).waitFor({ timeout: 10_000 })
  await page.evaluate(({ selector, index }) => {
    const code = document.querySelectorAll(selector)[index]
    if (!code) throw new Error(`Missing rendered code block ${index}`)

    const range = document.createRange()
    range.selectNodeContents(code)
    const selection = window.getSelection()
    selection?.removeAllRanges()
    selection?.addRange(range)
  }, { selector: RICH_CODE_SELECTOR, index: blockIndex })
}

async function selectRawEditorText(page: Page, text: string) {
  await expect(page.locator('.cm-content')).toBeVisible({ timeout: 10_000 })
  await page.evaluate((expectedText) => {
    const content = document.querySelector('.cm-content') as (HTMLElement & {
      cmTile?: {
        view?: {
          state: { doc: { toString: () => string } }
          focus: () => void
          dispatch: (transaction: { selection: { anchor: number; head: number } }) => void
        }
      }
    }) | null
    const view = content?.cmTile?.view
    if (!view) throw new Error('CodeMirror view is not available')

    const documentText = view.state.doc.toString()
    const start = documentText.indexOf(expectedText)
    if (start === -1) throw new Error(`Raw editor text not found: ${expectedText}`)

    view.focus()
    view.dispatch({ selection: { anchor: start, head: start + expectedText.length } })
  }, text)
}

test.describe('Fenced code copy', () => {
  let tempVaultDir: string

  test.setTimeout(30_000)

  test.beforeEach(async ({ page }) => {
    await page.context().grantPermissions(['clipboard-read', 'clipboard-write'])
    tempVaultDir = createFixtureVaultCopy()
    writeCodeCopyFixtureNote(tempVaultDir)
    await openFixtureVault(page, tempVaultDir)
  })

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

  test('copies rich code blocks from the button and selections without corruption', async ({ page }) => {
    const noteList = page.locator('[data-testid="note-list-container"]')
    const noteItem = noteList.getByText(CODE_COPY_NOTE_TITLE, { exact: true })
    await expect(noteItem).toBeVisible({ timeout: 10_000 })
    await noteItem.click()
    await expect(page.locator(RICH_CODE_SELECTOR)).toHaveCount(3, { timeout: 10_000 })

    await expectRichCodeBlockButtonCopy(page, 0, JSON_SNIPPET)
    await expectRichCodeBlockButtonCopy(page, 1, TYPESCRIPT_SNIPPET)
    await expectRichCodeBlockButtonCopy(page, 2, CJK_SNIPPET)

    await selectRichCodeBlock(page, 0)
    await expect.poll(() => copySelectedText(page)).toBe(JSON_SNIPPET)

    await selectRichCodeBlock(page, 1)
    await expect.poll(() => copySelectedText(page)).toBe(TYPESCRIPT_SNIPPET)

    await selectRichCodeBlock(page, 2)
    await expect.poll(() => copySelectedText(page)).toBe(CJK_SNIPPET)

    await openCommandPalette(page)
    await executeCommand(page, 'Toggle Raw')
    await selectRawEditorText(page, JSON_SNIPPET)
    await expect.poll(() => copySelectedText(page)).toBe(JSON_SNIPPET)
  })
})
