import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { Object3D } from "three";
import { findMenuController } from "./findMenuController.js";
import { wrapCatalogDishRoot } from "./dishPlacement.js";
import { ensureDishMeshes } from "./dishMeshFallback.js";

/**
 * Dish roots expected in the menu but missing from the exported MenuScene.glb.
 * Keys must match Unity GameObject names. Re-export from Unity to embed meshes instead.
 */
export const CATALOG_DISH_GLBS: Record<string, string> = {
  quistibi_scan: "include/dishes/quistibi_scan.glb",
};

function resolveIncludeUrl(relative: string): string {
  const base = import.meta.env.BASE_URL ?? "./";
  const normalized = base.endsWith("/") ? base : `${base}/`;
  return new URL(relative, new URL(normalized, window.location.href)).href;
}

function notifyDishesUpdated(): void {
  document.querySelector("needle-engine")?.dispatchEvent(
    new CustomEvent("menu-dishes-updated"),
  );
}

function findExistingDishRoot(
  menu: NonNullable<ReturnType<typeof findMenuController>>,
  dishName: string,
): Object3D | undefined {
  const registered = menu.dishes.find((d) => d?.name === dishName);
  if (registered) return registered;
  return menu.gameObject.children.find((child) => child.name === dishName);
}

async function loadCatalogDishesForMenu(
  menu: NonNullable<ReturnType<typeof findMenuController>>,
): Promise<number> {
  const loader = new GLTFLoader();
  let added = 0;

  for (const [dishName, relativePath] of Object.entries(CATALOG_DISH_GLBS)) {
    if (findExistingDishRoot(menu, dishName)) continue;

    try {
      const gltf = await loader.loadAsync(resolveIncludeUrl(relativePath));
      if (findExistingDishRoot(menu, dishName)) continue;

      const root = wrapCatalogDishRoot(gltf.scene, dishName);
      menu.gameObject.add(root);
      menu.registerDishRoot(root);
      added += 1;
      console.log(`[loadCatalogDishes] Loaded catalog dish: ${dishName}`);
    } catch (err) {
      console.warn(
        `[loadCatalogDishes] Could not load "${dishName}" from ${relativePath}. ` +
          "Copy the GLB to Needle/MenuScene/include/dishes/ or re-export from Unity.",
        err,
      );
    }
  }

  if (added === 0 && menu.getDishSlotCount() <= 1) {
    console.warn(
      "[loadCatalogDishes] Menu has only one dish slot. In Unity: assign all dishes on MenuController, " +
        "keep them active, then re-export MenuScene.glb.",
    );
  }

  return added;
}

let catalogLoadPromise: Promise<void> | null = null;

function initCatalogDishLoader(): void {
  const needle = document.querySelector("needle-engine");
  if (!needle) return;

  const run = (): Promise<void> => {
    if (catalogLoadPromise) return catalogLoadPromise;

    catalogLoadPromise = (async () => {
      try {
        const ctx = await needle.getContext();
        const menu = findMenuController(ctx);
        if (!menu) return;
        const added = await loadCatalogDishesForMenu(menu);
        await ensureDishMeshes(menu);
        if (added > 0) notifyDishesUpdated();
      } catch (err) {
        console.warn("[loadCatalogDishes] Failed:", err);
      }
    })();

    return catalogLoadPromise;
  };

  needle.addEventListener("loadfinished", () => void run());
}

initCatalogDishLoader();

/** Wait until catalog dish loading has finished (no-op if already done). */
export function whenCatalogDishesReady(): Promise<void> {
  return catalogLoadPromise ?? Promise.resolve();
}
