// @vitest-environment jsdom
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest'
import { cleanup, fireEvent, render, waitFor, within } from '@testing-library/react'
import React from 'react'

// The terminals use xterm + ResizeObserver, which don't belong in a jsdom unit
// test. Stub them — the rest of the workbench renders for real against the mock.
vi.mock('../src/renderer/src/terminals', () => {
  let n = 0
  return { Terminal: () => null, lid: () => ++n }
})

import { App } from '../src/renderer/src/App'
import { ProjectProvider } from '../src/renderer/src/project'

beforeAll(() => {
  // jsdom gaps used by the tree/tab code.
  globalThis.ResizeObserver = class { observe() {} unobserve() {} disconnect() {} } as never
  if (!Element.prototype.scrollIntoView) Element.prototype.scrollIntoView = () => {}
})

afterEach(() => { cleanup(); localStorage.clear() })

function renderApp(): HTMLElement {
  const { container } = render(
    <ProjectProvider>
      <App />
    </ProjectProvider>,
  )
  return container
}

function rowWithText(container: HTMLElement, selector: string, text: string): HTMLElement | undefined {
  return Array.from(container.querySelectorAll<HTMLElement>(selector)).find((el) => el.textContent?.includes(text))
}
// The tree opens fully collapsed, so expand each ancestor folder before reaching
// a nested file. Each folder is clicked exactly once (a second click collapses).
async function expandTo(c: HTMLElement, ...folders: string[]): Promise<void> {
  for (const name of folders) {
    const row = await waitFor(() => {
      const r = rowWithText(c, '.tree-row', name)
      if (!r) throw new Error(`folder ${name} not ready`)
      return r
    })
    fireEvent.click(row)
  }
}

describe('App (mock data, jsdom)', () => {
  it('renders the four-column workbench with the git change list', async () => {
    const c = renderApp()
    await waitFor(() => expect(rowWithText(c, '.git-row', 'UserController.php')).toBeTruthy())
    expect(c.querySelector('.workbench')).toBeTruthy()
    expect(c.querySelector('.commit-box')).toBeTruthy() // git panel
    expect(c.querySelector('.tree-body')).toBeTruthy()  // explorer
    // no file open yet
    expect(c.textContent).toContain('No file open')
  })

  it('opens a changed file from the git panel into the diff view', async () => {
    const c = renderApp()
    const row = await waitFor(() => {
      const r = rowWithText(c, '.git-row', 'UserController.php')
      if (!r) throw new Error('row not ready')
      return r
    })
    fireEvent.click(row)
    // no tabs anymore — the file opens straight into the editor's diff toolbar,
    // and its path shows in the title-bar breadcrumb.
    await waitFor(() => expect(c.querySelector('.diff-bar')).toBeTruthy())
    expect(c.querySelector('.diff-bar')?.textContent).toContain('Modified')
    expect(rowWithText(c, '.tb-crumb', 'UserController.php')).toBeTruthy()
  })

  it('makes an edited buffer dirty (breadcrumb dot)', async () => {
    const c = renderApp()
    await expandTo(c, 'public', 'assets')
    const treeRow = await waitFor(() => {
      const r = rowWithText(c, '.tree-row', 'store.js')
      if (!r) throw new Error('tree not ready')
      return r
    })
    fireEvent.click(treeRow) // unchanged file → editable "code" mode
    const ta = await waitFor(() => {
      const t = c.querySelector<HTMLTextAreaElement>('.ce-ta')
      if (!t) throw new Error('editor not ready')
      return t
    })
    expect(c.querySelector('.tb-dirty')).toBeNull()
    fireEvent.change(ta, { target: { value: '// edited\n' } })
    await waitFor(() => expect(c.querySelector('.tb-dirty')).toBeTruthy())
  })

  it('searches file contents from the search modal', async () => {
    const c = renderApp()
    await waitFor(() => expect(rowWithText(c, '.git-row', 'UserController.php')).toBeTruthy())
    const searchBtn = rowWithText(c, '.tb-btn', 'Search')!
    fireEvent.click(searchBtn)
    const modal = await waitFor(() => {
      const m = c.querySelector('.search-modal')
      if (!m) throw new Error('modal not open')
      return m as HTMLElement
    })
    const input = within(modal).getByPlaceholderText(/Search content/i)
    fireEvent.change(input, { target: { value: 'balance' } })
    await waitFor(() => expect(modal.querySelectorAll('.sr-file').length).toBeGreaterThan(0))
  })
})
