<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, nextTick, watch, computed } from 'vue'
import { Terminal } from '@xterm/xterm'
import { FitAddon } from '@xterm/addon-fit'
import { WebLinksAddon } from '@xterm/addon-web-links'
import '@xterm/xterm/css/xterm.css'

const props = defineProps<{
  modelValue: boolean
}>()

const emit = defineEmits<{
  'update:modelValue': [value: boolean]
}>()

const isOpen = computed({
  get: () => props.modelValue,
  set: (val) => emit('update:modelValue', val)
})

const terminalContainer = ref<HTMLElement | null>(null)
const terminalRef = ref<HTMLElement | null>(null)
const connected = ref(false)
const connecting = ref(false)
const sessionId = ref<string | null>(null)

const isDragging = ref(false)
const position = ref({ x: 0, y: 0 })
const hasCustomPosition = ref(false)
const dragOffset = ref({ x: 0, y: 0 })

// Resize state
const isResizing = ref(false)
const size = ref({ w: 580, h: 360 })

let terminal: Terminal | null = null
let fitAddon: FitAddon | null = null
let socket: WebSocket | null = null
let resizeObserver: ResizeObserver | null = null

const WS_URL = `ws://${window.location.hostname}:4103`

// Mouse position tracking for Ctrl+E
const mousePos = ref({ x: 0, y: 0 })
let lastToggle = 0

function trackMouse(e: MouseEvent) {
  mousePos.value = { x: e.clientX, y: e.clientY }
}

function toggleTerminal() {
  const now = Date.now()
  if (now - lastToggle < 150) return // Debounce 150ms
  lastToggle = now

  if (!isOpen.value) {
    // Open at mouse position (allow 75% occlusion)
    const w = size.value.w
    const h = size.value.h
    const minX = -w * 0.75
    const maxX = window.innerWidth - w * 0.25
    const minY = -h * 0.75
    const maxY = window.innerHeight - h * 0.25
    position.value = {
      x: Math.max(minX, Math.min(mousePos.value.x - w / 2, maxX)),
      y: Math.max(minY, Math.min(mousePos.value.y - h / 2, maxY))
    }
    hasCustomPosition.value = true
    isOpen.value = true
  } else {
    isOpen.value = false
  }
}

function handleKeydown(e: KeyboardEvent) {
  if (e.ctrlKey && e.key === 'e') {
    e.preventDefault()
    toggleTerminal()
  }
}

function startDrag(e: MouseEvent) {
  if ((e.target as HTMLElement).closest('.window-controls')) return

  isDragging.value = true
  const rect = terminalRef.value?.getBoundingClientRect()
  if (rect) {
    // Capture actual position if using default bottom/right
    if (!hasCustomPosition.value) {
      position.value = { x: rect.left, y: rect.top }
    }
    dragOffset.value = {
      x: e.clientX - rect.left,
      y: e.clientY - rect.top
    }
  }

  document.addEventListener('mousemove', onDrag)
  document.addEventListener('mouseup', stopDrag)
}

function onDrag(e: MouseEvent) {
  if (!isDragging.value) return

  const newX = e.clientX - dragOffset.value.x
  const newY = e.clientY - dragOffset.value.y

  const w = terminalRef.value?.offsetWidth || 580
  const h = terminalRef.value?.offsetHeight || 360

  // Allow up to 75% occlusion per side (25% must remain visible)
  const minX = -w * 0.75
  const maxX = window.innerWidth - w * 0.25
  const minY = -h * 0.75
  const maxY = window.innerHeight - h * 0.25

  position.value = {
    x: Math.max(minX, Math.min(newX, maxX)),
    y: Math.max(minY, Math.min(newY, maxY))
  }
}

function stopDrag() {
  isDragging.value = false
  hasCustomPosition.value = true
  document.removeEventListener('mousemove', onDrag)
  document.removeEventListener('mouseup', stopDrag)
}

// Resize functions
const resizeStart = ref({ x: 0, y: 0, w: 0, h: 0 })

