<script setup lang="ts">
import { ref, computed, watch, onMounted, nextTick, shallowRef } from 'vue'
import { api } from '../api'
import Dropdown from 'primevue/dropdown'
import Button from 'primevue/button'
import DataTable from 'primevue/datatable'
import Column from 'primevue/column'
import Tree from 'primevue/tree'
import Dialog from 'primevue/dialog'
import Textarea from 'primevue/textarea'
import InputText from 'primevue/inputtext'
import Splitter from 'primevue/splitter'
import SplitterPanel from 'primevue/splitterpanel'
import TabView from 'primevue/tabview'
import TabPanel from 'primevue/tabpanel'
import ProgressSpinner from 'primevue/progressspinner'
import { useToast } from 'primevue/usetoast'
import hljs from 'highlight.js'
import loader from '@monaco-editor/loader'

const toast = useToast()

// Monaco Editor instance
const monacoEditor = shallowRef<any>(null)
const monacoInstance = shallowRef<any>(null)
const editorContainer = ref<HTMLElement | null>(null)

// Monaco themes mapping (highlight.js theme -> Monaco theme)
const monacoThemeMap: Record<string, string> = {
  'github-dark': 'vs-dark',
  'github-dark-dimmed': 'vs-dark',
  'monokai': 'monokai',
  'dracula': 'dracula',
  'atom-one-dark': 'vs-dark',
  'vs2015': 'vs-dark',
  'nord': 'nord',
  'tokyo-night-dark': 'vs-dark',
  'agate': 'vs-dark',
  'androidstudio': 'vs-dark',
  'hybrid': 'vs-dark',
  'obsidian': 'vs-dark',
  'stackoverflow-dark': 'vs-dark',
  'night-owl': 'night-owl',
  'a11y-dark': 'vs-dark',
}

// Code themes list
const codeThemes = [
  { value: 'github-dark', label: 'GitHub Dark' },
  { value: 'github-dark-dimmed', label: 'GitHub Dimmed' },
  { value: 'monokai', label: 'Monokai' },
  { value: 'dracula', label: 'Dracula' },
  { value: 'atom-one-dark', label: 'Atom One Dark' },
  { value: 'vs2015', label: 'VS 2015' },
  { value: 'nord', label: 'Nord' },
  { value: 'tokyo-night-dark', label: 'Tokyo Night' },
  { value: 'agate', label: 'Agate' },
  { value: 'androidstudio', label: 'Android Studio' },
  { value: 'hybrid', label: 'Hybrid' },
  { value: 'obsidian', label: 'Obsidian' },
  { value: 'stackoverflow-dark', label: 'Stack Overflow' },
  { value: 'night-owl', label: 'Night Owl' },
  { value: 'a11y-dark', label: 'A11y Dark' },
]

// Dynamic theme loading
const currentTheme = ref(localStorage.getItem('ams_code_theme') || 'github-dark')
let themeLink: HTMLLinkElement | null = null

function loadTheme(themeName: string) {
  if (themeLink) {
    themeLink.remove()
  }
  themeLink = document.createElement('link')
  themeLink.rel = 'stylesheet'
  themeLink.href = `https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/styles/${themeName}.min.css`
  document.head.appendChild(themeLink)
}

function onThemeChange(event: CustomEvent) {
  currentTheme.value = event.detail
  loadTheme(event.detail)
  if (monacoInstance.value && monacoEditor.value) {
    const monacoTheme = monacoThemeMap[event.detail] || 'vs-dark'
    monacoInstance.value.editor.setTheme(monacoTheme)
  }
}

function onThemeSelect() {
  loadTheme(currentTheme.value)
  localStorage.setItem('ams_code_theme', currentTheme.value)
  if (monacoInstance.value && monacoEditor.value) {
    const monacoTheme = monacoThemeMap[currentTheme.value] || 'vs-dark'
    monacoInstance.value.editor.setTheme(monacoTheme)
  }
}

