<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { createAdminMapSource, deleteAdminMapSource, getAdminMapSources, setDefaultAdminMapSource, updateAdminMapSource } from '../api'
import type { MapTileSource, MapTileSourcePayload } from '../types'

const items = ref<MapTileSource[]>([])
const loading = ref(false)
const error = ref('')
const message = ref('')
const page = ref(1)
const pageSize = 25

const newSource = ref<MapTileSourcePayload>({
  name: '',
  url_template: 'https://tile.openstreetmap.jp/{z}/{x}/{y}.png',
  attribution: '&copy; OpenStreetMap contributors',
  max_zoom: 19,
  enabled: true,
  is_default: false,
  proxy_enabled: true,
})

const canPrev = () => page.value > 1
const canNext = () => items.value.length === pageSize
const enabledCount = computed(() => items.value.filter((item) => item.enabled).length)
const defaultSource = computed(() => items.value.find((item) => item.is_default) ?? null)

function editableCopy(item: MapTileSource): MapTileSourcePayload {
  return {
    name: item.name,
    url_template: item.url_template,
    attribution: item.attribution,
    max_zoom: item.max_zoom,
    enabled: item.enabled,
    is_default: item.is_default,
    proxy_enabled: item.proxy_enabled,
  }
}

const drafts = ref<Record<number, MapTileSourcePayload>>({})

function resetNewSource() {
  newSource.value = {
    name: '',
    url_template: '',
    attribution: '&copy; OpenStreetMap contributors',
    max_zoom: 19,
    enabled: true,
    is_default: false,
    proxy_enabled: true,
  }
}

function validatePayload(payload: MapTileSourcePayload): string {
  if (!payload.name.trim()) {
    return '请输入图源名称'
  }
  const url = payload.url_template.trim()
  if (!url) {
    return '请输入图源 URL 模板'
  }
  for (const placeholder of ['{z}', '{x}', '{y}']) {
    if (!url.includes(placeholder)) {
      return `URL 模板必须包含 ${placeholder}`
    }
  }
  if (!Number.isInteger(payload.max_zoom) || payload.max_zoom < 1 || payload.max_zoom > 30) {
    return '最大缩放级别必须是 1 到 30 之间的整数'
  }
  if (payload.is_default && !payload.enabled) {
    return '默认图源必须启用'
  }
  return ''
}

async function refreshItems() {
  loading.value = true
  error.value = ''
  try {
    const response = await getAdminMapSources(pageSize, (page.value - 1) * pageSize)
    items.value = response.items
    drafts.value = Object.fromEntries(response.items.map((item) => [item.id, editableCopy(item)]))
  } catch (err) {
    error.value = err instanceof Error ? err.message : String(err)
  } finally {
    loading.value = false
  }
}

function changePage(nextPage: number) {
  page.value = Math.max(1, nextPage)
  refreshItems()
}

async function createSource() {
  const validation = validatePayload(newSource.value)
  if (validation) {
    error.value = validation
    return
  }
  loading.value = true
  error.value = ''
  message.value = ''
  try {
    await createAdminMapSource({ ...newSource.value })
    message.value = '图源已添加'
    resetNewSource()
    page.value = 1
    await refreshItems()
  } catch (err) {
    error.value = err instanceof Error ? err.message : String(err)
  } finally {
    loading.value = false
  }
}

async function saveSource(item: MapTileSource) {
  const draft = drafts.value[item.id]
  if (!draft) {
    return
  }
  const validation = validatePayload(draft)
  if (validation) {
    error.value = validation
    return
  }
  loading.value = true
  error.value = ''
  message.value = ''
  try {
    await updateAdminMapSource(item.id, { ...draft })
    message.value = '图源已保存'
    await refreshItems()
  } catch (err) {
    error.value = err instanceof Error ? err.message : String(err)
  } finally {
    loading.value = false
  }
}

async function setDefaultSource(item: MapTileSource) {
  loading.value = true
  error.value = ''
  message.value = ''
  try {
    await setDefaultAdminMapSource(item.id)
    message.value = '默认图源已更新'
    await refreshItems()
  } catch (err) {
    error.value = err instanceof Error ? err.message : String(err)
  } finally {
    loading.value = false
  }
}

async function removeSource(item: MapTileSource) {
  if (!window.confirm(`确定要删除图源「${item.name}」吗？`)) {
    return
  }
  loading.value = true
  error.value = ''
  message.value = ''
  try {
    await deleteAdminMapSource(item.id)
    message.value = '图源已删除'
    if (items.value.length === 1 && page.value > 1) {
      page.value -= 1
    }
    await refreshItems()
  } catch (err) {
    error.value = err instanceof Error ? err.message : String(err)
  } finally {
    loading.value = false
  }
}

onMounted(refreshItems)
</script>

