import { createInjector } from '@furystack/inject'
import { createComponent, flushUpdates, initializeShadeRoot, LocationService } from '@furystack/shades'
import { usingAsync } from '@furystack/utils'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { AppBarLink } from './app-bar-link.js'

describe('AppBarLink component', () => {
  beforeEach(() => {
    document.body.innerHTML = '<div id="root"></div>'
  })

  afterEach(() => {
    document.body.innerHTML = ''
  })

  describe('rendering', () => {
    it('should render the shade-app-bar-link custom element', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <AppBarLink path="/test">Link</AppBarLink>,
        })

        await flushUpdates()

        const appBarLink = document.querySelector('shade-app-bar-link')
        expect(appBarLink).not.toBeNull()
        expect(appBarLink?.tagName.toLowerCase()).toBe('shade-app-bar-link')
      })
    })

    it('should render children through NestedRouteLink', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <AppBarLink path="/test">Test Link Text</AppBarLink>,
        })

        await flushUpdates()

        expect(document.body.innerHTML).toContain('Test Link Text')
      })
    })

    it('should render NestedRouteLink with correct href', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <AppBarLink path="/my-route">Link</AppBarLink>,
        })

        await flushUpdates()

        expect(document.body.innerHTML).toContain('href="/my-route"')
      })
    })
  })

  describe('route matching', () => {
    it('should have active class when current URL matches href', async () => {
      await usingAsync(createInjector(), async (injector) => {
        history.pushState(null, '', '/dashboard')

        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <AppBarLink path="/dashboard">Dashboard</AppBarLink>,
        })

        await flushUpdates()

        const appBarLink = document.querySelector('shade-app-bar-link')
        expect(appBarLink?.hasAttribute('data-active')).toBe(true)
      })
    })

    it('should not have active class when current URL does not match href', async () => {
      await usingAsync(createInjector(), async (injector) => {
        history.pushState(null, '', '/other-page')

        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <AppBarLink path="/dashboard">Dashboard</AppBarLink>,
        })

        await flushUpdates()

        const appBarLink = document.querySelector('shade-app-bar-link')
        expect(appBarLink?.hasAttribute('data-active')).toBe(false)
      })
    })

    it('should update active class when location changes', async () => {
      await usingAsync(createInjector(), async (injector) => {
        history.pushState(null, '', '/home')

        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <AppBarLink path="/dashboard">Dashboard</AppBarLink>,
        })

        await flushUpdates()

        const appBarLink = document.querySelector('shade-app-bar-link')
        expect(appBarLink?.hasAttribute('data-active')).toBe(false)

        // Navigate to matching route
        history.pushState(null, '', '/dashboard')
        injector.get(LocationService).updateState()

        await flushUpdates()

        expect(appBarLink?.hasAttribute('data-active')).toBe(true)
      })
    })

    it('should remove active class when navigating away', async () => {
      await usingAsync(createInjector(), async (injector) => {
        history.pushState(null, '', '/dashboard')

        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <AppBarLink path="/dashboard">Dashboard</AppBarLink>,
        })

        await flushUpdates()

        const appBarLink = document.querySelector('shade-app-bar-link')
        expect(appBarLink?.hasAttribute('data-active')).toBe(true)

        // Navigate away
        history.pushState(null, '', '/other')
        injector.get(LocationService).updateState()

        await flushUpdates()

        expect(appBarLink?.hasAttribute('data-active')).toBe(false)
      })
    })

    it('should match routes with parameters', async () => {
      await usingAsync(createInjector(), async (injector) => {
        history.pushState(null, '', '/users/123')

        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <AppBarLink path="/users/:id">Users</AppBarLink>,
        })

        await flushUpdates()

        const appBarLink = document.querySelector('shade-app-bar-link')
        expect(appBarLink?.hasAttribute('data-active')).toBe(true)
      })
    })

    it('should support routingOptions with end: false for prefix matching', async () => {
      await usingAsync(createInjector(), async (injector) => {
        history.pushState(null, '', '/admin/settings/security')

        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: (
            <AppBarLink path="/admin" routingOptions={{ end: false }}>
              Admin
            </AppBarLink>
          ),
        })

        await flushUpdates()

        const appBarLink = document.querySelector('shade-app-bar-link')
        expect(appBarLink?.hasAttribute('data-active')).toBe(true)
      })
    })

    it('should not match prefix by default (end: true)', async () => {
      await usingAsync(createInjector(), async (injector) => {
        history.pushState(null, '', '/admin/settings')

        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <AppBarLink path="/admin">Admin</AppBarLink>,
        })

        await flushUpdates()

        const appBarLink = document.querySelector('shade-app-bar-link')
        expect(appBarLink?.hasAttribute('data-active')).toBe(false)
      })
    })
  })

  describe('multiple links', () => {
    it('should only activate the matching link', async () => {
      await usingAsync(createInjector(), async (injector) => {
        history.pushState(null, '', '/settings')

        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: (
            <div>
              <AppBarLink path="/home">Home</AppBarLink>
              <AppBarLink path="/settings">Settings</AppBarLink>
              <AppBarLink path="/about">About</AppBarLink>
            </div>
          ),
        })

        await flushUpdates()

        const links = document.querySelectorAll('shade-app-bar-link')
        expect(links[0]?.hasAttribute('data-active')).toBe(false)
        expect(links[1]?.hasAttribute('data-active')).toBe(true)
        expect(links[2]?.hasAttribute('data-active')).toBe(false)
      })
    })

    it('should update all links when location changes', async () => {
      await usingAsync(createInjector(), async (injector) => {
        history.pushState(null, '', '/home')

        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: (
            <div>
              <AppBarLink path="/home">Home</AppBarLink>
              <AppBarLink path="/settings">Settings</AppBarLink>
            </div>
          ),
        })

        await flushUpdates()

        const links = document.querySelectorAll('shade-app-bar-link')
        expect(links[0]?.hasAttribute('data-active')).toBe(true)
        expect(links[1]?.hasAttribute('data-active')).toBe(false)

        // Navigate to settings
        history.pushState(null, '', '/settings')
        injector.get(LocationService).updateState()

        await flushUpdates()

        expect(links[0]?.hasAttribute('data-active')).toBe(false)
        expect(links[1]?.hasAttribute('data-active')).toBe(true)
      })
    })
  })

  describe('styling', () => {
    it('should have flex display', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <AppBarLink path="/test">Link</AppBarLink>,
        })

        await flushUpdates()

        const appBarLink = document.querySelector('shade-app-bar-link') as HTMLElement
        const computedStyle = window.getComputedStyle(appBarLink)
        expect(computedStyle.display).toBe('flex')
      })
    })

    it('should have pointer cursor', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <AppBarLink path="/test">Link</AppBarLink>,
        })

        await flushUpdates()

        const appBarLink = document.querySelector('shade-app-bar-link') as HTMLElement
        const computedStyle = window.getComputedStyle(appBarLink)
        expect(computedStyle.cursor).toBe('pointer')
      })
    })

    it('should have transition for animations', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <AppBarLink path="/test">Link</AppBarLink>,
        })

        await flushUpdates()

        const appBarLink = document.querySelector('shade-app-bar-link') as HTMLElement
        const computedStyle = window.getComputedStyle(appBarLink)
        expect(computedStyle.transition).toContain('color')
      })
    })
  })
})
