using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
using EventsAppQuest.Application.Services;
using EventsAppQuest.Core;
using EventsAppQuest.Domain.Models;

namespace EventsAppQuest.Presentation
{
    /// <summary>
    /// Controlador principal da cena de apresentação Quest: monta sessão a partir do projeto ativo, exibe HUD e views, e trata navegação entre passos.
    /// Consome dados reais (MRPresentationPlan, GuidedTourSteps, VisualHotspots, SceneStates, DecisionSummary).
    /// </summary>
    public class QuestPresentationController : MonoBehaviour
    {
        private const string FallbackMessage = "Para usar o modo apresentação no Quest, inicie um projeto na tela principal, preencha o briefing, gere a narrativa e o roteiro guiado, e garanta que a proposta tenha um plano Quest/MR (roteiro, hotspots ou estados de cena).";
        private const string BtnVoltar = "Voltar";
        private const string BtnProximo = "Próx. passo";
        private const string BtnAnterior = "Ant. passo";
        private const string BtnHotspotPrincipal = "Hotspot 1";
        private const string BtnProxHotspot = "Próx. hotspot";
        private const string BtnAntHotspot = "Ant. hotspot";
        private const string BtnProxEstado = "Próx. estado";
        private const string BtnEstadoAnt = "Ant. estado";
        private const string BtnModo = "Modo";
        private const string BtnShortFlow = "Fluxo curto";
        private const string BtnStageBack = "Etapa -";
        private const string BtnStageNext = "Etapa +";
        private const string BtnGoClosing = "Ir p/ closing";
        private const string BtnReiniciar = "Reiniciar";
        private const string BtnUIToggle = "UI: HUD/Espacial";
        private const string BtnProxFoco = "Próx. foco";
        private const string BtnAtivarFoco = "Ativar";
        private const string BtnSairFoco = "Sair foco";
        private const string BtnProxAlvo = "Próx. alvo";
        private const string BtnAtivarSel = "Ativar sel.";
        private const string BtnCancelarSel = "Cancelar sel.";
        private const string BtnFechamento = "Fechamento";
        private const string BtnContexto = "Contexto";

        private AppSession _session;
        private IMRPresentationPlanningService _mrPlanning;
        private QuestPresentationSession _questSession;
        private QuestPresentationNavigationController _navController;
        private QuestPresentationFocusController _focusController;
        private QuestSelectionController _selectionController;
        private QuestPointerPreparationController _pointerPrepController;
        private QuestSimulatedPointerController _simulatedPointerController;
        private QuestWorldCursorController _worldCursorController;
        private QuestPresentationInputDispatcher _inputDispatcher;
        private IQuestXRInputBridge _xrInputBridge;
        private QuestXRRaycastController _xrRaycastController;
        private QuestHoverDwellController _hoverDwellController;
        private QuestTargetResolverController _targetResolverController;
        private QuestGazeLikeController _gazeLikeController;
        private QuestHotspotContextController _hotspotContextController;
        private QuestHotspotStoryController _hotspotStoryController;
        private QuestHotspotComparisonController _hotspotComparisonController;
        private QuestClosingExperienceController _closingController;
        private QuestCommercialOutcomeController _commercialOutcomeController;
        private QuestSessionExportController _exportController;
        private QuestSessionLocalPersistenceController _persistenceController;
        private QuestSessionHandoffReportController _handoffController;
        private QuestSessionShareableSummaryController _shareableSummaryController;
        private QuestSessionQualityAssessmentController _qualityAssessmentController;
        private QuestSessionImprovementPlanController _improvementPlanController;
        private QuestPresentationPresetController _presetController;
        private QuestPresentationOnboardingController _onboardingController;
        private QuestShortFlowController _shortFlowController;
        private GameObject _onboardingOverlayRoot;
        private Text _onboardingOverlayTitle;
        private Text _onboardingOverlayBody;
        private Button _onboardingOverlayStartBtn;
        private Button _onboardingOverlayCloseBtn;
        private bool _lastConfirmFromDwell;
        private GameObject _fallbackPanel;
        private GameObject _mainPanel;
        private GameObject _hudContentRoot;
        private bool _useSpatialUI;
        private QuestWorldUIPanelLayout _worldPanelLayout;
        private QuestPresentationHUD _hud;
        private QuestGuidedStepView _stepView;
        private QuestHotspotView _hotspotView;
        private QuestSceneStateView _sceneStateView;
        private Font _font;
        private const float ButtonHeight = 52f;
        private const float ButtonWidth = 140f;

