/* OfficeTrack — chat UI components (lean set for the support chat).
   Exported on `window` for the app scripts to consume. */

const NCColors = {
  lavender: '#9F95E3', lavenderLight: '#F4F1FB', lavender200: '#CFC8F0',
  navy: '#15296B', navyDark: '#0E1F55', navy900: '#07112E',
  yellow: '#F5A623', yellowSoft: '#FFD37A', blue: '#2E7BC4', cobalt: '#1A4FE8',
  white: '#FFFFFF',
  gray25: '#FAFAFB', gray50: '#F4F4F6', gray100: '#ECECEF', gray200: '#DCDCE2',
  gray300: '#C4C4CC', gray400: '#9A9AA5', gray500: '#707079', gray600: '#4A4A52',
  gray700: '#2E2E36', gray900: '#0F0F14',
  success: '#5DB85D', danger: '#DA5350', warning: '#F0AD4E',
};

/* ───────── OfficeTrack duotone icons (lavender fill + navy stroke + yellow) ───────── */
const OTIcons = {
  clock: (
    <svg viewBox="0 0 32 32" width="22" height="22" aria-hidden="true">
      <circle cx="16" cy="16" r="12" fill={NCColors.lavender200} stroke={NCColors.navy} strokeWidth="2.6"/>
      <path d="M16 9v7l5 3" fill="none" stroke={NCColors.yellow} strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round"/>
    </svg>
  ),
  pin: (
    <svg viewBox="0 0 32 32" width="20" height="20" aria-hidden="true">
      <path d="M16 4c-4.4 0-8 3.6-8 8 0 6 8 16 8 16s8-10 8-16c0-4.4-3.6-8-8-8z"
            fill={NCColors.lavender200} stroke={NCColors.navy} strokeWidth="2.4" strokeLinejoin="round"/>
      <circle cx="16" cy="12" r="3.2" fill={NCColors.yellow}/>
    </svg>
  ),
};

/* ───────── header (brand + language toggle + reset) ───────── */
function ChatHeader({ t, lang, onLang, brandColor, brandName, onReset }) {
  return (
    <div className="nc-header" style={{ background: brandColor }}>
      <div className="nc-header-left">
        <div className="nc-logo-mark">
          <img src="assets/elcuatro-logo.png" alt="El Cuatro TV"
               style={{ width: 30, height: 30, borderRadius: '50%',
                        display: 'block', objectFit: 'cover' }}/>
        </div>
        <div className="nc-header-titles">
          <div className="nc-header-brand">{brandName}</div>
          <div className="nc-header-status">
            <span className="nc-online-dot"/>{t.online}
          </div>
        </div>
      </div>
      <div className="nc-header-actions">
        {onLang && (
          <div className="nc-lang-toggle" role="group" aria-label="language">
            {['es', 'en', 'pt', 'he'].map(code => (
              <button key={code}
                      className={'nc-lang-btn' + (code === lang ? ' active' : '')}
                      onClick={() => code !== lang && onLang(code)}>
                {code.toUpperCase()}
              </button>
            ))}
          </div>
        )}
        {onReset && (
          <button className="nc-header-reset" onClick={onReset}
                  title={t.resetTitle || 'Start over'} aria-label="Start over">
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none"
                 stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
              <polyline points="1 4 1 10 7 10"/>
              <path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/>
            </svg>
          </button>
        )}
      </div>
    </div>
  );
}

/* ───────── text cleanup + lightweight markdown ─────────
   The LLM sometimes emits empty ```json fences or markdown like **bold** /
   bullet lists. Strip empty fences and render a minimal markdown subset. */
function cleanAiText(s) {
  if (!s) return '';
  s = s.replace(/```[a-zA-Z0-9_-]*\s*```/g, '');
  s = s.replace(/\n{3,}/g, '\n\n');
  return s.trim();
}

function renderInline(s) {
  const parts = [];
  const re = /\*\*(.+?)\*\*/g;
  let i = 0, m;
  while ((m = re.exec(s)) !== null) {
    if (m.index > i) parts.push(s.slice(i, m.index));
    parts.push(<strong key={parts.length}>{m[1]}</strong>);
    i = re.lastIndex;
  }
  if (i < s.length) parts.push(s.slice(i));
  return parts;
}