// Define custom Monaco themes
async function defineCustomThemes(monaco: any) {
  monaco.editor.defineTheme('monokai', {
    base: 'vs-dark',
    inherit: true,
    rules: [
      { token: 'comment', foreground: '88846f' },
      { token: 'keyword', foreground: 'f92672' },
      { token: 'string', foreground: 'e6db74' },
      { token: 'number', foreground: 'ae81ff' },
      { token: 'type', foreground: '66d9ef' },
      { token: 'function', foreground: 'a6e22e' },
      { token: 'variable', foreground: 'f8f8f2' },
    ],
    colors: {
      'editor.background': '#272822',
      'editor.foreground': '#f8f8f2',
      'editorLineNumber.foreground': '#90908a',
      'editor.selectionBackground': '#49483e',
      'editor.lineHighlightBackground': '#3e3d32',
    }
  })

  monaco.editor.defineTheme('dracula', {
    base: 'vs-dark',
    inherit: true,
    rules: [
      { token: 'comment', foreground: '6272a4' },
      { token: 'keyword', foreground: 'ff79c6' },
      { token: 'string', foreground: 'f1fa8c' },
      { token: 'number', foreground: 'bd93f9' },
      { token: 'type', foreground: '8be9fd' },
      { token: 'function', foreground: '50fa7b' },
      { token: 'variable', foreground: 'f8f8f2' },
    ],
    colors: {
      'editor.background': '#282a36',
      'editor.foreground': '#f8f8f2',
      'editorLineNumber.foreground': '#6272a4',
      'editor.selectionBackground': '#44475a',
      'editor.lineHighlightBackground': '#44475a',
    }
  })

  monaco.editor.defineTheme('nord', {
    base: 'vs-dark',
    inherit: true,
    rules: [
      { token: 'comment', foreground: '616e88' },
      { token: 'keyword', foreground: '81a1c1' },
      { token: 'string', foreground: 'a3be8c' },
      { token: 'number', foreground: 'b48ead' },
      { token: 'type', foreground: '8fbcbb' },
      { token: 'function', foreground: '88c0d0' },
      { token: 'variable', foreground: 'd8dee9' },
    ],
    colors: {
      'editor.background': '#2e3440',
      'editor.foreground': '#d8dee9',
      'editorLineNumber.foreground': '#4c566a',
      'editor.selectionBackground': '#434c5e',
      'editor.lineHighlightBackground': '#3b4252',
    }
  })

  monaco.editor.defineTheme('night-owl', {
    base: 'vs-dark',
    inherit: true,
    rules: [
      { token: 'comment', foreground: '637777' },
      { token: 'keyword', foreground: 'c792ea' },
      { token: 'string', foreground: 'ecc48d' },
      { token: 'number', foreground: 'f78c6c' },
      { token: 'type', foreground: 'ffcb8b' },
      { token: 'function', foreground: '82aaff' },
      { token: 'variable', foreground: 'd6deeb' },
    ],
    colors: {
      'editor.background': '#011627',
      'editor.foreground': '#d6deeb',
      'editorLineNumber.foreground': '#4b6479',
      'editor.selectionBackground': '#1d3b53',
      'editor.lineHighlightBackground': '#0b2942',
    }
  })
}

// Initialize Monaco editor
async function initMonaco() {
  if (!editorContainer.value) return

  const monaco = await loader.init()
  monacoInstance.value = monaco
  await defineCustomThemes(monaco)

  const monacoTheme = monacoThemeMap[currentTheme.value] || 'vs-dark'
  
  monacoEditor.value = monaco.editor.create(editorContainer.value, {
    value: fileContent.value,
    language: getMonacoLanguage(currentFile.value?.name || ''),
    theme: monacoTheme,
    automaticLayout: true,
    minimap: { enabled: true },
    fontSize: 14,
    fontFamily: "'JetBrains Mono', 'Fira Code', 'Consolas', monospace",
    lineNumbers: 'on',
    scrollBeyondLastLine: false,
    wordWrap: 'off',
    tabSize: 2,
    insertSpaces: true,
    renderWhitespace: 'selection',
    bracketPairColorization: { enabled: true },
    cursorBlinking: 'smooth',
    smoothScrolling: true,
    padding: { top: 16, bottom: 16 },
    suggestOnTriggerCharacters: true,
    quickSuggestions: true,
    acceptSuggestionOnEnter: 'on',
    tabCompletion: 'on',
    wordBasedSuggestions: 'currentDocument',
    parameterHints: { enabled: true },
    folding: true,
    foldingStrategy: 'indentation',
    showFoldingControls: 'mouseover',
    formatOnPaste: true,
    formatOnType: true,
  })

  monacoEditor.value.onDidChangeModelContent(() => {
    fileContent.value = monacoEditor.value.getValue()
  })

  monacoEditor.value.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => {
    if (hasChanges.value) {
      openSaveDialog()
    }
  })
}

function destroyMonaco() {
  if (monacoEditor.value) {
    monacoEditor.value.dispose()
    monacoEditor.value = null
  }
}

function getMonacoLanguage(filename: string): string {
  const ext = filename.split('.').pop()?.toLowerCase() || ''
  const langMap: Record<string, string> = {
    ts: 'typescript', tsx: 'typescript', js: 'javascript', jsx: 'javascript',
    vue: 'html', html: 'html', css: 'css', scss: 'scss', less: 'less',
    json: 'json', md: 'markdown', py: 'python', rs: 'rust', go: 'go',
    sh: 'shell', bash: 'shell', yaml: 'yaml', yml: 'yaml', sql: 'sql',
    dockerfile: 'dockerfile', xml: 'xml', c: 'c', cpp: 'cpp', h: 'c',
    hpp: 'cpp', cs: 'csharp', java: 'java', rb: 'ruby', php: 'php',
    swift: 'swift', kt: 'kotlin', scala: 'scala', r: 'r', lua: 'lua',
    perl: 'perl', pl: 'perl',
  }
  return langMap[ext] || 'plaintext'
}

