<template>
  <div class="map-page">
    <div class="page-header">
      <h2>Shop Floor Map</h2>
      <router-link v-if="authStore.isAuthenticated" to="/map/editor" class="btn btn-primary">
        Edit Map
      </router-link>
    </div>

    <div v-if="loading" class="loading">Loading...</div>

    <template v-else>
      <!-- Filter Controls -->
      <div class="map-filters">
        <select
          v-if="levelChoices.length > 1"
          v-model.number="shownLevelId"
          @change="onLevelChange"
          title="Which floor plan to show"
        >
          <option v-for="level in levelChoices" :key="level.levelid" :value="level.levelid">
            {{ level.label }}
          </option>
        </select>

        <select v-model="selectedType" @change="onTypeChange">
          <option value="">All Asset Types</option>
          <option v-for="t in assetTypes" :key="t.assettypeid" :value="t.assettype">
            {{ formatTypeName(t.assettype) }} ({{ getTypeCount(t.assettype) }})
          </option>
        </select>

        <select v-model="selectedSubtype" @change="updateMapLayers" :disabled="!selectedType || !currentSubtypes.length">
          <option value="">{{ subtypeLabel }}</option>
          <option v-for="st in currentSubtypes" :key="st.id" :value="st.id">
            {{ st.name }}
          </option>
        </select>

        <select v-model="selectedBusinessUnit" @change="updateMapLayers">
          <option value="">All Business Units</option>
          <option v-for="bu in businessunits" :key="bu.businessunitid" :value="bu.businessunitid">
            {{ bu.businessunit }}
          </option>
        </select>

        <select v-model="selectedStatus" @change="updateMapLayers">
          <option value="">All Statuses</option>
          <option v-for="s in statuses" :key="s.statusid" :value="s.statusid">
            {{ s.status }}
          </option>
        </select>

        <input
          type="text"
          v-model="searchQuery"
          placeholder="Search assets..."
          @input="debouncedSearch"
        />

        <button
          class="btn btn-secondary export-btn"
          @click="exportPdf"
          :disabled="exporting || !filteredAssets.length"
          :title="filteredAssets.length ? 'Export the filtered map to PDF' : 'No assets to export'"
        >
          {{ exporting ? 'Exporting...' : 'Export PDF' }}
        </button>

        <span class="result-count">{{ filteredAssets.length }} assets</span>
      </div>

      <ShopFloorMap
        :levelid="shownLevelId"
        :machines="filteredAssets"
        :machinetypes="[]"
        :businessunits="businessunits"
        :statuses="statuses"
        :assetTypeMode="true"
        :theme="currentTheme"
        :selectedAssetType="selectedType"
        :subtypeColors="subtypeColorMap"
        :subtypeNames="subtypeNameMap"
        @markerClick="handleMarkerClick"
      />
    </template>
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import ShopFloorMap from '../components/ShopFloorMap.vue'
import { assetsApi } from '../api'
import { currentTheme } from '../stores/theme'
import { useAuthStore } from '../stores/auth'
import { loadMapConfig, blueprintUrlFor, dimensionsFor, levelName, levelOptions,
         setCurrentLevel, state as mapConfig } from '@/composables/mapConfig'
import { exportMapPdf } from '../utils/mapPdf'
import { assetTypeLabel, assetDetailRoute } from '../utils/assetTypes'
import { getSubtypeId } from '../utils/mapColors'
import { useToast } from '../composables/toast'
const toast = useToast()

const router = useRouter()
const authStore = useAuthStore()
const loading = ref(true)
const assets = ref([])
const assetTypes = ref([])
const businessunits = ref([])
const statuses = ref([])
const subtypes = ref({})

// Filter state
const selectedType = ref('')
const selectedSubtype = ref('')
const selectedBusinessUnit = ref('')
const selectedStatus = ref('')
const searchQuery = ref('')

// Which floor plan is on screen. Every marker coordinate is pixels of ONE level
// (ADR-017), so this decides both the drawing and which markers belong on it.
const shownLevelId = ref(null)

const levelChoices = computed(() => levelOptions())

function onLevelChange() {
  // Keep the shared composable in step, so a map-position picker opened later
  // starts on the level being looked at rather than the site default.
  setCurrentLevel(shownLevelId.value)
}

// A search whose only matches are on another floor would otherwise show an empty
// map: the assets matched, the filter counted them, and nothing was drawn.
// Follow the results to the level that actually holds them - the level with the
// most matches, so a search matching several floors lands on the best one.
function followSearchAcrossLevels() {
  if (!searchQuery.value.trim()) return
  const matches = filteredAssets.value.filter(a => a.mapx != null && a.mapy != null)
  if (!matches.length) return
  if (matches.some(a => (a.levelid ?? null) === shownLevelId.value)) return

  const tally = new Map()
  matches.forEach(asset => {
    const levelid = asset.levelid ?? null
    if (levelid === null) return
    tally.set(levelid, (tally.get(levelid) || 0) + 1)
  })
  if (!tally.size) return

  const best = [...tally.entries()].sort((a, b) => b[1] - a[1])[0][0]
  shownLevelId.value = best
  setCurrentLevel(best)
}
const exporting = ref(false)