        private void Start()
        {
            _session = ServiceRegistry.Instance.Get<AppSession>();
            _mrPlanning = ServiceRegistry.Instance.Get<IMRPresentationPlanningService>();
            _font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");

            // Se a cena foi iniciada sem `AppBootstrap` (ex.: build técnico apenas com `QuestPresentation`),
            // inicializa o container com mocks para garantir que o vertical slice monta.
            if (_session == null)
            {
                var registry = ServiceRegistry.Instance;
                registry.RegisterMockServices();
                registry.Register<SceneRouter>(new SceneRouter());
                registry.Register<AppSession>(new AppSession());

#if UNITY_ANDROID && !UNITY_EDITOR
                // Substitui o status XR mock por detecção OpenXR básica,
                // garantindo que o vertical slice em Quest tente operar em RealXR.
                registry.Register<IQuestXRRuntimeStatusService>(
                    new EventsAppQuest.Infrastructure.Services.QuestOpenXRRuntimeStatusService());
#endif

                _session = registry.Get<AppSession>();
                _mrPlanning = registry.Get<IMRPresentationPlanningService>();
            }

            // Vertical slice Quest: no Android/Quest, prepara dados mock antes de montar a sessão.
            if (QuestAndroidVerticalSliceSeeder.ShouldSeedForVerticalSlice())
                QuestAndroidVerticalSliceSeeder.TryEnsureSeed(_session);

            // Garante inicialização do loader XR (quando disponível) mesmo se `QuestXRRuntimeBootstrap`
            // não estiver presente na cena (ex.: scene build técnico apenas com QuestPresentation).
            QuestXRLoaderInitializer.TryInitializeXR();

            QuestXRRuntimeBootstrap.RefreshStatus();
            BuildSession();
            EvaluateAndSetReadiness();
            EvaluateAndSetXRStatus();

            if (!HasEnoughData())
            {
                ShowFallback();
                return;
            }
            _navController = gameObject.AddComponent<QuestPresentationNavigationController>();
            _navController.Bind(_questSession);
            _navController.OnNavigationChanged = RefreshAll;

            _focusController = gameObject.AddComponent<QuestPresentationFocusController>();
            _focusController.Bind(_questSession, _navController);
            _focusController.OnFocusChanged = RefreshAll;

            _selectionController = gameObject.AddComponent<QuestSelectionController>();
            _selectionController.Bind(_questSession, _navController);
            _selectionController.OnSelectionChanged = RefreshAll;

            _pointerPrepController = gameObject.AddComponent<QuestPointerPreparationController>();
            _simulatedPointerController = gameObject.AddComponent<QuestSimulatedPointerController>();
            _simulatedPointerController.OnPointerModeChanged = RefreshAll;
            _worldCursorController = gameObject.AddComponent<QuestWorldCursorController>();
            _hoverDwellController = gameObject.AddComponent<QuestHoverDwellController>();
            _targetResolverController = gameObject.AddComponent<QuestTargetResolverController>();
            _gazeLikeController = gameObject.AddComponent<QuestGazeLikeController>();
            _hotspotContextController = gameObject.AddComponent<QuestHotspotContextController>();
            _hotspotContextController.Bind(_questSession);
            _hotspotStoryController = gameObject.AddComponent<QuestHotspotStoryController>();
            _hotspotStoryController.Bind(_questSession);
            _hotspotComparisonController = gameObject.AddComponent<QuestHotspotComparisonController>();
            _hotspotComparisonController.Bind(_questSession);
            _closingController = gameObject.AddComponent<QuestClosingExperienceController>();
            _closingController.Bind(_questSession);
            _commercialOutcomeController = gameObject.AddComponent<QuestCommercialOutcomeController>();
            _commercialOutcomeController.Bind(_questSession);
            _exportController = gameObject.AddComponent<QuestSessionExportController>();
            _exportController.Bind(_questSession);
            _exportController.SetProjectIdGetter(() => _session?.CurrentProject?.Id);
            _persistenceController = gameObject.AddComponent<QuestSessionLocalPersistenceController>();
            _persistenceController.Bind(_questSession);
            _persistenceController.SetProjectIdGetter(() => _session?.CurrentProject?.Id);
            _handoffController = gameObject.AddComponent<QuestSessionHandoffReportController>();
            _handoffController.Bind(_questSession);
            _handoffController.SetProjectIdGetter(() => _session?.CurrentProject?.Id);
            _shareableSummaryController = gameObject.AddComponent<QuestSessionShareableSummaryController>();
            _shareableSummaryController.Bind(_questSession);
            _qualityAssessmentController = gameObject.AddComponent<QuestSessionQualityAssessmentController>();
            _qualityAssessmentController.Bind(_questSession);
            _improvementPlanController = gameObject.AddComponent<QuestSessionImprovementPlanController>();
            _improvementPlanController.Bind(_questSession);
            _presetController = gameObject.AddComponent<QuestPresentationPresetController>();
            _presetController.Bind(_questSession);
            _onboardingController = gameObject.AddComponent<QuestPresentationOnboardingController>();
            _onboardingController.Bind(_questSession);
            _shortFlowController = gameObject.AddComponent<QuestShortFlowController>();
            _shortFlowController.Bind(_questSession, _navController, _onboardingController, _hotspotContextController, _hotspotStoryController, _hotspotComparisonController, _closingController, _presetController);
            _selectionController.OnConfirmed = id =>
            {
                if (_questSession != null) _questSession.LastConfirmationTargetId = id;
                _hoverDwellController?.ConsumeDwellConfirm();
                if (id == "hotspot")
                {
                    _hotspotContextController?.OpenOrToggleForCurrentHotspot(_lastConfirmFromDwell ? "Dwell" : "Selection");
                    if (_questSession != null && _questSession.HasHotspotContextOpen && _questSession.CurrentHotspot != null
                        && _questSession.CurrentHotspotContext?.HotspotId == _questSession.CurrentHotspot.Id)
                        _hotspotStoryController?.BuildAndAssignStorySteps(_questSession.CurrentHotspot);
                }
                _lastConfirmFromDwell = false;
            };

            _inputDispatcher = gameObject.AddComponent<QuestPresentationInputDispatcher>();
            _inputDispatcher.Bind(_navController, _focusController, _selectionController, _pointerPrepController, _simulatedPointerController);
            _inputDispatcher.BindStory(() => _questSession, _hotspotStoryController);

            var inputAdapter = gameObject.AddComponent<QuestPresentationInputAdapter>();
            inputAdapter.BindDispatcher(_inputDispatcher);
            inputAdapter.Bind(_navController);
            inputAdapter.BindFocus(_focusController);
            inputAdapter.BindSelection(_selectionController);
            inputAdapter.BindPointer(_pointerPrepController, _simulatedPointerController);

            _xrInputBridge = new QuestXRInputBridge();
            _xrInputBridge.SetDispatcher(_inputDispatcher);

            BuildMainUI();
            BuildWorldSpaceUIIfPossible();
            BuildWorldCursorIfPossible();

            // No headset, painéis world-space precisam estar visíveis por padrão.
            _useSpatialUI = Application.platform == RuntimePlatform.Android;
            if (_worldPanelLayout != null && _worldPanelLayout.IsBuilt)
                _worldPanelLayout.SetVisible(_useSpatialUI);
            if (_worldCursorController != null && _worldCursorController.IsBuilt)
                _worldCursorController.SetVisible(_useSpatialUI);
            if (_hudContentRoot != null)
                _hudContentRoot.SetActive(!_useSpatialUI);

            if (_questSession != null && !_questSession.HasSeenOnboarding && _questSession.MRPlan != null)
                _onboardingController?.OpenOnboarding();
            RefreshAll();

            // Demo curto automático para você validar no headset.
            if (_questSession != null && QuestAndroidVerticalSliceSeeder.IsQuestAndroidBuild() && QuestAndroidVerticalSliceSeeder.ShouldAutoRunDemo())
                StartCoroutine(RunVerticalSliceDemo());
        }

        private void EvaluateAndSetReadiness()
        {
            var readinessService = ServiceRegistry.Instance.Get<IQuestRuntimeReadinessService>();
            if (readinessService != null && _questSession != null)
                _questSession.RuntimeReadiness = readinessService.EvaluateReadiness(_questSession);
        }

        private void EvaluateAndSetXRStatus()
        {
            if (_questSession == null) return;
            var xrService = ServiceRegistry.Instance.Get<IQuestXRRuntimeStatusService>();
            var status = xrService?.GetStatus();
            if (status == null) return;
            _questSession.RuntimeMode = status.RuntimeMode;
            _questSession.IsXRActive = status.IsXRActive;
            _questSession.IsFallbackActive = status.IsFallbackActive;
            _questSession.XRInitStatus = status.XRInitMessage ?? "";
        }

