<template>
  <div class="app">
    <header class="top-bar">
      <h1>Simple Publisher</h1>
      <button class="theme-toggle" @click="toggleDarkMode"
              :aria-label="darkMode ? 'Switch to light mode' : 'Switch to dark mode'">
        {{ darkMode ? 'Light Mode' : 'Dark Mode' }}
      </button>
    </header>

    <main class="body-wrapper">
      <div class="layout">
        <div class="sidebar-col">
          <Sidebar :sites="sites"
                   :selected-id="selectedId"
                   :pending-add="pendingAdd"
                   @select-site="selectSite"
                   @start-add="startAdd"
                   @cancel-add="cancelAdd"
                   @add-site="confirmAdd" />
        </div>

        <div class="settings-col">
          <SettingsPanel v-if="selectedSite"
                        :site="selectedSite"
                        :last-saved="lastSaved"
                        :site-id="selectedSite.id"
                        @delete="deleteSelected"
                        @preview="openPreview"
                        @changed="handleEditChanged" />
          <SettingsEmpty v-else />
        </div>

        <div class="filetree-col" v-if="selectedSite">
          <FileTree :site-id="selectedSite.id" />
        </div>
      </div>
    </main>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import Sidebar from './components/Sidebar.vue'
import SettingsPanel from './components/SettingsPanel.vue'
import SettingsEmpty from './components/SettingsEmpty.vue'
import FileTree from './components/FileTree.vue'

const API_BASE = import.meta.url.match(/^https?:\/\/[^/]+/) 
  ? `${location.protocol}//${location.host}`
  : ''

function api(url: string, options: RequestInit = {}) {
  return fetch(`${API_BASE}/api/sites${url}`, {
    headers: { 'Content-Type': 'application/json', ...options.headers },
    ...options,
  })
}

function genId() {
  return crypto.randomUUID?.() ?? Date.now().toString(36) + Math.random().toString(36).slice(2)
}

const STORAGE_KEY = 'simple-publisher-sites'
const THEME_KEY = 'simple-publisher-theme'

function defaultSite(name: string) {
  return {
    id: genId(),
    name,
    url: '',
    description: '',
    themeColor: '#2563eb',
    targetWindow: '_blank',
    enabled: true,
    gitRepoUrl: '',
    gitBranch: 'main',
  }
}

const darkMode = ref(false)

function toggleDarkMode() {
  darkMode.value = !darkMode.value
  applyClass()
  try { localStorage.setItem(THEME_KEY, darkMode.value ? 'dark' : 'light') } catch {}
}

function applyClass() {
  document.documentElement.classList.toggle('dark', darkMode.value)
}

const sites = ref<Array<{ _id: string; id: string; name: string; url: string; description: string; themeColor: string; targetWindow: string; enabled: boolean; gitRepoUrl?: string; gitBranch?: string }>>([])
const selectedId = ref(null)
const pendingAdd = ref(false)
const lastSaved = ref('')

let newSites = new Map<string, any>()
let dirtyEdits = new Map<string, any>()
let loadTimer: ReturnType<typeof setTimeout> | null = null
let lastSavedTimeout: ReturnType<typeof setTimeout> | null = null
let autoSaveTimer: ReturnType<typeof setTimeout> | null = null

const selectedSite = computed(() => {
  return sites.value.find(s => s.id === selectedId.value) || null
})

onMounted(async () => {
  try {
    const saved = localStorage.getItem(THEME_KEY)
    if (saved === 'dark' || saved === 'light') {
      darkMode.value = saved === 'dark'
    } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
      darkMode.value = true
    }
  } catch {}

  applyClass()

  try {
    const res = await api('/')
    if (res.ok) {
      sites.value = await res.json()
      return
    }
  } catch {}

  const stored = localStorage.getItem(STORAGE_KEY)
  if (stored) {
    sites.value = JSON.parse(stored)
  }
})

onMounted(() => {
  loadTimer = setInterval(async () => {
    try {
      const res = await api('/')
      if (res.ok) {
        const sitesFromServer = await res.json()
        sites.value = sitesFromServer.map((serverSite: any) => {
          if (dirtyEdits.has(serverSite.id)) {
            return { ...serverSite, ...dirtyEdits.get(serverSite.id) }
          }
          return serverSite
        })
      }
    } catch {}
  }, 5000)
})

async function refreshSites() {
  try {
    const res = await api('/')
    if (res.ok) {
      sites.value = await res.json()
    }
  } catch {
    // keep local state on network failure
  }
}

async function saveSite(site: any) {
  try {
    const res = await api(`/${encodeURIComponent(site.id)}`, {
      method: 'PUT',
      body: JSON.stringify({
        name: site.name,
        url: site.url,
        description: site.description,
        themeColor: site.themeColor,
        targetWindow: site.targetWindow,
        enabled: site.enabled,
        gitRepoUrl: site.gitRepoUrl || '',
        gitBranch: site.gitBranch || 'main',
      }),
    })
    if (res.ok) {
      const updated = await res.json()
      lastSaved.value = 'Updated at ' + new Date().toLocaleTimeString()
      return true
    }
    return false
  } catch {
    return false
  }
}

