// document-dropzone.jsx — Shared React component for uploading documents
// to a Workspace. F019B Phase 5.
//
// File picker + drag-and-drop, multi-select. Each file runs:
//   1. createWorkspaceUploadUrl callable → signed URL + content_slug.
//   2. fetch(signed_url, { method: 'PUT', body: file }) → 200.
//   3. ingestWorkspaceUpload callable → spine doc + extraction.
//
// Per-file status: uploading → extracting → complete | failed. Status
// surfaces inline in a list under the drop zone. Sequential by default
// (max 3 concurrent slots) — Cloud Functions cold-start cost is the
// gate.
//
// Exposes `window.DocumentDropZone`.

(function (window) {
  'use strict';

  const { fns } = window.exoteamAuth || {};
  if (!fns) {
    console.error('document-dropzone.jsx: window.exoteamAuth.fns missing');
    return;
  }

  // Client-side echo of the server allow-list (server is authoritative).
  const DOCX_MIME = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
  const ACCEPT_MIMES = ['application/pdf', DOCX_MIME, 'text/plain', 'text/markdown'];
  const ACCEPT_INPUT = ACCEPT_MIMES.join(',') + ',.txt,.md,.pdf,.docx';
  const MAX_BYTES = 10 * 1024 * 1024;
  const MAX_PARALLEL = 3;

  function _humanSize(n) {
    if (n < 1024) return `${n} B`;
    if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
    return `${(n / (1024 * 1024)).toFixed(2)} MB`;
  }

  function _inferMime(file) {
    // Browsers sometimes leave .md files with empty `type`. Be lenient.
    if (file.type && ACCEPT_MIMES.includes(file.type)) return file.type;
    const name = (file.name || '').toLowerCase();
    if (name.endsWith('.pdf')) return 'application/pdf';
    if (name.endsWith('.txt')) return 'text/plain';
    if (name.endsWith('.md') || name.endsWith('.markdown')) return 'text/markdown';
    if (name.endsWith('.docx')) return DOCX_MIME;
    return null;
  }

  function DocumentDropZone({ workspaceId, disabled, onUploadCountChange }) {
    const [items, setItems] = React.useState([]); // {id, name, size, status, error, slug}
    const [dragOver, setDragOver] = React.useState(false);
    const inputRef = React.useRef(null);
    const slotsInFlightRef = React.useRef(0);
    const queueRef = React.useRef([]);

    // Bubble the count of successfully-extracted uploads up to the
    // parent. The onboarding work step uses this to allow "Learn from
    // what you've given us" without a website URL.
    React.useEffect(() => {
      if (typeof onUploadCountChange !== 'function') return;
      const done = items.filter((it) => it.status === 'done').length;
      onUploadCountChange(done);
    }, [items, onUploadCountChange]);

    function _setItem(id, patch) {
      setItems((prev) => prev.map((it) => (it.id === id ? { ...it, ...patch } : it)));
    }

    async function _uploadOne(item, file) {
      try {
        _setItem(item.id, { status: 'requesting URL' });
        const createCallable = fns.httpsCallable('createWorkspaceUploadUrl');
        const { data: created } = await createCallable({
          workspace_id: workspaceId,
          filename: file.name,
          mime_type: item.mime,
          file_size_bytes: file.size,
        });
        _setItem(item.id, { status: 'uploading', slug: created.content_slug });

        const putResp = await fetch(created.upload_url, {
          method: 'PUT',
          headers: { 'Content-Type': item.mime },
          body: file,
        });
        if (!putResp.ok) {
          throw new Error(`PUT failed: ${putResp.status} ${putResp.statusText}`);
        }

        _setItem(item.id, { status: 'extracting' });
        const ingestCallable = fns.httpsCallable('ingestWorkspaceUpload');
        const { data: ingested } = await ingestCallable({
          workspace_id: workspaceId,
          slug: created.content_slug,
        });
        // R09T05 — PDFs are usable for enrichment even when text-only
        // Stage-1 extraction fails, because the grounded call now
        // attaches the original PDF as multimodal inlineData. Surface
        // the SERVER'S actual error message (not a hardcoded one) when
        // a doc really isn't usable. The 'done' status counts toward
        // uploadCount so the parent enables "Learn".
        const ok = ingested.usable_for_enrichment === true
          || ingested.body_md_extraction_status === 'complete';
        const isPdfPartial = ingested.mime_type === 'application/pdf'
          && ingested.body_md_extraction_status !== 'complete';
        _setItem(item.id, {
          status: ok ? 'done' : 'failed',
          error: ok
            ? null
            : (ingested.body_md_extraction_error
                || 'Extraction did not produce text'),
          note: isPdfPartial
            ? 'Text extraction skipped — Gemini will read the PDF directly during enrichment.'
            : null,
        });
      } catch (err) {
        console.error('DocumentDropZone: upload failed', err);
        _setItem(item.id, { status: 'failed', error: err.message || String(err) });
      } finally {
        slotsInFlightRef.current -= 1;
        _pump();
      }
    }

    function _pump() {
      while (slotsInFlightRef.current < MAX_PARALLEL && queueRef.current.length > 0) {
        const { item, file } = queueRef.current.shift();
        slotsInFlightRef.current += 1;
        _uploadOne(item, file);
      }
    }

    function _enqueue(files) {
      const next = [];
      for (const file of files) {
        const mime = _inferMime(file);
        const id = `${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
        if (!mime) {
          next.push({ id, name: file.name, size: file.size, status: 'failed', error: 'Unsupported type (PDF, DOCX, TXT, MD).' });
          continue;
        }
        if (file.size > MAX_BYTES) {
          next.push({ id, name: file.name, size: file.size, status: 'failed', error: `Over 10 MiB cap (${_humanSize(file.size)}).` });
          continue;
        }
        const item = { id, name: file.name, size: file.size, status: 'queued', mime, error: null, slug: null };
        next.push(item);
        queueRef.current.push({ item, file });
      }
      if (next.length > 0) {
        setItems((prev) => prev.concat(next));
        _pump();
      }
    }

    function _onFilePick(e) {
      const files = Array.from(e.target.files || []);
      if (!files.length) return;
      _enqueue(files);
      if (inputRef.current) inputRef.current.value = '';
    }

    function _onDrop(e) {
      e.preventDefault();
      setDragOver(false);
      const files = Array.from(e.dataTransfer.files || []);
      if (!files.length) return;
      _enqueue(files);
    }

    function _statusColour(s) {
      if (s === 'complete' || s === 'done') return '#5db98a';
      if (s === 'failed') return '#e85b5b';
      if (s === 'queued') return '#86868b';
      return '#5b8def';
    }

    const browseDisabled = disabled || !workspaceId;

    return (
      <div>
        <style>{`
.exo-dz-zone {
  border: 1.5px dashed rgba(91,141,239,0.5);
  border-radius: 10px;
  background: rgba(91,141,239,0.04);
  padding: 18px 16px;
  text-align: center;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
  font-size: 13px;
  color: #424245;
}
.exo-dz-zone.exo-dz-over {
  background: rgba(91,141,239,0.12);
  border-color: #5b8def;
}
.exo-dz-zone.exo-dz-disabled { opacity: 0.5; cursor: default; }
.exo-dz-zone strong { color: #1d1d1f; }
.exo-dz-list { list-style: none; padding: 0; margin: 10px 0 0; }
.exo-dz-row {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border-radius: 8px;
  background: #f5f5f7; margin-bottom: 6px;
}
.exo-dz-row-name { flex: 1; font-size: 13px; color: #1d1d1f; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.exo-dz-row-size { font-size: 11.5px; color: #86868b; }
.exo-dz-row-status { font-size: 11.5px; font-weight: 500; min-width: 80px; text-align: right; }
.exo-dz-row-error { font-size: 11px; color: #e85b5b; margin-top: 2px; }
        `}</style>
        <div
          className={
            'exo-dz-zone'
            + (dragOver ? ' exo-dz-over' : '')
            + (browseDisabled ? ' exo-dz-disabled' : '')
          }
          onClick={() => { if (!browseDisabled && inputRef.current) inputRef.current.click(); }}
          onDragOver={(e) => { e.preventDefault(); if (!browseDisabled) setDragOver(true); }}
          onDragLeave={() => setDragOver(false)}
          onDrop={(e) => { if (!browseDisabled) _onDrop(e); }}
        >
          <strong>Drop documents here</strong> or click to browse.
          <div style={{ fontSize: 11.5, color: '#86868b', marginTop: 4 }}>
            PDF, DOCX, TXT, or MD · up to 10 MB each
          </div>
          <input
            ref={inputRef}
            type="file"
            multiple
            accept={ACCEPT_INPUT}
            style={{ display: 'none' }}
            onChange={_onFilePick}
            disabled={browseDisabled}
          />
        </div>
        {items.length > 0 && (
          <ul className="exo-dz-list">
            {items.map((it) => (
              <li key={it.id} className="exo-dz-row">
                <div className="exo-dz-row-name" title={it.name}>{it.name}</div>
                <div className="exo-dz-row-size">{_humanSize(it.size)}</div>
                <div className="exo-dz-row-status" style={{ color: _statusColour(it.status) }}>
                  {it.status}
                </div>
                {it.error && (
                  <div className="exo-dz-row-error" style={{ flexBasis: '100%', textAlign: 'left' }}>
                    {it.error}
                  </div>
                )}
                {it.note && (
                  <div className="exo-dz-row-error" style={{ flexBasis: '100%', textAlign: 'left', color: '#6e6e73', fontSize: 11.5 }}>
                    {it.note}
                  </div>
                )}
              </li>
            ))}
          </ul>
        )}
      </div>
    );
  }

  window.DocumentDropZone = DocumentDropZone;
})(window);