// LocalStorage keys for remembering state
const STORAGE_KEYS = {
  project: 'ams_gitlab_project_id',
  branch: 'ams_gitlab_branch',
  file: 'ams_gitlab_file_path',
  scrollTop: 'ams_gitlab_scroll_top',
  scrollLeft: 'ams_gitlab_scroll_left',
  expandedPrefix: 'ams_gitlab_expanded_'
}

// Helper functions for expanded keys storage
function saveExpandedKeys(projectId: number, keys: Record<string, boolean>) {
  localStorage.setItem(STORAGE_KEYS.expandedPrefix + projectId, JSON.stringify(keys))
}

function loadExpandedKeys(projectId: number): Record<string, boolean> {
  const saved = localStorage.getItem(STORAGE_KEYS.expandedPrefix + projectId)
  return saved ? JSON.parse(saved) : {}
}

// Flag to track if we're restoring state
const isRestoring = ref(false)

// Refs for scroll position
const editorContentRef = ref<HTMLElement | null>(null)

// Debounced scroll position saver
let scrollSaveTimeout: number | null = null
function saveScrollPosition() {
  if (scrollSaveTimeout) clearTimeout(scrollSaveTimeout)
  scrollSaveTimeout = window.setTimeout(() => {
    if (editorContentRef.value) {
      localStorage.setItem(STORAGE_KEYS.scrollTop, editorContentRef.value.scrollTop.toString())
      localStorage.setItem(STORAGE_KEYS.scrollLeft, editorContentRef.value.scrollLeft.toString())
    }
  }, 300)
}

function restoreScrollPosition() {
  const savedTop = localStorage.getItem(STORAGE_KEYS.scrollTop)
  const savedLeft = localStorage.getItem(STORAGE_KEYS.scrollLeft)
  setTimeout(() => {
    if (editorContentRef.value) {
      if (savedTop) editorContentRef.value.scrollTop = parseInt(savedTop, 10)
      if (savedLeft) editorContentRef.value.scrollLeft = parseInt(savedLeft, 10)
    }
  }, 200)
}

import { onUnmounted } from 'vue'
onUnmounted(() => {
  window.removeEventListener('code-theme-changed', onThemeChange as EventListener)
  if (themeLink) themeLink.remove()
  destroyMonaco()
})

interface Project {
  id: number
  name: string
  fullName: string
  path: string
  description: string | null
  defaultBranch: string
  webUrl: string
  lastActivity: string
}

interface Branch {
  name: string
  isDefault: boolean
  isProtected: boolean
  lastCommit: { id: string; shortId: string; title: string; author: string; date: string }
}

interface Commit {
  id: string
  shortId: string
  title: string
  message: string
  author: string
  authorEmail: string
  date: string
  webUrl: string
}

interface TreeNode {
  key: string
  label: string
  icon: string
  data: { path: string; type: 'folder' | 'file' }
  children?: TreeNode[]
  leaf?: boolean
}

interface FileContent {
  name: string
  path: string
  size: number
  content: string
  branch: string
}

// State
const loading = ref(false)
const projects = ref<Project[]>([])
const selectedProject = ref<Project | null>(null)
const branches = ref<Branch[]>([])
const selectedBranch = ref<Branch | null>(null)
const commits = ref<Commit[]>([])
const treeNodes = ref<TreeNode[]>([])
const expandedKeys = ref<Record<string, boolean>>({})
const treeKey = ref(0)

// File editor state
const currentFile = ref<FileContent | null>(null)
const fileContent = ref('')
const originalContent = ref('')
const fileLoading = ref(false)
const showSaveDialog = ref(false)
const commitMessage = ref('')
const saving = ref(false)
const isEditing = ref(false)
const codeRef = ref<HTMLElement | null>(null)

// Commit detail state
const showCommitDialog = ref(false)
const selectedCommit = ref<any>(null)
const commitLoading = ref(false)

const hasChanges = computed(() => fileContent.value !== originalContent.value)

function getLanguage(filename: string): string {
  const ext = filename.split('.').pop()?.toLowerCase() || ''
  const langMap: Record<string, string> = {
    ts: 'typescript', tsx: 'typescript', js: 'javascript', jsx: 'javascript',
    vue: 'xml', html: 'xml', css: 'css', scss: 'scss', json: 'json',
    md: 'markdown', py: 'python', rs: 'rust', go: 'go', sh: 'bash',
    yaml: 'yaml', yml: 'yaml', sql: 'sql', dockerfile: 'dockerfile'
  }
  return langMap[ext] || 'plaintext'
}

