import { useEffect, useState, useCallback, useRef } from "react";
import { Canvas } from "@react-three/fiber";
import EditorViewer from "./EditorViewer";
import EditorControls from "./EditorControls";
import type { TransformMode, MapNode } from "./types";
import type { SceneData } from "./types";
import "./EditorPage.css";

interface ObjectTransform {
  uuid: string;
  position: { x: number; y: number; z: number };
  rotation: { x: number; y: number; z: number };
  scale: { x: number; y: number; z: number };
}

class HistoryManager {
  private history: ObjectTransform[][] = [];
  private currentIndex = -1;
  private maxSize: number;

  constructor(maxSize = 50) {
    this.maxSize = maxSize;
  }

  saveSnapshot(objects: ObjectTransform[]) {
    if (this.currentIndex < this.history.length - 1) {
      this.history = this.history.slice(0, this.currentIndex + 1);
    }

    this.history.push(objects.map((obj) => ({ ...obj })));

    this.currentIndex = this.history.length - 1;

    if (this.history.length > this.maxSize) {
      this.history.shift();
      this.currentIndex--;
    }
  }

  undo(): ObjectTransform[] | undefined {
    if (this.currentIndex > 0) {
      this.currentIndex--;
      return this.history[this.currentIndex];
    }
    return undefined;
  }

  redo(): ObjectTransform[] | undefined {
    if (this.currentIndex < this.history.length - 1) {
      this.currentIndex++;
      return this.history[this.currentIndex];
    }
    return undefined;
  }

  getUndoCount(): number {
    return this.currentIndex;
  }

  getRedoCount(): number {
    return this.history.length - 1 - this.currentIndex;
  }

  clear() {
    this.history = [];
    this.currentIndex = -1;
  }

  canUndo(): boolean {
    return this.currentIndex > 0;
  }

  canRedo(): boolean {
    return this.currentIndex < this.history.length - 1;
  }
}

