import { createInjector } from '@furystack/inject'
import { createComponent, initializeShadeRoot, Shade } from '@furystack/shades'
import { ObservableValue, sleepAsync, usingAsync } from '@furystack/utils'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { ThemeProviderService } from '../services/theme-provider-service.js'
import { LinearProgress } from './linear-progress.js'

const ProgressWrapper = Shade<{ obs: ObservableValue<number> }>({
  customElementName: 'test-linear-progress-wrapper',
  render: ({ props, useObservable }) => {
    const [value] = useObservable('value', props.obs)
    return <LinearProgress variant="determinate" value={value} />
  },
})

describe('LinearProgress', () => {
  let originalAnimate: typeof Element.prototype.animate

  beforeEach(() => {
    document.body.innerHTML = '<div id="root"></div>'
    originalAnimate = Element.prototype.animate

    Element.prototype.animate = vi.fn(
      (_keyframes: Keyframe[] | PropertyIndexedKeyframes | null, _options?: number | KeyframeAnimationOptions) => {
        const mockAnimation = {
          onfinish: null as ((event: AnimationPlaybackEvent) => void) | null,
          oncancel: null as ((event: AnimationPlaybackEvent) => void) | null,
          cancel: vi.fn(),
          play: vi.fn(),
          pause: vi.fn(),
          finish: vi.fn(),
          addEventListener: vi.fn(),
          removeEventListener: vi.fn(),
        }
        return mockAnimation as unknown as Animation
      },
    )
  })

  afterEach(() => {
    document.body.innerHTML = ''
    Element.prototype.animate = originalAnimate
    vi.restoreAllMocks()
  })

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

      initializeShadeRoot({
        injector,
        rootElement,
        jsxElement: <LinearProgress />,
      })

      await sleepAsync(50)

      const el = document.querySelector('shade-linear-progress')
      expect(el).not.toBeNull()
    })
  })

  it('should render the progress bar element', async () => {
    await usingAsync(createInjector(), async (injector) => {
      const rootElement = document.getElementById('root') as HTMLDivElement

      initializeShadeRoot({
        injector,
        rootElement,
        jsxElement: <LinearProgress />,
      })

      await sleepAsync(50)

      const bar = document.querySelector('shade-linear-progress .progress-bar')
      expect(bar).not.toBeNull()
    })
  })

  it('should set role="progressbar"', async () => {
    await usingAsync(createInjector(), async (injector) => {
      const rootElement = document.getElementById('root') as HTMLDivElement

      initializeShadeRoot({
        injector,
        rootElement,
        jsxElement: <LinearProgress />,
      })

      await sleepAsync(50)

      const el = document.querySelector('shade-linear-progress') as HTMLElement
      expect(el.getAttribute('role')).toBe('progressbar')
    })
  })

  describe('determinate variant', () => {
    it('should set aria-valuenow for determinate variant', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <LinearProgress variant="determinate" value={50} />,
        })

        await sleepAsync(50)

        const el = document.querySelector('shade-linear-progress') as HTMLElement
        expect(el.getAttribute('aria-valuenow')).toBe('50')
        expect(el.getAttribute('aria-valuemin')).toBe('0')
        expect(el.getAttribute('aria-valuemax')).toBe('100')
      })
    })

    it('should set bar width based on value', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <LinearProgress variant="determinate" value={75} />,
        })

        await sleepAsync(50)

        const bar = document.querySelector('shade-linear-progress .progress-bar') as HTMLElement
        expect(bar.style.width).toBe('75%')
      })
    })

    it('should clamp value to 0-100 range', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <LinearProgress variant="determinate" value={150} />,
        })

        await sleepAsync(50)

        const bar = document.querySelector('shade-linear-progress .progress-bar') as HTMLElement
        expect(bar.style.width).toBe('100%')
      })
    })

    it('should clamp negative values to 0', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <LinearProgress variant="determinate" value={-20} />,
        })

        await sleepAsync(50)

        const bar = document.querySelector('shade-linear-progress .progress-bar') as HTMLElement
        expect(bar.style.width).toBe('0%')
      })
    })

    it('should update bar width when value prop changes', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement
        const obs = new ObservableValue(20)

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <ProgressWrapper obs={obs} />,
        })

        await sleepAsync(50)

        const bar = document.querySelector('shade-linear-progress .progress-bar') as HTMLElement
        expect(bar.style.width).toBe('20%')

        obs.setValue(80)
        await sleepAsync(50)

        expect(bar.style.width).toBe('80%')
      })
    })

    it('should update aria-valuenow when value prop changes', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement
        const obs = new ObservableValue(30)

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <ProgressWrapper obs={obs} />,
        })

        await sleepAsync(50)

        const el = document.querySelector('shade-linear-progress') as HTMLElement
        expect(el.getAttribute('aria-valuenow')).toBe('30')

        obs.setValue(90)
        await sleepAsync(50)

        expect(el.getAttribute('aria-valuenow')).toBe('90')
      })
    })
  })

  describe('indeterminate variant', () => {
    it('should default to indeterminate variant', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <LinearProgress />,
        })

        await sleepAsync(50)

        const bar = document.querySelector('shade-linear-progress .progress-bar') as HTMLElement
        expect(bar.hasAttribute('data-indeterminate')).toBe(true)
      })
    })

    it('should not set aria-valuenow for indeterminate variant', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <LinearProgress />,
        })

        await sleepAsync(50)

        const el = document.querySelector('shade-linear-progress') as HTMLElement
        expect(el.hasAttribute('aria-valuenow')).toBe(false)
      })
    })
  })

  describe('size', () => {
    it('should set data-size="small" when size is small', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <LinearProgress size="small" />,
        })

        await sleepAsync(50)

        const el = document.querySelector('shade-linear-progress') as HTMLElement
        expect(el.getAttribute('data-size')).toBe('small')
      })
    })

    it('should not set data-size when size is medium (default)', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <LinearProgress />,
        })

        await sleepAsync(50)

        const el = document.querySelector('shade-linear-progress') as HTMLElement
        expect(el.hasAttribute('data-size')).toBe(false)
      })
    })
  })

  describe('theme integration', () => {
    it('should set CSS color variable from theme', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <LinearProgress />,
        })

        await sleepAsync(50)

        const el = document.querySelector('shade-linear-progress') as HTMLElement
        const themeService = injector.get(ThemeProviderService)
        expect(el.style.getPropertyValue('--progress-color')).toBe(themeService.theme.palette.primary.main)
      })
    })

    it('should use custom color from color prop', async () => {
      await usingAsync(createInjector(), async (injector) => {
        const rootElement = document.getElementById('root') as HTMLDivElement

        initializeShadeRoot({
          injector,
          rootElement,
          jsxElement: <LinearProgress color="error" />,
        })

        await sleepAsync(50)

        const el = document.querySelector('shade-linear-progress') as HTMLElement
        const themeService = injector.get(ThemeProviderService)
        expect(el.style.getPropertyValue('--progress-color')).toBe(themeService.theme.palette.error.main)
      })
    })
  })
})
