/* global React, Icon, IconBtn, db */
/* Flashcards import wizard.
 * Steps: 1) Upload file → 2) Map columns → 3) Preview → 4) Result.
 * CSV is parsed in-browser; Excel uses SheetJS loaded lazily from CDN.
 */
const { useState, useEffect, useMemo, useRef, useCallback } = React;

// ----------------------------------------------------------------
// CSV parser — handles quotes, embedded newlines, escaped quotes.
// Returns string[][].
// ----------------------------------------------------------------
function parseCsv(text, delim = ',') {
  const rows = [];
  let row = [];
  let cur = '';
  let inQuote = false;
  let i = 0;
  // Strip UTF-8 BOM
  if (text.charCodeAt(0) === 0xfeff) text = text.slice(1);
  while (i < text.length) {
    const ch = text[i];
    if (inQuote) {
      if (ch === '"') {
        if (text[i + 1] === '"') { cur += '"'; i += 2; continue; }
        inQuote = false; i++; continue;
      }
      cur += ch; i++; continue;
    }
    if (ch === '"') { inQuote = true; i++; continue; }
    if (ch === delim) { row.push(cur); cur = ''; i++; continue; }
    if (ch === '\r') { i++; continue; }
    if (ch === '\n') { row.push(cur); cur = ''; rows.push(row); row = []; i++; continue; }
    cur += ch; i++;
  }
  if (cur !== '' || row.length > 0) { row.push(cur); rows.push(row); }
  // Drop trailing empty row
  while (rows.length && rows[rows.length - 1].every(c => c === '')) rows.pop();
  return rows;
}

// Auto-detect delimiter from sample (commas, tabs, semicolons)
function detectDelimiter(sample) {
  const candidates = [',', '\t', ';'];
  let best = ',', bestScore = -1;
  for (const d of candidates) {
    // Count appearances in first 10 lines, take the one with most consistent count per line
    const lines = sample.split(/\r?\n/).slice(0, 10).filter(Boolean);
    if (lines.length === 0) continue;
    const counts = lines.map(l => (l.match(new RegExp(`\\${d}`, 'g')) || []).length);
    const avg = counts.reduce((a, b) => a + b, 0) / counts.length;
    if (avg > bestScore) { bestScore = avg; best = d; }
  }
  return best;
}

// Lazy-load SheetJS for Excel parsing
let _xlsxPromise = null;
function loadXlsx() {
  if (window.XLSX) return Promise.resolve(window.XLSX);
  if (_xlsxPromise) return _xlsxPromise;
  _xlsxPromise = new Promise((resolve, reject) => {
    const s = document.createElement('script');
    s.src = 'https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js';
    s.onload = () => resolve(window.XLSX);
    s.onerror = () => reject(new Error('Failed to load XLSX'));
    document.head.appendChild(s);
  });
  return _xlsxPromise;
}

