<template>
  <div class="app-layout">
    <aside class="sidebar">
      <div class="sidebar-header">
        <img :src="siteLogo" alt="Site logo" class="sidebar-logo" />
        <h1>{{ facilityName }}</h1>
        <!-- Under the site name, where the classic site put it. -->
        <div class="fiscal-week">
          <span class="fiscal-week-label">Fiscal Week</span>
          <span class="fiscal-week-number">{{ currentFiscalWeek }}</span>
        </div>
      </div>

      <div class="sidebar-search">
        <input
          v-model="searchQuery"
          type="text"
          placeholder="Search..."
          @keyup.enter="performSearch"
        />
      </div>

      <nav class="sidebar-nav">
        <template v-for="item in navItems" :key="item.route">
          <div v-if="item.section" class="nav-section">{{ item.section }}</div>
          <router-link :to="item.route">
            <component v-if="item.iconComponent" :is="item.iconComponent" :size="16" />
            {{ item.name }}
          </router-link>
        </template>

        <div v-if="showDisplays" class="nav-section">Displays</div>
        <a v-if="showShopfloor" :href="withBase('/shopfloor')" target="_blank" class="external-link">Shopfloor Dashboard</a>
        <a v-if="hasRoute('/tv')" :href="withBase('/tv')" target="_blank" class="external-link">TV Slideshow</a>
        <a v-if="hasRoute('/parts-kiosk')" :href="withBase('/parts-kiosk')"
           target="_blank" class="external-link">Parts Kiosk</a>

        <router-link v-if="authStore.isAdmin" to="/settings">Settings</router-link>
      </nav>

      <div class="sidebar-footer">
        <button class="theme-toggle" @click="toggleTheme" :title="currentTheme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'">
          <span v-if="currentTheme === 'dark'"><Sun :size="14" /> Light</span>
          <span v-else><Moon :size="14" /> Dark</span>
        </button>

        <div class="user-menu">
          <template v-if="authStore.isAuthenticated">
            <div class="user-identity">
              <img :src="authStore.avatarUrl || fallbackAvatar" class="user-avatar"
                   :class="{ 'ge-avatar-fallback': !authStore.avatarUrl }"
                   :alt="authStore.displayName" @error="onAvatarError" />
              <div class="user-ident">
                <div class="username">{{ authStore.displayName }}</div>
                <div v-if="authStore.displayName !== authStore.username" class="user-sso">{{ authStore.username }}</div>
              </div>
            </div>
            <div class="user-actions">
              <router-link to="/change-password" class="icon-btn" title="Change password">
                <KeyRound :size="16" /> Password
              </router-link>
              <button class="icon-btn" @click="handleLogout" title="Log out">
                <LogOut :size="16" /> Logout
              </button>
            </div>
          </template>
          <router-link v-else to="/login" class="btn btn-primary">Login</router-link>
        </div>
      </div>
    </aside>

    <main class="main-content">
      <div v-if="activeNotifications.length" class="notification-banner">
        <div
          v-for="n in activeNotifications"
          :key="n.notificationid"
          class="notification-item"
          :class="getNotificationType(n)"
        >
          <span class="notification-text">{{ n.notification }}</span>
          <span class="notification-meta">
            <span v-if="n.starttime" class="notification-date">{{ formatDate(n.starttime) }}</span>
            <template v-if="n.ticketnumber">
              <a
                v-if="getTicketSearchUrl(n.ticketnumber)"
                :href="getTicketSearchUrl(n.ticketnumber)"
                target="_blank"
                class="notification-ticket"
              >{{ n.ticketnumber }}</a>
              <span v-else class="notification-ticket">{{ n.ticketnumber }}</span>
            </template>
          </span>
        </div>
      </div>
      <!-- Keyed on path so same-component navigation (machine -> machine via
           a relationship link) remounts and reloads; query-only changes
           (e.g. /reports?report=x) do not remount, and the settings shell
           keys as one unit so its rail survives child navigation. -->
      <router-view :key="routeViewKey" />
    </main>
    <ToastHost />
  </div>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import ToastHost from '../components/ToastHost.vue'
