import { render, screen, fireEvent } from '@testing-library/react'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { NoteList, getSortComparator, filterEntries } from './NoteList'
import type { VaultEntry, SidebarSelection } from '../types'

const allSelection: SidebarSelection = { kind: 'filter', filter: 'all' }
const noopSelect = vi.fn()

const mockEntries: VaultEntry[] = [
  {
    path: '/Users/luca/Laputa/project/26q1-laputa-app.md',
    filename: '26q1-laputa-app.md',
    title: 'Build Laputa App',
    isA: 'Project',
    aliases: [],
    belongsTo: [],
    relatedTo: ['[[topic/software-development]]'],
    status: 'Active',
    owner: 'Luca',
    cadence: null,
    archived: false,
    trashed: false,
    trashedAt: null,
    modifiedAt: 1700000000,
    createdAt: null,
    fileSize: 1024,
    snippet: 'Build a personal knowledge management app.',
    relationships: {
      'Related to': ['[[topic/software-development]]'],
    },
    icon: null,
    color: null,
    order: null,
  },
  {
    path: '/Users/luca/Laputa/note/facebook-ads-strategy.md',
    filename: 'facebook-ads-strategy.md',
    title: 'Facebook Ads Strategy',
    isA: 'Note',
    aliases: [],
    belongsTo: ['[[project/26q1-laputa-app]]'],
    relatedTo: ['[[topic/growth]]'],
    status: null,
    owner: null,
    cadence: null,
    archived: false,
    trashed: false,
    trashedAt: null,
    modifiedAt: 1700000000,
    createdAt: null,
    fileSize: 847,
    snippet: 'Lookalike audiences convert 3x better.',
    relationships: {
      'Belongs to': ['[[project/26q1-laputa-app]]'],
      'Related to': ['[[topic/growth]]'],
    },
    icon: null,
    color: null,
    order: null,
  },
  {
    path: '/Users/luca/Laputa/person/matteo-cellini.md',
    filename: 'matteo-cellini.md',
    title: 'Matteo Cellini',
    isA: 'Person',
    aliases: [],
    belongsTo: [],
    relatedTo: [],
    status: null,
    owner: null,
    cadence: null,
    archived: false,
    trashed: false,
    trashedAt: null,
    modifiedAt: 1700000000,
    createdAt: null,
    fileSize: 320,
    snippet: 'Sponsorship manager.',
    relationships: {},
    icon: null,
    color: null,
    order: null,
  },
  {
    path: '/Users/luca/Laputa/event/2026-02-14-kickoff.md',
    filename: '2026-02-14-kickoff.md',
    title: 'Kickoff Meeting',
    isA: 'Event',
    aliases: [],
    belongsTo: [],
    relatedTo: [],
    status: null,
    owner: null,
    cadence: null,
    archived: false,
    trashed: false,
    trashedAt: null,
    modifiedAt: 1700000000,
    createdAt: null,
    fileSize: 512,
    snippet: 'Project kickoff meeting notes.',
    relationships: {},
    icon: null,
    color: null,
    order: null,
  },
  {
    path: '/Users/luca/Laputa/topic/software-development.md',
    filename: 'software-development.md',
    title: 'Software Development',
    isA: 'Topic',
    aliases: [],
    belongsTo: [],
    relatedTo: [],
    status: null,
    owner: null,
    cadence: null,
    archived: false,
    trashed: false,
    trashedAt: null,
    modifiedAt: 1700000000,
    createdAt: null,
    fileSize: 256,
    snippet: 'Frontend, backend, and systems programming.',
    relationships: {},
    icon: null,
    color: null,
    order: null,
  },
]

