<template>
  <section class="andale_search_insights-analytics">
    <header class="andale_search_insights-analytics__header">
      <h1 class="andale_search_insights-analytics__title">{{ labels.title }}</h1>
      <p class="andale_search_insights-analytics__description">{{ labels.description }}</p>
    </header>

    <div class="andale_search_insights-analytics__toolbar">
      <div class="andale_search_insights-analytics__toolbar-primary">
        <label class="andale_search_insights-analytics__field">
          <span>{{ labels.range }}</span>
          <select v-model="draftFilters.days">
            <option value="7">7 days</option>
            <option value="30">30 days</option>
            <option value="90">90 days</option>
            <option value="180">180 days</option>
            <option value="365">365 days</option>
            <option value="all">{{ labels.allTime }}</option>
          </select>
        </label>

        <label class="andale_search_insights-analytics__field andale_search_insights-analytics__field--search">
          <span>{{ labels.search }}</span>
          <input v-model="draftFilters.term" type="search" :placeholder="labels.searchPlaceholder" />
        </label>

        <label class="andale_search_insights-analytics__field">
          <span>{{ labels.resultsMode }}</span>
          <select v-model="draftFilters.results_mode">
            <option value="any">{{ labels.allTerms }}</option>
            <option value="zero_only">{{ labels.zeroOnly }}</option>
            <option value="with_results_only">{{ labels.withResultsOnly }}</option>
          </select>
        </label>

        <label class="andale_search_insights-analytics__field">
          <span>{{ labels.sortBy }}</span>
          <select v-model="draftFilters.sort_by">
            <option value="search_count">{{ labels.sortBySearches }}</option>
            <option value="zero_result_rate">{{ labels.sortByZeroRate }}</option>
            <option value="click_rate">{{ labels.sortByClickRate }}</option>
            <option value="total_results">{{ labels.sortByTotalResults }}</option>
            <option value="term">{{ labels.sortByTerm }}</option>
          </select>
        </label>

        <label class="andale_search_insights-analytics__field">
          <span>{{ labels.sortDirection }}</span>
          <select v-model="draftFilters.sort_dir">
            <option value="desc">{{ labels.sortDesc }}</option>
            <option value="asc">{{ labels.sortAsc }}</option>
          </select>
        </label>
      </div>

      <div class="andale_search_insights-analytics__toolbar-slot" data-si-slot="toolbar.extensions"></div>

      <div class="andale_search_insights-analytics__toolbar-footer">
        <label class="andale_search_insights-analytics__field andale_search_insights-analytics__field--toggle">
          <span>{{ labels.detailMode }}</span>
          <label class="andale_search_insights-analytics__switch-control">
            <input v-model="singleOpenMode" type="checkbox" class="andale_search_insights-analytics__switch-input" />
            <span class="andale_search_insights-analytics__switch-track" aria-hidden="true"></span>
            <span class="andale_search_insights-analytics__switch-text">{{ labels.detailModeSingleOpen }}</span>
          </label>
        </label>

        <div class="andale_search_insights-analytics__actions">
          <UiButton variant="primary" @click="applyFilters">{{ labels.apply }}</UiButton>
          <UiButton variant="default" @click="resetFilters">{{ labels.reset }}</UiButton>
        </div>
      </div>
    </div>

    <div class="andale_search_insights-analytics__meta-row">
      <div class="andale_search_insights-analytics__updated" v-if="lastUpdatedLabel">
        {{ labels.lastUpdated }}: {{ lastUpdatedLabel }}
        <div class="andale_search_insights-analytics__timezone-hint">{{ labels.timesInLocalTimezone }}</div>
      </div>
      <div class="andale_search_insights-analytics__meta-actions">
        <div class="andale_search_insights-analytics__meta-slot" data-si-slot="meta.actions"></div>
        <UiButton
          v-if="enableCsvExport"
          tag="a"
          variant="info"
          size="sm"
          :href="exportHref"
        >
          {{ labels.exportCsv }}
        </UiButton>
        <UiButton variant="info" size="sm" @click="fetchRows">{{ labels.refresh }}</UiButton>
      </div>
    </div>

    <section class="andale_search_insights-analytics__charts" v-if="trendPoints.length">
      <article class="andale_search_insights-analytics__chart-card">
        <div class="andale_search_insights-analytics__chart-head">
          <span>{{ labels.chartSearches }}</span>
          <strong>{{ formatInteger(summary.search_count || 0) }}</strong>
        </div>
        <svg viewBox="0 0 100 28" preserveAspectRatio="none" role="img" :aria-label="labels.chartSearches">
          <path :d="sparklinePath(searchesSeries)" class="andale_search_insights-analytics__sparkline andale_search_insights-analytics__sparkline--searches"></path>
        </svg>
      </article>
      <article class="andale_search_insights-analytics__chart-card">
        <div class="andale_search_insights-analytics__chart-head">
          <span>{{ labels.chartZeroRate }}</span>
          <strong>{{ formatPercent(summaryZeroRate) }}</strong>
        </div>
        <svg viewBox="0 0 100 28" preserveAspectRatio="none" role="img" :aria-label="labels.chartZeroRate">
          <path :d="sparklinePath(zeroRateSeries)" class="andale_search_insights-analytics__sparkline andale_search_insights-analytics__sparkline--zero"></path>
        </svg>
      </article>
      <article class="andale_search_insights-analytics__chart-card">
        <div class="andale_search_insights-analytics__chart-head">
          <span>{{ labels.chartClickRate }}</span>
          <strong>{{ formatPercent(summaryClickRate) }}</strong>
        </div>
        <svg viewBox="0 0 100 28" preserveAspectRatio="none" role="img" :aria-label="labels.chartClickRate">
          <path :d="sparklinePath(clickRateSeries)" class="andale_search_insights-analytics__sparkline andale_search_insights-analytics__sparkline--click"></path>
        </svg>
      </article>
    </section>
    <div v-else-if="!loading && !error" class="andale_search_insights-analytics__charts-empty">
      {{ labels.chartNoData }}
    </div>

    <div v-if="loading" class="andale_search_insights-analytics__state">{{ labels.loading }}</div>
    <div v-else-if="error" class="andale_search_insights-analytics__state andale_search_insights-analytics__state--error">{{ error }}</div>
    <div v-else class="andale_search_insights-analytics__table-wrap">
      <table class="andale_search_insights-analytics__table">
        <thead>
          <tr>
            <th>{{ labels.termCol }}</th>
            <th>{{ labels.searchCountCol }}</th>
            <th>{{ labels.zeroCountCol }}</th>
            <th>{{ labels.zeroRateCol }}</th>
            <th>{{ labels.totalResultsCol }}</th>
            <th>{{ labels.clickCountCol }}</th>
            <th>{{ labels.clickRateCol }}</th>
          </tr>
        </thead>
        <tbody v-if="rows.length === 0">
          <tr>
            <td colspan="7" class="andale_search_insights-analytics__empty">{{ labels.noRows }}</td>
          </tr>
        </tbody>
        <tbody v-else>
          <template v-for="row in rows" :key="row.term">
            <tr>
              <td>
                <strong>{{ row.term }}</strong>
                <div class="andale_search_insights-analytics__row-action">
                  <button
                    type="button"
                    class="button-link"
                    @click="toggleTermDetails(row.term)"
                  >
                    {{ isTermExpanded(row.term) ? labels.hideDetails : labels.viewDetails }}
                  </button>
                </div>
              </td>
              <td>{{ row.search_count }}</td>
              <td>{{ row.zero_result_count }}</td>
              <td>{{ formatZeroRate(row) }}</td>
              <td>{{ row.total_results }}</td>
              <td>{{ row.click_count || 0 }}</td>
              <td>{{ formatClickRate(row) }}</td>
            </tr>
            <tr v-if="isTermExpanded(row.term)" class="andale_search_insights-analytics__details-row">
              <td colspan="7">
                <div v-if="termDetailsLoading[row.term]" class="andale_search_insights-analytics__details-state">
                  {{ labels.detailsLoading }}
                </div>
                <div v-else-if="termDetailsError[row.term]" class="andale_search_insights-analytics__details-state andale_search_insights-analytics__details-state--error">
                  {{ termDetailsError[row.term] }}
                </div>
                <div v-else-if="termDetails[row.term]" class="andale_search_insights-analytics__details-grid">
                  <section class="andale_search_insights-analytics__details-card">
                    <div class="andale_search_insights-analytics__details-header">
                      <h3>{{ labels.detailsRecentTitle }}</h3>
                      <UiButton
                        v-if="enableCsvExport"
                        tag="a"
                        variant="info"
                        size="sm"
                        :href="buildTermDetailsExportHref(row.term)"
                      >
                        {{ labels.detailsExportCsv }}
                      </UiButton>
                    </div>
                    <div class="andale_search_insights-analytics__details-summary">
                      <div class="andale_search_insights-analytics__details-chip">
                        <span>{{ labels.detailsSummarySearches }}</span>
                        <strong>{{ termDetails[row.term].summary.search_count || 0 }}</strong>
                      </div>
                      <div class="andale_search_insights-analytics__details-chip">
                        <span>{{ labels.detailsSummaryZeroRate }}</span>
                        <strong>{{ formatZeroRate(termDetails[row.term].summary || {}) }}</strong>
                      </div>
                      <div class="andale_search_insights-analytics__details-chip">
                        <span>{{ labels.detailsSummaryClickRate }}</span>
                        <strong>{{ formatClickRate(termDetails[row.term].summary || {}) }}</strong>
                      </div>
                    </div>
                    <table v-if="(termDetails[row.term].recent_searches || []).length" class="andale_search_insights-analytics__details-table">
                      <thead>
                        <tr>
                          <th>{{ labels.detailsWhenCol }}</th>
                          <th>{{ labels.detailsResultsCol }}</th>
                          <th>{{ labels.detailsClicksCol }}</th>
                          <th>{{ labels.detailsSourceCol }}</th>
                        </tr>
                      </thead>
                      <tbody>
                        <tr v-for="entry in termDetails[row.term].recent_searches" :key="entry.id">
                          <td>{{ formatDateTime(entry.created_gmt) }}</td>
                          <td>{{ entry.results_count }}</td>
                          <td>{{ entry.click_count || 0 }}</td>
                          <td>{{ entry.source }}</td>
                        </tr>
                      </tbody>
                    </table>
                    <p v-else class="andale_search_insights-analytics__details-empty">{{ labels.detailsNoRecent }}</p>
                  </section>

                  <section class="andale_search_insights-analytics__details-card">
                    <h3>{{ labels.detailsTopProductsTitle }}</h3>
                    <ul v-if="(termDetails[row.term].top_clicked_products || []).length" class="andale_search_insights-analytics__details-products">
                      <li v-for="product in termDetails[row.term].top_clicked_products" :key="product.product_id">
                        <a :href="product.url" target="_blank" rel="noopener noreferrer">
                          {{ product.title }}
                        </a>
                        <span>{{ product.click_count }}</span>
                      </li>
                    </ul>
                    <p v-else class="andale_search_insights-analytics__details-empty">{{ labels.detailsNoProducts }}</p>
                  </section>

                  <section v-if="(termDetails[row.term].pro_result_snapshots || []).length" class="andale_search_insights-analytics__details-card">
                    <div class="andale_search_insights-analytics__details-header">
                      <h3>{{ labels.detailsReturnedResultsTitle }}</h3>
                      <UiButton
                        v-if="termDetails[row.term].pro_export_term_snapshots_url"
                        tag="a"
                        variant="info"
                        size="sm"
                        :href="termDetails[row.term].pro_export_term_snapshots_url"
                      >
                        {{ labels.detailsExportCsv }}
                      </UiButton>
                    </div>
                    <ul class="andale_search_insights-analytics__details-products andale_search_insights-analytics__details-products--ranked">
                      <li v-for="snapshot in termDetails[row.term].pro_result_snapshots" :key="`snapshot-${snapshot.entity_id}`">
                        <a :href="buildEntityEditOrViewUrl(snapshot)" target="_blank" rel="noopener noreferrer">
                          {{ snapshot.title_snapshot || `#${snapshot.entity_id}` }}
                        </a>
                        <span>#{{ snapshot.best_rank }} · {{ snapshot.seen_count }}x</span>
                      </li>
                    </ul>
                  </section>
                </div>
              </td>
            </tr>
          </template>
        </tbody>
      </table>
    </div>

    <footer v-if="!loading && !error && rows.length > 0" class="andale_search_insights-analytics__pagination">
      <label class="andale_search_insights-analytics__rows-per-page">
        <span>{{ labels.rowsPerPage }}</span>
        <select v-model.number="draftFilters.per_page" @change="applyPerPage">
          <option :value="10">10</option>
          <option :value="25">25</option>
          <option :value="50">50</option>
          <option :value="100">100</option>
        </select>
      </label>

      <div class="andale_search_insights-analytics__page-controls">
        <UiButton variant="default" :disabled="pagination.page <= 1" @click="goPrevPage">{{ labels.prev }}</UiButton>
        <span>{{ labels.page }} {{ pagination.page }} {{ labels.of }} {{ pagination.total_pages }}</span>
        <UiButton variant="default" :disabled="pagination.page >= pagination.total_pages" @click="goNextPage">{{ labels.next }}</UiButton>
      </div>
    </footer>
  </section>
