import { GameObject, onBeforeRender, onStart } from "@needle-tools/engine";
import type { Context } from "@needle-tools/engine";
import {
  Box3,
  CanvasTexture,
  DoubleSide,
  Mesh,
  MeshBasicMaterial,
  Object3D,
  PlaneGeometry,
  Vector3,
} from "three";
import { findMenuController } from "./findMenuController.js";
import type { MenuController } from "./scripts/MenuController.js";
import { DishIngredientsTap } from "./scripts/DishIngredientsTap.js";

/** Dish root names → ingredient lines (extend when adding dishes). */
export const INGREDIENTS_BY_DISH_NAME: Record<string, string[]> = {
  "cc0_-_pizza_salami": ["Tomato sauce", "Mozzarella", "Salami", "Oregano", "Olive oil"],
};

const PLANE_WORLD_WIDTH = 0.28;
const PLANE_OFFSET_Y = 0.08;
const TEX_W = 512;
const TEX_H = 384;

let arSessionActive = false;
let ingredientsMenuButton: HTMLElement | null = null;

let markerMesh: Mesh | null = null;
let markerDishName: string | null = null;
let markerDishRoot: Object3D | null = null;

const _box = new Box3();
const _anchor = new Vector3();
const _viewPos = new Vector3();

/** World position of the camera the user is viewing through (XR eye in AR, main camera otherwise). */
function getViewCameraWorldPosition(context: Context, out: Vector3): boolean {
  const xrCam = context.xrCamera;
  if (context.isInXR && xrCam) {
    const eye = xrCam.cameras.length > 0 ? xrCam.cameras[0] : xrCam;
    eye.getWorldPosition(out);
    return true;
  }

  const cam = context.mainCamera;
  if (!cam) return false;
  cam.getWorldPosition(out);
  return true;
}

export function isArSessionActive(context: Context): boolean {
  if (arSessionActive) return true;
  const xr = context.xr as { running?: boolean; mode?: string } | undefined;
  return xr?.running === true && xr?.mode === "immersive-ar";
}

function roundedRect(
  c: CanvasRenderingContext2D,
  x: number,
  y: number,
  w: number,
  h: number,
  r: number,
): void {
  const rr = Math.min(r, w / 2, h / 2);
  c.moveTo(x + rr, y);
  c.arcTo(x + w, y, x + w, y + h, rr);
  c.arcTo(x + w, y + h, x, y + h, rr);
  c.arcTo(x, y + h, x, y, rr);
  c.arcTo(x, y, x + w, y, rr);
  c.closePath();
}

function drawIngredientsTexture(
  dishName: string,
  ingredients: string[],
): HTMLCanvasElement {
  const canvas = document.createElement("canvas");
  canvas.width = TEX_W;
  canvas.height = TEX_H;
  const g = canvas.getContext("2d")!;
  const pad = 28;
  const corner = 20;
  const innerW = TEX_W - pad * 2;
  const innerH = TEX_H - pad * 2;

  g.fillStyle = "rgba(18, 18, 24, 0.92)";
  g.beginPath();
  roundedRect(g, pad, pad, innerW, innerH, corner);
  g.fill();
  g.strokeStyle = "rgba(255, 255, 255, 0.12)";
  g.lineWidth = 2;
  g.stroke();

  const title = dishName.replace(/_/g, " ");
  g.fillStyle = "#f2f2f7";
  g.font = "bold 28px system-ui, Segoe UI, sans-serif";
  g.textAlign = "left";
  g.fillText("Ingredients", pad + 18, pad + 48);

  g.font = "600 22px system-ui, Segoe UI, sans-serif";
  g.fillStyle = "rgba(242, 242, 247, 0.92)";
  g.fillText(title, pad + 18, pad + 88);

  g.font = "22px system-ui, Segoe UI, sans-serif";
  g.fillStyle = "rgba(242, 242, 247, 0.88)";
  let y = pad + 130;

  if (ingredients.length > 0) {
    for (const line of ingredients) {
      g.fillText(`• ${line}`, pad + 26, y);
      y += 34;
    }
  } else {
    g.fillStyle = "rgba(242, 242, 247, 0.7)";
    g.fillText("No ingredients listed yet.", pad + 26, y);
  }

  return canvas;
}

function disposeMarker(): void {
  if (!markerMesh) return;
  markerMesh.removeFromParent();
  markerMesh.geometry.dispose();
  const mat = markerMesh.material as MeshBasicMaterial;
  if (mat.map) mat.map.dispose();
  mat.dispose();
  markerMesh = null;
  markerDishName = null;
  markerDishRoot = null;
}

function computeAnchorAboveDish(dishRoot: Object3D, liftAboveTop: number): void {
  _box.setFromObject(dishRoot);
  if (_box.isEmpty()) {
    dishRoot.getWorldPosition(_anchor);
    _anchor.y += liftAboveTop;
  } else {
    _anchor.set(
      (_box.min.x + _box.max.x) / 2,
      _box.max.y + liftAboveTop,
      (_box.min.z + _box.max.z) / 2,
    );
  }
}

