<template>
  <div class="query-report-page">
    <!-- Header -->
    <div class="page-header">
      <div class="header-left">
        <h2>{{ pageTitle }}</h2>
      </div>
      <div class="header-right">
        <NSpace>
          <NButton quaternary size="small" @click="handleRefresh" :loading="loading">
            <template #icon><i class="fa-solid fa-rotate-right" /></template>
          </NButton>
          <NDropdown trigger="click" :options="menuOptions" @select="handleMenuSelect">
            <NButton quaternary size="small">
              <template #icon><i class="fa-solid fa-ellipsis-vertical" /></template>
            </NButton>
          </NDropdown>
        </NSpace>
      </div>
    </div>

    <!-- No report selected -->
    <div v-if="!reportName" class="empty-state">
      <p>{{ t('Nothing to show') }}</p>
    </div>

    <template v-else>
      <!-- Filters -->
      <div v-if="filterDefs.length > 0" class="filter-section">
        <NForm inline label-placement="left" size="small">
          <template v-for="f in filterDefs" :key="f.fieldname">
            <!-- Check: checkbox before label -->
            <NCheckbox
              v-if="f.fieldtype === 'Check' && !f._hidden"
              :checked="!!filterValues[f.fieldname]"
              @update:checked="(v: boolean) => { filterValues[f.fieldname] = v ? 1 : 0; onFilterChange() }"
            >
              {{ t(f.label) }}
            </NCheckbox>
            <!-- Other fields with label -->
            <NFormItem v-else-if="!f._hidden" :label="t(f.label)">
              <!-- Link -->
              <div v-if="f.fieldtype === 'Link'" class="filter-link-field">
                <LinkControl
                  :df="f"
                  :record="filterValues"
                  :canEdit="true"
                  :ctx="linkCtx"
                />
              </div>
              <!-- Select -->
              <NSelect
                v-else-if="f.fieldtype === 'Select'"
                v-model:value="filterValues[f.fieldname]"
                :options="parseSelectOptions(f.options)"
                clearable
                @update:value="onFilterChange"
                style="min-width: 140px"
              />
              <!-- Date -->
              <NDatePicker
                v-else-if="f.fieldtype === 'Date'"
                v-model:formatted-value="filterValues[f.fieldname]"
                type="date"
                value-format="yyyy-MM-dd"
                clearable
                @update:value="onFilterChange"
                style="min-width: 140px"
              />
              <!-- Int / Float / Currency -->
              <NInputNumber
                v-else-if="['Int', 'Float', 'Currency'].includes(f.fieldtype)"
                v-model:value="filterValues[f.fieldname]"
                clearable
                @update:value="onFilterChange"
                style="min-width: 120px"
              />
              <!-- MultiSelectList -->
              <NSelect
                v-else-if="f.fieldtype === 'MultiSelectList'"
                v-model:value="filterValues[f.fieldname]"
                filterable
                clearable
                multiple
                remote
                :loading="f._loading"
                :options="f._options || []"
                @search="(q: string) => handleLinkSearch(f, q)"
                @update:value="onFilterChange"
                style="min-width: 180px"
              />
              <!-- Default: Data / SmallText etc -->
              <NInput
                v-else
                v-model:value="filterValues[f.fieldname]"
                clearable
                @update:value="onFilterChange"
                style="min-width: 140px"
              />
            </NFormItem>
          </template>
        </NForm>
      </div>

      <!-- Status message -->
      <div v-if="statusMessage" class="status-bar" v-html="statusMessage" />

      <!-- Summary -->
      <div v-if="reportSummary.length > 0" class="report-summary">
        <div v-for="(s, idx) in reportSummary" :key="idx" class="summary-item">
          <span class="summary-label">{{ s.label }}:</span>
          <span class="summary-value" :class="s.indicator ? `text-${s.indicator}` : ''">
            {{ s.value }}
          </span>
        </div>
      </div>

      <!-- Chart -->
      <div v-if="reportChartData" class="chart-section">
        <ChartRenderer
          :data="reportChartData"
          :config="reportChartConfig"
          :height="280"
        />
      </div>

      <!-- Prepared report actions -->
      <div v-if="isPreparedReport && !loading" class="prepared-actions">
        <NSpace>
          <NButton type="primary" size="small" @click="handlePreparedAction">
            {{ preparedActionLabel }}
          </NButton>
        </NSpace>
      </div>

      <!-- Loading -->
      <div v-if="loading" class="loading-state">
        <NSpin size="medium" />
        <p>{{ t('Loading') }}...</p>
      </div>

      <!-- Data table -->
      <div v-else-if="rows.length > 0" class="report-table-section">
        <NDataTable
          :columns="tableColumns"
          :data="tableData"
          :bordered="false"
          :single-line="false"
          :scroll-x="scrollX"
          :max-height="600"
          :row-key="(row: any) => row._rowIndex"
          size="small"
          striped
        />
        <!-- Footer -->
        <div class="report-footer">
          <span class="footer-hint">
            {{ t('For comparison, use >5, <10 or =324. For ranges, use 5:10 (for values between 5 & 10).') }}
          </span>
          <span class="footer-time">
            {{ t('Execution Time: {0} sec', [executionTime]) }}
          </span>
        </div>
      </div>

      <!-- Empty -->
      <div v-else-if="!loading && dataFetched" class="empty-state">
        <p>{{ t('Nothing to show') }}</p>
      </div>
    </template>
  </div>