        private void BuildSession()
        {
            _questSession = new QuestPresentationSession();
            var project = _session?.CurrentProject;
            var proposal = project?.GetActiveProposal();
            if (proposal == null) return;

            if (proposal.MRPresentationPlan == null && project.CurrentMRPresentationPlan != null)
                proposal.MRPresentationPlan = project.CurrentMRPresentationPlan;
            if (proposal.MRPresentationPlan == null && _mrPlanning != null)
                proposal.MRPresentationPlan = _mrPlanning.GeneratePlan(project, proposal);

            var plan = proposal.MRPresentationPlan;
            if (plan == null) return;

            if ((proposal.GuidedTourSteps == null || proposal.GuidedTourSteps.Count == 0) && project.CurrentGuidedTourSteps != null && project.CurrentGuidedTourSteps.Count > 0)
                proposal.GuidedTourSteps = new List<ProposalGuidedTourStep>(project.CurrentGuidedTourSteps);
            if ((proposal.VisualHotspots == null || proposal.VisualHotspots.Count == 0) && project.CurrentVisualHotspots != null && project.CurrentVisualHotspots.Count > 0)
                proposal.VisualHotspots = new List<ProposalVisualHotspot>(project.CurrentVisualHotspots);
            if ((proposal.SceneStates == null || proposal.SceneStates.Count == 0) && project.CurrentSceneStates != null && project.CurrentSceneStates.Count > 0)
                proposal.SceneStates = new List<ProposalSceneState>(project.CurrentSceneStates);

            _questSession.Proposal = proposal;
            _questSession.MRPlan = plan;

            var steps = proposal.GuidedTourSteps ?? new List<ProposalGuidedTourStep>();
            if (plan.GuidedSequenceStepIds != null && plan.GuidedSequenceStepIds.Count > 0)
            {
                var ordered = new List<ProposalGuidedTourStep>();
                foreach (var id in plan.GuidedSequenceStepIds)
                {
                    var s = steps.FirstOrDefault(x => x.Id == id);
                    if (s != null) ordered.Add(s);
                }
                _questSession.GuidedSteps = ordered;
            }
            else
                _questSession.GuidedSteps = steps.OrderBy(s => s.DisplayOrder).ToList();

            var hotspots = proposal.VisualHotspots ?? new List<ProposalVisualHotspot>();
            if (plan.HotspotIds != null && plan.HotspotIds.Count > 0)
            {
                var ordered = new List<ProposalVisualHotspot>();
                foreach (var id in plan.HotspotIds)
                {
                    var h = hotspots.FirstOrDefault(x => x.Id == id);
                    if (h != null) ordered.Add(h);
                }
                _questSession.Hotspots = ordered;
            }
            else
                _questSession.Hotspots = hotspots.OrderBy(h => h.HighlightPriority).ToList();

            var states = proposal.SceneStates ?? new List<ProposalSceneState>();
            if (plan.SceneStateIds != null && plan.SceneStateIds.Count > 0)
            {
                var ordered = new List<ProposalSceneState>();
                foreach (var id in plan.SceneStateIds)
                {
                    var st = states.FirstOrDefault(x => x.Id == id);
                    if (st != null) ordered.Add(st);
                }
                _questSession.SceneStates = ordered;
            }
            else
                _questSession.SceneStates = states.OrderBy(s => s.DisplayOrder).ToList();

            if (project.LastDecisionSummary != null && project.LastRecommendationSummary?.RecommendedProposalId == proposal.Id)
            {
                _questSession.DecisionSummary = project.LastDecisionSummary;
                _questSession.RecommendationSummary = project.LastRecommendationSummary;
            }

            _questSession.CurrentGuidedStepIndex = 0;
            _questSession.CurrentHotspotIndex = _questSession.Hotspots != null && _questSession.Hotspots.Count > 0 ? 0 : -1;
            _questSession.CurrentSceneStateIndex = _questSession.SceneStates != null && _questSession.SceneStates.Count > 0 ? 0 : -1;
            _questSession.NavigationMode = QuestPresentationNavigationMode.Guided;
            _questSession.IsPresentationReady = HasEnoughData();
            _questSession.SessionStartedAtUtc = System.DateTime.UtcNow;
            if (_questSession.GuidedSteps == null) _questSession.GuidedSteps = new List<ProposalGuidedTourStep>();
            if (_questSession.Hotspots == null) _questSession.Hotspots = new List<ProposalVisualHotspot>();
            if (_questSession.SceneStates == null) _questSession.SceneStates = new List<ProposalSceneState>();
        }

        private bool HasEnoughData()
        {
            if (_questSession?.MRPlan == null) return false;
            var hasSteps = _questSession.GuidedSteps != null && _questSession.GuidedSteps.Count > 0;
            var hasHotspots = _questSession.Hotspots != null && _questSession.Hotspots.Count > 0;
            var hasStates = _questSession.SceneStates != null && _questSession.SceneStates.Count > 0;
            return hasSteps || hasHotspots || hasStates;
        }