function startResize(e: MouseEvent) {
  e.preventDefault()
  e.stopPropagation()
  isResizing.value = true
  resizeStart.value = {
    x: e.clientX,
    y: e.clientY,
    w: size.value.w,
    h: size.value.h
  }
  document.addEventListener('mousemove', onResize)
  document.addEventListener('mouseup', stopResize)
}

function onResize(e: MouseEvent) {
  if (!isResizing.value) return

  const deltaX = e.clientX - resizeStart.value.x
  const deltaY = e.clientY - resizeStart.value.y

  size.value = {
    w: Math.max(400, Math.min(resizeStart.value.w + deltaX, window.innerWidth - 40)),
    h: Math.max(250, Math.min(resizeStart.value.h + deltaY, window.innerHeight - 40))
  }
}

function stopResize() {
  isResizing.value = false
  document.removeEventListener('mousemove', onResize)
  document.removeEventListener('mouseup', stopResize)
  nextTick(() => fitAddon?.fit())
}

const terminalStyle = computed(() => {
  const base = {
    width: `${size.value.w}px`,
    height: `${size.value.h}px`
  }
  if (!hasCustomPosition.value) {
    return { ...base, bottom: '16px', right: '16px' }
  }
  return {
    ...base,
    top: `${position.value.y}px`,
    left: `${position.value.x}px`,
    bottom: 'auto',
    right: 'auto'
  }
})

function initTerminal() {
  if (!terminalContainer.value || terminal) return

  terminal = new Terminal({
    cursorBlink: true,
    cursorStyle: 'block',
    fontSize: 12,
    fontFamily: "'Consolas', 'Lucida Console', monospace",
    theme: {
      background: 'rgba(12, 12, 12, 0.95)',
      foreground: '#ffffff',
      cursor: '#ffffff',
      cursorAccent: '#000000',
      selectionBackground: 'rgba(100, 150, 255, 0.4)',
      black: '#0c0c0c',
      red: '#c50f1f',
      green: '#13a10e',
      yellow: '#c19c00',
      blue: '#0037da',
      magenta: '#881798',
      cyan: '#3a96dd',
      white: '#cccccc',
      brightBlack: '#767676',
      brightRed: '#e74856',
      brightGreen: '#16c60c',
      brightYellow: '#f9f1a5',
      brightBlue: '#3b78ff',
      brightMagenta: '#b4009e',
      brightCyan: '#61d6d6',
      brightWhite: '#f2f2f2'
    },
    allowProposedApi: true
  })

  fitAddon = new FitAddon()
  terminal.loadAddon(fitAddon)
  terminal.loadAddon(new WebLinksAddon())
  terminal.open(terminalContainer.value)

  nextTick(() => fitAddon?.fit())

  resizeObserver = new ResizeObserver(() => {
    if (fitAddon && terminal) {
      fitAddon.fit()
      if (socket && socket.readyState === WebSocket.OPEN) {
        socket.send(JSON.stringify({
          type: 'resize',
          cols: terminal.cols,
          rows: terminal.rows
        }))
      }
    }
  })
  resizeObserver.observe(terminalContainer.value)

  terminal.onData((data) => {
    if (socket && socket.readyState === WebSocket.OPEN) {
      socket.send(JSON.stringify({ type: 'input', data }))
    }
  })

  // Capture Ctrl+E even when terminal has focus
  terminal.attachCustomKeyEventHandler((e) => {
    if (e.ctrlKey && e.key === 'e') {
      e.preventDefault()
      toggleTerminal()
      return false // Prevent terminal from processing
    }
    return true // Let terminal handle other keys
  })
}