</template>

<script setup lang="ts">
import { ref, reactive, computed, watch, onMounted, onUnmounted, nextTick, h, type VNodeChild } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import {
  NButton, NSpace, NDropdown, NForm, NFormItem, NInput, NInputNumber,
  NSelect, NDatePicker, NCheckbox, NDataTable, NSpin,
  useMessage, useDialog,
  type DataTableColumn
} from 'naive-ui'
import { api, getRecord } from '@/shared/api/common'
import { searchWidget } from '@/shared/api/common'
import { t } from '@/shared/i18n'
import { pageTypeToSlug } from '@/shared/utils/slug'
import { getReportFilters } from '@/shared/extensions'
import ChartRenderer from '@/core/features/workspace_builder/blocks/ChartRenderer.vue'
import LinkControl from '@/core/pagetype/form/controls/Link.vue'

const route = useRoute()
const router = useRouter()
const message = useMessage()
const dialog = useDialog()

// ─── State ───
const reportName = ref('')
const reportPg = ref<any>(null)
const reportSettings = ref<any>({})
const pageTitle = ref(t('Query Report'))
const loading = ref(false)
const dataFetched = ref(false)

// Filters
const filterDefs = ref<any[]>([])
const filterValues = reactive<Record<string, any>>({})

// Link context for LinkControl
const linkCtx = {
  t,
  onFieldChange: (fieldname: string, newValue: any, oldValue: any, record: any) => {
    onFilterChange()
  }
}

// Report data
const columns = ref<any[]>([])
const rows = ref<any[]>([])
const rawData = ref<any>(null)
const executionTime = ref('0.1')
const statusMessage = ref('')
const reportSummary = ref<any[]>([])
const chartData = ref<any>(null)

// Prepared report
const isPreparedReport = ref(false)
const preparedReportDoc = ref<any>(null)
const preparedReportName = ref<string | null>(null)
const preparedAction = ref<'New' | 'Edit' | 'Rebuild'>('New')
const ignorePreparedReport = ref(false)
const preparedActionLabel = computed(() => {
  const labels = { New: t('Generate New Report'), Edit: t('Edit'), Rebuild: t('Rebuild') }
  return labels[preparedAction.value]
})

// Abort controller
let abortController: AbortController | null = null

// ─── Computed ───
const tableColumns = computed<DataTableColumn[]>(() => {
  return columns.value
    .filter((col: any) => !col.hidden)
    .map((col: any) => {
      const base: DataTableColumn = {
        title: t(col.label),
        key: col.fieldname,
        width: col.width ? parseInt(col.width) : undefined,
        ellipsis: { tooltip: true },
        sorter: getSorter(col.fieldtype),
      }
      if (col.fieldtype === 'Currency' || col.fieldtype === 'Float' || col.fieldtype === 'Percent') {
        base.align = 'right'
      }
      if (col.fieldtype === 'Link' && col.options) {
        base.render = (row: any) => {
          const val = row[col.fieldname]
          if (!val) return ''
          return h('a', {
            style: 'color: var(--primary-color); cursor: pointer;',
            onClick: () => {
              const slug = pageTypeToSlug(col.options)
              router.push(`/app/${slug}/${encodeURIComponent(val)}`)
            }
          }, String(val))
        }
      }
      return base
    })
})

const tableData = computed(() => {
  return rows.value.map((row, idx) => ({ ...row, _rowIndex: idx }))
})

