/* Mandera Portal — shared chrome (Icon, Sidebar, Topbar, helpers).
   Role-agnostic: client app and admin console both reuse these. */

const PASSET = '../assets/';

function initials(name) { return (name || '').split(' ').filter(Boolean).slice(0, 2).map((w) => w[0]).join('').toUpperCase() || '?'; }
function timeAgo(ts) {
  const s = Math.floor((Date.now() - ts) / 1000);
  if (s < 60) return 'just now';
  if (s < 3600) return Math.floor(s / 60) + 'm ago';
  if (s < 86400) return Math.floor(s / 3600) + 'h ago';
  return Math.floor(s / 86400) + 'd ago';
}

/* Downscale an image file to a small square avatar dataUrl (fast to sync). */
function mReadAvatar(file, cb) {
  if (!file || !file.type || !file.type.startsWith('image')) { alert('Please choose an image file.'); return; }
  const r = new FileReader();
  r.onload = () => {
    const img = new Image();
    img.onload = () => {
      const S = 256; const c = document.createElement('canvas'); c.width = S; c.height = S;
      const x = c.getContext('2d');
      const side = Math.min(img.width, img.height);
      x.drawImage(img, (img.width - side) / 2, (img.height - side) / 2, side, side, 0, 0, S, S);
      cb(c.toDataURL('image/jpeg', 0.85));
    };
    img.onerror = () => alert('Could not read that image.');
    img.src = r.result;
  };
  r.readAsDataURL(file);
}

/* Upload a file to the server store (api/portal-files.php → /portal-uploads/).
   Resolves to {url,name,type,size} or null when the server isn't reachable. */
async function mUploadFile(file) {
  try {
    const fd = new FormData(); fd.append('file', file);
    const res = await fetch('../api/portal-files.php', { method: 'POST', body: fd });
    const j = await res.json();
    if (j && j.ok && j.url) return { url: j.url, name: file.name, type: file.type || '', size: file.size };
  } catch (e) {}
  return null;
}
/* Small-file fallback: read as dataUrl if under maxMB, else null. */
function mReadSmall(file, maxMB) {
  return new Promise((resolve) => {
    if (file.size > (maxMB || 2.5) * 1048576) return resolve(null);
    const r = new FileReader();
    r.onload = () => resolve(r.result); r.onerror = () => resolve(null);
    r.readAsDataURL(file);
  });
}
function mAvatarOf(accountId) { if (!accountId || !window.MData) return null; const a = window.MData.get('accounts').find((x) => x.id === accountId); return (a && a.avatar) || null; }

function Avatar({ name, src, size = 38, accent, fontSize, style }) {
  const fs = fontSize || Math.round(size * 0.36);
  const base = { width: size, height: size, borderRadius: '50%', flex: 'none', overflow: 'hidden', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', ...style };
  if (src) return <span style={{ ...base, border: '1px solid var(--border-default)' }}><img src={src} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} /></span>;
  return (
    <span style={{ ...base, background: accent ? 'var(--grad-mark)' : 'var(--surface-raised)', color: accent ? 'var(--ink-950)' : 'var(--accent-text)', border: accent ? 'none' : '1px solid var(--border-default)', fontWeight: 700, fontFamily: 'var(--font-sans)', fontSize: fs }}>{initials(name)}</span>
  );
}

function PIcon({ n, s = 18, color = 'currentColor', stroke = 1.8 }) {
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (ref.current && window.lucide) {
      ref.current.innerHTML = '';
      const el = document.createElement('i');
      el.setAttribute('data-l', n);
      ref.current.appendChild(el);
      window.lucide.createIcons({ nameAttr: 'data-l', attrs: { width: s, height: s, stroke: color, 'stroke-width': stroke }, root: ref.current });
    }
  }, [n, s, color, stroke]);
  return <span ref={ref} style={{ display: 'inline-flex', width: s, height: s, flex: 'none' }} />;
}