        private void ShowFallback()
        {
            var canvas = CreateCanvas();
            _fallbackPanel = new GameObject("FallbackPanel");
            _fallbackPanel.transform.SetParent(canvas.transform, false);
            var rt = _fallbackPanel.AddComponent<RectTransform>();
            rt.anchorMin = Vector2.zero;
            rt.anchorMax = Vector2.one;
            rt.offsetMin = new Vector2(40, 40);
            rt.offsetMax = new Vector2(-40, -40);
            var vlg = _fallbackPanel.AddComponent<VerticalLayoutGroup>();
            vlg.spacing = 24;
            vlg.padding = new RectOffset(32, 32, 32, 32);
            vlg.childAlignment = TextAnchor.MiddleCenter;

            var bg = _fallbackPanel.AddComponent<Image>();
            bg.color = new Color(0.15f, 0.15f, 0.2f, 0.98f);

            var msgGo = new GameObject("Message");
            msgGo.transform.SetParent(_fallbackPanel.transform, false);
            msgGo.AddComponent<RectTransform>().sizeDelta = new Vector2(0, 200);
            var msgText = msgGo.AddComponent<Text>();
            msgText.text = FallbackMessage;
            msgText.font = _font;
            msgText.fontSize = 18;
            msgText.alignment = TextAnchor.MiddleCenter;

            var statusReadiness = _questSession?.RuntimeReadiness;
            var statusLine = statusReadiness != null
                ? $"Build: {(statusReadiness.IsBuildReady ? "Sim" : "Não")}  |  Dados: {(statusReadiness.HasPresentationData ? "Sim" : "Não")}  |  Modo teste Quest"
                : "Modo teste Quest";
            var statusGo = new GameObject("ReadinessStatus");
            statusGo.transform.SetParent(_fallbackPanel.transform, false);
            statusGo.AddComponent<RectTransform>().sizeDelta = new Vector2(0, 28);
            var statusText = statusGo.AddComponent<Text>();
            statusText.text = statusLine;
            statusText.font = _font;
            statusText.fontSize = 14;
            statusText.alignment = TextAnchor.MiddleCenter;

            var xrLine = _questSession != null
                ? $"Runtime: {_questSession.RuntimeMode}  |  XR: {( _questSession.IsXRActive ? "Sim" : "Não")}  |  Fallback: {(_questSession.IsFallbackActive ? "Sim" : "Não")}"
                : "Runtime: —";
            var xrGo = new GameObject("XRStatus");
            xrGo.transform.SetParent(_fallbackPanel.transform, false);
            xrGo.AddComponent<RectTransform>().sizeDelta = new Vector2(0, 20);
            var xrText = xrGo.AddComponent<Text>();
            xrText.text = xrLine;
            xrText.font = _font;
            xrText.fontSize = 12;
            xrText.alignment = TextAnchor.MiddleCenter;
            xrText.color = new Color(0.7f, 0.72f, 0.76f, 1f);

            AddButton(_fallbackPanel.transform, BtnVoltar, () => GoBack(), 48);
        }

        private void BuildMainUI()
        {
            var canvas = CreateCanvas();
            _mainPanel = new GameObject("MainPanel");
            _mainPanel.transform.SetParent(canvas.transform, false);
            var rt = _mainPanel.AddComponent<RectTransform>();
            rt.anchorMin = Vector2.zero;
            rt.anchorMax = Vector2.one;
            rt.offsetMin = new Vector2(24, 24);
            rt.offsetMax = new Vector2(-24, -24);
            var vlg = _mainPanel.AddComponent<VerticalLayoutGroup>();
            vlg.spacing = 16;
            vlg.padding = new RectOffset(24, 24, 24, 24);
            vlg.childForceExpandHeight = false;

            var bg = _mainPanel.AddComponent<Image>();
            bg.color = new Color(0.12f, 0.14f, 0.18f, 0.95f);

            _hudContentRoot = new GameObject("HudContentRoot");
            _hudContentRoot.transform.SetParent(_mainPanel.transform, false);
            _hudContentRoot.AddComponent<RectTransform>().anchorMin = new Vector2(0, 0);
            _hudContentRoot.GetComponent<RectTransform>().anchorMax = new Vector2(1, 1);
            _hudContentRoot.GetComponent<RectTransform>().offsetMin = Vector2.zero;
            _hudContentRoot.GetComponent<RectTransform>().offsetMax = Vector2.zero;
            var contentVlg = _hudContentRoot.AddComponent<VerticalLayoutGroup>();
            contentVlg.spacing = 16;
            contentVlg.padding = new RectOffset(0, 0, 0, 0);
            contentVlg.childForceExpandHeight = false;
            var contentLe = _hudContentRoot.AddComponent<LayoutElement>();
            contentLe.flexibleHeight = 1;

            _hud = _hudContentRoot.AddComponent<QuestPresentationHUD>();
            _hud.Build(_hudContentRoot.transform, _font);
            if (Application.platform == RuntimePlatform.Android)
            {
                // Reduce visual/debug overload no headset para o vertical slice técnico.
                _hud.ShowInputDebugLine = false;
                _hud.ShowXRDebugLine = true;
                _hud.ShowXRInputDebugLine = false;
                _hud.ShowXRRaycastDebugLine = false;
                _hud.ShowHoverDwellDebugLine = false;
                _hud.ShowTargetingDebugLine = false;
                _hud.ShowGazeLikeDebugLine = false;
                _hud.ShowHotspotContextDebugLine = false;
                _hud.ShowComparisonDebugLine = false;
                _hud.ShowClosingDebugLine = true;
            }

            _stepView = _hudContentRoot.AddComponent<QuestGuidedStepView>();
            _stepView.Build(_hudContentRoot.transform, _font);

            _hotspotView = _hudContentRoot.AddComponent<QuestHotspotView>();
            _hotspotView.Build(_hudContentRoot.transform, _font);

            _sceneStateView = _hudContentRoot.AddComponent<QuestSceneStateView>();
            _sceneStateView.Build(_hudContentRoot.transform, _font);

            var navRow = new GameObject("NavRow");
            navRow.transform.SetParent(_mainPanel.transform, false);
            navRow.AddComponent<LayoutElement>().preferredHeight = ButtonHeight + 8;
            var hlg = navRow.AddComponent<HorizontalLayoutGroup>();
            hlg.spacing = 10;
            hlg.childForceExpandWidth = false;
            hlg.childControlSize = true;

            AddButton(navRow.transform, BtnAnterior, () => _navController?.PreviousStep(), ButtonHeight);
            AddButton(navRow.transform, BtnProximo, () => _navController?.NextStep(), ButtonHeight);
            AddButton(navRow.transform, BtnAntHotspot, () => _navController?.PreviousHotspot(), ButtonHeight);
            AddButton(navRow.transform, BtnProxHotspot, () => _navController?.NextHotspot(), ButtonHeight);
            AddButton(navRow.transform, BtnHotspotPrincipal, () => _navController?.GoToMainHotspot(), ButtonHeight);
            AddButton(navRow.transform, BtnEstadoAnt, () => _navController?.PreviousSceneState(), ButtonHeight);
            AddButton(navRow.transform, BtnProxEstado, () => _navController?.NextSceneState(), ButtonHeight);
            AddButton(navRow.transform, BtnModo, () => _navController?.ToggleNavigationMode(), ButtonHeight);
            AddButton(navRow.transform, BtnShortFlow, () => { _shortFlowController?.ToggleFlowMode(); RefreshAll(); }, ButtonHeight);
            AddButton(navRow.transform, BtnStageBack, () => { _shortFlowController?.PreviousCoreStage(); RefreshAll(); }, ButtonHeight);
            AddButton(navRow.transform, BtnStageNext, () => { _shortFlowController?.NextCoreStage(); RefreshAll(); }, ButtonHeight);
            AddButton(navRow.transform, BtnGoClosing, () => { _shortFlowController?.TryGoToClosing(); RefreshAll(); }, ButtonHeight);
            AddButton(navRow.transform, BtnReiniciar, () => _navController?.RestartPresentation(), ButtonHeight);
            AddButton(navRow.transform, BtnProxFoco, () => _focusController?.NextFocus(), ButtonHeight);
            AddButton(navRow.transform, BtnAtivarFoco, () => _focusController?.ActivateFocus(), ButtonHeight);
            AddButton(navRow.transform, BtnSairFoco, () => _focusController?.ExitFocusMode(), ButtonHeight);
            AddButton(navRow.transform, BtnProxAlvo, () => _selectionController?.NextSelectable(), ButtonHeight);
            AddButton(navRow.transform, BtnAtivarSel, () => _selectionController?.ConfirmSelection(), ButtonHeight);
            AddButton(navRow.transform, BtnCancelarSel, () => _selectionController?.CancelSelection(), ButtonHeight);
            AddButton(navRow.transform, BtnFechamento, () => _closingController?.OpenClosing(), ButtonHeight);
            AddButton(navRow.transform, BtnContexto, () => _onboardingController?.OpenOnboarding(), ButtonHeight);
            AddButton(navRow.transform, BtnUIToggle, () => ToggleSpatialUI(), ButtonHeight);
            AddButton(navRow.transform, BtnVoltar, () => GoBack(), ButtonHeight);

            BuildOnboardingOverlay(canvas.transform);
        }