const scrollX = computed(() => {
  const visibleCols = columns.value.filter((c: any) => !c.hidden)
  return visibleCols.reduce((sum: number, col: any) => sum + (parseInt(col.width) || 150), 0)
})

const reportChartData = computed(() => {
  if (!chartData.value?.data) return null
  return chartData.value.data
})

const reportChartConfig = computed(() => {
  if (!chartData.value) return null
  const typeMap: Record<string, string> = {
    line: 'Line', bar: 'Bar', pie: 'Pie', donut: 'Donut',
    heatmap: 'Heatmap', percentage: 'Percentage'
  }
  const raw = chartData.value.type || 'bar'
  return {
    type: typeMap[raw] || raw.charAt(0).toUpperCase() + raw.slice(1),
    name: reportName.value
  } as any
})

const menuOptions = computed(() => {
  const items: any[] = []
  items.push({ label: t('Refresh'), key: 'refresh' })
  if (reportPg.value) {
    items.push({ label: t('Edit'), key: 'edit_report' })
    items.push({ label: t('Export'), key: 'export' })
    items.push({ label: t('Print'), key: 'print' })
    items.push({ label: t('Setup Auto Email'), key: 'auto_email' })
  }
  return items
})

// ─── Route watch ───
watch(() => route.params.reportName, (newVal) => {
  if (newVal && typeof newVal === 'string') {
    loadReport(newVal)
  } else {
    reportName.value = ''
    pageTitle.value = t('Query Report')
  }
}, { immediate: true })

// Also watch query params for prepared_report_name etc
watch(() => route.query, () => {
  if (reportName.value) {
    applyQueryParams()
  }
}, { deep: true })

// ─── Core logic ───
async function loadReport(name: string) {
  reportName.value = decodeURIComponent(name)
  pageTitle.value = t(reportName.value)
  dataFetched.value = false
  isPreparedReport.value = false
  preparedReportDoc.value = null
  preparedAction.value = 'New'
  statusMessage.value = ''
  reportSummary.value = []
  chartData.value = null
  columns.value = []
  rows.value = []
  rawData.value = null

  try {
    loading.value = true

    // Load report document
    const pgResult = await getRecord('Report', reportName.value)
    if (!pgResult.success || !pgResult.data) {
      message.error(t('Report not found'))
      loading.value = false
      return
    }
    reportPg.value = pgResult.data

    // Load report settings/script
    const settingsResult = await api.call('jingrow.desk.query_report.get_script', {
      report_name: reportName.value
    })
    const settings = settingsResult?.message || settingsResult || {}
    reportSettings.value = settings

    await setupFilters(settings)

    // Apply query params / route options
    applyQueryParams()

    loading.value = false

    // Auto refresh
    await refresh()
  } catch (e: any) {
    loading.value = false
    message.error(e.message || t('Failed to load report'))
  }
}

function normalizeFilterValue(value: any, fieldtype: string): any {
  if (Array.isArray(value) && fieldtype !== 'MultiSelectList') {
    return value[0] ?? null
  }
  // Convert string "0"/"1" to number for Check fields
  if (fieldtype === 'Check' && typeof value === 'string') {
    return value === '1' ? 1 : 0
  }
  return value
}

async function setupFilters(settings: any) {
  const defs: any[] = []
  Object.keys(filterValues).forEach(k => delete filterValues[k])

  let filters: any[] = []

  const extConfig = getReportFilters(reportName.value)
  if (extConfig?.filters?.length) {
    filters = extConfig.filters.map((f: any) => ({ ...f }))
    if (extConfig.resolveDefaults) {
      try {
        const defaults = await extConfig.resolveDefaults()
        if (defaults && typeof defaults === 'object') {
          for (const f of filters) {
            if (f.fieldname && f.fieldname in defaults && (f.default === undefined || f.default === null)) {
              f.default = normalizeFilterValue(defaults[f.fieldname], f.fieldtype)
            }
          }
        }
      } catch { /* ignore */ }
    }
  } else {
    filters = settings.filters || []
    if ((!filters || filters.length === 0) && reportPg.value?.filters?.length) {
      filters = reportPg.value.filters
    }
  }

  if (typeof filters === 'string') {
    try { filters = JSON.parse(filters) } catch { filters = [] }
  }

  for (const df of filters) {
    if (df.fieldtype === 'Break') continue
    const f = { ...df, _hidden: false, _loading: false, _options: [] as any[] }
    if (df.default !== undefined && df.default !== null) {
      const val = normalizeFilterValue(df.default, df.fieldtype)
      filterValues[df.fieldname] = val
      if (df.fieldtype === 'Link' && val) {
        f._options = [{ label: String(val), value: val }]
      }
    }
    defs.push(f)
  }

  if (reportPg.value?.add_translate_data) {
    defs.push({
      fieldname: 'translate_data',
      fieldtype: 'Check',
      label: 'Translate Data',
      _hidden: false
    })
  }

  filterDefs.value = defs
}