function updateMarkerInWorld(context: Context): void {
  if (!markerMesh || !markerDishRoot) return;
  computeAnchorAboveDish(markerDishRoot, PLANE_OFFSET_Y);
  markerMesh.position.copy(_anchor);
  if (getViewCameraWorldPosition(context, _viewPos)) {
    markerMesh.lookAt(_viewPos);
  }
}

function resolveDishRoot(menu: MenuController, dishName: string): Object3D | null {
  return menu.dishes.find((d) => d?.name === dishName) ?? null;
}

export function hideIngredientsPanel(): void {
  disposeMarker();
}

export function showIngredientsPanel(context: Context, dishName: string): void {
  if (!isArSessionActive(context)) return;

  const menu = findMenuController(context);
  if (!menu) return;

  const dishRoot = resolveDishRoot(menu, dishName);
  if (!dishRoot) return;

  const ingredients = INGREDIENTS_BY_DISH_NAME[dishName] ?? [];

  disposeMarker();

  const canvas = drawIngredientsTexture(dishName, ingredients);
  const map = new CanvasTexture(canvas);
  map.needsUpdate = true;
  const aspect = TEX_W / TEX_H;
  const geo = new PlaneGeometry(PLANE_WORLD_WIDTH, PLANE_WORLD_WIDTH / aspect);
  const mat = new MeshBasicMaterial({
    map,
    transparent: true,
    side: DoubleSide,
    depthWrite: false,
  });

  markerMesh = new Mesh(geo, mat);
  markerMesh.name = "IngredientsInfoPlane";
  markerMesh.renderOrder = 10;
  markerMesh.frustumCulled = false;
  context.scene.add(markerMesh);

  markerDishName = dishName;
  markerDishRoot = dishRoot;
  updateMarkerInWorld(context);
}

export function toggleIngredientsPanel(context: Context, dishName: string): void {
  if (!isArSessionActive(context)) return;

  if (markerMesh && markerDishName === dishName) {
    hideIngredientsPanel();
    return;
  }
  showIngredientsPanel(context, dishName);
}

function showIngredientsForActiveDish(context: Context): void {
  const menu = findMenuController(context);
  if (!menu) return;
  const dish = menu.dishes[menu.selectedDishIndex];
  if (!dish) return;
  toggleIngredientsPanel(context, dish.name);
}

function removeIngredientsMenuButton(): void {
  ingredientsMenuButton?.remove();
  ingredientsMenuButton = null;
}

function addIngredientsMenuButton(context: Context): void {
  if (ingredientsMenuButton) return;

  ingredientsMenuButton = context.menu.appendChild({
    label: "Ingredients",
    icon: "restaurant",
    title: "Show ingredients above the dish in AR",
    priority: 6,
    onClick: () => showIngredientsForActiveDish(context),
  }) as HTMLElement;
}

export function attachDishTapHandlers(menu: MenuController): void {
  for (const dish of menu.dishes) {
    if (!dish) continue;

    if (!GameObject.getComponent(dish, DishIngredientsTap)) {
      dish.addComponent(DishIngredientsTap);
    }

    dish.traverse((child) => {
      const mesh = child as { isMesh?: boolean };
      if (!mesh.isMesh) return;
      if (!GameObject.getComponent(child, DishIngredientsTap)) {
        child.addComponent(DishIngredientsTap);
      }
    });
  }
}

function bindArSessionUi(context: Context): void {
  const needle = document.querySelector("needle-engine");
  if (!needle || needle.hasAttribute("data-ingredients-ar-bound")) return;
  needle.setAttribute("data-ingredients-ar-bound", "true");

  needle.addEventListener("enter-ar", () => {
    arSessionActive = true;
    addIngredientsMenuButton(context);
  });

  needle.addEventListener("exit-ar", () => {
    arSessionActive = false;
    hideIngredientsPanel();
    removeIngredientsMenuButton();
  });
}

function tryAttachTapHandlers(context: Context): void {
  const menu = findMenuController(context);
  if (menu) attachDishTapHandlers(menu);
}

function initIngredientsArUi(context: Context): void {
  context.menu.showSpatialMenu(true);
  bindArSessionUi(context);

  const needle = document.querySelector("needle-engine");
  needle?.addEventListener("loadfinished", () => tryAttachTapHandlers(context));
  needle?.addEventListener("menu-dishes-updated", () => tryAttachTapHandlers(context));

  tryAttachTapHandlers(context);
}

onStart((context) => {
  initIngredientsArUi(context);
});

// After LateUpdate so the WebXR rig + xrCamera reflect the current head pose.
onBeforeRender((context) => {
  if (!isArSessionActive(context) || !markerMesh) return;
  updateMarkerInWorld(context);
});