        private void BuildOnboardingOverlay(Transform canvasTransform)
        {
            _onboardingOverlayRoot = new GameObject("OnboardingOverlay");
            _onboardingOverlayRoot.transform.SetParent(canvasTransform, false);
            var rt = _onboardingOverlayRoot.AddComponent<RectTransform>();
            rt.anchorMin = Vector2.zero;
            rt.anchorMax = Vector2.one;
            rt.offsetMin = Vector2.zero;
            rt.offsetMax = Vector2.zero;
            var bg = _onboardingOverlayRoot.AddComponent<Image>();
            bg.color = new Color(0.1f, 0.14f, 0.18f, 0.96f);
            var vlg = _onboardingOverlayRoot.AddComponent<VerticalLayoutGroup>();
            vlg.spacing = 12;
            vlg.padding = new RectOffset(48, 48, 48, 48);
            vlg.childAlignment = TextAnchor.MiddleCenter;
            vlg.childForceExpandHeight = false;

            _onboardingOverlayTitle = new GameObject("OnboardingTitle").AddComponent<Text>();
            _onboardingOverlayTitle.transform.SetParent(_onboardingOverlayRoot.transform, false);
            _onboardingOverlayTitle.font = _font;
            _onboardingOverlayTitle.fontSize = 28;
            _onboardingOverlayTitle.color = Color.white;
            _onboardingOverlayTitle.alignment = TextAnchor.MiddleCenter;
            _onboardingOverlayTitle.text = "Apresentação Quest";
            _onboardingOverlayTitle.gameObject.AddComponent<RectTransform>().sizeDelta = new Vector2(600f, 36f);
            _onboardingOverlayTitle.gameObject.AddComponent<LayoutElement>().preferredHeight = 36f;

            _onboardingOverlayBody = new GameObject("OnboardingBody").AddComponent<Text>();
            _onboardingOverlayBody.transform.SetParent(_onboardingOverlayRoot.transform, false);
            _onboardingOverlayBody.font = _font;
            _onboardingOverlayBody.fontSize = 16;
            _onboardingOverlayBody.color = new Color(0.9f, 0.9f, 0.92f, 1f);
            _onboardingOverlayBody.alignment = TextAnchor.MiddleCenter;
            _onboardingOverlayBody.gameObject.AddComponent<RectTransform>().sizeDelta = new Vector2(700f, 220f);
            _onboardingOverlayBody.gameObject.AddComponent<LayoutElement>().preferredHeight = 220f;

            var btnRow = new GameObject("OnboardingBtnRow");
            btnRow.transform.SetParent(_onboardingOverlayRoot.transform, false);
            btnRow.AddComponent<LayoutElement>().preferredHeight = 52f;
            var hlg = btnRow.AddComponent<HorizontalLayoutGroup>();
            hlg.spacing = 16;
            hlg.childForceExpandWidth = false;
            _onboardingOverlayStartBtn = AddButton(btnRow.transform, "Iniciar apresentação", () => { _onboardingController?.StartPresentation(); RefreshAll(); }, 48);
            _onboardingOverlayCloseBtn = AddButton(btnRow.transform, "Fechar", () => { _onboardingController?.CloseOnboarding(); RefreshAll(); }, 48);

            _onboardingOverlayRoot.SetActive(false);
        }