import {
  Sun, Moon, LayoutDashboard, Calendar, Map, Cog, Monitor,
  Printer, Globe, Usb, AppWindow, BookOpen, BarChart3, Bell, Image, ShieldCheck, Ruler,
  Box, KeyRound, LogOut, Wrench
} from 'lucide-vue-next'
import { useAuthStore } from '../stores/auth'
import { currentTheme, toggleTheme } from '../stores/theme'
import { dashboardApi, notificationsApi } from '../api'
import { fiscalWeek } from '@/utils/fiscalWeek'
import { getFacilityName, getSiteLogo, getServicenowUrls } from '../utils/siteSettings'
import { withBase } from '../utils/basePath'

const router = useRouter()
const route = useRoute()
const authStore = useAuthStore()

// The Displays links are hardcoded (not plugin-driven nav), so gate each by
// whether its route was actually staged into THIS build. A lean site without
// slides / printedparts must not show a TV Slideshow / Parts Kiosk link that
// dead-ends on a blank page. Route existence is the true "is it in this build"
// test - more reliable than plugin-enabled state, which a copied registry can
// report for a plugin that was never staged.
const stagedRoutePaths = new Set(router.getRoutes().map(r => r.path))
const hasRoute = (path) => stagedRoutePaths.has(path)
// Shopfloor Dashboard is a core view but runs entirely on the notifications
// plugin (notificationsApi.getShopfloor + the calendar); without it the display
// is empty, so gate the link on the notifications plugin being in this build.
const showShopfloor = hasRoute('/shopfloor')
  && (hasRoute('/notifications') || hasRoute('/calendar'))
const showDisplays = showShopfloor || hasRoute('/tv') || hasRoute('/parts-kiosk')
const routeViewKey = computed(() =>
  route.path.startsWith('/settings') ? '/settings' : route.path
)
const searchQuery = ref('')
const navItems = ref([])
const activeNotifications = ref([])
const facilityName = ref('ShopDB')

// A tab left open overnight would otherwise show last week's number until
// somebody reloaded. Recomputed on a timer rather than only at mount.
const currentFiscalWeek = ref(fiscalWeek())
let fiscalWeekTimer = null
const siteLogo = ref(withBase('/ge-aerospace-logo.svg'))
const servicenowConfig = ref({ enabled: true, searchUrl: '' })

function getTicketSearchUrl(ticketnumber) {
  // Null when ServiceNow is disabled or no search template is set: the
  // ticket number renders as plain text instead of a link.
  const config = servicenowConfig.value
  if (!ticketnumber || !config.enabled || !config.searchUrl) return null
  return config.searchUrl.replace('{ticket}', encodeURIComponent(ticketnumber))
}

// Map backend icon names to Lucide components
const iconMap = {
  'layout-dashboard': LayoutDashboard,
  'calendar': Calendar,
  'map': Map,
  'cog': Cog,
  'desktop': Monitor,
  'printer': Printer,
  'network-wired': Globe,
  'usb': Usb,
  'bell': Bell,
  'app-window': AppWindow,
  'book-open': BookOpen,
  'bar-chart-3': BarChart3,
  'image': Image,
  'shield': ShieldCheck,
  'ruler': Ruler,
  'box': Box,
  'wrench': Wrench,
}

// Default navigation (used as fallback if API fails)
const defaultNav = [
  { name: 'Dashboard', icon: 'layout-dashboard', route: '/', position: 0 },
  { name: 'Notifications', icon: 'bell', route: '/notifications', position: 5 },
  { name: 'Calendar', icon: 'calendar', route: '/calendar', position: 6 },
  { name: 'Map', icon: 'map', route: '/map', position: 4 },
  { name: 'Machines', icon: 'cog', route: '/machines', position: 10 },
  { name: 'PCs', icon: 'desktop', route: '/pcs', position: 15 },
  { name: 'Network', icon: 'network-wired', route: '/network', position: 18 },
  { name: 'Printers', icon: 'printer', route: '/printers', position: 20 },
  { name: 'USB Devices', icon: 'usb', route: '/usb', position: 45 },
  { name: 'Applications', icon: 'app-window', route: '/applications', position: 30, section: 'information' },
  { name: 'Knowledge Base', icon: 'book-open', route: '/knowledgebase', position: 35 },
  { name: 'Reports', icon: 'bar-chart-3', route: '/reports', position: 40 },
]

