import { act, fireEvent, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { openNoteListPropertiesPicker } from './note-list/noteListPropertiesEvents'
import {
  allSelection,
  makeEntry,
  makeTypeDefinition,
  mockEntries,
  renderNoteList,
} from '../test-utils/noteListTestUtils'

describe('NoteList rendering', () => {
  it('shows an empty state when there are no entries', () => {
    renderNoteList({ entries: [] })
    expect(screen.getByText('No notes found')).toBeInTheDocument()
  })

  it('renders all entries in the all-notes view', () => {
    renderNoteList()
    expect(screen.getByText('Build Laputa App')).toBeInTheDocument()
    expect(screen.getByText('Facebook Ads Strategy')).toBeInTheDocument()
    expect(screen.getByText('Matteo Cellini')).toBeInTheDocument()
  })

  it('filters section groups by type', () => {
    renderNoteList({ selection: { kind: 'sectionGroup', type: 'Person' } })
    expect(screen.getByText('Matteo Cellini')).toBeInTheDocument()
    expect(screen.queryByText('Build Laputa App')).not.toBeInTheDocument()
  })

  it('supports event sections', () => {
    renderNoteList({ selection: { kind: 'sectionGroup', type: 'Event' } })
    expect(screen.getByText('Kickoff Meeting')).toBeInTheDocument()
    expect(screen.queryByText('Build Laputa App')).not.toBeInTheDocument()
  })

  it('supports project sections', () => {
    renderNoteList({ selection: { kind: 'sectionGroup', type: 'Project' } })
    expect(screen.getByText('Build Laputa App')).toBeInTheDocument()
    expect(screen.queryByText('Matteo Cellini')).not.toBeInTheDocument()
  })

  it('passes the selected type when creating a note from a type section', () => {
    const { onCreateNote } = renderNoteList({ selection: { kind: 'sectionGroup', type: 'Project' } })
    fireEvent.click(screen.getByTitle('Create new note'))
    expect(onCreateNote).toHaveBeenCalledWith('Project')
  })

  it('creates an untyped note from all notes', () => {
    const { onCreateNote } = renderNoteList()
    fireEvent.click(screen.getByTitle('Create new note'))
    expect(onCreateNote).toHaveBeenCalledWith(undefined)
  })

  it('pins the current entity and shows grouped children', () => {
    renderNoteList({ selection: { kind: 'entity', entry: mockEntries[0] } })
    expect(screen.getAllByText('Build Laputa App').length).toBeGreaterThanOrEqual(1)
    expect(screen.getByText('Facebook Ads Strategy')).toBeInTheDocument()
    expect(screen.queryByText('Matteo Cellini')).not.toBeInTheDocument()
    expect(screen.getByText('Children')).toBeInTheDocument()
    expect(screen.getByText('Related to')).toBeInTheDocument()
  })

  it('shows referenced-by groups for topic entities', () => {
    renderNoteList({ selection: { kind: 'entity', entry: mockEntries[4] } })
    expect(screen.getByText('Build Laputa App')).toBeInTheDocument()
    expect(screen.getByText('Referenced By')).toBeInTheDocument()
  })

  it('toggles the search input from the header action', () => {
    renderNoteList()
    expect(screen.queryByPlaceholderText('Search notes...')).not.toBeInTheDocument()
    fireEvent.click(screen.getByTitle('Search notes'))
    expect(screen.getByPlaceholderText('Search notes...')).toBeInTheDocument()
  })

  it('filters by a case-insensitive search query', () => {
    renderNoteList()
    fireEvent.click(screen.getByTitle('Search notes'))
    fireEvent.change(screen.getByPlaceholderText('Search notes...'), { target: { value: 'facebook' } })
    expect(screen.getByText('Facebook Ads Strategy')).toBeInTheDocument()
    expect(screen.queryByText('Build Laputa App')).not.toBeInTheDocument()
  })

  it('sorts entries by last modified descending by default', () => {
    renderNoteList({
      entries: [
        { ...mockEntries[0], modifiedAt: 1000, title: 'Oldest' },
        { ...mockEntries[1], modifiedAt: 3000, title: 'Newest', path: '/p2' },
        { ...mockEntries[2], modifiedAt: 2000, title: 'Middle', path: '/p3' },
      ],
    })

    const titles = screen.getAllByText(/Oldest|Newest|Middle/).map((element) => element.textContent)
    expect(titles).toEqual(['Newest', 'Middle', 'Oldest'])
  })

  it('hides standalone status badges inside note rows', () => {
    renderNoteList()
    expect(screen.queryByText('Active')).not.toBeInTheDocument()
  })

  it('shows search and create actions in the header instead of a count badge', () => {
    renderNoteList()
    expect(screen.getByTitle('Search notes')).toBeInTheDocument()
    expect(screen.getByTitle('Create new note')).toBeInTheDocument()
  })

  it('shows backlinks from outgoing links in entity view', () => {
    const entriesWithBacklink = mockEntries.map((entry) =>
      entry.path === mockEntries[2].path ? { ...entry, outgoingLinks: ['Build Laputa App'] } : entry,
    )

    renderNoteList({
      entries: entriesWithBacklink,
      selection: { kind: 'entity', entry: mockEntries[0] },
    })

    expect(screen.getByText('Backlinks')).toBeInTheDocument()
    expect(screen.getByText('Matteo Cellini')).toBeInTheDocument()
  })

  it('collapses and expands entity groups', () => {
    renderNoteList({ selection: { kind: 'entity', entry: mockEntries[0] } })
    expect(screen.getByText('Facebook Ads Strategy')).toBeInTheDocument()

    fireEvent.click(screen.getByText('Children'))
    expect(screen.queryByText('Facebook Ads Strategy')).not.toBeInTheDocument()

    fireEvent.click(screen.getByText('Children'))
    expect(screen.getByText('Facebook Ads Strategy')).toBeInTheDocument()
  })

  it('shows the entity snippet in the prominent card', () => {
    renderNoteList({ selection: { kind: 'entity', entry: mockEntries[0] } })
    expect(screen.getByText('Build a personal knowledge management app.')).toBeInTheDocument()
  })

  it('shows the inbox customize-columns action and falls back to type-defined chips', () => {
    const entries = [
      makeTypeDefinition('Book', ['Priority']),
      makeEntry({
        path: '/vault/book.md',
        filename: 'book.md',
        title: 'Book Note',
        isA: 'Book',
        properties: { Priority: 'High', Owner: 'Luca' },
        createdAt: 1700000000,
      }),
    ]

    renderNoteList({
      entries,
      selection: { kind: 'filter', filter: 'inbox' },
      inboxNoteListProperties: null,
      onUpdateInboxNoteListProperties: () => undefined,
    })

    expect(screen.getByTitle('Customize Inbox columns')).toBeInTheDocument()
    expect(screen.getByText('High')).toBeInTheDocument()
    expect(screen.queryByText('Luca')).not.toBeInTheDocument()
  })

  it('opens the inbox column picker from the global event and saves new columns', () => {
    const onUpdateInboxNoteListProperties = vi.fn()
    const entries = [
      makeTypeDefinition('Book', ['Priority']),
      makeEntry({
        path: '/vault/book.md',
        filename: 'book.md',
        title: 'Book Note',
        isA: 'Book',
        properties: { Priority: 'High', Owner: 'Luca' },
        createdAt: 1700000000,
      }),
    ]

    renderNoteList({
      entries,
      selection: { kind: 'filter', filter: 'inbox' },
      inboxNoteListProperties: null,
      onUpdateInboxNoteListProperties,
    })

    act(() => {
      openNoteListPropertiesPicker('inbox')
    })

    expect(screen.getByTestId('list-properties-popover')).toBeInTheDocument()
    expect(screen.getByRole('checkbox', { name: 'Priority' })).toBeChecked()

    fireEvent.click(screen.getByRole('checkbox', { name: 'Owner' }))
    expect(onUpdateInboxNoteListProperties).toHaveBeenCalledWith(['Priority', 'Owner'])
  })

  it('shows status in the type column picker when at least one note has it set', () => {
    const entries = [
      makeTypeDefinition('Book'),
      makeEntry({
        path: '/vault/book.md',
        filename: 'book.md',
        title: 'Book Note',
        isA: 'Book',
        status: 'Active',
        createdAt: 1700000000,
      }),
    ]

    renderNoteList({
      entries,
      selection: { kind: 'sectionGroup', type: 'Book' },
      onUpdateTypeSort: () => undefined,
    })

    act(() => {
      openNoteListPropertiesPicker('type')
    })

    expect(screen.getByTestId('list-properties-popover')).toBeInTheDocument()
    expect(screen.getByRole('checkbox', { name: 'status' })).toBeInTheDocument()
  })

  it('keeps blank statuses out of the type column picker', () => {
    const entries = [
      makeTypeDefinition('Book'),
      makeEntry({
        path: '/vault/book.md',
        filename: 'book.md',
        title: 'Book Note',
        isA: 'Book',
        status: '',
        properties: { Owner: 'Luca' },
        createdAt: 1700000000,
      }),
    ]

    renderNoteList({
      entries,
      selection: { kind: 'sectionGroup', type: 'Book' },
      onUpdateTypeSort: () => undefined,
    })

    act(() => {
      openNoteListPropertiesPicker('type')
    })

    expect(screen.getByTestId('list-properties-popover')).toBeInTheDocument()
    expect(screen.getByRole('checkbox', { name: 'Owner' })).toBeInTheDocument()
    expect(screen.queryByRole('checkbox', { name: 'status' })).not.toBeInTheDocument()
  })

  it('renders status as a note-list chip when a type displays it', () => {
    const entries = [
      makeTypeDefinition('Book', ['status']),
      makeEntry({
        path: '/vault/book.md',
        filename: 'book.md',
        title: 'Book Note',
        isA: 'Book',
        status: 'Active',
        createdAt: 1700000000,
      }),
    ]

    renderNoteList({
      entries,
      selection: { kind: 'sectionGroup', type: 'Book' },
    })

    expect(screen.getByTestId('property-chips')).toHaveTextContent('Active')
  })

  it('uses inbox overrides when configured', () => {
    const entries = [
      makeTypeDefinition('Book', ['Priority']),
      makeEntry({
        path: '/vault/book.md',
        filename: 'book.md',
        title: 'Book Note',
        isA: 'Book',
        properties: { Priority: 'High', Owner: 'Luca' },
        createdAt: 1700000000,
      }),
    ]

    renderNoteList({
      entries,
      selection: { kind: 'filter', filter: 'inbox' },
      inboxNoteListProperties: ['Owner'],
      onUpdateInboxNoteListProperties: () => undefined,
    })

    expect(screen.getByText('Luca')).toBeInTheDocument()
    expect(screen.queryByText('High')).not.toBeInTheDocument()
  })
})

describe('NoteList click behavior', () => {
  it('opens the current tab on a regular click', () => {
    const { onReplaceActiveTab, onSelectNote } = renderNoteList()
    fireEvent.click(screen.getByText('Build Laputa App'))
    expect(onReplaceActiveTab).toHaveBeenCalledWith(mockEntries[0])
    expect(onSelectNote).not.toHaveBeenCalled()
  })

  it('opens a new tab on Cmd+Click', () => {
    const { onReplaceActiveTab, onSelectNote } = renderNoteList()
    fireEvent.click(screen.getByText('Build Laputa App'), { metaKey: true })
    expect(onSelectNote).toHaveBeenCalledWith(mockEntries[0])
    expect(onReplaceActiveTab).not.toHaveBeenCalled()
  })

  it('opens a new tab on Ctrl+Click', () => {
    const { onReplaceActiveTab, onSelectNote } = renderNoteList()
    fireEvent.click(screen.getByText('Build Laputa App'), { ctrlKey: true })
    expect(onSelectNote).toHaveBeenCalledWith(mockEntries[0])
    expect(onReplaceActiveTab).not.toHaveBeenCalled()
  })

  it('supports Cmd+Click on the entity pinned card', () => {
    const { onReplaceActiveTab, onSelectNote } = renderNoteList({ selection: { kind: 'entity', entry: mockEntries[0] } })
    const titles = screen.getAllByText('Build Laputa App')
    fireEvent.click(titles[titles.length - 1], { metaKey: true })
    expect(onSelectNote).toHaveBeenCalledWith(mockEntries[0])
    expect(onReplaceActiveTab).not.toHaveBeenCalled()
  })

  it('opens the current tab from the entity pinned card on regular click', () => {
    const { onReplaceActiveTab, onSelectNote } = renderNoteList({ selection: { kind: 'entity', entry: mockEntries[0] } })
    const titles = screen.getAllByText('Build Laputa App')
    fireEvent.click(titles[titles.length - 1])
    expect(onReplaceActiveTab).toHaveBeenCalledWith(mockEntries[0])
    expect(onSelectNote).not.toHaveBeenCalled()
  })

  it('opens child notes from entity view in the current tab', () => {
    const { onReplaceActiveTab, onSelectNote } = renderNoteList({ selection: { kind: 'entity', entry: mockEntries[0] } })
    fireEvent.click(screen.getByText('Facebook Ads Strategy'))
    expect(onReplaceActiveTab).toHaveBeenCalledWith(mockEntries[1])
    expect(onSelectNote).not.toHaveBeenCalled()
  })
})

describe('NoteList type sections', () => {
  const typeEntry = {
    ...makeEntry({
      path: '/Users/luca/Laputa/types/project.md',
      filename: 'project.md',
      title: 'Project',
      isA: 'Type',
      snippet: 'Defines the Project type.',
      modifiedAt: 1700000000,
      fileSize: 200,
      wordCount: 50,
    }),
  }
  const entriesWithType = [...mockEntries, typeEntry]

  it('does not show a type note pinned card while browsing the section', () => {
    renderNoteList({
      entries: entriesWithType,
      selection: { kind: 'sectionGroup', type: 'Project' },
    })

    expect(screen.queryByText('Defines the Project type.')).not.toBeInTheDocument()
    expect(screen.getByText('Build Laputa App')).toBeInTheDocument()
  })

  it('renders a clickable type header that opens the type note', () => {
    const { onReplaceActiveTab } = renderNoteList({
      entries: entriesWithType,
      selection: { kind: 'sectionGroup', type: 'Project' },
    })

    const headerLink = screen.getByTestId('type-header-link')
    expect(headerLink).toHaveTextContent('Project')
    fireEvent.click(headerLink)
    expect(onReplaceActiveTab).toHaveBeenCalledWith(typeEntry)
  })

  it('does not render a type header outside type sections', () => {
    renderNoteList({ entries: entriesWithType, selection: allSelection })
    expect(screen.queryByTestId('type-header-link')).not.toBeInTheDocument()
  })
})

describe('NoteList traffic-light padding', () => {
  it('adds left padding when the sidebar is collapsed', () => {
    const { container } = renderNoteList({ sidebarCollapsed: true })
    const header = container.querySelector('.h-\\[52px\\]') as HTMLElement
    expect(header.style.paddingLeft).toBe('80px')
  })

  it('does not add extra left padding when the sidebar is expanded', () => {
    const { container } = renderNoteList({ sidebarCollapsed: false })
    const header = container.querySelector('.h-\\[52px\\]') as HTMLElement
    expect(header.style.paddingLeft).toBe('')
  })

  it('defaults to no extra padding when sidebarCollapsed is omitted', () => {
    const { container } = renderNoteList()
    const header = container.querySelector('.h-\\[52px\\]') as HTMLElement
    expect(header.style.paddingLeft).toBe('')
  })
})