        private void BuildWorldSpaceUIIfPossible()
        {
            Transform rigTransform = null;
            var xrRig = FindObjectOfType<QuestXRPresentationRig>();
            if (xrRig != null)
                rigTransform = xrRig.transform;
            else
            {
                var rig = FindObjectOfType<QuestPresentationCameraRig>();
                if (rig != null)
                    rigTransform = rig.transform;
            }
            if (rigTransform == null)
            {
                var cam = Camera.main;
                if (cam != null)
                {
                    var rigGo = new GameObject("QuestXRPresentationRig");
                    cam.transform.SetParent(rigGo.transform, true);
                    rigGo.AddComponent<QuestXRPresentationRig>();
                    rigTransform = rigGo.transform;
                }
            }
            if (rigTransform == null) return;

            var cam = rigTransform.GetComponentInChildren<Camera>(true);
            if (cam != null && cam.GetComponent<QuestHMDPoseDriver>() == null)
                cam.gameObject.AddComponent<QuestHMDPoseDriver>();
            _xrRaycastController = gameObject.AddComponent<QuestXRRaycastController>();
            _xrRaycastController.SetRayOrigin(cam != null ? cam.transform : rigTransform);

            var worldRoot = new GameObject("QuestWorldUIRoot");
            _worldPanelLayout = worldRoot.AddComponent<QuestWorldUIPanelLayout>();
            _worldPanelLayout.Build(rigTransform, _font);
            _worldPanelLayout.OnCloseHotspotContextRequested = () => _hotspotContextController?.CloseContext();
            _worldPanelLayout.OnStartHotspotStoryRequested = () => _hotspotStoryController?.StartStory();
            _worldPanelLayout.OnPreviousStoryStepRequested = () => _hotspotStoryController?.PreviousStep();
            _worldPanelLayout.OnNextStoryStepRequested = () => _hotspotStoryController?.NextStep();
            _worldPanelLayout.OnCloseHotspotStoryRequested = () => _hotspotStoryController?.CloseStory();
            _worldPanelLayout.OnOpenComparisonRequested = () => _hotspotComparisonController?.OpenComparisonFromCurrentHotspot("ContextPanel");
            _worldPanelLayout.OnSwapComparisonRequested = () => _hotspotComparisonController?.SwapSides();
            _worldPanelLayout.OnCloseComparisonRequested = () => _hotspotComparisonController?.CloseComparison();
            _worldPanelLayout.OnCloseClosingRequested = () => _closingController?.CloseClosing();
            _worldPanelLayout.OnConfirmPrimaryCTARequested = () =>
            {
                _commercialOutcomeController?.RecordFromClosing("PrimaryCTA");
                _closingController?.ConfirmPrimaryCTA();
            };
            _worldPanelLayout.OnConfirmSecondaryCTARequested = () =>
            {
                _commercialOutcomeController?.RecordFromClosing("SecondaryCTA");
                _closingController?.ConfirmSecondaryCTA();
            };
            _worldPanelLayout.OnMarkNeedsFollowUpRequested = () => _commercialOutcomeController?.MarkNeedsFollowUp();
            _worldPanelLayout.OnMarkReadyToAdvanceRequested = () => _commercialOutcomeController?.MarkReadyToAdvance();
            _worldPanelLayout.OnBackToClosingRequested = () => _closingController?.OpenClosing();
            _worldPanelLayout.OnConfirmFinalDecisionRequested = () => _commercialOutcomeController?.ConfirmFinalDecision();
            _worldPanelLayout.OnGenerateExportRequested = () => _exportController?.GeneratePayload();
            _worldPanelLayout.OnRegenerateExportRequested = () => _exportController?.GeneratePayload();
            _worldPanelLayout.OnClearExportRequested = () => _exportController?.ClearPayload();
            _worldPanelLayout.OnBackToOutcomeRequested = () => _exportController?.ClearPayload();
            _worldPanelLayout.OnSaveSessionRequested = () => _persistenceController?.Save();
            _worldPanelLayout.OnRestoreSessionRequested = () =>
            {
                if (_persistenceController != null && _questSession != null && _persistenceController.Restore(open => { if (open) _closingController?.OpenClosing(); }))
                    RefreshAll();
            };
            _worldPanelLayout.OnClearSavedSessionRequested = () => _persistenceController?.Clear();
            _worldPanelLayout.OnGenerateHandoffRequested = () =>
            {
                _handoffController?.GenerateReport();
                RefreshAll();
            };
            _worldPanelLayout.OnRegenerateHandoffRequested = () =>
            {
                _handoffController?.GenerateReport();
                RefreshAll();
            };
            _worldPanelLayout.OnClearHandoffRequested = () =>
            {
                _handoffController?.ClearReport();
                RefreshAll();
            };
            _worldPanelLayout.OnBackFromHandoffRequested = () =>
            {
                _handoffController?.ClearReport();
                RefreshAll();
            };
            _worldPanelLayout.OnGenerateShareableRequested = () =>
            {
                _shareableSummaryController?.GenerateSummary();
                RefreshAll();
            };
            _worldPanelLayout.OnRegenerateShareableRequested = () =>
            {
                _shareableSummaryController?.GenerateSummary();
                RefreshAll();
            };
            _worldPanelLayout.OnClearShareableRequested = () =>
            {
                _shareableSummaryController?.ClearSummary();
                RefreshAll();
            };
            _worldPanelLayout.OnCopyShareableRequested = () => _shareableSummaryController?.CopyToClipboard();
            _worldPanelLayout.OnCalculateQualityRequested = () =>
            {
                _qualityAssessmentController?.CalculateAssessment();
                RefreshAll();
            };
            _worldPanelLayout.OnRecalculateQualityRequested = () =>
            {
                _qualityAssessmentController?.CalculateAssessment();
                RefreshAll();
            };
            _worldPanelLayout.OnClearQualityRequested = () =>
            {
                _qualityAssessmentController?.ClearAssessment();
                RefreshAll();
            };
            _worldPanelLayout.OnBackFromQualityRequested = () => RefreshAll();
            _worldPanelLayout.OnGenerateImprovementPlanRequested = () =>
            {
                _improvementPlanController?.GeneratePlan();
                RefreshAll();
            };
            _worldPanelLayout.OnRegenerateImprovementPlanRequested = () =>
            {
                _improvementPlanController?.GeneratePlan();
                RefreshAll();
            };
            _worldPanelLayout.OnClearImprovementPlanRequested = () =>
            {
                _improvementPlanController?.ClearPlan();
                RefreshAll();
            };
            _worldPanelLayout.OnBackFromImprovementPlanRequested = () => RefreshAll();
            _worldPanelLayout.OnApplyPresetRequested = id =>
            {
                if (!string.IsNullOrEmpty(id) && _presetController.ApplyPresetById(id))
                    RefreshAll();
            };
            _worldPanelLayout.OnClearPresetRequested = () =>
            {
                _presetController?.ClearPreset();
                RefreshAll();
            };
            _worldPanelLayout.OnStartPresentationRequested = () =>
            {
                _onboardingController?.StartPresentation();
                RefreshAll();
            };
            _worldPanelLayout.OnCloseOnboardingRequested = () =>
            {
                _onboardingController?.CloseOnboarding();
                RefreshAll();
            };
            _worldPanelLayout.SetVisible(false);
        }

        private void BuildWorldCursorIfPossible()
        {
            if (_worldPanelLayout == null || !_worldPanelLayout.IsBuilt) return;
            var cursorParent = _worldPanelLayout.GetCursorParent();
            if (cursorParent != null && _font != null && _worldCursorController != null)
            {
                _worldCursorController.Build(cursorParent, _font);
                _worldCursorController.SetVisible(false);
            }
        }

        private void ToggleSpatialUI()
        {
            _useSpatialUI = !_useSpatialUI;
            if (_hudContentRoot != null)
                _hudContentRoot.SetActive(!_useSpatialUI);
            if (_worldPanelLayout != null && _worldPanelLayout.IsBuilt)
                _worldPanelLayout.SetVisible(_useSpatialUI);
            if (_worldCursorController != null && _worldCursorController.IsBuilt)
                _worldCursorController.SetVisible(_useSpatialUI);
        }

        private GameObject CreateCanvas()
        {
            var canvasGo = new GameObject("QuestCanvas");
            var canvas = canvasGo.AddComponent<Canvas>();
            canvas.renderMode = RenderMode.ScreenSpaceOverlay;
            canvasGo.AddComponent<CanvasScaler>().uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
            canvasGo.GetComponent<CanvasScaler>().referenceResolution = new Vector2(1920, 1080);
            canvasGo.AddComponent<GraphicRaycaster>();
            return canvasGo;
        }