function applyQueryParams() {
  const query = route.query
  if (!query) return

  if (query.prepared_report_name) {
    preparedReportName.value = String(query.prepared_report_name)
  }
  if (query.ignore_prepared_report) {
    ignorePreparedReport.value = true
  }

  for (const f of filterDefs.value) {
    if (query[f.fieldname] !== undefined) {
      let val: any = query[f.fieldname]
      if (typeof val === 'string' && val.startsWith('[')) {
        try { val = JSON.parse(val) } catch { /* keep string */ }
      }
      val = normalizeFilterValue(val, f.fieldtype)
      filterValues[f.fieldname] = val
      if (f.fieldtype === 'Link' && val && (!f._options || f._options.length === 0)) {
        f._options = [{ label: String(val), value: val }]
      }
    }
  }
}

function getFilterValues(): Record<string, any> {
  const result: Record<string, any> = {}
  for (const f of filterDefs.value) {
    const v = filterValues[f.fieldname]
    if (v !== undefined && v !== null && v !== '' && v !== '%') {
      result[f.fieldname] = f.wildcard_filter ? `%${v}%` : v
    }
  }
  return result
}

function checkMandatoryFilters(): boolean {
  for (const f of filterDefs.value) {
    if ((f.reqd || f.mandatory) && !filterValues[f.fieldname]) {
      message.warning(t('Please set filters'))
      // Clear data when mandatory filters are missing
      rows.value = []
      columns.value = []
      chartData.value = null
      rawData.value = null
      dataFetched.value = true
      return false
    }
  }
  return true
}

async function refresh() {
  if (!reportName.value) return
  if (!checkMandatoryFilters()) return

  loading.value = true
  dataFetched.value = false
  statusMessage.value = ''
  reportSummary.value = []

  if (abortController) {
    abortController.abort()
  }
  abortController = new AbortController()

  const filters = getFilterValues()
  if (preparedReportName.value) {
    filters.prepared_report_name = preparedReportName.value
  }

  try {
    const result = await api.call('jingrow.desk.query_report.run', {
      report_name: reportName.value,
      filters: filters,
      ignore_prepared_report: ignorePreparedReport.value,
      is_tree: reportSettings.value?.tree || false,
      parent_field: reportSettings.value?.parent_field || null,
      are_default_filters: true
    })

    const data = result?.message || result
    if (!data) {
      rows.value = []
      columns.value = []
      loading.value = false
      dataFetched.value = true
      return
    }

    executionTime.value = String(data.execution_time || 0.1)

    if (data.custom_filters) {
      for (const [k, v] of Object.entries(data.custom_filters)) {
        filterValues[k] = v
      }
    }

    if (data.prepared_report) {
      isPreparedReport.value = true
      preparedReportDoc.value = data.pg || null
      if (preparedReportName.value) {
        preparedAction.value = 'Edit'
      } else {
        preparedAction.value = 'Rebuild'
      }
    }

    if (data.report_summary) {
      reportSummary.value = data.report_summary
    }

    if (data.message && !data.prepared_report) {
      statusMessage.value = data.message
    }

    // Process columns
    columns.value = processColumns(data.columns || [])

    // Process rows
    if (data.result && data.result.length) {
      rows.value = processData(data.result, columns.value)
      rawData.value = data

      // Chart
      if (data.chart) {
        chartData.value = data.chart
      }
    } else {
      rows.value = []
      rawData.value = null
    }

    dataFetched.value = true
  } catch (e: any) {
    if (e.name !== 'AbortError') {
      message.error(e.message || t('Failed to load report data'))
    }
    // Clear data on error
    rows.value = []
    columns.value = []
    chartData.value = null
    rawData.value = null
    dataFetched.value = true
  } finally {
    loading.value = false
    dataFetched.value = true
    updateUrlWithFilters()
  }
}