<template>
  <section class="map-source-page">
    <div class="map-source-hero panel">
      <div class="hero-copy">
        <p class="eyebrow">Map source</p>
        <h2>地图图源</h2>
        <p class="muted">集中维护 Leaflet 瓦片图源。URL 模板必须包含 <code>{z}</code>、<code>{x}</code>、<code>{y}</code>。</p>
      </div>
      <div class="hero-stats">
        <div>
          <strong>{{ items.length }}</strong>
          <span>当前图源</span>
        </div>
        <div>
          <strong>{{ enabledCount }}</strong>
          <span>已启用</span>
        </div>
        <div>
          <strong>{{ defaultSource?.name || '-' }}</strong>
          <span>默认图源</span>
        </div>
      </div>
    </div>

    <div class="panel map-source-create-panel">
      <div class="panel-heading compact">
        <div>
          <p class="eyebrow">Create</p>
          <h2>新增图源</h2>
        </div>
        <button class="admin-button ghost" type="button" @click="refreshItems" :disabled="loading">{{ loading ? '刷新中...' : '刷新数据' }}</button>
      </div>

      <form class="map-source-form" @submit.prevent="createSource">
        <label class="field">名称<input v-model="newSource.name" placeholder="OpenStreetMap Japan" /></label>
        <label class="field url-field">URL 模板<input v-model="newSource.url_template" placeholder="https://tile.example.com/{z}/{x}/{y}.png" /></label>
        <label class="field attribution-field">Attribution<input v-model="newSource.attribution" placeholder="&copy; OpenStreetMap contributors" /></label>
        <label class="field zoom-field">最大缩放<input v-model.number="newSource.max_zoom" type="number" min="1" max="30" /></label>
        <label class="switch-card"><input v-model="newSource.enabled" type="checkbox" /> <span>启用</span></label>
        <label class="switch-card"><input v-model="newSource.is_default" type="checkbox" /> <span>设为默认</span></label>
        <label class="switch-card"><input v-model="newSource.proxy_enabled" type="checkbox" /> <span>是否代理</span></label>
        <div class="form-actions">
          <button class="admin-button" type="submit" :disabled="loading">添加图源</button>
        </div>
      </form>
      <p class="template-tip">示例：<code>https://tile.openstreetmap.jp/{z}/{x}/{y}.png</code></p>
      <p v-if="error" class="error">{{ error }}</p>
      <p v-if="message" class="success">{{ message }}</p>
    </div>

    <div class="panel map-source-list-panel">
      <div class="panel-heading">
        <div>
          <p class="eyebrow">Sources</p>
          <h2>图源列表</h2>
        </div>
        <span class="badge">{{ items.length }} 条</span>
      </div>

      <div v-if="items.length === 0" class="empty-state">暂无地图图源，先在上方添加一个配置。</div>

      <article v-for="item in items" :key="item.id" class="map-source-card" :class="{ default: item.is_default, disabled: !item.enabled }">
        <header class="source-card-title">
          <div>
            <div class="source-title-row">
              <h3>{{ item.name }}</h3>
              <span v-if="item.is_default" class="status-pill ok">默认</span>
              <span v-else-if="item.enabled" class="status-pill">启用</span>
              <span v-else class="status-pill disabled">停用</span>
            </div>
            <p class="source-url">{{ item.url_template }}</p>
          </div>
          <button v-if="!item.is_default" class="admin-button ghost" :disabled="loading || !item.enabled" @click="setDefaultSource(item)">设为默认</button>
        </header>

        <div v-if="drafts[item.id]" class="source-edit-grid">
          <label class="field">名称<input v-model="drafts[item.id].name" /></label>
          <label class="field url-field">URL 模板<input v-model="drafts[item.id].url_template" /></label>
          <label class="field attribution-field">Attribution<input v-model="drafts[item.id].attribution" /></label>
          <label class="field zoom-field">最大缩放<input v-model.number="drafts[item.id].max_zoom" type="number" min="1" max="30" /></label>
          <label class="switch-card"><input v-model="drafts[item.id].enabled" type="checkbox" :disabled="item.is_default" /> <span>启用图源</span></label>
          <label class="switch-card"><input v-model="drafts[item.id].proxy_enabled" type="checkbox" /> <span>是否代理</span></label>
        </div>

        <div class="source-meta">
          <div><span>ID</span><strong>{{ item.id }}</strong></div>
          <div><span>最大缩放</span><strong>{{ item.max_zoom }}</strong></div>
          <div><span>Attribution</span><strong>{{ item.attribution || '-' }}</strong></div>
        </div>

        <div class="actions">
          <button class="admin-button" :disabled="loading" @click="saveSource(item)">保存</button>
          <button class="admin-button danger" :disabled="loading || item.is_default" @click="removeSource(item)">删除</button>
        </div>
      </article>

      <div class="pagination">
        <button :disabled="loading || !canPrev()" @click="changePage(page - 1)">上一页</button>
        <span>第 {{ page }} 页</span>
        <span>每页 {{ pageSize }} 条</span>
        <button :disabled="loading || !canNext()" @click="changePage(page + 1)">下一页</button>
      </div>
    </div>
  </section>
</template>

<style scoped>
.map-source-page {
  display: grid;
  gap: 12px;
}

