/**
 * ComposeEmailModal — v2
 * Full-featured email compose modal using Quill.js (loaded from CDN).
 * Features: Send-As account, To/CC chip inputs, Subject, rich-text body (Quill),
 *           file attachments, server-file picker, image paste, signature insertion.
 *
 * Props:
 *   open                     – boolean
 *   onClose                  – () => void
 *   defaultTo                – string   (pre-fill To field)
 *   defaultSubject           – string
 *   defaultFromAccount       – string
 *   requireFromAccount       – boolean  (default true)
 *   defaultServerAttachments – [{ path, filename, mime_type }]  (Nextcloud path)
 *   defaultAttachments       – File[]  (pre-fetched File objects, attached immediately on open)
 *   customerId               – string | null
 *   onSent                   – (entry) => void
 */
import { useState, useEffect, useRef, useCallback } from 'react'
import Button from '@/components/ui/Button'
import Select from '@/components/ui/Select'
import Spinner from '@/components/ui/Spinner'
import { useToast } from '@/components/ui/Toast'
import api from '@/lib/api'
import { loadQuill } from '@/lib/loadQuill'

// ── Attachment pill ─────────────────────────────────────────────────────────
function AttachmentPill({ name, size, onRemove }) {
  const kb = size ? ` (${Math.ceil(size / 1024)} KB)` : ''
  return (
    <div className="flex items-center gap-1.5 px-2 py-1 rounded-full text-xs" style={{ backgroundColor: 'var(--color-bg-elevated)', color: 'var(--color-text-secondary)', border: '1px solid var(--color-border)' }}>
      <span className="truncate" style={{ maxWidth: 160 }}>{name}{kb}</span>
      <button
        type="button"
        onClick={onRemove}
        className="flex-shrink-0 cursor-pointer hover:opacity-70"
        style={{ color: 'var(--color-danger)', background: 'none', border: 'none', padding: 0, lineHeight: 1, fontSize: 14 }}
      >×</button>
    </div>
  )
}

// ── Email chip input ─────────────────────────────────────────────────────────
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/

function EmailChipInput({ list, setList, inputVal, setInputVal, placeholder, onOpenPicker }) {
  const commit = (raw) => {
    const parts = raw.split(',').map(s => s.trim()).filter(Boolean)
    const valid = parts.filter(p => EMAIL_RE.test(p))
    if (valid.length) setList(prev => [...prev, ...valid.filter(e => !prev.includes(e))])
    setInputVal('')
  }

  const onKeyDown = (e) => {
    if (e.key === 'Enter' || e.key === ',') {
      e.preventDefault()
      commit(inputVal)
    } else if (e.key === 'Backspace' && !inputVal && list.length > 0) {
      setList(prev => prev.slice(0, -1))
    }
  }

  return (
    <div
      className="input"
      style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 4, cursor: 'text', paddingRight: onOpenPicker ? 28 : undefined, position: 'relative' }}
      onClick={e => e.currentTarget.querySelector('input')?.focus()}
    >
      {list.map(email => (
        <span
          key={email}
          style={{ display: 'inline-flex', alignItems: 'center', gap: 4, padding: '2px 8px', borderRadius: 'var(--radius-full)', fontSize: 'var(--font-size-xs)', backgroundColor: 'var(--color-bg-island)', border: '1px solid var(--color-border-strong)', color: 'var(--color-text-secondary)', flexShrink: 0, maxWidth: 220 }}
          title={email}
        >
          <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{email}</span>
          <button type="button" style={{ background: 'none', border: 'none', padding: 0, lineHeight: 1, cursor: 'pointer', color: 'var(--color-text-muted)', fontSize: 14, flexShrink: 0 }} onClick={() => setList(prev => prev.filter(e => e !== email))}>×</button>
        </span>
      ))}
      <input
        type="text"
        value={inputVal}
        onChange={e => setInputVal(e.target.value)}
        onKeyDown={onKeyDown}
        onBlur={() => inputVal.trim() && commit(inputVal)}
        placeholder={list.length === 0 ? placeholder : ''}
        style={{ flex: '1 1 120px', minWidth: 80, border: 'none', outline: 'none', background: 'transparent', color: 'var(--color-text-primary)', fontSize: 'var(--font-size-sm)', padding: '1px 2px', fontFamily: 'var(--font-family-base)' }}
      />
      {onOpenPicker && (
        <button
          type="button"
          onClick={e => { e.stopPropagation(); onOpenPicker() }}
          title="Search customers"
          style={{ position: 'absolute', right: 6, top: '50%', transform: 'translateY(-50%)', background: 'none', border: 'none', padding: 2, cursor: 'pointer', color: 'var(--color-text-muted)', display: 'flex', alignItems: 'center', lineHeight: 1, borderRadius: 'var(--radius-sm)', flexShrink: 0 }}
          onMouseEnter={e => { e.currentTarget.style.color = 'var(--color-text-primary)'; e.currentTarget.style.backgroundColor = 'var(--color-bg-elevated)' }}
          onMouseLeave={e => { e.currentTarget.style.color = 'var(--color-text-muted)'; e.currentTarget.style.backgroundColor = 'transparent' }}
        >
          <svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
            <circle cx="6.5" cy="6.5" r="4.5" />
            <line x1="10.5" y1="10.5" x2="14" y2="14" />
          </svg>
        </button>
      )}
    </div>
  )
}

