<template>
  <div class="map-editor">
    <div class="page-header">
      <h2>Map Editor</h2>
      <div class="header-actions">
        <button class="btn btn-secondary" @click="openTransform">Recalibrate level</button>
        <button class="btn btn-secondary" @click="openSnapshots">Undo history</button>
        <router-link to="/map" class="btn btn-secondary">Back to Map</router-link>
      </div>
    </div>

    <div class="editor-layout">
      <!-- Asset List Panel -->
      <div class="asset-panel">
        <div class="panel-header">
          <h3>Assets</h3>
          <select v-if="levelOptions().length > 1" v-model.number="editingLevelId"
                  class="form-control" title="Which drawing you are placing on">
            <option v-for="option in levelOptions()" :key="option.levelid"
                    :value="option.levelid">{{ option.label }}</option>
          </select>
          <input v-model="search" class="form-control" type="search"
                 placeholder="Search name or asset number" />
          <select v-model="filterType" class="form-control">
            <option value="">All Types</option>
            <option value="machine">Machines</option>
            <option value="computer">Computers</option>
            <option value="printer">Printers</option>
            <option value="network_device">Network Devices</option>
            <option value="measuring_tool">Measuring Tools</option>
          </select>
        </div>

        <div class="asset-filter">
          <label class="filter-checkbox">
            <input type="checkbox" v-model="showUnplacedOnly" />
            Unplaced only
          </label>
          <label class="filter-checkbox" title="Positions a transform moved, which nobody has confirmed against the current drawing yet">
            <input type="checkbox" v-model="showUnverifiedOnly" />
            Needs review ({{ unverifiedCount }})
          </label>
          <label class="filter-checkbox" title="Only assets on the level you are editing">
            <input type="checkbox" v-model="thisLevelOnly" />
            This level only
          </label>
        </div>

        <div class="asset-list">
          <div
            v-for="asset in filteredAssets"
            :key="asset.assetid"
            class="asset-item"
            :class="{
              selected: selectedAsset?.assetid === asset.assetid,
              placed: asset.mapx && asset.mapy,
              unplaced: !asset.mapx || !asset.mapy
            }"
            @click="selectAsset(asset)"
          >
            <span class="asset-icon"><component :is="getTypeIcon(asset.assettype)" :size="16" /></span>
            <div class="asset-info">
              <div class="asset-name">{{ asset.name || asset.assetnumber }}</div>
              <div class="asset-meta">
                <span class="badge badge-sm">{{ asset.assettype }}</span>
                <span v-if="asset.mapx && asset.mapy" class="placed-indicator" title="Placed on map"><MapPin :size="12" /></span>
                <span v-if="asset.levelid && asset.levelid !== editingLevelId"
                      class="badge badge-sm badge-other-level"
                      :title="'On ' + levelName(asset.levelid)">{{ levelName(asset.levelid) }}</span>
                <span v-if="asset.mapx && !asset.levelid" class="badge badge-sm badge-warning"
                      title="Has a position but no level, so it cannot be drawn">no level</span>
                <button v-if="asset.mapx && asset.levelid && !asset.mapverifiedat"
                        class="badge badge-sm badge-review" type="button"
                        title="This position came from a transform. Click to confirm it is right."
                        @click.stop="markReviewed(asset)">confirm</button>
              </div>
            </div>
          </div>

          <div v-if="filteredAssets.length === 0" class="empty">
            No assets found.
          </div>
        </div>
      </div>

      <!-- Map Panel -->
      <div class="map-panel">
        <div class="map-toolbar" v-if="selectedAsset">
          <span class="selected-info">
            <strong>Selected:</strong> {{ selectedAsset.name || selectedAsset.assetnumber }}
          </span>
          <span class="position-info" v-if="pickedPosition">
            Position: ({{ Math.round(pickedPosition.left) }}, {{ Math.round(pickedPosition.top) }})
          </span>
          <div class="toolbar-actions">
            <button
              class="btn btn-primary"
              :disabled="!pickedPosition"
              @click="savePosition"
            >
              Save Position
            </button>
            <button
              class="btn btn-danger"
              v-if="selectedAsset.mapx && selectedAsset.mapy"
              @click="clearPosition"
            >
              Remove from Map
            </button>
            <button class="btn btn-secondary" @click="cancelEdit">Cancel</button>
          </div>
        </div>

        <div class="map-toolbar" v-else>
          <span class="instruction">Select an asset from the list to place it on the map</span>
        </div>

        <ShopFloorMap
          ref="mapRef"
          :machines="placedAssets"
          :assetTypeMode="true"
          :theme="currentTheme"
          :pickerMode="!!selectedAsset"
          :initialPosition="selectedAsset ? { left: selectedAsset.mapx, top: selectedAsset.mapy } : null"
          :levelid="editingLevelId"
          @positionPicked="handlePositionPicked"
          @markerClick="handleMarkerClick"
        />
      </div>
    </div>
    <!-- Recalibrate: move every marker on this level by a transform read off two
         landmarks. Deriving it from the image dimensions instead would stretch
         one axis and be wrong everywhere, so the numbers come from features
         visible on both drawings. -->
    <div v-if="showTransform" class="modal-overlay">
      <div class="modal modal-wide">
        <div class="modal-header"><h3>Recalibrate {{ levelName(editingLevelId) }}</h3></div>
        <div class="modal-body">
          <p class="input-hint">
            Pick two features present on both the old and the new drawing. A
            building corner plus something central beats two corners: a long
            baseline makes the derived scale more forgiving.
          </p>
          <table class="data-table">
            <thead>
              <tr><th>Landmark</th><th>Old X</th><th>Old Y</th><th>New X</th><th>New Y</th></tr>
            </thead>
            <tbody>
              <tr v-for="(mark, index) in landmarks" :key="index">
                <td>{{ index + 1 }}</td>
                <td><input v-model.number="mark.fromx" type="number" class="form-control" /></td>
                <td><input v-model.number="mark.fromy" type="number" class="form-control" /></td>
                <td><input v-model.number="mark.tox" type="number" class="form-control" /></td>
                <td><input v-model.number="mark.toy" type="number" class="form-control" /></td>
              </tr>
            </tbody>
          </table>
          <button class="btn btn-small btn-secondary" @click="landmarks.push({})">
            Add a third landmark
          </button>

          <div v-if="preview" class="transform-preview">
            <h4>Preview</h4>
            <p class="mono">
              X: scale {{ preview.transform.scalex.toFixed(4) }}, offset {{ Math.round(preview.transform.offsetx) }}<br />
              Y: scale {{ preview.transform.scaley.toFixed(4) }}, offset {{ Math.round(preview.transform.offsety) }}
            </p>
            <p>
              {{ preview.assetcount }} marker(s) would move.
              <strong v-if="preview.outofboundscount" class="warn">
                {{ preview.outofboundscount }} would land outside the drawing.
              </strong>
            </p>
            <p class="input-hint">
              A scale near 1 means the drawing shifted rather than rescaled. A
              scale far from 1 on an axis that only gained canvas is the sign of a
              bad landmark pair.
            </p>
          </div>
          <div v-if="transformError" class="error-message">{{ transformError }}</div>
        </div>
        <div class="modal-footer">
          <button class="btn btn-secondary" @click="showTransform = false">Cancel</button>
          <button class="btn btn-secondary" @click="runTransform(true)" :disabled="working">Preview</button>
          <button class="btn btn-primary" @click="runTransform(false)" :disabled="working || !preview">
            Move {{ preview ? preview.assetcount : 0 }} marker(s)
          </button>
        </div>
      </div>
    </div>

    <!-- Undo. Every bulk write snapshots first, and a restore snapshots too, so a
         second attempt is always possible. -->
    <div v-if="showSnapshots" class="modal-overlay">
      <div class="modal">
        <div class="modal-header"><h3>Undo history</h3></div>
        <div class="modal-body">
          <table class="data-table">
            <thead><tr><th>When</th><th>What</th><th>Markers</th><th></th></tr></thead>
            <tbody>
              <tr v-for="snapshot in snapshots" :key="snapshot.snapshotid">
                <td class="mono">{{ (snapshot.createddate || '').slice(0, 16).replace('T', ' ') }}</td>
                <td>{{ snapshot.reason }}</td>
                <td>{{ snapshot.assetcount }}</td>
                <td>
                  <button class="btn btn-small" @click="restore(snapshot)" :disabled="working">
                    {{ snapshot.restoredat ? 'Restore again' : 'Restore' }}
                  </button>
                </td>
              </tr>
              <tr v-if="!snapshots.length">
                <td colspan="4" class="empty">Nothing to undo yet.</td>
              </tr>
            </tbody>
          </table>
        </div>
        <div class="modal-footer">
          <button class="btn btn-secondary" @click="showSnapshots = false">Close</button>
        </div>
      </div>
    </div>

  </div>
