/**
 * Extended test utilities for the UI component library.
 * Provides Jest-like matchers and additional testing helpers.
 */

import { expect } from '@open-wc/testing'

/**
 * Custom matchers for better test assertions
 */
export const customMatchers = {
  /**
   * Check if an element has specific CSS classes
   */
  toHaveClasses(received: Element, ...classes: string[]) {
    const classList = Array.from(received.classList)
    const missing = classes.filter((cls) => !classList.includes(cls))

    if (missing.length === 0) {
      return {
        message: () => `Expected element not to have classes: ${classes.join(', ')}`,
        pass: true,
      }
    } else {
      return {
        message: () => `Expected element to have classes: ${missing.join(', ')}`,
        pass: false,
      }
    }
  },

  /**
   * Check if an element is visible (not hidden by CSS)
   */
  toBeVisible(received: Element) {
    const style = window.getComputedStyle(received)
    const isVisible = style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'

    if (isVisible) {
      return {
        message: () => 'Expected element to be hidden',
        pass: true,
      }
    } else {
      return {
        message: () => 'Expected element to be visible',
        pass: false,
      }
    }
  },

  /**
   * Check if an element has a specific attribute value
   */
  toHaveAttribute(received: Element, attribute: string, value?: string) {
    const hasAttribute = received.hasAttribute(attribute)

    if (!hasAttribute) {
      return {
        message: () => `Expected element to have attribute "${attribute}"`,
        pass: false,
      }
    }

    if (value !== undefined) {
      const actualValue = received.getAttribute(attribute)
      if (actualValue !== value) {
        return {
          message: () => `Expected attribute "${attribute}" to be "${value}", but got "${actualValue}"`,
          pass: false,
        }
      }
    }

    return {
      message: () => `Expected element not to have attribute "${attribute}"${value ? ` with value "${value}"` : ''}`,
      pass: true,
    }
  },

  /**
   * Helper to demonstrate proper DOM element comparison
   * Note: Always use assert.dom.equal() for DOM element comparison, not assert.equal()
   */
  domElementsEqual(actual: Element, expected: Element): boolean {
    // This is just a helper - in actual tests, use assert.dom.equal(actual, expected)
    return actual === expected && actual.isEqualNode(expected)
  },
}

/**
 * Test factory functions for common test scenarios
 */
export const testFactory = {
  /**
   * Create a test suite for a custom element
   */
  createElementTestSuite(elementName: string, importPath: string) {
    return {
      async setup() {
        await import(importPath)
      },

      async createElement(attributes: Record<string, string> = {}, content = '') {
        const attrs = Object.entries(attributes)
          .map(([key, value]) => `${key}="${value}"`)
          .join(' ')

        const element = document.createElement('div')
        element.innerHTML = `<${elementName} ${attrs}>${content}</${elementName}>`
        document.body.appendChild(element)

        return element.firstElementChild as HTMLElement
      },

      cleanup() {
        document.body.innerHTML = ''
      },
    }
  },

  /**
   * Create a test data factory
   */
  createDataFactory<T>(defaults: T) {
    return (overrides: Partial<T> = {}): T => ({
      ...defaults,
      ...overrides,
    })
  },
}

/**
 * Async test helpers
 */
export const asyncHelpers = {
  /**
   * Wait for multiple conditions to be true
   */
  async waitForAll(conditions: (() => boolean)[], timeout = 5000): Promise<void> {
    const promises = conditions.map((condition) => window.testUtils.waitForCondition(condition, timeout))
    await Promise.all(promises)
  },

  /**
   * Wait for any of multiple conditions to be true
   */
  async waitForAny(conditions: (() => boolean)[], timeout = 5000): Promise<number> {
    return new Promise((resolve, reject) => {
      const startTime = Date.now()
      let resolved = false

      function checkConditions() {
        if (resolved) return

        for (let i = 0; i < conditions.length; i++) {
          try {
            if (conditions[i]()) {
              resolved = true
              resolve(i)
              return
            }
          } catch {
            // Continue checking other conditions
          }
        }

        if (Date.now() - startTime > timeout) {
          resolved = true
          reject(new Error(`None of the conditions met within ${timeout}ms`))
          return
        }

        setTimeout(checkConditions, 50)
      }

      checkConditions()
    })
  },

  /**
   * Retry an async operation with exponential backoff
   */
  async retry<T>(operation: () => Promise<T>, maxAttempts = 3, baseDelay = 100): Promise<T> {
    let lastError: Error

    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      try {
        return await operation()
      } catch (error) {
        lastError = error as Error

        if (attempt === maxAttempts) {
          throw lastError
        }

        const delay = baseDelay * Math.pow(2, attempt - 1)
        await new Promise((resolve) => setTimeout(resolve, delay))
      }
    }

    throw lastError!
  },
}

/**
 * Performance testing utilities
 */
export const performanceHelpers = {
  /**
   * Measure the execution time of a function
   */
  async measureTime<T>(fn: () => Promise<T> | T): Promise<{ result: T; duration: number }> {
    const start = performance.now()
    const result = await fn()
    const duration = performance.now() - start

    return { result, duration }
  },

  /**
   * Assert that an operation completes within a time limit
   */
  async expectWithinTime<T>(operation: () => Promise<T> | T, maxDuration: number, message?: string): Promise<T> {
    const { result, duration } = await this.measureTime(operation)

    if (duration > maxDuration) {
      const errorMessage = message || `Operation took ${duration.toFixed(2)}ms, expected < ${maxDuration}ms`
      throw new Error(errorMessage)
    }

    return result
  },
}

// Export all utilities
export { expect }
export default {
  customMatchers,
  testFactory,
  asyncHelpers,
  performanceHelpers,
}
