import { LitElement, PropertyValues, html, nothing, type TemplateResult } from 'lit'
import { customElement, property, state } from 'lit/decorators.js'
import type { HelpTopic, HelpExample } from './help_topic.js'
import styles from './help_panel.styles.js'
import mdStyles from '../elements/highlight/MarkdownStyles.js'

import '../md/icons/ui-icon.js'
import '../md/icon-button/ui-icon-button.js'
import '../elements/highlight/marked-highlight.js'

// Type definition for ToggleEvent (may not be available in all TypeScript versions)
interface ToggleEvent extends Event {
  readonly newState: 'open' | 'closed'
  readonly oldState: 'open' | 'closed'
}

/**
 * A panel that displays help content for a specific topic.
 * This component is used by the HelpCoordinator to show help information.
 * Uses the popover API to ensure it appears in the top layer.
 * @fires close - Dispatched when the panel is closed.
 */
@customElement('help-panel')
export class HelpPanel extends LitElement {
  static override styles = [styles, mdStyles]

  /**
   * The help topic to display in the panel.
   * This should be set by the HelpCoordinator when a help request is made.
   */
  @property({ type: Object }) accessor topic: HelpTopic | undefined
  /**
   * The element that triggered the help request.
   * This is used to position the help panel relative to the triggering element.
   * Might not always be set, depending on how the help request is triggered.
   */
  @property({ type: Object }) accessor trigger: HTMLElement | undefined
  /**
   * Whether the panel is currently opened.
   * This is controlled by the HelpCoordinator and reflects the current state of the panel.
   */
  @property({ type: Boolean, reflect: true }) accessor opened = false

  @state() private accessor isDragging = false
  @state() private accessor position = { x: 20, y: 20 }

  private dragOffset = { x: 0, y: 0 }
  /**
   * Duration of the opening/closing animation in ms.
   * This is defined in the css file.
   */
  protected animationDuration = 200

  constructor() {
    super()
    this.popover = 'manual'
  }

  protected override updated(cp: PropertyValues<this>): void {
    if (cp.has('opened')) {
      if (this.opened) {
        // @ts-expect-error This is a relatively new API, ensure your TypeScript version supports it
        this.showPopover({ source: this.trigger })
      } else {
        this.hidePopover()
      }
    }
  }

  /**
   * Handles the popover toggle event
   */
  private handlePopoverToggle = (event: Event): void => {
    const toggleEvent = event as ToggleEvent
    this.opened = toggleEvent.newState === 'open'
  }

  resetPositionAfterAnimation(): void {
    setTimeout(() => {
      this.style.left = 'auto'
      this.style.top = '20px'
      this.style.right = '20px'
      this.position = { x: 20, y: 20 }
    }, this.animationDuration + 1) // Add 1ms to ensure it runs after the transition
  }

  private renderMarkdown(md: string | undefined): TemplateResult | typeof nothing {
    if (!md) {
      return nothing
    }
    return html`<marked-highlight .markdown="${md}"><div slot="markdown-html"></div></marked-highlight>`
  }

  /**
   * Handles the close event for the help panel.
   * Dispatches a custom 'close' event and sets the opened state to false.
   * The event informs the help coordinator or any parent component that the panel has been closed.
   */
  protected handleClose(): void {
    this.dispatchEvent(new CustomEvent('close'))
    this.opened = false
  }

  /**
   * Handles the start of a drag operation.
   * Only initiates drag if not clicking on interactive elements or text content.
   */
  protected handleMouseDown(event: MouseEvent): void {
    // Don't drag if clicking on interactive elements
    const target = event.target as Element
    if (target.closest('ui-icon-button, a, button, input, textarea, [contenteditable]')) {
      return
    }

    // Only drag when clicking on header padding or non-text areas
    const clickedOnHeader = target.closest('.header')
    const clickedOnPadding =
      target.matches('.content-wrapper, .content') && this.isClickOnPadding(event, target as HTMLElement)

    if (!clickedOnHeader && !clickedOnPadding) {
      return
    }

    this.isDragging = true

    const rect = this.getBoundingClientRect()
    this.dragOffset = {
      x: event.clientX - rect.left,
      y: event.clientY - rect.top,
    }

    // Prevent text selection during drag
    event.preventDefault()

    // Add global event listeners
    document.addEventListener('mousemove', this.handleMouseMove)
    document.addEventListener('mouseup', this.handleMouseUp)

    // Add dragging class for visual feedback
    this.style.cursor = 'grabbing'
  }

  /**
   * Checks if the click is on padding area (not on text content)
   */
  private isClickOnPadding(event: MouseEvent, element: HTMLElement): boolean {
    const rect = element.getBoundingClientRect()
    const clickX = event.clientX - rect.left
    const clickY = event.clientY - rect.top

    // Check if click is in padding areas (first/last 16px of padding)
    const paddingThreshold = 16
    return (
      clickX < paddingThreshold ||
      clickX > rect.width - paddingThreshold ||
      clickY < paddingThreshold ||
      clickY > rect.height - paddingThreshold
    )
  }

