import * as flexui from '@openflexui/core'
import { type FrontendEnv } from './env'
import { type CodeSpace } from './space'
import { StyleIsolation } from './types'
import type { ComponentCaller } from './component'
import { registerRootForScreenResize, unregisterRootForScreenResize } from './screen_events'

/**
 * A backend context that has been associated to an environment
 */
export class AssociatedBackend {
  private _$env: FrontendEnv
  /** @internal */
  _$: flexui.GeneralBackendContext

  /** @internal */
  constructor(env: FrontendEnv, backendContext: flexui.GeneralBackendContext) {
    this._$env = env
    this._$ = backendContext
  }

  /**
   * Register a style sheet resource
   *
   * Some backend cannot load style sheet URL.
   * In this cases, the content should be registered here.
   */
  registerStyleSheetContent(url: string, content: any) {
    this._$.registerStyleSheetContent(url, content)
  }

  /**
   * Create a root component in specified backend
   *
   * The component is searched in the `codeSpace` with `url` .
   * If `url` contains "?" params, it will be parsed and try to set to component properties.
   */
  createRoot(
    tagName: string,
    codeSpace: CodeSpace,
    url: string,
    genericTargets?: { [key: string]: string },
  ): Root {
    if (this._$env !== codeSpace._$env)
      throw new Error('The code space is not in the same environment as the backend')
    return new Root(this._$, tagName, codeSpace, url, genericTargets)
  }
}

/**
 * A root component
 */
export class Root {
  private _$comp: flexui.GeneralComponent
  private _$backendContext: flexui.GeneralBackendContext

  /** @internal */
  constructor(
    backendContext: flexui.GeneralBackendContext,
    tagName: string,
    codeSpace: CodeSpace,
    url: string,
    genericTargets?: { [key: string]: string },
  ) {
    this._$backendContext = backendContext
    this._$comp = codeSpace
      .getComponentSpace()
      .createComponentByUrl(tagName, url, genericTargets || null, backendContext)
    if (codeSpace.isMainSpace()) {
      const globalStyleSheet = codeSpace.getStyleSheet('app')
      if (globalStyleSheet !== undefined) {
        const styleIsolation = codeSpace._$styleIsolationMap[this._$comp.is]
        if (
          styleIsolation === StyleIsolation.Isolated ||
          styleIsolation === StyleIsolation.ApplyShared ||
          styleIsolation === StyleIsolation.Shared
        ) {
          backendContext.appendStyleSheetPath(globalStyleSheet, codeSpace._$sharedStyleScope)
        }
      }
    }
    const addStyleSheet = (comp: flexui.GeneralComponentDefinition) => {
      const { styleScope } = comp.getComponentOptions()
      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
      const codeSpace = (comp.behavior.ownerSpace as any).__hasCodeSpace as CodeSpace | undefined
      const path = codeSpace?.getStyleSheet(comp.is)
      if (path !== undefined) {
        backendContext.appendStyleSheetPath(
          path,
          styleScope ?? flexui.StyleScopeManager.globalScope(),
        )
      }
    }
    this._$comp.getRootBehavior().getComponentDependencies().forEach(addStyleSheet)
    addStyleSheet(this._$comp.getComponentDefinition())
    // 屏幕事件(Page.onResize / pageLifetimes.resize):页面根挂载即注册,
    // 由后端尺寸变化(引擎卡片容器变化 / 宿主手动 notify)驱动分发。
    registerRootForScreenResize(this._$backendContext, this._$comp)
  }

  /**
   * Get the root component
   */
  get(): ComponentCaller<any, any, any, any> {
    return this._$comp.getMethodCaller() as unknown as ComponentCaller<any, any, any, any>
  }

  /**
   * Get the underlying component of the root component
   */
  getComponent(): flexui.GeneralComponent {
    return this._$comp
  }

  /**
   * Attach the root component to the backend
   *
   * This component, the `parent` and the `placeholder` MUST be in the same context.
   * The `parent` MUST be a parent node of the `placeholder` .
   * This also triggers `attached` lifetimes for components.
   */
  attach(parent: flexui.GeneralBackendElement, placeholder: flexui.GeneralBackendElement) {
    flexui.Element.replaceDocumentElement(this._$comp, parent, placeholder)
  }

  /**
   * Release the root component
   *
   * Some backends require this explicit call to avoid memory leaks.
   * This also triggers `detached` lifetimes for components.
   */
  release() {
    unregisterRootForScreenResize(this._$backendContext, this._$comp)
    flexui.Element.pretendDetached(this._$comp)
    this._$comp.destroyBackendElement()
  }
}