export function EditorPage(): React.JSX.Element {
  const [hasMapJson, setHasMapJson] = useState<boolean>(false);
  const [isMapLoading, setIsMapLoading] = useState<boolean>(true);
  const [sceneData, setSceneData] = useState<SceneData | null>(null);

  // État partagé entre Canvas (3D) et EditorControls (HTML)
  const [selectedNodeIndex, setSelectedNodeIndex] = useState<number | null>(
    null,
  );
  const [hoveredNodeIndex, setHoveredNodeIndex] = useState<number | null>(null);
  const [transformMode, setTransformMode] =
    useState<TransformMode>("translate");
  const [undoCount, setUndoCount] = useState(0);
  const [redoCount, setRedoCount] = useState(0);
  const [isPlayerMode, setIsPlayerMode] = useState(false);

  const historyManagerRef = useCallback(() => new HistoryManager(50), []);
  const historyManager = useRef<HistoryManager>(historyManagerRef());

  // Callbacks partagés
  const handleSelectNode = useCallback((index: number | null) => {
    setSelectedNodeIndex(index);
  }, []);

  const handleHoverNode = useCallback((index: number | null) => {
    setHoveredNodeIndex(index);
  }, []);

  const handleTransformModeChange = useCallback((mode: TransformMode) => {
    setTransformMode(mode);
  }, []);

  const applySnapshot = useCallback(
    (snapshot: ObjectTransform[]) => {
      if (!sceneData) return;
      setSceneData((prev) => {
        if (!prev) return null;
        const newMapNodes = prev.mapNodes.map((node, index) => {
          const transform = snapshot.find((s) => s.uuid === `node-${index}`);
          if (transform) {
            return {
              ...node,
              position: [
                transform.position.x,
                transform.position.y,
                transform.position.z,
              ] as [number, number, number],
              rotation: [
                transform.rotation.x,
                transform.rotation.y,
                transform.rotation.z,
              ] as [number, number, number],
              scale: [
                transform.scale.x,
                transform.scale.y,
                transform.scale.z,
              ] as [number, number, number],
            };
          }
          return node;
        });
        return { ...prev, mapNodes: newMapNodes };
      });
    },
    [sceneData],
  );

  const handleUndo = useCallback(() => {
    const snapshot = historyManager.current.undo();
    if (snapshot) {
      applySnapshot(snapshot);
      setUndoCount(historyManager.current.getUndoCount());
      setRedoCount(historyManager.current.getRedoCount());
    }
  }, [applySnapshot]);

  const handleRedo = useCallback(() => {
    const snapshot = historyManager.current.redo();
    if (snapshot) {
      applySnapshot(snapshot);
      setUndoCount(historyManager.current.getUndoCount());
      setRedoCount(historyManager.current.getRedoCount());
    }
  }, [applySnapshot]);

  const handleSaveToServer = useCallback(async () => {
    if (!sceneData) return;
    const json = JSON.stringify(sceneData.mapNodes, null, 2);

    try {
      const response = await fetch("/api/save-map", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: json,
      });

      if (response.ok) {
        alert("Map enregistrée avec succès!");
      } else {
        alert("Erreur lors de l'enregistrement");
      }
    } catch (err) {
      console.error("Error saving map:", err);
      alert("Erreur lors de l'enregistrement");
    }
  }, [sceneData]);

  const handleExportJson = useCallback(() => {
    if (!sceneData) return;
    const json = JSON.stringify(sceneData.mapNodes, null, 2);
    const blob = new Blob([json], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = "map.json";
    a.click();
    URL.revokeObjectURL(url);
  }, [sceneData]);

  const handleResetCamera = useCallback(() => {
    // Logique pour reset camera
    console.log("Reset camera");
  }, []);

  const handlePlayerMode = useCallback(() => {
    setIsPlayerMode((prev) => !prev);
  }, []);

  const handleTransformStart = useCallback(() => {
    if (!sceneData) return;
    const snapshot = sceneData.mapNodes.map((node, index) => ({
      uuid: `node-${index}`,
      position: {
        x: node.position[0],
        y: node.position[1],
        z: node.position[2],
      },
      rotation: {
        x: node.rotation[0],
        y: node.rotation[1],
        z: node.rotation[2],
      },
      scale: { x: node.scale[0], y: node.scale[1], z: node.scale[2] },
    }));
    historyManager.current.saveSnapshot(snapshot);
  }, [sceneData]);

  const handleTransformEnd = useCallback(() => {
    if (!sceneData) return;
    const snapshot = sceneData.mapNodes.map((node, index) => ({
      uuid: `node-${index}`,
      position: {
        x: node.position[0],
        y: node.position[1],
        z: node.position[2],
      },
      rotation: {
        x: node.rotation[0],
        y: node.rotation[1],
        z: node.rotation[2],
      },
      scale: { x: node.scale[0], y: node.scale[1], z: node.scale[2] },
    }));
    historyManager.current.saveSnapshot(snapshot);
    setUndoCount(historyManager.current.getUndoCount());
    setRedoCount(historyManager.current.getRedoCount());
  }, [sceneData]);

  const handleNodeTransform = useCallback(
    (nodeIndex: number, updatedNode: MapNode) => {
      if (!sceneData) return;
      setSceneData((prev) => {
        if (!prev) return null;
        const newMapNodes = [...prev.mapNodes];
        newMapNodes[nodeIndex] = updatedNode;
        return { ...prev, mapNodes: newMapNodes };
      });
    },
    [sceneData],
  );

  useEffect(() => {
    const loadMapData = async (): Promise<void> => {
      setIsMapLoading(true);

      try {
        const response = await fetch("/map.json");

        if (!response.ok) {
          setHasMapJson(false);
          setIsMapLoading(false);
          return;
        }

        const mapNodes: MapNode[] = await response.json();

        const models = new Map<string, string>();

        const uniqueModelNames = [
          ...new Set(mapNodes.map((n: MapNode) => n.name)),
        ];
        console.log("Unique model names in map:", uniqueModelNames);

        for (const modelName of uniqueModelNames) {
          try {
            const modelUrl = `/models/${modelName}/model.gltf`;
            const modelResponse = await fetch(modelUrl);

            if (modelResponse.ok) {
              const contentType =
                modelResponse.headers.get("content-type") || "";

              if (
                contentType.includes("gltf") ||
                contentType.includes("json") ||
                contentType.includes("model")
              ) {
                const text = await modelResponse.text();
                if (
                  text.includes('"glTF"') ||
                  text.includes('"scene"') ||
                  text.includes('"nodes"')
                ) {
                  models.set(modelName, modelUrl);
                } else {
                  console.warn(
                    `Invalid GLTF content for ${modelName}:`,
                    text.substring(0, 100),
                  );
                }
              } else {
                console.warn(
                  `Invalid Content-Type for ${modelName}:`,
                  contentType,
                );
              }
            }
          } catch {
            /* empty */
          }
        }
        console.log("Loaded models:", Array.from(models.keys()));

        setSceneData({ mapNodes, models });
        setHasMapJson(true);
      } catch (error) {
        console.error("Error loading map data:", error);
        setHasMapJson(false);
      } finally {
        setIsMapLoading(false);
      }
    };

    loadMapData();
  }, []);

  const handleFolderUpload = async (
    event: React.ChangeEvent<HTMLInputElement>,
  ): Promise<void> => {
    const files = event.target.files;
    if (!files) return;

    const fileMap = new Map<string, File>();
    for (const file of Array.from(files)) {
      const webkitRelativePath =
        (file as File & { webkitRelativePath?: string }).webkitRelativePath ||
        "/" + file.name;
      fileMap.set(webkitRelativePath, file);
    }

    const mapFile = fileMap.get("/map.json");
    if (!mapFile) {
      alert("Fichier map.json manquant à la racine du dossier");
      return;
    }

    try {
      const mapText = await mapFile.text();
      const mapNodes = JSON.parse(mapText);

      const models = new Map<string, string>();

      for (const [path, file] of fileMap.entries()) {
        const modelMatch = path && path.match(/^\/models\/(.+)\/model\.gltf$/);
        if (modelMatch && modelMatch[1]) {
          const modelName = modelMatch[1];
          const blobUrl = URL.createObjectURL(file);
          models.set(modelName, blobUrl);
        }
      }

      setSceneData({ mapNodes, models });
      setHasMapJson(true);
    } catch (error) {
      console.error("Error processing upload:", error);
      alert("Erreur lors du traitement du dossier");
    }
  };

  if (isMapLoading) {
    return (
      <div className="editor-container">
        <div className="editor-loading">
          <h2>Chargement de l'éditeur...</h2>
          <p>Vérification de map.json dans public/</p>
        </div>
      </div>
    );
  }

  if (!hasMapJson) {
    return (
      <div className="editor-container">
        <div className="editor-error">
          <h2>Erreur : map.json introuvable</h2>
          <p>
            Le fichier map.json est requis dans le dossier <code>public/</code>.
          </p>

          <div className="upload-section">
            <h3>Télécharger un dossier contenant map.json</h3>

            <label className="drop-zone">
              <input
                type="file"
                className="folder-input"
                onChange={handleFolderUpload}
              />
              Choisir un dossier contenant map.json
            </label>

            <div className="folder-structure">
              <h4>Structure requise :</h4>
              <pre>
                public/ ├── <strong>map.json</strong> (à la racine) └── models/
                ├── arbre/ │ └── model.glb ├── building/ │ └── model.glb └── ...
              </pre>
            </div>
          </div>
        </div>
      </div>
    );
  }

  return (
    <div className="editor-container">
      <Canvas
        camera={{ position: [0, 50, 100], fov: 50 }}
        style={{ width: "100%", height: "100%" }}
        onCreated={({ gl }) => {
          gl.setClearColor("#1e293b");
        }}
      >
        <EditorViewer
          sceneData={sceneData!}
          selectedNodeIndex={selectedNodeIndex}
          onSelectNode={handleSelectNode}
          hoveredNodeIndex={hoveredNodeIndex}
          onHoverNode={handleHoverNode}
          transformMode={transformMode}
          onTransformModeChange={handleTransformModeChange}
          onTransformStart={handleTransformStart}
          onTransformEnd={handleTransformEnd}
          onNodeTransform={handleNodeTransform}
          onUndo={handleUndo}
          onRedo={handleRedo}
          isPlayerMode={isPlayerMode}
        />
      </Canvas>

      {/* EditorControls rendu en dehors du Canvas (HTML overlay) */}
      {sceneData && (
        <EditorControls
          transformMode={transformMode}
          onTransformModeChange={handleTransformModeChange}
          selectedNodeIndex={selectedNodeIndex}
          nodesCount={sceneData.mapNodes.length}
          selectedNodeName={
            selectedNodeIndex !== null && sceneData.mapNodes[selectedNodeIndex]
              ? sceneData.mapNodes[selectedNodeIndex].name || null
              : null
          }
          undoCount={undoCount}
          redoCount={redoCount}
          onUndo={handleUndo}
          onRedo={handleRedo}
          onExportJson={handleExportJson}
          onSaveToServer={handleSaveToServer}
          onResetCamera={handleResetCamera}
          onPlayerMode={handlePlayerMode}
          isPlayerMode={isPlayerMode}
        />
      )}
    </div>
  );
}
