<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useServerStore } from '@/stores/server'
import { useInstancesStore } from '@/stores/instances'
import { useToastStore } from '@/stores/toast'
import { useThemeGame } from '@/composables/useThemeGame'
import { useGameProfile } from '@/config/gameProfiles'
import type { DeploymentConfig, DeploymentStatus } from '@/types'
import { useWebSocket } from '@/composables/useWebSocket'
import { useApi } from '@/composables/useApi'
import Panel from '@/components/ds/data/Panel.vue'
import Button from '@/components/ds/core/Button.vue'
import Badge from '@/components/ds/core/Badge.vue'
import StatusDot from '@/components/ds/core/StatusDot.vue'
import Icon from '@/components/ds/core/Icon.vue'
import Alert from '@/components/ds/feedback/Alert.vue'
import EmptyState from '@/components/ds/feedback/EmptyState.vue'
import Input from '@/components/ds/forms/Input.vue'
import Switch from '@/components/ds/forms/Switch.vue'
import Tabs from '@/components/ds/navigation/Tabs.vue'

const server = useServerStore()
const instancesStore = useInstancesStore()
const toast = useToastStore()
const { activeGame } = useThemeGame()

// ---- Current game instance (the thing this page actually manages) ----
const currentInstance = computed(() => instancesStore.current)
const instanceState = computed(() => currentInstance.value?.state ?? null)
const instanceRunning = computed(() => instanceState.value === 'running')
const instanceManaged = computed(() =>
  !!instanceState.value && !['unmanaged', 'configured', 'missing_root'].includes(instanceState.value),
)
const instanceStateTone = computed<'online' | 'offline' | 'warn'>(() => {
  const s = instanceState.value
  if (s === 'running') return 'online'
  if (s === 'crashed') return 'warn'
  return 'offline'
})
const instanceStateLabel = computed(() => {
  const s = instanceState.value
  if (!s) return 'No instance'
  return s.charAt(0).toUpperCase() + s.slice(1).replace('_', ' ')
})
function fmtUptime(secs: number | undefined): string {
  if (!secs || secs <= 0) return '—'
  const d = Math.floor(secs / 86400)
  const h = Math.floor((secs % 86400) / 3600)
  const m = Math.floor((secs % 3600) / 60)
  if (d > 0) return `${d}d ${h}h`
  if (h > 0) return `${h}h ${m}m`
  return `${m}m`
}

// Profile follows the GameSwitcher. 'all' defaults to rust (neutral house skin).
const profile = computed(() => {
  const game = activeGame.value === 'all' ? 'rust' : activeGame.value
  return useGameProfile(game)
})

// Game-specific derived flags
const isRust = computed(() => profile.value.mods === 'umod')
const hasPluginSystem = computed(() => profile.value.mods === 'umod')
const isDockerManaged = computed(() => profile.value.managementModel === 'docker-compose')

// Management model human label for the identity badge
const managementModelLabel = computed(() => {
  const m = profile.value.managementModel
  const c = profile.value.console
  if (m === 'docker-compose') {
    return profile.value.clustering === 'battlegroup' ? 'Docker · BattleGroup' : 'Docker · Compose'
  }
  if (c === 'rcon+ingame') return 'Process · RCON + In-game'
  if (c === 'rcon+gm') return 'Process · RCON + GM'
  return 'Process · RCON'
})

// Clustering section label per game
const clusterLabel = computed(() => {
  const cl = profile.value.clustering
  if (cl === 'battlegroup') return 'BattleGroups & Sietches'
  if (cl === 'main-client') return 'Cluster'
  if (cl === 'character-transfer') return 'Clans & Character Transfer'
  return ''
})

const editMode = ref(false)
const saving = ref(false)
const actionLoading = ref<string | null>(null)
const copied = ref(false)
const setupTab = ref<'linux' | 'windows'>('linux')
const windowsCopied = ref(false)
const showDeployForm = ref(false)
const deployLoading = ref(false)
const oxideStatus = ref<{ stage: string; progress: number; message: string; error?: string } | null>(null)
const isInstallingOxide = ref(false)

// Agent credentials (fetched from /api/servers/agent-credentials on mount)
interface AgentCreds {
  license_id: string
  nats_user: string
  nats_password: string
  nats_url: string
}
const agentCreds = ref<AgentCreds | null>(null)
const showCreds = ref(false)
// Ref for the TOML block copy button
const tomlCopied = ref(false)

const deployForm = ref<DeploymentConfig>({
  server_name: '',
  max_players: 100,
  world_size: 4000,
  seed: Math.floor(Math.random() * 2147483647),
  server_port: 28015,
  rcon_port: 28016,
  rcon_password: '',
})

const isAgentConnected = computed(() =>
  server.connection?.connection_type === 'bare_metal' &&
  server.connection?.connection_status === 'connected'
)

const agentLastSeen = computed(() => {
  const ts = server.connection?.companion_last_seen
  if (!ts) return null
  return new Date(ts)
})