const highlightedCode = computed(() => {
  if (!currentFile.value || !fileContent.value) return ''
  const lang = getLanguage(currentFile.value.name)
  try {
    if (hljs.getLanguage(lang)) {
      return hljs.highlight(fileContent.value, { language: lang }).value
    }
    return hljs.highlightAuto(fileContent.value).value
  } catch {
    return fileContent.value.replace(/</g, '&lt;').replace(/>/g, '&gt;')
  }
})

async function startEditing() {
  isEditing.value = true
  await nextTick()
  await initMonaco()
}

function cancelEditing() {
  isEditing.value = false
  fileContent.value = originalContent.value
  destroyMonaco()
}

function getBranchLabel(branch: Branch): string {
  return branch.name + (branch.isDefault ? ' (default)' : '') + (branch.isProtected ? ' 🔒' : '')
}

onMounted(async () => {
  loadTheme(currentTheme.value)
  window.addEventListener('code-theme-changed', onThemeChange as EventListener)
  
  await loadProjects()
  
  // Restore last selected project
  const savedProjectId = localStorage.getItem(STORAGE_KEYS.project)
  if (savedProjectId) {
    const project = projects.value.find(p => p.id === parseInt(savedProjectId))
    if (project) {
      isRestoring.value = true
      selectedProject.value = project
    }
  }
})

// Watch for project change
watch(selectedProject, async (project, oldProject) => {
  if (project) {
    // Save to localStorage
    if (!isRestoring.value) {
      localStorage.setItem(STORAGE_KEYS.project, project.id.toString())
    }
    
    // Load expanded keys for this project
    expandedKeys.value = loadExpandedKeys(project.id)
    treeKey.value++
    
    await loadBranches(project.id)
  } else {
    branches.value = []
    selectedBranch.value = null
  }
})

// Watch for branch change
watch(selectedBranch, async (branch) => {
  if (branch && selectedProject.value) {
    // Save to localStorage
    if (!isRestoring.value) {
      localStorage.setItem(STORAGE_KEYS.branch, branch.name)
    }
    
    const [, rootNodes] = await Promise.all([
      loadCommits(selectedProject.value.id, branch.name),
      loadTree(selectedProject.value.id, branch.name, '')
    ])
    treeNodes.value = rootNodes
    
    // Restore expanded keys after tree is loaded
    await nextTick()
    if (!isRestoring.value) {
      const savedKeys = loadExpandedKeys(selectedProject.value.id)
      if (Object.keys(savedKeys).length > 0) {
        expandedKeys.value = { ...savedKeys }
      }
    }
    
    // Restore file if we're restoring state
    if (isRestoring.value) {
      const savedFilePath = localStorage.getItem(STORAGE_KEYS.file)
      if (savedFilePath) {
        await loadFile(selectedProject.value.id, branch.name, savedFilePath)
        restoreScrollPosition()
      }
      isRestoring.value = false
    }
  } else {
    commits.value = []
    treeNodes.value = []
  }
  
  // Clear current file when branch changes (unless restoring)
  if (!isRestoring.value) {
    currentFile.value = null
    fileContent.value = ''
    originalContent.value = ''
    destroyMonaco()
    isEditing.value = false
  }
})

// Watch expandedKeys to save them
watch(expandedKeys, (keys) => {
  if (selectedProject.value && !isRestoring.value) {
    saveExpandedKeys(selectedProject.value.id, keys)
  }
}, { deep: true })

async function loadProjects() {
  loading.value = true
  try {
    projects.value = await api.get('/gitlab/projects')
  } catch (error: any) {
    toast.add({ severity: 'error', summary: 'Fehler', detail: 'Projekte konnten nicht geladen werden', life: 3000 })
  } finally {
    loading.value = false
  }
}

async function loadBranches(projectId: number) {
  loading.value = true
  try {
    branches.value = await api.get(`/gitlab/projects/${projectId}/branches`)
    
    // Restore saved branch or use default
    const savedBranch = localStorage.getItem(STORAGE_KEYS.branch)
    let branchToSelect = null
    
    if (isRestoring.value && savedBranch) {
      branchToSelect = branches.value.find(b => b.name === savedBranch)
    }
    
    if (!branchToSelect) {
      branchToSelect = branches.value.find(b => b.isDefault) || branches.value[0]
    }
    
    if (branchToSelect) {
      selectedBranch.value = branchToSelect
    }
  } catch (error: any) {
    toast.add({ severity: 'error', summary: 'Fehler', detail: 'Branches konnten nicht geladen werden', life: 3000 })
  } finally {
    loading.value = false
  }
}

async function loadCommits(projectId: number, branch: string) {
  try {
    commits.value = await api.get(`/gitlab/projects/${projectId}/commits?branch=${encodeURIComponent(branch)}&per_page=50`)
  } catch (error: any) {
    toast.add({ severity: 'error', summary: 'Fehler', detail: 'Commits konnten nicht geladen werden', life: 3000 })
  }
}