        private void AddButton(Transform parent, string label, System.Action onClick, float height)
        {
            var go = new GameObject("Btn_" + label);
            go.transform.SetParent(parent, false);
            go.AddComponent<RectTransform>().sizeDelta = new Vector2(ButtonWidth, height);
            var le = go.AddComponent<LayoutElement>();
            le.preferredWidth = ButtonWidth;
            le.preferredHeight = height;
            var img = go.AddComponent<Image>();
            img.color = new Color(0.22f, 0.38f, 0.55f, 1f);
            var btn = go.AddComponent<Button>();
            btn.targetGraphic = img;
            btn.onClick.AddListener(() => onClick?.Invoke());
            var textGo = new GameObject("Text");
            textGo.transform.SetParent(go.transform, false);
            var textRt = textGo.AddComponent<RectTransform>();
            textRt.anchorMin = Vector2.zero;
            textRt.anchorMax = Vector2.one;
            textRt.offsetMin = Vector2.zero;
            textRt.offsetMax = Vector2.zero;
            var t = textGo.AddComponent<Text>();
            t.text = label;
            t.font = _font;
            t.fontSize = 15;
            t.alignment = TextAnchor.MiddleCenter;
            t.color = Color.white;
        }

        private void GoBack()
        {
            var router = ServiceRegistry.Instance.Get<SceneRouter>();
            if (router != null)
                router.LoadScene("Main");
            else
                UnityEngine.SceneManagement.SceneManager.LoadScene("Main");
        }

        private void Update()
        {
            if (_xrInputBridge != null)
                _xrInputBridge.ProcessFrame();
            if (Input.GetKeyDown(KeyCode.U) && _worldPanelLayout != null && _worldPanelLayout.IsBuilt)
                ToggleSpatialUI();
            if (_questSession != null && _pointerPrepController != null)
                UpdatePointerAndHover();
        }

        /// <summary>Actualiza o apontamento (raycast/simulado) e o estado de hover/dwell; chamado cada frame e a partir de RefreshAll.</summary>
        private void UpdatePointerAndHover()
        {
            if (_pointerPrepController == null) return;
            _pointerPrepController.RefreshPointableTargets(_questSession);

            bool useXRRaycast = _xrRaycastController != null && _xrRaycastController.IsAvailable()
                && (_questSession != null && (_questSession.IsXRActive || (_xrInputBridge?.GetStatus().IsXRInputAvailable == true)));
            if (useXRRaycast)
            {
                var xrHit = _xrRaycastController.PerformRaycast();
                if (xrHit != null && xrHit.IsValidTarget)
                {
                    _pointerPrepController.SetPointedFromXRRaycast(xrHit);
                    if (_questSession != null)
                    {
                        _questSession.LastXRRaycastHit = xrHit;
                        _questSession.XRRaycastActive = true;
                    }
                }
                else
                {
                    _pointerPrepController.SetPointedFromXRRaycast(null);
                    if (_questSession != null)
                    {
                        _questSession.LastXRRaycastHit = _xrRaycastController.LastHit;
                        _questSession.XRRaycastActive = false;
                    }
                    if (_simulatedPointerController != null)
                    {
                        var hit = _simulatedPointerController.PerformSimulatedHit(
                            _questSession, _focusController, _selectionController, _pointerPrepController.PointableTargets);
                        _pointerPrepController.SetPointedFromSimulatedHit(hit);
                    }
                    else
                        _pointerPrepController.SetPointedFromSelection(_selectionController);
                }
            }
            else
            {
                if (_questSession != null)
                {
                    _questSession.XRRaycastActive = false;
                    _questSession.LastXRRaycastHit = _xrRaycastController?.LastHit;
                }
                if (_simulatedPointerController != null)
                {
                    var hit = _simulatedPointerController.PerformSimulatedHit(
                        _questSession, _focusController, _selectionController, _pointerPrepController.PointableTargets);
                    _pointerPrepController.SetPointedFromSimulatedHit(hit);
                }
                else
                    _pointerPrepController.SetPointedFromSelection(_selectionController);
            }

            var pointedId = _pointerPrepController.GetPointedTargetId();
            var rawSourceMode = (_pointerPrepController != null && _pointerPrepController.IsPointedFromXRRaycast)
                ? QuestTargetSourceMode.XRRaycast
                : QuestTargetSourceMode.SimulatedPointer;

            var aimingMode = _questSession?.AimingMode ?? QuestAimingMode.XRRaycastPreferred;
            string inputToResolver = pointedId;
            bool currentTargetFromGazeLike = false;

            if (aimingMode == QuestAimingMode.FallbackOnly)
            {
                inputToResolver = null;
                _gazeLikeController?.UpdateGazeLike(null, Time.deltaTime, "FallbackOnly");
            }
            else
            {
                var gazeDesc = rawSourceMode == QuestTargetSourceMode.XRRaycast ? "XRRaycast" : "SimulatedPointer";
                _gazeLikeController?.UpdateGazeLike(pointedId, Time.deltaTime, gazeDesc);

                if ((aimingMode == QuestAimingMode.GazeLikePreferred || aimingMode == QuestAimingMode.Hybrid)
                    && _gazeLikeController?.CurrentState != null
                    && _gazeLikeController.CurrentState.IsStableEnough
                    && !string.IsNullOrEmpty(_gazeLikeController.CurrentState.CurrentGazeTargetId))
                {
                    inputToResolver = _gazeLikeController.CurrentState.CurrentGazeTargetId;
                    currentTargetFromGazeLike = true;
                }
            }

            if (_questSession != null)
            {
                _questSession.GazeLikeState = _gazeLikeController?.CurrentState;
                _questSession.CurrentTargetFromGazeLike = currentTargetFromGazeLike;
            }

            var resolved = _targetResolverController?.Resolve(
                inputToResolver,
                currentTargetFromGazeLike ? QuestTargetSourceMode.XRRaycast : rawSourceMode,
                _questSession?.FocusedTargetType ?? QuestPresentationFocusTargetType.GuidedStepPanel,
                _selectionController?.GetSelectedTarget()?.TargetId,
                _pointerPrepController?.PointableTargets);
            if (_questSession != null)
            {
                _questSession.DetectedTargetId = pointedId;
                _questSession.ResolvedTargetId = resolved?.TargetId;
                _questSession.ResolvedTargetSourceMode = resolved?.SourceMode ?? rawSourceMode;
                _questSession.StickinessAppliedLastFrame = _targetResolverController?.StickinessAppliedThisFrame ?? false;
            }

            var resolvedId = resolved?.TargetId ?? inputToResolver ?? pointedId;
            var hoverSourceMode = rawSourceMode == QuestTargetSourceMode.XRRaycast ? QuestHoverSourceMode.XRRaycast : QuestHoverSourceMode.SimulatedPointer;
            _hoverDwellController?.UpdateHover(Time.deltaTime, resolvedId, hoverSourceMode);
            if (_questSession != null)
                _questSession.CurrentHoverState = _hoverDwellController?.CurrentState;

            if (_questSession != null && _hoverDwellController != null && _selectionController != null)
            {
                var mode = _questSession.ConfirmationMode;
                if ((mode == QuestTargetConfirmationMode.DwellOnly || mode == QuestTargetConfirmationMode.DwellOrExplicit)
                    && _hoverDwellController.IsDwellReadyToConfirm())
                {
                    var pointed = _pointerPrepController?.GetPointableTargetById(resolvedId ?? pointedId);
                    if (pointed != null)
                    {
                        _lastConfirmFromDwell = (resolvedId == "hotspot" || pointedId == "hotspot");
                        _selectionController.ConfirmPointedTarget(pointed);
                        _hoverDwellController.ConsumeDwellConfirm();
                    }
                }

        private IEnumerator RunVerticalSliceDemo()
        {
            // Aguarda 1 frame para UI/painéis atualizarem.
            yield return null;
            yield return new WaitForSeconds(0.25f);

            // Se houver snapshot anterior, restaura e interrompe o demo.
            if (_persistenceController != null && _persistenceController.HasRestorableSnapshot())
            {
                _persistenceController.Restore(open =>
                {
                    if (open)
                        _closingController?.OpenClosing();
                });
                RefreshAll();
                yield break;
            }

            // 1) abrir app e entrar na apresentação.
            if (_onboardingController != null && _questSession != null && !_questSession.HasSeenOnboarding)
                _onboardingController.StartPresentation();

            // 2) aplicar preset rápido (opcional, mas acelera o "fechamento").
            _presetController?.ApplyPresetById("QuickPitch");
            RefreshAll();

            yield return new WaitForSeconds(0.35f);

            // 3) navegar por algumas etapas.
            _navController?.NextStep();
            yield return null;
            _navController?.NextStep();
            RefreshAll();

            yield return new WaitForSeconds(0.25f);

            // 4) abrir hotspots + micro-narrativa.
            if (_questSession?.CurrentHotspot != null)
            {
                _hotspotContextController?.OpenOrToggleForCurrentHotspot("VerticalSliceDemo_Context");
                _hotspotStoryController?.BuildAndAssignStorySteps(_questSession.CurrentHotspot);
                _hotspotStoryController?.StartStory();
                RefreshAll();
            }

            yield return new WaitForSeconds(0.35f);

            // 5) comparar duas opções (troca sides uma vez).
            _hotspotComparisonController?.OpenComparisonFromCurrentHotspot("VerticalSliceDemo_Compare");
            RefreshAll();

            yield return new WaitForSeconds(0.35f);
            _hotspotComparisonController?.SwapSides();
            RefreshAll();

            yield return new WaitForSeconds(0.25f);
            _hotspotComparisonController?.CloseComparison();
            RefreshAll();

            // 6) abrir fechamento comercial + registrar CTA.
            yield return new WaitForSeconds(0.25f);
            _closingController?.OpenClosing();
            RefreshAll();

            yield return new WaitForSeconds(0.45f);
            _commercialOutcomeController?.RecordFromClosing("PrimaryCTA");
            RefreshAll();

            // 7) persistência local mínima (snapshot com closing aberto).
            yield return new WaitForSeconds(0.15f);
            _persistenceController?.Save();
            RefreshAll();

            // (Validação) tenta restaurar para confirmar que o storage funciona.
            yield return new WaitForSeconds(0.25f);
            if (_persistenceController != null)
            {
                _persistenceController.Restore(open =>
                {
                    if (open)
                        _closingController?.OpenClosing();
                });
                RefreshAll();
            }
        }
            }
        }

