// dsp-import-engine.jsx — DSP file-drop ingest pipeline
// ─────────────────────────────────────────────────────────────────
// Operators routinely get CSV/TSV/XLSX exports from DSP back-ends:
//   · Spotify for Artists  — Trends, Audience, Playlists, Songwriter
//   · Apple Music for Artists — Top tracks, Shazams, Trends
//   · YouTube Studio / YT for Artists — Analytics, Topic claims
//   · Tidal Stage          — Catalog & Streams
//   · Amazon Music for Artists — Streams, Listeners
//   · SoundCloud Pulse     — Plays, top tracks
//   · Audiomack Creator    — Streams, geography
//   · Anghami Artist Hub   — Streams, listeners
//   · JioSaavn Artist Hub  — Streams, top tracks
//   · Beatport Pro Account — Sales, top genres (electronic specialty)
//   · Pandora AMP          — Spins, listeners
//   · Deezer for Creators  — Plays, top tracks
//
// These aren't royalty statements (those go through stmt-parser) and
// they aren't catalog imports (those go through bulk-import). They're
// VENDOR-NATIVE ANALYTICS exports that hydrate audience/recording/
// release pages with platform-specific metrics: streams by date,
// listeners by city, playlist adds, save rate, etc.
//
// Each adapter declares:
//   - identity: filename + header signature for sniff
//   - target dataset: 'streams' | 'audience' | 'playlists' | 'links'
//   - schema: how columns map into ASTRO's analytics-engine.
//
// Exports: window.DSP_IMPORT_ENGINE
// ─────────────────────────────────────────────────────────────────
(function () {
  if (typeof window === 'undefined') return;

  // ════════════════════════════════════════════════════════════════
  // VENDOR ADAPTERS
  // ════════════════════════════════════════════════════════════════
  // Each adapter is one ingest variant: a vendor can have many (S4A
  // Trends vs S4A Audience are different files with different schemas).
  // sniff: a function or object — .filenamePat (regex), .headers (must
  // include all of these; header match is fuzzy via lowercase+nospace).
  const ADAPTERS = [
    // ── Spotify for Artists ──────────────────────────────────────
    {
      id: 'spotify-trends-daily', vendor: 'spotify', vendorLabel: 'Spotify for Artists',
      label: 'Trends — daily streams', dataset: 'streams', period: 'daily',
      sniff: { filenamePat: /(spotify|s4a|trends).*\.(csv|tsv|xlsx)/i, headers: ['date','streams'] },
      schema: {
        date:        ['date','day','reporting day'],
        recording:   ['track','song','title','track name','recording','content'],
        isrc:        ['isrc'],
        streams:     ['streams','plays','count','total streams'],
        listeners:   ['listeners','unique listeners','users'],
        territory:   ['country','market','region'],
        source:      ['source','release type','source of streams'],
      },
      docHint: 'Top right of S4A → "Trends" tab → … menu → Download CSV',
    },
    {
      id: 'spotify-audience-cities', vendor: 'spotify', vendorLabel: 'Spotify for Artists',
      label: 'Audience — top cities', dataset: 'audience', period: 'rolling-28',
      sniff: { filenamePat: /(spotify|s4a).*(audience|cit(y|ies)|geo).*\.(csv|tsv)/i, headers: ['city','listeners'] },
      schema: {
        city:        ['city','metro'],
        country:     ['country','market'],
        listeners:   ['listeners','unique listeners','users'],
        streams:     ['streams','plays'],
      },
      docHint: 'S4A → Audience → Where → Cities → Download',
    },
    {
      id: 'spotify-playlists', vendor: 'spotify', vendorLabel: 'Spotify for Artists',
      label: 'Playlists — top adds', dataset: 'playlists', period: 'rolling-28',
      sniff: { filenamePat: /(spotify|s4a).*(playlist|playlists).*\.(csv|tsv)/i, headers: ['playlist','listeners'] },
      schema: {
        playlist:    ['playlist','playlist name','name'],
        owner:       ['owner','curator','editor'],
        followers:   ['followers','playlist followers','reach'],
        addedAt:     ['date added','added on','add date'],
        streams:     ['streams','plays'],
        listeners:   ['listeners','unique'],
      },
      docHint: 'S4A → Music → Playlists → Filter → Export',
    },
    {
      id: 'spotify-songwriter', vendor: 'spotify', vendorLabel: 'Spotify for Songwriters',
      label: 'Songwriter — top works', dataset: 'works', period: 'rolling-28',
      sniff: { filenamePat: /(spotify|songwriter).*\.(csv|tsv)/i, headers: ['work','iswc'] },
      schema: {
        work:        ['work','composition','song'],
        iswc:        ['iswc'],
        streams:     ['streams','plays'],
        recordings:  ['recordings','versions','recording count'],
      },
      docHint: 'Spotify for Songwriters → Catalog → Export',
    },

    // ── Apple Music for Artists ──────────────────────────────────
    {
      id: 'apple-trends-daily', vendor: 'apple', vendorLabel: 'Apple Music for Artists',
      label: 'Trends — daily plays + Shazams', dataset: 'streams', period: 'daily',
      sniff: { filenamePat: /(apple|amfa|trends).*\.(csv|tsv|xlsx)/i, headers: ['date','plays'] },
      schema: {
        date:        ['date','day'],
        recording:   ['song','track','title','content'],
        isrc:        ['isrc'],
        plays:       ['plays','streams'],
        shazams:     ['shazams','shazam count'],
        listeners:   ['listeners','unique listeners'],
        territory:   ['country','region','storefront'],
      },
      docHint: 'AMfA → Trends → Export → CSV',
    },
    {
      id: 'apple-top-songs', vendor: 'apple', vendorLabel: 'Apple Music for Artists',
      label: 'Top songs by storefront', dataset: 'streams', period: 'rolling-28',
      sniff: { filenamePat: /(apple|amfa).*(top|songs|tracks).*\.(csv|tsv)/i, headers: ['song','plays'] },
      schema: {
        recording:   ['song','track','title'],
        isrc:        ['isrc'],
        plays:       ['plays','streams'],
        territory:   ['country','storefront'],
        rank:        ['rank','position'],
      },
      docHint: 'AMfA → Music → Top Songs → Export',
    },

    // ── YouTube Studio / YT for Artists ──────────────────────────
    {
      id: 'youtube-analytics-daily', vendor: 'youtube', vendorLabel: 'YouTube Studio',
      label: 'Analytics — daily views & watch-time', dataset: 'streams', period: 'daily',
      sniff: { filenamePat: /(youtube|yt|analytics|studio).*\.(csv|tsv)/i, headers: ['date','views'] },
      schema: {
        date:        ['date','day'],
        videoId:     ['video','video id','content'],
        videoTitle:  ['video title','title'],
        views:       ['views','plays'],
        watchMinutes:['watch time (hours)','watch time','watch minutes','minutes watched'],
        revenueUsd:  ['estimated revenue (usd)','revenue','estimated revenue'],
        territory:   ['geography','country'],
        source:      ['traffic source','source'],
      },
      docHint: 'YT Studio → Analytics → Advanced mode → Export → Sheets/CSV',
    },
    {
      id: 'youtube-content-id-claims', vendor: 'youtube', vendorLabel: 'YouTube CMS',
      label: 'Content ID claims (UGC)', dataset: 'claims', period: 'rolling-28',
      sniff: { filenamePat: /(content[\s_-]?id|cms|claim).*\.(csv|tsv)/i, headers: ['asset id','claim'] },
      schema: {
        assetId:     ['asset id'],
        recording:   ['title','asset title'],
        isrc:        ['isrc'],
        claims:      ['claims','active claims','claim count'],
        views:       ['views'],
        revenue:     ['revenue (usd)','revenue'],
      },
      docHint: 'CMS → Reports → Asset Snapshot → Download',
    },

    // ── Tidal Stage ──────────────────────────────────────────────
    {
      id: 'tidal-streams-daily', vendor: 'tidal', vendorLabel: 'Tidal Stage',
      label: 'Streams — daily', dataset: 'streams', period: 'daily',
      sniff: { filenamePat: /(tidal|stage).*\.(csv|tsv)/i, headers: ['date','streams'] },
      schema: {
        date:        ['date','day'],
        recording:   ['track','title','content'],
        isrc:        ['isrc'],
        streams:     ['streams','plays'],
        listeners:   ['listeners','unique'],
        territory:   ['country','region'],
      },
      docHint: 'Tidal Stage → Performance → Export',
    },

    // ── Amazon Music for Artists ─────────────────────────────────
    {
      id: 'amazon-streams-daily', vendor: 'amazon', vendorLabel: 'Amazon Music for Artists',
      label: 'Streams — daily', dataset: 'streams', period: 'daily',
      sniff: { filenamePat: /(amazon|ama|music for artists).*\.(csv|tsv)/i, headers: ['date','streams'] },
      schema: {
        date:        ['date','day'],
        recording:   ['title','track','song'],
        asin:        ['asin'],
        streams:     ['streams','plays'],
        listeners:   ['listeners','unique listeners'],
        territory:   ['country','market'],
      },
      docHint: 'Amazon Music for Artists → Insights → Export',
    },

    // ── SoundCloud Pulse / Insights ──────────────────────────────
    {
      id: 'soundcloud-plays-daily', vendor: 'soundcloud', vendorLabel: 'SoundCloud Insights',
      label: 'Plays — daily', dataset: 'streams', period: 'daily',
      sniff: { filenamePat: /(soundcloud|pulse|insights).*\.(csv|tsv)/i, headers: ['date','plays'] },
      schema: {
        date:        ['date','day'],
        track:       ['track','title','content'],
        plays:       ['plays','streams'],
        likes:       ['likes','favorites'],
        reposts:     ['reposts','shares'],
        comments:    ['comments'],
      },
      docHint: 'SC Pro Unlimited → Insights → Export',
    },

    // ── Audiomack ────────────────────────────────────────────────
    {
      id: 'audiomack-streams', vendor: 'audiomack', vendorLabel: 'Audiomack Creator',
      label: 'Streams + geography', dataset: 'streams', period: 'rolling-28',
      sniff: { filenamePat: /audiomack.*\.(csv|tsv)/i, headers: ['date','streams'] },
      schema: {
        date:        ['date','day'],
        recording:   ['title','track'],
        plays:       ['streams','plays'],
        territory:   ['country'],
      },
      docHint: 'Audiomack Creator → Insights → Export',
    },

    // ── Anghami ──────────────────────────────────────────────────
    {
      id: 'anghami-streams', vendor: 'anghami', vendorLabel: 'Anghami Artist Hub',
      label: 'Streams + listeners', dataset: 'streams', period: 'rolling-28',
      sniff: { filenamePat: /anghami.*\.(csv|tsv)/i, headers: ['date','streams'] },
      schema: {
        date:        ['date'],
        recording:   ['track','title'],
        streams:     ['streams','plays'],
        listeners:   ['listeners','unique users'],
        territory:   ['country','region'],
      },
      docHint: 'Anghami Artist Hub → Statistics → Download',
    },

    // ── JioSaavn ─────────────────────────────────────────────────
    {
      id: 'jiosaavn-streams', vendor: 'jiosaavn', vendorLabel: 'JioSaavn Artist Hub',
      label: 'Streams + listeners', dataset: 'streams', period: 'rolling-28',
      sniff: { filenamePat: /(jiosaavn|saavn).*\.(csv|tsv)/i, headers: ['date','streams'] },
      schema: {
        date:        ['date'],
        recording:   ['track','title','song'],
        streams:     ['streams','plays'],
        listeners:   ['listeners','unique'],
        territory:   ['state','region','country'],
      },
      docHint: 'JioSaavn Artist Hub → Stats → Download',
    },

    // ── Beatport Pro ─────────────────────────────────────────────
    {
      id: 'beatport-sales', vendor: 'beatport', vendorLabel: 'Beatport Pro',
      label: 'Sales by track + genre', dataset: 'sales', period: 'rolling-30',
      sniff: { filenamePat: /beatport.*\.(csv|tsv)/i, headers: ['track','sales'] },
      schema: {
        date:        ['date','sale date'],
        recording:   ['track','title'],
        isrc:        ['isrc'],
        sales:       ['sales','units'],
        revenueUsd:  ['revenue','usd','price'],
        genre:       ['genre','sub-genre'],
      },
      docHint: 'Beatport Pro → Sales → Export',
    },

    // ── Pandora AMP ──────────────────────────────────────────────
    {
      id: 'pandora-spins', vendor: 'pandora', vendorLabel: 'Pandora AMP',
      label: 'Spins + listeners', dataset: 'streams', period: 'rolling-28',
      sniff: { filenamePat: /pandora.*\.(csv|tsv)/i, headers: ['date','spins'] },
      schema: {
        date:        ['date','day'],
        recording:   ['title','track'],
        spins:       ['spins','plays','streams'],
        listeners:   ['listeners','unique'],
        territory:   ['dma','country','market'],
      },
      docHint: 'Pandora AMP → Reports → Export',
    },

    // ── Deezer for Creators ──────────────────────────────────────
    {
      id: 'deezer-streams', vendor: 'deezer', vendorLabel: 'Deezer for Creators',
      label: 'Streams + saves', dataset: 'streams', period: 'rolling-28',
      sniff: { filenamePat: /deezer.*\.(csv|tsv)/i, headers: ['date','streams'] },
      schema: {
        date:        ['date'],
        recording:   ['track','title'],
        streams:     ['streams','plays'],
        listeners:   ['listeners','unique'],
        saves:       ['saves','adds','library adds'],
        territory:   ['country'],
      },
      docHint: 'Deezer for Creators → Insights → Export',
    },
  ];

  // Vendor styling (mirrors integrations.jsx convention)
  const VENDOR_STYLE = {
    spotify:    { color:'#1ed760', bg:'#0a3d1f' },
    apple:      { color:'#ff5e8a', bg:'#3d0a14' },
    youtube:    { color:'#ff0000', bg:'#3d0a0a' },
    tidal:      { color:'#0d4f6f', bg:'#031621' },
    amazon:     { color:'#ff9900', bg:'#3d2407' },
    soundcloud: { color:'#ff7700', bg:'#3d1d00' },
    audiomack:  { color:'#ff8800', bg:'#3d2007' },
    anghami:    { color:'#7a3eff', bg:'#1f0a3d' },
    jiosaavn:   { color:'#1eccb0', bg:'#063128' },
    beatport:   { color:'#01ff95', bg:'#013d24' },
    pandora:    { color:'#3668ff', bg:'#091638' },
    deezer:     { color:'#a238ff', bg:'#220a3d' },
  };

  // ════════════════════════════════════════════════════════════════
  // PARSE — relies on bulk-import-engine for CSV/JSON sniffing
  // ════════════════════════════════════════════════════════════════
  function parse(name, text) {
    const E = window.BULK_IMPORT_ENGINE;
    if (!E) return { headers: [], rows: [], format: 'unknown' };
    return E.parse(name, text);
  }

  // ════════════════════════════════════════════════════════════════
  // VENDOR DETECTION
  // ════════════════════════════════════════════════════════════════
  function norm(s) { return String(s || '').toLowerCase().replace(/[\s_\-()]+/g, ''); }

  function scoreAdapter(adapter, name, headers) {
    let score = 0;
    if (adapter.sniff?.filenamePat?.test?.(name)) score += 0.4;
    if (adapter.sniff?.headers) {
      const Hn = headers.map(norm);
      let matched = 0;
      for (const h of adapter.sniff.headers) {
        if (Hn.some(x => x.includes(norm(h)))) matched++;
      }
      score += 0.6 * (matched / adapter.sniff.headers.length);
    }
    // Bonus: schema field matches
    if (adapter.schema) {
      const Hn = headers.map(norm);
      let schemaMatches = 0;
      let schemaTotal = 0;
      Object.values(adapter.schema).forEach(syns => {
        schemaTotal++;
        if (syns.some(s => Hn.some(x => x.includes(norm(s))))) schemaMatches++;
      });
      if (schemaTotal > 0) score += 0.3 * (schemaMatches / schemaTotal);
    }
    return score;
  }

  function detectAdapter(name, headers) {
    const scored = ADAPTERS.map(a => ({ adapter: a, score: scoreAdapter(a, name, headers) }));
    scored.sort((a,b) => b.score - a.score);
    return { picked: scored[0]?.score > 0.45 ? scored[0].adapter : null, scored };
  }

  // ════════════════════════════════════════════════════════════════
  // FIELD MAPPING — match adapter's schema synonyms to actual headers
  // ════════════════════════════════════════════════════════════════
  function autoMap(adapter, headers) {
    const out = {};
    if (!adapter?.schema) return out;
    const Hn = headers.map(norm);
    Object.entries(adapter.schema).forEach(([key, syns]) => {
      let bestIdx = -1, bestScore = 0;
      headers.forEach((h, i) => {
        for (const s of syns) {
          const sn = norm(s); const hn = Hn[i];
          if (hn === sn) { if (1 > bestScore) { bestScore = 1; bestIdx = i; } }
          else if (hn.includes(sn) || sn.includes(hn)) { if (0.8 > bestScore) { bestScore = 0.8; bestIdx = i; } }
        }
      });
      if (bestIdx >= 0) out[key] = headers[bestIdx];
    });
    return out;
  }

  // ════════════════════════════════════════════════════════════════
  // ENRICH ROWS — link to RS recordings by ISRC / fuzzy title
  // ════════════════════════════════════════════════════════════════
  function buildIsrcIndex() {
    if (window.__DSP_IMPORT_ISRC_INDEX) return window.__DSP_IMPORT_ISRC_INDEX;
    const RS = window.RS || {};
    const idx = new Map();
    (RS.recordings || []).forEach(r => {
      const isrc = (r['ISRC (Audio)'] || r['isrc'] || '').replace(/[\s\-]/g, '').toUpperCase();
      if (isrc) idx.set(isrc, r);
    });
    window.__DSP_IMPORT_ISRC_INDEX = idx;
    return idx;
  }

  function buildTitleIndex() {
    if (window.__DSP_IMPORT_TITLE_INDEX) return window.__DSP_IMPORT_TITLE_INDEX;
    const RS = window.RS || {};
    const idx = new Map();
    (RS.recordings || []).forEach(r => {
      const t = (r['Track Name'] || r.title || '').toLowerCase().trim();
      if (t) idx.set(t, r);
    });
    window.__DSP_IMPORT_TITLE_INDEX = idx;
    return idx;
  }

  function enrichRow(row, mapping, adapter) {
    const isrcIdx = buildIsrcIndex();
    const titleIdx = buildTitleIndex();
    const isrcRaw = mapping.isrc ? row[mapping.isrc] : null;
    const isrc = isrcRaw ? String(isrcRaw).replace(/[\s\-]/g, '').toUpperCase() : null;
    let match = isrc ? isrcIdx.get(isrc) : null;
    let matchType = match ? 'isrc' : null;
    if (!match && mapping.recording) {
      const t = String(row[mapping.recording] || '').toLowerCase().trim();
      if (t) {
        match = titleIdx.get(t);
        if (match) matchType = 'title';
      }
    }
    return { match, matchType };
  }

  // ════════════════════════════════════════════════════════════════
  // VALIDATE — light per-row checks for DSP exports
  // ════════════════════════════════════════════════════════════════
  function validateRow(row, mapping, adapter) {
    const errors = []; const warnings = [];
    // Date sanity
    if (mapping.date) {
      const v = row[mapping.date];
      if (v && isNaN(Date.parse(v))) errors.push({ field: 'date', kind: 'invalid-date' });
    }
    // Numeric metric sanity
    ['streams','plays','spins','listeners','sales','views','watchMinutes','shazams','revenueUsd'].forEach(k => {
      if (mapping[k]) {
        const v = String(row[mapping[k]] || '').replace(/[,$ ]/g, '');
        if (v && isNaN(Number(v))) errors.push({ field: k, kind: 'non-numeric' });
      }
    });
    // ISRC format
    if (mapping.isrc) {
      const v = String(row[mapping.isrc] || '').replace(/[\s\-]/g, '');
      if (v && !/^[A-Z]{2}[A-Z0-9]{3}\d{2}\d{5}$/.test(v.toUpperCase())) {
        warnings.push({ field: 'isrc', kind: 'malformed-isrc' });
      }
    }
    return { errors, warnings };
  }

  // ════════════════════════════════════════════════════════════════
  // FULL PIPELINE
  // ════════════════════════════════════════════════════════════════
  function runPipeline({ name, text, adapterOverride, mappingOverride }) {
    const t0 = performance.now();
    const parsed = parse(name, text);
    if (!parsed.rows || parsed.rows.length === 0) {
      return { ok: false, error: 'no-rows', headers: parsed.headers || [], rows: [], ms: performance.now() - t0 };
    }
    const detection = detectAdapter(name, parsed.headers);
    const adapter = adapterOverride || detection.picked || ADAPTERS[0];
    const mapping = mappingOverride || autoMap(adapter, parsed.headers);
    const rows = parsed.rows;

    let matchedISRC = 0, matchedTitle = 0, unmatched = 0;
    let errCount = 0, warnCount = 0;
    let totalStreams = 0, totalListeners = 0;
    const sample = [];
    const territoryAgg = new Map();
    const recordingAgg = new Map();
    const dateAgg = new Map();

    rows.forEach((row, i) => {
      const v = validateRow(row, mapping, adapter);
      const e = enrichRow(row, mapping, adapter);
      if (v.errors.length) errCount++;
      if (v.warnings.length) warnCount++;
      if (e.matchType === 'isrc') matchedISRC++;
      else if (e.matchType === 'title') matchedTitle++;
      else unmatched++;

      const streams = mapping.streams ? Number(String(row[mapping.streams] || '').replace(/[,$ ]/g,'')) || 0 :
                      mapping.plays ? Number(String(row[mapping.plays] || '').replace(/[,$ ]/g,'')) || 0 :
                      mapping.spins ? Number(String(row[mapping.spins] || '').replace(/[,$ ]/g,'')) || 0 :
                      mapping.views ? Number(String(row[mapping.views] || '').replace(/[,$ ]/g,'')) || 0 : 0;
      const listeners = mapping.listeners ? Number(String(row[mapping.listeners] || '').replace(/[,$ ]/g,'')) || 0 : 0;
      totalStreams += streams; totalListeners += listeners;

      if (mapping.territory) {
        const key = String(row[mapping.territory] || 'unknown');
        territoryAgg.set(key, (territoryAgg.get(key) || 0) + streams);
      }
      if (mapping.recording && e.match) {
        const key = e.match.id || e.match['Recording ID'] || row[mapping.recording];
        recordingAgg.set(key, (recordingAgg.get(key) || 0) + streams);
      }
      if (mapping.date) {
        const key = String(row[mapping.date]).slice(0, 10);
        dateAgg.set(key, (dateAgg.get(key) || 0) + streams);
      }

      if (i < 200) {
        sample.push({
          rowIndex: i, row, match: e.match, matchType: e.matchType,
          errors: v.errors, warnings: v.warnings, streams, listeners,
        });
      }
    });

    const ms = performance.now() - t0;
    const matchRate = rows.length > 0 ? (matchedISRC + matchedTitle) / rows.length : 0;
    return {
      ok: true, ms,
      adapter, detection, mapping,
      headers: parsed.headers, format: parsed.format,
      rowCount: rows.length,
      stats: {
        matchedISRC, matchedTitle, unmatched,
        matchRate,
        errors: errCount, warnings: warnCount,
        totalStreams, totalListeners,
        uniqueTerritories: territoryAgg.size,
        uniqueRecordings: recordingAgg.size,
        dateRange: dateAgg.size > 0 ? {
          from: [...dateAgg.keys()].sort()[0],
          to: [...dateAgg.keys()].sort().pop(),
          days: dateAgg.size,
        } : null,
      },
      topTerritories: [...territoryAgg.entries()].sort((a,b) => b[1]-a[1]).slice(0, 10),
      topRecordings: [...recordingAgg.entries()].sort((a,b) => b[1]-a[1]).slice(0, 10),
      timeSeries: [...dateAgg.entries()].sort().slice(0, 365),
      sample,
    };
  }

  // ════════════════════════════════════════════════════════════════
  // APPLY — merges into window.RS_DSP_IMPORTS for in-app analytics
  // ════════════════════════════════════════════════════════════════
  function apply(result) {
    if (!result?.ok) return { ok: false };
    const store = window.RS_DSP_IMPORTS = (window.RS_DSP_IMPORTS || []);
    store.push({
      id: 'dspi_' + Date.now(),
      vendor: result.adapter.vendor,
      vendorLabel: result.adapter.vendorLabel,
      adapterId: result.adapter.id,
      adapterLabel: result.adapter.label,
      dataset: result.adapter.dataset,
      rows: result.rowCount,
      stats: result.stats,
      timeSeries: result.timeSeries,
      topTerritories: result.topTerritories,
      topRecordings: result.topRecordings,
      committedAt: Date.now(),
    });
    // Trigger any listeners (e.g. analytics-engine cross-filter chip)
    try { window.dispatchEvent(new CustomEvent('astro-dsp-import', { detail: store[store.length - 1] })); } catch(e){}
    return { ok: true, applied: result.rowCount };
  }

  // ════════════════════════════════════════════════════════════════
  // SEED RUN HISTORY
  // ════════════════════════════════════════════════════════════════
  function seedRunHistory() {
    if (window.__DSP_IMPORT_RUNS) return window.__DSP_IMPORT_RUNS;
    const now = Date.now();
    const runs = [
      { id:'dspi_010', fileName:'spotify_trends_oct_2025.csv',           adapter:'spotify-trends-daily',    rowCount:8124,  matchRate:0.94, errCount:0, status:'ok',     startedAt: now - 86400_000*1, ms: 1820, user:'Avery Cohen' },
      { id:'dspi_009', fileName:'apple_amfa_top_songs_q3.csv',           adapter:'apple-top-songs',         rowCount:412,   matchRate:0.97, errCount:0, status:'ok',     startedAt: now - 86400_000*2, ms: 380, user:'Avery Cohen' },
      { id:'dspi_008', fileName:'youtube_studio_advanced_q3.csv',        adapter:'youtube-analytics-daily', rowCount:11240, matchRate:0.71, errCount:14, status:'partial',startedAt: now - 86400_000*3, ms: 3120, user:'Mira Tan' },
      { id:'dspi_007', fileName:'spotify_audience_cities_oct.csv',       adapter:'spotify-audience-cities', rowCount:1812,  matchRate:1.0,  errCount:0, status:'ok',     startedAt: now - 86400_000*4, ms: 240, user:'Mira Tan' },
      { id:'dspi_006', fileName:'tidal_stage_streams_q3.csv',            adapter:'tidal-streams-daily',     rowCount:6420,  matchRate:0.88, errCount:8, status:'partial',startedAt: now - 86400_000*8, ms: 1650, user:'Avery Cohen' },
      { id:'dspi_005', fileName:'amazon_amp_streams_oct.csv',            adapter:'amazon-streams-daily',    rowCount:3812,  matchRate:0.91, errCount:0, status:'ok',     startedAt: now - 86400_000*10, ms: 920, user:'Avery Cohen' },
      { id:'dspi_004', fileName:'soundcloud_pulse_export.csv',           adapter:'soundcloud-plays-daily',  rowCount:1240,  matchRate:0.62, errCount:0, status:'ok',     startedAt: now - 86400_000*15, ms: 380, user:'Mira Tan' },
      { id:'dspi_003', fileName:'spotify_playlists_oct.csv',             adapter:'spotify-playlists',       rowCount:88,    matchRate:0.92, errCount:0, status:'ok',     startedAt: now - 86400_000*18, ms: 80, user:'Avery Cohen' },
      { id:'dspi_002', fileName:'pandora_amp_oct.csv',                   adapter:'pandora-spins',           rowCount:2841,  matchRate:0.84, errCount:0, status:'ok',     startedAt: now - 86400_000*22, ms: 720, user:'Avery Cohen' },
      { id:'dspi_001', fileName:'jiosaavn_artist_hub_q3.csv',            adapter:'jiosaavn-streams',        rowCount:912,   matchRate:0.49, errCount:0, status:'partial',startedAt: now - 86400_000*30, ms: 220, user:'Mira Tan' },
    ];
    window.__DSP_IMPORT_RUNS = runs;
    return runs;
  }

  // ════════════════════════════════════════════════════════════════
  // EXPORTS
  // ════════════════════════════════════════════════════════════════
  window.DSP_IMPORT_ENGINE = {
    ADAPTERS, VENDOR_STYLE,
    parse, detectAdapter, autoMap,
    enrichRow, validateRow, runPipeline, apply,
    seedRunHistory,
  };
})();
