<script setup lang="ts">
import { ref, computed, onBeforeUnmount } from 'vue'
import { useRouter } from 'vue-router'
import { NButton, NInput, NSelect, NIcon, useMessage } from 'naive-ui'
import { SearchOutline, CodeSlashOutline, AddOutline } from '@vicons/ionicons5'
import { api } from '../../api/client'
import { useSearchHistory } from '../../composables/useSearchHistory'

const router = useRouter()
const message = useMessage()
const { entries: historyEntries, add, remove, clear: clearHistory, download: downloadHistory } = useSearchHistory()

// ── Query Box ──
const queryText = ref('')

// ── Translation Table ──
const fieldLabelMap: Record<string, string> = {
  TI: 'Title',
  TIAB: 'Title/Abstract',
  AB: 'Abstract',
  AU: 'Author',
  AD: 'Affiliation',
  CN: 'Author - Corporate',
  FAU: 'Author - First',
  LAU: 'Author - Last',
  AUID: 'Author - Identifier',
  TA: 'Journal',
  MH: 'MeSH Terms',
  MAJR: 'MeSH Major Topic',
  SH: 'MeSH Subheading',
  PT: 'Publication Type',
  DP: 'Publication Date',
  PMID: 'PubMed ID',
  DOI: 'DOI',
  LA: 'Language',
  TW: 'Text Word',
  COIS: 'Conflict of Interest Statements',
  DCOM: 'Date - Completion',
  CRDT: 'Date - Create',
  EDAT: 'Date - Entry',
  MHDA: 'Date - MeSH',
  LR: 'Date - Modification',
  RN: 'EC/RN Number',
  ED: 'Editor',
  GR: 'Grants and Funding',
  IR: 'Investigator',
  IP: 'Issue',
  LID: 'Location ID',
  OT: 'Other Term',
  PG: 'Pagination',
  PA: 'Pharmacological Action',
  PUBN: 'Publisher',
  SI: 'Secondary Source ID',
  PS: 'Subject - Personal Name',
  NM: 'Supplementary Concept',
  TT: 'Transliterated Title',
  VI: 'Volume',
}

interface TransItem {
  field: string
  value: string
}

// 模块级常量的 validTags Set，避免每次 validateQuery 时重新创建
const _VALID_TAGS = new Set(['AB', 'AD', 'AU', 'CN', 'FAU', 'AUID', 'LAU', 'COIS', 'DCOM', 'CRDT', 'EDAT', 'MHDA', 'LR', 'DP', 'DOI', 'RN', 'ED', 'GR', 'IR', 'IP', 'TA', 'LA', 'LID', 'MAJR', 'SH', 'MH', 'OT', 'PG', 'PA', 'PMID', 'PT', 'PUBN', 'SI', 'PS', 'NM', 'TW', 'TI', 'TIAB', 'TT', 'VI'])
function useValidTags() { return _VALID_TAGS }

const translated = computed(() => {
  const items: TransItem[] = []
  const seen = new Set<string>()
  // 先解析 queryText 中的 #N 引用
  const displayText = resolveQuery(queryText.value)
  const re = /(?:"([^"]+)"|([^\[]+?))\s*\[(\w+)\]/g
  let m: RegExpExecArray | null
  while ((m = re.exec(displayText)) !== null) {
    const val = (m[1] || m[2] || '').trim().replace(/^(?:AND|OR|NOT)\s*/i, '')
    if (!val) continue
    const tag = (m[3] || '').toUpperCase()
    if (tag === 'DP') continue  // handled by date range regex below
    const label = fieldLabelMap[tag] || tag
    const key = `${m.index}|${tag}|${val}`
    if (!seen.has(key)) {
      seen.add(key)
      items.push({ field: `[${tag}] ${label}`, value: `"${val}"` })
    }
  }
  // Date range: supports YYYY:YYYY[DP], YYYY-MM-DD:YYYY-MM-DD[DP], YYYY/MM/DD:YYYY/MM/DD[DP],
  // YYYY-M-D:YYYY-M-D[DP] (single digit), YYYY-MM:YYYY-MM[DP] (month-only)
  const yrRe = /(\d{4}(?:[-\/]\d{1,2}(?:[-\/]\d{1,2})?)?)\s*:\s*(\d{4}(?:[-\/]\d{1,2}(?:[-\/]\d{1,2})?)?)\s*\[DP\]/g
  while ((m = yrRe.exec(displayText)) !== null) {
    const key = `dp_range_${m.index}`
    if (!seen.has(key)) {
      seen.add(key)
      items.push({ field: '[DP] Publication Date', value: `${m[1]}:${m[2]}` })
    }
  }
  return items
})