        private void RefreshAll()
        {
            _selectionController?.RefreshTargets();
            UpdatePointerAndHover();
            if (_questSession != null && _xrInputBridge != null)
                _questSession.XRInputStatus = _xrInputBridge.GetStatus();
            _shortFlowController?.RefreshState();
            _hud?.Refresh(_questSession, _selectionController, _shortFlowController?.Current, _pointerPrepController, _simulatedPointerController, _inputDispatcher, _hoverDwellController, _targetResolverController, _gazeLikeController);
            _stepView?.Refresh(_questSession);
            _hotspotView?.Refresh(_questSession);
            _sceneStateView?.Refresh(_questSession);
            var resolvedId = _targetResolverController?.ResolvedTargetId ?? _pointerPrepController?.GetPointedTargetId();
            if (_persistenceController != null && _questSession != null)
                _questSession.RestorableSnapshotAvailable = _persistenceController.HasRestorableSnapshot();
            _worldPanelLayout?.Refresh(_questSession, _selectionController, _pointerPrepController, _hoverDwellController, resolvedId);
            if (_worldCursorController != null && _worldCursorController.IsBuilt)
                _worldCursorController.Refresh(_pointerPrepController?.GetPointableTargetById(resolvedId ?? _pointerPrepController?.GetPointedTargetId()), _hoverDwellController);
            RefreshOnboardingOverlay();
        }

        private void RefreshOnboardingOverlay()
        {
            if (_onboardingOverlayRoot == null || _questSession == null) return;
            var state = _questSession.CurrentOnboardingState;
            var isOpen = state != null && state.IsOnboardingOpen;
            _onboardingOverlayRoot.SetActive(isOpen);
            if (!isOpen) return;
            _onboardingOverlayTitle.text = state.SessionTitle ?? "Apresentação Quest";
            var body = "Proposta: " + (state.ProposalLabel ?? "—") + "\nModo: " + (state.PresetLabel ?? "Nenhum") + "  |  Runtime: " + (state.RuntimeModeLabel ?? "—") + "\n\n" + (state.PrimaryGoal ?? "Conhecer a proposta e concluir.") + "\n\nDuração estimada: " + (state.EstimatedDurationMinutes > 0 ? state.EstimatedDurationMinutes + " min" : "—");
            if (state.InteractionHints != null && state.InteractionHints.Count > 0)
                body += "\n\n" + string.Join("\n", state.InteractionHints);
            _onboardingOverlayBody.text = body;
            if (_onboardingOverlayStartBtn != null) _onboardingOverlayStartBtn.interactable = state.IsReadyToStart;
        }
    }
}