function RichText({ text }) {
  if (!text) return null;
  const lines = text.split('\n');
  const blocks = [];
  let listBuf = [];
  const flush = () => {
    if (listBuf.length) {
      blocks.push(
        <ul key={'l' + blocks.length} className="nc-rt-ul">
          {listBuf.map((it, i) => <li key={i}>{renderInline(it)}</li>)}
        </ul>);
      listBuf = [];
    }
  };
  for (const raw of lines) {
    const line = raw.trim();
    if (!line) { flush(); continue; }
    const m = line.match(/^[-•*]\s+(.+)/);
    if (m) { listBuf.push(m[1]); }
    else { flush(); blocks.push(<p key={'p' + blocks.length} className="nc-rt-p">{renderInline(line)}</p>); }
  }
  flush();
  return <>{blocks}</>;
}

/* ───────── chat bubble ───────── */
function ChatMsg({ from, children, brandColor }) {
  return (
    <div className={'nc-msg nc-msg-' + from}>
      {from === 'ai' && (
        <div className="nc-msg-avatar" style={{ background: brandColor }}>
          <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#fff" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
            <path d="M12 3 L21 8 V16 L12 21 L3 16 V8 Z"/>
            <circle cx="12" cy="12" r="3" fill="#fff"/>
          </svg>
        </div>
      )}
      <div className={'nc-bubble nc-bubble-' + from}
           style={from === 'me' ? { background: brandColor, color: '#fff' } : null}>
        {children}
      </div>
    </div>
  );
}

/* ───────── typing dots ───────── */
function TypingDots({ brandColor }) {
  return (
    <div className="nc-msg nc-msg-ai">
      <div className="nc-msg-avatar" style={{ background: brandColor }}>
        <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="#fff" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
          <path d="M12 3 L21 8 V16 L12 21 L3 16 V8 Z"/><circle cx="12" cy="12" r="3" fill="#fff"/>
        </svg>
      </div>
      <div className="nc-bubble nc-bubble-ai nc-typing"><span/><span/><span/></div>
    </div>
  );
}

/* ───────── suggestion chips (used by the multi-account picker) ───────── */
function SuggestionChips({ items, onPick, brandColor }) {
  return (
    <div className="nc-suggestions">
      {items.map((it, i) => (
        <button key={i} className="nc-chip" onClick={() => onPick(it)}>
          {it.icon && <span className="nc-chip-icon" style={{ color: brandColor }}>{it.icon}</span>}
          <span>{it.label}</span>
        </button>
      ))}
    </div>
  );
}

/* ───────── composer (text + photo upload + voice) ───────── */
function Composer({ t, value, onChange, onSend, brandColor, disabled,
                    allowImage, awaitingImage, pendingImage,
                    onPickImage, onClearImage,
                    voiceSupported, listening, onVoiceStart, onVoiceStop }) {
  const ref = React.useRef(null);
  const fileRef = React.useRef(null);
  const canSend = !disabled && (value.trim() || pendingImage);
  const attachHot = awaitingImage && !pendingImage;
  // Mic is independent of the camera: it shows when there's nothing to send
  // (empty input) OR while actively listening (so you can tap to stop).
  const showMic = voiceSupported && !pendingImage && (listening || !value.trim());
  return (
    <div className={'nc-composer-wrap' + (disabled ? ' nc-composer-disabled' : '')}>
      {pendingImage && (
        <div className="nc-photo-preview">
          <img src={pendingImage} alt="" />
          <span className="nc-photo-preview-label">{t.photoSelected}</span>
          <button className="nc-photo-preview-x" onClick={onClearImage}
                  aria-label={t.photoRemove}>×</button>
        </div>
      )}
      <div className="nc-composer">
        {allowImage && (
          <>
            <input ref={fileRef} type="file" accept="image/*" capture="environment"
                   style={{ display: 'none' }}
                   onChange={e => {
                     const f = e.target.files && e.target.files[0];
                     if (f && onPickImage) onPickImage(f);
                     e.target.value = '';
                   }}/>
            <button className={'nc-comp-icon' + (attachHot ? ' nc-comp-icon-hot' : '')}
                    aria-label={t.attachLabel} disabled={disabled}
                    title={t.attachLabel}
                    onClick={() => fileRef.current && fileRef.current.click()}
                    style={attachHot ? { color: brandColor } : null}>
              <svg width="20" height="20" viewBox="0 0 24 24" fill="none"
                   stroke={attachHot ? brandColor : NCColors.gray500}
                   strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
                <path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/>
                <circle cx="12" cy="13" r="4"/>
              </svg>
            </button>
          </>
        )}
        <div className="nc-comp-input">
          <input ref={ref} value={value} disabled={disabled}
                 onChange={e => onChange(e.target.value)}
                 onKeyDown={e => { if (e.key === 'Enter' && canSend) onSend(); }}
                 placeholder={t.chatPlaceholder}/>
        </div>
        {showMic ? (
          <button className={'nc-comp-mic' + (listening ? ' nc-comp-mic-on' : '')}
                  onPointerDown={(e) => { if (!disabled && onVoiceStart) { e.preventDefault(); onVoiceStart(); } }}
                  onPointerUp={(e) => { if (onVoiceStop) { e.preventDefault(); onVoiceStop(); } }}
                  onPointerLeave={() => onVoiceStop && onVoiceStop()}
                  onPointerCancel={() => onVoiceStop && onVoiceStop()}
                  onContextMenu={(e) => e.preventDefault()}
                  disabled={disabled}
                  aria-label={t.voiceLabel}
                  title={listening ? t.voiceListening : (t.voiceHold || t.voiceLabel)}
                  style={{ ...(listening ? { background: brandColor, color: '#fff' } : { color: brandColor }),
                           touchAction: 'none', userSelect: 'none' }}>
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none"
                 stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
              <rect x="9" y="2" width="6" height="13" rx="3"/>
              <path d="M5 11a7 7 0 0014 0"/>
              <line x1="12" y1="19" x2="12" y2="23"/>
              <line x1="8" y1="23" x2="16" y2="23"/>
            </svg>
          </button>
        ) : (
          <button className="nc-comp-send" onClick={() => canSend && onSend()}
                  disabled={!canSend}
                  style={{ background: brandColor, opacity: canSend ? 1 : 0.4 }}
                  aria-label={t.sendLabel}>
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
              <line x1="22" y1="2" x2="11" y2="13"/>
              <polygon points="22 2 15 22 11 13 2 9 22 2"/>
            </svg>
          </button>
        )}
      </div>
      {listening && (
        <div style={{ padding: '0 14px 8px', fontSize: 12, color: brandColor,
                      fontWeight: 600 }}>{t.voiceListening}</div>
      )}
    </div>
  );
}

