// ── Req — shared "this field is required" marker ──────────────────────────
// Render as `<Req/>` after a label or inside a placeholder context. Exposed on
// window so every JSX file (loaded after shell.jsx) can use it without import.
const Req = () => <span style={{ color:'#c53030', marginLeft:2 }} aria-hidden="true">*</span>;
window.Req = Req;

// ── scrimDismiss — modal-scrim click-outside-to-close, but drag-safe ───────
// Spread the returned props onto the scrim div. A click only counts as a
// dismiss when BOTH the mousedown AND the mouseup landed directly on the
// scrim element — not on a child (the modal box) and not as the tail of a
// drag-select that started inside the box. We stash the "pressed here?"
// flag on the scrim DOM node's dataset so the helper is a plain function
// (no React hooks needed), making it safe to use anywhere inline.
const scrimDismiss = (close) => ({
  onMouseDown: (e) => {
    e.currentTarget.dataset.scrimPress = (e.target === e.currentTarget) ? '1' : '';
  },
  onMouseUp: (e) => {
    const startedHere = e.currentTarget.dataset.scrimPress === '1';
    e.currentTarget.dataset.scrimPress = '';
    if (startedHere && e.target === e.currentTarget) close();
  },
  onMouseLeave: (e) => {
    // If the cursor leaves the scrim while the button is still down (drag
    // outside the viewport / browser chrome), forget the press so a stray
    // mouseup elsewhere doesn't fire dismiss when it comes back.
    e.currentTarget.dataset.scrimPress = '';
  },
});
window.scrimDismiss = scrimDismiss;

// ── Per-campaign role helpers ─────────────────────────────────────────────
// Roles live on `campaign.myRole` (the requesting user's role) and on each
// entry of `campaign.members[].role`. These helpers wrap the checks so every
// caller agrees on what "is DM" means and we can change it in one place.
const isDmOf       = (campaign) => campaign?.myRole === 'DM' || campaign?.myRole === 'Co-DM';
const isFullDmOf   = (campaign) => campaign?.myRole === 'DM';
const isMemberOf   = (campaign) => !!campaign?.myRole;
const dmsOf        = (campaign) => (campaign?.members || []).filter(m => m.role === 'DM' || m.role === 'Co-DM');
const playersOf    = (campaign) => (campaign?.members || []).filter(m => m.role === 'Player');
// Display helper: comma-separated DM names for compact summaries.
const dmNamesOf    = (campaign) => dmsOf(campaign).map(m => m.name).join(', ');
// Returns true if the user is a DM/Co-DM in ANY campaign — used by global
// resource UIs (worldmap, association, world events) that previously asked
// "is the user a DM?" with a single global flag.
const isAnyDm      = (campaigns) => (campaigns || []).some(c => isDmOf(c));
Object.assign(window, { isDmOf, isFullDmOf, isMemberOf, dmsOf, playersOf, dmNamesOf, isAnyDm });

// ── CityPicker — reusable dropdown backed by /api/cities?campaignId=… ─────
// Listens for `kamia:cities-changed` (dispatched by the world map's pin
// editor when a city-typed pin is saved) so the dropdown reflects new /
// renamed cities without forcing a full page reload.
const CityPicker = ({ value, onChange, campaignId, placeholder, includeNone, disabled, style }) => {
  const [cities, setCities] = React.useState([]);
  const reload = React.useCallback(() => {
    let cancelled = false;
    api.cities.list(campaignId ? { campaignId } : undefined)
      .then(list => { if (!cancelled) setCities(list || []); })
      .catch(err => console.error('[CityPicker] load failed:', err));
    return () => { cancelled = true; };
  }, [campaignId]);
  React.useEffect(() => reload(), [reload]);
  React.useEffect(() => {
    const onChanged = () => reload();
    window.addEventListener('kamia:cities-changed', onChanged);
    return () => window.removeEventListener('kamia:cities-changed', onChanged);
  }, [reload]);
  return (
    <select disabled={disabled}
      value={value || ''}
      onChange={e => onChange(e.target.value || null)}
      style={{ background:'#16161b', border:'1px solid #2a2a32', color:'#e8e6e3', borderRadius:6, padding:'6px 10px', fontSize:13, ...(style||{}) }}>
      <option value="">{placeholder || '— Select a city —'}</option>
      {cities.map(c => (
        <option key={c.id} value={c.id}>{c.name}{c.size ? ` · ${c.size}` : ''}</option>
      ))}
      {includeNone && <option value="">No base</option>}
    </select>
  );
};
window.CityPicker = CityPicker;