function buildNavItems(items) {
  // Drop items the user cannot reach: a nav item may carry a `permission`
  // (e.g. Slides -> slides.manage); hide it from anyone who lacks it so the
  // link does not dead-end at the router guard. Admins hold every permission.
  const visible = items.filter(item =>
    !item.permission || authStore.hasPermission(item.permission))

  // Sort by position
  const sorted = [...visible].sort((a, b) => (a.position || 99) - (b.position || 99))

  // Assign section headers based on position ranges
  const result = []
  let currentSection = null

  for (const item of sorted) {
    let section = null
    if (item.position >= 10 && item.position < 30 && currentSection !== 'Assets') {
      section = 'Assets'
      currentSection = 'Assets'
    } else if (item.section === 'information' || (item.position >= 30 && item.position < 50 && currentSection !== 'Information')) {
      if (currentSection !== 'Information') {
        section = 'Information'
        currentSection = 'Information'
      }
    }

    result.push({
      ...item,
      section,
      iconComponent: iconMap[item.icon] || null
    })
  }

  return result
}

onMounted(async () => {
  // Half-hourly: cheap, and it crosses midnight into the new week without a
  // reload on a screen nobody touches.
  fiscalWeekTimer = setInterval(() => { currentFiscalWeek.value = fiscalWeek() }, 30 * 60 * 1000)

  getFacilityName().then(name => { facilityName.value = name })
  getSiteLogo().then(logo => { siteLogo.value = logo })
  getServicenowUrls().then(config => { servicenowConfig.value = config })

  try {
    const response = await dashboardApi.navigation()
    navItems.value = buildNavItems(response.data.data || [])
  } catch (err) {
    navItems.value = buildNavItems(defaultNav)
  }

  try {
    const resp = await notificationsApi.active()
    activeNotifications.value = resp.data.data?.notifications || []
  } catch (err) {
    // Notifications banner is non-critical
  }
})

function getNotificationType(n) {
  const type = (n.typename || '').toLowerCase()
  if (type.includes('incident')) return 'type-incident'
  if (type.includes('change')) return 'type-change'
  if (type.includes('awareness')) return 'type-awareness'
  return 'type-default'
}

function formatDate(dateStr) {
  if (!dateStr) return ''
  const d = new Date(dateStr)
  return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' })
}

function performSearch() {
  if (searchQuery.value.trim()) {
    router.push({ path: '/search', query: { q: searchQuery.value.trim() } })
    searchQuery.value = ''
  }
}

async function handleLogout() {
  await authStore.logout()
  router.push('/login')
}

const fallbackAvatar = withBase('/ge-monogram.svg')

// A broken avatar (photo set but file missing) degrades to the GE monogram.
function onAvatarError(event) {
  event.target.src = fallbackAvatar
  event.target.classList.add('ge-avatar-fallback')
}
onUnmounted(() => {
  if (fiscalWeekTimer) clearInterval(fiscalWeekTimer)
})
</script>

<style scoped>
/* Fiscal week, under the site name, where the classic site put it. The number
   carries the weight; the label is small because it never changes. */
.fiscal-week {
  margin-top: 0.35rem;
  text-align: center;
  line-height: 1.15;
}
.fiscal-week-label {
  display: block;
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.65;
}
.fiscal-week-number {
  display: block;
  font-size: 1.2rem;
  font-weight: 700;
}

/* Footer user block: identity row on top, compact icon actions below, all
   kept inside the fixed-width sidebar (no full-width buttons overflowing). */
.user-menu { display: flex; flex-direction: column; gap: 0.6rem; }
.user-identity { display: flex; align-items: center; gap: 0.6rem; min-width: 0; }
.user-avatar { width: 34px; height: 34px; border-radius: 50%; object-fit: cover; border: 1px solid var(--border); flex-shrink: 0; }
.ge-avatar-fallback { object-fit: contain; padding: 4px; background: #fff; }
.user-ident { display: flex; flex-direction: column; line-height: 1.1; min-width: 0; }
.user-ident .username { margin-bottom: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-sso { font-size: 0.72rem; color: var(--text-light); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.user-actions { display: flex; gap: 0.5rem; }
.icon-btn {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  padding: 0.4rem 0.5rem;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.15);
  border-radius: 6px;
  color: rgba(255,255,255,0.8);
  font-size: 12px;
  cursor: pointer;
  text-decoration: none;
}
.icon-btn:hover { background: rgba(255,255,255,0.16); color: #fff; }
</style>