<template>
    <div id="app" class="app-container" v-cloak>

        <div class="app-body">
            <AppSidebar
                @logout="handleLogout"
            />
            <!-- Mobile: spacer reserves rail width when sidebar is absolute/overlay -->
            <div class="sidebar-spacer" />

            <div class="main-column">
                <div class="content-area">
                    <TtsPlayerBar />
                    <!-- Socket views: v-if=visited (lazy first mount), class-based hide (preserve scroll) -->
                    <AgentsView v-if="visited.agents" :class="{ 'view-hidden': route.name !== 'agents' }" />
                    <ViewerView v-if="visited.viewer" :class="{ 'view-hidden': route.name !== 'viewer' }" />
                    <DevView    v-if="visited.dev"    :class="{ 'view-hidden': route.name !== 'dev' }" />
                    <!-- Non-socket views: normal router -->
                    <RouterView v-if="routerReady && !isSocketRoute" />
                </div>
                <GridOverlay />
                <BreakpointBadge />
            </div>
        </div>
    </div>
</template>

<script setup lang="ts">
import { ref, reactive, onMounted, computed, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useUI } from './composables/ui';
import { ws, auth, agents } from './store';
import { THEME_LOGOS, useTheme } from './composables/useTheme';
import { useChatStore } from './store/chat';

import { defineAsyncComponent } from 'vue';

import GridOverlay from './components/GridOverlay.vue';
import BreakpointBadge from './components/BreakpointBadge.vue';
import AppSidebar from './components/AppSidebar.vue';
import router from './router';

import TtsPlayerBar from './components/TtsPlayerBar.vue';
const AgentsView = defineAsyncComponent(() => import('./views/AgentsView.vue'));
const ViewerView = defineAsyncComponent(() => import('./views/ViewerView.vue'));
const DevView = defineAsyncComponent(() => import('./views/DevView.vue'));

const chatStore = useChatStore();
const visited = reactive({ agents: false, viewer: false, dev: false });
const isSocketRoute = computed(() => ['agents', 'viewer', 'dev'].includes(route.name as string));

const routerReady = ref(false);
router.isReady().then(() => { routerReady.value = true; });

const route = useRoute();


const { version } = useUI(ws.status);
const { isLoggedIn, doLogout } = auth;
const { currentUser, connected, status, sessionId, disconnect, onMessage: onWsMessage, replayBuffer } = ws;
const { selectedAgent, updateFromServer } = agents;

const { theme } = useTheme();

function handleLogout() {
    doLogout(disconnect);
    visited.agents = visited.viewer = visited.dev = false;
}

function maybeConnect() {
    if (auth.isLoggedIn.value && !ws.connected.value) {
        ws.connect(agents.selectedAgent, auth.isLoggedIn, auth.loginError, agents.selectedMode);
    }
}

router.beforeEach((to) => {
    if (to.meta?.requiresSocket) {
        if (!auth.isLoggedIn.value) return { name: 'login' };
        if (!selectedAgent.value) {
            const urlAgent = to.query?.agent as string | undefined;
            const saved = sessionStorage.getItem('agent');
            if (urlAgent) selectedAgent.value = urlAgent;
            else if (saved) selectedAgent.value = saved;
        }
        maybeConnect();
    }
});

// Mark socket views as visited (triggers v-if → first mount)
router.afterEach((to) => {
    const name = to.name as string;
    if (name in visited) (visited as any)[name] = true;
});

// Reset SM state to CONNECTING on disconnect so UI doesn't show stale state
watch(connected, (isConnected) => {
    if (!isConnected) chatStore.setConnecting();
});

// Global handler: connection_state + channel_state apply regardless of active view
onWsMessage((data: any) => {
    if (data.type === 'connection_state' && data.state) {
        chatStore.applyConnectionState(data.state);
    }
    if (data.type === 'channel_state' && data.state) {
        chatStore.applyChannelState(data.state);
    }
});
// Replay buffered connection/channel states (may have arrived before this handler)
replayBuffer((data: any) => {
    if (data.type === 'connection_state' && data.state) chatStore.applyConnectionState(data.state);
    if (data.type === 'channel_state' && data.state) chatStore.applyChannelState(data.state);
});

// Update favicon when theme changes
watch(theme, (t) => {
    const logo = THEME_LOGOS[t];
    const el = document.querySelector<HTMLLinkElement>('link[rel~="icon"]');
    if (el) el.href = logo ?? '/favicon.ico';
});

const beVersion = ref('');
const envLabel = import.meta.env.PROD ? 'prod' : 'dev';
const versionState = ref<'short' | 'full' | 'copied'>('short');
const fullVersionText = computed(() => `[${envLabel}] fe: ${version} | be: ${beVersion.value || '…'}`);
const versionDisplay = computed(() => {
    if (versionState.value === 'short') return version.split('-')[0];
    if (versionState.value === 'copied') return '✓ copied';
    return fullVersionText.value;
});

function cycleVersion() {
    if (versionState.value === 'short') {
        versionState.value = 'full';
    } else if (versionState.value === 'full') {
        navigator.clipboard.writeText(fullVersionText.value).then(() => {
            versionState.value = 'copied';
            setTimeout(() => versionState.value = 'short', 2000);
        });
    } else {
        versionState.value = 'short';
    }
}

onMounted(() => {
    onWsMessage((data: any) => {
        if (data.type === 'ready' || data.type === 'auth_ok') {
            connected.value = true;
            currentUser.value = data.user;
            sessionId.value = data.sessionId;
            status.value = 'Connected';
            if (data.version) { beVersion.value = data.version; chatStore.beVersion = data.version; }
            updateFromServer(data);
            if (route.path === '/login') router.push('/agents');
        } else if (data.type === 'cost_update') {
            chatStore.sessionUsage = data.usage;
            chatStore.sessionCost = data.cost;
        }
    });
});
</script>

<style>
/* styles.css moved to main.ts for independent HMR */

#app {
    height: 100vh;
    width: 100vw;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: transparent;
}

.main-footer {
    height: 28px;
    background: transparent;
    border-top: none;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-dim);
    cursor: pointer;
    user-select: none;
    opacity: 1;
}
.main-footer:hover { opacity: 0.85; }
.main-footer p { margin: 0; }

[v-cloak] { display: none; }

/* Hide inactive socket views without display:none — preserves OverlayScrollbars scroll position */
.view-hidden {
    visibility: hidden;
    position: absolute;
    pointer-events: none;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    overflow: hidden;
}
.content-area { position: relative; }
</style>