async function loadTree(projectId: number, branch: string, path: string): Promise<TreeNode[]> {
  try {
    const pathParam = path ? `&path=${encodeURIComponent(path)}` : ''
    const items: any[] = await api.get(`/gitlab/projects/${projectId}/tree?branch=${encodeURIComponent(branch)}${pathParam}`)
    return items.map((item: any) => ({
      key: item.path,
      label: item.name,
      icon: item.type === 'folder' ? 'pi pi-folder' : getFileIcon(item.name),
      data: { path: item.path, type: item.type },
      leaf: item.type === 'file',
      children: item.type === 'folder' ? [] : undefined
    }))
  } catch (error: any) {
    return []
  }
}

function getFileIcon(filename: string): string {
  const ext = filename.split('.').pop()?.toLowerCase()
  switch (ext) {
    case 'ts': case 'tsx': case 'js': case 'jsx': case 'vue': return 'pi pi-code'
    case 'json': case 'md': return 'pi pi-file'
    case 'css': case 'scss': return 'pi pi-palette'
    case 'html': return 'pi pi-globe'
    case 'png': case 'jpg': case 'jpeg': case 'gif': case 'svg': return 'pi pi-image'
    default: return 'pi pi-file'
  }
}

function formatDiff(diffText: string, filename: string): string {
  if (!diffText) return ''
  const lines = diffText.split('\n')
  return lines.map(line => {
    const escaped = line.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
    if (line.startsWith('@@')) return `<div class="diff-line diff-hunk">${escaped}</div>`
    if (line.startsWith('+')) return `<div class="diff-line diff-add">${escaped}</div>`
    if (line.startsWith('-')) return `<div class="diff-line diff-remove">${escaped}</div>`
    return `<div class="diff-line diff-context">${escaped}</div>`
  }).join('')
}

async function onNodeExpand(node: any) {
  if (node.data.type === 'folder' && (!node.children || node.children.length === 0)) {
    if (selectedProject.value && selectedBranch.value) {
      node.children = await loadTree(selectedProject.value.id, selectedBranch.value.name, node.data.path)
    }
  }
}

async function onNodeSelect(node: any) {
  if (node.data.type === 'file' && selectedProject.value && selectedBranch.value) {
    if (isEditing.value) {
      destroyMonaco()
      isEditing.value = false
    }
    await loadFile(selectedProject.value.id, selectedBranch.value.name, node.data.path)
    // Save file path to localStorage
    localStorage.setItem(STORAGE_KEYS.file, node.data.path)
  }
}

async function loadFile(projectId: number, branch: string, path: string) {
  fileLoading.value = true
  try {
    const file: FileContent = await api.get(`/gitlab/projects/${projectId}/file?branch=${encodeURIComponent(branch)}&path=${encodeURIComponent(path)}`)
    currentFile.value = file
    fileContent.value = file.content
    originalContent.value = file.content
  } catch (error: any) {
    toast.add({ severity: 'error', summary: 'Fehler', detail: 'Datei konnte nicht geladen werden', life: 3000 })
  } finally {
    fileLoading.value = false
  }
}

function openSaveDialog() {
  commitMessage.value = `Update ${currentFile.value?.name}`
  showSaveDialog.value = true
}

async function saveFile() {
  if (!currentFile.value || !selectedProject.value || !selectedBranch.value || !commitMessage.value) return
  
  saving.value = true
  try {
    await api.put(`/gitlab/projects/${selectedProject.value.id}/file`, {
      path: currentFile.value.path,
      branch: selectedBranch.value.name,
      content: fileContent.value,
      commitMessage: commitMessage.value
    })
    toast.add({ severity: 'success', summary: 'Gespeichert', detail: 'Datei wurde committed', life: 3000 })
    originalContent.value = fileContent.value
    showSaveDialog.value = false
    await loadCommits(selectedProject.value.id, selectedBranch.value.name)
  } catch (error: any) {
    toast.add({ severity: 'error', summary: 'Fehler', detail: 'Datei konnte nicht gespeichert werden', life: 3000 })
  } finally {
    saving.value = false
  }
}

function discardChanges() {
  fileContent.value = originalContent.value
  if (monacoEditor.value) {
    monacoEditor.value.setValue(originalContent.value)
  }
}

async function viewCommit(commit: Commit) {
  if (!selectedProject.value) return
  commitLoading.value = true
  showCommitDialog.value = true
  try {
    selectedCommit.value = await api.get(`/gitlab/projects/${selectedProject.value.id}/commit/${commit.id}`)
  } catch (error: any) {
    toast.add({ severity: 'error', summary: 'Fehler', detail: 'Commit-Details konnten nicht geladen werden', life: 3000 })
  } finally {
    commitLoading.value = false
  }
}

function formatDate(dateStr: string) {
  return new Date(dateStr).toLocaleString('de-DE', {
    day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit'
  })
}
</script>

