import * as flexui from '@openflexui/core'
import { AssociatedBackend } from './backend'
import { CodeSpace } from './space'
import { NATIVE_JS_LOGIC_BUILTINS, registerBuiltinComponentsOnSpace } from './components'

/**
 * A FlexUI application environment
 *
 * Each environment manages multiple backend contexts.
 * However, a backend context should be exclusively managed by a single environment
 * (to avoid `StyleScopeId` confliction).
 * It is able to create multiple `CodeSpace` within the environment.
 */
export class FrontendEnv {
  private codeSpaceMap: { [id: string]: CodeSpace }
  private globalCodeSpace: CodeSpace

  /**
   * Create an empty FlexUI application environment
   */
  constructor() {
    this.codeSpaceMap = Object.create(null) as { [id: string]: CodeSpace }
    this.globalCodeSpace = new CodeSpace(this, false, {})
    // Native webpack bootstrap skips builtin Components (adapter path).
    // Register only JS-logic tags here; view/button/… stay native adapters.
    registerBuiltinComponentsOnSpace(this.globalCodeSpace, NATIVE_JS_LOGIC_BUILTINS)
  }

  /**
   * Get a global code space in which global components can be defined
   *
   * External flexui based components can be defined in this code space.
   * All global components should be defined before any other `CodeSpace` created!
   */
  getGlobalCodeSpace(): CodeSpace {
    return this.globalCodeSpace
  }

  /**
   * Associate a backend context
   *
   * If `backendContext` is not given, the DOM-based backend is used
   * (causes failure if running outside browsers).
   * The backend context SHOULD NOT be associated by other environments!
   */
  associateBackend(backendContent?: flexui.GeneralBackendContext): AssociatedBackend {
    const ctx = backendContent ?? new flexui.CurrentWindowBackendContext()
    return new AssociatedBackend(this, ctx)
  }

  /**
   * Create a component space that can manage templates, stylesheets, JS and static JSON config files
   *
   * The space can be specified as a *main* space.
   * This makes some features available:
   * * the `app.hjss` will be used as a style sheet for all root components in the space;
   * * the `StyleIsolation.Shared` is accepted for components in the space.
   * Non-main spaces usually act as plugins.
   * `publicComponents` is a map for specifying a map of aliases and component paths.
   */
  createCodeSpace(
    id: string,
    isMainSpace: boolean,
    publicComponents = Object.create(null) as { [alias: string]: string },
  ): CodeSpace {
    const cs = new CodeSpace(this, isMainSpace, publicComponents, this.globalCodeSpace)
    this.codeSpaceMap[id] = cs
    registerBuiltinComponentsOnSpace(cs, NATIVE_JS_LOGIC_BUILTINS)
    return cs
  }

  /**
   * Get a component space by the `id` specified when created
   */
  getCodeSpace(id: string): CodeSpace | undefined {
    return this.codeSpaceMap[id]
  }
}