/* ───────── slot chips (available times → tappable cards) ───────── */
function SlotChips({ slots, onPick, disabled }) {
  return (
    <div className="nc-slotchips">
      {slots.map((s, i) => (
        <button key={i} className="nc-slotchip" disabled={disabled}
                onClick={() => !disabled && onPick(s)}>
          <span className="nc-slotchip-ic">{OTIcons.clock}</span>
          <span className="nc-slotchip-txt">
            <span className="nc-slotchip-date">{s.label}</span>
            {s.time && <span className="nc-slotchip-time">{s.time}</span>}
          </span>
        </button>
      ))}
    </div>
  );
}

/* ───────── location sheet (Leaflet map, confirm before booking) ─────────
   Bottom-sheet with an interactive OSM map: detect the customer's position,
   let them drag the pin, reverse-geocode the address, and confirm. */
function LocationSheet({ t, open, brandColor, onConfirm, onCancel, initialCoords }) {
  const mapRef = React.useRef(null);
  const mapObj = React.useRef(null);
  const markerRef = React.useRef(null);
  const geoTimer = React.useRef(null);
  const [coords, setCoords] = React.useState(null);
  const [address, setAddress] = React.useState('');
  const [loadingAddr, setLoadingAddr] = React.useState(false);

  function reverseGeocode(lat, lng, lang) {
    setLoadingAddr(true);
    clearTimeout(geoTimer.current);
    geoTimer.current = setTimeout(() => {
      const url = 'https://nominatim.openstreetmap.org/reverse?format=jsonv2'
        + `&lat=${lat}&lon=${lng}&accept-language=${lang || 'es'}`;
      fetch(url, { headers: { 'Accept': 'application/json' } })
        .then(r => r.json())
        .then(d => { setAddress((d && d.display_name) || ''); setLoadingAddr(false); })
        .catch(() => setLoadingAddr(false));
    }, 600);
  }

  React.useEffect(() => {
    if (!open) return;
    const L = window.L;
    if (!L || !mapRef.current) return;
    const lang = (t && t.code ? t.code.toLowerCase() : 'es');
    // El Cuatro: start at the task's own coordinates when provided, so the
    // customer confirms/adjusts the real visit location (not their device's).
    const hasInit = initialCoords
      && isFinite(initialCoords.lat) && isFinite(initialCoords.lng);
    const start = hasInit
      ? [initialCoords.lat, initialCoords.lng]
      : [-34.6037, -58.3816]; // neutral fallback
    const map = L.map(mapRef.current, { zoomControl: true })
      .setView(start, hasInit ? 16 : 13);
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
      { maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(map);
    // OfficeTrack duotone pin (lavender fill + navy stroke + yellow dot).
    const pinHtml =
      '<svg width="38" height="48" viewBox="0 0 38 48" xmlns="http://www.w3.org/2000/svg">'
      + '<path d="M19 2C11 2 5 8 5 16c0 10 14 30 14 30s14-20 14-30C33 8 27 2 19 2z" '
      + 'fill="#CFC8F0" stroke="#15296B" stroke-width="2.6" stroke-linejoin="round"/>'
      + '<circle cx="19" cy="16" r="5" fill="#F5A623"/></svg>';
    const otIcon = L.divIcon({
      className: 'nc-map-pin', html: pinHtml,
      iconSize: [38, 48], iconAnchor: [19, 46],
    });
    const marker = L.marker(start, { draggable: true, icon: otIcon }).addTo(map);
    mapObj.current = map; markerRef.current = marker;
    const set = (lat, lng) => { setCoords({ lat, lng }); reverseGeocode(lat, lng, lang); };
    marker.on('dragend', () => { const p = marker.getLatLng(); map.panTo(p); set(p.lat, p.lng); });
    map.on('click', (e) => { marker.setLatLng(e.latlng); set(e.latlng.lat, e.latlng.lng); });
    set(start[0], start[1]);
    // Only auto-locate the device when we DON'T already know the task location.
    if (!hasInit && navigator.geolocation) {
      navigator.geolocation.getCurrentPosition((pos) => {
        const { latitude, longitude } = pos.coords;
        map.setView([latitude, longitude], 16);
        marker.setLatLng([latitude, longitude]);
        set(latitude, longitude);
      }, () => {}, { enableHighAccuracy: true, timeout: 10000, maximumAge: 60000 });
    }
    setTimeout(() => map.invalidateSize(), 250);
    return () => { clearTimeout(geoTimer.current); map.remove(); mapObj.current = null; markerRef.current = null; };
  }, [open]);

  if (!open) return null;
  return (
    <div className="nc-sheet-scrim" onClick={onCancel}>
      <div className="nc-sheet" onClick={e => e.stopPropagation()}>
        <div className="nc-sheet-title">{t.mapTitle}</div>
        <div ref={mapRef} className="nc-map"></div>
        <div className="nc-map-addr">
          {loadingAddr
            ? t.locating
            : (address ? <span><b>{t.isItHere}</b> {address}</span> : t.dragHint)}
        </div>
        <div className="nc-sheet-actions">
          <button className="nc-btn nc-btn-secondary" onClick={onCancel}>{t.mapCancel}</button>
          <button className="nc-btn nc-btn-primary" style={{ background: brandColor }}
                  disabled={!coords}
                  onClick={() => coords && onConfirm({ ...coords, address })}>
            {t.useThisLocation}
          </button>
        </div>
      </div>
    </div>
  );
}

/* ───────── appointment summary card (El Cuatro) ─────────
   Shows what the task carries — service type, customer, address — and a
   button to view/confirm the visit location on the map. NEVER shows the
   internal ERP date. */
// Helper for the small action buttons used in the success card.
function actionPill(brandColor, active, extra) {
  return {
    display: 'inline-flex', alignItems: 'center', gap: 6, padding: '8px 12px',
    borderRadius: 9, fontSize: 13, fontWeight: 600, cursor: 'pointer',
    border: `1.5px solid ${brandColor}33`,
    background: active ? `${NCColors.success}14` : `${brandColor}0D`,
    color: active ? NCColors.success : brandColor, ...extra,
  };
}

function AppointmentCard({ t, task, brandColor }) {
  if (!task) return null;
  const addr = (task.address && task.address.trim()) || t.apptAddressDefault;
  const Row = ({ k, v }) => (
    <div style={{ display: 'flex', gap: 10, fontSize: 13.5, lineHeight: 1.6 }}>
      <span style={{ color: '#8A8DA3', width: 62, flexShrink: 0 }}>{k}</span>
      <span style={{ color: '#1F224A', fontWeight: 600, flex: 1 }}>{v}</span>
    </div>
  );
  return (
    <div style={{ margin: '4px 0 8px', padding: '14px 16px', borderRadius: 14,
      border: `1.5px solid ${brandColor}22`, background: 'rgba(255,255,255,0.95)',
      boxShadow: '0 2px 8px rgba(0,0,0,0.05)', alignSelf: 'stretch' }}>
      <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: 0.4,
        textTransform: 'uppercase', color: brandColor, marginBottom: 8 }}>
        {t.apptTitle}
      </div>
      {task.serviceType && <Row k={t.apptService} v={task.serviceType} />}
      {task.customerName && <Row k={t.apptCustomer} v={task.customerName} />}
      <Row k={t.apptAddress} v={addr} />
    </div>
  );
}