<template>
  <div class="gitlab-view">
    <!-- Header -->
    <div class="header">
      <h1><i class="pi pi-github"></i> GitLab</h1>
      <div class="controls">
        <Dropdown
          v-model="selectedProject"
          :options="projects"
          optionLabel="fullName"
          placeholder="Projekt auswählen..."
          :loading="loading"
          class="project-dropdown"
          filter
          filterPlaceholder="Suchen..."
        />
        <Dropdown
          v-model="selectedBranch"
          :options="branches"
          :optionLabel="getBranchLabel"
          placeholder="Branch..."
          :disabled="!selectedProject"
          class="branch-dropdown"
        />
        <Button
          v-if="selectedProject"
          icon="pi pi-external-link"
          label="GitLab öffnen"
          severity="secondary"
          size="small"
          @click="window.open(selectedProject.webUrl, '_blank')"
        />
      </div>
    </div>

    <!-- Main Content -->
    <div v-if="selectedProject && selectedBranch" class="main-content">
      <Splitter class="main-splitter">
        <!-- Left: File Tree + Commits -->
        <SplitterPanel :size="30" :minSize="20">
          <TabView>
            <TabPanel header="Dateien">
              <div class="file-tree">
                <Tree
                  :key="treeKey"
                  :value="treeNodes"
                  v-model:expandedKeys="expandedKeys"
                  selectionMode="single"
                  @nodeExpand="onNodeExpand"
                  @nodeSelect="onNodeSelect"
                  class="w-full"
                />
              </div>
            </TabPanel>
            <TabPanel header="Commits">
              <div class="commits-list">
                <div
                  v-for="commit in commits"
                  :key="commit.id"
                  class="commit-item"
                  @click="viewCommit(commit)"
                >
                  <div class="commit-title">{{ commit.title }}</div>
                  <div class="commit-meta">
                    <span class="commit-id">{{ commit.shortId }}</span>
                    <span class="commit-author">{{ commit.author }}</span>
                    <span class="commit-date">{{ formatDate(commit.date) }}</span>
                  </div>
                </div>
              </div>
            </TabPanel>
          </TabView>
        </SplitterPanel>

        <!-- Right: File Editor -->
        <SplitterPanel :size="70" :minSize="40">
          <div class="editor-panel">
            <template v-if="currentFile">
              <div class="editor-header">
                <div class="file-info">
                  <i :class="getFileIcon(currentFile.name)"></i>
                  <span class="file-path">{{ currentFile.path }}</span>
                  <span v-if="hasChanges" class="unsaved-badge">●</span>
                </div>
                <div class="editor-actions">
                  <select v-model="currentTheme" @change="onThemeSelect" class="theme-select-inline">
                    <option v-for="theme in codeThemes" :key="theme.value" :value="theme.value">
                      {{ theme.label }}
                    </option>
                  </select>
                  <Button v-if="!isEditing" icon="pi pi-pencil" label="Bearbeiten" severity="secondary" size="small" @click="startEditing" />
                  <Button v-if="isEditing" icon="pi pi-times" label="Abbrechen" severity="secondary" size="small" @click="cancelEditing" />
                  <Button v-if="isEditing && hasChanges" icon="pi pi-save" label="Committen" size="small" @click="openSaveDialog" />
                </div>
              </div>
              <div class="editor-content" ref="editorContentRef" @scroll="saveScrollPosition">
                <div v-if="isEditing" ref="editorContainer" class="monaco-container"></div>
                <pre v-else class="code-view"><code ref="codeRef" v-html="highlightedCode"></code></pre>
              </div>
            </template>
            <template v-else-if="fileLoading">
              <div class="loading-state">
                <ProgressSpinner />
                <p>Datei wird geladen...</p>
              </div>
            </template>
            <template v-else>
              <div class="empty-state">
                <i class="pi pi-file"></i>
                <p>Wähle eine Datei aus dem Baum</p>
              </div>
            </template>
          </div>
        </SplitterPanel>
      </Splitter>
    </div>

    <!-- No project selected -->
    <div v-else class="empty-state large">
      <i class="pi pi-github"></i>
      <h2>GitLab Projekte</h2>
      <p>Wähle ein Projekt und Branch aus um loszulegen</p>
    </div>

    <!-- Save Dialog -->
    <Dialog v-model:visible="showSaveDialog" header="Änderungen committen" :style="{ width: '90vw', maxWidth: '500px' }" modal appendTo="self">
      <div class="save-dialog-content">
        <label>Commit-Nachricht:</label>
        <InputText v-model="commitMessage" class="w-full" />
      </div>
      <template #footer>
        <Button label="Abbrechen" severity="secondary" @click="showSaveDialog = false" />
        <Button label="Committen" icon="pi pi-check" :loading="saving" @click="saveFile" />
      </template>
    </Dialog>

    <!-- Commit Detail Dialog -->
    <Dialog v-model:visible="showCommitDialog" header="Commit Details" :style="{ width: '90vw', maxWidth: '900px' }" :contentStyle="{ maxHeight: '75vh', overflow: 'auto' }" modal appendTo="self">
      <template v-if="commitLoading">
        <div class="loading-state"><ProgressSpinner /></div>
      </template>
      <template v-else-if="selectedCommit">
        <div class="commit-detail">
          <div class="commit-header">
            <h3>{{ selectedCommit.title }}</h3>
            <p class="commit-message" v-if="selectedCommit.message !== selectedCommit.title">{{ selectedCommit.message }}</p>
            <div class="commit-meta-detail">
              <span><i class="pi pi-user"></i> {{ selectedCommit.author }}</span>
              <span><i class="pi pi-calendar"></i> {{ formatDate(selectedCommit.date) }}</span>
              <span><i class="pi pi-hashtag"></i> {{ selectedCommit.shortId }}</span>
            </div>
            <div class="commit-stats" v-if="selectedCommit.stats">
              <span class="additions">+{{ selectedCommit.stats.additions }}</span>
              <span class="deletions">-{{ selectedCommit.stats.deletions }}</span>
            </div>
          </div>
          <div class="commit-diff" v-if="selectedCommit.diff">
            <div v-for="(file, idx) in selectedCommit.diff" :key="idx" class="diff-file">
              <div class="diff-file-header"><span class="diff-file-name">{{ file.newPath }}</span></div>
              <div class="diff-content" v-html="formatDiff(file.diff, file.newPath)"></div>
            </div>
          </div>
        </div>
      </template>
    </Dialog>
  </div>