</template>

<script>
import { computed, onMounted, reactive, ref } from 'vue';
import UiButton from './ui/UiButton.vue';

export default {
  name: 'AndaleSearchInsightsDashboardApp',
  components: {
    UiButton,
  },
  setup() {
    const root = document.getElementById('andale_search_insights-admin-app');
    const rawConfig = root ? root.getAttribute('data-config') : null;
    const config = rawConfig ? JSON.parse(rawConfig) : {};

    const labels = {
      ...{
        title: 'Andale Search Insights Analytics',
        description: '',
        range: 'Range',
        search: 'Search',
        searchPlaceholder: 'Search term contains…',
        resultsMode: 'Result Mode',
        sortBy: 'Sort By',
        sortDirection: 'Sort Direction',
        sortAsc: 'Ascending',
        sortDesc: 'Descending',
        allTerms: 'All Terms',
        zeroOnly: 'Zero-Results Only',
        withResultsOnly: 'With Results Only',
        sortBySearches: 'Searches',
        sortByZeroRate: 'Zero-Result Rate',
        sortByClickRate: 'Click-Through Rate',
        sortByTotalResults: 'Total Results',
        sortByTerm: 'Term (A-Z)',
        preset: 'Preset',
        presetPlaceholder: 'Select preset…',
        presetApply: 'Apply Preset',
        detailMode: 'Details Mode',
        detailModeSingleOpen: 'Single-open mode',
        apply: 'Apply',
        reset: 'Reset',
        refresh: 'Refresh',
        loading: 'Loading analytics…',
        loadFailed: 'Unable to load analytics.',
        noRows: 'No data available for this filter combination yet.',
        lastUpdated: 'Last updated',
        rowsPerPage: 'Rows per page',
        prev: 'Previous',
        next: 'Next',
        page: 'Page',
        of: 'of',
        exportCsv: 'Export Filtered CSV',
        termCol: 'Term',
        searchCountCol: 'Searches',
        zeroCountCol: 'Zero-Result Searches',
        zeroRateCol: 'Zero-Result Rate',
        totalResultsCol: 'Total Results Returned',
        clickCountCol: 'Result Clicks',
        clickRateCol: 'Click-Through Rate',
        chartSearches: 'Searches',
        chartZeroRate: 'Zero-Result Rate',
        chartClickRate: 'Click-Through Rate',
        chartNoData: 'No trend data yet for these filters.',
        viewDetails: 'View details',
        hideDetails: 'Hide details',
        detailsLoading: 'Loading term details…',
        detailsLoadFailed: 'Unable to load term details.',
        detailsRecentTitle: 'Recent Searches',
        detailsTopProductsTitle: 'Top Clicked Products',
        detailsNoRecent: 'No recent searches found for this term and filter.',
        detailsNoProducts: 'No clicked products found yet for this term.',
        detailsReturnedResultsTitle: 'Returned Results (Snapshot)',
        detailsWhenCol: 'When',
        detailsResultsCol: 'Results',
        detailsClicksCol: 'Clicks',
        detailsSourceCol: 'Source',
        detailsExportCsv: 'Export Term Details CSV',
        timesInLocalTimezone: 'Times are shown in your local timezone (source data stored in UTC).',
      },
      ...(config.labels || {}),
    };

    const defaultsFromConfig = config.defaultFilters && typeof config.defaultFilters === 'object' ? config.defaultFilters : {};
    const initialFilters = {
      days: '30',
      term: '',
      results_mode: 'any',
      sort_by: 'search_count',
      sort_dir: 'desc',
      page: 1,
      per_page: 25,
      ...defaultsFromConfig,
    };

    const draftFilters = reactive({ ...initialFilters });
    const activeFilters = reactive({ ...initialFilters });
    const rows = ref([]);
    const loading = ref(false);
    const error = ref('');
    const lastUpdated = ref('');
    const expandedTerms = ref([]);
    const singleOpenMode = ref(true);
    const termDetails = reactive({});
    const termDetailsLoading = reactive({});
    const termDetailsError = reactive({});
    const pagination = reactive({
      page: 1,
      per_page: 25,
      total: 0,
      total_pages: 1,
    });
    const summary = reactive({
      search_count: 0,
      zero_result_count: 0,
      total_results: 0,
      click_count: 0,
    });
    const trendPoints = ref([]);

    const ajaxUrl = computed(() => config.ajaxUrl || '');
    const nonce = computed(() => config.nonce || '');
    const exportBaseUrl = computed(() => config.exportBaseUrl || '');
    const enableCsvExport = computed(() => !!config.enableCsvExport);
    const summaryZeroRate = computed(() => {
      const searches = Number(summary.search_count || 0);
      const zero = Number(summary.zero_result_count || 0);
      return searches > 0 ? (zero / searches) * 100 : 0;
    });
    const summaryClickRate = computed(() => {
      const searches = Number(summary.search_count || 0);
      const clicks = Number(summary.click_count || 0);
      return searches > 0 ? (clicks / searches) * 100 : 0;
    });
    const searchesSeries = computed(() => trendPoints.value.map((point) => Number(point.search_count || 0)));
    const zeroRateSeries = computed(() => trendPoints.value.map((point) => Number(point.zero_result_rate || 0)));
    const clickRateSeries = computed(() => trendPoints.value.map((point) => Number(point.click_rate || 0)));

    const lastUpdatedLabel = computed(() => {
      if (!lastUpdated.value) {
        return '';
      }
      const maybeIso = `${String(lastUpdated.value).replace(' ', 'T')}Z`;
      const value = new Date(maybeIso);
      if (Number.isNaN(value.getTime())) {
        return String(lastUpdated.value);
      }
      return value.toLocaleString(undefined, { timeZoneName: 'short' });
    });

    const exportHref = computed(() => {
      if (!exportBaseUrl.value) {
        return '#';
      }
      const url = new URL(exportBaseUrl.value, window.location.origin);
      url.searchParams.set('dataset', 'terms');
      url.searchParams.set('days', String(activeFilters.days));
      url.searchParams.set('term', activeFilters.term);
      url.searchParams.set('results_mode', activeFilters.results_mode);
      url.searchParams.set('sort_by', String(activeFilters.sort_by || 'search_count'));
      url.searchParams.set('sort_dir', String(activeFilters.sort_dir || 'desc'));
      return url.toString();
    });

    const buildTermDetailsExportHref = (term) => {
      if (!exportBaseUrl.value || !term) {
        return '#';
      }
      const url = new URL(exportBaseUrl.value, window.location.origin);
      url.searchParams.set('dataset', 'term_details');
      url.searchParams.set('term', String(term));
      url.searchParams.set('days', String(activeFilters.days));
      url.searchParams.set('results_mode', String(activeFilters.results_mode || 'any'));
      url.searchParams.set('sort_by', String(activeFilters.sort_by || 'search_count'));
      url.searchParams.set('sort_dir', String(activeFilters.sort_dir || 'desc'));
      return url.toString();
    };

    const buildEntityEditOrViewUrl = (snapshot) => {
      if (snapshot?.entity_url) {
        return snapshot.entity_url;
      }
      const id = Number(snapshot?.entity_id || 0);
      if (!id) {
        return '#';
      }
      return '#';
    };

    const postAjax = async (action, payload = {}) => {
      const body = new URLSearchParams();
      body.append('action', action);
      body.append('nonce', nonce.value);

      Object.keys(payload).forEach((key) => {
        body.append(key, payload[key]);
      });

      const response = await fetch(ajaxUrl.value, {
        method: 'POST',
        credentials: 'same-origin',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
        },
        body,
      });

      const data = await response.json();
      if (!response.ok || !data?.success) {
        throw new Error(data?.data?.message || labels.loadFailed);
      }

      return data.data;
    };

    const fetchRows = async () => {
      loading.value = true;
      error.value = '';

      try {
        const payload = await postAjax('andale_search_insights_dashboard_data', {
          days: String(activeFilters.days),
          term: activeFilters.term,
          results_mode: activeFilters.results_mode,
          sort_by: String(activeFilters.sort_by || 'search_count'),
          sort_dir: String(activeFilters.sort_dir || 'desc'),
          page: String(activeFilters.page),
          per_page: String(activeFilters.per_page),
        });

        rows.value = payload.rows || [];
        pagination.page = Number(payload.pagination?.page || 1);
        pagination.per_page = Number(payload.pagination?.per_page || activeFilters.per_page);
        pagination.total = Number(payload.pagination?.total || 0);
        pagination.total_pages = Number(payload.pagination?.total_pages || 1);
        summary.search_count = Number(payload.summary?.search_count || 0);
        summary.zero_result_count = Number(payload.summary?.zero_result_count || 0);
        summary.total_results = Number(payload.summary?.total_results || 0);
        summary.click_count = Number(payload.summary?.click_count || 0);
        trendPoints.value = Array.isArray(payload.trend_points) ? payload.trend_points : [];
        lastUpdated.value = payload.last_updated_gmt || payload.last_updated || '';
      } catch (err) {
        error.value = err.message || labels.loadFailed;
        trendPoints.value = [];
      } finally {
        loading.value = false;
      }
    };

    const applyFilters = () => {
      activeFilters.days = String(draftFilters.days || '30');
      activeFilters.term = String(draftFilters.term || '');
      activeFilters.results_mode = String(draftFilters.results_mode || 'any');
      activeFilters.sort_by = String(draftFilters.sort_by || 'search_count');
      activeFilters.sort_dir = String(draftFilters.sort_dir || 'desc');
      activeFilters.page = 1;
      resetDetailsState();
      fetchRows();
    };

    const resetFilters = () => {
      Object.assign(draftFilters, initialFilters);
      Object.assign(activeFilters, initialFilters);
      resetDetailsState();
      fetchRows();
    };

    const applyPerPage = () => {
      activeFilters.per_page = Number(draftFilters.per_page || 25);
      activeFilters.page = 1;
      resetDetailsState();
      fetchRows();
    };

    const goPrevPage = () => {
      if (pagination.page <= 1) {
        return;
      }
      activeFilters.page = pagination.page - 1;
      resetDetailsState();
      fetchRows();
    };

    const goNextPage = () => {
      if (pagination.page >= pagination.total_pages) {
        return;
      }
      activeFilters.page = pagination.page + 1;
      resetDetailsState();
      fetchRows();
    };

    const formatZeroRate = (row) => {
      const searches = Number(row.search_count || 0);
      const zero = Number(row.zero_result_count || 0);
      if (searches <= 0) {
        return '0%';
      }
      return `${((zero / searches) * 100).toFixed(1)}%`;
    };

    const formatClickRate = (row) => {
      const searches = Number(row.search_count || 0);
      const clicks = Number(row.click_count || 0);
      if (searches <= 0) {
        return '0%';
      }
      return `${((clicks / searches) * 100).toFixed(1)}%`;
    };
    const formatPercent = (value) => `${Number(value || 0).toFixed(1)}%`;
    const formatInteger = (value) => Number(value || 0).toLocaleString();

    const sparklinePath = (values) => {
      if (!Array.isArray(values) || values.length === 0) {
        return '';
      }
      if (values.length === 1) {
        const y = 14;
        return `M0 ${y} L100 ${y}`;
      }
      const max = Math.max(...values);
      const min = Math.min(...values);
      const range = max - min || 1;
      const step = 100 / (values.length - 1);
      return values
        .map((value, index) => {
          const x = step * index;
          const y = 26 - ((value - min) / range) * 24;
          return `${index === 0 ? 'M' : 'L'}${x.toFixed(2)} ${y.toFixed(2)}`;
        })
        .join(' ');
    };

    const formatDateTime = (rawDate) => {
      if (!rawDate) {
        return '';
      }
      const maybeIso = `${String(rawDate).replace(' ', 'T')}Z`;
      const value = new Date(maybeIso);
      if (Number.isNaN(value.getTime())) {
        return String(rawDate);
      }
      return value.toLocaleString(undefined, { timeZoneName: 'short' });
    };

    const resetDetailsState = () => {
      expandedTerms.value = [];
      Object.keys(termDetails).forEach((key) => delete termDetails[key]);
      Object.keys(termDetailsLoading).forEach((key) => delete termDetailsLoading[key]);
      Object.keys(termDetailsError).forEach((key) => delete termDetailsError[key]);
    };

    const isTermExpanded = (term) => expandedTerms.value.includes(term);

    const loadTermDetails = async (term) => {
      termDetailsLoading[term] = true;
      termDetailsError[term] = '';
      try {
        const payload = await postAjax('andale_search_insights_dashboard_term_details', {
          term,
          days: String(activeFilters.days),
          results_mode: String(activeFilters.results_mode || 'any'),
        });
        termDetails[term] = payload.details || null;
      } catch (err) {
        termDetailsError[term] = err.message || labels.detailsLoadFailed;
      } finally {
        termDetailsLoading[term] = false;
      }
    };

    const toggleTermDetails = async (term) => {
      if (!term) {
        return;
      }
      if (isTermExpanded(term)) {
        expandedTerms.value = expandedTerms.value.filter((entry) => entry !== term);
        return;
      }
      if (singleOpenMode.value) {
        expandedTerms.value = [term];
      } else {
        expandedTerms.value = [...expandedTerms.value, term];
      }
      if (!termDetails[term]) {
        await loadTermDetails(term);
      }
    };

    const getDraftFilters = () => ({ ...draftFilters });
    const getActiveFilters = () => ({ ...activeFilters });
    const setDraftFilters = (patch) => {
      if (!patch || typeof patch !== 'object') {
        return;
      }
      Object.assign(draftFilters, patch);
    };
    const setActiveFilters = (patch) => {
      if (!patch || typeof patch !== 'object') {
        return;
      }
      Object.assign(activeFilters, patch);
    };

    onMounted(() => {
      if (!ajaxUrl.value || !nonce.value) {
        error.value = labels.loadFailed;
        return;
      }
      fetchRows();
    });

    return {
      labels,
      rows,
      loading,
      error,
      lastUpdatedLabel,
      draftFilters,
      pagination,
      summary,
      trendPoints,
      summaryZeroRate,
      summaryClickRate,
      searchesSeries,
      zeroRateSeries,
      clickRateSeries,
      enableCsvExport,
      exportHref,
      buildTermDetailsExportHref,
      buildEntityEditOrViewUrl,
      fetchRows,
      applyFilters,
      resetFilters,
      applyPerPage,
      goPrevPage,
      goNextPage,
      getDraftFilters,
      getActiveFilters,
      setDraftFilters,
      setActiveFilters,
      formatZeroRate,
      formatClickRate,
      formatPercent,
      formatInteger,
      sparklinePath,
      formatDateTime,
      termDetails,
      termDetailsLoading,
      termDetailsError,
      singleOpenMode,
      toggleTermDetails,
      isTermExpanded,
    };
  },
};
</script>