/* ───────── note sheet (leave a note for the technician) ───────── */
function NoteSheet({ t, open, brandColor, onSend, onCancel }) {
  const [text, setText] = React.useState('');
  React.useEffect(() => { if (open) setText(''); }, [open]);
  if (!open) return null;
  return (
    <div className="nc-sheet-scrim" onClick={onCancel}>
      <div className="nc-sheet" onClick={e => e.stopPropagation()}>
        <div className="nc-sheet-title">{t.noteTitle}</div>
        <textarea value={text} onChange={e => setText(e.target.value)}
          placeholder={t.notePlaceholder} rows={4} autoFocus
          style={{ width: '100%', boxSizing: 'border-box', resize: 'none',
            border: '1.5px solid #D9DAE6', borderRadius: 10, padding: '10px 12px',
            fontSize: 14, fontFamily: 'inherit', color: '#1F224A', outline: 'none' }}/>
        <div className="nc-sheet-actions">
          <button className="nc-btn nc-btn-secondary" onClick={onCancel}>{t.mapCancel}</button>
          <button className="nc-btn nc-btn-primary" style={{ background: brandColor }}
            disabled={!text.trim()}
            onClick={() => text.trim() && onSend(text.trim())}>
            {t.noteSend}
          </button>
        </div>
      </div>
    </div>
  );
}