  /**
   * Handles mouse movement during drag operation.
   */
  private handleMouseMove = (event: MouseEvent): void => {
    if (!this.isDragging) return

    // Calculate new position
    const newX = event.clientX - this.dragOffset.x
    const newY = event.clientY - this.dragOffset.y

    // Get viewport dimensions
    const viewportWidth = window.innerWidth
    const viewportHeight = window.innerHeight
    const rect = this.getBoundingClientRect()

    // Constrain to viewport bounds
    const constrainedX = Math.max(0, Math.min(newX, viewportWidth - rect.width))
    const constrainedY = Math.max(0, Math.min(newY, viewportHeight - rect.height))

    // Update position
    this.position = { x: constrainedX, y: constrainedY }
    this.style.left = `${constrainedX}px`
    this.style.top = `${constrainedY}px`
    this.style.right = 'auto' // Override the CSS right positioning
  }

  /**
   * Handles the end of a drag operation.
   */
  private handleMouseUp = (): void => {
    if (!this.isDragging) return

    this.isDragging = false
    this.style.cursor = ''

    // Remove global event listeners
    document.removeEventListener('mousemove', this.handleMouseMove)
    document.removeEventListener('mouseup', this.handleMouseUp)
  }

  override connectedCallback(): void {
    super.connectedCallback()
    // Set up popover behavior
    this.setAttribute('popover', 'manual')

    // Listen for popover toggle events
    this.addEventListener('toggle', this.handlePopoverToggle)
  }

  override firstUpdated(): void {
    // Set up scroll listener for scroll shadows
    const contentWrapper = this.shadowRoot?.querySelector('.content-wrapper') as HTMLElement
    if (contentWrapper) {
      // Initial scroll state check
      this.updateScrollShadows(contentWrapper)
    }
  }

  /**
   * Updates scroll shadow opacity based on scroll position
   */
  private handleScroll = (event: Event): void => {
    const element = event.target as HTMLElement
    this.updateScrollShadows(element)
  }

  private updateScrollShadows(element: HTMLElement): void {
    const { scrollTop, scrollHeight, clientHeight } = element
    const maxScroll = scrollHeight - clientHeight

    // Calculate scroll shadow opacity (0-1)
    const scrollTopOpacity = Math.min(scrollTop / 20, 1) // Fade in over first 20px
    const scrollBottomOpacity = Math.min((maxScroll - scrollTop) / 20, 1) // Fade out over last 20px

    // Only show bottom shadow if content is actually scrollable
    const bottomOpacity = maxScroll > 0 ? Math.min(scrollBottomOpacity, 1) : 0

    // Update CSS custom properties
    element.style.setProperty('--scroll-top', scrollTopOpacity.toString())
    element.style.setProperty('--scroll-bottom', bottomOpacity.toString())

    // Update host attribute for header styling
    if (scrollTop > 5) {
      this.setAttribute('data-scrolled', '')
    } else {
      this.removeAttribute('data-scrolled')
    }
  }

  override disconnectedCallback(): void {
    super.disconnectedCallback()
    // Clean up event listeners
    document.removeEventListener('mousemove', this.handleMouseMove)
    document.removeEventListener('mouseup', this.handleMouseUp)
    this.removeEventListener('toggle', this.handlePopoverToggle)
  }

  override render() {
    if (!this.topic) {
      return nothing
    }
    return html`
      <div class="header" @mousedown="${this.handleMouseDown}">
        <span class="title">${this.topic.title}</span>
        <ui-icon-button @click="${this.handleClose}" aria-label="Close help panel">
          <ui-icon>close</ui-icon>
        </ui-icon-button>
      </div>
      <div class="content-wrapper" @mousedown="${this.handleMouseDown}" @scroll="${this.handleScroll}">
        <div class="content">
          ${this.renderMarkdown(this.topic.body)} ${this.renderExamples(this.topic.examples)}
          ${this.topic.links && this.topic.links.length > 0
            ? html`
                <div class="links">
                  <h3>Related Links</h3>
                  <ul>
                    ${this.topic.links.map(
                      (link) =>
                        html`<li>
                          <a href="${link.url}" target="_blank" rel="noopener noreferrer"
                            >${link.text}
                            <ui-icon style="font-size: 1em; vertical-align: middle;">open_in_new</ui-icon></a
                          >
                        </li>`
                    )}
                  </ul>
                </div>
              `
            : nothing}
        </div>
      </div>
    `
  }

  /**
   * Renders examples section, handling both string and array formats
   */
  private renderExamples(examples: string | HelpExample[] | undefined): TemplateResult | typeof nothing {
    if (!examples) {
      return nothing
    }

    // Handle legacy string format
    if (typeof examples === 'string') {
      return html`<h3>Examples</h3>
        ${this.renderMarkdown(examples)}`
    }

    // Handle new array format
    if (Array.isArray(examples) && examples.length > 0) {
      return html`
        <h3>Examples</h3>
        ${examples.map(
          (example) => html`
            ${example.title ? html`<h4>${example.title}</h4>` : nothing} ${this.renderMarkdown(example.value)}
          `
        )}
      `
    }

    return nothing
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'help-panel': HelpPanel
  }
}
