// The scene-editor compile composition, shared by every host that compiles an edit-mode bundle
// for a .scene.ts file: the platform backend (src/scene/compileSceneEditor.ts), the standalone
// scene-editor dev server, and (later) the lecodes CLI. One bundle on purpose: chisel's
// method-granular DCE trims each bundle's SDK copy to its own calls, so a separately-compiled
// harness could not call scene methods on objects the scene bundle created.

import { existsSync, readdirSync, readFileSync } from "node:fs"
import { join } from "node:path"
import type { CompileEntry } from "./compileProject"

/** Where the harness sources land in the compile's file map. The dotfolder can't collide with
 *  project assets (the tree forbids leading-dot names) and reads clearly in compile errors. */
export const SCENE_HARNESS_DIR = "./.scene-editor"

/** The virtual entrypoint path (`entryOverride`). */
export const SCENE_ENTRY_PATH = "./.scene-entry.ts"

/**
 * Read the harness sources (packages/projects/src/scene-editor) from a directory:
 * `LECODES_SCENE_HARNESS_DIR` env override first (deployments vendor the sources, like
 * LECODES_SDK_INJECT does for the SDK), else the given fallback dir. Returns name → source.
 */
export const loadSceneHarness = (fallbackDir?: string): Record<string, string> => {
  const override = process.env.LECODES_SCENE_HARNESS_DIR
  const dir = override && existsSync(override) ? override
    : fallbackDir && existsSync(fallbackDir) ? fallbackDir
    : null
  if (!dir) throw new Error("scene-editor harness sources not found (set LECODES_SCENE_HARNESS_DIR)")
  const out: Record<string, string> = {}
  for (const f of readdirSync(dir)) {
    if (f.endsWith(".ts")) out[f] = readFileSync(join(dir, f), "utf8")
  }
  return out
}

/**
 * The extra entries that turn a project compile into a scene-editor compile: the harness modules
 * plus a virtual entry that (1) sets the edit-mode flag in its own module — imports hoist, so the
 * flag must run as a module, first — (2) imports the scene, which registers its handle on
 * `__lecodesScenes`, (3) imports the project's `*.editor.ts` plugin files (editor bundles ONLY —
 * production compiles never import them, so they cost zero bytes in shipped `app.js`), and
 * (4) imports the harness main, which installs `__lecodesSceneHarness`.
 * FULL extensions everywhere — exact-match specifiers need no resolver guesswork on dotted names,
 * so even the unpatched published chisel resolves the `.scene.ts` import.
 *
 * `scenePath` is the project path of the scene file ("./scenes/city.scene.ts"); `projectPaths` is
 * the project's file list — `.editor.ts` entries are picked out of it. Compile with
 * `entryOverride: SCENE_ENTRY_PATH`, `header: false`, `format: "iife"`, `editor: true`.
 */
export const sceneEditorEntries = (
  scenePath: string,
  harness: Record<string, string>,
  projectPaths: string[] = [],
): CompileEntry[] => {
  const entries: CompileEntry[] = Object.entries(harness).map(([ name, text ]) => ({
    path: `${SCENE_HARNESS_DIR}/${name}`,
    type: "text" as const,
    text,
  }))
  entries.push({
    path: `${SCENE_HARNESS_DIR}/.edit-mode.ts`,
    type: "text",
    text: `;(globalThis as any).__lecodesSceneEdit = true\nexport {}\n`,
  })
  const editorImports = projectPaths
    .filter((p) => p.endsWith(".editor.ts"))
    .map((p) => `import "${p}"\n`)
    .join("")
  // Every material asset registers on `__lecodesMaterials` BY PROJECT PATH, so the editor can
  // resolve a scene's `material: handle` reference (an import the doc sees as an identifier) to
  // the live handle and edit it in place. Imports dedupe with the scene's own, so a material the
  // scene uses is the same instance here.
  const materialPaths = projectPaths.filter((p) => p.endsWith(".material.ts"))
  const materialImports = materialPaths.map((p, i) => `import __mat${i} from "${p}"\n`).join("")
  const materialRegister = materialPaths.length === 0 ? ""
    : `;(globalThis as any).__lecodesMaterials = { ${materialPaths.map((p, i) => `${JSON.stringify(p)}: __mat${i}`).join(", ")} }\n`
  entries.push({
    path: SCENE_ENTRY_PATH,
    type: "text",
    text: `import "${SCENE_HARNESS_DIR}/.edit-mode.ts"\n`
      + materialImports
      + `import "${scenePath}"\n`
      + editorImports
      + `import "${SCENE_HARNESS_DIR}/main.ts"\n`
      + materialRegister,
  })
  return entries
}