/* ───────── booking success card (El Cuatro) ─────────
   Polished confirmation after the visit is booked: arrival window + address +
   "add to calendar" (.ics). */
function SuccessCard({ t, windowLabel, address, brandColor, onAddToCalendar,
                      hasLocation, onLocation, locConfirmed, onNote, noteSent }) {
  return (
    <div style={{ margin: '6px 0', padding: '16px', borderRadius: 14,
      border: `2px solid ${NCColors.success}`, background: `${NCColors.success}0D`,
      alignSelf: 'stretch' }}>
      <div style={{ fontSize: 14, fontWeight: 700, color: NCColors.success,
        marginBottom: 6 }}>✓ {t.successTitle}</div>
      <div style={{ fontSize: 15, fontWeight: 700, color: '#1F224A' }}>{windowLabel}</div>
      {address && <div style={{ fontSize: 12.5, color: '#8A8DA3', marginTop: 3 }}>{address}</div>}
      {/* Post-booking actions: refine location + leave a note for the technician. */}
      <div style={{ marginTop: 12, display: 'flex', flexWrap: 'wrap', gap: 8 }}>
        {onAddToCalendar && (
          <button onClick={onAddToCalendar}
            style={{ display: 'inline-flex', alignItems: 'center', gap: 6,
              padding: '8px 12px', borderRadius: 9, border: 'none',
              background: brandColor, color: '#fff', fontSize: 13, fontWeight: 600,
              cursor: 'pointer' }}>
            {t.addToCalendar}
          </button>
        )}
        {hasLocation && onLocation && (
          <button onClick={locConfirmed ? undefined : onLocation} disabled={locConfirmed}
            style={{ ...actionPill(brandColor, locConfirmed),
                     cursor: locConfirmed ? 'default' : 'pointer' }}>
            <span style={{ display: 'flex' }}>{OTIcons.pin}</span>
            {locConfirmed ? t.locationConfirmed : t.viewLocation}
          </button>
        )}
        {onNote && (
          <button onClick={noteSent ? undefined : onNote} disabled={noteSent}
            style={{ ...actionPill(brandColor, noteSent),
                     cursor: noteSent ? 'default' : 'pointer' }}>
            {noteSent ? t.noteSent : t.noteCta}
          </button>
        )}
      </div>
    </div>
  );
}

Object.assign(window, {
  NCColors, OTIcons, ChatHeader, ChatMsg, TypingDots, SuggestionChips,
  SlotChips, Composer, LocationSheet, NoteSheet, AppointmentCard, SuccessCard,
  cleanAiText, RichText,
});