function processColumns(cols: any[]): any[] {
  return cols.map((col: any) => {
    if (typeof col === 'string') {
      const parts = col.split(':')
      const label = parts[0]
      let fieldtype = 'Data'
      let options: string | null = null
      let width: string | null = null
      if (parts.length > 1 && parts[1]) {
        if (parts[1].includes('/')) {
          const [ft, opt] = parts[1].split('/')
          fieldtype = ft
          options = opt
        } else {
          fieldtype = parts[1]
        }
      }
      if (parts.length > 2) width = parts[2]
      return { label, fieldname: label.toLowerCase().replace(/ /g, '_'), fieldtype, options, width }
    }
    if (!col.fieldname) {
      col.fieldname = col.label ? col.label.toLowerCase().replace(/ /g, '_') : `col_${Math.random().toString(36).slice(2, 8)}`
    }
    return col
  })
}

function processData(data: any[], cols: any[]): any[] {
  return data.map((row: any) => {
    if (Array.isArray(row)) {
      const obj: Record<string, any> = {}
      cols.forEach((col, i) => {
        obj[col.fieldname] = row[i]
      })
      return obj
    }
    return row
  })
}

function getSorter(fieldtype: string) {
  if (['Int', 'Float', 'Currency', 'Percent'].includes(fieldtype)) {
    return (a: any, b: any, key: string) => (Number(a[key]) || 0) - (Number(b[key]) || 0)
  }
  if (fieldtype === 'Date' || fieldtype === 'Datetime') {
    return (a: any, b: any, key: string) => {
      const da = a[key] ? new Date(a[key]).getTime() : 0
      const db = b[key] ? new Date(b[key]).getTime() : 0
      return da - db
    }
  }
  return true
}

function updateUrlWithFilters() {
  const query: Record<string, string> = {}
  if (preparedReportName.value) {
    query.prepared_report_name = preparedReportName.value
  } else {
    const vals = getFilterValues()
    for (const [k, v] of Object.entries(vals)) {
      if (Array.isArray(v)) {
        query[k] = JSON.stringify(v)
      } else {
        query[k] = String(v)
      }
    }
  }
  router.replace({ query }).catch(() => {})
}

// ─── Filter helpers ───
let filterChangeTimer: ReturnType<typeof setTimeout> | null = null
function onFilterChange() {
  if (filterChangeTimer) clearTimeout(filterChangeTimer)
  filterChangeTimer = setTimeout(() => {
    if (isPreparedReport.value) {
      isPreparedReport.value = false
      preparedReportDoc.value = null
    }
    refresh()
  }, 300)
}

async function handleLinkSearch(f: any, query: string) {
  if (!f.options) return
  f._loading = true
  try {
    const result = await searchWidget(f.options, query, { page_length: 20 })
    if (result.success && result.data) {
      f._options = result.data.map((item: any) => ({
        label: item.value || item.name,
        value: item.value || item.name,
        description: item.description || ''
      }))
    }
  } catch { /* ignore */ } finally {
    f._loading = false
  }
}

function parseSelectOptions(options: any): { label: string; value: string }[] {
  if (!options) return []
  if (typeof options === 'string') {
    return options.split('\n').filter(Boolean).map((o: string) => ({ label: t(o.trim()), value: o.trim() }))
  }
  if (Array.isArray(options)) {
    return options.map((o: any) => {
      if (typeof o === 'string') return { label: t(o), value: o }
      return { label: t(o.label || o.value), value: o.value }
    })
  }
  return []
}

// ─── Actions ───
function handleRefresh() {
  refresh()
}

function handleMenuSelect(key: string) {
  switch (key) {
    case 'refresh':
      refresh()
      break
    case 'edit_report':
      router.push(`/app/report/${encodeURIComponent(reportName.value)}`)
      break
    case 'export':
      exportReport()
      break
    case 'print':
      printReport()
      break
    case 'auto_email':
      router.push(`/app/auto-email-report?report=${encodeURIComponent(reportName.value)}`)
      break
  }
}

async function exportReport() {
  if (!rawData.value) {
    message.warning(t('No data to export'))
    return
  }

  const filters = getFilterValues()
  const params = new URLSearchParams()
  params.append('cmd', 'jingrow.desk.query_report.export_query')
  params.append('report_name', reportName.value)
  params.append('file_format_type', 'Excel')
  params.append('filters', JSON.stringify(filters))
  params.append('visible_idx', JSON.stringify([]))
  params.append('include_indentation', '0')
  params.append('include_filters', '0')
  params.append('custom_columns', '[]')

  const url = '/api/action/jingrow.desk.query_report.export_query'
  // Use form submission for file download
  const form = document.createElement('form')
  form.method = 'POST'
  form.action = url
  form.style.display = 'none'

  for (const [k, v] of params.entries()) {
    const input = document.createElement('input')
    input.type = 'hidden'
    input.name = k
    input.value = v
    form.appendChild(input)
  }

  document.body.appendChild(form)
  form.submit()
  document.body.removeChild(form)
}