async function connect() {
  if (connecting.value || connected.value) return
  connecting.value = true

  try {
    socket = new WebSocket(WS_URL)

    socket.onopen = () => {
      connected.value = true
      connecting.value = false
      terminal?.focus()
      if (terminal) {
        socket?.send(JSON.stringify({
          type: 'resize',
          cols: terminal.cols,
          rows: terminal.rows
        }))
      }
    }

    socket.onmessage = (event) => {
      const msg = JSON.parse(event.data)
      if (msg.type === 'connected') {
        sessionId.value = msg.sessionId
        if (!msg.isNew) {
          terminal?.write('\x1b[36m[Reconnected]\x1b[0m\r\n')
        }
      } else if (msg.type === 'replay') {
        terminal?.write(msg.data)
      } else if (msg.type === 'output') {
        terminal?.write(msg.data)
      } else if (msg.type === 'exit') {
        terminal?.write(msg.data)
        sessionId.value = null
      } else if (msg.type === 'error') {
        terminal?.write(`\r\n\x1b[31mError: ${msg.message}\x1b[0m\r\n`)
      }
    }

    socket.onclose = () => {
      connected.value = false
      connecting.value = false
    }

    socket.onerror = () => {
      connecting.value = false
    }
  } catch (e) {
    connecting.value = false
  }
}

function close() {
  isOpen.value = false
}

function runClaude() {
  if (socket && socket.readyState === WebSocket.OPEN) {
    socket.send(JSON.stringify({ type: 'input', data: 'claude\r' }))
  }
}

watch(isOpen, async (open) => {
  if (open) {
    await nextTick()
    initTerminal()
    if (!connected.value && !connecting.value) connect()
    nextTick(() => {
      fitAddon?.fit()
      terminal?.focus()
    })
  } else {
    // Cleanup when closing
    resizeObserver?.disconnect()
    resizeObserver = null
    terminal?.dispose()
    terminal = null
    fitAddon = null
  }
})

onMounted(async () => {
  // Global listeners for Ctrl+E
  document.addEventListener('mousemove', trackMouse)
  document.addEventListener('keydown', handleKeydown)

  if (isOpen.value) {
    await nextTick()
    initTerminal()
    connect()
  }
})

onBeforeUnmount(() => {
  resizeObserver?.disconnect()
  socket?.close()
  terminal?.dispose()
  document.removeEventListener('mousemove', onDrag)
  document.removeEventListener('mouseup', stopDrag)
  document.removeEventListener('mousemove', onResize)
  document.removeEventListener('mouseup', stopResize)
  document.removeEventListener('mousemove', trackMouse)
  document.removeEventListener('keydown', handleKeydown)
})

// Expose controls for MCP tools
defineExpose({
  open: (x?: number, y?: number) => {
    if (x !== undefined && y !== undefined) {
      position.value = { x, y }
      hasCustomPosition.value = true
    }
    isOpen.value = true
  },
  close: () => {
    isOpen.value = false
  },
  toggle: () => {
    toggleTerminal()
  },
  move: (x: number, y: number) => {
    position.value = { x, y }
    hasCustomPosition.value = true
  },
  resize: (w: number, h: number) => {
    size.value = { w: Math.max(400, w), h: Math.max(250, h) }
    nextTick(() => fitAddon?.fit())
  },
  getState: () => ({
    isOpen: isOpen.value,
    position: position.value,
    size: size.value
  })
})
</script>

<template>
  <Teleport to="body">
    <Transition name="win-slide">
      <div
        v-if="isOpen"
        ref="terminalRef"
        class="aero-win"
        :class="{ dragging: isDragging, resizing: isResizing }"
        :style="terminalStyle"
      >
        <div class="glass">
          <!-- Titlebar -->
          <div class="titlebar" @mousedown="startDrag">
            <div class="left">
              <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
                <polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/>
              </svg>
              <span>Terminal</span>
              <i class="dot" :class="{ on: connected, wait: connecting }"></i>
              <a v-if="!connected && !connecting" class="link" @click.stop="connect">connect</a>
            </div>
            <div class="window-controls">
              <button @click="runClaude" title="Claude"><svg width="8" height="8" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg></button>
              <button class="x" @click="close" title="Close"><svg width="8" height="8" viewBox="0 0 10 10"><line x1="0" y1="0" x2="10" y2="10" stroke="currentColor" stroke-width="1.5"/><line x1="10" y1="0" x2="0" y2="10" stroke="currentColor" stroke-width="1.5"/></svg></button>
            </div>
          </div>
          <!-- Content -->
          <div class="content">
            <div ref="terminalContainer" class="term"></div>
          </div>
          <!-- Resize handle -->
          <div class="resize-handle" @mousedown="startResize"></div>
        </div>
      </div>
    </Transition>
  </Teleport>
