import { useEffect, useRef, useState } from "react";

import type { LoadedModelAsset } from "../assets/gltf-model-import";
import type { LoadedImageAsset } from "../assets/image-assets";
import type { ProjectAssetRecord } from "../assets/project-assets";
import type { EditorSelection } from "../core/selection";
import type { ToolMode } from "../core/tool-mode";
import type { Vec3 } from "../core/vector";
import { DEFAULT_BOX_BRUSH_CENTER, DEFAULT_BOX_BRUSH_SIZE } from "../document/brushes";
import type { SceneDocument } from "../document/scene-document";
import type { WorldSettings } from "../document/world-settings";
import { createWorldBackgroundStyle } from "../shared-ui/world-background-style";
import { getViewportPanelLabel, type ViewportDisplayMode, type ViewportLayoutMode, type ViewportPanelId } from "./viewport-layout";
import {
  getViewportViewModeControlHint,
  getViewportViewModeGridPlaneLabel,
  getViewportViewModeLabel,
  type ViewportViewMode
} from "./viewport-view-modes";
import type { PlacementViewportToolPreview, ViewportToolPreview } from "./viewport-transient-state";

import { ViewportHost } from "./viewport-host";

interface ViewportCanvasProps {
  panelId: ViewportPanelId;
  world: WorldSettings;
  sceneDocument: SceneDocument;
  projectAssets: Record<string, ProjectAssetRecord>;
  loadedModelAssets: Record<string, LoadedModelAsset>;
  loadedImageAssets: Record<string, LoadedImageAsset>;
  selection: EditorSelection;
  toolMode: ToolMode;
  toolPreview: ViewportToolPreview;
  viewMode: ViewportViewMode;
  displayMode: ViewportDisplayMode;
  layoutMode: ViewportLayoutMode;
  isActivePanel: boolean;
  focusRequestId: number;
  focusSelection: EditorSelection;
  onSelectionChange(selection: EditorSelection): void;
  onCreateBoxBrush(center: Vec3): void;
  onCommitPlacement(toolPreview: PlacementViewportToolPreview): void;
  onToolPreviewChange(toolPreview: ViewportToolPreview): void;
}

function formatVec3(vector: Vec3): string {
  return `${vector.x}, ${vector.y}, ${vector.z}`;
}

function getViewportOverlayText(
  toolMode: ToolMode,
  viewMode: ViewportViewMode,
  displayMode: ViewportDisplayMode,
  layoutMode: ViewportLayoutMode
): string | null {
  if (layoutMode === "quad") {
    return null;
  }

  if (toolMode === "box-create") {
    return `Hover the ${getViewportViewModeGridPlaneLabel(viewMode)} grid to place a ${DEFAULT_BOX_BRUSH_SIZE.x} x ${DEFAULT_BOX_BRUSH_SIZE.y} x ${DEFAULT_BOX_BRUSH_SIZE.z} box. ${getViewportViewModeControlHint(viewMode)}`;
  }

  if (toolMode === "place") {
    return `Hover the ${getViewportViewModeGridPlaneLabel(viewMode)} grid to position the preview. Click to place it. ${getViewportViewModeControlHint(viewMode)}`;
  }

  return `${displayMode === "authoring" ? "Authoring view" : `${getViewportViewModeLabel(viewMode)} view`} on the ${getViewportViewModeGridPlaneLabel(viewMode)} grid. ${getViewportViewModeControlHint(viewMode)}`;
}