// ── Search Builder (顶部) ──
const builderOperator = ref('AND')
const builderField = ref('')
const builderValue = ref('')
const builderOpOptions = [
  { label: 'AND', value: 'AND' },
  { label: 'OR', value: 'OR' },
  { label: 'NOT', value: 'NOT' },
]
const fieldOptions = [
  { label: 'All Fields', value: '' },
  { label: 'Affiliation [AD]', value: 'AD' },
  { label: 'Author [AU]', value: 'AU' },
  { label: 'Author - Corporate [CN]', value: 'CN' },
  { label: 'Author - First [FAU]', value: 'FAU' },
  { label: 'Author - Identifier [AUID]', value: 'AUID' },
  { label: 'Author - Last [LAU]', value: 'LAU' },
  { label: 'Conflict of Interest Statements [COIS]', value: 'COIS' },
  { label: 'Date - Completion [DCOM]', value: 'DCOM' },
  { label: 'Date - Create [CRDT]', value: 'CRDT' },
  { label: 'Date - Entry [EDAT]', value: 'EDAT' },
  { label: 'Date - MeSH [MHDA]', value: 'MHDA' },
  { label: 'Date - Modification [LR]', value: 'LR' },
  { label: 'Date - Publication [DP]', value: 'DP' },
  { label: 'EC/RN Number [RN]', value: 'RN' },
  { label: 'Editor [ED]', value: 'ED' },
  { label: 'Grants and Funding [GR]', value: 'GR' },
  { label: 'Investigator [IR]', value: 'IR' },
  { label: 'Issue [IP]', value: 'IP' },
  { label: 'Journal [TA]', value: 'TA' },
  { label: 'Language [LA]', value: 'LA' },
  { label: 'Location ID [LID]', value: 'LID' },
  { label: 'MeSH Major Topic [MAJR]', value: 'MAJR' },
  { label: 'MeSH Subheading [SH]', value: 'SH' },
  { label: 'MeSH Terms [MH]', value: 'MH' },
  { label: 'Other Term [OT]', value: 'OT' },
  { label: 'Pagination [PG]', value: 'PG' },
  { label: 'Pharmacological Action [PA]', value: 'PA' },
  { label: 'Publication Type [PT]', value: 'PT' },
  { label: 'Publisher [PUBN]', value: 'PUBN' },
  { label: 'Secondary Source ID [SI]', value: 'SI' },
  { label: 'Subject - Personal Name [PS]', value: 'PS' },
  { label: 'Supplementary Concept [NM]', value: 'NM' },
  { label: 'Text Word [TW]', value: 'TW' },
  { label: 'Title [TI]', value: 'TI' },
  { label: 'Title/Abstract [TIAB]', value: 'TIAB' },
  { label: 'Transliterated Title [TT]', value: 'TT' },
  { label: 'Volume [VI]', value: 'VI' },
  { label: 'Abstract [AB]', value: 'AB' },
  { label: 'DOI [DOI]', value: 'DOI' },
  { label: 'PMID [PMID]', value: 'PMID' },
]

