import { html, TemplateResult, CSSResult } from 'lit'
import { RenderableMixin } from '../mixins/RenderableMixin.js'
import reactive from '../decorators/reactive.js'
import styles from './DemoStyles.js'
import { RouteMixin } from '../mixins/RouteMixin.js'
import * as MonacoLoader from '../monaco/loader.js'
import typography from '../styles/m3/typography.module.js'
import surface from '../styles/m3/surface.module.js'
import type CheckboxElement from '../md/checkbox/internals/CheckboxElement.js'
import { adaptStatic } from '../decorators/styles.js'

import '../md/icons/ui-icon.js'
import '../md/button/ui-button.js'

const Base = RenderableMixin(RouteMixin(EventTarget))

/**
 * A base class for demo pages in the API Client ecosystem.
 */
export abstract class DemoPage extends Base {
  @adaptStatic
  static get styles(): CSSResult[] {
    return [typography, styles, surface]
  }

  /**
   * Component name rendered in the header section.
   */
  @reactive() accessor componentName = ''

  /**
   * Determines whether the initial render had run and the `firstRender()`
   * function was called.
   */
  override accessor firstRendered = false

  /**
   * Whether or not the dark theme is active
   */
  @reactive() accessor darkThemeActive = false

  /**
   * For some demo pages, whether the user is authenticated in the store.
   */
  authenticated = false

  constructor() {
    super()
    this.handleMediaQuery = this.handleMediaQuery.bind(this)
    this.initMediaQueries()

    document.body.classList.add('demo')
  }

  /**
   * Initializes media queries and observers.
   */
  initMediaQueries(): void {
    const matcher = window.matchMedia('(prefers-color-scheme: dark)')
    if (matcher.matches) {
      this.darkThemeActive = true
    }
    this.themeActiveCallback()
    matcher.addEventListener('change', this.handleMediaQuery)
  }

  protected handleMediaQuery(e: MediaQueryListEvent): void {
    this.darkThemeActive = e.matches
    this.themeActiveCallback()
  }

  /**
   * The callback is called when the `darkThemeActive` property change.
   * Overwrite this function to change the theme of the demo page.
   *
   * Note, this is not called by the `darkThemeActive` setter. This is called during the
   * initialization and then when the media query event is dispatched.
   * Call this manually when changing this value in the child page.
   */
  protected themeActiveCallback(): void {
    const { darkThemeActive } = this
    if (darkThemeActive) {
      this.activateTheme('dark')
    } else {
      this.activateTheme('light')
    }
  }

  protected activateTheme(type: 'light' | 'dark'): void {
    const root = document.querySelector('html')
    if (!root) {
      return
    }
    const { classList } = root
    // clear all themes
    classList.forEach((value) => {
      if (value.startsWith('theme-')) {
        classList.remove(value)
      }
    })
    const name = `theme-${type}`
    classList.add(name)
  }

  /**
   * A handler for the `change` event for an element that has `checked` and `name` properties.
   * This can be used with `ui-switch`, `ui-checkbox`, and `checkbox` elements.
   *
   * The `name` should correspond to a variable name to be set. The set value is the value
   * of `checked` property read from the event's target.
   */
  protected _toggleBooleanOption(e: Event): void {
    const { name, checked } = e.target as CheckboxElement
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    ;(this as any)[name as string] = checked
  }

  async loadMonaco(): Promise<void> {
    const base = new URL('/node_modules/monaco-editor/', window.location.href).toString()
    MonacoLoader.createEnvironment(base)
    await MonacoLoader.loadMonaco(base)
    await MonacoLoader.monacoReady()
  }

  /**
   * The page render function. Usually you don't need to use it.
   * It renders the header template, main section, and the content.
   */
  override pageTemplate(): TemplateResult {
    return html` ${this.headerTemplate()} ${this.navigationTemplate()}
      <main>${this.contentTemplate()}</main>`
  }

  /**
   * Call this on the top of the `render()` method to render demo navigation
   * @returns HTML template for demo header
   */
  headerTemplate(): TemplateResult {
    const { componentName } = this
    return html` <header>${componentName ? html`<h1 class="api-title">${componentName}</h1>` : ''}</header>`
  }

  navigationTemplate(): TemplateResult {
    return html``
  }

  /**
   * Abstract method. When not overriding `render()` method you can use
   * this function to render content inside the standard API components layout.
   *
   * ```
   * contentTemplate() {
   *  return html`<p>Demo content</p>`;
   * }
   * ```
   */
  abstract contentTemplate(): TemplateResult

  handleAuthenticate(): void {
    // ...
  }

  authenticateTemplate(): TemplateResult {
    return html`
      <p>Store authorization required.</p>
      <ui-button color="filled" @click="${this.handleAuthenticate}">Authenticate</ui-button>
    `
  }
}