let searchTimeout = null

// Lookup helper for subtypes. Normalizes case AND underscores-vs-spaces, since
// the asset-type value is 'network_device' but the subtypes key is
// 'Network Device' - without normalizing, network subtypes never match.
function getSubtypesForType(typeName) {
  if (!typeName || !subtypes.value) return []
  // Try exact match first
  if (subtypes.value[typeName]) return subtypes.value[typeName]
  const norm = typeName.toLowerCase().replace(/_/g, ' ')
  for (const [key, value] of Object.entries(subtypes.value)) {
    if (key.toLowerCase().replace(/_/g, ' ') === norm) return value
  }
  return []
}

const currentSubtypes = computed(() => {
  if (!selectedType.value) return []
  return getSubtypesForType(selectedType.value)
})

const subtypeLabel = computed(() => {
  if (!selectedType.value) return 'Select type first'
  if (!currentSubtypes.value.length) return 'No subtypes'
  const labels = {
    'machine': 'All Machine Types',
    'computer': 'All Computer Types',
    'network device': 'All Device Types',
    'printer': 'All Printer Types',
    'measuring tool': 'All Tool Types'
  }
  return labels[selectedType.value.toLowerCase().replace(/_/g, ' ')] || 'All Subtypes'
})

// Auto colors for subtypes that have not been given one. Every step clears 3:1
// against BOTH map surfaces (white blueprint and the dark navy card), so none
// washes out; the old list ran through pale yellows and limes (#FFEB3B was
// 1.1:1 on white) that were invisible on the light blueprint. Ordered by
// max-min perceptual separation, so the fewer subtypes are on screen the
// further apart their colors sit. Past roughly the fifth entry no palette can
// keep every pair distinguishable under color-blind simulation - the legend and
// the hover tooltip carry identity from there.
const subtypeColorPalette = [
  '#7c4dff', '#ef6c00', '#0097a7', '#c2185b', '#1565c0',
  '#e03131', '#e0479e', '#00897b', '#827717', '#1976d2',
  '#d81b60', '#388e3c', '#9c4dcc', '#2e7d32'
]

// Map subtype IDs to colors: prefer each subtype's stored color; fall back to
// the auto palette (by index) for subtypes that have not been given one.
const subtypeColorMap = computed(() => {
  const colorMap = {}
  const allSubtypes = currentSubtypes.value
  allSubtypes.forEach((st, index) => {
    colorMap[st.id] = st.color || subtypeColorPalette[index % subtypeColorPalette.length]
  })
  return colorMap
})

// Map subtype IDs to names
const subtypeNameMap = computed(() => {
  const nameMap = {}
  currentSubtypes.value.forEach(st => {
    nameMap[st.id] = st.name
  })
  return nameMap
})

const filteredAssets = computed(() => {
  let result = assets.value

  // Filter by asset type (case-insensitive)
  if (selectedType.value) {
    const selectedLower = selectedType.value.toLowerCase()
    result = result.filter(a => a.assettype && a.assettype.toLowerCase() === selectedLower)
  }

  // Filter by subtype. Uses the shared getSubtypeId helper so this stays in
  // step with the marker coloring - a local copy of the per-type id lookup went
  // stale and silently dropped every measuring tool.
  if (selectedSubtype.value) {
    const subtypeId = parseInt(selectedSubtype.value)
    result = result.filter(a => getSubtypeId(a) === subtypeId)
  }

  // Filter by business unit
  if (selectedBusinessUnit.value) {
    result = result.filter(a => a.businessunitid === parseInt(selectedBusinessUnit.value))
  }

  // Filter by status
  if (selectedStatus.value) {
    result = result.filter(a => a.statusid === parseInt(selectedStatus.value))
  }

  // Filter by search query
  if (searchQuery.value) {
    const q = searchQuery.value.toLowerCase()
    result = result.filter(a =>
      (a.assetnumber && a.assetnumber.toLowerCase().includes(q)) ||
      (a.name && a.name.toLowerCase().includes(q)) ||
      (a.displayname && a.displayname.toLowerCase().includes(q)) ||
      (a.serialnumber && a.serialnumber.toLowerCase().includes(q))
    )
  }

  return result
})