function printReport() {
  if (!rawData.value || rows.value.length === 0) {
    message.warning(t('No data to print'))
    return
  }

  const printWindow = window.open('', '_blank')
  if (!printWindow) return

  const visibleCols = columns.value.filter((c: any) => !c.hidden)
  let html = `<html><head><title>${t(reportName.value)}</title>
    <style>
      body { font-family: sans-serif; padding: 20px; }
      h1 { font-size: 18px; margin-bottom: 10px; }
      table { border-collapse: collapse; width: 100%; font-size: 12px; }
      th, td { border: 1px solid #ddd; padding: 6px 8px; text-align: left; }
      th { background: #f5f5f5; font-weight: 600; }
      @media print { body { padding: 0; } }
    </style></head><body>`
  html += `<h1>${t(reportName.value)}</h1>`
  html += '<table><thead><tr>'
  for (const col of visibleCols) {
    html += `<th>${t(col.label)}</th>`
  }
  html += '</tr></thead><tbody>'
  for (const row of rows.value) {
    html += '<tr>'
    for (const col of visibleCols) {
      const val = row[col.fieldname] ?? ''
      html += `<td>${val}</td>`
    }
    html += '</tr>'
  }
  html += '</tbody></table></body></html>'

  printWindow.document.write(html)
  printWindow.document.close()
  printWindow.print()
}

async function handlePreparedAction() {
  if (preparedAction.value === 'Edit') {
    preparedReportName.value = null
    ignorePreparedReport.value = false
    await refresh()
    return
  }
  // New or Rebuild
  if (!checkMandatoryFilters()) return
  const filters = getFilterValues()
  try {
    const result = await api.call(
      'jingrow.core.pagetype.prepared_report.prepared_report.make_prepared_report',
      { report_name: reportName.value, filters }
    )
    const data = result?.message || result
    if (data?.name) {
      message.info(t('Report initiated, click to view status'))
    }
  } catch (e: any) {
    message.error(e.message || t('Failed to generate report'))
  }
}

// ─── Cleanup ───
onUnmounted(() => {
  if (abortController) abortController.abort()
  if (filterChangeTimer) clearTimeout(filterChangeTimer)
})
</script>

<style scoped>
.query-report-page {
  padding: 16px 24px;
  height: 100%;
  overflow-y: auto;
}

.page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
}

.page-header h2 {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
}

.filter-section {
  margin-bottom: 12px;
  padding: 10px 12px;
  background: var(--n-color, #fafafa);
  border-radius: 6px;
}

.status-bar {
  padding: 8px 12px;
  margin-bottom: 8px;
  background: #fffbe6;
  border-radius: 4px;
  font-size: 13px;
}

.report-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  padding: 10px 0;
  margin-bottom: 8px;
}

.summary-item {
  font-size: 13px;
}

.summary-label {
  font-weight: 600;
  margin-right: 4px;
}

.chart-section {
  margin-bottom: 12px;
  min-height: 200px;
}

.prepared-actions {
  margin-bottom: 12px;
}

.loading-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 40vh;
  color: #999;
}

.loading-state p {
  margin-top: 12px;
}

.empty-state {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 40vh;
  color: #999;
  font-size: 14px;
}

.report-table-section {
  margin-top: 4px;
}

.report-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 8px 0;
  font-size: 12px;
  color: #999;
}

.footer-hint {
  flex: 1;
}

.footer-time {
  white-space: nowrap;
  margin-left: 12px;
}

:deep(.n-data-table .n-data-table-td) {
  font-size: 13px;
}

:deep(.n-data-table .n-data-table-th) {
  font-size: 13px;
  font-weight: 600;
}

.filter-link-field {
  min-width: 160px;
}

.filter-link-field :deep(.field-wrapper) {
  gap: 0;
}

.filter-link-field :deep(.field-label) {
  display: none;
}

.filter-link-field :deep(.link-field-container) {
  margin: 0;
}
</style>