// ── Customer email picker ────────────────────────────────────────────────────
// Loads ALL customers once on open, then filters locally — instant search.
function CustomerEmailPicker({ open, onClose, onSelect }) {
  const [query, setQuery] = useState('')
  const [allCustomers, setAllCustomers] = useState([])
  const [loading, setLoading] = useState(false)
  const inputRef = useRef(null)

  // Load all customers once when picker opens
  useEffect(() => {
    if (!open) { setQuery(''); return }
    setTimeout(() => inputRef.current?.focus(), 60)
    if (allCustomers.length > 0) return  // already loaded
    let cancelled = false
    setLoading(true)
    api.get('/crm/customers')
      .then(data => { if (!cancelled) setAllCustomers(data.customers || data || []) })
      .catch(() => { if (!cancelled) setAllCustomers([]) })
      .finally(() => { if (!cancelled) setLoading(false) })
    return () => { cancelled = true }
  }, [open])  // intentionally omit allCustomers from deps

  if (!open) return null

  const q = query.trim().toLowerCase()
  const emailsOf = (c) => (c.contacts || []).filter(ct => ct.type === 'email')

  const filtered = allCustomers.filter(c => {
    if (!q) return emailsOf(c).length > 0
    const emails = emailsOf(c)
    if (emails.length === 0) return false
    return (
      (c.name || '').toLowerCase().includes(q) ||
      (c.surname || '').toLowerCase().includes(q) ||
      (c.organization || '').toLowerCase().includes(q) ||
      (c.location?.city || '').toLowerCase().includes(q) ||
      (c.location?.country || '').toLowerCase().includes(q) ||
      emails.some(ct => ct.value.toLowerCase().includes(q))
    )
  })

  return (
    <div
      style={{ position: 'fixed', inset: 0, zIndex: 1200, backgroundColor: 'rgba(0,0,0,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
      onClick={onClose}
    >
      <div
        style={{ backgroundColor: 'var(--color-bg-surface)', border: '1px solid var(--color-border-strong)', borderRadius: 'var(--radius-xl)', width: 560, maxHeight: '70vh', display: 'flex', flexDirection: 'column', overflow: 'hidden', boxShadow: 'var(--shadow-lg)' }}
        onClick={e => e.stopPropagation()}
      >
        {/* Header */}
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '14px 18px', borderBottom: '1px solid var(--color-border-strong)', flexShrink: 0 }}>
          <span style={{ fontSize: 'var(--font-size-md)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-text-primary)', fontFamily: 'var(--font-family-display)' }}>Search Customers</span>
          <button type="button" onClick={onClose} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--color-text-muted)', fontSize: 20, lineHeight: 1, padding: 2 }}>×</button>
        </div>

        {/* Search input */}
        <div style={{ padding: '12px 16px', borderBottom: '1px solid var(--color-border)', flexShrink: 0 }}>
          <div style={{ position: 'relative', display: 'flex', alignItems: 'center' }}>
            <svg style={{ position: 'absolute', left: 10, color: 'var(--color-text-muted)', pointerEvents: 'none', flexShrink: 0 }} width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
              <circle cx="6.5" cy="6.5" r="4.5" /><line x1="10.5" y1="10.5" x2="14" y2="14" />
            </svg>
            <input
              ref={inputRef}
              type="text"
              value={query}
              onChange={e => setQuery(e.target.value)}
              placeholder="Name, surname, organization, email…"
              style={{ width: '100%', padding: '7px 10px 7px 32px', background: 'var(--color-bg-abyss)', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-md)', color: 'var(--color-text-primary)', fontSize: 'var(--font-size-sm)', fontFamily: 'var(--font-family-base)', outline: 'none' }}
              onFocus={e => { e.target.style.borderColor = 'var(--color-border-focus)' }}
              onBlur={e => { e.target.style.borderColor = 'var(--color-border)' }}
            />
            {query && (
              <button type="button" onClick={() => setQuery('')} style={{ position: 'absolute', right: 8, background: 'none', border: 'none', cursor: 'pointer', color: 'var(--color-text-muted)', fontSize: 16, lineHeight: 1, padding: 0 }}>×</button>
            )}
          </div>
        </div>

        {/* Results */}
        <div style={{ overflowY: 'auto', flex: 1 }}>
          {loading && (
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, padding: '32px 0', color: 'var(--color-text-muted)', fontSize: 'var(--font-size-sm)' }}>
              <Spinner size="sm" /> Loading customers…
            </div>
          )}
          {!loading && filtered.length === 0 && (
            <div style={{ textAlign: 'center', padding: '32px 0', color: 'var(--color-text-muted)', fontSize: 'var(--font-size-sm)' }}>
              {q ? 'No customers match your search.' : 'No customers with email addresses found.'}
            </div>
          )}
          {!loading && filtered.map(c => {
            const emails = emailsOf(c)
            const hasMultiple = emails.length > 1
            const nameParts = [c.name, c.surname].filter(Boolean).join(' ')
            const metaParts = [c.organization, [c.location?.city, c.location?.country].filter(Boolean).join(', ')].filter(Boolean)
            return (
              <div key={c.id} style={{ borderBottom: '1px solid var(--color-border)' }}>
                {/* Name · org · location — all left, dots between */}
                <div style={{ display: 'flex', alignItems: 'baseline', flexWrap: 'wrap', padding: '9px 16px 3px' }}>
                  {nameParts && (
                    <span style={{ fontSize: 'var(--font-size-base)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-text-primary)', marginRight: metaParts.length ? 8 : 0 }}>{nameParts}</span>
                  )}
                  {metaParts.map((part, i) => (
                    <span key={i} style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)' }}>
                      {i > 0 && <span style={{ margin: '0 5px', opacity: 0.5 }}>·</span>}
                      {part}
                    </span>
                  ))}
                </div>
                {/* One row per email, all flush left at 16px, no extra indent */}
                {emails.map((ct, idx) => (
                  <div
                    key={idx}
                    onMouseDown={e => { e.preventDefault(); onSelect(ct.value); onClose() }}
                    style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '4px 16px 4px 16px', cursor: 'pointer' }}
                    onMouseEnter={e => { e.currentTarget.style.backgroundColor = 'var(--color-bg-elevated)' }}
                    onMouseLeave={e => { e.currentTarget.style.backgroundColor = 'transparent' }}
                  >
                    <svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="var(--color-text-muted)" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink: 0, opacity: 0.6 }}>
                      <rect x="1" y="3" width="14" height="10" rx="2" /><polyline points="1,3 8,9 15,3" />
                    </svg>
                    <span style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-primary)', fontFamily: 'var(--font-family-mono)' }}>{ct.value}</span>
                    {ct.label && (
                      <span style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)' }}>– {ct.label}</span>
                    )}
                    {hasMultiple && ct.primary && (
                      <span style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-text-muted)' }}>–</span>
                    )}
                    {hasMultiple && ct.primary && (
                      <span style={{ fontSize: 'var(--font-size-xs)', color: 'var(--color-success)', padding: '1px 6px', borderRadius: 'var(--radius-sm)', backgroundColor: 'var(--color-success-bg)' }}>primary</span>
                    )}
                  </div>
                ))}
                <div style={{ height: 3 }} />
              </div>
            )
          })}
        </div>
      </div>
    </div>
  )
}

