Source: core/SceneManager.js

// src/engine/core/SceneManager.js

/**
 * @file SceneManager.js
 * @description Manages a stack of game scenes, controlling which scene(s) are active,
 * updating, and rendering. Passes engine instance to scene methods.
 */

// JSDoc typedefs as before...

class SceneManager {
  constructor() {
    this.scenes = {}
    this.sceneStack = []
    this.context = null
    this.engine = null
    // console.log('SceneManager: Initialized with stack support.');
  }

  setContextAndEngine(context, engineInstance) {
    if (!context) console.error('SceneManager: Provided context is invalid.')
    this.context = context

    if (!engineInstance) {
      console.error('SceneManager: Provided engine instance is invalid.')
      return
    }
    this.engine = engineInstance
    // console.log('SceneManager: Engine instance and context set.');
  }

  add(name, sceneInstance) {
    if (typeof name !== 'string' || !name) {
      console.error('SceneManager: Scene name invalid.')
      return
    }
    if (typeof sceneInstance !== 'object' || sceneInstance === null) {
      console.error(`SceneManager: Scene object for "${name}" invalid.`)
      return
    }
    if (
      typeof sceneInstance.initialize !== 'function' ||
      typeof sceneInstance.update !== 'function' ||
      typeof sceneInstance.render !== 'function' ||
      typeof sceneInstance.unload !== 'function'
    ) {
      console.error(
        `SceneManager: Scene "${name}" missing required methods. Check JSDoc SceneInstance typedef.`,
      )
      return
    }
    if (this.scenes[name]) {
      console.warn(`SceneManager: Scene "${name}" already exists. Overwriting.`)
    }
    this.scenes[name] = sceneInstance
    // console.log(`SceneManager: Scene "${name}" registered.`);
  }

  getActiveSceneInstance() {
    if (this.sceneStack.length > 0) {
      return this.sceneStack[this.sceneStack.length - 1].instance
    }
    return null
  }

  getActiveSceneName() {
    if (this.sceneStack.length > 0) {
      return this.sceneStack[this.sceneStack.length - 1].name
    }
    return null
  }

  switchTo(sceneName, data = {}) {
    if (!this.engine || !this.context) {
      console.error('SceneManager.switchTo: Engine or context not set.')
      return
    }
    const newSceneInstance = this.scenes[sceneName]
    if (!newSceneInstance) {
      console.error(`SceneManager.switchTo: Scene "${sceneName}" not found.`)
      return
    }

    // console.log(`SceneManager: Switching to base scene "${sceneName}". Unloading current stack...`);
    while (this.sceneStack.length > 0) {
      const sceneWrapper = this.sceneStack.pop()
      if (typeof sceneWrapper.instance.unload === 'function') {
        // console.log(`SceneManager: Unloading "${sceneWrapper.name}" from stack.`);
        sceneWrapper.instance.unload(this.engine)
      }
    }

    this.sceneStack.push({ name: sceneName, instance: newSceneInstance })
    if (typeof newSceneInstance.initialize === 'function') {
      // Ensure consistency with scenes expecting initialize(engine, data)
      // Your BaseScene expects (engine, data).
      // The old SceneManager JSDoc had (engine, context, data)
      // For now, matching pushScene's call signature which works with BaseScene
      newSceneInstance.initialize(this.engine, data)
    }
    if (this.engine.events) this.engine.events.emit('scene:switched', { name: sceneName, data })
    // console.log(`SceneManager: Switched to scene "${sceneName}". Stack size: ${this.sceneStack.length}`);
  }

  pushScene(sceneName, data = {}) {
    if (!this.engine || !this.context) {
      // context check might be redundant if scenes don't get it directly
      console.error('SceneManager.pushScene: Engine or context not set.')
      return
    }
    const newSceneInstance = this.scenes[sceneName]
    if (!newSceneInstance) {
      console.error(`SceneManager.pushScene: Scene "${sceneName}" not found.`)
      return
    }

    // MODIFIED CONSOLE LOG to prevent circular structure error
    if (typeof data === 'object' && data !== null) {
      console.log(
        `SceneManager: Pushing scene "${sceneName}". Data keys being passed: ${Object.keys(data).join(', ')}`,
      )
    } else {
      console.log(`SceneManager: Pushing scene "${sceneName}". Data being passed:`, data)
    }

    const currentTopSceneInstance = this.getActiveSceneInstance()
    if (currentTopSceneInstance && typeof currentTopSceneInstance.pause === 'function') {
      // console.log(`SceneManager: Pausing current top scene "${this.getActiveSceneName()}".`);
      currentTopSceneInstance.pause(this.engine)
    }

    this.sceneStack.push({ name: sceneName, instance: newSceneInstance })
    // console.log(`SceneManager: Pushed scene "${sceneName}". Stack size: ${this.sceneStack.length}`);
    if (typeof newSceneInstance.initialize === 'function') {
      // This assumes scenes (like BaseScene) expect initialize(engine, data)
      newSceneInstance.initialize(this.engine, data)
    }
    if (this.engine.events) this.engine.events.emit('scene:pushed', { name: sceneName, data })
  }

  popScene(dataToPassDown = {}) {
    if (!this.engine /*|| !this.context*/) {
      // context check might be redundant
      console.error('SceneManager.popScene: Engine not set.')
      return
    }
    if (this.sceneStack.length === 0) {
      console.warn('SceneManager.popScene: No scene to pop from stack.')
      return
    }

    const poppedSceneWrapper = this.sceneStack.pop()
    const poppedSceneName = poppedSceneWrapper.name
    // console.log(`SceneManager: Popping scene "${poppedSceneName}". Stack size: ${this.sceneStack.length}`);
    if (typeof poppedSceneWrapper.instance.unload === 'function') {
      poppedSceneWrapper.instance.unload(this.engine)
    }
    if (this.engine.events) {
      this.engine.events.emit('scene:popped', { name: poppedSceneName, dataPassed: dataToPassDown })
    }

    const newTopSceneInstance = this.getActiveSceneInstance()
    if (newTopSceneInstance && typeof newTopSceneInstance.resume === 'function') {
      // console.log(`SceneManager: Resuming new top scene "${this.getActiveSceneName()}".`);
      // BaseScene expects resume(engine, data)
      newTopSceneInstance.resume(this.engine, dataToPassDown)
    }
  }

  update(deltaTime) {
    for (let i = this.sceneStack.length - 1; i >= 0; i--) {
      const sceneEntry = this.sceneStack[i]
      if (sceneEntry.instance && typeof sceneEntry.instance.update === 'function') {
        sceneEntry.instance.update(deltaTime, this.engine)
      }
      const isModalScene =
        sceneEntry.instance.isModal !== undefined ? sceneEntry.instance.isModal : true
      if (isModalScene) {
        break
      }
    }
  }

  render() {
    if (!this.engine || !this.context) return
    for (const sceneWrapper of this.sceneStack) {
      if (sceneWrapper.instance && typeof sceneWrapper.instance.render === 'function') {
        sceneWrapper.instance.render(this.context, this.engine)
      }
    }
  }
}

export default SceneManager