</template>

<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import { Cog, Monitor, Printer, Globe, Ruler, Package, MapPin } from 'lucide-vue-next'
import ShopFloorMap from '../components/ShopFloorMap.vue'
import { assetsApi, mapPositionsApi } from '../api'
import { loadMapConfig, levelOptions, levelName, setCurrentLevel,
         state as mapConfig } from '../composables/mapConfig'
import { currentTheme } from '../stores/theme'
import { useToast } from '../composables/toast'
const toast = useToast()

const assets = ref([])
const loading = ref(true)
const selectedAsset = ref(null)
const pickedPosition = ref(null)
const filterType = ref('')
const showUnplacedOnly = ref(false)
// A transform is a guess: it moves markers but cannot know which machines
// actually moved, so everything it touches needs confirming against the drawing.
// This is the working queue for that.
const showUnverifiedOnly = ref(false)
const thisLevelOnly = ref(true)
const search = ref('')
// Which drawing is being placed on. Every position saved from here belongs to it.
const editingLevelId = ref(null)

// Recalibration state. Two landmarks is the minimum that determines both axes;
// a third is offered because points picked by eye carry a few pixels of error and
// three let it cancel rather than accumulate.
const showTransform = ref(false)
const landmarks = ref([{}, {}])
const preview = ref(null)
const transformError = ref('')
const showSnapshots = ref(false)
const snapshots = ref([])
const working = ref(false)

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

  if (filterType.value) {
    result = result.filter(a => a.assettype === filterType.value)
  }

  if (showUnplacedOnly.value) {
    result = result.filter(a => !a.mapx || !a.mapy)
  }

  if (showUnverifiedOnly.value) {
    // Placed, on a level, and never confirmed. An unplaced asset is not
    // "unreviewed" - it is simply not on the map yet.
    result = result.filter(a => a.mapx && a.levelid && !a.mapverifiedat)
  }

  if (thisLevelOnly.value && editingLevelId.value) {
    // Unplaced assets stay visible whatever the level filter says: they are the
    // ones you are here to place, and they belong to no level yet.
    result = result.filter(a => !a.mapx || a.levelid === editingLevelId.value)
  }

  const term = search.value.trim().toLowerCase()
  if (term) {
    result = result.filter(a =>
      (a.name || '').toLowerCase().includes(term) ||
      (a.assetnumber || '').toLowerCase().includes(term))
  }

  return result
})

