import { classMap } from 'lit/directives/class-map.js'
import { customElement, property, state } from 'lit/decorators.js'
import { html, nothing, PropertyValues } from 'lit'
import { PktElement } from '@/base-elements/element'
import { PktSlotController } from '@/controllers/pkt-slot-controller'
import { ref } from 'lit/directives/ref.js'
import { Ref, createRef } from 'lit/directives/ref.js'
import { TPktSize } from '@/types/size'
import specs from 'componentSpecs/modal.json'
import '@/components/icon'

export interface IPktModal {
  headingText?: string
  removePadding?: boolean
  hideCloseButton?: boolean
  closeOnBackdropClick?: boolean
  closeButtonSkin?: 'blue' | 'yellow-filled'
  size?: TPktSize
  variant?: 'dialog' | 'drawer'
  drawerPosition?: 'left' | 'right'
  transparentBackdrop?: boolean
}

@customElement('pkt-modal')
export class PktModal extends PktElement implements IPktModal {
  // Public properties"
  @property({ type: String }) headingText?: string = ''
  @property({ type: Boolean }) removePadding?: boolean = false
  @property({ type: Boolean }) hideCloseButton?: boolean = specs.props.hideCloseButton.default
  @property({ type: Boolean })
  closeOnBackdropClick?: boolean = specs.props.closeOnBackdropClick.default
  @property({ type: String }) closeButtonSkin?: 'blue' | 'yellow-filled' = 'blue'
  @property({ type: String }) size?: TPktSize = specs.props.size.default as TPktSize
  @property({ type: String }) variant?: 'dialog' | 'drawer' = 'dialog'
  @property({ type: String }) drawerPosition?: 'left' | 'right' = 'right'
  @property({ type: Boolean }) transparentBackdrop?: boolean = false

  defaultSlot: Ref<HTMLElement> = createRef()
  dialogRef: Ref<HTMLDialogElement> = createRef()

  @state() _isOpen: boolean = false

  constructor() {
    super()
    this.slotController = new PktSlotController(this, this.defaultSlot)
    this._isOpen = false
  }

  async connectedCallback(): Promise<void> {
    super.connectedCallback()
    document.addEventListener('keydown', this.handleKeyDown.bind(this))
    document.addEventListener('click', this.handleBackdropClick.bind(this))
  }

  disconnectedCallback(): void {
    super.disconnectedCallback()
    document.removeEventListener('keydown', this.handleKeyDown)
    document.removeEventListener('click', this.handleBackdropClick)
  }

  protected async firstUpdated(_changedProperties: PropertyValues): Promise<void> {
    super.firstUpdated(_changedProperties)
    if (this.dialogRef.value && !window.HTMLDialogElement && !this.dialogRef.value.showModal) {
      if ('document' in window && 'createElement' in document) {
        const dialogPolyfill = await import('dialog-polyfill').then((module) => module.default)
        dialogPolyfill.registerDialog(this.dialogRef.value)
      }
      this.dialogRef.value.addEventListener('close', () => {
        this.close(new Event('close'), true)
      })
    }
  }

  // P R I V A T E    M E T H O D S
  private handleKeyDown(event: KeyboardEvent) {
    if (event.key === 'Escape') {
      this.close(event)
    }
  }

  private handleBackdropClick(event: MouseEvent) {
    if (this.closeOnBackdropClick && event.target === this.dialogRef?.value) {
      this.close(event)
    }
  }

  private isElementInViewport(element: HTMLElement): boolean {
    const rect = element.getBoundingClientRect()
    return (
      rect.top >= 0 &&
      rect.left >= 0 &&
      rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
      rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    )
  }

  // P U B L I C    M E T H O D S
  public close = (event: Event, bypassNativeClose: boolean = false) => {
    if (!this._isOpen) return
    this._isOpen = false

    document.body.classList.remove('pkt-modal--open')

    // Scroll to the opener element if it's not in the viewport
    const openerElement = document.activeElement as HTMLElement
    if (openerElement && !this.isElementInViewport(openerElement)) {
      openerElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
    }
    // Dispatch close event
    this.dispatchEvent(
      new CustomEvent('close', { detail: { origin: event }, bubbles: true, composed: true }),
    )
    if (!bypassNativeClose) this.dialogRef.value?.close()

    this.requestUpdate()
  }

  public showModal = (event: Event | null = null) => {
    this._isOpen = true
    this.dialogRef.value?.showModal()
    const modal = document.querySelector('.pkt-modal')

    // Prevent the first focusable child from being auto-focused
    requestAnimationFrame(() => {
      if (this.dialogRef.value) {
        this.dialogRef.value?.focus()
      }
    })

    if (modal) {
      document.body.classList.add('pkt-modal--open')
    }
    this.dispatchEvent(
      new CustomEvent('showModal', { detail: { origin: event }, bubbles: true, composed: true }),
    )
    this.requestUpdate()
  }

  render() {
    const classes = {
      'pkt-modal': true,
      'pkt-modal--removePadding': this.removePadding ?? false,
      'pkt-modal--noHeadingText': this.headingText === '' || this.headingText === undefined,
      'pkt-modal--noShadow': this.closeButtonSkin === 'yellow-filled',
      'pkt-modal--transparentBackdrop': this.transparentBackdrop ?? false,
      [`pkt-modal--${this.size}`]: this.size !== undefined,
      [`pkt-modal__${this.variant}`]: this.variant !== undefined,
      [`pkt-modal__drawer--${this.drawerPosition}`]: this.variant === 'drawer',
    }

    const headingClasses = {
      'pkt-modal__headingText': true,
      'pkt-txt-24': true,
    }

    const contentClasses = {
      'pkt-modal__content': true,
      'pkt-txt-18-light': true,
    }

    const isCloseButtonSkinDefault = this.closeButtonSkin === 'blue'
    const closeButtonClasses = {
      'pkt-modal__closeButton': true,
      [`pkt-modal__closeButton--${this.closeButtonSkin}`]: true,
    }

    const buttonClasses = {
      'pkt-btn': true,
      [`pkt-btn--${isCloseButtonSkinDefault ? 'tertiary' : 'primary'}`]: true,
      'pkt-btn--icon-only': true,
      'pkt-btn--medium': true,
    }

    return html`
      <dialog
        class=${classMap(classes)}
        ${ref(this.dialogRef)}
        aria-labelledby="pkt-modal__headingText"
        aria-describedby="pkt-modal__content"
        @close=${(event: Event) => this.close(event, true)}
      >
        <div class="pkt-modal__wrapper">
          ${this.headingText || !this.hideCloseButton
            ? html`<div class="pkt-modal__header">
                <div class="pkt-modal__header-background"></div>
                ${this.headingText
                  ? html`<h1 id="pkt-modal__headingText" class=${classMap(headingClasses)}>
                      ${this.headingText}
                    </h1>`
                  : html`<div class="pkt-modal__headingText"></div>`}
                ${!this.hideCloseButton
                  ? html`<div class="${classMap(closeButtonClasses)}">
                      <pkt-button
                        @click=${(event: Event) => this.close(event)}
                        class=${classMap(buttonClasses)}
                        aria-label="close"
                        iconname="close"
                        variant="icon-only"
                      >
                        Lukk
                      </pkt-button>
                    </div>`
                  : html`<div class="pkt-modal__noCloseButton"></div>`}
              </div>`
            : nothing}
          <div class="pkt-modal__container">
            <div
              id="pkt-modal__content"
              class=${classMap(contentClasses)}
              ${ref(this.defaultSlot)}
            ></div>
          </div>
        </div>
      </dialog>
    `
  }
}