function PLogo({ height = 22, label = 'Client Portal' }) {
  return (
    <div style={{ display: 'inline-flex', alignItems: 'center', gap: 10 }}>
      <img src={PASSET + 'mandera-mark-transparent.png'} alt="" style={{ height: height + 10, width: height + 10, objectFit: 'contain' }} />
      <span style={{ display: 'flex', flexDirection: 'column', lineHeight: 1 }}>
        <span style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: height, color: 'var(--text-strong)', letterSpacing: '0.03em' }}>Mandera</span>
        <span style={{ fontFamily: 'var(--font-sans)', fontWeight: 500, fontSize: height * 0.28, letterSpacing: '0.28em', textTransform: 'uppercase', color: 'var(--accent-text)', marginTop: 3 }}>{label}</span>
      </span>
    </div>
  );
}

function Sidebar({ nav, active, go, label, footer, onSignOut, mobileOpen }) {
  return (
    <aside className={'mp-sidebar' + (mobileOpen ? ' mp-open' : '')} style={{
      width: 260, flex: 'none', background: 'var(--ink-900)', borderRight: '1px solid var(--border-subtle)',
      display: 'flex', flexDirection: 'column', padding: '24px 16px', height: '100%', boxSizing: 'border-box',
    }}>
      <div style={{ padding: '4px 8px 24px' }}><PLogo label={label} /></div>
      <nav style={{ display: 'flex', flexDirection: 'column', gap: 4, flex: 1, overflowY: 'auto' }} className="mandera-scroll">
        {nav.map((it) => {
          const on = active === it.id;
          return (
            <button key={it.id} onClick={() => go(it.id)} style={{
              display: 'flex', alignItems: 'center', gap: 12, padding: '11px 12px', borderRadius: 'var(--radius-md)',
              background: on ? 'var(--accent-soft)' : 'transparent', border: '1px solid', borderColor: on ? 'var(--border-accent)' : 'transparent',
              cursor: 'pointer', textAlign: 'left', width: '100%',
              fontFamily: 'var(--font-sans)', fontSize: 14.5, fontWeight: on ? 600 : 500,
              color: on ? 'var(--text-strong)' : 'var(--text-muted)', transition: 'all var(--dur-fast) var(--ease-out)',
            }}
              onMouseEnter={(e) => { if (!on) { e.currentTarget.style.background = 'var(--surface-raised)'; e.currentTarget.style.color = 'var(--text-secondary)'; } }}
              onMouseLeave={(e) => { if (!on) { e.currentTarget.style.background = 'transparent'; e.currentTarget.style.color = 'var(--text-muted)'; } }}>
              <PIcon n={it.icon} s={18} color={on ? 'var(--accent-text)' : 'currentColor'} />
              <span style={{ flex: 1 }}>{it.label}</span>
              {it.badge ? <span style={{ minWidth: 18, height: 18, padding: '0 5px', borderRadius: 'var(--radius-pill)', background: 'var(--accent)', color: 'var(--ink-950)', fontSize: 11, fontWeight: 700, display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>{it.badge}</span> : null}
            </button>
          );
        })}
      </nav>
      <a href="../index.html" title="Back to the main website" style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '11px 12px', margin: '8px 0', borderRadius: 'var(--radius-md)', textDecoration: 'none', border: '1px solid var(--border-subtle)', background: 'var(--surface-card)', color: 'var(--text-muted)', fontFamily: 'var(--font-sans)', fontSize: 14, fontWeight: 500 }}
        onMouseEnter={(e) => { e.currentTarget.style.color = 'var(--text-strong)'; e.currentTarget.style.background = 'var(--surface-raised)'; }}
        onMouseLeave={(e) => { e.currentTarget.style.color = 'var(--text-muted)'; e.currentTarget.style.background = 'var(--surface-card)'; }}>
        <PIcon n="globe" s={17} /> <span style={{ flex: 1 }}>Main website</span> <PIcon n="arrow-up-right" s={14} color="var(--text-faint)" />
      </a>
      {footer}
      {onSignOut && (
        <button onClick={onSignOut} style={{ marginTop: 10, display: 'flex', alignItems: 'center', gap: 10, padding: '10px 12px', borderRadius: 'var(--radius-md)', background: 'transparent', border: '1px solid transparent', cursor: 'pointer', color: 'var(--text-muted)', fontFamily: 'var(--font-sans)', fontSize: 14, fontWeight: 500 }}
          onMouseEnter={(e) => { e.currentTarget.style.background = 'var(--surface-raised)'; e.currentTarget.style.color = 'var(--text-strong)'; }}
          onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; e.currentTarget.style.color = 'var(--text-muted)'; }}>
          <PIcon n="log-out" s={18} /> <span>Sign out</span>
        </button>
      )}
    </aside>
  );
}