// Only markers on the level being drawn. Markers from another level would appear
// at coordinates that mean nothing on this one.
const placedAssets = computed(() => {
  return assets.value.filter(a => a.mapx && a.mapy &&
    a.levelid === editingLevelId.value)
})

const unverifiedCount = computed(() =>
  assets.value.filter(a => a.mapx && a.levelid && !a.mapverifiedat).length)

onMounted(async () => {
  await loadMapConfig()
  editingLevelId.value = mapConfig.defaultlevelid
  await loadAssets()
})

watch(editingLevelId, (levelid) => {
  if (levelid) setCurrentLevel(levelid)
})

async function loadAssets() {
  loading.value = true
  try {
    const response = await assetsApi.getMap()
    assets.value = response.data.data?.assets || []
  } catch (error) {
    console.error('Failed to load assets:', error)
  } finally {
    loading.value = false
  }
}

function openTransform() {
  preview.value = null
  transformError.value = ''
  landmarks.value = [{}, {}]
  showTransform.value = true
}

async function runTransform(dryrun) {
  working.value = true
  transformError.value = ''
  try {
    const { data } = await mapPositionsApi.transform({
      levelid: editingLevelId.value,
      landmarks: landmarks.value.filter(mark =>
        [mark.fromx, mark.fromy, mark.tox, mark.toy].every(
          value => value !== undefined && value !== null && value !== '')),
      dryrun,
    })
    if (dryrun) {
      preview.value = data.data
    } else {
      showTransform.value = false
      preview.value = null
      await loadAssets()
      // Straight into the review queue: every moved marker is now unconfirmed,
      // and that is the work the transform created.
      showUnverifiedOnly.value = true
      toast.success(data.message || 'Markers moved')
    }
  } catch (err) {
    transformError.value = err?.response?.data?.data?.error?.message
      || 'The transform could not be applied'
  } finally {
    working.value = false
  }
}

