// bug-catcher.jsx — Faithful port of Dasher's Bug Catcher widget for the Wishlist review site.
//
// Same UX as the Dasher dashboard:
//   1. Click 🐞 FAB (bottom-right)
//   2. Pick "Box — drag a region" or "Pin — click a spot"
//   3. AnnotateOverlay covers the viewport at z=199
//   4. On complete: html2canvas captures the annotated page → attached as an image
//   5. Panel opens (anchored to the marker on desktop) — describe the problem, paste/attach more files
//   6. Submit → POST /api/feedback (Vercel proxy → dashboard.k2oc.xyz with x-app-key)
//
// All tickets are tagged: project=wishlist, source=design-review.
// This is the only file in the site that imports html2canvas (loaded via CDN above).
//
// Mounts itself into <div id="bug-catcher-root"></div> at end of body. Independent
// of any runtime tree so it survives even if other scripts fail.

(function () {
  const { useState, useRef, useCallback, useEffect } = React;

  // ─── Bug-catcher chrome tokens (kept dark to read against any background) ────────────────────────────────────────
  const C = {
    brand:    '#3B82F6',
    critical: '#EF4444',
    bg:       '#0E1219',
    surface:  '#161B25',
    border:   'rgba(255,255,255,0.10)',
    t_primary:   '#E7EAF0',
    t_secondary: 'rgba(231,234,240,0.70)',
    t_tertiary:  'rgba(231,234,240,0.50)',
  };

  const PROJECT_ID   = 'wishlist';
  const SOURCE       = 'design-review';
  const API_ENDPOINT = '/api/feedback';

  const BUG_MAX_FILE_SIZE = 5 * 1024 * 1024; // 5 MB — matches server
  const BUG_MAX_FILES = 10;
  const BUG_ALLOWED_MIMES = [
    'image/png', 'image/jpeg', 'image/gif', 'image/webp',
    'application/pdf', 'text/plain', 'text/csv', 'application/json',
    'application/msword',
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  ];

  const OVERLAY_Z = 199;
  const MARKER_COLOR = '#FF3B6B';
  const MARKER_BG = 'rgba(255, 59, 107, 0.18)';

  // ─── useScreenshotAttachments hook ──────────────────────────────────────
  function useScreenshotAttachments(opts) {
    const maxFiles    = (opts && opts.maxFiles)    || 10;
    const maxSize     = (opts && opts.maxSize)     || 5 * 1024 * 1024;
    const allowedMimes = (opts && opts.allowedMimes) || BUG_ALLOWED_MIMES;

    const [files, setFiles] = useState([]);
    const [attachError, setAttachError] = useState('');
    const fileInputRef = useRef(null);

    const addFiles = useCallback((incoming) => {
      const list = Array.from(incoming || []);
      if (!list.length) return;
      let error = '';
      setFiles((prev) => {
        const next = [...prev];
        for (const f of list) {
          if (next.length >= maxFiles) {
            error = `Max ${maxFiles} attachments`;
            break;
          }
          if (f.size > maxSize) {
            error = `${f.name}: exceeds ${Math.round(maxSize / 1024 / 1024)} MB limit`;
            continue;
          }
          if (allowedMimes.length && !allowedMimes.includes(f.type)) {
            error = `${f.name}: type ${f.type || 'unknown'} not allowed`;
            continue;
          }
          next.push({
            id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
            file: f,
            previewUrl: f.type.startsWith('image/') ? URL.createObjectURL(f) : null,
          });
        }
        return next;
      });
      setAttachError(error);
    }, [maxFiles, maxSize, allowedMimes]);

    const removeFile = useCallback((id) => {
      setFiles((prev) => {
        const target = prev.find((x) => x.id === id);
        if (target && target.previewUrl) URL.revokeObjectURL(target.previewUrl);
        return prev.filter((x) => x.id !== id);
      });
    }, []);

    const handlePaste = useCallback((e) => {
      const items = e.clipboardData && e.clipboardData.items;
      if (!items) return;
      const pastedFiles = [];
      for (const it of items) {
        if (it.kind === 'file') {
          const f = it.getAsFile();
          if (f) pastedFiles.push(f);
        }
      }
      if (pastedFiles.length) {
        e.preventDefault();
        addFiles(pastedFiles);
      }
    }, [addFiles]);

    const reset = useCallback(() => {
      setFiles((prev) => {
        prev.forEach((f) => f.previewUrl && URL.revokeObjectURL(f.previewUrl));
        return [];
      });
      setAttachError('');
    }, []);

    return { files, attachError, setAttachError, fileInputRef, addFiles, removeFile, handlePaste, reset };
  }

  // ─── AnnotateOverlay ────────────────────────────────────────────────────
  function AnnotateOverlay({ tool, onComplete, onCancel }) {
    const [drag, setDrag] = useState(null);
    const containerRef = useRef(null);

    useEffect(() => {
      const onKey = (e) => { if (e.key === 'Escape') onCancel && onCancel(); };
      window.addEventListener('keydown', onKey);
      return () => window.removeEventListener('keydown', onKey);
    }, [onCancel]);

    const handlePointerDown = (e) => {
      if (tool === 'pin') {
        const geo = {
          type: 'pin',
          xPx: e.clientX,
          yPx: e.clientY,
          widthPx: 0,
          heightPx: 0,
          x: e.clientX / window.innerWidth,
          y: e.clientY / window.innerHeight,
          width: 0,
          height: 0,
        };
        onComplete && onComplete(geo);
        return;
      }
      setDrag({ x0: e.clientX, y0: e.clientY, x1: e.clientX, y1: e.clientY });
    };

    const handlePointerMove = (e) => {
      if (!drag) return;
      setDrag((d) => (d ? { ...d, x1: e.clientX, y1: e.clientY } : d));
    };

    const handlePointerUp = (e) => {
      if (!drag) return;
      const xPx = Math.min(drag.x0, e.clientX);
      const yPx = Math.min(drag.y0, e.clientY);
      const widthPx = Math.abs(e.clientX - drag.x0);
      const heightPx = Math.abs(e.clientY - drag.y0);
      setDrag(null);
      if (widthPx < 6 || heightPx < 6) return;
      onComplete && onComplete({
        type: 'box',
        xPx, yPx, widthPx, heightPx,
        x: xPx / window.innerWidth,
        y: yPx / window.innerHeight,
        width: widthPx / window.innerWidth,
        height: heightPx / window.innerHeight,
      });
    };

    const previewRect = drag ? {
      left:   Math.min(drag.x0, drag.x1),
      top:    Math.min(drag.y0, drag.y1),
      width:  Math.abs(drag.x1 - drag.x0),
      height: Math.abs(drag.y1 - drag.y0),
    } : null;

    return (
      <div
        ref={containerRef}
        onPointerDown={handlePointerDown}
        onPointerMove={handlePointerMove}
        onPointerUp={handlePointerUp}
        style={{
          position: 'fixed', inset: 0, zIndex: OVERLAY_Z,
          cursor: 'crosshair',
          background: 'rgba(0, 0, 0, 0.04)',
          userSelect: 'none',
        }}
        data-bugcatcher-overlay="true"
        role="presentation"
        aria-label={tool === 'box' ? 'Drag to mark a region' : 'Click to drop a pin'}
      >
        {previewRect && (
          <div style={{
            position: 'absolute',
            left: previewRect.left, top: previewRect.top,
            width: previewRect.width, height: previewRect.height,
            border: `2px solid ${MARKER_COLOR}`,
            background: MARKER_BG,
            pointerEvents: 'none',
          }} />
        )}
        <div style={{
          position: 'fixed', top: 16, left: '50%', transform: 'translateX(-50%)',
          background: '#1E3A5F', color: '#E4E4E7',
          padding: '8px 14px', borderRadius: 8,
          fontSize: 13, fontWeight: 500,
          fontFamily: 'system-ui, sans-serif',
          boxShadow: '0 4px 16px rgba(0,0,0,0.4)',
          pointerEvents: 'none',
        }}>
          {tool === 'box' ? 'Drag to mark a region' : 'Click to drop a pin'} · Esc to cancel
        </div>
      </div>
    );
  }

  function MarkerVisual({ geometry }) {
    if (!geometry) return null;
    if (geometry.type === 'pin') {
      return (
        <div aria-hidden="true" style={{
          position: 'fixed',
          left: geometry.xPx - 12, top: geometry.yPx - 24,
          width: 24, height: 24, zIndex: 198,
          fontSize: 24, lineHeight: 1, pointerEvents: 'none',
          filter: 'drop-shadow(0 2px 4px rgba(0,0,0,0.4))',
        }}>📍</div>
      );
    }
    return (
      <div aria-hidden="true" style={{
        position: 'fixed',
        left: geometry.xPx, top: geometry.yPx,
        width: geometry.widthPx, height: geometry.heightPx,
        zIndex: 198,
        border: `2px solid ${MARKER_COLOR}`,
        background: MARKER_BG,
        pointerEvents: 'none',
        boxShadow: '0 0 0 9999px rgba(0,0,0,0.18)',
      }} />
    );
  }

  // ─── BugCatcher (FAB + flow controller) ─────────────────────────────────
  function BugCatcher() {
    const [open, setOpen] = useState(false);
    const [flow, setFlow] = useState('picker'); // picker | box-active | pin-active | panel
    const [geometry, setGeometry] = useState(null);
    const [problem, setProblem] = useState('');
    const [thanks, setThanks] = useState(false);
    const [submitting, setSubmitting] = useState(false);
    const [capturing, setCapturing] = useState(false);

    const {
      files, attachError, setAttachError, fileInputRef,
      addFiles, removeFile, handlePaste, reset: resetAttachments,
    } = useScreenshotAttachments({
      maxFiles: BUG_MAX_FILES,
      maxSize: BUG_MAX_FILE_SIZE,
      allowedMimes: BUG_ALLOWED_MIMES,
    });

    const closeAndReset = () => {
      setOpen(false);
      setFlow('picker');
      setGeometry(null);
      setProblem('');
      resetAttachments();
      setThanks(false);
    };

    const handleMarkComplete = useCallback(async (geo) => {
      setGeometry(geo);
      setFlow('panel');
      setCapturing(true);
      try {
        await new Promise((r) => requestAnimationFrame(() => r()));
        if (typeof window.html2canvas !== 'function') {
          setAttachError("Couldn't capture screenshot — html2canvas not loaded");
          return;
        }
        const canvas = await window.html2canvas(document.body, {
          x: window.scrollX,
          y: window.scrollY,
          width: window.innerWidth,
          height: window.innerHeight,
          windowWidth: window.innerWidth,
          windowHeight: window.innerHeight,
          backgroundColor: null,
          useCORS: true,
          logging: false,
          ignoreElements: (el) => el && el.dataset && el.dataset.bugcatcherIgnore === 'true',
        });
        await new Promise((resolve) => {
          canvas.toBlob((blob) => {
            if (blob) {
              const filename = `bug-mark-${geo.type}-${Date.now()}.png`;
              const file = new File([blob], filename, { type: 'image/png' });
              addFiles([file]);
            }
            resolve();
          }, 'image/png');
        });
      } catch (err) {
        console.error('Screenshot capture failed:', err);
        setAttachError("Couldn't capture screenshot — note will still submit");
      } finally {
        setCapturing(false);
      }
    }, [addFiles, setAttachError]);

    const handleSubmit = () => {
      const trimmedProblem = problem.trim();
      if (!trimmedProblem || submitting) return;

      // Optimistic UX — show "Thanks!" the instant Submit is clicked.
      // The actual POST travels Vercel → dashboard.k2oc.xyz → MongoDB and
      // can take 300–800ms; we fire it in the background and the user is
      // gone by the time it lands.
      setSubmitting(true);
      setThanks(true);
      setTimeout(() => closeAndReset(), 1200);

      (async () => {
        try {
          const fd = new FormData();
          let body = trimmedProblem;
          if (geometry) {
            const tag = geometry.type === 'box'
              ? `[box ${(geometry.x * 100).toFixed(1)}%,${(geometry.y * 100).toFixed(1)}% ${(geometry.width * 100).toFixed(1)}×${(geometry.height * 100).toFixed(1)}%]`
              : `[pin ${(geometry.x * 100).toFixed(1)}%,${(geometry.y * 100).toFixed(1)}%]`;
            const safeUrl = typeof window !== 'undefined'
              ? `${window.location.origin}${window.location.pathname}`
              : '';
            body = `${tag} ${safeUrl}\n\n${trimmedProblem}`;
          }
          fd.append('problem', body);
          fd.append('type', 'bug');
          fd.append('project', PROJECT_ID);
          fd.append('source', SOURCE);
          for (const f of files) fd.append('file', f.file, f.file.name);
          const response = await fetch(API_ENDPOINT, {
            method: 'POST',
            body: fd,
            credentials: 'include',
          });
          if (!response.ok) {
            const errBody = await response.json().catch(() => ({}));
            console.error('Feedback submit failed:', errBody.error || response.status);
          }
        } catch (err) {
          console.error('Feedback submit failed:', err);
        } finally {
          setSubmitting(false);
        }
      })();
    };

    // Anchor panel near geometry on desktop; fall back to fixed bottom-right.
    const panelStyle = (() => {
      const base = {
        position: 'fixed', zIndex: 200,
        width: 480,
        background: C.surface, border: `1px solid ${C.border}`,
        borderRadius: 12, padding: 14,
        boxShadow: '0 8px 32px rgba(0,0,0,0.4)',
        maxHeight: 'calc(100vh - 24px)',
        overflowY: 'auto',
        color: C.t_primary,
        fontFamily: '"DM Sans", system-ui, -apple-system, sans-serif',
      };
      if (!geometry) return { ...base, bottom: 76, right: 20 };
      const margin = 12;
      const panelW = 480;
      const panelH = 280;
      const vw = window.innerWidth;
      const vh = window.innerHeight;
      const markRight = (geometry.xPx || 0) + (geometry.widthPx || 0);
      let left = markRight + margin;
      if (left + panelW > vw - margin) left = (geometry.xPx || 0) - panelW - margin;
      if (left < margin) left = margin;
      let top = geometry.yPx || 0;
      if (top + panelH > vh - margin) top = vh - panelH - margin;
      if (top < margin) top = margin;
      return { ...base, top, left };
    })();

    const fabStyle = {
      position: 'fixed', bottom: 20, right: 20, zIndex: 200,
      width: 48, height: 48, borderRadius: '50%',
      background: C.bg, border: `1px solid ${C.border}`,
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      cursor: 'pointer', fontSize: 22, boxShadow: '0 4px 16px rgba(0,0,0,0.3)',
      transition: 'transform 0.15s, border-color 0.15s',
      color: '#fff',
    };

    const pickerStyle = {
      position: 'fixed', bottom: 76, right: 20, zIndex: 200,
      background: C.surface, border: `1px solid ${C.border}`,
      borderRadius: 12, padding: 12,
      boxShadow: '0 8px 32px rgba(0,0,0,0.4)',
      display: 'flex', flexDirection: 'column', gap: 10, alignItems: 'stretch',
      minWidth: 220,
      color: C.t_primary,
      fontFamily: '"DM Sans", system-ui, -apple-system, sans-serif',
    };

    const pickerBtn = {
      padding: '10px 14px', fontSize: 14, fontWeight: 600,
      background: C.bg, color: C.t_primary, border: `1px solid ${C.border}`,
      borderRadius: 8, cursor: 'pointer',
      display: 'flex', alignItems: 'center', gap: 10,
      fontFamily: 'inherit',
    };

    const labelStyle = { display: 'block', fontSize: 12, fontWeight: 600, color: C.t_tertiary, marginBottom: 4 };
    const textareaStyle = {
      width: '100%', padding: '8px 10px', fontSize: 14, boxSizing: 'border-box',
      background: C.bg, color: C.t_primary, border: `1px solid ${C.border}`,
      borderRadius: 8, outline: 'none', resize: 'vertical',
      fontFamily: 'inherit',
    };

    const renderChips = () => files.length > 0 && (
      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
        {files.map((f) => (
          <div key={f.id} style={{
            position: 'relative', display: 'flex', alignItems: 'center', gap: 6,
            padding: '4px 6px', background: C.bg, border: `1px solid ${C.border}`,
            borderRadius: 6, fontSize: 11, color: C.t_secondary, maxWidth: 160,
          }}>
            {f.previewUrl ? (
              <img src={f.previewUrl} alt="" style={{ width: 24, height: 24, objectFit: 'cover', borderRadius: 3 }} />
            ) : (
              <span>📄</span>
            )}
            <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
              {f.file.name}
            </span>
            <button
              type="button"
              onClick={() => removeFile(f.id)}
              style={{
                background: 'transparent', border: 'none', color: C.t_tertiary,
                cursor: 'pointer', fontSize: 14, padding: 0, lineHeight: 1,
              }}
              aria-label="Remove attachment"
            >
              ✕
            </button>
          </div>
        ))}
      </div>
    );

    return (
      <>
        {open && flow === 'picker' && (
          <div style={pickerStyle} data-bugcatcher-ignore="true">
            <div style={{ fontSize: 12, fontWeight: 600, color: C.t_tertiary, marginBottom: 2 }}>
              Mark a spot to start
            </div>
            <button type="button" onClick={() => setFlow('box-active')} style={pickerBtn}>
              <span style={{ fontSize: 18 }}>📦</span> Box — drag a region
            </button>
            <button type="button" onClick={() => setFlow('pin-active')} style={pickerBtn}>
              <span style={{ fontSize: 18 }}>📍</span> Pin — click a spot
            </button>
          </div>
        )}

        {open && (flow === 'box-active' || flow === 'pin-active') && (
          <AnnotateOverlay
            tool={flow === 'box-active' ? 'box' : 'pin'}
            onComplete={handleMarkComplete}
            onCancel={() => setFlow('picker')}
          />
        )}

        {open && flow === 'panel' && geometry && <MarkerVisual geometry={geometry} />}

        {open && flow === 'panel' && (
          <div style={panelStyle} data-bugcatcher-ignore="true">
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
              <div style={{ fontSize: 15, fontWeight: 700, color: C.t_primary }}>
                {'🐞 Bug Catcher'}
              </div>
              <button
                type="button"
                onClick={() => { setGeometry(null); resetAttachments(); setProblem(''); setFlow('picker'); }}
                style={{
                  background: 'transparent', border: 'none', color: C.t_tertiary,
                  cursor: 'pointer', fontSize: 12, padding: 0, fontFamily: 'inherit',
                }}
              >
                ← Re-mark
              </button>
            </div>

            {thanks ? (
              <div style={{ textAlign: 'center', padding: '18px 0', color: C.brand, fontWeight: 600, fontSize: 16 }}>Thanks!</div>
            ) : (<>
              <div style={{ marginBottom: 10 }}>
                <label style={labelStyle}>Problem *</label>
                <textarea
                  placeholder={'Describe what went wrong... (paste images with ⌘V)'}
                  value={problem}
                  onChange={(e) => setProblem(e.target.value)}
                  onPaste={handlePaste}
                  maxLength={900}
                  rows={3}
                  style={textareaStyle}
                  autoFocus
                />
              </div>

              <div style={{ marginBottom: 10 }}>
                <div style={{ display: 'flex', gap: 6, alignItems: 'center', flexWrap: 'wrap', marginBottom: 6 }}>
                  <button
                    type="button"
                    onClick={() => fileInputRef.current && fileInputRef.current.click()}
                    style={{
                      padding: '5px 10px', fontSize: 12, fontWeight: 600,
                      background: C.bg, color: C.t_secondary, border: `1px solid ${C.border}`,
                      borderRadius: 6, cursor: 'pointer', fontFamily: 'inherit',
                    }}
                  >
                    📎 Attach
                  </button>
                  <span style={{ fontSize: 11, color: C.t_tertiary }}>
                    {capturing ? 'Capturing…' : `${files.length}/${BUG_MAX_FILES} · 5 MB max`}
                  </span>
                  <input
                    ref={fileInputRef}
                    type="file"
                    multiple
                    accept={BUG_ALLOWED_MIMES.join(',')}
                    onChange={(e) => { addFiles(e.target.files); e.target.value = ''; }}
                    style={{ display: 'none' }}
                  />
                </div>
                {renderChips()}
                {attachError && (
                  <div style={{ marginTop: 6, fontSize: 11, color: C.critical }}>{attachError}</div>
                )}
              </div>

              <button
                type="button"
                onClick={handleSubmit}
                disabled={!problem.trim() || submitting || capturing}
                style={{
                  width: '100%', padding: '8px 0', fontSize: 14, fontWeight: 600,
                  background: C.brand, color: '#fff', border: 'none', borderRadius: 8,
                  cursor: (!problem.trim() || submitting || capturing) ? 'not-allowed' : 'pointer',
                  opacity: (!problem.trim() || submitting || capturing) ? 0.5 : 1,
                  fontFamily: 'inherit',
                }}
              >
                {submitting ? 'Submitting...' : 'Submit'}
              </button>
            </>)}
          </div>
        )}

        <button
          type="button"
          onClick={() => { open ? closeAndReset() : setOpen(true); }}
          style={fabStyle}
          aria-label="Bug Catcher"
          data-bugcatcher-ignore="true"
        >
          {open ? '✕' : '🐞'}
        </button>
      </>
    );
  }

  // ─── Mount ──────────────────────────────────────────────────────────────
  function mount() {
    const root = document.getElementById('bug-catcher-root');
    if (!root) return;
    if (!window.React || !window.ReactDOM) {
      console.warn('bug-catcher: React/ReactDOM not loaded yet');
      return;
    }
    ReactDOM.createRoot(root).render(<BugCatcher />);
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', mount);
  } else {
    mount();
  }
})();