.map-source-page :deep(input) {
  width: 100%;
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  padding: 9px 11px;
  color: var(--color-heading);
  font: inherit;
  background: var(--color-surface);
  outline: none;
  transition: border-color 0.16s ease, box-shadow 0.16s ease;
}

.map-source-page :deep(input:focus) {
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 20%, transparent);
}

.map-source-page :deep(input[type='checkbox']) {
  width: auto;
}

.map-source-hero,
.map-source-create-panel,
.map-source-list-panel {
  padding: 18px;
}

.map-source-hero {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  background: linear-gradient(135deg, var(--color-surface) 0%, var(--color-surface-soft) 100%);
}

.hero-copy {
  min-width: 260px;
}

.hero-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(120px, 1fr));
  gap: 0.75rem;
}

.hero-stats div {
  min-width: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 12px 16px;
  text-align: center;
  background: color-mix(in srgb, var(--color-surface) 84%, transparent);
}

.hero-stats strong {
  display: block;
  overflow: hidden;
  color: color-mix(in srgb, var(--color-primary) 72%, var(--color-heading));
  font-size: 22px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hero-stats span,
.source-meta span,
.template-tip,
.source-url {
  color: var(--color-muted);
  font-size: 13px;
}

.panel-heading,
.source-card-title,
.source-title-row,
.actions {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  flex-wrap: wrap;
}

.panel-heading,
.source-card-title {
  justify-content: space-between;
}

.panel-heading.compact {
  margin-bottom: 1rem;
}

.map-source-form,
.source-edit-grid {
  display: grid;
  grid-template-columns: minmax(180px, 1fr) minmax(320px, 2fr) minmax(220px, 1.4fr) minmax(100px, 0.5fr) auto auto;
  gap: 0.75rem;
  align-items: end;
}

.field {
  display: grid;
  gap: 6px;
  color: var(--color-text);
  font-size: 13px;
  font-weight: 700;
}

.url-field {
  min-width: 320px;
}

.zoom-field {
  min-width: 96px;
}

.form-actions {
  display: flex;
  justify-content: flex-end;
}

.switch-card {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 39px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 9px 11px;
  color: var(--color-text);
  font-size: 13px;
  font-weight: 700;
  background: var(--color-surface-soft);
}

.template-tip {
  margin: 12px 0 0;
}

.map-source-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: 1rem;
  margin-top: 1rem;
  background: var(--color-surface);
  box-shadow: inset 4px 0 0 var(--color-primary-soft);
}

.map-source-card.default {
  box-shadow: inset 4px 0 0 var(--color-success);
}

.map-source-card.disabled {
  background: var(--color-surface-soft);
  box-shadow: inset 4px 0 0 var(--color-border-strong);
}

.source-title-row h3 {
  margin: 0;
  color: var(--color-heading);
  font-size: 18px;
}

.source-url {
  max-width: 860px;
  margin: 6px 0 0;
  overflow-wrap: anywhere;
  font-family: var(--font-mono);
}

.status-pill {
  border-radius: 999px;
  padding: 7px 12px;
  color: color-mix(in srgb, var(--color-primary) 72%, var(--color-heading));
  font-size: 13px;
  font-weight: 800;
  background: var(--color-primary-soft);
}

.status-pill.ok {
  color: color-mix(in srgb, var(--color-success) 72%, var(--color-heading));
  background: var(--color-success-soft);
}

.status-pill.disabled {
  color: var(--color-muted);
  background: var(--color-surface-muted);
}

.source-edit-grid {
  grid-template-columns: minmax(180px, 1fr) minmax(320px, 2fr) minmax(220px, 1.4fr) minmax(100px, 0.5fr) auto;
  margin-top: 1rem;
}

.source-meta {
  display: grid;
  grid-template-columns: minmax(70px, 0.4fr) minmax(100px, 0.5fr) minmax(220px, 2fr);
  gap: 0.75rem;
  margin: 1rem 0;
}

.source-meta div {
  min-width: 0;
  border-radius: var(--radius-md);
  padding: 10px 12px;
  background: var(--color-surface-soft);
}

.source-meta strong {
  display: block;
  margin-top: 3px;
  overflow-wrap: anywhere;
  color: var(--color-heading);
}

.actions {
  justify-content: flex-end;
}

.empty-state {
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-md);
  padding: 24px;
  color: var(--color-muted);
  text-align: center;
  background: var(--color-surface-soft);
}

@media (max-width: 1100px) {
  .map-source-hero,
  .panel-heading,
  .source-card-title {
    align-items: stretch;
    flex-direction: column;
  }

  .hero-stats,
  .map-source-form,
  .source-edit-grid,
  .source-meta {
    grid-template-columns: 1fr 1fr;
  }

  .url-field,
  .attribution-field {
    grid-column: 1 / -1;
    min-width: 0;
  }
}

@media (max-width: 700px) {
  .hero-stats,
  .map-source-form,
  .source-edit-grid,
  .source-meta {
    grid-template-columns: 1fr;
  }
}
</style>