async function openSnapshots() {
  showSnapshots.value = true
  try {
    const { data } = await mapPositionsApi.snapshots()
    snapshots.value = data.data || []
  } catch (err) {
    snapshots.value = []
  }
}

async function restore(snapshot) {
  if (!confirm(`Restore ${snapshot.assetcount} marker position(s) from ` +
               `"${snapshot.reason}"? The current positions are snapshotted first.`)) return
  working.value = true
  try {
    const { data } = await mapPositionsApi.restore(snapshot.snapshotid)
    await loadAssets()
    await openSnapshots()
    toast.success(data.message || 'Positions restored')
  } catch (err) {
    toast.error('Could not restore that snapshot')
  } finally {
    working.value = false
  }
}

// Confirm a marker is in the right place without moving it - the common case in
// a review pass, and the reason the queue empties.
async function markReviewed(asset) {
  try {
    await mapPositionsApi.verify([asset.assetid])
    asset.mapverifiedat = new Date().toISOString()
  } catch (err) {
    toast.error('Could not mark that reviewed')
  }
}

function getTypeIcon(assettype) {
  const icons = {
    'machine': Cog,
    'computer': Monitor,
    'printer': Printer,
    'network_device': Globe,
    'measuring_tool': Ruler
  }
  return icons[assettype] || Package
}

function selectAsset(asset) {
  selectedAsset.value = asset
  pickedPosition.value = asset.mapx && asset.mapy
    ? { left: asset.mapx, top: asset.mapy }
    : null
}

function handlePositionPicked(position) {
  pickedPosition.value = position
}

function handleMarkerClick(asset) {
  selectAsset(asset)
}

async function savePosition() {
  if (!selectedAsset.value || !pickedPosition.value) return

  try {
    // Through the bulk endpoint, which requires a level and stamps the review
    // state: placing a marker by hand IS the confirmation, and a snapshot is
    // taken so the placement can be undone.
    await mapPositionsApi.setPositions([{
      assetid: selectedAsset.value.assetid,
      mapx: Math.round(pickedPosition.value.left),
      mapy: Math.round(pickedPosition.value.top),
      levelid: editingLevelId.value,
    }])

    // Update local state
    const asset = assets.value.find(a => a.assetid === selectedAsset.value.assetid)
    if (asset) {
      asset.mapx = Math.round(pickedPosition.value.left)
      asset.mapy = Math.round(pickedPosition.value.top)
      asset.levelid = editingLevelId.value
      asset.mapverifiedat = new Date().toISOString()
    }

    selectedAsset.value = null
    pickedPosition.value = null
  } catch (error) {
    console.error('Failed to save position:', error)
    toast.error('Failed to save position')
  }
}