// ----------------------------------------------------------------
// Import wizard
// ----------------------------------------------------------------
function FlashcardImport({ collection, onDone }) {
  const [step, setStep] = useState(1);   // 1: upload, 2: map, 3: preview, 4: done
  const [filename, setFilename] = useState('');
  const [rows, setRows] = useState([]);  // string[][]
  const [hasHeader, setHasHeader] = useState(true);
  const [mapping, setMapping] = useState({}); // columnIdx -> fieldId | '__skip'
  const [error, setError] = useState('');
  const [busy, setBusy] = useState(false);
  const [result, setResult] = useState(null); // { imported, skipped, errors }

  const headerRow = hasHeader && rows.length > 0 ? rows[0] : [];
  const dataRows = hasHeader ? rows.slice(1) : rows;
  const columnCount = rows[0]?.length || 0;

  // Suggest mappings from header names when we move to step 2
  useEffect(() => {
    if (step !== 2 || rows.length === 0) return;
    const guess = {};
    for (let c = 0; c < columnCount; c++) {
      const header = (headerRow[c] || `Column ${c + 1}`).trim().toLowerCase();
      // Find best field match by name
      const match = collection.schema.find(f => f.name.trim().toLowerCase() === header);
      if (match) guess[c] = match.id;
      else guess[c] = '__skip';
    }
    // If no header match found, try a fallback: map first N columns to first N fields in order
    const anyMatched = Object.values(guess).some(v => v !== '__skip');
    if (!anyMatched) {
      const fields = collection.schema;
      for (let c = 0; c < Math.min(columnCount, fields.length); c++) guess[c] = fields[c].id;
    }
    setMapping(guess);
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [step, hasHeader, rows]);

  // -- Step 1: file ingest --
  const handleFile = async (file) => {
    if (!file) return;
    setError('');
    setFilename(file.name);
    const name = file.name.toLowerCase();
    if (file.size > 5 * 1024 * 1024) {
      setError('File is larger than 5 MB. Try splitting it.');
      return;
    }
    try {
      if (name.endsWith('.csv') || name.endsWith('.tsv') || name.endsWith('.txt')) {
        const text = await file.text();
        const delim = name.endsWith('.tsv') ? '\t' : detectDelimiter(text.slice(0, 4000));
        const parsed = parseCsv(text, delim);
        if (parsed.length === 0) throw new Error('No rows found.');
        if (parsed.length > 5000) {
          setError(`File has ${parsed.length} rows — limit is 5,000. Truncating.`);
          parsed.length = 5000;
        }
        setRows(parsed);
        setStep(2);
      } else if (name.endsWith('.xlsx') || name.endsWith('.xls')) {
        setBusy(true);
        const XLSX = await loadXlsx();
        const buf = await file.arrayBuffer();
        const wb = XLSX.read(buf, { type: 'array' });
        const sheet = wb.Sheets[wb.SheetNames[0]];
        const parsed = XLSX.utils.sheet_to_json(sheet, { header: 1, defval: '', raw: false });
        setBusy(false);
        if (parsed.length === 0) throw new Error('No rows found.');
        if (parsed.length > 5000) {
          setError(`File has ${parsed.length} rows — limit is 5,000. Truncating.`);
          parsed.length = 5000;
        }
        // sheet_to_json gives any[][] — coerce to strings
        const coerced = parsed.map(r => r.map(v => v == null ? '' : String(v)));
        setRows(coerced);
        setStep(2);
      } else {
        setError('Unsupported file type. Use .csv, .tsv, or .xlsx/.xls.');
      }
    } catch (e) {
      setBusy(false);
      setError(e.message || String(e));
    }
  };

  const downloadTemplate = () => {
    const headers = collection.schema.map(f => f.name);
    const sample = collection.schema.map(f => {
      if (f.type === 'tags') return 'tag1, tag2';
      if (f.type === 'number') return '0';
      if (f.type === 'date') return '2026-01-15';
      return f.name;
    });
    const csv = [headers, sample].map(r => r.map(v => /[",\n]/.test(v) ? `"${String(v).replace(/"/g, '""')}"` : v).join(',')).join('\n');
    const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `${collection.name.replace(/[^\w]+/g, '_') || 'flashcards'}_template.csv`;
    a.click();
    URL.revokeObjectURL(url);
  };

  // -- Step 3: derived preview --
  const previewCards = useMemo(() => {
    if (step < 3) return { cards: [], valid: 0, errors: 0 };
    const fieldToCol = {};
    for (const [colIdxStr, fieldId] of Object.entries(mapping)) {
      if (fieldId === '__skip') continue;
      fieldToCol[fieldId] = Number(colIdxStr);
    }
    const cards = [];
    let validCount = 0;
    let errorCount = 0;
    for (let i = 0; i < dataRows.length; i++) {
      const row = dataRows[i];
      const fields = {};
      const rowErrors = [];
      for (const f of collection.schema) {
        const ci = fieldToCol[f.id];
        if (ci == null) continue;
        const raw = (row[ci] ?? '').toString().trim();
        if (raw === '') continue;
        let val = raw;
        if (f.type === 'tags') val = raw.split(/[,;]/).map(s => s.trim()).filter(Boolean);
        else if (f.type === 'number') {
          const n = Number(raw);
          if (Number.isFinite(n)) val = n;
          else { rowErrors.push(`${f.name}: not a number`); continue; }
        } else if (f.type === 'date') {
          const d = new Date(raw);
          if (!isNaN(d.getTime())) val = d.getTime();
          else { rowErrors.push(`${f.name}: bad date`); continue; }
        }
        fields[f.id] = val;
      }
      // Required-field check
      for (const f of collection.schema) {
        if (!f.required) continue;
        const v = fields[f.id];
        if (v == null || v === '' || (Array.isArray(v) && v.length === 0)) {
          rowErrors.push(`${f.name} is required`);
        }
      }
      const valid = rowErrors.length === 0;
      cards.push({ fields, errors: rowErrors, valid, sourceRow: i + (hasHeader ? 2 : 1) });
      if (valid) validCount++; else errorCount++;
    }
    return { cards, valid: validCount, errors: errorCount };
  }, [step, dataRows, mapping, collection.schema, hasHeader]);

  const mappingErrors = useMemo(() => {
    const out = [];
    const usedFieldIds = new Set(Object.values(mapping).filter(v => v !== '__skip'));
    if (!usedFieldIds.has(collection.promptField)) out.push('Map at least one column to the prompt field.');
    const requiredMissing = collection.schema.filter(f => f.required && !usedFieldIds.has(f.id));
    for (const f of requiredMissing) out.push(`Required field "${f.name}" is not mapped.`);
    // Duplicate mapping check
    const counts = {};
    for (const v of Object.values(mapping)) {
      if (v === '__skip') continue;
      counts[v] = (counts[v] || 0) + 1;
    }
    for (const [fid, n] of Object.entries(counts)) {
      if (n > 1) out.push(`Field "${collection.schema.find(f => f.id === fid)?.name}" is mapped to ${n} columns.`);
    }
    return out;
  }, [mapping, collection.schema, collection.promptField]);

  const commit = async () => {
    setBusy(true);
    const toImport = previewCards.cards.filter(c => c.valid);
    for (const c of toImport) {
      await db.flashcardCards.put({
        collectionId: collection.id,
        fields: c.fields,
        fsrs: { state: 'new', stability: 0, difficulty: 0, due: null, lastReview: null, reps: 0, lapses: 0 },
      });
    }
    setBusy(false);
    setResult({ imported: toImport.length, skipped: previewCards.cards.length - toImport.length });
    setStep(4);
  };

  return (
    <div className="fc-import">
      <button className="fc-detail-back" onClick={onDone}>
        <Icon name="arrow_back" />Back to {collection.name}
      </button>
      <div className="fc-builder-header">
        <h2>Import cards</h2>
        <p>Bring in a CSV, TSV, or Excel file and map each column to a collection field.</p>
      </div>

      {/* Step indicator */}
      <div className="fc-import-steps">
        {[
          { n: 1, label: 'Upload' },
          { n: 2, label: 'Map columns' },
          { n: 3, label: 'Preview' },
          { n: 4, label: 'Done' },
        ].map(s => (
          <div key={s.n} className={`fc-import-step ${step === s.n ? 'is-active' : step > s.n ? 'is-done' : ''}`}>
            <span className="n">{step > s.n ? '✓' : s.n}</span>
            <span>{s.label}</span>
          </div>
        ))}
      </div>

      {step === 1 && (
        <Step1Upload
          onFile={handleFile}
          downloadTemplate={downloadTemplate}
          busy={busy}
          error={error}
        />
      )}

      {step === 2 && (
        <Step2Map
          collection={collection}
          rows={rows}
          headerRow={headerRow}
          dataRows={dataRows}
          columnCount={columnCount}
          hasHeader={hasHeader}
          setHasHeader={setHasHeader}
          mapping={mapping}
          setMapping={setMapping}
          mappingErrors={mappingErrors}
          filename={filename}
          onBack={() => { setStep(1); setRows([]); setFilename(''); }}
          onNext={() => setStep(3)}
        />
      )}

      {step === 3 && (
        <Step3Preview
          collection={collection}
          preview={previewCards}
          onBack={() => setStep(2)}
          onCommit={commit}
          busy={busy}
        />
      )}

      {step === 4 && result && (
        <Step4Done
          result={result}
          collection={collection}
          onDone={onDone}
        />
      )}
    </div>
  );
}

window.FlashcardImport = FlashcardImport;

// ----------------------------------------------------------------
// Step 1: file upload
// ----------------------------------------------------------------
function Step1Upload({ onFile, downloadTemplate, busy, error }) {
  const inputRef = useRef(null);
  const [over, setOver] = useState(false);
  return (
    <>
      <div
        className={`fc-dropzone ${over ? 'is-over' : ''}`}
        onClick={() => inputRef.current?.click()}
        onDragOver={e => { e.preventDefault(); setOver(true); }}
        onDragLeave={() => setOver(false)}
        onDrop={e => { e.preventDefault(); setOver(false); onFile(e.dataTransfer.files?.[0]); }}
      >
        <div className="icon-wrap"><Icon name={busy ? 'progress_activity' : 'upload_file'} className={busy ? 'fc-spin' : ''} /></div>
        <h3>{busy ? 'Parsing your file…' : 'Drop a file here, or click to choose'}</h3>
        <p>Up to 5 MB · 5,000 rows · CSV, TSV, XLSX, XLS · UTF-8 safe</p>
        <input
          ref={inputRef}
          type="file"
          accept=".csv,.tsv,.txt,.xlsx,.xls"
          hidden
          onChange={e => onFile(e.target.files?.[0])}
        />
      </div>

      {error && (
        <div className="fc-callout is-error" style={{ marginTop: 14 }}>
          <Icon name="error" />
          <span>{error}</span>
        </div>
      )}

      <div style={{ marginTop: 24, padding: 14, background: 'var(--color-surface)', border: '1px solid var(--color-border)', borderRadius: 12 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
          <Icon name="lightbulb" style={{ color: 'var(--color-primary)' }} />
          <b style={{ color: 'var(--text-hi)', fontSize: 13 }}>Don't have a file yet?</b>
        </div>
        <p style={{ margin: '0 0 10px', fontSize: 12, color: 'var(--text-muted)' }}>
          Download a template matching this collection's fields, fill it in offline, then import it.
        </p>
        <button className="lh-btn ghost" onClick={downloadTemplate}>
          <Icon name="download" />Download CSV template
        </button>
      </div>
    </>
  );
}

// ----------------------------------------------------------------
// Step 2: column mapping
// ----------------------------------------------------------------
function Step2Map({ collection, rows, headerRow, dataRows, columnCount, hasHeader, setHasHeader, mapping, setMapping, mappingErrors, filename, onBack, onNext }) {
  return (
    <>
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 14 }}>
        <div style={{ flex: 1 }}>
          <div style={{ fontSize: 12, color: 'var(--text-muted)' }}>File</div>
          <div style={{ fontWeight: 600, color: 'var(--text-hi)' }}>{filename}</div>
          <div style={{ fontSize: 11, color: 'var(--text-muted)' }}>
            {rows.length} row{rows.length === 1 ? '' : 's'} · {columnCount} column{columnCount === 1 ? '' : 's'}
          </div>
        </div>
        <label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, cursor: 'pointer' }}>
          <input
            type="checkbox"
            checked={hasHeader}
            onChange={e => setHasHeader(e.target.checked)}
            style={{ accentColor: 'var(--color-primary)' }}
          />
          First row is a header
        </label>
      </div>

      <div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 12 }}>
        Match each column in your file to a field in <b style={{ color: 'var(--text-hi)' }}>{collection.name}</b>. Suggestions are based on header names.
      </div>

      <div>
        {Array.from({ length: columnCount }, (_, c) => {
          const header = hasHeader ? (headerRow[c] || `Column ${c + 1}`) : `Column ${c + 1}`;
          const sample = dataRows.slice(0, 3).map(r => r[c] || '').filter(Boolean).join('  ·  ');
          return (
            <div key={c} className="fc-mapper-row">
              <div className="col-sample">
                <span className="col-name">{header}</span>
                <span className="col-sample-text">{sample || <em style={{ color: 'var(--text-faint)' }}>(empty)</em>}</span>
              </div>
              <div className="arrow"><Icon name="arrow_forward" /></div>
              <select
                className="fc-select"
                value={mapping[c] || '__skip'}
                onChange={e => setMapping({ ...mapping, [c]: e.target.value })}
              >
                <option value="__skip">— Skip this column —</option>
                {collection.schema.map(f => (
                  <option key={f.id} value={f.id}>
                    {f.name}
                    {f.id === collection.promptField ? ' (prompt)' : ''}
                    {f.required ? ' *' : ''}
                  </option>
                ))}
              </select>
            </div>
          );
        })}
      </div>

      {mappingErrors.length > 0 && (
        <div className="fc-mapper-summary">
          {mappingErrors.map((e, i) => (
            <span key={i} className="pip is-error">
              <Icon name="error" style={{ fontSize: 12 }} />{e}
            </span>
          ))}
        </div>
      )}

      <div className="fc-import-foot">
        <button className="lh-btn ghost" onClick={onBack}>
          <Icon name="arrow_back" />Back
        </button>
        <button className="lh-btn primary" disabled={mappingErrors.length > 0} onClick={onNext}>
          Preview <Icon name="arrow_forward" />
        </button>
      </div>
    </>
  );
}

// ----------------------------------------------------------------
// Step 3: preview
// ----------------------------------------------------------------
function Step3Preview({ collection, preview, onBack, onCommit, busy }) {
  const { fieldPreview } = window.flashcards;
  const showCols = collection.schema; // show all fields

  return (
    <>
      <div className="fc-mapper-summary" style={{ marginTop: 0, marginBottom: 14 }}>
        <span className="pip is-ok">
          <Icon name="check_circle" style={{ fontSize: 12 }} />
          <b>{preview.valid}</b>&nbsp;valid card{preview.valid === 1 ? '' : 's'}
        </span>
        {preview.errors > 0 && (
          <span className="pip is-warn">
            <Icon name="warning" style={{ fontSize: 12 }} />
            <b>{preview.errors}</b>&nbsp;row{preview.errors === 1 ? '' : 's'} will be skipped
          </span>
        )}
        <span style={{ color: 'var(--text-muted)', fontSize: 11 }}>
          Showing first 10 rows below.
        </span>
      </div>

      <div className="fc-preview-table">
        <table>
          <thead>
            <tr>
              <th style={{ width: 36 }}>#</th>
              {showCols.map(f => (
                <th key={f.id}>
                  {f.name}
                  {f.id === collection.promptField && <span style={{ color: 'var(--color-primary)', marginLeft: 4 }}>·</span>}
                </th>
              ))}
              <th>Issues</th>
            </tr>
          </thead>
          <tbody>
            {preview.cards.slice(0, 10).map((c, i) => (
              <tr key={i} style={{ opacity: c.valid ? 1 : 0.65 }}>
                <td style={{ color: 'var(--text-muted)' }}>{c.sourceRow}</td>
                {showCols.map(f => {
                  const v = c.fields[f.id];
                  const empty = v == null || v === '' || (Array.isArray(v) && v.length === 0);
                  return (
                    <td key={f.id} className={empty ? 'is-empty' : ''}>
                      {empty ? '—' : fieldPreview(v, f.type)}
                    </td>
                  );
                })}
                <td className={c.errors.length ? 'is-error' : ''}>
                  {c.errors.length ? c.errors.join('; ') : <span style={{ color: '#6ee7b7' }}>OK</span>}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      <div className="fc-import-foot">
        <button className="lh-btn ghost" onClick={onBack}>
          <Icon name="arrow_back" />Back
        </button>
        <button className="lh-btn primary" onClick={onCommit} disabled={busy || preview.valid === 0}>
          <Icon name={busy ? 'progress_activity' : 'check'} className={busy ? 'fc-spin' : ''} />
          {busy ? 'Importing…' : `Import ${preview.valid} card${preview.valid === 1 ? '' : 's'}`}
        </button>
      </div>
    </>
  );
}

// ----------------------------------------------------------------
// Step 4: success
// ----------------------------------------------------------------
function Step4Done({ result, collection, onDone }) {
  return (
    <div className="fc-empty" style={{ paddingTop: 60 }}>
      <div className="icon-wrap" style={{ background: 'rgba(52,211,153,0.1)', color: '#6ee7b7' }}>
        <Icon name="check_circle" />
      </div>
      <h3>Import complete</h3>
      <p>
        <b style={{ color: 'var(--text-hi)' }}>{result.imported}</b> card{result.imported === 1 ? '' : 's'} added to <b style={{ color: 'var(--text-hi)' }}>{collection.name}</b>.
        {result.skipped > 0 && <> {result.skipped} row{result.skipped === 1 ? '' : 's'} skipped due to errors.</>}
      </p>
      <div className="row">
        <button className="lh-btn primary" onClick={onDone}>
          <Icon name="visibility" />View cards
        </button>
      </div>
    </div>
  );
}

// Spinner helper class
if (!document.getElementById('fc-spin-style')) {
  const s = document.createElement('style');
  s.id = 'fc-spin-style';
  s.textContent = `.fc-spin { animation: fc-spin 0.8s linear infinite; } @keyframes fc-spin { to { transform: rotate(360deg); } }`;
  document.head.appendChild(s);
}
