/* ════════════════════════════════════════════════════════════════════════
   MISIÓN — panel de chat con el astronauta
   ────────────────────────────────────────────────────────────────────────
   Este módulo construye y controla el panel de chat 3D donde el jugador le
   habla al astronauta sobre el planeta que eligió. NO crea su propio
   renderer, escena, cámara ni sesión WebXR — todo eso ya existe en
   lobbyScene.js y se lo pasan como parámetros a initMission(). Esto es a
   propósito: antes este módulo (entonces llamado threeScene.js) vivía en su
   propia página (vr.php) con su propia sesión, y pasar de una página a la
   otra dentro de una sesión WebXR activa resultaba poco fiable en el
   navegador de Meta Quest. Ahora todo vive en una sola página/sesión.

   Diseño actual ("modo ventana"): la charla con el astronauta sucede EN LA
   VENTANA de la nave, frente al planeta grande que ya dibuja lobbyScene.js
   — este módulo no dibuja su propio planeta ni fondo espacial, solo el
   panel de chat y el astronauta que flota junto a él.

   initMission({THREE, renderer, scene, camera, cameraRig, onExit}) arma
   todo UNA sola vez (panel, chat, mic, controles de mouse/touch/mandos,
   listeners...) y devuelve funciones para reutilizar esa misma instancia
   en cada misión:
     - setPlanet(planetId)  → solo actualiza qué astronauta/tema corresponde
     - show() / hide()      → aparece/desaparece el panel sin reconstruirlo
     - tick(tSec)             → se llama una vez por frame, solo si show() ya
                               se llamó (evita trabajo de más cuando no hace falta)
     - onXRSessionStart/End → los llama lobbyScene.js cuando el jugador se
                               pone o se quita el casco
   ════════════════════════════════════════════════════════════════════════ */

import {
  PLANET_ENVIRONMENTS,
  ASTRONAUT_SPRITE,
  ASTRONAUT_SIDE_OFFSET,
  ASTRONAUT_SCALE_MULT,
  PANEL_DISTANCE_DEFAULT,
  HIT_ZONES,
  SETTINGS_ZONES,
  VOICES,
} from "./config.js";

import {
  createUIState,
  createPanel,
  drawPanelFactory,
} from "./uiPanel.js";

import { createTTSPlayer, createMicChatController, sendTextMessage, setupKeyboardInput } from "./chat.js";
import { setupDesktopMobileInput } from "./inputDesktopMobile.js";
import { setupXRInput } from "./inputXR.js";

function isMobileUA() {
  return /Android|iPhone|iPad|iPod/i.test(navigator.userAgent || "");
}

// -------------------------
// Ayuda de controles (escritorio) — solo tiene sentido una vez que hay
// misión que controlar, así que se muestra/oculta junto con show()/hide().
// -------------------------
function createDesktopHelp(renderer) {
  if (renderer.xr.isPresenting) return;
  if (isMobileUA()) return;
  if (localStorage.getItem("astronauta_help_dismissed") === "1") return;
  if (document.getElementById("vrDesktopHelp")) return;

  const wrap = document.createElement("div");
  wrap.id = "vrDesktopHelp";
  wrap.style.position = "fixed";
  wrap.style.left = "16px";
  wrap.style.bottom = "16px";
  wrap.style.maxWidth = "460px";
  wrap.style.background = "rgba(11, 18, 32, 0.92)";
  wrap.style.color = "#fff";
  wrap.style.border = "1px solid rgba(255,255,255,0.18)";
  wrap.style.borderRadius = "14px";
  wrap.style.padding = "12px 14px";
  wrap.style.fontFamily = "system-ui, -apple-system, Segoe UI, Roboto, Arial";
  wrap.style.zIndex = "9999";
  wrap.style.backdropFilter = "blur(6px)";
  wrap.style.boxShadow = "0 10px 30px rgba(0,0,0,0.35)";
  wrap.innerHTML = `
    <div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
      <div style="font-weight:700; font-size:14px;">Controles (Escritorio)</div>
      <button id="vrHelpClose"
        style="cursor:pointer; border:0; background:rgba(255,255,255,0.12); color:#fff;
              border-radius:10px; padding:6px 10px; font-weight:700;">
        ✕
      </button>
    </div>
    <div style="margin-top:8px; font-size:13px; line-height:1.35; color:rgba(255,255,255,0.92);">
      <div>🖱️ <b>Mover panel:</b> Arrastra desde el botón con flechas</div>
      <div>🧾 <b>Scroll chat:</b> Rueda del mouse sobre el chat</div>
      <div>🔎 <b>Zoom panel:</b> <b>Shift</b> + rueda</div>
      <div>🧭 <b>Mirar alrededor:</b> Click derecho + arrastrar <i>o</i> <b>Shift</b> + arrastrar</div>
      <div style="margin-top:6px; opacity:0.85;">En Quest: apunta al chat y usa el thumbstick para hacer scroll.</div>
      <div style="margin-top:6px; opacity:0.85;">Con manos: haz <b>pinch</b> y arrastra dentro del chat.</div>
    </div>
  `;
  document.body.appendChild(wrap);
  wrap.querySelector("#vrHelpClose")?.addEventListener("click", () => {
    localStorage.setItem("astronauta_help_dismissed", "1");
    wrap.remove();
  });
}