async function clearPosition() {
  if (!selectedAsset.value) return

  if (!confirm(`Remove ${selectedAsset.value.name || selectedAsset.value.assetnumber} from the map?`)) return

  try {
    await assetsApi.update(selectedAsset.value.assetid, {
      mapx: null,
      mapy: null
    })

    // Update local state
    const asset = assets.value.find(a => a.assetid === selectedAsset.value.assetid)
    if (asset) {
      asset.mapx = null
      asset.mapy = null
    }

    selectedAsset.value = null
    pickedPosition.value = null
  } catch (error) {
    console.error('Failed to clear position:', error)
    toast.error('Failed to clear position')
  }
}

function cancelEdit() {
  selectedAsset.value = null
  pickedPosition.value = null
}
</script>

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

.header-actions {
  display: flex;
  gap: 0.5rem;
}

.editor-layout {
  display: flex;
  flex: 1;
  gap: 1rem;
  min-height: 0;
}

.asset-panel {
  width: 320px;
  display: flex;
  flex-direction: column;
  background: var(--bg-card);
  border-radius: 8px;
  border: 1px solid var(--border);
  overflow: hidden;
}

.panel-header {
  /* The heading takes its own row and the three controls share the next one(s).
     All four on a single row inside a 320px panel squeezed the search box until
     its placeholder read "Search na". */
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  padding: 1rem;
  border-bottom: 1px solid var(--border);
}

.panel-header h3 {
  flex: 1 1 100%;
}

.panel-header .form-control {
  /* One control per row. Two of them side by side in a 318px panel left the
     search box 139px for a placeholder needing 216px, so it read "Search name
     o" - a control whose own label does not fit is not a narrower control, it is
     an unlabelled one. */
  flex: 1 1 100%;
  min-width: 0;
}

.panel-header h3 {
  margin: 0;
  font-size: 1rem;
}

.panel-header select {
  width: auto;
  padding: 0.375rem 0.5rem;
  font-size: 0.8rem;
}

.asset-filter {
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--border);
}

.filter-checkbox {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.875rem;
  cursor: pointer;
}

.asset-list {
  flex: 1;
  overflow-y: auto;
  padding: 0.5rem;
}

.asset-item {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem;
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.2s;
}

.asset-item:hover {
  background: var(--bg);
}

.asset-item.selected {
  background: rgba(65, 129, 255, 0.2);
  border: 1px solid var(--primary);
}

.asset-item.unplaced {
  opacity: 0.7;
}

.asset-icon {
  font-size: 1.5rem;
}

.asset-info {
  flex: 1;
  min-width: 0;
}

.asset-name {
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.asset-meta {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.25rem;
}

.badge-sm {
  font-size: 0.7rem;
  padding: 0.15rem 0.4rem;
}

.placed-indicator {
  font-size: 0.875rem;
}

.map-panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

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

.selected-info {
  font-size: 0.9rem;
}

.position-info {
  font-size: 0.875rem;
  color: var(--text-light);
  font-family: monospace;
}

.toolbar-actions {
  display: flex;
  gap: 0.5rem;
  margin-left: auto;
}

.instruction {
  color: var(--text-light);
  font-style: italic;
}

.map-panel :deep(.shopfloor-map) {
  flex: 1;
  border-radius: 8px;
  overflow: hidden;
}

.empty {
  padding: 2rem;
  text-align: center;
  color: var(--text-light);
}

.badge-review {
  background: var(--warning);
  color: #1a1a1a;
  border: none;
  cursor: pointer;
}

.badge-other-level {
  background: var(--bg);
  color: var(--text-light);
  border: 1px solid var(--border);
}

.transform-preview {
  margin-top: 1rem;
  padding: 0.75rem;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg);
}

.transform-preview h4 { margin: 0 0 0.5rem; }
.mono { font-family: monospace; }
.warn { color: var(--warning); }
.modal-wide { min-width: 44rem; }
.input-hint { display: block; color: var(--text-light); font-size: 0.8rem; }
</style>