async function bulkSave() {
  if (autoSaveTimer) clearTimeout(autoSaveTimer)
  autoSaveTimer = null
  const allEntries = [...newSites.entries(), ...dirtyEdits.entries()]
  for (const [, site] of allEntries) {
    await saveSite(site)
  }
  newSites.clear()
  dirtyEdits.clear()
  try {
    await refreshSites()
  } catch {}
  lastSaved.value = 'Updated at ' + new Date().toLocaleTimeString()
  
  if (lastSavedTimeout) clearTimeout(lastSavedTimeout)
  lastSavedTimeout = setTimeout(() => {
    lastSaved.value = ''
  }, 3000)
}

async function handleEditChanged(site: { id: string; name: string; url: string; description: string; themeColor: string; targetWindow: string; enabled: boolean; gitRepoUrl?: string; gitBranch?: string }) {
  if (newSites.has(site.id)) {
    newSites.set(site.id, site)
  } else {
    dirtyEdits.set(site.id, site)
  }
  if (autoSaveTimer) clearTimeout(autoSaveTimer)
  autoSaveTimer = setTimeout(async () => {
    await bulkSave()
  }, 800)
}

function selectSite(site: { id: string; name: string; enabled: boolean }) {
  if (selectedId.value && dirtyEdits.has(selectedId.value)) {
    bulkSave()
  }
  selectedId.value = site.id
}

function startAdd() {
  pendingAdd.value = true
}

async function confirmAdd(name: string) {
  const trimmed = name.trim()
  if (!trimmed) return
  const site = defaultSite(trimmed)

  try {
    const res = await api('/', {
      method: 'POST',
      body: JSON.stringify(site),
    })
    if (res.ok) {
      const created = await res.json()
      sites.value.unshift(created)
      pendingAdd.value = false
      selectedId.value = created.id
      lastSaved.value = 'Updated at ' + new Date().toLocaleTimeString()
      
      if (lastSavedTimeout) clearTimeout(lastSavedTimeout)
      lastSavedTimeout = setTimeout(() => {
        lastSaved.value = ''
      }, 3000)
      return
    }
  } catch {}

  sites.value.unshift(site)
  pendingAdd.value = false
  selectedId.value = site.id
  try { saveSites() } catch {}
}

function cancelAdd() {
  pendingAdd.value = false
}

async function removeSite(index: number) {
   const site = sites.value[index]
   newSites.delete(site.id)
   dirtyEdits.delete(site.id)
   try {
     const res = await api(`/${encodeURIComponent(site.id)}`, { method: 'DELETE' })
     if (res.ok) await res.json()
   } catch {}
   if (site.id === selectedId.value) selectedId.value = null
   sites.value.splice(index, 1)
   try { localStorage.setItem(STORAGE_KEY, JSON.stringify(sites.value)) } catch {}
}

async function deleteSelected() {
  if (!selectedSite.value) return
  const idx = sites.value.findIndex(s => s.id === selectedSite.value!.id)
  if (idx !== -1) await removeSite(idx)
}

function openPreview() {
  const site = selectedSite.value
  if (!site?.url) return
  let url = site.url.trim()
  if (!/^https?:\/\//i.test(url)) url = 'https://' + url
  window.open(url, site.targetWindow || '_blank')
}
</script>

<style scoped>
.app {
  width: 100%;
  padding: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  height: 100vh;
  display: flex;
  flex-direction: column;
}

.top-bar {
  position: sticky;
  top: 0;
  z-index: 100;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px 24px;
  flex-shrink: 0;
  background: var(--bg-primary);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border-color);
}

.theme-toggle {
  padding: 8px 16px;
  border: 1px solid var(--border-color);
  border-radius: 6px;
  background: var(--bg-secondary);
  color: var(--text-secondary);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s, color 0.2s;
}

.theme-toggle:hover {
  background: var(--cancel-bg);
  border-color: var(--border-input);
}

h1 {
  text-align: left;
  margin: 0;
  font-size: 2rem;
  color: var(--text-primary);
}

.body-wrapper {
  flex: 1;
  overflow: hidden;
}

.layout {
  display: flex;
  gap: 0;
  height: 100%;
}

.sidebar-col {
  width: 280px;
  min-width: 280px;
  max-width: 280px;
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  border-right: 1px solid var(--border-color);
  padding: 16px;
}

.settings-col {
  flex: 1;
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  min-width: 0;
  border-right: 1px solid var(--border-color);
  padding: 24px;
}

.settings-col .settings-panel,
.settings-col .settings-empty {
  width: 100%;
}

.filetree-col {
  flex: 1;
  min-width: 280px;
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  background: var(--bg-secondary);
  padding: 16px;
}

</style>

<style>
html, body {
  margin: 0;
  padding: 0;
  height: 100vh;
  overflow: hidden;
  background: var(--bg-primary);
  color: var(--text-primary);
  transition: background 0.3s, color 0.3s;
}

* {
  box-sizing: border-box;
}

#__nuxt {
  height: 100vh;
  background: var(--bg-primary);
  transition: background 0.3s;
}
</style>