function addToQuery() {
  let val = builderValue.value.trim()
  // 去除用户输入的多余引号
  val = val.replace(/^['""“]+|['""”]+$/g, '')
  // R31: 去除内部双引号防止构建非法 PubMed 语法（保留单引号如 don't）
  val = val.replace(/["""“”]/g, '')
  if (!val) {
    message.warning('请输入搜索词')
    return
  }
  const tag = builderField.value
  // 构建新词条
  let term: string
  if (!tag) {
    term = val.includes(' ') ? `"${val}"` : val
  } else {
    term = val.includes(' ') ? `"${val}"[${tag}]` : `${val}[${tag}]`
  }
  // 如果 Query Box 非空，在前加逻辑条件
  if (queryText.value.trim()) {
    queryText.value += ` ${builderOperator.value} ${term}`
  } else {
    queryText.value += term
  }
  builderValue.value = ''
}

// ── Search History ──
const combineA = ref('')
const combineB = ref('')
const combineOp = ref('AND')
const combineOps = [
  { label: 'AND', value: 'AND' },
  { label: 'OR', value: 'OR' },
  { label: 'NOT', value: 'NOT' },
]

function combineQuery() {
  const a = combineA.value.trim()
  const b = combineB.value.trim()
  if (!a || !b) {
    message.warning('请输入两个查询编号')
    return
  }
  // 校验仅接受数字或 #N 引用
  const refRe = /^#?\d+$/
  if (!refRe.test(a) || !refRe.test(b)) {
    message.warning('查询编号必须为数字（如 1 或 #1）')
    return
  }
  // 自动补 # 前缀
  const refA = a.startsWith('#') ? a : `#${a}`
  const refB = b.startsWith('#') ? b : `#${b}`
  const sep = queryText.value && !queryText.value.endsWith(' ') ? ' ' : ''
  queryText.value += `${sep}${refA} ${combineOp.value} ${refB}`
  combineA.value = ''
  combineB.value = ''
}

function clickHistoryId(id: string) {
  const sep = queryText.value && !queryText.value.endsWith(' ') ? ' ' : ''
  queryText.value += `${sep}${id}`
}

function removeEntry(id: string) {
  remove(id)
}

// ── resolve #N（迭代展开，支持嵌套引用） ──
function resolveQuery(q: string): string {
  if (!q.includes('#')) return q
  const all = historyEntries.value
  const seen = new Set<string>()
  let prev = ''
  let current = q
  for (let i = 0; i < 10; i++) {
    if (current === prev) break
    // P12: only count #N outside quotes for cycle detection (quoted #N are literals)
    const refs = [...current.matchAll(/"[^"]*"|'[^']*'|#(\d+)/g)]
      .filter(m => m[1] !== undefined)
      .map(m => `#${m[1]}`)
    if (refs.length) {
      const uniqueRefs = [...new Set(refs)]
      for (const ref of uniqueRefs) {
        if (seen.has(ref)) return prev
        seen.add(ref)
      }
    }
    prev = current
    current = current.replace(/"[^"]*"|'[^']*'|#(\d+)/g, (m, num) => {
      if (num === undefined) return m  // 在引号内，不做替换
      const found = all.find(e => e.id === `#${num}`)
      if (!found) {
        return m
      }
      return `(${found.expanded_query})`
    })
  }
  return current
}

function validateQuery(q: string): { valid: boolean; query: string; error?: string } {
  if (!q.trim()) {
    return { valid: false, query: q, error: '请输入查询' }
  }

  // 校验 #N 引用格式
  const refRe = /#\d+/g
  let refMatch: RegExpExecArray | null
  while ((refMatch = refRe.exec(q)) !== null) {
    const num = parseInt(refMatch[0].slice(1), 10)
    const exists = historyEntries.value.find(e => e.id === `#${num}`)
    if (!exists) {
      return { valid: false, query: q, error: `历史引用 ${refMatch[0]} 不存在，请先执行相关查询` }
    }
  }

  // 校验空括号 []
  if (/\[\]/.test(q)) {
    return { valid: false, query: q, error: '字段标签不能为空，请在 [] 中输入标签名' }
  }

  // 校验字段标签格式
  const tagRe = /\[(\w+)\]/g
  let tagMatch: RegExpExecArray | null
  while ((tagMatch = tagRe.exec(q)) !== null) {
    const tag = (tagMatch[1] || '').toUpperCase()
    const validTags = useValidTags()
    if (!validTags.has(tag)) {
      return { valid: false, query: q, error: `字段标签 [${tag}] 不合法` }
    }
  }

  // 校验布尔运算符格式（不能连续 OR/AND，不能以运算符开头/结尾）
  // 注：AND NOT / OR NOT 是有效语法；前导 NOT 也是有效语法
  const trimmed = q.trim()
  const ops = ['AND', 'OR', 'NOT']
  const upperTrimmed = trimmed.toUpperCase()
  const words = upperTrimmed.split(/\s+/).filter(Boolean)
  const firstWord = words[0] || ''
  const lastWord = words[words.length - 1] || ''
  if (firstWord !== 'NOT' && ops.includes(firstWord)) {
    return { valid: false, query: q, error: '查询不能以布尔运算符（AND/OR）开头（前导 NOT 允许）' }
  }
  if (ops.includes(lastWord)) {
    return { valid: false, query: q, error: '查询不能以布尔运算符（AND/OR/NOT）结束' }
  }
  for (let i = 0; i < words.length - 1; i++) {
    if (ops.includes(words[i] || '') && ops.includes(words[i + 1] || '')) {
      // 允许 AND NOT 和 OR NOT
      if (words[i] === 'AND' && words[i + 1] === 'NOT') continue
      if (words[i] === 'OR' && words[i + 1] === 'NOT') continue
      return { valid: false, query: q, error: '不允许连续使用布尔运算符（AND NOT / OR NOT 除外）' }
    }
  }

  // 校验括号匹配（在展开后的查询上检查，确保历史引用展开后也平衡）
  const expanded = resolveQuery(q)
  let depth = 0
  for (const ch of expanded) {
    if (ch === '(') depth++
    if (ch === ')') depth--
    if (depth < 0) {
      return { valid: false, query: q, error: '括号不匹配：多余的右括号（结合历史查询展开后）' }
    }
  }
  if (depth !== 0) {
    return { valid: false, query: q, error: '括号不匹配：缺少右括号（结合历史查询展开后）' }
  }

  return { valid: true, query: expanded }
}

const loading = ref(false)
const reversedHistory = computed(() => historyEntries.value.slice().reverse())
const fetchCountController = ref<AbortController | null>(null)

async function fetchCount(q: string): Promise<number | null> {
  fetchCountController.value?.abort()
  fetchCountController.value = new AbortController()
  try {
    const { data } = await api.post('/features/search/advanced', { query: q, page_size: 1 }, { signal: fetchCountController.value.signal })
    return (data && typeof data.total === 'number') ? data.total : null
  } catch (e: any) {
    if (e?.name === 'CanceledError' || e?.code === 'ERR_CANCELED') return null
    console.warn('fetchCount failed:', e)
    return null
  }
}

async function pubSearch() {
  const raw = queryText.value.trim()
  const check = validateQuery(raw)
  if (!check.valid) {
    message.warning(check.error || '查询无效')
    return
  }

  loading.value = true
  try {
    const count = await fetchCount(check.query)
    add(raw, count)
    await router.push({ name: 'public-search', query: { q: check.query } }).catch(() => {})
  } finally {
    loading.value = false
  }
}

async function addToHistory() {
  const raw = queryText.value.trim()
  const check = validateQuery(raw)
  if (!check.valid) {
    message.warning(check.error || '查询无效')
    return
  }

  loading.value = true
  try {
    const count = await fetchCount(check.query)
    const entry = add(raw, count)
    message.success(`${entry.id} 已加入历史（${count ?? '?'} 条结果）`)
  } finally {
    loading.value = false
  }
}

function clearQuery() {
  queryText.value = ''
}

function formatDate(ts: string): string {
  try {
    const d = new Date(ts)
    return d.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false })
  } catch { return ts }
}

onBeforeUnmount(() => {
  fetchCountController.value?.abort()
})
</script>

<template>
  <div class="aps-page" style="max-width:1200px;margin:0 auto;padding:32px 24px">
      <div class="aps-header">
        <h1>
          <NIcon size="24" style="vertical-align:-4px;margin-right:6px"><CodeSlashOutline /></NIcon>
          Pub高级搜索
        </h1>
        <p class="aps-subtitle">使用 PubMed 语法构建精确查询，支持 #N 历史组合引用</p>
      </div>

      <!-- ═══ 1. Search Builder (顶部) ═══ -->
      <div class="aps-section">
        <div class="aps-section-title">Search Builder</div>
        <div class="aps-builder-row">
          <NSelect
            v-model:value="builderOperator"
            :options="builderOpOptions"
            class="aps-builder-op"
            size="small"
          />
          <NSelect
            v-model:value="builderField"
            :options="fieldOptions"
            placeholder="All Fields"
            clearable
            class="aps-field-select"
            size="small"
            popup-class="aps-field-popup"
          />
          <NInput
            v-model:value="builderValue"
            placeholder="输入搜索词..."
            clearable
            class="aps-builder-input"
            size="small"
            @keyup.enter="addToQuery"
          />
          <NButton @click="addToQuery" class="sky-btn" size="small">
            <template #icon><NIcon size="14"><AddOutline /></NIcon></template>
            Add
          </NButton>
        </div>
      </div>

      <!-- ═══ 2. Query Box ═══ -->
      <div class="aps-section">
        <div class="aps-section-title">Query Box</div>
        <textarea
          v-model="queryText"
          class="aps-textarea"
          placeholder='例如: "lung cancer"[TI] AND 2024:2025[DP]'
          rows="4"
        ></textarea>
        <!-- 搜索按钮紧接在 Query Box 下方 -->
        <div class="aps-query-actions">
          <NButton type="primary" :loading="loading" @click="pubSearch" class="sky-btn sky-btn-primary" size="small">
            <template #icon><NIcon size="14"><SearchOutline /></NIcon></template>
            Search
          </NButton>
          <NButton @click="clearQuery" class="sky-btn" size="small">Clear</NButton>
          <NButton @click="addToHistory" class="sky-btn" size="small" :disabled="loading">
            <template #icon><NIcon size="14"><AddOutline /></NIcon></template>
            Add to History
          </NButton>
        </div>
      </div>

      <!-- ═══ 3. Translation Table ═══ -->
      <div class="aps-section" v-if="translated.length">
        <div class="aps-section-title">Translation Table</div>
        <div class="aps-tt-wrapper">
          <table class="aps-table">
            <thead>
              <tr><th>Field</th><th>Query Terms</th></tr>
            </thead>
            <tbody>
              <tr v-for="(t, i) in translated" :key="i">
                <td class="aps-tt-field">{{ t.field }}</td>
                <td class="aps-tt-value"><code>{{ t.value }}</code></td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>

      <!-- ═══ 4. Search History ═══ -->
      <div class="aps-section">
        <div class="aps-section-title">
          Search History
          <span class="aps-history-actions" v-if="historyEntries.length">
            <NButton text size="tiny" @click="downloadHistory">Download</NButton>
            <NButton text size="tiny" @click="clearHistory" style="margin-left:8px;color:var(--badge-error-text)">Clear All</NButton>
          </span>
        </div>
        <div v-if="historyEntries.length === 0" class="aps-empty">暂无搜索历史，输入查询后点击 Search 或 Add to History</div>
        <table v-else class="aps-table aps-history-table">
          <thead>
            <tr><th>#</th><th>Query</th><th>Results</th><th>Time</th><th></th></tr>
          </thead>
          <tbody>
            <tr v-for="entry in reversedHistory" :key="entry.id" class="aps-history-row">
              <td class="aps-h-id" @click="clickHistoryId(entry.id)" title="点击 #N 添加到 Query Box">{{ entry.id }}</td>
              <td class="aps-h-query" @click="clickHistoryId(entry.id)" title="点击添加到 Query Box">
                <code>{{ entry.expanded_query }}</code>
              </td>
              <td class="aps-h-count">
                <span v-if="entry.result_count != null" class="aps-count-tag">{{ entry.result_count }}</span>
                <span v-else class="aps-h-na">—</span>
              </td>
              <td class="aps-h-time">{{ formatDate(entry.timestamp) }}</td>
              <td class="aps-h-del"><NButton text size="tiny" @click="removeEntry(entry.id)" title="删除">✕</NButton></td>
            </tr>
          </tbody>
        </table>

        <!-- Combine -->
        <div class="aps-combine-row" v-if="historyEntries.length >= 2">
          <span class="aps-combine-label">Combine:</span>
          <NInput v-model:value="combineA" placeholder="#1" class="aps-combine-input" size="tiny" />
          <NSelect v-model:value="combineOp" :options="combineOps" class="aps-combine-op" size="tiny" />
          <NInput v-model:value="combineB" placeholder="#2" class="aps-combine-input" size="tiny" />
          <NButton size="tiny" @click="combineQuery" class="sky-btn">Combine</NButton>
        </div>
      </div>
  </div>
</template>

<style scoped>
.aps-page {
  min-height: 100vh;
}
.aps-header {
  margin-bottom: 24px;
}
.aps-header h1 {
  margin: 0 0 4px 0;
  font-size: 22px;
  color: var(--text-primary);
}
.aps-subtitle {
  margin: 0;
  font-size: 13px;
  color: var(--text-muted);
}
.aps-section {
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-radius: 8px;
  padding: 16px;
  margin-bottom: 16px;
}
.aps-section-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
  margin-bottom: 10px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* ── Search Builder ── */
.aps-builder-row {
  display: flex;
  gap: 8px;
  align-items: center;
}
.aps-builder-op {
  width: 90px;
  flex-shrink: 0;
}
.aps-field-select {
  width: 260px;
  flex-shrink: 0;
}
.aps-builder-input {
  flex: 1;
}

/* ── Query Box ── */
.aps-textarea {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border-color);
  border-radius: 6px;
  font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
  font-size: 14px;
  line-height: 1.6;
  resize: vertical;
  background: var(--bg-page);
  color: var(--text-primary);
  box-sizing: border-box;
}
.aps-textarea:focus {
  outline: none;
  border-color: #6ab0e0;
}
.aps-query-actions {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-top: 12px;
}

/* ── Table ── */
.aps-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
.aps-table th,
.aps-table td {
  padding: 6px 10px;
  text-align: left;
  border-bottom: 1px solid var(--border-color);
}
.aps-table th {
  font-weight: 600;
  color: var(--text-secondary);
  font-size: 12px;
  background: var(--bg-page);
}

/* ── Translation Table ── */
.aps-tt-wrapper {
  max-height: 200px;
  overflow-y: auto;
}
.aps-tt-field {
  color: #2a6a9a;
  font-weight: 500;
  width: 160px;
}
.aps-tt-value code {
  background: var(--preview-ai-bg);
  padding: 2px 6px;
  border-radius: 3px;
  font-size: 13px;
}

/* ── History ── */
.aps-history-actions {
  font-size: 12px;
}
.aps-empty {
  text-align: center;
  padding: 20px 0;
  font-size: 13px;
  color: var(--text-muted);
}
.aps-history-row {
  cursor: pointer;
}
.aps-history-row:hover td {
  background: var(--bg-hover);
}
.aps-h-id {
  font-weight: 600;
  color: #2a6a9a;
  width: 40px;
  cursor: pointer;
}
.aps-h-id:hover {
  text-decoration: underline;
}
.aps-h-query {
  max-width: 400px;
}
.aps-h-query code {
  font-size: 12px;
  color: var(--text-primary);
  word-break: break-all;
}
.aps-h-count {
  width: 80px;
  text-align: center;
}
.aps-count-tag {
  display: inline-block;
  background: var(--preview-ai-bg);
  padding: 1px 8px;
  border-radius: 10px;
  font-size: 12px;
  color: var(--text-primary);
}
.aps-h-na {
  color: var(--text-muted);
  font-size: 12px;
}
.aps-h-time {
  color: var(--text-muted);
  font-size: 12px;
  white-space: nowrap;
  width: 80px;
}
.aps-h-del {
  width: 32px;
  text-align: center;
}

/* ── Combine ── */
.aps-combine-row {
  display: flex;
  gap: 6px;
  align-items: center;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--border-color);
}
.aps-combine-label {
  font-size: 13px;
  font-weight: 500;
  color: var(--text-secondary);
  white-space: nowrap;
}
.aps-combine-input {
  width: 70px;
}
.aps-combine-op {
  width: 90px;
}

html.dark .aps-tt-field {
  color: #6ab0e0;
}
html.dark .aps-h-id {
  color: #6ab0e0;
}

:global(.aps-field-popup.n-base-select-menu) {
  --n-option-container-max-height: 660px !important;
}

html.dark .aps-textarea::selection {
  background: #1a6bb0;
  color: #fff;
}

@media (max-width: 768px) {
  .aps-page { padding: 16px; }
  .aps-builder-row { flex-direction: column; align-items: stretch; }
  .aps-field-select { width: 100%; }
  .aps-combine-row { flex-wrap: wrap; }
}
</style>
