import { afterEach, describe, expect, it, vi } from 'vitest'

const { isNodeLikeMock, spawnMock } = vi.hoisted(() => ({
  isNodeLikeMock: vi.fn(),
  spawnMock: vi.fn(),
}))

vi.mock('../src/dimsdk/pages/utils/runtime.ts', () => ({
  isNodeLike: isNodeLikeMock,
}))

vi.mock('node:child_process', () => ({
  spawn: spawnMock,
}))

const {
  clearClipboard,
  copyTextToClipboard,
} = await import('../src/dimsdk/pages/utils/clipboard.ts')

const realProcess = globalThis.process
const realNavigator = globalThis.navigator
const realDocument = globalThis.document

function setProcess(value: NodeJS.Process | undefined): void {
  Object.defineProperty(globalThis, 'process', {
    configurable: true,
    value,
  })
}

function setNavigator(value: Navigator | undefined): void {
  Object.defineProperty(globalThis, 'navigator', {
    configurable: true,
    value,
  })
}

function setDocument(value: Document | undefined): void {
  Object.defineProperty(globalThis, 'document', {
    configurable: true,
    value,
  })
}

afterEach(() => {
  isNodeLikeMock.mockReset()
  spawnMock.mockReset()
  setProcess(realProcess)
  setNavigator(realNavigator)
  setDocument(realDocument)
})

describe('clipboard helpers', () => {
  it('uses navigator.clipboard when available', async () => {
    const writeText = vi.fn(async () => {})
    setNavigator({
      clipboard: { writeText },
    } as unknown as Navigator)
    isNodeLikeMock.mockReturnValue(false)

    await expect(copyTextToClipboard('hello')).resolves.toBe(true)

    expect(writeText).toHaveBeenCalledWith('hello')
    expect(spawnMock).not.toHaveBeenCalled()
  })

  it('uses the node clipboard utility in node-like runtimes', async () => {
    const stdinWrite = vi.fn()
    const stdinEnd = vi.fn()
    spawnMock.mockImplementation(() => ({
      on(event: 'error' | 'close', cb: (value?: number) => void) {
        if (event === 'close')
          queueMicrotask(() => cb(0))
        return this
      },
      stdin: {
        write: stdinWrite,
        end: stdinEnd,
      },
    }))
    isNodeLikeMock.mockReturnValue(true)
    setNavigator(undefined)
    setDocument(undefined)
    setProcess({
      platform: 'linux',
      stdout: {
        isTTY: false,
      },
    } as unknown as NodeJS.Process)

    await expect(copyTextToClipboard('hello from node')).resolves.toBe(true)

    expect(spawnMock).toHaveBeenCalledWith('wl-copy', [], {
      stdio: ['pipe', 'ignore', 'ignore'],
      windowsHide: true,
    })
    expect(stdinWrite).toHaveBeenCalledWith('hello from node', 'utf8')
    expect(stdinEnd).toHaveBeenCalledTimes(1)
  })

  it('falls back to osc52 when stdout is a tty', async () => {
    const stdoutWrite = vi.fn()
    spawnMock.mockImplementation(() => {
      throw new Error('clipboard utility missing')
    })
    isNodeLikeMock.mockReturnValue(true)
    setNavigator(undefined)
    setDocument(undefined)
    setProcess({
      platform: 'linux',
      stdout: {
        isTTY: true,
        write: stdoutWrite,
      },
    } as unknown as NodeJS.Process)

    await expect(copyTextToClipboard('osc52')).resolves.toBe(true)

    expect(stdoutWrite).toHaveBeenCalledWith('\u001B]52;c;b3NjNTI=\u0007')
  })

  it('returns false for empty clipboard clears', async () => {
    isNodeLikeMock.mockReturnValue(false)
    setNavigator(undefined)
    setDocument(undefined)

    await expect(clearClipboard()).resolves.toBe(false)
  })
})
