(function($) {
  'use strict';
  var D = window.ElsDash;
  var H = D.helpers;
  var CONFIG = D.CONFIG;

  // ============================================================
  // GLOBAL STATE
  // ============================================================
  D.allApps = [];
  D.filteredApps = [];
  D.overviewMap = null;
  D.overviewCharts = {};

  // ============================================================
  // INITIALIZATION
  // ============================================================
  function init(rawData, fromCSV) {
    if (typeof DATA_FORMAT !== 'undefined' && DATA_FORMAT === 'grouped') {
      D.allApps = rawData.map(function(a) {
        a.receiptDate = H.parseDate(a.receiptDate);
        a.statusDate = H.parseDate(a.statusDate);
        (a.charges || []).forEach(function(c) {
          c.openDate = H.parseDate(c.openDate);
          c.closeDate = H.parseDate(c.closeDate);
        });
        if (a.experimentTypeDescription && !a.experimentType) a.experimentType = a.experimentTypeDescription;
        a.assigned = !!a.assigned;
        a.callsign = a.callsign || '';
        a.frequencies = a.frequencies || [];
        a.fixedCoords = a.fixedCoords || [];
        a.mobileCoords = a.mobileCoords || [];
        a.applicationUrl = a.applicationUrl || '';
        a.exhibitsUrl = a.exhibitsUrl || '';
        a.correspondenceUrl = a.correspondenceUrl || '';
        a.rows = [];
        return a;
      });
    } else {
      D.allApps = D.processData(rawData);
    }
    D.allApps.forEach(function(a) {
      if (a.status === CONFIG.STATUS.PENDING) {
        if (a.charges.length > 0) {
          a.status = CONFIG.STATUS.REFERRED;
        } else if (a.assigned) {
          a.status = CONFIG.STATUS.ASSIGNED;
        } else {
          a.status = CONFIG.STATUS.SUBMITTED;
        }
      }
    });
    D.filteredApps = D.allApps.slice();
    var dateStr = (typeof DATA_DATE !== 'undefined') ? DATA_DATE : new Date().toLocaleDateString();
    $('#data-date').text('Data: ' + dateStr);
    if (fromCSV) $('#btn-save-datajs').show();
    D.showDiagnostics(rawData, fromCSV);
    initFilters();
    applyFilters();
    D.initDrilldown();
    D.initReferrals();
    D.initFlags();
    D.updateRenewals();
    D.updateTransfers();
    $('#btn-export-friday').on('click', D.exportFridayCSV);
    $('#btn-print-friday').on('click', function() {
      $('#fr-print-date').text($('#fr-date-range').text());
      $('body').addClass('print-friday');
      window.print();
      $('body').removeClass('print-friday');
    });
    $('#btn-print-overview').on('click', function() {
      $('#print-data-date').text($('#data-date').text());
      $('body').addClass('print-overview');
      window.print();
      $('body').removeClass('print-overview');
    });

    var $sidebar = $('#global-filter-sidebar');
    var $mainCol = $sidebar.siblings('.col-md-9');
    $('a[data-toggle="tab"]').on('shown.bs.tab', function(e) {
      var target = $(e.target).attr('href');
      if (target === '#overview' || target === '#analysis') {
        $sidebar.show();
        $mainCol.removeClass('col-md-12 col-sm-12').addClass('col-md-9 col-sm-8');
      } else {
        $sidebar.hide();
        $mainCol.removeClass('col-md-9 col-sm-8').addClass('col-md-12 col-sm-12');
      }
      if (target === '#overview') {
        setTimeout(function() { if (D.overviewMap) D.overviewMap.invalidateSize(); }, 200);
      }
      if (target === '#analysis') {
        D.updateAnalysis();
      }
      if (target === '#transfers') {
        D.updateTransfers();
      }
      if (target === '#friday-report') {
        D.updateFridayReport();
      }
    });
  }

  // ============================================================
  // FILTERS
  // ============================================================
  function initFilters() {
    // Populate experiment type dropdown
    var types = {};
    D.allApps.forEach(function(app) { if (app.experimentType) types[app.experimentType] = true; });
    var $sel = $('#filter-experiment-type');
    Object.keys(types).sort().forEach(function(t) {
      $sel.append('<option value="' + t + '">' + t + '</option>');
    });

    // Populate charge entity checkboxes
    var chargeEntities = {};
    D.allApps.forEach(function(app) {
      app.charges.forEach(function(c) { if (c.charge) chargeEntities[c.charge] = true; });
    });
    var $chargeList = $('#filter-charge-list');
    Object.keys(chargeEntities).sort().forEach(function(e) {
      $chargeList.append('<div class="checkbox"><label><input type="checkbox" class="filter-charge" value="' + e + '" checked> ' + e + '</label></div>');
    });

    // Default date range: one year ago (or earliest if data is newer) to today
    var today = new Date();
    var todayStr = today.toISOString().slice(0, 10);
    var oneYearAgo = new Date(today.getFullYear() - 1, today.getMonth(), today.getDate());
    var earliest = today;
    D.allApps.forEach(function(app) {
      if (app.receiptDate && app.receiptDate < earliest) earliest = app.receiptDate;
    });
    var defaultFrom = earliest > oneYearAgo ? earliest : oneYearAgo;
    var defaultFromStr = defaultFrom.toISOString().slice(0, 10);
    $('#filter-date-from').val(defaultFromStr);
    $('#filter-date-to').val(todayStr);

    // Bind filter events
    $('#filter-date-from, #filter-date-to').on('change', applyFilters);
    $('.filter-status').on('change', applyFilters);
    $('#filter-experiment-type').on('change', applyFilters);
    $('#filter-applicant').on('input', H.debounce(applyFilters, 300));
    $('.filter-charge').on('change', applyFilters);
    $('#btn-reset-filters').on('click', function() {
      $('#filter-date-from').val(defaultFromStr);
      $('#filter-date-to').val(todayStr);
      $('.filter-status').prop('checked', true);
      $('#filter-experiment-type').val('');
      $('#filter-applicant').val('');
      $('.filter-charge').prop('checked', true);
      applyFilters();
    });

    $('#btn-export-filtered').on('click', exportFilteredCSV);

    $('#period-selector').on('change', function() {
      updateKPIs();
      updateCharts();
    });
    $('#analysis-period-selector').on('change', function() {
      D.updateAnalysis();
    });

    initPresets();
  }

  function applyFilters() {
    var dateFrom = H.parseDate($('#filter-date-from').val());
    var dateTo = H.parseDate($('#filter-date-to').val());
    var statuses = [];
    $('.filter-status:checked').each(function() { statuses.push($(this).val()); });
    var expType = $('#filter-experiment-type').val();
    var applicantQuery = ($('#filter-applicant').val() || '').toLowerCase();
    var chargeFilters = [];
    $('.filter-charge:checked').each(function() { chargeFilters.push($(this).val()); });
    var allChargesChecked = chargeFilters.length === $('.filter-charge').length;

    D.filteredApps = D.allApps.filter(function(app) {
      if (dateFrom && app.receiptDate && app.receiptDate < dateFrom) return false;
      if (dateTo && app.receiptDate && app.receiptDate > dateTo) return false;
      if (statuses.length && statuses.indexOf(app.status) === -1) return false;
      if (expType && app.experimentType !== expType) return false;
      if (applicantQuery && app.name.toLowerCase().indexOf(applicantQuery) === -1) return false;
      if (!allChargesChecked) {
        var hasMatchingCharge = app.charges.some(function(c) { return chargeFilters.indexOf(c.charge) !== -1; });
        if (app.charges.length > 0 && !hasMatchingCharge) return false;
      }
      return true;
    });

    updateKPIs();
    updateCharts();
    updateMap();
    if ($('#analysis').hasClass('active')) D.updateAnalysis();
  }

  // ============================================================
  // PRESETS
  // ============================================================
  var PRESET_KEY = 'els-dash-presets';
  var MAX_PRESETS = 20;

  function getPresets() {
    try {
      return JSON.parse(localStorage.getItem(PRESET_KEY)) || [];
    } catch (e) { return []; }
  }

  function savePresetsToStorage(presets) {
    localStorage.setItem(PRESET_KEY, JSON.stringify(presets));
  }

  function populatePresetDropdown() {
    var $sel = $('#preset-select');
    $sel.find('option:not(:first)').remove();
    getPresets().forEach(function(p) {
      $sel.append($('<option>').val(p.name).text(p.name));
    });
  }

  function captureFilterState() {
    var statuses = [];
    $('.filter-status:checked').each(function() { statuses.push($(this).val()); });
    var chargeEntities = [];
    $('.filter-charge:checked').each(function() { chargeEntities.push($(this).val()); });
    return {
      dateFrom: $('#filter-date-from').val(),
      dateTo: $('#filter-date-to').val(),
      statuses: statuses,
      experimentType: $('#filter-experiment-type').val(),
      applicant: $('#filter-applicant').val(),
      chargeEntities: chargeEntities
    };
  }

  function applyPreset(preset) {
    $('#filter-date-from').val(preset.dateFrom || '');
    $('#filter-date-to').val(preset.dateTo || '');
    $('.filter-status').each(function() {
      $(this).prop('checked', preset.statuses.indexOf($(this).val()) !== -1);
    });
    $('#filter-experiment-type').val(preset.experimentType || '');
    $('#filter-applicant').val(preset.applicant || '');
    if (preset.chargeEntities && preset.chargeEntities.length) {
      $('.filter-charge').each(function() {
        $(this).prop('checked', preset.chargeEntities.indexOf($(this).val()) !== -1);
      });
    }
    applyFilters();
  }

  function initPresets() {
    populatePresetDropdown();

    $('#preset-select').on('change', function() {
      var name = $(this).val();
      if (!name) {
        $('#btn-preset-delete').hide();
        return;
      }
      $('#btn-preset-delete').show();
      var presets = getPresets();
      var match = presets.filter(function(p) { return p.name === name; })[0];
      if (match) applyPreset(match);
      $(this).val('');
      $('#btn-preset-delete').hide();
    });

    $('#btn-preset-save').on('click', function() {
      var $row = $('#preset-name-row');
      if ($row.is(':visible')) {
        $row.hide();
        return;
      }
      $row.show();
      $('#preset-name-input').val('').focus();
    });

    function doSave() {
      var name = $('#preset-name-input').val().trim();
      if (!name) return;
      var presets = getPresets();
      var state = captureFilterState();
      state.name = name;
      var existing = -1;
      presets.forEach(function(p, i) { if (p.name === name) existing = i; });
      if (existing !== -1) {
        presets[existing] = state;
      } else {
        presets.push(state);
        while (presets.length > MAX_PRESETS) presets.shift();
      }
      savePresetsToStorage(presets);
      populatePresetDropdown();
      $('#preset-name-row').hide();
    }

    $('#preset-name-input').on('keypress', function(e) {
      if (e.which === 13) doSave();
    });
    $('#preset-name-input').on('blur', function() {
      if ($(this).val().trim()) doSave();
      else $('#preset-name-row').hide();
    });

    $('#btn-preset-delete').on('click', function() {
      var name = $('#preset-select').val();
      if (!name) return;
      var presets = getPresets().filter(function(p) { return p.name !== name; });
      savePresetsToStorage(presets);
      populatePresetDropdown();
      $('#btn-preset-delete').hide();
    });
  }

  // ============================================================
  // KPI COMPUTATION
  // ============================================================
  function updateKPIs() {
    var now = new Date();
    var period = H.getReportPeriod();
    var thisPeriod = H.periodKey(now, period);

    var pending = D.filteredApps.filter(function(a) { return H.isPending(a.status); });
    var newThisMonth = D.filteredApps.filter(function(a) { return a.receiptDate && H.periodKey(a.receiptDate, period) === thisPeriod; });
    var completedThisMonth = D.filteredApps.filter(function(a) {
      return !H.isPending(a.status) && a.statusDate && H.periodKey(a.statusDate, period) === thisPeriod;
    });

    // Median days pending
    var pendingDays = [];
    pending.forEach(function(a) {
      if (a.receiptDate) pendingDays.push(H.daysBetween(a.receiptDate, now));
    });
    var avgDays = H.median(pendingDays);

    // Open referrals
    var openReferrals = 0;
    D.filteredApps.forEach(function(a) {
      a.charges.forEach(function(c) {
        if (c.openDate && !c.closeDate) openReferrals++;
      });
    });

    // Secondary KPIs
    var oldestDays = 0;
    pending.forEach(function(a) {
      if (a.receiptDate) {
        var age = H.daysBetween(a.receiptDate, now);
        if (age > oldestDays) oldestDays = age;
      }
    });

    var closedCharges = [];
    D.filteredApps.forEach(function(a) {
      a.charges.forEach(function(c) {
        if (c.openDate && c.closeDate) {
          closedCharges.push(H.daysBetween(c.openDate, c.closeDate));
        }
      });
    });
    var avgTurnaround = H.median(closedCharges);

    var nonPending = D.filteredApps.filter(function(a) { return !H.isPending(a.status); });
    var granted = nonPending.filter(function(a) { return a.status === CONFIG.STATUS.GRANTED; });
    var grantRate = nonPending.length ? Math.round(granted.length / nonPending.length * 100) : 0;

    var over90 = pending.filter(function(a) {
      return a.receiptDate && H.daysBetween(a.receiptDate, now) > 90;
    }).length;

    var submittedCount = pending.filter(function(a) { return a.status === CONFIG.STATUS.SUBMITTED; }).length;
    var assignedCount = pending.filter(function(a) { return a.status === CONFIG.STATUS.ASSIGNED; }).length;
    var referredCount = pending.filter(function(a) { return a.status === CONFIG.STATUS.REFERRED; }).length;

    // Update DOM
    $('#kpi-total-pending').text(pending.length);
    $('#kpi-submitted-count').text(submittedCount);
    $('#kpi-assigned-count').text(assignedCount);
    $('#kpi-referred-count').text(referredCount);
    var periodName = period === 'weekly' ? 'Week' : period === 'quarterly' ? 'Quarter' : 'Month';
    $('#kpi-new-label').text('New This ' + periodName);
    $('#kpi-completed-label').text('Completed This ' + periodName);
    $('#kpi-new-month').text(newThisMonth.length);
    $('#kpi-completed-month').text(completedThisMonth.length);
    $('#kpi-avg-days').text(avgDays);
    $('#kpi-open-referrals').text(openReferrals);
    $('#kpi-oldest').text(oldestDays);
    $('#kpi-avg-turnaround').text(avgTurnaround + 'd');
    $('#kpi-grant-rate').text(grantRate + '%');
    $('#kpi-over-90').text(over90);

    var $strip = $('#kpi-age-strip');
    $strip.empty();
    if (pending.length > 0) {
      var now = new Date();
      var ageCounts = CONFIG.AGING_BUCKETS.map(function() { return 0; });
      pending.forEach(function(a) {
        if (!a.receiptDate) return;
        var age = H.daysBetween(a.receiptDate, now);
        for (var i = 0; i < CONFIG.AGING_BUCKETS.length; i++) {
          if (age >= CONFIG.AGING_BUCKETS[i].min && age < CONFIG.AGING_BUCKETS[i].max) {
            ageCounts[i]++;
            break;
          }
        }
      });
      CONFIG.AGING_BUCKETS.forEach(function(bucket, i) {
        if (ageCounts[i] === 0) return;
        var pct = Math.round(ageCounts[i] / pending.length * 100);
        $strip.append('<div style="width:' + pct + '%;background:' + bucket.color + ';height:100%" title="' + bucket.label + ': ' + ageCounts[i] + ' apps"></div>');
      });
    }
  }

  // ============================================================
  // CHARTS
  // ============================================================
  function updateCharts() {
    updateOverTimeChart();
    updateStatusChart();
    updateAgingChart();
  }

  function updateOverTimeChart() {
    var period = H.getReportPeriod();
    var buckets = {};
    D.filteredApps.forEach(function(app) {
      if (app.receiptDate) {
        var mk = H.periodKey(app.receiptDate, period);
        if (!buckets[mk]) buckets[mk] = { received: 0, completed: 0 };
        buckets[mk].received++;
      }
      if (!H.isPending(app.status) && app.statusDate) {
        var mk2 = H.periodKey(app.statusDate, period);
        if (!buckets[mk2]) buckets[mk2] = { received: 0, completed: 0 };
        buckets[mk2].completed++;
      }
    });

    var keys = Object.keys(buckets).sort();
    var labels = keys.map(function(k) { return H.periodLabel(k); });

    // Running total pending: start from apps received before the chart window
    var pendingBefore = 0;
    D.filteredApps.forEach(function(app) {
      var rk = app.receiptDate ? H.periodKey(app.receiptDate, period) : null;
      var ck = (!H.isPending(app.status) && app.statusDate) ? H.periodKey(app.statusDate, period) : null;
      if (rk && rk < keys[0]) pendingBefore++;
      if (ck && ck < keys[0]) pendingBefore--;
    });
    var pendingData = [];
    var running = pendingBefore;
    keys.forEach(function(k) {
      running += buckets[k].received - buckets[k].completed;
      pendingData.push(running);
    });

    if (D.overviewCharts.overTime) D.overviewCharts.overTime.destroy();
    D.overviewCharts.overTime = new Chart(document.getElementById('chart-over-time'), {
      type: 'bar',
      data: {
        labels: labels,
        datasets: [
          {
            label: 'Received',
            data: keys.map(function(k) { return buckets[k].received; }),
            backgroundColor: CONFIG.COLORS.PRIMARY + '99',
            borderColor: CONFIG.COLORS.PRIMARY,
            borderWidth: 1,
            order: 3,
            yAxisID: 'y'
          },
          {
            label: 'Completed',
            data: keys.map(function(k) { return buckets[k].completed; }),
            type: 'line',
            borderColor: CONFIG.COLORS.SUCCESS,
            backgroundColor: CONFIG.COLORS.SUCCESS + '33',
            borderWidth: 2,
            fill: false,
            tension: 0.3,
            pointRadius: 4,
            order: 2,
            yAxisID: 'y'
          },
          {
            label: 'Total Pending',
            data: pendingData,
            type: 'line',
            borderColor: '#e86e00',
            backgroundColor: '#e86e0022',
            borderWidth: 2,
            fill: true,
            tension: 0.3,
            pointRadius: 3,
            borderDash: [5, 3],
            order: 1,
            yAxisID: 'y2'
          }
        ]
      },
      options: {
        responsive: true,
        maintainAspectRatio: false,
        animation: D.filteredApps.length > 500 ? false : undefined,
        plugins: { legend: { position: 'bottom' } },
        scales: {
          y: { beginAtZero: true, ticks: { stepSize: 1 }, position: 'left', title: { display: true, text: 'Per Period' } },
          y2: { beginAtZero: true, position: 'right', grid: { drawOnChartArea: false }, title: { display: true, text: 'Pending' } }
        }
      }
    });
  }

  function updateStatusChart() {
    var counts = {};
    counts[CONFIG.STATUS.SUBMITTED] = 0;
    counts[CONFIG.STATUS.ASSIGNED] = 0;
    counts[CONFIG.STATUS.REFERRED] = 0;
    counts[CONFIG.STATUS.GRANTED] = 0;
    counts[CONFIG.STATUS.DENIED] = 0;
    counts[CONFIG.STATUS.EXPIRED] = 0;
    D.filteredApps.forEach(function(app) {
      if (counts[app.status] !== undefined) counts[app.status]++;
    });

    if (D.overviewCharts.status) D.overviewCharts.status.destroy();
    D.overviewCharts.status = new Chart(document.getElementById('chart-status'), {
      type: 'doughnut',
      data: {
        labels: [CONFIG.STATUS.SUBMITTED, CONFIG.STATUS.ASSIGNED, CONFIG.STATUS.REFERRED, CONFIG.STATUS.GRANTED, CONFIG.STATUS.DENIED, CONFIG.STATUS.EXPIRED],
        datasets: [{
          data: [counts[CONFIG.STATUS.SUBMITTED], counts[CONFIG.STATUS.ASSIGNED], counts[CONFIG.STATUS.REFERRED], counts[CONFIG.STATUS.GRANTED], counts[CONFIG.STATUS.DENIED], counts[CONFIG.STATUS.EXPIRED]],
          backgroundColor: ['#4a90d9', '#2f69b2', CONFIG.COLORS.PRIMARY, CONFIG.COLORS.SUCCESS, CONFIG.COLORS.DANGER, CONFIG.COLORS.NEUTRAL]
        }]
      },
      options: {
        responsive: true,
        maintainAspectRatio: false,
        animation: D.filteredApps.length > 500 ? false : undefined,
        plugins: { legend: { position: 'bottom' } }
      }
    });
  }

  function updateAgingChart() {
    var now = new Date();
    var buckets = CONFIG.AGING_BUCKETS.map(function() { return 0; });
    D.filteredApps.forEach(function(app) {
      if (!H.isPending(app.status) || !app.receiptDate) return;
      var age = H.daysBetween(app.receiptDate, now);
      for (var i = 0; i < CONFIG.AGING_BUCKETS.length; i++) {
        if (age >= CONFIG.AGING_BUCKETS[i].min && age < CONFIG.AGING_BUCKETS[i].max) {
          buckets[i]++;
          break;
        }
      }
    });

    if (D.overviewCharts.aging) D.overviewCharts.aging.destroy();
    D.overviewCharts.aging = new Chart(document.getElementById('chart-aging'), {
      type: 'bar',
      data: {
        labels: CONFIG.AGING_BUCKETS.map(function(b) { return b.label; }),
        datasets: [{
          label: 'Pending Applications',
          data: buckets,
          backgroundColor: CONFIG.AGING_BUCKETS.map(function(b) { return b.color; })
        }]
      },
      options: {
        responsive: true,
        maintainAspectRatio: false,
        animation: D.filteredApps.length > 500 ? false : undefined,
        indexAxis: 'y',
        plugins: { legend: { display: false } },
        scales: {
          x: { beginAtZero: true, ticks: { stepSize: 1 } }
        }
      }
    });
  }

  // ============================================================
  // MAP
  // ============================================================
  function updateMap() {
    if (!D.overviewMap) {
      D.overviewMap = L.map('overview-map', { preferCanvas: true }).setView([39.8283, -98.5795], 4);
      L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
        attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/">CARTO</a>',
        maxZoom: 19,
        subdomains: 'abcd'
      }).addTo(D.overviewMap);
      D.overviewMap._fixedLayer = L.layerGroup().addTo(D.overviewMap);
      D.overviewMap._mobileLayer = L.layerGroup().addTo(D.overviewMap);
      L.control.layers(null, {
        'Fixed Locations': D.overviewMap._fixedLayer,
        'Mobile Locations': D.overviewMap._mobileLayer
      }, { collapsed: false }).addTo(D.overviewMap);
    }

    D.overviewMap._fixedLayer.clearLayers();
    D.overviewMap._mobileLayer.clearLayers();

    var bounds = [];

    D.filteredApps.forEach(function(app) {
      var seenFixed = {};
      app.fixedCoords.forEach(function(c) {
        var key = c.lat.toFixed(5) + ',' + c.lon.toFixed(5);
        if (seenFixed[key]) return;
        seenFixed[key] = true;
        var marker = L.circleMarker([c.lat, c.lon], {
          radius: 6, fillColor: CONFIG.COLORS.PRIMARY, fillOpacity: 0.8,
          color: '#fff', weight: 1
        });
        marker.bindPopup('<strong>' + H.appLabel(app) + '</strong><br>' + app.name);
        D.overviewMap._fixedLayer.addLayer(marker);
        bounds.push([c.lat, c.lon]);
      });

      var seenMobile = {};
      app.mobileCoords.forEach(function(c) {
        var key = c.lat.toFixed(5) + ',' + c.lon.toFixed(5);
        if (seenMobile[key]) return;
        seenMobile[key] = true;
        var marker = L.circleMarker([c.lat, c.lon], {
          radius: 6, fillColor: '#e86e00', fillOpacity: 0.8,
          color: '#fff', weight: 1
        });
        marker.bindPopup('<strong>' + H.appLabel(app) + '</strong><br>' + app.name + '<br>Radius: ' + (CONFIG.RADIUS_UNIT_KM ? (c.radius / 1000) + ' km' : (c.radius / 1609.34).toFixed(1) + ' mi'));
        D.overviewMap._mobileLayer.addLayer(marker);
        bounds.push([c.lat, c.lon]);
      });
    });

    if (bounds.length) {
      D.overviewMap.fitBounds(bounds, { padding: [30, 30] });
    }

    setTimeout(function() { D.overviewMap.invalidateSize(); }, 200);
  }

  // ============================================================
  // EXPORT
  // ============================================================
  function csvEscape(val) {
    var s = String(val == null ? '' : val);
    return '"' + s.replace(/"/g, '""') + '"';
  }

  function exportFilteredCSV() {
    var now = new Date();
    var lines = ['File Number,Callsign,Name,Status,Experiment Type,Receipt Date,Status Date,Days Pending,Frequency Ranges,Fixed Coords,Mobile Coords,Open Referral Entities,Application URL,Exhibits URL,Correspondence URL'];

    D.filteredApps.forEach(function(a) {
      var age = a.receiptDate ? H.daysBetween(a.receiptDate, now) : '';
      var freqs = a.frequencies.map(function(f) {
        return f.lo + (f.hi && f.hi !== f.lo ? '-' + f.hi : '') + ' MHz';
      }).join('; ');
      var fixedC = a.fixedCoords.map(function(c) { return c.lat + ',' + c.lon; }).join('; ');
      var mobileC = a.mobileCoords.map(function(c) { return c.lat + ',' + c.lon; }).join('; ');
      var openRefs = a.charges.filter(function(c) { return !c.closeDate; }).map(function(c) { return c.charge; }).join('; ');

      lines.push([
        csvEscape(a.fileNumber),
        csvEscape(a.callsign || ''),
        csvEscape(a.name),
        csvEscape(a.status),
        csvEscape(a.experimentType || ''),
        a.receiptDate ? H.formatDate(a.receiptDate) : '',
        a.statusDate ? H.formatDate(a.statusDate) : '',
        age,
        csvEscape(freqs),
        csvEscape(fixedC),
        csvEscape(mobileC),
        csvEscape(openRefs),
        csvEscape(a.applicationUrl || ''),
        csvEscape(a.exhibitsUrl || ''),
        csvEscape(a.correspondenceUrl || '')
      ].join(','));
    });

    var blob = new Blob([lines.join('\n')], { type: 'text/csv' });
    var url = URL.createObjectURL(blob);
    var a = document.createElement('a');
    a.href = url;
    var today = new Date().toISOString().slice(0, 10);
    a.download = 'els-filtered-' + today + '.csv';
    a.click();
    URL.revokeObjectURL(url);
  }

  D.exportFilteredCSV = exportFilteredCSV;

  // ============================================================
  // STARTUP - check for data.js or show CSV fallback
  // ============================================================
  $(document).ready(function() {
    $('#btn-save-datajs').on('click', D.generateDataJS);
    if (typeof DATA !== 'undefined' && Array.isArray(DATA)) {
      init(DATA, false);
    } else {
      $('#csv-overlay').show();
      var dropZone = document.getElementById('csv-drop-zone');
      var fileInput = document.getElementById('csv-file-input');

      dropZone.addEventListener('click', function() { fileInput.click(); });
      dropZone.addEventListener('dragover', function(e) {
        e.preventDefault();
        dropZone.classList.add('dragover');
      });
      dropZone.addEventListener('dragleave', function() {
        dropZone.classList.remove('dragover');
      });
      dropZone.addEventListener('drop', function(e) {
        e.preventDefault();
        dropZone.classList.remove('dragover');
        handleFile(e.dataTransfer.files[0]);
      });
      fileInput.addEventListener('change', function() {
        if (fileInput.files.length) handleFile(fileInput.files[0]);
      });

      function handleFile(file) {
        var reader = new FileReader();
        reader.onload = function(e) {
          var rows = D.parseCSV(e.target.result);
          if (rows.length) {
            $('#csv-overlay').hide();
            init(rows, true);
          } else {
            alert('No data found in CSV file.');
          }
        };
        reader.readAsText(file);
      }
    }
  });
})(jQuery);