const agentLastSeenLabel = computed(() => {
  const d = agentLastSeen.value
  if (!d) return 'Never'
  const diff = Math.floor((Date.now() - d.getTime()) / 1000)
  if (diff < 60) return `${diff}s ago`
  if (diff < 3600) return `${Math.floor(diff / 60)}m ago`
  if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`
  return d.toLocaleDateString()
})

const linuxCommands = computed(() => `# Download the agent
curl -LO https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-linux-amd64
chmod +x corrosion-host-agent-linux-amd64

# Write /etc/corrosion/agent.toml (see config block below), then run:
sudo mkdir -p /etc/corrosion
sudo ./corrosion-host-agent-linux-amd64 --config /etc/corrosion/agent.toml`)

const windowsCommands = computed(() => `# Requires PowerShell (not Command Prompt)
# Download the agent
Invoke-WebRequest -Uri "https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-windows-amd64.exe" -OutFile "corrosion-host-agent-windows-amd64.exe"

# Write C:\\ProgramData\\Corrosion\\agent.toml (see config block below), then run:
New-Item -ItemType Directory -Force -Path "C:\\ProgramData\\Corrosion"
.\\corrosion-host-agent-windows-amd64.exe --config "C:\\ProgramData\\Corrosion\\agent.toml"`)

const agentTomlConfig = computed(() => {
  const c = agentCreds.value
  const licenseId = c?.license_id ?? 'YOUR-LICENSE-ID'
  const natsUrl = c?.nats_url ?? 'nats://nats.corrosionmgmt.com:4222'
  const natsUser = c?.nats_user ?? 'YOUR-LICENSE-ID'
  const natsPassword = c ? (showCreds.value ? c.nats_password : '••••••••') : 'YOUR-AGENT-TOKEN'
  return `[agent]
license_id = "${licenseId}"
nats_url = "${natsUrl}"
nats_user = "${natsUser}"
nats_password = "${natsPassword}"
heartbeat_seconds = 60

[[instance]]
id = "rust-main"
game = "rust"
root = "/opt/rustserver"
label = "My Server"`
})

// Returns the raw (unmasked) TOML for clipboard — always use actual password if available
const agentTomlConfigRaw = computed(() => {
  const c = agentCreds.value
  const licenseId = c?.license_id ?? 'YOUR-LICENSE-ID'
  const natsUrl = c?.nats_url ?? 'nats://nats.corrosionmgmt.com:4222'
  const natsUser = c?.nats_user ?? 'YOUR-LICENSE-ID'
  const natsPassword = c?.nats_password ?? 'YOUR-AGENT-TOKEN'
  return `[agent]
license_id = "${licenseId}"
nats_url = "${natsUrl}"
nats_user = "${natsUser}"
nats_password = "${natsPassword}"
heartbeat_seconds = 60

[[instance]]
id = "rust-main"
game = "rust"
root = "/opt/rustserver"
label = "My Server"`
})

async function copySetupCommands() {
  try {
    const text = setupTab.value === 'linux' ? linuxCommands.value : windowsCommands.value
    await navigator.clipboard.writeText(text)
    if (setupTab.value === 'linux') {
      copied.value = true
      setTimeout(() => { copied.value = false }, 2000)
    } else {
      windowsCopied.value = true
      setTimeout(() => { windowsCopied.value = false }, 2000)
    }
  } catch {
    // Clipboard API unavailable
  }
}

async function copyTomlConfig() {
  try {
    await navigator.clipboard.writeText(agentTomlConfigRaw.value)
    tomlCopied.value = true
    setTimeout(() => { tomlCopied.value = false }, 2000)
  } catch {
    // Clipboard API unavailable
  }
}

async function startDeploy() {
  if (!deployForm.value.rcon_password || deployForm.value.rcon_password.length < 6) return
  deployLoading.value = true
  try {
    await server.deployServer(deployForm.value)
    showDeployForm.value = false
  } catch {
    toast.error('Failed to start deployment')
  } finally {
    deployLoading.value = false
  }
}

const deployStages = [
  { key: 'downloading_steamcmd', label: 'Download SteamCMD' },
  { key: 'installing_steamcmd', label: 'Install SteamCMD' },
  { key: 'downloading_rust', label: 'Download Rust Server' },
  { key: 'configuring', label: 'Configure' },
  { key: 'starting', label: 'Start Server' },
  { key: 'online', label: 'Online' },
] as const

function getStageState(stageKey: string): 'pending' | 'active' | 'complete' | 'failed' {
  const status = server.deploymentStatus
  if (!status) return 'pending'
  if (status.stage === 'failed') {
    const idx = deployStages.findIndex(s => s.key === stageKey)
    const failIdx = deployStages.findIndex(s => s.key === status.stage)
    if (idx < failIdx) return 'complete'
    if (idx === failIdx) return 'failed'
    return 'pending'
  }
  const currentIdx = deployStages.findIndex(s => s.key === status.stage)
  const thisIdx = deployStages.findIndex(s => s.key === stageKey)
  if (thisIdx < currentIdx) return 'complete'
  if (thisIdx === currentIdx) return status.stage === 'online' ? 'complete' : 'active'
  return 'pending'
}

const oxideStages = [
  { key: 'fetching_release', label: 'Check latest release' },
  { key: 'downloading', label: 'Download Oxide' },
  { key: 'installing', label: 'Extract files' },
  { key: 'restarting', label: 'Restart server' },
  { key: 'complete', label: 'Complete' },
] as const

function getOxideStageState(stageKey: string): 'pending' | 'active' | 'complete' | 'failed' {
  if (!oxideStatus.value) return 'pending'
  const status = oxideStatus.value
  if (status.stage === 'failed') {
    const currentStages = oxideStages
    const idx = currentStages.findIndex(s => s.key === stageKey)
    return idx === 0 ? 'failed' : 'pending'
  }
  const currentIdx = oxideStages.findIndex(s => s.key === status.stage)
  const thisIdx = oxideStages.findIndex(s => s.key === stageKey)
  if (thisIdx < currentIdx) return 'complete'
  if (thisIdx === currentIdx) return status.stage === 'complete' ? 'complete' : 'active'
  return 'pending'
}

async function installOxide() {
  isInstallingOxide.value = true
  oxideStatus.value = null
  try {
    await server.installOxide()
  } catch {
    toast.error('Failed to start Oxide installation')
    isInstallingOxide.value = false
  }
}

const form = ref({
  server_name: '',
  max_players: 0,
  world_size: 0,
  current_seed: 0,
})

function loadFormFromConfig() {
  if (server.config) {
    form.value = {
      server_name: server.config.server_name || '',
      max_players: server.config.max_players ?? 100,
      world_size: server.config.world_size ?? 4000,
      current_seed: server.config.current_seed ?? 0,
    }
  }
}

async function saveConfig() {
  saving.value = true
  try {
    await server.updateConfig(form.value)
    editMode.value = false
    toast.success('Server configuration saved')
  } catch {
    toast.error('Failed to save server configuration')
  } finally {
    saving.value = false
  }
}

async function serverAction(action: 'start' | 'stop' | 'restart') {
  if (!currentInstance.value) {
    toast.error('No game instance to control — connect re-Agent first')
    return
  }
  actionLoading.value = action
  try {
    const res = await instancesStore.lifecycle(action)
    if ((res as { status?: string }).status === 'error') {
      toast.error(String((res as { message?: string }).message ?? `Failed to ${action}`))
    } else {
      toast.success(`${currentInstance.value?.agent_instance_id ?? 'Instance'}: ${action} ok`)
    }
  } catch (e) {
    toast.error(e instanceof Error ? e.message : `Failed to ${action} server`)
  } finally {
    actionLoading.value = null
  }
}

async function refreshInstanceStatus() {
  if (!currentInstance.value) return
  actionLoading.value = 'status'
  try {
    await instancesStore.lifecycle('status')
  } catch {
    /* status best-effort */
  } finally {
    actionLoading.value = null
  }
}

// ---- Config file editor (reads/writes via the jailed agent file manager) ----
const cfgPath = ref('')
const cfgContent = ref('')
const cfgLoaded = ref(false)
const cfgLoading = ref(false)
const cfgSaving = ref(false)
const cfgError = ref<string | null>(null)

// A reasonable default config-file hint per game (operator can change it).
const cfgHint = computed(() => profile.value.primaryConfigFile ?? '')

async function loadConfigFile() {
  const path = (cfgPath.value || cfgHint.value).trim()
  if (!path || !currentInstance.value) return
  cfgPath.value = path
  cfgLoading.value = true
  cfgError.value = null
  try {
    cfgContent.value = await instancesStore.readFile(path)
    cfgLoaded.value = true
  } catch (e) {
    // Not-found is fine — present an empty editor to create it.
    cfgContent.value = ''
    cfgLoaded.value = true
    cfgError.value = e instanceof Error ? e.message : 'File not found — saving will create it'
  } finally {
    cfgLoading.value = false
  }
}

async function saveConfigFile() {
  const path = cfgPath.value.trim()
  if (!path || !currentInstance.value) return
  cfgSaving.value = true
  try {
    await instancesStore.writeFile(path, cfgContent.value)
    cfgError.value = null
    toast.success(`Saved ${path}`)
  } catch (e) {
    toast.error(e instanceof Error ? e.message : 'Failed to save file')
  } finally {
    cfgSaving.value = false
  }
}

async function toggleAutomation(field: 'crash_recovery_enabled' | 'auto_update_on_force_wipe' | 'force_wipe_eligible') {
  if (!server.config) return
  const newValue = !server.config[field]
  try {
    await server.updateConfig({ [field]: newValue })
    toast.success('Automation setting saved')
  } catch {
    toast.error('Failed to save automation setting')
  }
}

// Deploy form string bindings (DS Input uses string v-model)
const deployServerName = computed({
  get: () => deployForm.value.server_name,
  set: (v: string) => { deployForm.value.server_name = v },
})
const deployRconPassword = computed({
  get: () => deployForm.value.rcon_password,
  set: (v: string) => { deployForm.value.rcon_password = v },
})

// Config form string bindings
const formServerName = computed({
  get: () => form.value.server_name,
  set: (v: string) => { form.value.server_name = v },
})

// Setup tab items
const setupTabItems = [
  { value: 'linux', label: 'Linux' },
  { value: 'windows', label: 'Windows' },
]

// Connection status derived values
const connStatusTone = computed<'online' | 'offline' | 'warn'>(() => {
  const cs = server.connection?.connection_status
  if (cs === 'connected') return 'online'
  if (cs === 'degraded') return 'warn'
  return 'offline'
})

onMounted(async () => {
  await server.fetchServer()
  loadFormFromConfig()
  // Load the fleet's instances; prefer one matching the active game.
  const game = activeGame.value === 'all' ? undefined : activeGame.value
  await instancesStore.fetchInstances(game)

  // Fetch agent credentials for the TOML config block (leave null on error — honest fallback)
  try {
    const creds = await useApi().get<AgentCreds | null>('/servers/agent-credentials')
    agentCreds.value = creds
  } catch {
    agentCreds.value = null
  }

  const ws = useWebSocket()
  ws.subscribe((msg) => {
    if (msg.type === 'event' && msg.event === 'deploy_status') {
      server.updateDeploymentStatus(msg.data as DeploymentStatus)
    }
    if (msg.type === 'event' && msg.event === 'oxide_status') {
      oxideStatus.value = msg.data as { stage: string; progress: number; message: string; error?: string }
      if (msg.data && ((msg.data as any).stage === 'complete' || (msg.data as any).stage === 'failed')) {
        isInstallingOxide.value = false
      }
    }
  })
})
</script>

<template>
  <div class="sv">
    <!-- Page head — game-aware identity -->
    <div class="sv__head">
      <div class="sv__head-id">
        <div class="sv__head-chip">
          <Icon name="server" :size="20" :stroke-width="2" />
        </div>
        <div>
          <div class="t-eyebrow">{{ profile.label }} · Server management</div>
          <h1 class="sv__title">Server</h1>
        </div>
      </div>
      <Badge tone="neutral" :mono="true" class="sv__model-badge">{{ managementModelLabel }}</Badge>
    </div>

    <!-- Connection -->
    <Panel title="Connection">
      <template #actions>
        <Badge :tone="connStatusTone" :dot="true" :pulse="connStatusTone === 'online'">
          {{ server.connection?.connection_status ?? 'Unknown' }}
        </Badge>
      </template>
      <div class="sv__grid4">
        <div class="sv__field">
          <div class="sv__field-label">Status</div>
          <div class="sv__field-val sv__field-val--inline">
            <StatusDot :tone="connStatusTone" :pulse="connStatusTone === 'online'" />
            <span>{{ server.connection?.connection_status ?? 'Unknown' }}</span>
          </div>
        </div>
        <div class="sv__field">
          <div class="sv__field-label">Connection type</div>
          <div class="sv__field-val sv__field-val--mono">{{ server.connection?.connection_type ?? '—' }}</div>
        </div>
        <div class="sv__field">
          <div class="sv__field-label">Server IP</div>
          <div class="sv__field-val sv__field-val--mono">
            {{ server.connection?.server_ip ?? '—' }}{{ server.connection?.server_port ? `:${server.connection.server_port}` : '' }}
          </div>
        </div>
        <div class="sv__field">
          <div class="sv__field-label">Game port</div>
          <div class="sv__field-val sv__field-val--mono">{{ server.connection?.game_port ?? '—' }}</div>
        </div>
      </div>
    </Panel>

    <!-- Game instance — real per-instance state + lifecycle -->
    <Panel title="Game instance">
      <template #actions>
        <Badge :tone="instanceStateTone" :dot="true" :pulse="instanceRunning">{{ instanceStateLabel }}</Badge>
      </template>

      <!-- No instance yet -->
      <EmptyState
        v-if="!currentInstance"
        icon="server"
        title="No game instance connected"
        :description="'Install re-Agent and add a ' + profile.label + ' instance to its config to manage it here.'"
      />

      <template v-else>
        <!-- Instance selector when more than one -->
        <div v-if="instancesStore.instances.length > 1" class="sv__instance-pick sv__mb">
          <span class="sv__field-label">Instance</span>
          <select
            class="sv__select"
            :value="instancesStore.currentId ?? ''"
            @change="instancesStore.select(($event.target as HTMLSelectElement).value)"
          >
            <option v-for="i in instancesStore.instances" :key="i.id" :value="i.id">
              {{ i.label || i.agent_instance_id }} ({{ i.game }}) · {{ i.host_hostname }}
            </option>
          </select>
        </div>

        <!-- Instance facts -->
        <div class="sv__grid4 sv__mb">
          <div class="sv__field">
            <div class="sv__field-label">Instance</div>
            <div class="sv__field-val sv__field-val--mono">{{ currentInstance.agent_instance_id }}</div>
          </div>
          <div class="sv__field">
            <div class="sv__field-label">State</div>
            <div class="sv__field-val sv__field-val--inline">
              <StatusDot :tone="instanceStateTone" :pulse="instanceRunning" />
              <span>{{ instanceStateLabel }}</span>
            </div>
          </div>
          <div class="sv__field">
            <div class="sv__field-label">Uptime</div>
            <div class="sv__field-val sv__field-val--mono">{{ fmtUptime(currentInstance.uptime_seconds) }}</div>
          </div>
          <div class="sv__field">
            <div class="sv__field-label">Host</div>
            <div class="sv__field-val sv__field-val--mono">{{ currentInstance.host_hostname }}</div>
          </div>
        </div>

        <!-- Lifecycle controls — gated on real instance state -->
        <div class="sv__controls">
          <Button
            variant="outline"
            icon="play"
            :loading="actionLoading === 'start'"
            :disabled="instanceRunning || !instanceManaged || actionLoading !== null"
            @click="serverAction('start')"
          >Start</Button>
          <Button
            variant="danger-soft"
            icon="power"
            :loading="actionLoading === 'stop'"
            :disabled="!instanceRunning || actionLoading !== null"
            @click="serverAction('stop')"
          >Stop</Button>
          <Button
            variant="secondary"
            icon="refresh-cw"
            :loading="actionLoading === 'restart'"
            :disabled="!instanceManaged || actionLoading !== null"
            @click="serverAction('restart')"
          >Restart</Button>
          <Button
            variant="ghost"
            icon="refresh-cw"
            :loading="actionLoading === 'status'"
            :disabled="actionLoading !== null"
            @click="refreshInstanceStatus"
          >Refresh</Button>
        </div>
        <Alert v-if="!instanceManaged" tone="info" class="sv__mt-sm">
          This instance is telemetry-only — add an <code>executable</code> to its agent config to enable start/stop.
        </Alert>
      </template>
    </Panel>

    <!-- Host agent -->
    <Panel title="re-Agent" subtitle="Bare-metal server management binary">
      <template #actions>
        <Badge :tone="isAgentConnected ? 'online' : 'offline'" :dot="true" :pulse="isAgentConnected">
          {{ isAgentConnected ? 'Active' : 'Inactive' }}
        </Badge>
      </template>

      <!-- Agent status row -->
      <div class="sv__grid3 sv__mb">
        <div class="sv__field">
          <div class="sv__field-label">Agent status</div>
          <div class="sv__field-val sv__field-val--inline">
            <StatusDot :tone="isAgentConnected ? 'online' : 'offline'" :pulse="isAgentConnected" />
            <span>{{ isAgentConnected ? 'Active' : 'Inactive' }}</span>
          </div>
        </div>
        <div class="sv__field">
          <div class="sv__field-label">Connection type</div>
          <div class="sv__field-val sv__field-val--mono">{{ server.connection?.connection_type ?? '—' }}</div>
        </div>
        <div class="sv__field">
          <div class="sv__field-label">Last heartbeat</div>
          <div class="sv__field-val">{{ agentLastSeenLabel }}</div>
        </div>
      </div>

      <!-- Download -->
      <div class="sv__section-head">
        <Icon name="download" :size="14" />
        <span>Download re-Agent</span>
      </div>
      <div class="sv__downloads sv__mb">
        <a
          href="https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-linux-amd64"
          download="corrosion-host-agent-linux-amd64"
          class="sv__dl-link"
        >
          <Icon name="download" :size="15" />
          Linux (amd64)
        </a>
        <a
          href="https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-windows-amd64.exe"
          download="corrosion-host-agent-windows-amd64.exe"
          class="sv__dl-link"
        >
          <Icon name="download" :size="15" />
          Windows (amd64)
        </a>
      </div>

      <!-- Quick setup -->
      <div class="sv__setup-head">
        <div class="sv__section-head">
          <Icon name="terminal" :size="14" />
          <span>Quick setup</span>
        </div>
        <Button
          variant="secondary"
          size="sm"
          :icon="(setupTab === 'linux' ? copied : windowsCopied) ? 'check' : 'copy'"
          @click="copySetupCommands"
        >{{ (setupTab === 'linux' ? copied : windowsCopied) ? 'Copied' : 'Copy' }}</Button>
      </div>

      <Tabs v-model="setupTab" :items="setupTabItems" class="sv__mb-sm" />

      <Alert v-if="setupTab === 'windows'" tone="warn" class="sv__mb-sm">
        PowerShell required — Command Prompt is not supported.
      </Alert>

      <!-- Linux commands -->
      <div v-if="setupTab === 'linux'" class="sv__codeblock">
        <p class="sv__cmt"># Download re-Agent</p>
        <p>curl -LO https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-linux-amd64</p>
        <p>chmod +x corrosion-host-agent-linux-amd64</p>
        <p class="sv__cmt sv__mt">&#x23; Write /etc/corrosion/agent.toml (see config block below), then run:</p>
        <p>sudo mkdir -p /etc/corrosion</p>
        <p>sudo ./corrosion-host-agent-linux-amd64 --config <span class="sv__accent">/etc/corrosion/agent.toml</span></p>
      </div>

      <!-- Windows commands -->
      <div v-if="setupTab === 'windows'" class="sv__codeblock">
        <p class="sv__cmt"># Requires PowerShell (not Command Prompt)</p>
        <p class="sv__cmt"># Download re-Agent</p>
        <p>Invoke-WebRequest -Uri <span class="sv__accent">"https://cdn.corrosionmgmt.com/host-agent/latest/corrosion-host-agent-windows-amd64.exe"</span> -OutFile <span class="sv__accent">"corrosion-host-agent-windows-amd64.exe"</span></p>
        <p class="sv__cmt sv__mt">&#x23; Write C:\ProgramData\Corrosion\agent.toml (see config block below), then run:</p>
        <p>New-Item -ItemType Directory -Force -Path <span class="sv__accent">"C:\ProgramData\Corrosion"</span></p>
        <p>.\corrosion-host-agent-windows-amd64.exe --config <span class="sv__accent">"C:\ProgramData\Corrosion\agent.toml"</span></p>
      </div>

      <!-- Agent configuration (agent.toml) -->
      <div class="sv__section-head sv__mt">
        <Icon name="file-text" :size="14" />
        <span>Agent configuration (agent.toml)</span>
      </div>
      <div class="sv__setup-head">
        <div class="sv__toml-reveal">
          <Button
            variant="ghost"
            size="sm"
            :icon="showCreds ? 'eye-off' : 'eye'"
            @click="showCreds = !showCreds"
          >{{ showCreds ? 'Hide credentials' : 'Reveal credentials' }}</Button>
        </div>
        <Button
          variant="secondary"
          size="sm"
          :icon="tomlCopied ? 'check' : 'copy'"
          @click="copyTomlConfig"
        >{{ tomlCopied ? 'Copied' : 'Copy' }}</Button>
      </div>
      <div class="sv__codeblock">
        <pre class="sv__pre">{{ agentTomlConfig }}</pre>
      </div>
      <Alert v-if="!agentCreds" tone="warn" class="sv__mt">
        Could not load credentials from server. Copy this config and replace the placeholders with values from your Catalyst dashboard settings.
      </Alert>
    </Panel>

    <!-- Deploy Server — Rust only (SteamCMD path). Other games use docker-compose or external tooling. -->
    <Panel v-if="isRust" title="Deploy Rust server" subtitle="One-click: SteamCMD, download, configure, start">
      <template #title-append>
        <Icon name="rocket" :size="15" />
      </template>

      <!-- Deployment progress -->
      <div v-if="server.deploymentStatus || server.isDeploying" class="sv__stages">
        <div
          v-for="stage in deployStages"
          :key="stage.key"
          class="sv__stage"
        >
          <div
            class="sv__stage-dot"
            :data-state="getStageState(stage.key)"
          >
            <Icon v-if="getStageState(stage.key) === 'active'" name="loader" :size="13" class="sv__spin" />
            <Icon v-else-if="getStageState(stage.key) === 'complete'" name="check" :size="13" />
            <Icon v-else-if="getStageState(stage.key) === 'failed'" name="triangle-alert" :size="13" />
            <span v-else class="sv__stage-nub" />
          </div>
          <span
            class="sv__stage-label"
            :data-state="getStageState(stage.key)"
          >{{ stage.label }}</span>
        </div>

        <Alert v-if="server.deploymentStatus?.message" tone="neutral" class="sv__mt">
          {{ server.deploymentStatus.message }}
        </Alert>
        <Alert v-if="server.deploymentStatus?.error" tone="danger" class="sv__mt">
          {{ server.deploymentStatus.error }}
        </Alert>

        <div v-if="server.deploymentStatus?.stage === 'failed'" class="sv__mt">
          <Button
            variant="secondary"
            icon="refresh-cw"
            @click="server.clearDeploymentStatus(); showDeployForm = true"
          >Retry deployment</Button>
        </div>
      </div>

      <!-- Deploy form / prompt -->
      <div v-else>
        <div v-if="!showDeployForm" class="sv__deploy-prompt">
          <p class="sv__deploy-desc">Automatically install SteamCMD, download Rust Dedicated Server, configure, and start — all with one click.</p>
          <Button icon="rocket" @click="showDeployForm = true">Deploy server</Button>
        </div>

        <form v-else @submit.prevent="startDeploy" class="sv__form">
          <div class="sv__form-grid">
            <Input
              v-model="deployServerName"
              label="Server name"
              :required="true"
              class="sv__col-span2"
            />
            <Input
              :model-value="String(deployForm.max_players)"
              @update:model-value="v => { deployForm.max_players = Number(v) }"
              label="Max players"
              type="number"
              :mono="true"
            />
            <Input
              :model-value="String(deployForm.world_size)"
              @update:model-value="v => { deployForm.world_size = Number(v) }"
              label="World size"
              type="number"
              :mono="true"
            />
            <Input
              :model-value="String(deployForm.seed)"
              @update:model-value="v => { deployForm.seed = Number(v) }"
              label="Map seed"
              type="number"
              :mono="true"
            />
            <Input
              :model-value="String(deployForm.server_port)"
              @update:model-value="v => { deployForm.server_port = Number(v) }"
              label="Server port"
              type="number"
              :mono="true"
            />
            <Input
              :model-value="String(deployForm.rcon_port)"
              @update:model-value="v => { deployForm.rcon_port = Number(v) }"
              label="RCON port"
              type="number"
              :mono="true"
            />
            <Input
              v-model="deployRconPassword"
              label="RCON password"
              type="password"
              placeholder="Minimum 6 characters"
              :required="true"
              class="sv__col-span2"
            />
          </div>
          <div class="sv__form-actions">
            <Button
              type="submit"
              icon="rocket"
              :loading="deployLoading"
              :disabled="deployLoading || !deployForm.rcon_password || deployForm.rcon_password.length < 6"
            >{{ deployLoading ? 'Deploying…' : 'Deploy server' }}</Button>
            <Button variant="ghost" type="button" @click="showDeployForm = false">Cancel</Button>
          </div>
        </form>
      </div>
    </Panel>

    <!-- Non-Rust: Docker-managed server note -->
    <Panel
      v-if="isDockerManaged"
      :title="profile.label + ' server deployment'"
      subtitle="Managed via Docker Compose"
    >
      <template #title-append>
        <Icon name="box" :size="15" />
      </template>
      <EmptyState
        icon="box"
        title="Docker-managed deployment"
        :description="profile.label + ' servers are managed via Docker Compose. Connect re-Agent on your Docker host to enable lifecycle management.'"
      >
        <template #action>
          <Badge tone="info">Docker · Compose</Badge>
        </template>
      </EmptyState>
    </Panel>

    <!-- Install Oxide / uMod — Rust only -->
    <Panel v-if="hasPluginSystem" title="Install Oxide / uMod" subtitle="Required for all plugins including CorrosionCompanion">
      <template #title-append>
        <Icon name="puzzle" :size="15" />
      </template>

      <!-- Installation progress -->
      <div v-if="oxideStatus || isInstallingOxide" class="sv__stages">
        <div
          v-for="stage in oxideStages"
          :key="stage.key"
          class="sv__stage"
        >
          <div
            class="sv__stage-dot"
            :data-state="getOxideStageState(stage.key)"
          >
            <Icon v-if="getOxideStageState(stage.key) === 'active'" name="loader" :size="13" class="sv__spin" />
            <Icon v-else-if="getOxideStageState(stage.key) === 'complete'" name="check" :size="13" />
            <Icon v-else-if="getOxideStageState(stage.key) === 'failed'" name="triangle-alert" :size="13" />
            <span v-else class="sv__stage-nub" />
          </div>
          <span
            class="sv__stage-label"
            :data-state="getOxideStageState(stage.key)"
          >{{ stage.label }}</span>
        </div>

        <Alert v-if="oxideStatus?.message" tone="neutral" class="sv__mt">
          {{ oxideStatus.message }}
        </Alert>
        <Alert v-if="oxideStatus?.error" tone="danger" class="sv__mt">
          {{ oxideStatus.error }}
        </Alert>

        <div v-if="oxideStatus?.stage === 'failed'" class="sv__mt">
          <Button
            variant="secondary"
            icon="refresh-cw"
            @click="oxideStatus = null; installOxide()"
          >Retry installation</Button>
        </div>
      </div>

      <!-- Install prompt -->
      <div v-else class="sv__deploy-prompt">
        <p class="sv__deploy-desc">Install or update Oxide / uMod. Required for all plugins including CorrosionCompanion.</p>
        <Button icon="puzzle" @click="installOxide()">Install Oxide</Button>
      </div>
    </Panel>

    <!-- Workshop Mods info — Conan / Soulmask (Steam Workshop, no install step needed) -->
    <Panel
      v-else-if="profile.mods === 'workshop'"
      :title="(profile.terminology.mods ?? 'Workshop Mods')"
      :subtitle="profile.label + ' uses Steam Workshop — no manual install step required'"
    >
      <template #title-append>
        <Icon name="layers" :size="15" />
      </template>
      <EmptyState
        icon="layers"
        :title="profile.label + ' mod management'"
        :description="profile.label + ' loads mods directly from Steam Workshop. Manage your mod list in server config — no Catalyst install step needed.'"
      />
    </Panel>

    <!-- Conan Exiles special concepts (Clans / Thralls / Purge) -->
    <Panel
      v-if="activeGame === 'conan'"
      title="Conan Exiles concepts"
      subtitle="Key admin mechanics for Conan Exiles servers"
    >
      <div class="sv__concept-grid">
        <div class="sv__concept">
          <Icon name="users" :size="16" />
          <div>
            <div class="sv__concept-label">Clans</div>
            <div class="sv__concept-desc">Player factions. Clan management via in-game admin panel or RCON.</div>
          </div>
        </div>
        <div class="sv__concept">
          <Icon name="zap" :size="16" />
          <div>
            <div class="sv__concept-label">Thralls &amp; Avatars</div>
            <div class="sv__concept-desc">Server-controlled NPCs and deity summons. Purge cycle managed via server settings.</div>
          </div>
        </div>
        <div class="sv__concept">
          <Icon name="shield" :size="16" />
          <div>
            <div class="sv__concept-label">Purge</div>
            <div class="sv__concept-desc">NPC raid events targeting player bases. Enable / tune via server config.</div>
          </div>
        </div>
      </div>
    </Panel>

    <!-- Soulmask clustering section -->
    <Panel
      v-if="profile.clustering === 'main-client'"
      :title="clusterLabel"
      subtitle="Main-client cluster topology for Soulmask"
    >
      <EmptyState
        icon="network"
        title="Cluster management coming soon"
        :description="'Connect a ' + profile.label + ' host to manage the main-client cluster from this panel. Cluster configuration requires re-Agent.'"
      />
    </Panel>

    <!-- Dune BattleGroup / Sietches section -->
    <Panel
      v-if="profile.clustering === 'battlegroup'"
      title="BattleGroups &amp; Sietches"
      subtitle="Dune: Awakening server cluster topology"
    >
      <EmptyState
        icon="map"
        title="Sietch management requires a connected Dune host"
        description="Connect re-Agent on your Dune: Awakening Docker host to manage BattleGroups and Sietches from this panel."
      />
    </Panel>

    <!-- Configuration -->
    <Panel title="Configuration">
      <template #actions>
        <Button
          v-if="!editMode"
          variant="ghost"
          size="sm"
          icon="settings"
          @click="editMode = true; loadFormFromConfig()"
        >Edit</Button>
        <template v-else>
          <Button
            variant="secondary"
            size="sm"
            type="button"
            @click="editMode = false"
          >Cancel</Button>
          <Button
            size="sm"
            icon="check"
            :loading="saving"
            :disabled="saving"
            @click="saveConfig"
          >Save changes</Button>
        </template>
      </template>

      <!-- Read mode -->
      <div v-if="!editMode" class="sv__grid4">
        <div class="sv__field">
          <div class="sv__field-label">Server name</div>
          <div class="sv__field-val">{{ server.config?.server_name ?? 'Not configured' }}</div>
        </div>
        <div class="sv__field">
          <div class="sv__field-label">Max players</div>
          <div class="sv__field-val sv__field-val--mono">{{ server.config?.max_players ?? '—' }}</div>
        </div>
        <!-- Rust-only: world size and seed are Facepunch/procgen concepts -->
        <div v-if="isRust" class="sv__field">
          <div class="sv__field-label">World size</div>
          <div class="sv__field-val sv__field-val--mono">{{ server.config?.world_size ?? '—' }}</div>
        </div>
        <div v-if="isRust" class="sv__field">
          <div class="sv__field-label">Current seed</div>
          <div class="sv__field-val sv__field-val--mono">{{ server.config?.current_seed ?? '—' }}</div>
        </div>
      </div>
      <!-- Non-Rust: game-specific settings live in config files on the host -->
      <Alert v-if="!editMode && !isRust" tone="neutral" class="sv__mt">
        Game-specific settings for {{ profile.label }} live in config files on the host — manage them in the
        <Button variant="ghost" size="sm" icon="folder" @click="$router.push('/files')">File Manager</Button>
      </Alert>

      <!-- Edit mode -->
      <form v-else @submit.prevent="saveConfig" class="sv__form">
        <div class="sv__form-grid">
          <Input
            v-model="formServerName"
            label="Server name"
            class="sv__col-span2"
          />
          <Input
            :model-value="String(form.max_players)"
            @update:model-value="v => { form.max_players = Number(v) }"
            label="Max players"
            type="number"
            :mono="true"
          />
          <!-- Rust-only: world size and seed are Facepunch/procgen concepts -->
          <Input
            v-if="isRust"
            :model-value="String(form.world_size)"
            @update:model-value="v => { form.world_size = Number(v) }"
            label="World size"
            type="number"
            :mono="true"
          />
          <Input
            v-if="isRust"
            :model-value="String(form.current_seed)"
            @update:model-value="v => { form.current_seed = Number(v) }"
            label="Current seed"
            type="number"
            :mono="true"
            class="sv__col-span2"
          />
          <!-- Non-Rust: redirect to file manager for game-specific config -->
          <Alert v-if="!isRust" tone="neutral" class="sv__col-span2">
            Game-specific settings for {{ profile.label }} live in config files on the host — manage them in the
            <Button variant="ghost" size="sm" icon="folder" @click="$router.push('/files')">File Manager</Button>
          </Alert>
        </div>
      </form>
    </Panel>

    <!-- Config file editor — reads/writes via the jailed agent file manager -->
    <Panel v-if="currentInstance" title="Configuration file" subtitle="Edit a config file directly on the host (jailed to the instance)">
      <div class="sv__cfg-row sv__mb-sm">
        <Input
          v-model="cfgPath"
          :placeholder="cfgHint || 'path/relative/to/instance/root'"
          class="sv__cfg-path"
          :mono="true"
        />
        <Button
          variant="secondary"
          icon="folder-open"
          :loading="cfgLoading"
          :disabled="(!cfgPath && !cfgHint) || cfgLoading"
          @click="loadConfigFile"
        >Load</Button>
        <Button
          v-if="cfgLoaded"
          icon="check"
          :loading="cfgSaving"
          :disabled="!cfgPath || cfgSaving"
          @click="saveConfigFile"
        >Save</Button>
      </div>
      <Alert v-if="cfgError" tone="info" class="sv__mb-sm">{{ cfgError }}</Alert>
      <textarea
        v-if="cfgLoaded"
        v-model="cfgContent"
        class="sv__cfg-editor"
        spellcheck="false"
        rows="16"
      ></textarea>
      <p v-else class="sv__cfg-hint">
        Load <code>{{ cfgHint || 'a config file' }}</code> to view and edit it. Changes are written
        straight to the host through the agent — jailed to this instance's directory.
      </p>
    </Panel>

    <!-- Automation -->
    <Panel title="Automation">
      <div class="sv__toggles">
        <div class="sv__toggle-row">
          <div class="sv__toggle-body">
            <div class="sv__toggle-label">Auto-restart</div>
            <div class="sv__toggle-sub">Restart on crash detection</div>
          </div>
          <Switch
            :model-value="server.config?.crash_recovery_enabled ?? false"
            :disabled="!server.config"
            @update:model-value="toggleAutomation('crash_recovery_enabled')"
          />
        </div>
        <div class="sv__toggle-row">
          <div class="sv__toggle-body">
            <div class="sv__toggle-label">
              <!-- Rust: "force wipe" is a Facepunch concept. Others: plain "auto-update" -->
              {{ isRust ? 'Auto-update on force wipe' : 'Auto-update on patch' }}
            </div>
            <div class="sv__toggle-sub">
              {{ isRust ? 'Update when Facepunch pushes' : 'Update when the developer pushes a patch' }}
            </div>
          </div>
          <Switch
            :model-value="server.config?.auto_update_on_force_wipe ?? false"
            :disabled="!server.config"
            @update:model-value="toggleAutomation('auto_update_on_force_wipe')"
          />
        </div>
        <!-- Rust-only: force wipe eligibility is a Facepunch concept -->
        <div v-if="isRust" class="sv__toggle-row">
          <div class="sv__toggle-body">
            <div class="sv__toggle-label">Force wipe eligible</div>
            <div class="sv__toggle-sub">Server participates in force wipes</div>
          </div>
          <Switch
            :model-value="server.config?.force_wipe_eligible ?? false"
            :disabled="!server.config"
            @update:model-value="toggleAutomation('force_wipe_eligible')"
          />
        </div>
      </div>
    </Panel>
  </div>
</template>

<style scoped>
.sv { max-width: 900px; margin: 0 auto; display: flex; flex-direction: column; gap: 16px; }

/* Page head */
.sv__head { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; }
.sv__head-id { display: flex; align-items: center; gap: 12px; }
.sv__head-chip {
  width: 40px; height: 40px; flex: none; border-radius: var(--radius-md);
  display: flex; align-items: center; justify-content: center;
  color: var(--accent); background: var(--accent-soft);
  box-shadow: inset 0 0 0 1px var(--accent-border);
}
.sv__title {
  font-size: var(--text-2xl); font-weight: 700; letter-spacing: -0.02em;
  color: var(--text-primary); margin-top: 3px;
}

/* Layout grids */
.sv__grid4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; }
.sv__grid3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
@media (max-width: 768px) {
  .sv__grid4 { grid-template-columns: repeat(2, 1fr); }
  .sv__grid3 { grid-template-columns: repeat(2, 1fr); }
}

/* Field */
.sv__field-label { font-size: var(--text-xs); color: var(--text-tertiary); margin-bottom: 4px; }
.sv__field-val { font-size: var(--text-sm); font-weight: 500; color: var(--text-primary); }
.sv__field-val--mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.sv__field-val--inline { display: flex; align-items: center; gap: 7px; }

/* Spacing helpers */
.sv__mb { margin-bottom: 20px; }
.sv__mb-sm { margin-bottom: 12px; }
.sv__mt { margin-top: 12px; }

/* Controls */
.sv__controls { display: flex; flex-wrap: wrap; gap: 10px; }
.sv__mt-sm { margin-top: 12px; }
.sv__instance-pick { display: flex; align-items: center; gap: 12px; }
.sv__cfg-row { display: flex; gap: 10px; align-items: center; }
.sv__cfg-path { flex: 1; }
.sv__cfg-editor {
  width: 100%;
  min-height: 320px;
  background: var(--surface-base);
  color: var(--text-primary);
  border: none;
  box-shadow: var(--ring-subtle);
  border-radius: var(--radius-md);
  padding: 14px 16px;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.6;
  resize: vertical;
}
.sv__cfg-hint { margin: 0; font-size: var(--text-sm); color: var(--text-tertiary); line-height: 1.55; }
.sv__select {
  background: var(--surface-base);
  color: var(--text-primary);
  border: none;
  box-shadow: var(--ring-subtle);
  border-radius: var(--radius-md);
  padding: 8px 12px;
  font-size: var(--text-sm);
  font-family: var(--font-mono);
  min-width: 280px;
}

/* Section head (label inside panel body) */
.sv__section-head {
  display: flex; align-items: center; gap: 7px;
  font-size: var(--text-xs); font-weight: 600; color: var(--text-tertiary);
  text-transform: uppercase; letter-spacing: var(--tracking-wider);
  margin-bottom: 10px;
}

/* Download links */
.sv__downloads { display: flex; flex-wrap: wrap; gap: 10px; }
.sv__dl-link {
  display: inline-flex; align-items: center; gap: 8px;
  height: var(--control-h-md); padding: 0 14px;
  background: var(--surface-raised-2); border-radius: var(--radius-md);
  box-shadow: var(--ring-default);
  font-size: var(--text-sm); font-weight: 600; color: var(--text-primary);
  text-decoration: none; transition: var(--transition-colors);
}
.sv__dl-link:hover { background: var(--surface-active); }

/* Setup head */
.sv__setup-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }

/* TOML reveal row */
.sv__toml-reveal { display: flex; align-items: center; }

/* Pre inside codeblock — preserve whitespace, no extra margin */
.sv__pre { margin: 0; white-space: pre; }

/* Code block */
.sv__codeblock {
  background: var(--surface-inset); border-radius: var(--radius-md);
  box-shadow: var(--ring-default);
  padding: 14px 16px; font-family: var(--font-mono); font-size: var(--text-xs);
  color: var(--text-secondary); overflow-x: auto; line-height: 1.7;
}
.sv__cmt { color: var(--text-muted); }
.sv__accent { color: var(--accent-text); }
.sv__mt { margin-top: 12px; }

/* Deploy prompt */
.sv__deploy-prompt { display: flex; flex-direction: column; align-items: center; text-align: center; gap: 14px; padding: 12px 0; }
.sv__deploy-desc { font-size: var(--text-sm); color: var(--text-tertiary); max-width: 420px; line-height: 1.5; }

/* Form */
.sv__form { display: flex; flex-direction: column; gap: 16px; }
.sv__form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; }
.sv__col-span2 { grid-column: 1 / -1; }
.sv__form-actions { display: flex; align-items: center; gap: 10px; }

/* Deployment / Oxide stages */
.sv__stages { display: flex; flex-direction: column; gap: 10px; }
.sv__stage { display: flex; align-items: center; gap: 11px; }
.sv__stage-dot {
  width: 24px; height: 24px; border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-raised-2); color: var(--text-muted);
  box-shadow: var(--ring-default);
}
.sv__stage-dot[data-state="active"] { background: var(--status-warn-soft); color: var(--status-warn); box-shadow: inset 0 0 0 1px var(--status-warn-border); }
.sv__stage-dot[data-state="complete"] { background: var(--status-online-soft); color: var(--status-online); box-shadow: inset 0 0 0 1px var(--status-online-border); }
.sv__stage-dot[data-state="failed"] { background: var(--status-offline-soft); color: var(--status-offline); box-shadow: inset 0 0 0 1px var(--status-offline-border); }
.sv__stage-nub { width: 6px; height: 6px; border-radius: 50%; background: var(--text-muted); }
.sv__stage-label { font-size: var(--text-sm); color: var(--text-tertiary); }
.sv__stage-label[data-state="active"] { color: var(--status-warn); font-weight: 600; }
.sv__stage-label[data-state="complete"] { color: var(--status-online); }
.sv__stage-label[data-state="failed"] { color: var(--status-offline); }

@keyframes sv-spin { to { transform: rotate(360deg); } }
.sv__spin { animation: sv-spin 0.7s linear infinite; }

/* Automation toggles */
.sv__toggles { display: flex; flex-direction: column; gap: 0; }
.sv__toggle-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; padding: 14px 0;
  border-bottom: 1px solid var(--border-subtle);
}
.sv__toggle-row:last-child { border-bottom: 0; padding-bottom: 0; }
.sv__toggle-row:first-child { padding-top: 0; }
.sv__toggle-label { font-size: var(--text-sm); font-weight: 500; color: var(--text-primary); }
.sv__toggle-sub { font-size: var(--text-xs); color: var(--text-tertiary); margin-top: 2px; }

/* Management model badge in page head */
.sv__model-badge { align-self: center; }

/* Game concept cards (Conan Exiles special features) */
.sv__concept-grid { display: flex; flex-direction: column; gap: 14px; }
.sv__concept {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 12px 14px;
  background: var(--surface-raised); border-radius: var(--radius-md);
  box-shadow: var(--ring-default);
  color: var(--accent);
}
.sv__concept-label { font-size: var(--text-sm); font-weight: 600; color: var(--text-primary); margin-bottom: 2px; }
.sv__concept-desc { font-size: var(--text-xs); color: var(--text-tertiary); line-height: 1.5; }
</style>