</template>

<style scoped>
.aero-win {
  position: fixed;
  min-width: 400px;
  min-height: 250px;
  z-index: 9999;
}

.glass {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  background: rgba(200,215,235,0.35);
  backdrop-filter: blur(24px) saturate(1.6);
  -webkit-backdrop-filter: blur(24px) saturate(1.6);
  border-radius: 5px;
  border: 1px solid rgba(255,255,255,0.6);
  box-shadow:
    0 0 0 1px rgba(80,120,180,0.25),
    0 6px 24px rgba(0,0,0,0.25),
    inset 0 1px 0 rgba(255,255,255,0.6);
  overflow: hidden;
}

.titlebar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 22px;
  padding: 0 2px 0 6px;
  background: rgba(255,255,255,0.25);
  border-bottom: 1px solid rgba(255,255,255,0.3);
  cursor: grab;
  user-select: none;
}
.aero-win.dragging .titlebar { cursor: grabbing; }

.left {
  display: flex;
  align-items: center;
  gap: 5px;
  color: #222;
  font: 500 10px/1 system-ui, sans-serif;
}

.dot {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: #999;
}
.dot.on { background: #0a0; box-shadow: 0 0 4px #0a0; }
.dot.wait { background: #a80; animation: pulse .8s infinite; }

.link {
  margin-left: 2px;
  color: #369;
  font-size: 9px;
  text-decoration: underline;
  cursor: pointer;
}
.link:hover { color: #47a; }

.window-controls {
  display: flex;
  gap: 1px;
}
.window-controls button {
  width: 20px;
  height: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255,255,255,0.3);
  border: 1px solid rgba(0,0,0,0.1);
  border-radius: 2px;
  color: #333;
  cursor: pointer;
}
.window-controls button:hover {
  background: rgba(255,255,255,0.5);
}
.window-controls button.x:hover {
  background: linear-gradient(180deg, #e66 0%, #c33 100%);
  border-color: #a22;
  color: #fff;
}

.content {
  flex: 1;
  margin: 2px;
  border-radius: 2px;
  overflow: hidden;
  background: rgba(0,0,0,0.92);
}

.resize-handle {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 16px;
  height: 16px;
  cursor: nwse-resize;
  background: linear-gradient(135deg, transparent 50%, rgba(255,255,255,0.3) 50%, rgba(255,255,255,0.1) 100%);
  border-radius: 0 0 5px 0;
}
.resize-handle:hover {
  background: linear-gradient(135deg, transparent 50%, rgba(255,255,255,0.5) 50%, rgba(255,255,255,0.2) 100%);
}

.aero-win.resizing {
  user-select: none;
}
.aero-win.resizing .term {
  pointer-events: none;
}

.term {
  width: 100%;
  height: 100%;
}
.term :deep(.xterm) {
  height: 100%;
  padding: 2px;
}
.term :deep(.xterm-viewport) {
  overflow-y: auto !important;
}
.term :deep(.xterm-viewport::-webkit-scrollbar) {
  width: 8px;
  background: rgba(0,0,0,0.2);
}
.term :deep(.xterm-viewport::-webkit-scrollbar-thumb) {
  background: rgba(255,255,255,0.15);
  border-radius: 4px;
}
.term :deep(.xterm-viewport::-webkit-scrollbar-thumb:hover) {
  background: rgba(255,255,255,0.25);
}

.win-slide-enter-active, .win-slide-leave-active { transition: all .15s ease; }
.win-slide-enter-from, .win-slide-leave-to { opacity: 0; transform: translateY(16px) scale(0.98); }

@keyframes pulse { 0%,100% { opacity:1; } 50% { opacity:.4; } }

@media (max-width: 640px) {
  .aero-win {
    inset: auto 0 0 0 !important;
    width: 100% !important;
    height: 55% !important;
  }
  .glass { border-radius: 6px 6px 0 0; }
}
</style>