function UserCard({ name, sub, accent, src, onAvatar }) {
  const ref = React.useRef(null);
  return (
    <div style={{ padding: 14, borderRadius: 'var(--radius-md)', background: 'var(--surface-card)', border: '1px solid var(--border-subtle)', display: 'flex', alignItems: 'center', gap: 11 }}>
      <button type="button" title={onAvatar ? 'Change your photo' : undefined} onClick={() => onAvatar && ref.current && ref.current.click()}
        style={{ appearance: 'none', border: 'none', background: 'none', padding: 0, cursor: onAvatar ? 'pointer' : 'default', position: 'relative', flex: 'none', display: 'inline-flex' }}>
        <Avatar name={name} src={src} size={38} accent={accent && !src} />
        {onAvatar && <span style={{ position: 'absolute', right: -3, bottom: -3, width: 17, height: 17, borderRadius: '50%', background: 'var(--surface-raised)', border: '1px solid var(--border-default)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}><PIcon n="camera" s={9} color="var(--text-secondary)" /></span>}
      </button>
      {onAvatar && <input ref={ref} type="file" accept="image/*" style={{ display: 'none' }} onChange={(e) => { const f = e.target.files && e.target.files[0]; if (f) mReadAvatar(f, onAvatar); e.target.value = ''; }} />}
      <div style={{ minWidth: 0 }}>
        <div style={{ fontFamily: 'var(--font-sans)', fontSize: 13.5, fontWeight: 600, color: 'var(--text-strong)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{name}</div>
        <div style={{ fontFamily: 'var(--font-sans)', fontSize: 12, color: 'var(--text-muted)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{sub}</div>
      </div>
    </div>
  );
}

function SoundToggle() {
  const [on, setOn] = React.useState(window.MSound ? window.MSound.isEnabled() : true);
  React.useEffect(() => {
    const h = () => setOn(window.MSound ? window.MSound.isEnabled() : true);
    window.addEventListener('msound-changed', h);
    return () => window.removeEventListener('msound-changed', h);
  }, []);
  return (
    <button onClick={() => window.MSound && window.MSound.setEnabled(!on)} title={on ? 'Mute notification sounds' : 'Unmute notification sounds'}
      style={{ width: 40, height: 40, borderRadius: 'var(--radius-md)', background: 'var(--surface-raised)', border: '1px solid var(--border-default)', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: on ? 'var(--text-muted)' : 'var(--text-faint)' }}>
      <PIcon n={on ? 'volume-2' : 'volume-x'} s={18} />
    </button>
  );
}

function Topbar({ title, sub, user, notifCount = 0, onBell, onMenu }) {
  return (
    <header className="mp-topbar" style={{
      display: 'flex', alignItems: 'center', justifyContent: 'space-between',
      padding: '20px 32px', borderBottom: '1px solid var(--border-subtle)', background: 'rgba(6,7,10,0.6)', backdropFilter: 'blur(12px)',
    }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, minWidth: 0 }}>
        <button className="mp-burger" onClick={onMenu} aria-label="Open menu" style={{ width: 40, height: 40, flex: 'none', borderRadius: 'var(--radius-md)', background: 'var(--surface-raised)', border: '1px solid var(--border-default)', cursor: 'pointer', alignItems: 'center', justifyContent: 'center', color: 'var(--text-secondary)' }}>
          <PIcon n="menu" s={18} />
        </button>
        <div style={{ minWidth: 0 }}>
          <h1 className="mp-title" style={{ fontFamily: 'var(--font-display)', fontWeight: 600, fontSize: 28, color: 'var(--text-strong)', margin: 0, letterSpacing: '-0.01em', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{title}</h1>
          {sub && <div className="mp-sub" style={{ fontFamily: 'var(--font-sans)', fontSize: 14, color: 'var(--text-muted)', marginTop: 3 }}>{sub}</div>}
        </div>
      </div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14, flex: 'none' }}>
        <SoundToggle />
        <button onClick={onBell} style={{ width: 40, height: 40, borderRadius: 'var(--radius-md)', background: 'var(--surface-raised)', border: '1px solid var(--border-default)', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-muted)', position: 'relative' }}>
          <PIcon n="bell" s={18} />
          {notifCount > 0 && <span style={{ position: 'absolute', top: 7, right: 8, minWidth: 15, height: 15, padding: '0 3px', borderRadius: 'var(--radius-pill)', background: 'var(--accent)', color: 'var(--ink-950)', fontSize: 9.5, fontWeight: 700, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', border: '2px solid var(--surface-raised)' }}>{notifCount}</span>}
        </button>
        {user && (
          <div className="mp-userbox" style={{ display: 'flex', alignItems: 'center', gap: 10, paddingLeft: 6 }}>
            <Avatar name={user.name} src={user.src} size={38} />
            <div style={{ lineHeight: 1.2 }}>
              <div style={{ fontFamily: 'var(--font-sans)', fontSize: 13.5, fontWeight: 600, color: 'var(--text-strong)', whiteSpace: 'nowrap' }}>{user.name}</div>
              <div style={{ fontFamily: 'var(--font-sans)', fontSize: 12, color: 'var(--text-muted)', whiteSpace: 'nowrap' }}>{user.sub}</div>
            </div>
          </div>
        )}
      </div>
    </header>
  );
}

/* Comment / note thread stored on any record (item.notes = [{who,role,text,ts}]).
   Used on content, timeline and more — clients and the team talk about a
   specific item and the notes appear right under it on BOTH sides. */
function PNotes({ coll, item, me, clientId, what, link, onChange, placeholder }) {
  const notes = Array.isArray(item.notes) ? item.notes : [];
  const [text, setText] = React.useState('');
  const isTeam = me.role !== 'client';
  const submit = (e) => {
    e.preventDefault();
    const t = text.trim(); if (!t) return;
    const note = { id: 'n' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6), who: me.name || (isTeam ? 'Mandera' : 'Client'), role: isTeam ? 'team' : 'client', text: t, ts: Date.now() };
    window.MData.update(coll, item.id, { notes: notes.concat([note]) });
    const cl = clientId ? window.MData.client(clientId) : null;
    const label = what || '“' + (item.title || item.task || 'an item') + '”';
    if (isTeam) window.MData.notify({ clientId, type: 'note', text: 'Mandera left a note on ' + label + ': “' + t + '”', link: link || 'calendar', refId: item.id });
    else window.MData.notify({ audience: 'team', type: 'note', text: (cl ? cl.brand : 'A client') + ' commented on ' + label + ': “' + t + '”', link: 'clients', refId: clientId });
    setText(''); onChange && onChange();
  };
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      {notes.map((n) => (
        <div key={n.id} style={{ display: 'flex', gap: 10, alignItems: 'flex-start', padding: '9px 12px', background: 'var(--surface-sunken)', border: '1px solid var(--border-subtle)', borderRadius: 'var(--radius-md)' }}>
          <Avatar name={n.who} size={24} accent={n.role === 'team'} fontSize={10} style={{ marginTop: 1 }} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <span style={{ fontFamily: 'var(--font-sans)', fontSize: 12, color: 'var(--text-muted)' }}><b style={{ color: 'var(--text-secondary)' }}>{n.who}</b>{n.role === 'team' ? ' · Mandera' : ''} · {timeAgo(n.ts)}</span>
            <div style={{ fontFamily: 'var(--font-sans)', fontSize: 13.5, color: 'var(--text-body)', marginTop: 2, whiteSpace: 'pre-wrap' }}>{n.text}</div>
          </div>
        </div>
      ))}
      <form onSubmit={submit} style={{ display: 'flex', gap: 8 }}>
        <input value={text} onChange={(e) => setText(e.target.value)} placeholder={placeholder || 'Write a note…'}
          style={{ flex: 1, minWidth: 0, height: 36, padding: '0 12px', background: 'var(--surface-sunken)', border: '1px solid var(--border-default)', borderRadius: 'var(--radius-md)', color: 'var(--text-strong)', fontFamily: 'var(--font-sans)', fontSize: 13.5, outline: 'none' }}
          onFocus={(e) => e.target.style.borderColor = 'var(--accent)'} onBlur={(e) => e.target.style.borderColor = 'var(--border-default)'} />
        <button type="submit" title="Add note" style={{ appearance: 'none', width: 36, height: 36, flex: 'none', border: '1px solid var(--border-default)', background: 'var(--surface-raised)', borderRadius: 'var(--radius-md)', cursor: 'pointer', color: 'var(--accent-text)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
          <PIcon n="send" s={15} color="var(--accent-text)" />
        </button>
      </form>
    </div>
  );
}

/* Collapsible PNotes — shows a small “N notes” toggle; auto-open when there
   already are notes so they're visible without a click. */
function PNotesBox(props) {
  const count = Array.isArray(props.item.notes) ? props.item.notes.length : 0;
  const [open, setOpen] = React.useState(count > 0);
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
      <button type="button" onClick={() => setOpen(!open)} style={{ appearance: 'none', alignSelf: 'flex-start', display: 'inline-flex', alignItems: 'center', gap: 7, background: 'none', border: 'none', cursor: 'pointer', padding: 0, fontFamily: 'var(--font-sans)', fontSize: 12.5, fontWeight: 600, color: count ? 'var(--accent-text)' : 'var(--text-muted)' }}>
        <PIcon n="message-circle" s={14} color={count ? 'var(--accent-text)' : 'var(--text-muted)'} />
        {count ? count + (count === 1 ? ' note' : ' notes') : 'Add a note'}
        <PIcon n={open ? 'chevron-up' : 'chevron-down'} s={13} color="var(--text-faint)" />
      </button>
      {open && <PNotes {...props} />}
    </div>
  );
}

function MarkWash({ opacity = 0.1, size = 560, pos = { top: '-160px', right: '-160px' } }) {
  return <img src={PASSET + 'mandera-mark-transparent.png'} alt="" aria-hidden="true"
    style={{ position: 'absolute', width: size, height: size, objectFit: 'contain', opacity, pointerEvents: 'none', ...pos }} />;
}

/* Fixed warning bar shown ONLY when the shared database can't be reached — so
   the owner immediately knows accounts/messages won't sync between devices
   (the #1 cause of "the client can't sign in"). Silent when all is well. */
function ConnBanner() {
  const [state, setState] = React.useState('checking'); // checking | ok | offline
  React.useEffect(() => {
    let alive = true;
    const api = (window.MData && window.MData.API) || '../api/portal-api.php';
    fetch(api, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'health' }) })
      .then((r) => r.json()).then((j) => { if (alive) setState(j && j.ok ? 'ok' : 'offline'); })
      .catch(() => { if (alive) setState('offline'); });
    return () => { alive = false; };
  }, []);
  if (state !== 'offline') return null;
  return (
    <div style={{ position: 'fixed', top: 0, left: 0, right: 0, zIndex: 9999, background: '#3a1418', borderBottom: '1px solid #7a2630', color: '#ffb3b8', fontFamily: 'var(--font-sans)', fontSize: 13, fontWeight: 500, padding: '9px 16px', textAlign: 'center', lineHeight: 1.4 }}>
      ⚠ Not connected to the shared database — new accounts &amp; messages won’t sync between devices. Open <b>/api/health.php</b> on your site to fix it.
    </div>
  );
}

Object.assign(window, { PIcon, PLogo, Sidebar, Topbar, UserCard, MarkWash, Avatar, ConnBanner, SoundToggle, PNotes, PNotesBox, mReadAvatar, mUploadFile, mReadSmall, mAvatarOf, PASSET, mInitials: initials, mTimeAgo: timeAgo });