export function ViewportCanvas({
  panelId,
  world,
  sceneDocument,
  projectAssets,
  loadedModelAssets,
  loadedImageAssets,
  selection,
  toolMode,
  toolPreview,
  viewMode,
  displayMode,
  layoutMode,
  isActivePanel,
  focusRequestId,
  focusSelection,
  onSelectionChange,
  onCreateBoxBrush,
  onCommitPlacement,
  onToolPreviewChange
}: ViewportCanvasProps) {
  const containerRef = useRef<HTMLDivElement | null>(null);
  const hostRef = useRef<ViewportHost | null>(null);
  const [viewportMessage, setViewportMessage] = useState<string | null>(null);

  useEffect(() => {
    const container = containerRef.current;

    if (container === null) {
      return;
    }

    const testCanvas = document.createElement("canvas");
    const hasWebGl =
      testCanvas.getContext("webgl2") !== null ||
      testCanvas.getContext("webgl") !== null ||
      testCanvas.getContext("experimental-webgl") !== null;

    if (!hasWebGl) {
      setViewportMessage("WebGL is unavailable in this browser environment. The viewport shell is visible, but rendering is disabled.");
      return;
    }

    try {
      const viewportHost = new ViewportHost();
      hostRef.current = viewportHost;
      viewportHost.mount(container);
      setViewportMessage(null);

      return () => {
        viewportHost.dispose();
        hostRef.current = null;
      };
    } catch (error) {
      const message = error instanceof Error ? error.message : "Viewport initialization failed.";
      setViewportMessage(`Viewport initialization failed: ${message}`);
      return;
    }
  }, []);

  useEffect(() => {
    hostRef.current?.updateWorld(world);
  }, [world]);

  useEffect(() => {
    hostRef.current?.updateAssets(projectAssets, loadedModelAssets, loadedImageAssets);
  }, [projectAssets, loadedModelAssets, loadedImageAssets]);

  useEffect(() => {
    hostRef.current?.updateDocument(sceneDocument, selection);
  }, [sceneDocument, selection]);

  useEffect(() => {
    hostRef.current?.setViewMode(viewMode);
  }, [viewMode]);

  useEffect(() => {
    hostRef.current?.setDisplayMode(displayMode);
  }, [displayMode]);

  useEffect(() => {
    hostRef.current?.setBrushSelectionChangeHandler(onSelectionChange);
  }, [onSelectionChange]);

  useEffect(() => {
    hostRef.current?.setCreateBoxBrushHandler(onCreateBoxBrush);
  }, [onCreateBoxBrush]);

  useEffect(() => {
    hostRef.current?.setBoxCreatePreviewChangeHandler((center) => {
      onToolPreviewChange(
        center === null
          ? {
              kind: "none"
            }
          : {
              kind: "box-create",
              sourcePanelId: panelId,
              center
            }
      );
    });
  }, [onToolPreviewChange, panelId]);

  useEffect(() => {
    hostRef.current?.setPlacementPreviewChangeHandler(onToolPreviewChange);
  }, [onToolPreviewChange]);

  useEffect(() => {
    hostRef.current?.setPlacementCommitHandler(onCommitPlacement);
  }, [onCommitPlacement]);

  useEffect(() => {
    hostRef.current?.setToolMode(toolMode);
  }, [toolMode]);

  useEffect(() => {
    hostRef.current?.setBoxCreatePreview(toolMode === "box-create" && toolPreview.kind === "box-create" ? toolPreview.center : null);
  }, [toolMode, toolPreview]);

  useEffect(() => {
    hostRef.current?.setPlacementPreview(toolMode === "place" && toolPreview.kind === "placement" ? toolPreview : null);
  }, [toolMode, toolPreview]);

  useEffect(() => {
    if (focusRequestId === 0) {
      return;
    }

    hostRef.current?.focusSelection(sceneDocument, focusSelection);
  }, [focusRequestId, focusSelection, sceneDocument]);

  const overlayText = getViewportOverlayText(toolMode, viewMode, displayMode, layoutMode);
  const previewVisible =
    (toolMode === "box-create" && toolPreview.kind === "box-create" && toolPreview.center !== null) ||
    (toolMode === "place" && toolPreview.kind === "placement" && toolPreview.center !== null);

  return (
    <div
      ref={containerRef}
      className={`viewport-canvas viewport-canvas--${toolMode} viewport-canvas--${viewMode} viewport-canvas--${displayMode} viewport-canvas--${layoutMode} ${isActivePanel ? "viewport-canvas--active" : ""}`}
      data-testid={`viewport-canvas-${panelId}`}
      aria-label={`${getViewportPanelLabel(panelId)} editor viewport`}
      style={
        displayMode === "authoring"
          ? {
              backgroundColor: "#000000",
              backgroundImage: "none"
            }
          : createWorldBackgroundStyle(world.background, world.background.mode === "image" ? loadedImageAssets[world.background.assetId]?.sourceUrl ?? null : null)
      }
    >
      <div className="viewport-canvas__overlay" data-testid={`viewport-overlay-${panelId}`}>
        <div className="viewport-canvas__overlay-badges">
          <div className="viewport-canvas__overlay-badge">
            {toolMode === "box-create" ? "Box Create" : toolMode === "place" ? "Place" : "Select"}
          </div>
          <div className="viewport-canvas__overlay-badge viewport-canvas__overlay-badge--view">{getViewportViewModeLabel(viewMode)}</div>
          <div className="viewport-canvas__overlay-badge viewport-canvas__overlay-badge--display">
            {displayMode === "authoring" ? "Authoring" : "Lit"}
          </div>
        </div>
        {overlayText === null ? null : <div className="viewport-canvas__overlay-text">{overlayText}</div>}
        {!previewVisible ? null : (
          <div className="viewport-canvas__overlay-preview" data-testid={`viewport-snap-preview-${panelId}`}>
            Preview: {formatVec3(toolPreview.center)}
          </div>
        )}
      </div>

      {viewportMessage === null ? null : (
        <div className="viewport-canvas__fallback" role="status">
          <div className="viewport-canvas__fallback-title">Viewport Unavailable</div>
        <div>{viewportMessage}</div>
          {toolMode !== "box-create" && toolMode !== "place" ? null : (
            <button
              className="toolbar__button toolbar__button--accent"
              type="button"
              data-testid={
                toolMode === "place" ? `viewport-fallback-place-${panelId}` : `viewport-fallback-create-box-${panelId}`
              }
              onClick={() => {
                if (toolMode === "place" && toolPreview.kind === "placement") {
                  onCommitPlacement(toolPreview);
                  return;
                }

                onCreateBoxBrush(DEFAULT_BOX_BRUSH_CENTER);
              }}
            >
              {toolMode === "place" ? "Place Preview at Default Position" : "Create Default Box"}
            </button>
          )}
        </div>
      )}
    </div>
  );
}