/**
 * Monta el sistema de misión (astronauta + panel de chat) UNA sola vez
 * dentro del renderer/escena/cámara que ya trae el lobby.
 */
export function initMission({ THREE, renderer, scene, camera, cameraRig, onExit }) {
  let _clickAudioCtx = null;
  window.playClickSound = window.playClickSound || function playClickSound() {
    if (window.ASTRO_CONFIG && window.ASTRO_CONFIG.sfx === false) return;
    try {
      _clickAudioCtx = _clickAudioCtx || new (window.AudioContext || window.webkitAudioContext)();
      if (_clickAudioCtx.state === "suspended") _clickAudioCtx.resume();
      const ctx = _clickAudioCtx;
      const t0 = ctx.currentTime;
      const osc = ctx.createOscillator();
      const gain = ctx.createGain();
      osc.type = "sine";
      osc.frequency.setValueAtTime(880, t0);
      osc.frequency.exponentialRampToValueAtTime(440, t0 + 0.07);
      gain.gain.setValueAtTime(0.0001, t0);
      gain.gain.exponentialRampToValueAtTime(0.18, t0 + 0.008);
      gain.gain.exponentialRampToValueAtTime(0.0001, t0 + 0.09);
      osc.connect(gain).connect(ctx.destination);
      osc.start(t0);
      osc.stop(t0 + 0.1);
    } catch (_) { /* WebAudio no disponible: se ignora silenciosamente */ }
  };
  const playClickSound = window.playClickSound;

  const texLoader = new THREE.TextureLoader();

  // Panel UI — se crea una vez
  const state = createUIState();
  const { panelCanvas, ctx, panelTex, panelMesh, uiGroup } = createPanel({ THREE, scene, camera });
  uiGroup.visible = false;

  let panelDistance = PANEL_DISTANCE_DEFAULT;

  function applyPanelScale() {
    if (renderer.xr.isPresenting) {
      uiGroup.scale.set(1, 1, 1);
      return;
    }
    if (isMobileUA()) uiGroup.scale.set(0.75, 0.75, 0.75);
    else uiGroup.scale.set(1.00, 1.00, 1.00);
  }
  applyPanelScale();

  const drawPanel = drawPanelFactory({ THREE, panelCanvas, ctx, panelTex, state });

  function centerPanelDesktopLikeReload() {
    applyPanelScale();

    const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion);
    forward.y = 0;
    if (forward.lengthSq() < 1e-6) forward.set(0, 0, -1);
    forward.normalize();

    const scale = uiGroup.scale.x || 1;
    const dist = panelDistance / Math.max(0.65, scale);

    const target = camera.position
      .clone()
      .add(forward.multiplyScalar(dist))
      .add(new THREE.Vector3(0, 0.12, 0));

    uiGroup.position.copy(target);
    uiGroup.lookAt(camera.position);
  }

  function recenterPanel() {
    const inXR = renderer.xr.isPresenting === true;
    if (!inXR) return centerPanelDesktopLikeReload();

    try {
      applyPanelScale();
      const headPos = new THREE.Vector3();
      const headQuat = new THREE.Quaternion();
      camera.getWorldPosition(headPos);
      camera.getWorldQuaternion(headQuat);

      const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(headQuat).normalize();
      const up = new THREE.Vector3(0, 1, 0).applyQuaternion(headQuat).normalize();

      const target = headPos.clone().add(forward.multiplyScalar(panelDistance)).add(up.multiplyScalar(-0.10));

      uiGroup.position.copy(target);
      uiGroup.lookAt(headPos);
    } catch (err) {
      console.error("[missionScene] Error al recalibrar el panel en VR:", err);
    }
  }

  // "SALIR" = abandonar la misión y volver a la nave. YA NO navega a otra
  // página — antes esto era justo el punto que fallaba dentro de una sesión
  // XR activa en el navegador de Quest. Ahora solo avisa al lobby, que anima
  // la cámara de regreso dentro de la MISMA sesión/página.
  function exitAndRedirect() {
    if (typeof onExit === "function") onExit();
  }

  async function handleActionFromHit(hit) {
    const p = hit?.uv
      ? { x: hit.uv.x * panelCanvas.width, y: (1 - hit.uv.y) * panelCanvas.height }
      : null;
    if (!p) return;

    const inRect = (pt, r) =>
      pt.x >= r.x && pt.x <= r.x + r.w && pt.y >= r.y && pt.y <= r.y + r.h;

    if (inRect(p, HIT_ZONES.exit)) { playClickSound(); return exitAndRedirect(); }

    if (inRect(p, HIT_ZONES.settings)) {
      playClickSound();
      state.showSettings = !state.showSettings;
      drawPanel();
      return;
    }

    if (inRect(p, HIT_ZONES.recenter)) { playClickSound(); return recenterPanel(); }

    if (state.showSettings) {
      const cfg = window.ASTRO_CONFIG || {};
      if (inRect(p, SETTINGS_ZONES.langEs))  { playClickSound(); cfg.lang = "es"; drawPanel(); return; }
      if (inRect(p, SETTINGS_ZONES.langEn))  { playClickSound(); cfg.lang = "en"; drawPanel(); return; }
      if (inRect(p, SETTINGS_ZONES.sfxOn))   { playClickSound(); cfg.sfx = true; drawPanel(); return; }
      if (inRect(p, SETTINGS_ZONES.sfxOff))  { playClickSound(); cfg.sfx = false; drawPanel(); return; }
      if (inRect(p, SETTINGS_ZONES.voice))   {
        playClickSound();
        const i = VOICES.findIndex(v => v.id === cfg.voice);
        cfg.voice = VOICES[(i + 1 + VOICES.length) % VOICES.length].id;
        drawPanel();
        return;
      }
      if (inRect(p, SETTINGS_ZONES.back))    { playClickSound(); state.showSettings = false; drawPanel(); return; }
      return;
    }

    if (inRect(p, HIT_ZONES.talk)) { playClickSound(); return mic.toggleMic(); }

    // Tocar la caja de texto: además de marcarla como "enfocada" para que el
    // panel dibuje el cursor, enfoca de VERDAD el <input> oculto (ver más
    // abajo) — eso es lo que hace que el navegador de Quest (o el teclado
    // en pantalla de un celular) muestre su teclado. Sin un elemento HTML
    // real enfocado, el sistema operativo no tiene motivo para mostrar nada.
    if (inRect(p, HIT_ZONES.textInput)) {
      playClickSound();
      focusHiddenTextInput();
      return;
    }

    if (inRect(p, HIT_ZONES.sendBtn)) {
      if ((state.textInput || "").trim()) playClickSound();
      sendTypedText();
      return;
    }
  }

  /* ── Teclado en VR/celular ────────────────────────────────────────────
     La caja de texto que se VE es un dibujo dentro del canvas del panel —
     no es un <input> real, así que tocarla no le puede pedir nada al
     sistema. Por eso existe este <input> invisible: su ÚNICO trabajo es
     recibir el foco real del navegador para que Quest (o el teclado en
     pantalla de un celular) decida mostrar su teclado. El texto que se ve
     en el panel sigue siendo el dibujado a mano; este input solo se usa
     como "gancho" y se mantiene sincronizado con state.textInput. */
  const hiddenTextInput = document.createElement("input");
  hiddenTextInput.type = "text";
  hiddenTextInput.autocomplete = "off";
  hiddenTextInput.setAttribute("autocapitalize", "sentences");
  hiddenTextInput.spellcheck = false;
  hiddenTextInput.style.cssText =
    "position:fixed;left:0;top:0;width:1px;height:1px;opacity:0;border:0;padding:0;margin:0;pointer-events:none;";
  document.body.appendChild(hiddenTextInput);

  // Cada tecla que llega al input real se refleja en el estado del panel,
  // para que el dibujo del panel (el que sí se ve) quede al día.
  hiddenTextInput.addEventListener("input", () => {
    state.textInput = hiddenTextInput.value;
    drawPanel();
  });
  hiddenTextInput.addEventListener("keydown", (e) => {
    if (e.key === "Enter") { e.preventDefault(); sendTypedText(); }
    else if (e.key === "Escape") { blurHiddenTextInput(); drawPanel(); }
  });

  function focusHiddenTextInput() {
    state.textInputFocused = true;
    hiddenTextInput.value = state.textInput || "";
    drawPanel();
    // El foco se pide en el mismo gesto del usuario (el click/tap que
    // disparó esta función), sin await ni setTimeout de por medio — los
    // navegadores solo confían en mostrar el teclado del sistema cuando el
    // foco se pide de forma síncrona, como reacción directa a ese gesto.
    hiddenTextInput.focus({ preventScroll: true });
  }
  function blurHiddenTextInput() {
    state.textInputFocused = false;
    hiddenTextInput.blur();
  }
  function sendTypedText() {
    const text = (state.textInput || "").trim();
    if (!text) return;
    state.textInput = "";
    hiddenTextInput.value = "";
    blurHiddenTextInput();
    drawPanel();
    sendTextMessage({ text, state, drawPanel, ttsPlayer: tts });
  }

  // Mic/chat — se crea una vez
  const tts = createTTSPlayer();
  const mic = createMicChatController({ state, drawPanel, ttsPlayer: tts });
  setupKeyboardInput({ state, drawPanel, ttsPlayer: tts });

  // Inputs desktop/mobile — se registran UNA sola vez (no en cada misión,
  // para no acumular listeners duplicados si el jugador visita varios
  // planetas en la misma sesión de página).
  setupDesktopMobileInput({
    THREE,
    renderer,
    camera,
    panelMesh,
    panelCanvas,
    uiGroup,
    state,
    drawPanel,
    recenterPanel,
    setPanelDistance: (v) => { panelDistance = v; },
    getPanelDistance: () => panelDistance,
    handleActionFromHit,
    isActive: () => missionActive,
  });

  // Entradas XR — también una sola vez. El lobby ya creó el VRButton y la
  // sesión; aquí solo se enganchan los controles/manos a ESE mismo renderer.
  const xr = setupXRInput({
    THREE,
    renderer,
    scene,
    cameraRig,
    camera,
    panelMesh,
    panelCanvas,
    uiGroup,
    state,
    drawPanel,
    handleActionFromHit,
  });

  // El astronauta que flota junto al panel — es el único "objeto extra" que
  // queda en este archivo desde el rediseño "modo ventana" (antes también
  // había un entorno de 360° y objetos flotantes decorativos por planeta;
  // se quitaron por completo, ver notas más arriba).
  let astronautSprite = null;
  const AOFF = new THREE.Vector3(ASTRONAUT_SIDE_OFFSET.x, ASTRONAUT_SIDE_OFFSET.y, ASTRONAUT_SIDE_OFFSET.z);

  function makeSprite(url, size, opacity) {
    const tex = texLoader.load(
      url,
      (t) => { t.colorSpace = THREE.SRGBColorSpace; },
      undefined,
      (e) => console.warn("No pude cargar sprite:", url, e)
    );
    const mat = new THREE.SpriteMaterial({
      map: tex,
      transparent: true,
      opacity: typeof opacity === "number" ? opacity : 1.0,
      depthWrite: false,
      depthTest: true,
    });
    const sp = new THREE.Sprite(mat);
    sp.scale.set(size, size, 1);
    return sp;
  }

  // Quita el astronauta actual de la escena y libera su textura/material —
  // se llama antes de crear uno nuevo cada vez que arranca una misión.
  function clearEnvironment() {
    if (astronautSprite) {
      uiGroup.remove(astronautSprite);
      astronautSprite.material?.map?.dispose?.();
      astronautSprite.material?.dispose?.();
      astronautSprite = null;
    }
  }

  // Prepara la misión para el planeta elegido: aplica los colores de panel
  // que le corresponden y crea el sprite del astronauta junto al panel.
  function buildEnvironment(planetId) {
    clearEnvironment();

    const ENV = PLANET_ENVIRONMENTS[planetId] || PLANET_ENVIRONMENTS["default"];
    if (window.ASTRO_CONFIG) window.ASTRO_CONFIG._panelColors = ENV.panel || null;

    const sp = makeSprite(ASTRONAUT_SPRITE.url, ASTRONAUT_SPRITE.size, ASTRONAUT_SPRITE.opacity);
    sp.name = "astronaut";
    astronautSprite = sp;
    astronautSprite.position.copy(AOFF);
    astronautSprite.scale.set(ASTRONAUT_SPRITE.size * ASTRONAUT_SCALE_MULT, ASTRONAUT_SPRITE.size * ASTRONAUT_SCALE_MULT, 1);
    astronautSprite.renderOrder = 3;
    astronautSprite.material.depthWrite = false;
    uiGroup.add(astronautSprite);
  }

  // Pequeño balanceo/respiración del astronauta, para que no se vea
  // congelado en el aire — se llama una vez por frame mientras hay misión.
  function updateAstronaut(tSec) {
    if (!astronautSprite) return;
    const bobLocal = Math.sin(tSec * 1.15) * 0.05;
    const sway = Math.sin(tSec * 0.85) * 0.03;
    astronautSprite.position.set(AOFF.x + sway, AOFF.y + bobLocal, AOFF.z);
    astronautSprite.material.rotation = Math.sin(tSec * 0.9) * 0.08;
  }

  drawPanel();

  let missionActive = false;

  function setMissionControllerVisualsVisible(v) {
    [xr.controllers?.controller0, xr.controllers?.controller1].forEach((c) => {
      if (!c) return;
      if (c.userData.missionReticle) c.userData.missionReticle.visible = v;
      if (c.userData.missionRayLine) c.userData.missionRayLine.visible = v;
    });
  }

  function show() {
    missionActive = true;
    // MODO VENTANA: la nave sigue visible detrás — solo se muestra el
    // panel de chat (con el astronauta colgado de él).
    uiGroup.visible = true;
    setMissionControllerVisualsVisible(true);
    createDesktopHelp(renderer);
    recenterPanel();
  }

  function hide() {
    missionActive = false;
    uiGroup.visible = false;
    setMissionControllerVisualsVisible(false);
    document.getElementById("vrDesktopHelp")?.remove();
    document.getElementById("astro-exit-fallback")?.remove();
    // Si el jugador salía mientras tenía el teclado abierto, que no se
    // quede pegado mostrando el teclado del sistema en el lobby.
    blurHiddenTextInput();
    // Blindaje extra: alejar el panel del origen para que un rayo/click
    // perdido mientras estamos en el lobby no pueda "tocarlo" por accidente
    // (three.js sigue probando intersecciones contra objetos invisibles).
    uiGroup.position.set(0, -600, 0);
  }
  hide();

  function tick(tSec) {
    if (!missionActive) return;
    xr.tickXR?.();
    if (Math.abs(state.scrollVel) > 0.01 && state.scrollMax > 0) {
      state.scrollY = THREE.MathUtils.clamp(state.scrollY + state.scrollVel, 0, state.scrollMax);
      state.scrollVel *= 0.86;
      if (state.scrollY <= 0 || state.scrollY >= state.scrollMax) state.scrollVel = 0;
      drawPanel();
    }
    updateAstronaut(tSec);
  }

  function onXRSessionStart() {
    if (!missionActive) return;
    applyPanelScale();
    setTimeout(recenterPanel, 120);
    try { mic?.onXRSessionStart?.(); } catch (_) {}
  }

  function onXRSessionEnd() {
    if (!missionActive) return;
    applyPanelScale();
    centerPanelDesktopLikeReload();
    createDesktopHelp(renderer);
  }

  function resize() {
    applyPanelScale();
    if (!renderer.xr.isPresenting) centerPanelDesktopLikeReload();
  }

  return {
    setPlanet: buildEnvironment,
    show,
    hide,
    tick,
    onXRSessionStart,
    onXRSessionEnd,
    resize,
    recenterPanel,
    isActive: () => missionActive,
    // Se exponen para que quien llame a initMission() pueda "pegar" objetos
    // extra junto al panel de chat (por ejemplo, paneles de datos del
    // planeta a los costados) — al ser hijos de uiGroup, se mueven y giran
    // siempre junto con el panel, como un solo bloque.
    uiGroup,
    panelMesh,
  };
}