</template>

<style scoped>
.gitlab-view {
  padding: 0.5rem;
  height: 100%;
  display: flex;
  flex-direction: column;
}

.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 0.5rem;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.header h1 {
  margin: 0;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.controls {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  flex-wrap: wrap;
}

.project-dropdown { min-width: 300px; }
.branch-dropdown { min-width: 200px; }

.main-content {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.main-splitter {
  flex: 1;
  min-height: 0;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  background: #0d1117;
}

.file-tree {
  height: calc(100vh - 170px);
  overflow: auto;
  padding: 0;
  background: #0d1117 !important;
}

.file-tree * { background-color: transparent; }

.commits-list {
  height: calc(100vh - 170px);
  overflow: auto;
  background: #0d1117;
}

.commit-item {
  padding: 0.75rem 1rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  cursor: pointer;
  transition: background 0.2s;
}

.commit-item:hover { background: rgba(255, 255, 255, 0.08); }

.commit-title {
  font-weight: 500;
  margin-bottom: 0.25rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: #e6edf3;
}

.commit-meta {
  display: flex;
  gap: 1rem;
  font-size: 0.75rem;
  color: #8b949e;
}

.commit-id {
  font-family: monospace;
  background: rgba(255, 255, 255, 0.1);
  padding: 0.1rem 0.4rem;
  border-radius: 4px;
  color: #79c0ff;
}

.commit-author { color: #e6edf3; }
.commit-date { color: #8b949e; }

.editor-panel {
  height: 100%;
  display: flex;
  flex-direction: column;
  background: #0d1117;
}

.editor-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.5rem 1rem;
  background: #161b22;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}

.file-info {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.file-path {
  font-family: monospace;
  font-size: 0.875rem;
}

.unsaved-badge {
  color: var(--primary-color);
  font-size: 1.5rem;
  line-height: 1;
}

.editor-actions {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}

.theme-select-inline {
  padding: 0.4rem 0.75rem;
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 6px;
  color: #e6edf3;
  font-size: 0.8rem;
  cursor: pointer;
  min-width: 140px;
}

.theme-select-inline option {
  background: #161b22;
  color: #e6edf3;
}

.theme-select-inline:hover { border-color: rgba(255, 255, 255, 0.3); }
.theme-select-inline:focus { outline: none; border-color: #6366f1; }

.editor-content {
  flex: 1;
  min-height: 0;
  overflow: auto;
}

.monaco-container {
  width: 100%;
  height: 100%;
  min-height: 500px;
}

.code-view {
  margin: 0;
  padding: 1rem;
  height: 100%;
  overflow: auto;
  background: #0d1117;
  font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
  font-size: 0.875rem;
  line-height: 1.6;
  tab-size: 2;
}

.code-view code { font-family: inherit; padding: 0; }
.code-view .hljs { background: transparent !important; }

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: #8b949e;
}

.empty-state i {
  font-size: 3rem;
  margin-bottom: 1rem;
  opacity: 0.5;
}

.empty-state.large { height: calc(100vh - 150px); }
.empty-state.large i { font-size: 5rem; }

.loading-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 200px;
  gap: 1rem;
}

.save-dialog-content {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.commit-detail .commit-header { margin-bottom: 1rem; }
.commit-detail h3 { margin: 0 0 0.5rem 0; }
.commit-message { white-space: pre-wrap; color: #8b949e; margin: 0.5rem 0; }

.commit-meta-detail {
  display: flex;
  gap: 1.5rem;
  font-size: 0.875rem;
  color: #8b949e;
  margin: 0.75rem 0;
}

.commit-meta-detail i { margin-right: 0.25rem; }
.commit-stats { display: flex; gap: 1rem; font-family: monospace; }
.commit-stats .additions { color: #22c55e; }
.commit-stats .deletions { color: #ef4444; }

.diff-file {
  margin-bottom: 1rem;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  overflow: hidden;
}

.diff-file-header {
  background: #161b22;
  padding: 0.5rem 1rem;
  font-family: monospace;
  font-size: 0.875rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  color: #e6edf3;
}

.diff-content {
  background: #0d1117;
  color: #e6edf3;
  padding: 0;
  margin: 0;
  overflow-x: auto;
  font-size: 0.8rem;
  line-height: 1;
  font-family: 'JetBrains Mono', 'Fira Code', monospace;
}

/* v-html generated content needs :deep() to receive scoped styles */
.diff-content :deep(.diff-line) {
  padding: 0.15rem 1rem;
  white-space: pre;
  border-left: 3px solid transparent;
}

.diff-content :deep(.diff-hunk) {
  background: rgba(56, 139, 253, 0.15);
  color: #79c0ff;
  border-left-color: #388bfd;
  padding: 0.3rem 1rem;
  margin: 0.25rem 0;
}

.diff-content :deep(.diff-add) {
  background: rgba(46, 160, 67, 0.2);
  color: #7ee787;
  border-left-color: #2ea043;
}

.diff-content :deep(.diff-remove) {
  background: rgba(248, 81, 73, 0.2);
  color: #ffa198;
  border-left-color: #f85149;
}

.diff-content :deep(.diff-context) { color: #8b949e; }
.diff-file-name { color: #e6edf3; }

/* PrimeVue Dark Theme Overrides */
:deep(.p-tabview) { background: #0d1117; }
:deep(.p-tabview-panels) { background: #0d1117; padding: 0; }
:deep(.p-tabview-nav) { background: #161b22; border-bottom: 1px solid rgba(255, 255, 255, 0.1); }
:deep(.p-tabview-nav li) { margin: 0; }
:deep(.p-tabview-nav li .p-tabview-nav-link) { background: transparent; color: #8b949e; border: none; padding: 0.75rem 1.25rem; font-weight: 500; }
:deep(.p-tabview-nav li .p-tabview-nav-link:hover) { background: rgba(255, 255, 255, 0.05); color: #e6edf3; }
:deep(.p-tabview-nav li.p-highlight .p-tabview-nav-link) { background: transparent; color: #58a6ff; border-bottom: 2px solid #58a6ff; }

:deep(.p-tree) { background: #0d1117 !important; border: none !important; padding: 0.5rem !important; color: #e6edf3 !important; }
:deep(.p-tree-root), :deep(.p-tree-root-children) { background: #0d1117 !important; }
:deep(.p-treenode) { background: transparent !important; }
:deep(.p-treenode-content) { padding: 0.35rem 0.5rem !important; border-radius: 6px !important; background: transparent !important; color: #e6edf3 !important; }
:deep(.p-treenode-content:hover) { background: rgba(255, 255, 255, 0.08) !important; }
:deep(.p-treenode-content.p-highlight) { background: rgba(88, 166, 255, 0.2) !important; }
:deep(.p-tree-toggler) { color: #8b949e !important; background: transparent !important; }
:deep(.p-tree-toggler:hover) { background: rgba(255, 255, 255, 0.1) !important; color: #e6edf3 !important; }
:deep(.p-treenode-icon) { color: #8b949e !important; }
:deep(.p-treenode-label) { color: #e6edf3 !important; }
:deep(.p-tree-wrapper) { background: #0d1117 !important; }

:deep(.p-splitter) { background: #0d1117; border: none; }
:deep(.p-splitter-gutter) { background: #21262d; }
:deep(.p-splitter-gutter:hover) { background: #30363d; }

:deep(.p-dialog) { background: #161b22; border: 1px solid rgba(255, 255, 255, 0.1); }
:deep(.p-dialog-header) { background: #161b22; color: #e6edf3; border-bottom: 1px solid rgba(255, 255, 255, 0.1); }
:deep(.p-dialog-content) { background: #161b22; color: #e6edf3; }
:deep(.p-dialog-footer) { background: #161b22; border-top: 1px solid rgba(255, 255, 255, 0.1); }
:deep(.p-inputtext) { background: #0d1117; border: 1px solid rgba(255, 255, 255, 0.2); color: #e6edf3; }
:deep(.p-inputtext:focus) { border-color: #58a6ff; box-shadow: 0 0 0 2px rgba(88, 166, 255, 0.2); }
</style>