// App Shell — Sidebar + Top Bar + Layout
// `campaigns` is API-sourced and already scoped by the backend to what this user can see.
const AppShell = ({ user, onLogout, children, nav, setNav, campaigns = [] }) => {
  const [sidebarCollapsed, setSidebarCollapsed] = React.useState(false);

  const navItems = [
    { id: 'dashboard', icon: '⌂', label: 'Dashboard' },
    { id: 'world', icon: '🌍', label: 'World Events' },
    { id: 'worldmap', icon: '🗺', label: 'World Map' },
    { id: 'association', icon: '⚔', label: 'Association' },
    { id: 'homebrew-repo', icon: '📚', label: 'Homebrew' },
    ...(user.isAdmin ? [{ id: 'admin', icon: '⚙', label: 'Admin' }] : []),
  ];

  return (
    <div style={shellStyles.root}>
      {/* Sidebar */}
      <aside style={{...shellStyles.sidebar, width: sidebarCollapsed ? 60 : 220}}>
        <div style={shellStyles.sidebarHeader}>
          <div style={shellStyles.logo}>
            {!sidebarCollapsed && <span style={shellStyles.logoText}>KAMIA</span>}
            <span style={shellStyles.logoIcon}>⚔</span>
          </div>
          <button style={shellStyles.collapseBtn} onClick={() => setSidebarCollapsed(!sidebarCollapsed)}>
            {sidebarCollapsed ? '›' : '‹'}
          </button>
        </div>

        <nav style={shellStyles.nav}>
          {navItems.map(item => (
            <button key={item.id} style={{...shellStyles.navItem, ...(nav.view === item.id ? shellStyles.navItemActive : {})}}
              onClick={() => setNav({ view: item.id, campaignId: null, tab: 'overview' })}>
              <span style={shellStyles.navIcon}>{item.icon}</span>
              {!sidebarCollapsed && <span style={shellStyles.navLabel}>{item.label}</span>}
            </button>
          ))}

          {campaigns.length > 0 && (
            <>
              <div style={{...shellStyles.sectionLabel, opacity: sidebarCollapsed ? 0 : 1}}>Campaigns</div>
              {campaigns.map(c => (
                <button key={c.id}
                  style={{...shellStyles.navItem, ...(nav.view === 'campaign' && nav.campaignId === c.id ? shellStyles.navItemActive : {})}}
                  onClick={() => setNav({ view: 'campaign', campaignId: c.id, tab: 'overview' })}>
                  <span style={{...shellStyles.navIcon, background: c.bannerColor, borderRadius: 4, width: 20, height: 20, display:'flex', alignItems:'center', justifyContent:'center', fontSize: 10}}>⚔</span>
                  {!sidebarCollapsed && <span style={{...shellStyles.navLabel, fontSize: 13}}>{c.name}</span>}
                </button>
              ))}
            </>
          )}
        </nav>

        <div style={shellStyles.sidebarFooter}>
          <div style={shellStyles.userChip}>
            <div style={{...shellStyles.avatarSmall, background: user.isAdmin ? '#c53030' : '#3a5c7a'}}>{user.avatar}</div>
            {!sidebarCollapsed && (
              <div style={{flex:1, minWidth:0}}>
                <div style={{fontSize:12, fontWeight:700, color:'#e8e6e3', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis'}}>{user.name}</div>
                <div style={{fontSize:10, color:'#6b6966', textTransform:'uppercase', letterSpacing:'0.08em'}}>{user.isAdmin ? 'Admin' : 'User'}</div>
              </div>
            )}
            {!sidebarCollapsed && (
              <button style={shellStyles.logoutBtn} onClick={onLogout} title="Sign out">↩</button>
            )}
          </div>
        </div>
      </aside>

      {/* Main content */}
      <main style={shellStyles.main}>
        {children}
      </main>
    </div>
  );
};

const shellStyles = {
  root: { display: 'flex', height: '100vh', background: '#111116', fontFamily: "'Nunito', sans-serif", overflow: 'hidden' },
  sidebar: {
    background: '#16161b', borderRight: '1px solid #2a2a32',
    display: 'flex', flexDirection: 'column', flexShrink: 0,
    transition: 'width 0.2s ease', overflow: 'hidden',
  },
  sidebarHeader: {
    padding: '16px 14px', display: 'flex', alignItems: 'center',
    justifyContent: 'space-between', borderBottom: '1px solid #2a2a32', minHeight: 56,
  },
  logo: { display: 'flex', alignItems: 'center', gap: 8 },
  logoText: { fontFamily: "'Cinzel', serif", fontSize: 16, fontWeight: 700, color: '#c9a227', letterSpacing: '0.12em' },
  logoIcon: { fontSize: 16, color: '#c9a227' },
  collapseBtn: {
    background: 'none', border: 'none', color: '#6b6966', cursor: 'pointer',
    fontSize: 18, padding: '0 4px', lineHeight: 1,
  },
  nav: { flex: 1, padding: '12px 8px', overflowY: 'auto', overflowX: 'hidden' },
  navItem: {
    width: '100%', display: 'flex', alignItems: 'center', gap: 10,
    background: 'none', borderRadius: 6, padding: '8px 10px',
    // Explicit per-side borders (not `border: 'none'` shorthand) so the active
    // state can swap `borderLeft` without React warning about shorthand mixing.
    borderTop: 'none', borderRight: 'none', borderBottom: 'none', borderLeft: '2px solid transparent',
    cursor: 'pointer', color: '#9a9793', fontSize: 14, fontWeight: 600,
    transition: 'background 0.12s, color 0.12s', textAlign: 'left',
    fontFamily: "'Nunito', sans-serif", marginBottom: 2,
  },
  navItemActive: { background: 'rgba(197,48,48,0.15)', color: '#e8e6e3', borderLeft: '2px solid #c53030' },
  navIcon: { fontSize: 15, width: 20, textAlign: 'center', flexShrink: 0 },
  navLabel: { fontSize: 13, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' },
  sectionLabel: {
    fontSize: 10, fontWeight: 700, color: '#4a4a52', textTransform: 'uppercase',
    letterSpacing: '0.12em', padding: '12px 10px 4px',
  },
  sidebarFooter: { padding: 10, borderTop: '1px solid #2a2a32' },
  userChip: { display: 'flex', alignItems: 'center', gap: 8, padding: '6px 4px' },
  avatarSmall: {
    width: 28, height: 28, borderRadius: '50%', display: 'flex',
    alignItems: 'center', justifyContent: 'center', fontSize: 11,
    fontWeight: 700, color: '#fff', flexShrink: 0,
  },
  logoutBtn: {
    background: 'none', border: 'none', color: '#6b6966', cursor: 'pointer',
    fontSize: 15, padding: 2, lineHeight: 1,
  },
  main: { flex: 1, overflowY: 'auto', overflowX: 'hidden' },
};

Object.assign(window, { AppShell, shellStyles });