// Human-readable labels for the filters currently applied, for the PDF header.
function activeFilterLabels() {
  const labels = []
  if (selectedType.value) labels.push(`Type: ${formatTypeName(selectedType.value)}`)
  if (selectedSubtype.value) {
    const st = currentSubtypes.value.find(s => String(s.id) === String(selectedSubtype.value))
    if (st) labels.push(`Subtype: ${st.name}`)
  }
  if (selectedBusinessUnit.value) {
    const bu = businessunits.value.find(b => String(b.businessunitid) === String(selectedBusinessUnit.value))
    if (bu) labels.push(`Business Unit: ${bu.businessunit}`)
  }
  if (selectedStatus.value) {
    const s = statuses.value.find(x => String(x.statusid) === String(selectedStatus.value))
    if (s) labels.push(`Status: ${s.status}`)
  }
  if (searchQuery.value) labels.push(`Search: "${searchQuery.value}"`)
  return labels
}

async function exportPdf() {
  if (!filteredAssets.value.length) return
  exporting.value = true
  try {
    await loadMapConfig()
    await exportMapPdf({
      // Only this level's markers. The sheet is one drawing, so a marker
      // positioned against another level would be printed on the wrong floor
      // plan - the same failure the on-screen map had, in a form nobody can
      // correct after it is printed and carried onto the floor.
      assets: filteredAssets.value.filter(
        asset => (asset.levelid ?? null) === shownLevelId.value),
      // blueprintUrlFor applies withBase - the raw setting value is a
      // root-relative /api path, which 404s under a subpath mount like /ops.
      // The PDF always uses the light blueprint: it prints on white paper.
      blueprintUrl: blueprintUrlFor('light', shownLevelId.value),
      mapWidth: dimensionsFor(shownLevelId.value).width,
      mapHeight: dimensionsFor(shownLevelId.value).height,
      // Named on the sheet, because a floor plan with no level on it is not
      // identifiable once it is printed and carried to the floor.
      levelname: levelName(shownLevelId.value),
      selectedType: selectedType.value,
      subtypeColors: subtypeColorMap.value,
      subtypeNames: subtypeNameMap.value,
      filters: activeFilterLabels()
    })
  } catch (e) {
    console.error('Map PDF export failed:', e)
    toast.error('Failed to export map PDF. See console for details.')
  } finally {
    exporting.value = false
  }
}

onMounted(async () => {
  await loadMapConfig()
  shownLevelId.value = mapConfig.currentlevelid ?? mapConfig.defaultlevelid ?? null
  try {
    const response = await assetsApi.getMap()
    const data = response.data.data || {}

    assets.value = data.assets || []
    assetTypes.value = data.filters?.assettypes || []
    businessunits.value = data.filters?.businessunits || []
    statuses.value = data.filters?.statuses || []
    subtypes.value = data.filters?.subtypes || {}
  } catch (error) {
    console.error('Failed to load map data:', error)
  } finally {
    loading.value = false
  }
})

function formatTypeName(assettype) {
  return assetTypeLabel(assettype)
}

function getTypeCount(assettype) {
  if (!assettype) return 0
  const lowerType = assettype.toLowerCase()
  return assets.value.filter(a => a.assettype && a.assettype.toLowerCase() === lowerType).length
}

function onTypeChange() {
  // Reset subtype when type changes
  selectedSubtype.value = ''
  updateMapLayers()
}

function updateMapLayers() {
  // Filter is reactive via computed property
}

function debouncedSearch() {
  clearTimeout(searchTimeout)
  searchTimeout = setTimeout(() => {
    followSearchAcrossLevels()
    updateMapLayers()
  }, 300)
}

function handleMarkerClick(asset) {
  router.push(assetDetailRoute(asset))
}
</script>

<style scoped>
.map-page {
  display: flex;
  flex-direction: column;
  height: calc(100vh - var(--main-pad-top) - var(--main-pad-bottom));
}

.map-page .page-header {
  flex-shrink: 0;
}

.map-filters {
  display: flex;
  gap: 0.75rem;
  padding: 0.75rem 1rem;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 8px;
  margin-bottom: 0.75rem;
  flex-wrap: wrap;
  align-items: center;
}

.map-filters select,
.map-filters input {
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  font-size: 0.875rem;
}

.map-filters select {
  min-width: 160px;
}

.map-filters select:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.map-filters input {
  min-width: 180px;
}

.export-btn {
  margin-left: auto;
  padding: 0.5rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-card);
  color: var(--text);
  font-size: 0.875rem;
  cursor: pointer;
}

.export-btn:hover:not(:disabled) {
  border-color: var(--primary);
  color: var(--primary);
}

.export-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.result-count {
  color: var(--text-light);
  font-size: 0.875rem;
}

.map-page :deep(.shopfloor-map) {
  flex: 1;
}
</style>