// ── Main component ───────────────────────────────────────────────────────────
export default function ComposeEmailModal({
  open,
  onClose,
  defaultTo = '',
  defaultSubject = '',
  defaultFromAccount = '',
  requireFromAccount = true,
  defaultServerAttachments = [],
  defaultAttachments = [],
  customerId = null,
  onSent,
}) {
  const { toast } = useToast()

  const [toList, setToList] = useState(defaultTo ? [defaultTo] : [])
  const [toInput, setToInput] = useState('')
  const [ccList, setCcList] = useState([])
  const [ccInput, setCcInput] = useState('')
  const [subject, setSubject] = useState(defaultSubject)
  const [fromAccount, setFromAccount] = useState(defaultFromAccount || '')
  const [mailAccounts, setMailAccounts] = useState([])
  const [attachments, setAttachments] = useState([])
  const [sending, setSending] = useState(false)
  const [error, setError] = useState('')
  const [quillLoaded, setQuillLoaded] = useState(() => !!window.Quill)
  const [showCustomerPicker, setShowCustomerPicker] = useState(false)
  const [showServerFiles, setShowServerFiles] = useState(false)
  const [serverFiles, setServerFiles] = useState([])
  const [serverFilesLoading, setServerFilesLoading] = useState(false)
  const [serverFileSearch, setServerFileSearch] = useState('')
  const [serverFileType, setServerFileType] = useState('all')
  const [previewFile, setPreviewFile] = useState(null)
  const [editorPreviewDark, setEditorPreviewDark] = useState(true)

  const editorRef = useRef(null)
  const quillRef = useRef(null)
  const fileInputRef = useRef(null)

  // Reset when opened
  useEffect(() => {
    if (open) {
      setToList(defaultTo ? [defaultTo] : [])
      setToInput('')
      setCcList([])
      setCcInput('')
      setSubject(defaultSubject)
      setFromAccount(defaultFromAccount || '')
      setAttachments(defaultAttachments.length ? defaultAttachments.map(f => ({ file: f, name: f.name })) : [])
      setError('')
      setEditorPreviewDark(true)
    }
  }, [open])  // eslint-disable-line react-hooks/exhaustive-deps

  // Load mail accounts
  useEffect(() => {
    if (!open) return
    let cancelled = false
    api.get('/crm/comms/email/accounts')
      .then(data => {
        if (cancelled) return
        const accounts = data.accounts || []
        setMailAccounts(accounts)
        if (!defaultFromAccount && accounts.length === 1) setFromAccount(accounts[0].key)
      })
      .catch(() => { if (!cancelled) setMailAccounts([]) })
    return () => { cancelled = true }
  }, [open, defaultFromAccount])

  // Load Quill from CDN
  useEffect(() => {
    if (!open) return
    loadQuill(() => setQuillLoaded(true))
  }, [open])

  // ESC to close
  useEffect(() => {
    if (!open) return
    const handler = e => { if (e.key === 'Escape') onClose() }
    window.addEventListener('keydown', handler)
    return () => window.removeEventListener('keydown', handler)
  }, [open, onClose])

  // Init / teardown Quill
  useEffect(() => {
    if (!open || !quillLoaded) return
    // editorRef.current is always mounted (div is hidden, not unmounted)
    if (!editorRef.current || quillRef.current) return

    const quill = new window.Quill(editorRef.current, {
      theme: 'snow',
      placeholder: 'Write your message...',
      modules: {
        toolbar: [
          [{ size: ['small', false, 'large', 'huge'] }],
          ['bold', 'italic', 'underline', 'strike'],
          [{ color: [] }, { background: [] }],
          [{ align: [] }],
          [{ list: 'ordered' }, { list: 'bullet' }, { indent: '-1' }, { indent: '+1' }],
          ['code-block', 'blockquote', 'link', 'image'],
          ['clean'],
        ],
        clipboard: { matchVisual: false },
      },
    })
    quillRef.current = quill

    // Force single-row toolbar (defeats Quill's float-based layout)
    const container = editorRef.current
    const toolbar = container.querySelector('.ql-toolbar')
    const qlContainer = container.querySelector('.ql-container')
    if (toolbar && qlContainer) {
      container.style.cssText += ';display:flex!important;flex-direction:column!important;'
      toolbar.style.cssText += ';display:flex!important;flex-wrap:nowrap!important;align-items:center!important;flex-shrink:0!important;overflow:visible!important;padding:3px 8px!important;'
      toolbar.querySelectorAll('.ql-formats').forEach(el => {
        el.style.cssText += ';float:none!important;display:inline-flex!important;flex-wrap:nowrap!important;align-items:center!important;flex-shrink:0!important;'
      })
      toolbar.querySelectorAll('button').forEach(el => {
        el.style.cssText += ';float:none!important;display:inline-flex!important;align-items:center!important;justify-content:center!important;width:24px!important;height:24px!important;'
      })
      toolbar.querySelectorAll('.ql-picker').forEach(el => {
        el.style.cssText += ';float:none!important;display:inline-flex!important;align-items:center!important;flex-shrink:0!important;height:24px!important;overflow:visible!important;'
      })
      qlContainer.style.cssText += ';flex:1!important;min-height:0!important;overflow:hidden!important;'
      qlContainer.querySelector('.ql-editor').style.cssText += ';height:100%!important;overflow-y:auto!important;'
    }

    // Sync color picker indicators with current selection
    const syncPickerIndicators = () => {
      if (!toolbar) return
      const formats = quill.getFormat()
      const textColor = typeof formats.color === 'string' ? formats.color : ''
      const highlightColor = typeof formats.background === 'string' ? formats.background : ''
      toolbar.style.setProperty('--ql-current-color', textColor || 'var(--color-text-secondary)')
      toolbar.style.setProperty('--ql-current-bg', highlightColor || 'var(--color-bg-elevated)')
      toolbar.classList.toggle('ql-has-bg-color', Boolean(highlightColor))

      const colorLabel =
        toolbar.querySelector('.ql-picker.ql-color-picker.ql-color .ql-picker-label') ||
        toolbar.querySelector('.ql-picker.ql-color .ql-picker-label')
      const bgLabel =
        toolbar.querySelector('.ql-picker.ql-color-picker.ql-background .ql-picker-label') ||
        toolbar.querySelector('.ql-picker.ql-background .ql-picker-label')

      if (colorLabel) {
        colorLabel.style.boxShadow = `inset 0 -3px 0 ${textColor || 'var(--color-text-secondary)'}`
        colorLabel.querySelectorAll('.ql-stroke, .ql-stroke-miter').forEach(el => {
          el.style.setProperty('stroke', textColor || 'var(--color-text-secondary)', 'important')
        })
        colorLabel.querySelectorAll('.ql-fill, .ql-color-label').forEach(el => {
          el.style.setProperty('fill', textColor || 'var(--color-text-secondary)', 'important')
        })
        let swatch = colorLabel.querySelector('.compose-picker-swatch')
        if (!swatch) {
          swatch = document.createElement('span')
          swatch.className = 'compose-picker-swatch'
          swatch.style.cssText = 'position:absolute;right:2px;top:2px;width:7px;height:7px;border-radius:999px;border:1px solid rgba(255,255,255,0.35);pointer-events:none;'
          colorLabel.appendChild(swatch)
        }
        swatch.style.background = textColor || 'var(--color-text-secondary)'
      }
      if (bgLabel) {
        bgLabel.style.boxShadow = highlightColor ? `inset 0 -7px 0 ${highlightColor}` : 'inset 0 -7px 0 transparent'
        bgLabel.style.borderBottom = '1px solid var(--color-border)'
        bgLabel.querySelectorAll('.ql-fill, .ql-color-label').forEach(el => {
          el.style.setProperty('fill', 'var(--color-text-secondary)', 'important')
        })
        let swatch = bgLabel.querySelector('.compose-picker-swatch')
        if (!swatch) {
          swatch = document.createElement('span')
          swatch.className = 'compose-picker-swatch'
          swatch.style.cssText = 'position:absolute;right:2px;top:2px;width:7px;height:7px;border-radius:999px;border:1px solid rgba(255,255,255,0.35);pointer-events:none;'
          bgLabel.appendChild(swatch)
        }
        swatch.style.background = highlightColor || 'transparent'
        swatch.style.borderColor = highlightColor ? 'rgba(255,255,255,0.35)' : 'var(--color-border)'
      }
    }
    quill.on('selection-change', syncPickerIndicators)
    quill.on('text-change', syncPickerIndicators)
    quill.on('editor-change', syncPickerIndicators)
    syncPickerIndicators()

    // Ctrl+V image paste
    quill.root.addEventListener('paste', e => {
      const items = (e.clipboardData || e.originalEvent?.clipboardData)?.items
      if (!items) return
      for (const item of items) {
        if (item.type.startsWith('image/')) {
          e.preventDefault()
          const blob = item.getAsFile()
          const reader = new FileReader()
          reader.onload = ev => {
            const range = quill.getSelection(true)
            quill.insertEmbed(range.index, 'image', ev.target.result)
          }
          reader.readAsDataURL(blob)
          break
        }
      }
    })

    return () => {
      quill.off('selection-change', syncPickerIndicators)
      quill.off('text-change', syncPickerIndicators)
      quill.off('editor-change', syncPickerIndicators)
      quillRef.current = null
    }
  }, [open, quillLoaded])

  // Apply light/dark preview to the Quill editor area
  useEffect(() => {
    const qlContainer = editorRef.current?.querySelector('.ql-container')
    const qlEditor = editorRef.current?.querySelector('.ql-editor')
    if (!qlContainer || !qlEditor) return
    if (editorPreviewDark) {
      qlContainer.style.backgroundColor = ''
      qlEditor.style.color = ''
      qlEditor.style.backgroundColor = ''
    } else {
      qlContainer.style.backgroundColor = '#ffffff'
      qlEditor.style.color = '#1a1a1a'
      qlEditor.style.backgroundColor = '#ffffff'
    }
  }, [editorPreviewDark, quillLoaded])

  const getContent = useCallback(() => {
    const q = quillRef.current
    if (!q) return { html: '', text: '' }
    return { html: q.root.innerHTML, text: q.getText().trim() }
  }, [])

  // File attachment helpers
  const handleFileAdd = files => {
    setAttachments(prev => [...prev, ...Array.from(files).map(f => ({ file: f, name: f.name }))])
  }

  // Pre-attach server files passed via props
  useEffect(() => {
    if (!open || !defaultServerAttachments?.length) return
    let cancelled = false;
    (async () => {
      for (const f of defaultServerAttachments) {
        try {
          const token = localStorage.getItem('access_token')
          const resp = await fetch(`/api/crm/nextcloud/file?path=${encodeURIComponent(f.path)}&token=${encodeURIComponent(token)}`)
          if (!resp.ok) continue
          const blob = await resp.blob()
          if (cancelled) return
          const file = new File([blob], f.filename, { type: f.mime_type || 'application/octet-stream' })
          setAttachments(prev => prev.some(a => a.name === f.filename) ? prev : [...prev, { file, name: f.filename }])
        } catch { /* ignore pre-attachment failures */ }
      }
    })()
    return () => { cancelled = true }
  }, [open, defaultServerAttachments])

  // Server file picker
  const openServerFiles = async () => {
    setShowServerFiles(true)
    setServerFileSearch('')
    setServerFileType('all')
    if (serverFiles.length > 0) return
    setServerFilesLoading(true)
    try {
      const token = localStorage.getItem('access_token')
      const resp = await fetch(`/api/crm/nextcloud/browse-all?customer_id=${customerId}`, {
        headers: { Authorization: `Bearer ${token}` },
      })
      if (!resp.ok) throw new Error('Failed to load files')
      const data = await resp.json()
      setServerFiles((data.items || []).filter(f => !f.is_dir))
    } catch {
      setServerFiles([])
    } finally {
      setServerFilesLoading(false)
    }
  }

  const attachServerFile = async f => {
    try {
      const token = localStorage.getItem('access_token')
      const resp = await fetch(`/api/crm/nextcloud/file?path=${encodeURIComponent(f.path)}&token=${encodeURIComponent(token)}`)
      if (!resp.ok) throw new Error('Download failed')
      const blob = await resp.blob()
      const file = new File([blob], f.filename, { type: f.mime_type || 'application/octet-stream' })
      setAttachments(prev => [...prev, { file, name: f.filename }])
    } catch (err) {
      setError(`Could not attach ${f.filename}: ${err.message}`)
    }
    setShowServerFiles(false)
  }

  function getFileCategory(f) {
    const mime = f.mime_type || ''
    const sub = f.subfolder || ''
    if (sub === 'quotations') return 'quotation'
    if (mime === 'application/pdf' || sub.includes('invoic') || sub.includes('document')) return 'document'
    if (mime.startsWith('image/') || mime.startsWith('video/') || mime.startsWith('audio/') || sub.includes('media')) return 'media'
    return 'document'
  }

  const handleSend = async () => {
    const { html, text } = getContent()
    // Commit any pending chip input before validation
    const finalToList = [...toList]
    if (toInput.trim()) {
      toInput.split(',').map(s => s.trim()).filter(s => EMAIL_RE.test(s)).forEach(e => { if (!finalToList.includes(e)) finalToList.push(e) })
    }
    const finalCcList = [...ccList]
    if (ccInput.trim()) {
      ccInput.split(',').map(s => s.trim()).filter(s => EMAIL_RE.test(s)).forEach(e => { if (!finalCcList.includes(e)) finalCcList.push(e) })
    }

    const ATTACH_LIMIT_MB = 25
    const totalAttachMB = attachments.reduce((s, a) => s + (a.file?.size ?? 0), 0) / (1024 * 1024)

    if (requireFromAccount && !fromAccount) { setError('Please select a sender account.'); return }
    if (finalToList.length === 0) { setError('Please enter a recipient email address.'); return }
    if (!subject.trim()) { setError('Please enter a subject.'); return }
    if (!text && !html.replace(/<[^>]*>/g, '').trim()) { setError('Please write a message.'); return }
    if (totalAttachMB > ATTACH_LIMIT_MB) { setError(`Attachments exceed the ${ATTACH_LIMIT_MB} MB limit (${totalAttachMB.toFixed(1)} MB total).`); return }

    setError('')
    setSending(true)
    try {
      const token = localStorage.getItem('access_token')
      const fd = new FormData()
      if (customerId) fd.append('customer_id', customerId)
      if (fromAccount) fd.append('from_account', fromAccount)
      fd.append('to', finalToList[0])
      fd.append('subject', subject.trim())
      fd.append('body', text)
      fd.append('body_html', html)
      fd.append('cc', JSON.stringify(finalCcList))
      for (const { file } of attachments) fd.append('files', file, file.name)

      const resp = await fetch('/api/crm/comms/email/send', {
        method: 'POST',
        headers: { Authorization: `Bearer ${token}` },
        body: fd,
      })
      if (!resp.ok) {
        const err = await resp.json().catch(() => ({}))
        throw new Error(err.detail || `Server error ${resp.status}`)
      }
      const data = await resp.json()
      toast.success('Email sent', `Message delivered to ${finalToList[0]}.`)
      onSent?.(data.entry || data)
      onClose()
    } catch (err) {
      setError(err.message || 'Failed to send email.')
    } finally {
      setSending(false)
    }
  }

  if (!open) return null

  const ATTACH_LIMIT_MB = 25
  const totalAttachMB = attachments.reduce((s, a) => s + (a.file?.size ?? 0), 0) / (1024 * 1024)
  const attachColor = totalAttachMB > 20 ? 'var(--color-danger)' : totalAttachMB > 15 ? '#f59e0b' : 'var(--color-text-muted)'

  const fieldLabel = {
    fontSize: 'var(--font-size-xs)',
    fontWeight: 'var(--font-weight-semibold)',
    color: 'var(--color-text-secondary)',
    marginBottom: 'var(--space-1)',
    display: 'block',
    textTransform: 'uppercase',
    letterSpacing: 'var(--tracking-wide)',
  }

  return (
    <>
      {/* ── Backdrop ── */}
      <div style={{ position: 'fixed', inset: 0, zIndex: 1000, backgroundColor: 'rgba(0,0,0,0.55)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 64 }}>

        {/* ── Dialog ── */}
        <div style={{ backgroundColor: 'var(--color-bg-surface)', border: '1px solid var(--color-border-strong)', borderRadius: 12, width: '100%', height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden', boxShadow: 'var(--shadow-lg)' }}>

          {/* Header */}
          <div className="flex items-center justify-between px-5 py-4" style={{ borderBottom: '1px solid var(--color-border-strong)', flexShrink: 0 }}>
            <h2 style={{ margin: 0, fontSize: 15, fontWeight: 600, color: 'var(--color-text-primary)' }}>New Email</h2>
            <button type="button" onClick={onClose} className="hover:opacity-70" style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--color-text-muted)', fontSize: 20, lineHeight: 1, padding: 2 }}>×</button>
          </div>

          {/* Fields */}
          <div className="px-5 py-4" style={{ borderBottom: '1px solid var(--color-border)', flexShrink: 0 }}>
            <div style={{ display: 'grid', gridTemplateColumns: '200px 1fr 1fr', gap: 10, marginBottom: 10 }}>
              <div>
                <label style={fieldLabel}>Send As</label>
                <Select value={fromAccount} onChange={e => setFromAccount(e.target.value)} placeholder="Select sender...">
                  <option value="">Select sender...</option>
                  {mailAccounts.filter(a => a.allow_send).map(a => (
                    <option key={a.key} value={a.key}>{a.label} ({a.email})</option>
                  ))}
                </Select>
              </div>
              <div>
                <label style={fieldLabel}>To</label>
                <EmailChipInput list={toList} setList={setToList} inputVal={toInput} setInputVal={setToInput} placeholder="recipient@example.com" onOpenPicker={() => setShowCustomerPicker(true)} />
              </div>
              <div>
                <label style={fieldLabel}>CC</label>
                <EmailChipInput list={ccList} setList={setCcList} inputVal={ccInput} setInputVal={setCcInput} placeholder="cc@example.com" />
              </div>
            </div>
            <div>
              <label style={fieldLabel}>Subject</label>
              <input className="input" value={subject} onChange={e => setSubject(e.target.value)} placeholder="Email subject" />
            </div>
          </div>

          {/* Quill editor */}
          <div className="quill-compose-wrapper" style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0, position: 'relative' }}>
            <div style={{ position: 'absolute', top: 8, right: 12, zIndex: 20 }}>
              <button
                type="button"
                onClick={() => setEditorPreviewDark(v => !v)}
                title={editorPreviewDark ? 'Switch to light preview' : 'Switch to dark preview'}
                style={{ padding: '3px 10px', fontSize: 11, borderRadius: 6, cursor: 'pointer', border: '1px solid var(--color-border)', backgroundColor: editorPreviewDark ? 'rgba(0,0,0,0.45)' : 'rgba(255,255,255,0.7)', color: editorPreviewDark ? 'var(--color-text-secondary)' : '#333', backdropFilter: 'blur(4px)' }}
              >
                {editorPreviewDark ? '☀ Light' : '🌙 Dark'}
              </button>
            </div>
            {/* editorRef div is always mounted so the ref is available when Quill initialises */}
            <div ref={editorRef} style={{ flex: 1, minHeight: 0, display: quillLoaded ? 'block' : 'none' }} />
            {!quillLoaded && (
              <div className="flex items-center justify-center gap-2 py-12" style={{ color: 'var(--color-text-muted)' }}>
                <Spinner size="sm" /> Loading editor…
              </div>
            )}
          </div>

          {/* Attachments */}
          {attachments.length > 0 && (
            <div className="px-5 py-3 flex flex-wrap gap-2" style={{ borderTop: '1px solid var(--color-border)', flexShrink: 0 }}>
              {attachments.map((a, i) => (
                <AttachmentPill key={i} name={a.name} size={a.file.size} onRemove={() => setAttachments(prev => prev.filter((_, j) => j !== i))} />
              ))}
            </div>
          )}

          {/* Footer */}
          <div className="flex items-center justify-between px-5 py-4" style={{ borderTop: '1px solid var(--color-border-strong)', flexShrink: 0 }}>
            {/* Left: attach + signature */}
            <div className="flex items-center gap-2 flex-wrap">
              {customerId && (
                <Button variant="ghost" size="sm" onClick={openServerFiles}>🗂 From Server</Button>
              )}
              <Button variant="ghost" size="sm" onClick={() => fileInputRef.current?.click()}>📎 Attach</Button>
              <input ref={fileInputRef} type="file" multiple style={{ display: 'none' }} onChange={e => handleFileAdd(e.target.files)} />
              <Button
                variant="ghost"
                size="sm"
                title="Append your saved signature"
                onClick={() => {
                  const sig = localStorage.getItem('mail_signature') || ''
                  if (!sig.trim() || sig === '<p><br></p>') return
                  const q = quillRef.current
                  if (!q) return
                  q.clipboard.dangerouslyPasteHTML(q.root.innerHTML + '<p><br></p><hr/><div class="mail-sig">' + sig + '</div>')
                }}
              >
                ✍ Signature
              </Button>
              {attachments.length > 0 && (
                <span style={{ fontSize: 12, color: attachColor, fontWeight: totalAttachMB > ATTACH_LIMIT_MB ? 700 : 400 }}>
                  {totalAttachMB.toFixed(1)}/{ATTACH_LIMIT_MB} MB{totalAttachMB > ATTACH_LIMIT_MB ? ' — too large' : ''}
                </span>
              )}
              <span style={{ fontSize: 11, color: 'var(--color-text-muted)' }}>Tip: paste images with Ctrl+V</span>
            </div>

            {/* Right: error + actions */}
            <div className="flex items-center gap-3">
              {error && <span style={{ fontSize: 12, color: 'var(--color-danger)' }}>{error}</span>}
              <Button variant="ghost" size="sm" onClick={onClose} disabled={sending}>Cancel</Button>
              <Button variant="primary" size="sm" onClick={handleSend} loading={sending} disabled={sending}>Send</Button>
            </div>
          </div>
        </div>
      </div>

      {/* ── Customer email picker ── */}
      <CustomerEmailPicker
        open={showCustomerPicker}
        onClose={() => setShowCustomerPicker(false)}
        onSelect={email => setToList(prev => prev.includes(email) ? prev : [...prev, email])}
      />

      {/* ── Server file picker ── */}
      {showServerFiles && (
        <div
          style={{ position: 'fixed', inset: 0, zIndex: 1100, backgroundColor: 'rgba(0,0,0,0.6)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
          onClick={() => setShowServerFiles(false)}
        >
          <div
            style={{ backgroundColor: 'var(--color-bg-surface)', border: '1px solid var(--color-border-strong)', borderRadius: 12, width: 540, maxHeight: '70vh', display: 'flex', flexDirection: 'column', overflow: 'hidden', boxShadow: 'var(--shadow-lg)' }}
            onClick={e => e.stopPropagation()}
          >
            <div className="flex items-center justify-between px-5 py-4" style={{ borderBottom: '1px solid var(--color-border-strong)', flexShrink: 0 }}>
              <h3 style={{ margin: 0, fontSize: 14, fontWeight: 600, color: 'var(--color-text-primary)' }}>Attach File from Server</h3>
              <button type="button" onClick={() => setShowServerFiles(false)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--color-text-muted)', fontSize: 20, lineHeight: 1 }}>×</button>
            </div>
            <div className="px-4 py-3 flex gap-2" style={{ borderBottom: '1px solid var(--color-border)', flexShrink: 0 }}>
              <input
                type="text"
                placeholder="Search by filename..."
                value={serverFileSearch}
                onChange={e => setServerFileSearch(e.target.value)}
                autoFocus
                style={{ ...nativeInput, flex: 1 }}
              />
              <select value={serverFileType} onChange={e => setServerFileType(e.target.value)} style={{ ...nativeInput, width: 'auto' }}>
                <option value="all">All types</option>
                <option value="document">Documents</option>
                <option value="quotation">Quotations</option>
                <option value="media">Media</option>
              </select>
            </div>
            <div style={{ overflowY: 'auto', flex: 1 }}>
              {serverFilesLoading && (
                <div className="flex items-center justify-center gap-2 py-8" style={{ color: 'var(--color-text-muted)', fontSize: 13 }}>
                  <Spinner size="sm" /> Loading files…
                </div>
              )}
              {!serverFilesLoading && serverFiles.length === 0 && (
                <div className="text-center py-8 text-sm" style={{ color: 'var(--color-text-muted)' }}>No files found for this customer.</div>
              )}
              {!serverFilesLoading && (() => {
                const token = localStorage.getItem('access_token')
                const filtered = serverFiles.filter(f => {
                  const matchSearch = !serverFileSearch.trim() || f.filename.toLowerCase().includes(serverFileSearch.toLowerCase())
                  const matchType = serverFileType === 'all' || getFileCategory(f) === serverFileType
                  return matchSearch && matchType && !f.is_dir
                })
                if (filtered.length === 0 && serverFiles.length > 0) {
                  return <div className="text-center py-8 text-sm" style={{ color: 'var(--color-text-muted)' }}>No files match your search.</div>
                }
                const catColors = {
                  quotation: { bg: '#1a2d1e', color: '#6aab7a' },
                  document:  { bg: '#1e1a2d', color: '#a78bfa' },
                  media:     { bg: '#2d2a1a', color: '#c9a84c' },
                }
                return filtered.map(f => {
                  const alreadyAttached = attachments.some(a => a.name === f.filename)
                  const cat = getFileCategory(f)
                  const c = catColors[cat] || catColors.document
                  const kb = f.size > 0 ? `${(f.size / 1024).toFixed(0)} KB` : ''
                  const isImage = (f.mime_type || '').startsWith('image/')
                  const thumbUrl = `/api/crm/nextcloud/file?path=${encodeURIComponent(f.path)}&token=${encodeURIComponent(token)}`

                  const thumb = isImage
                    ? <img src={thumbUrl} alt="" onClick={e => { e.stopPropagation(); setPreviewFile(f) }} style={{ width: 40, height: 40, objectFit: 'cover', borderRadius: 5, flexShrink: 0, cursor: 'zoom-in', border: '1px solid var(--color-border)' }} />
                    : (
                      <div onClick={e => { e.stopPropagation(); setPreviewFile(f) }} style={{ width: 40, height: 40, borderRadius: 5, flexShrink: 0, backgroundColor: c.bg, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 20, cursor: 'zoom-in', border: '1px solid var(--color-border)' }}>
                        {cat === 'quotation' ? '🧾' : cat === 'media' ? '🎵' : '📄'}
                      </div>
                    )

                  return (
                    <div
                      key={f.path}
                      onClick={() => !alreadyAttached && attachServerFile(f)}
                      style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '8px 16px', borderBottom: '1px solid var(--color-border)', cursor: alreadyAttached ? 'default' : 'pointer', opacity: alreadyAttached ? 0.5 : 1 }}
                      onMouseEnter={e => { if (!alreadyAttached) e.currentTarget.style.backgroundColor = 'var(--color-bg-elevated)' }}
                      onMouseLeave={e => { e.currentTarget.style.backgroundColor = '' }}
                    >
                      {thumb}
                      <span className="flex-1 text-sm truncate" style={{ color: 'var(--color-text-primary)' }}>{f.filename}</span>
                      <span className="text-xs flex-shrink-0" style={{ color: 'var(--color-text-muted)' }}>{kb}</span>
                      {alreadyAttached
                        ? <span style={{ fontSize: 11, color: 'var(--color-primary)' }}>Attached</span>
                        : <span style={{ fontSize: 11, padding: '2px 7px', borderRadius: 10, backgroundColor: c.bg, color: c.color, fontWeight: 500, flexShrink: 0, whiteSpace: 'nowrap' }}>{cat}</span>
                      }
                    </div>
                  )
                })
              })()}
            </div>
          </div>
        </div>
      )}

      {/* ── File preview ── */}
      {previewFile && (() => {
        const token = localStorage.getItem('access_token')
        const fileUrl = `/api/crm/nextcloud/file?path=${encodeURIComponent(previewFile.path)}&token=${encodeURIComponent(token)}`
        const mime = previewFile.mime_type || ''
        const isImage = mime.startsWith('image/')
        const isPdf = mime === 'application/pdf'
        return (
          <div
            style={{ position: 'fixed', inset: 0, zIndex: 1200, backgroundColor: 'rgba(0,0,0,0.8)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 40 }}
            onClick={() => setPreviewFile(null)}
          >
            <div
              style={{ position: 'relative', maxWidth: '90vw', maxHeight: '85vh', display: 'flex', flexDirection: 'column', borderRadius: 10, overflow: 'hidden', backgroundColor: 'var(--color-bg-surface)', boxShadow: 'var(--shadow-lg)' }}
              onClick={e => e.stopPropagation()}
            >
              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '10px 16px', borderBottom: '1px solid var(--color-border-strong)', flexShrink: 0 }}>
                <span style={{ fontSize: 13, fontWeight: 500, color: 'var(--color-text-primary)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: 400 }}>{previewFile.filename}</span>
                <div style={{ display: 'flex', gap: 8, marginLeft: 16, flexShrink: 0, alignItems: 'center' }}>
                  <a href={fileUrl} download={previewFile.filename} style={{ fontSize: 12, color: 'var(--color-primary)', textDecoration: 'none' }}>Download</a>
                  <button onClick={() => setPreviewFile(null)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--color-text-muted)', fontSize: 20, lineHeight: 1 }}>×</button>
                </div>
              </div>
              <div style={{ flex: 1, minHeight: 0, overflow: 'auto', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 16, backgroundColor: 'var(--color-bg-base)' }}>
                {isImage && <img src={fileUrl} alt={previewFile.filename} style={{ maxWidth: '80vw', maxHeight: '70vh', objectFit: 'contain', borderRadius: 6 }} />}
                {isPdf && <iframe src={fileUrl} title={previewFile.filename} style={{ width: '75vw', height: '70vh', border: 'none', borderRadius: 6 }} />}
                {!isImage && !isPdf && <span style={{ color: 'var(--color-text-muted)', fontSize: 13 }}>No preview available.</span>}
              </div>
            </div>
          </div>
        )
      })()}
    </>
  )
}