describe('NoteList', () => {
  it('shows empty state when no entries', () => {
    render(<NoteList entries={[]} selection={allSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />)
    expect(screen.getByText('No notes found')).toBeInTheDocument()
  })

  it('renders all entries with All Notes filter', () => {
    render(<NoteList entries={mockEntries} selection={allSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />)
    expect(screen.getByText('Build Laputa App')).toBeInTheDocument()
    expect(screen.getByText('Facebook Ads Strategy')).toBeInTheDocument()
    expect(screen.getByText('Matteo Cellini')).toBeInTheDocument()
  })

  it('filters by People (section group)', () => {
    render(<NoteList entries={mockEntries} selection={{ kind: 'sectionGroup', type: 'Person' }} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />)
    expect(screen.getByText('Matteo Cellini')).toBeInTheDocument()
    expect(screen.queryByText('Build Laputa App')).not.toBeInTheDocument()
  })

  it('filters by Events (section group)', () => {
    render(<NoteList entries={mockEntries} selection={{ kind: 'sectionGroup', type: 'Event' }} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />)
    expect(screen.getByText('Kickoff Meeting')).toBeInTheDocument()
    expect(screen.queryByText('Build Laputa App')).not.toBeInTheDocument()
  })

  it('filters by section group type', () => {
    render(<NoteList entries={mockEntries} selection={{ kind: 'sectionGroup', type: 'Project' }} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />)
    expect(screen.getByText('Build Laputa App')).toBeInTheDocument()
    expect(screen.queryByText('Matteo Cellini')).not.toBeInTheDocument()
  })

  it('shows entity pinned at top with grouped children', () => {
    render(
      <NoteList entries={mockEntries} selection={{ kind: 'entity', entry: mockEntries[0] }} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />
    )
    // Entity title appears in header and pinned card
    expect(screen.getAllByText('Build Laputa App').length).toBeGreaterThanOrEqual(1)
    // Child entry in "Children" group
    expect(screen.getByText('Facebook Ads Strategy')).toBeInTheDocument()
    // Unrelated entries not shown
    expect(screen.queryByText('Matteo Cellini')).not.toBeInTheDocument()
    // Group headers shown
    expect(screen.getByText('Children')).toBeInTheDocument()
    expect(screen.getByText('Related to')).toBeInTheDocument()
  })

  it('filters by topic (relatedTo references)', () => {
    render(
      <NoteList entries={mockEntries} selection={{ kind: 'topic', entry: mockEntries[4] }} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />
    )
    // Build Laputa App has relatedTo: [[topic/software-development]]
    expect(screen.getByText('Build Laputa App')).toBeInTheDocument()
    expect(screen.queryByText('Facebook Ads Strategy')).not.toBeInTheDocument()
  })

  it('shows search input when search icon is clicked', () => {
    render(<NoteList entries={mockEntries} selection={allSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />)
    // Search is hidden by default
    expect(screen.queryByPlaceholderText('Search notes...')).not.toBeInTheDocument()
    // Click search icon to show it
    fireEvent.click(screen.getByTitle('Search notes'))
    expect(screen.getByPlaceholderText('Search notes...')).toBeInTheDocument()
  })

  it('filters by search query (case-insensitive substring)', () => {
    render(<NoteList entries={mockEntries} selection={allSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />)
    // Open search
    fireEvent.click(screen.getByTitle('Search notes'))
    const input = screen.getByPlaceholderText('Search notes...')
    fireEvent.change(input, { 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', () => {
    const entriesWithDifferentDates: VaultEntry[] = [
      { ...mockEntries[0], modifiedAt: 1000, title: 'Oldest' },
      { ...mockEntries[1], modifiedAt: 3000, title: 'Newest', path: '/p2' },
      { ...mockEntries[2], modifiedAt: 2000, title: 'Middle', path: '/p3' },
    ]
    render(<NoteList entries={entriesWithDifferentDates} selection={allSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />)
    const titles = screen.getAllByText(/Oldest|Newest|Middle/)
    const titleTexts = titles.map((el) => el.textContent)
    expect(titleTexts).toEqual(['Newest', 'Middle', 'Oldest'])
  })

  it('does not render type badge or status on note items', () => {
    render(<NoteList entries={mockEntries} selection={allSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />)
    // Type badges like "Project", "Note" etc. should not appear as separate badge elements
    // The word "Project" should only appear in the ALL CAPS pill "PROJECTS 1", not as a standalone badge
    expect(screen.queryByText('Active')).not.toBeInTheDocument()
  })

  it('header shows search and plus icons instead of count badge', () => {
    render(<NoteList entries={mockEntries} selection={allSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />)
    expect(screen.getByTitle('Search notes')).toBeInTheDocument()
    expect(screen.getByTitle('Create new note')).toBeInTheDocument()
  })

  it('context view shows backlinks from allContent', () => {
    const allContent = {
      [mockEntries[2].path]: 'Met with [[project/26q1-laputa-app]] team.',
    }
    render(
      <NoteList entries={mockEntries} selection={{ kind: 'entity', entry: mockEntries[0] }} selectedNote={null} onSelectNote={noopSelect} allContent={allContent} onCreateNote={vi.fn()} />
    )
    expect(screen.getByText('Backlinks')).toBeInTheDocument()
    expect(screen.getByText('Matteo Cellini')).toBeInTheDocument()
  })

  it('context view collapses and expands groups', () => {
    render(
      <NoteList entries={mockEntries} selection={{ kind: 'entity', entry: mockEntries[0] }} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />
    )
    // Children group is expanded by default
    expect(screen.getByText('Facebook Ads Strategy')).toBeInTheDocument()
    // Click the Children header to collapse
    fireEvent.click(screen.getByText('Children'))
    // Items should be hidden
    expect(screen.queryByText('Facebook Ads Strategy')).not.toBeInTheDocument()
    // Click again to expand
    fireEvent.click(screen.getByText('Children'))
    expect(screen.getByText('Facebook Ads Strategy')).toBeInTheDocument()
  })

  it('context view shows prominent card with entity snippet', () => {
    render(
      <NoteList entries={mockEntries} selection={{ kind: 'entity', entry: mockEntries[0] }} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />
    )
    // Snippet appears in the prominent card
    expect(screen.getByText('Build a personal knowledge management app.')).toBeInTheDocument()
  })
})

describe('getSortComparator', () => {
  const makeEntry = (overrides: Partial<VaultEntry>): VaultEntry => ({
    path: '/test.md',
    filename: 'test.md',
    title: 'Test',
    isA: null,
    aliases: [],
    belongsTo: [],
    relatedTo: [],
    status: null,
    owner: null,
    cadence: null,
    archived: false,
    trashed: false,
    trashedAt: null,
    modifiedAt: null,
    createdAt: null,
    fileSize: 100,
    snippet: '',
    relationships: {},
    icon: null,
    color: null,
    order: null,
    ...overrides,
  })

  it('sorts by modified date descending', () => {
    const a = makeEntry({ title: 'A', modifiedAt: 1000 })
    const b = makeEntry({ title: 'B', modifiedAt: 3000 })
    const c = makeEntry({ title: 'C', modifiedAt: 2000 })
    const sorted = [a, b, c].sort(getSortComparator('modified'))
    expect(sorted.map((e) => e.title)).toEqual(['B', 'C', 'A'])
  })

  it('sorts by created date descending', () => {
    const a = makeEntry({ title: 'A', createdAt: 3000, modifiedAt: 1000 })
    const b = makeEntry({ title: 'B', createdAt: 1000, modifiedAt: 3000 })
    const c = makeEntry({ title: 'C', createdAt: 2000, modifiedAt: 2000 })
    const sorted = [a, b, c].sort(getSortComparator('created'))
    expect(sorted.map((e) => e.title)).toEqual(['A', 'C', 'B'])
  })

  it('sorts by created date, falling back to modifiedAt when createdAt is null', () => {
    const a = makeEntry({ title: 'A', createdAt: null, modifiedAt: 5000 })
    const b = makeEntry({ title: 'B', createdAt: 2000, modifiedAt: 1000 })
    const sorted = [a, b].sort(getSortComparator('created'))
    expect(sorted.map((e) => e.title)).toEqual(['A', 'B'])
  })

  it('sorts by title alphabetically', () => {
    const a = makeEntry({ title: 'Zebra' })
    const b = makeEntry({ title: 'Alpha' })
    const c = makeEntry({ title: 'Middle' })
    const sorted = [a, b, c].sort(getSortComparator('title'))
    expect(sorted.map((e) => e.title)).toEqual(['Alpha', 'Middle', 'Zebra'])
  })

  it('sorts by status (Active > Paused > Done > null)', () => {
    const a = makeEntry({ title: 'Done', status: 'Done', modifiedAt: 1000 })
    const b = makeEntry({ title: 'Active', status: 'Active', modifiedAt: 1000 })
    const c = makeEntry({ title: 'NoStatus', status: null, modifiedAt: 1000 })
    const d = makeEntry({ title: 'Paused', status: 'Paused', modifiedAt: 1000 })
    const sorted = [a, b, c, d].sort(getSortComparator('status'))
    expect(sorted.map((e) => e.title)).toEqual(['Active', 'Paused', 'Done', 'NoStatus'])
  })

  it('sorts by status with modified date as tiebreaker', () => {
    const a = makeEntry({ title: 'OlderActive', status: 'Active', modifiedAt: 1000 })
    const b = makeEntry({ title: 'NewerActive', status: 'Active', modifiedAt: 3000 })
    const sorted = [a, b].sort(getSortComparator('status'))
    expect(sorted.map((e) => e.title)).toEqual(['NewerActive', 'OlderActive'])
  })

  it('sorts by modified date ascending when direction is asc', () => {
    const a = makeEntry({ title: 'A', modifiedAt: 1000 })
    const b = makeEntry({ title: 'B', modifiedAt: 3000 })
    const c = makeEntry({ title: 'C', modifiedAt: 2000 })
    const sorted = [a, b, c].sort(getSortComparator('modified', 'asc'))
    expect(sorted.map((e) => e.title)).toEqual(['A', 'C', 'B'])
  })

  it('sorts by title descending when direction is desc', () => {
    const a = makeEntry({ title: 'Zebra' })
    const b = makeEntry({ title: 'Alpha' })
    const c = makeEntry({ title: 'Middle' })
    const sorted = [a, b, c].sort(getSortComparator('title', 'desc'))
    expect(sorted.map((e) => e.title)).toEqual(['Zebra', 'Middle', 'Alpha'])
  })

  it('sorts by created date ascending when direction is asc', () => {
    const a = makeEntry({ title: 'A', createdAt: 3000, modifiedAt: 1000 })
    const b = makeEntry({ title: 'B', createdAt: 1000, modifiedAt: 3000 })
    const c = makeEntry({ title: 'C', createdAt: 2000, modifiedAt: 2000 })
    const sorted = [a, b, c].sort(getSortComparator('created', 'asc'))
    expect(sorted.map((e) => e.title)).toEqual(['B', 'C', 'A'])
  })

  it('sorts by status descending (null first, Done before Active)', () => {
    const a = makeEntry({ title: 'Done', status: 'Done', modifiedAt: 1000 })
    const b = makeEntry({ title: 'Active', status: 'Active', modifiedAt: 1000 })
    const c = makeEntry({ title: 'NoStatus', status: null, modifiedAt: 1000 })
    const sorted = [a, b, c].sort(getSortComparator('status', 'desc'))
    expect(sorted.map((e) => e.title)).toEqual(['NoStatus', 'Done', 'Active'])
  })
})

describe('NoteList sort controls', () => {
  beforeEach(() => {
    try { localStorage.removeItem('laputa-sort-preferences') } catch { /* noop */ }
  })

  const makeEntry = (overrides: Partial<VaultEntry>): VaultEntry => ({
    path: '/test.md',
    filename: 'test.md',
    title: 'Test',
    isA: null,
    aliases: [],
    belongsTo: [],
    relatedTo: [],
    status: null,
    owner: null,
    cadence: null,
    archived: false,
    trashed: false,
    trashedAt: null,
    modifiedAt: null,
    createdAt: null,
    fileSize: 100,
    snippet: '',
    relationships: {},
    icon: null,
    color: null,
    order: null,
    ...overrides,
  })

  it('shows sort button in note list header for flat view', () => {
    render(
      <NoteList entries={mockEntries} selection={allSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />
    )
    expect(screen.getByTestId('sort-button-__list__')).toBeInTheDocument()
  })

  it('shows sort dropdown per relationship subsection in entity view', () => {
    render(
      <NoteList entries={mockEntries} selection={{ kind: 'entity', entry: mockEntries[0] }} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />
    )
    expect(screen.getByTestId('sort-button-Children')).toBeInTheDocument()
  })

  it('opens sort menu on click and shows all options', () => {
    render(
      <NoteList entries={mockEntries} selection={allSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />
    )
    fireEvent.click(screen.getByTestId('sort-button-__list__'))
    expect(screen.getByTestId('sort-menu-__list__')).toBeInTheDocument()
    expect(screen.getByTestId('sort-option-modified')).toBeInTheDocument()
    expect(screen.getByTestId('sort-option-created')).toBeInTheDocument()
    expect(screen.getByTestId('sort-option-title')).toBeInTheDocument()
    expect(screen.getByTestId('sort-option-status')).toBeInTheDocument()
  })

  it('changes sort order when an option is selected', () => {
    const entries = [
      makeEntry({ path: '/a.md', title: 'Zebra', modifiedAt: 3000 }),
      makeEntry({ path: '/b.md', title: 'Alpha', modifiedAt: 1000 }),
      makeEntry({ path: '/c.md', title: 'Middle', modifiedAt: 2000 }),
    ]
    render(
      <NoteList entries={entries} selection={allSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />
    )
    // Default sort: by modified (Zebra first)
    let titles = screen.getAllByText(/Zebra|Alpha|Middle/).map((el) => el.textContent)
    expect(titles).toEqual(['Zebra', 'Middle', 'Alpha'])

    // Switch to title sort
    fireEvent.click(screen.getByTestId('sort-button-__list__'))
    fireEvent.click(screen.getByTestId('sort-option-title'))

    // Now should be alphabetical
    titles = screen.getAllByText(/Zebra|Alpha|Middle/).map((el) => el.textContent)
    expect(titles).toEqual(['Alpha', 'Middle', 'Zebra'])
  })

  it('closes sort menu after selecting an option', () => {
    render(
      <NoteList entries={mockEntries} selection={allSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />
    )
    fireEvent.click(screen.getByTestId('sort-button-__list__'))
    expect(screen.getByTestId('sort-menu-__list__')).toBeInTheDocument()
    fireEvent.click(screen.getByTestId('sort-option-title'))
    expect(screen.queryByTestId('sort-menu-__list__')).not.toBeInTheDocument()
  })

  it('shows direction arrows in sort dropdown menu', () => {
    render(
      <NoteList entries={mockEntries} selection={allSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />
    )
    fireEvent.click(screen.getByTestId('sort-button-__list__'))
    // Each option should have asc and desc direction buttons
    expect(screen.getByTestId('sort-dir-asc-modified')).toBeInTheDocument()
    expect(screen.getByTestId('sort-dir-desc-modified')).toBeInTheDocument()
    expect(screen.getByTestId('sort-dir-asc-title')).toBeInTheDocument()
    expect(screen.getByTestId('sort-dir-desc-title')).toBeInTheDocument()
  })

  it('reverses sort order when clicking direction arrow', () => {
    const entries = [
      makeEntry({ path: '/a.md', title: 'Zebra', modifiedAt: 3000 }),
      makeEntry({ path: '/b.md', title: 'Alpha', modifiedAt: 1000 }),
      makeEntry({ path: '/c.md', title: 'Middle', modifiedAt: 2000 }),
    ]
    render(
      <NoteList entries={entries} selection={allSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />
    )
    // Default sort: modified descending (Zebra first at 3000)
    let titles = screen.getAllByText(/Zebra|Alpha|Middle/).map((el) => el.textContent)
    expect(titles).toEqual(['Zebra', 'Middle', 'Alpha'])

    // Click the asc arrow for modified to reverse
    fireEvent.click(screen.getByTestId('sort-button-__list__'))
    fireEvent.click(screen.getByTestId('sort-dir-asc-modified'))

    // Now ascending: Alpha (1000) first
    titles = screen.getAllByText(/Zebra|Alpha|Middle/).map((el) => el.textContent)
    expect(titles).toEqual(['Alpha', 'Middle', 'Zebra'])
  })

  it('persists sort direction via saveSortPreferences', () => {
    const entries = [
      makeEntry({ path: '/a.md', title: 'Zebra', modifiedAt: 3000 }),
      makeEntry({ path: '/b.md', title: 'Alpha', modifiedAt: 1000 }),
    ]
    render(
      <NoteList entries={entries} selection={allSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />
    )
    // Select title sort with desc direction
    fireEvent.click(screen.getByTestId('sort-button-__list__'))
    fireEvent.click(screen.getByTestId('sort-dir-desc-title'))

    // Verify direction took effect: desc title means Z before A
    const titles = screen.getAllByText(/Zebra|Alpha/).map((el) => el.textContent)
    expect(titles).toEqual(['Zebra', 'Alpha'])
  })

  it('shows direction icon on the sort button that reflects current direction', () => {
    render(
      <NoteList entries={mockEntries} selection={allSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />
    )
    // Default: modified desc → should have ArrowDown icon
    expect(screen.getByTestId('sort-direction-icon-__list__')).toBeInTheDocument()

    // Switch to title (default asc) and verify icon changes
    fireEvent.click(screen.getByTestId('sort-button-__list__'))
    fireEvent.click(screen.getByTestId('sort-option-title'))
    // Title default is asc → should show ArrowUp icon
    expect(screen.getByTestId('sort-direction-icon-__list__')).toBeInTheDocument()
  })

  it('sorts relationship subsection entries when sort option changed', () => {
    // Create an entity with children that have different titles
    const parent = makeEntry({
      path: '/parent.md',
      filename: 'parent.md',
      title: 'Parent',
      isA: 'Project',
    })
    const child1 = makeEntry({
      path: '/child1.md',
      filename: 'child1.md',
      title: 'Zebra Note',
      belongsTo: ['[[parent]]'],
      modifiedAt: 3000,
    })
    const child2 = makeEntry({
      path: '/child2.md',
      filename: 'child2.md',
      title: 'Alpha Note',
      belongsTo: ['[[parent]]'],
      modifiedAt: 1000,
    })
    const entries = [parent, child1, child2]

    render(
      <NoteList entries={entries} selection={{ kind: 'entity', entry: parent }} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />
    )

    // Default sort: by modified — Zebra Note (3000) before Alpha Note (1000)
    let titles = screen.getAllByText(/Zebra Note|Alpha Note/).map((el) => el.textContent)
    expect(titles).toEqual(['Zebra Note', 'Alpha Note'])

    // Switch to title sort
    fireEvent.click(screen.getByTestId('sort-button-Children'))
    fireEvent.click(screen.getByTestId('sort-option-title'))

    // Now alphabetical: Alpha before Zebra
    titles = screen.getAllByText(/Zebra Note|Alpha Note/).map((el) => el.textContent)
    expect(titles).toEqual(['Alpha Note', 'Zebra Note'])
  })
})

// --- Trash feature tests ---

const trashedEntry: VaultEntry = {
  path: '/vault/note/old-draft.md',
  filename: 'old-draft.md',
  title: 'Old Draft Notes',
  isA: 'Note',
  aliases: [],
  belongsTo: [],
  relatedTo: [],
  status: null,
  owner: null,
  cadence: null,
  archived: false,
  trashed: true,
  trashedAt: Date.now() / 1000 - 86400 * 5,
  modifiedAt: 1700000000,
  createdAt: null,
  fileSize: 280,
  snippet: 'Some draft content that is no longer needed.',
  relationships: {},
  icon: null,
  color: null,
  order: null,
}

const expiredTrashedEntry: VaultEntry = {
  path: '/vault/note/deprecated-api.md',
  filename: 'deprecated-api.md',
  title: 'Deprecated API Notes',
  isA: 'Note',
  aliases: [],
  belongsTo: [],
  relatedTo: [],
  status: null,
  owner: null,
  cadence: null,
  archived: false,
  trashed: true,
  trashedAt: Date.now() / 1000 - 86400 * 35,
  modifiedAt: 1700000000,
  createdAt: null,
  fileSize: 190,
  snippet: 'Old API docs replaced by v2.',
  relationships: {},
  icon: null,
  color: null,
  order: null,
}

const entriesWithTrashed = [...mockEntries, trashedEntry, expiredTrashedEntry]

describe('filterEntries — trash', () => {
  it('excludes trashed entries from "all" filter', () => {
    const result = filterEntries(entriesWithTrashed, { kind: 'filter', filter: 'all' })
    expect(result.find((e) => e.title === 'Old Draft Notes')).toBeUndefined()
    expect(result.find((e) => e.title === 'Build Laputa App')).toBeDefined()
  })

  it('excludes trashed entries from section group', () => {
    const result = filterEntries(entriesWithTrashed, { kind: 'sectionGroup', type: 'Note' })
    expect(result.find((e) => e.title === 'Old Draft Notes')).toBeUndefined()
  })

  it('trash filter returns only trashed entries', () => {
    const result = filterEntries(entriesWithTrashed, { kind: 'filter', filter: 'trash' })
    expect(result).toHaveLength(2)
    expect(result.every((e) => e.trashed)).toBe(true)
  })

  it('archived filter excludes trashed entries', () => {
    const archivedAndTrashed = [
      ...mockEntries,
      { ...mockEntries[0], path: '/archived.md', archived: true, trashed: false, trashedAt: null, title: 'Archived Note' },
      trashedEntry,
    ]
    const result = filterEntries(archivedAndTrashed, { kind: 'filter', filter: 'archived' })
    expect(result.find((e) => e.title === 'Archived Note')).toBeDefined()
    expect(result.find((e) => e.title === 'Old Draft Notes')).toBeUndefined()
  })

  it('topic filter excludes trashed entries', () => {
    const topicEntry: VaultEntry = { ...mockEntries[4] } // Software Development topic
    const trashedWithTopic: VaultEntry = {
      ...trashedEntry,
      relatedTo: ['[[topic/software-development]]'],
    }
    const all = [...mockEntries, trashedWithTopic]
    const result = filterEntries(all, { kind: 'topic', entry: topicEntry })
    expect(result.find((e) => e.title === 'Old Draft Notes')).toBeUndefined()
    // Normal entry with that topic should still appear
    expect(result.find((e) => e.title === 'Build Laputa App')).toBeDefined()
  })
})

describe('NoteList — trash view', () => {
  const trashSelection: SidebarSelection = { kind: 'filter', filter: 'trash' }

  it('shows "Trash" header when trash filter is active', () => {
    render(<NoteList entries={entriesWithTrashed} selection={trashSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />)
    expect(screen.getByText('Trash')).toBeInTheDocument()
  })

  it('shows only trashed entries in trash view', () => {
    render(<NoteList entries={entriesWithTrashed} selection={trashSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />)
    expect(screen.getByText('Old Draft Notes')).toBeInTheDocument()
    expect(screen.getByText('Deprecated API Notes')).toBeInTheDocument()
    expect(screen.queryByText('Build Laputa App')).not.toBeInTheDocument()
  })

  it('shows TRASHED badge on trashed entries', () => {
    render(<NoteList entries={entriesWithTrashed} selection={trashSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />)
    const badges = screen.getAllByText('TRASHED')
    expect(badges.length).toBeGreaterThanOrEqual(1)
  })

  it('shows 30-day warning banner when expired notes exist', () => {
    render(<NoteList entries={entriesWithTrashed} selection={trashSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />)
    expect(screen.getByText('Notes in trash for 30+ days will be permanently deleted')).toBeInTheDocument()
    expect(screen.getByText(/1 note is past the 30-day retention period/)).toBeInTheDocument()
  })

  it('shows "Trash is empty" when no trashed entries', () => {
    render(<NoteList entries={mockEntries} selection={trashSelection} selectedNote={null} onSelectNote={noopSelect} allContent={{}} onCreateNote={vi.fn()} />)
    expect(screen.getByText('Trash is empty')).toBeInTheDocument()
  })
})
