import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { HelmetProvider } from 'react-helmet-async'
import { beforeEach, describe, expect, it, vi } from 'vitest'

import SidebarComponent from '@/components/layout/SidebarComponent'


const sidebarWidth = (element: Element | null): string =>
    (element as HTMLElement).style.getPropertyValue ('--sidebar-width')


const sidebarRoot = (container: HTMLElement): Element =>
    container.querySelector ('[style*="--sidebar-width"]')!


const sidebarTree = (props: Partial<Parameters<typeof SidebarComponent>[0]>) => (
    <HelmetProvider>
      <div data-sidebar-container>
	<SidebarComponent sidebarKey="test-sidebar" {...props}>
	  Sidebar body
	</SidebarComponent>
      </div>
    </HelmetProvider>)


const renderSidebar = (
    props: Partial<Parameters<typeof SidebarComponent>[0]> = {},
    containerWidth = 800,
) => {
  Object.defineProperty (HTMLElement.prototype, 'clientWidth', {
      configurable: true,
      get () {
	return this.hasAttribute ('data-sidebar-container') ? containerWidth : 0
      },
  })

  const rendered = render (sidebarTree (props))

  return rendered
}


describe ('SidebarComponent', () => {
  beforeEach (() => {
    Object.defineProperty (window, 'innerWidth', {
	configurable: true,
	value:        1_000,
    })

    Object.defineProperty (HTMLElement.prototype, 'setPointerCapture', {
	configurable: true,
	value:        vi.fn (),
    })
    Object.defineProperty (HTMLElement.prototype, 'releasePointerCapture', {
	configurable: true,
	value:        vi.fn (),
    })
    Object.defineProperty (HTMLElement.prototype, 'hasPointerCapture', {
	configurable: true,
	value:        vi.fn (() => true),
    })

    vi.spyOn (HTMLElement.prototype, 'setPointerCapture').mockImplementation (() => {
      ;
    })
    vi.spyOn (HTMLElement.prototype, 'releasePointerCapture').mockImplementation (() => {
      ;
    })
    vi.spyOn (HTMLElement.prototype, 'hasPointerCapture').mockReturnValue (true)
  })

  it ('uses maxWidth without applying the container main-width reserve twice', async () => {
    localStorage.setItem ('sidebar:test-sidebar:left:width', '700')
    const onWidthChange = vi.fn ()

    const { container } = renderSidebar ({ maxWidth: 700, onWidthChange }, 800)

    await waitFor (() => {
      expect (sidebarWidth (sidebarRoot (container))).toBe ('700px')
    })
    expect (onWidthChange).toHaveBeenLastCalledWith (700)
  })

  it ('resets width and removes only its storage key on double click', async () => {
    localStorage.setItem ('sidebar:test-sidebar:right:width', '420')
    localStorage.setItem ('sidebar:other-sidebar:right:width', '480')

    const { container } = renderSidebar ({ side: 'right' }, 900)

    await waitFor (() => {
      expect (sidebarWidth (sidebarRoot (container))).toBe ('420px')
    })

    fireEvent.doubleClick (screen.getByRole ('separator', { name: 'サイドバー幅' }))

    await waitFor (() => {
      expect (sidebarWidth (sidebarRoot (container))).toBe ('256px')
    })
    expect (localStorage.getItem ('sidebar:test-sidebar:right:width')).toBeNull ()
    expect (localStorage.getItem ('sidebar:other-sidebar:right:width')).toBe ('480')
  })

  it ('clamps current width on maxWidth changes without re-reading storage', async () => {
    localStorage.setItem ('sidebar:test-sidebar:left:width', '420')

    const { container, rerender } = renderSidebar ({ maxWidth: 420 }, 900)

    await waitFor (() => {
      expect (sidebarWidth (sidebarRoot (container))).toBe ('420px')
    })

    rerender (sidebarTree ({ maxWidth: 300 }))

    await waitFor (() => {
      expect (sidebarWidth (sidebarRoot (container))).toBe ('300px')
    })

    rerender (sidebarTree ({ maxWidth: 500 }))

    await waitFor (() => {
      expect (sidebarWidth (sidebarRoot (container))).toBe ('300px')
    })
  })
})
