// Dashboard + World Events view
const severityColor = (s) => s === 'catastrophic' ? '#991b1b' : s === 'major' ? '#c53030' : s === 'moderate' ? '#b45309' : '#1e6b3c';
const severityBg = (s) => s === 'catastrophic' ? 'rgba(153,27,27,0.15)' : s === 'major' ? 'rgba(197,48,48,0.1)' : s === 'moderate' ? 'rgba(180,83,9,0.1)' : 'rgba(30,107,60,0.1)';

const Dashboard = ({ user, setNav, campaigns = [], campaignsLoading = false, onCampaignsChanged }) => {
  const [recentEvents, setRecentEvents] = React.useState([]);
  React.useEffect(() => {
    let cancelled = false;
    api.worldEvents.list().then(list => {
      if (!cancelled) setRecentEvents((list || []).slice(0, 3));
    }).catch(err => console.error('[Dashboard] worldEvents:', err));
    return () => { cancelled = true; };
  }, []);
  const [myChars, setMyChars] = React.useState([]);
  const [creatingChar, setCreatingChar] = React.useState(false);
  const reloadChars = React.useCallback(() => {
    api.characters.list().then(list => setMyChars((list || []).map(api.augmentCharacter)))
      .catch(err => console.error('[Dashboard] characters:', err));
  }, []);
  React.useEffect(() => { reloadChars(); }, [reloadChars, user.id]);

  // Pending direct invites for this user. Polled on mount + after accept/decline
  // mutates the list locally. The DM picks up changes on their next refresh.
  const [pendingInvites, setPendingInvites] = React.useState([]);
  const reloadInvites = React.useCallback(async () => {
    try { setPendingInvites((await api.invites.mine()) || []); }
    catch (err) { console.error('[Dashboard] invites.mine failed:', err); }
  }, []);
  React.useEffect(() => { reloadInvites(); }, [reloadInvites, user.id]);

  const acceptInvite = async (inv) => {
    try {
      const res = await api.invites.accept(inv.code);
      setPendingInvites(prev => prev.filter(i => i.code !== inv.code));
      await onCampaignsChanged?.();
      setNav({ view: 'campaign', campaignId: res.campaignId, tab: 'overview' });
    } catch (err) {
      console.error('[Dashboard] accept failed:', err);
      window.dialog.alert((err.body && err.body.message) || err.message || 'Could not accept invite.', { title:'Accept failed' });
    }
  };
  const declineInvite = async (inv) => {
    const ok = await window.dialog.confirm({
      title: 'Decline invitation?',
      message: `Decline the invitation to "${inv.campaignName}" from ${inv.invitedByName || 'the DM'}?`,
      danger: true, confirmLabel: 'Decline',
    });
    if (!ok) return;
    try {
      await api.invites.decline(inv.code);
      setPendingInvites(prev => prev.filter(i => i.code !== inv.code));
    } catch (err) {
      console.error('[Dashboard] decline failed:', err);
      window.dialog.alert((err.body && err.body.message) || err.message || 'Could not decline.', { title:'Error' });
    }
  };

  return (
    <div style={dbStyles.page}>
      <div style={dbStyles.header}>
        <div>
          <h1 style={dbStyles.title}>Welcome back, <span style={{color:'#c9a227'}}>{user.name.split(' ')[0]}</span></h1>
          <div style={dbStyles.subtitle}>The world continues to turn.</div>
        </div>
        <div style={dbStyles.dateChip}>
          <span style={{color:'#6b6966', fontSize:11, textTransform:'uppercase', letterSpacing:'0.1em'}}>Session Registry</span>
        </div>
      </div>

      <div style={dbStyles.grid}>
        {/* Campaigns column */}
        <div style={{display:'flex', flexDirection:'column', gap:18}}>
          {pendingInvites.length > 0 && (
            <div style={{...dbStyles.section, border:'1px solid rgba(124,58,237,0.45)', background:'linear-gradient(135deg, rgba(124,58,237,0.08), rgba(91,33,182,0.04))'}}>
              <div style={dbStyles.sectionHeader}>
                <span style={dbStyles.sectionTitle}>
                  <span style={{color:'#a78bfa', marginRight:6}}>✉</span>
                  Pending Invitations
                </span>
                <span style={{fontSize:12, color:'#a78bfa', fontWeight:700}}>{pendingInvites.length}</span>
              </div>
              <div style={{display:'flex', flexDirection:'column', gap:10}}>
                {pendingInvites.map(inv => (
                  <div key={inv.code} style={{background:'#16161b', border:'1px solid #2a2a32', borderRadius:8, padding:'12px 14px'}}>
                    <div style={{fontFamily:"'Cinzel',serif", fontSize:15, fontWeight:800, color:'#e8e6e3', marginBottom:2}}>{inv.campaignName}</div>
                    <div style={{fontSize:12, color:'#9a9793', marginBottom:8}}>
                      {inv.campaignSetting && <>{inv.campaignSetting} · </>}
                      Invited by <strong style={{color:'#e8e6e3'}}>{inv.invitedByName || 'a DM'}</strong>
                      <span style={{color:'#6b6966'}}> · {inv.createdAt}</span>
                    </div>
                    <div style={{display:'flex', gap:6}}>
                      <button onClick={() => acceptInvite(inv)}
                        style={{background:'rgba(34,197,94,0.15)', border:'1px solid #22c55e55', color:'#22c55e', borderRadius:6, padding:'6px 14px', cursor:'pointer', fontSize:12, fontWeight:700, fontFamily:"'Nunito',sans-serif"}}>
                        Accept
                      </button>
                      <button onClick={() => declineInvite(inv)}
                        style={{background:'transparent', border:'1px solid #3a3a42', color:'#9a9793', borderRadius:6, padding:'6px 14px', cursor:'pointer', fontSize:12, fontWeight:700, fontFamily:"'Nunito',sans-serif"}}>
                        Decline
                      </button>
                    </div>
                  </div>
                ))}
              </div>
            </div>
          )}

          {/* Campaigns */}
          <div style={dbStyles.section}>
            <div style={dbStyles.sectionHeader}>
              <span style={dbStyles.sectionTitle}>Your Campaigns</span>
              <span style={{fontSize:12, color:'#6b6966'}}>{campaigns.length} active</span>
            </div>
          {campaignsLoading && campaigns.length === 0 && (
            <div style={{fontSize:12, color:'#6b6966', padding:'8px 0'}}>Loading…</div>
          )}
          {campaigns.map(c => {
            // "Recently joined" if the requesting user's CampaignPlayer.JoinedAt
            // is within the last 7 days. The backend sends MyJoinedAt as ISO
            // round-trip; parse → diff in ms → days.
            const joined = c.myJoinedAt ? new Date(c.myJoinedAt) : null;
            const isNew = joined && (Date.now() - joined.getTime()) < 7 * 24 * 60 * 60 * 1000;
            return (
              <button key={c.id} style={dbStyles.campaignCard}
                onClick={() => setNav({ view: 'campaign', campaignId: c.id, tab: 'overview' })}>
                <div style={{...dbStyles.campaignBanner, background: c.bannerColor}}>
                  <span style={{fontFamily:"'Cinzel',serif", fontSize:16, color:'rgba(255,255,255,0.9)', fontWeight:700}}>{c.name}</span>
                  <div style={{display:'flex', gap:6, alignItems:'center'}}>
                    {isNew && (
                      <span title={`You joined on ${joined.toISOString().slice(0,10)}`}
                        style={{fontSize:10, fontWeight:800, letterSpacing:'0.1em', textTransform:'uppercase',
                          background:'rgba(124,58,237,0.85)', color:'#fff', padding:'2px 8px', borderRadius:20}}>
                        New
                      </span>
                    )}
                    <span style={dbStyles.statusBadge}>● Active</span>
                  </div>
                </div>
                <div style={dbStyles.campaignBody}>
                  <div style={dbStyles.campaignMeta}>
                    <span style={{color:'#9a9793', fontSize:12}}>DM: <span style={{color:'#e8e6e3'}}>{dmNamesOf(c) || '—'}</span></span>
                    <span style={{color:'#9a9793', fontSize:12}}>{c.sessionCount} sessions</span>
                  </div>
                  <p style={dbStyles.campaignDesc}>{(c.description || '').substring(0, 100)}…</p>
                  {c.lastSession && (
                    <div style={dbStyles.lastSession}>
                      Last session: <em style={{color:'#c9a227'}}>{c.lastSession}</em>
                    </div>
                  )}
                  {isNew && c.myRole && (
                    <div style={{fontSize:11, color:'#a78bfa', marginTop:6}}>
                      ✦ You joined as <strong>{c.myRole}</strong>{c.myJoinedAt ? <> on {joined.toISOString().slice(0,10)}</> : null}
                    </div>
                  )}
                </div>
              </button>
            );
          })}
          </div>
        </div>

        {/* Right column */}
        <div style={{display:'flex', flexDirection:'column', gap:20}}>
          {/* World events */}
          <div style={dbStyles.section}>
            <div style={dbStyles.sectionHeader}>
              <span style={dbStyles.sectionTitle}>World Events</span>
              <button style={dbStyles.linkBtn} onClick={() => setNav({ view: 'world' })}>View all →</button>
            </div>
            {recentEvents.map(ev => (
              <div key={ev.id} style={{...dbStyles.eventRow, borderLeftColor: severityColor(ev.severity)}}>
                <div style={{display:'flex', alignItems:'center', gap:8, marginBottom:4}}>
                  <span style={{...dbStyles.severityBadge, background: severityBg(ev.severity), color: severityColor(ev.severity)}}>
                    {ev.severity}
                  </span>
                  <span style={{fontSize:11, color:'#6b6966'}}>{ev.date}</span>
                </div>
                <div style={{fontSize:14, fontWeight:700, color:'#e8e6e3', marginBottom:4}}>{ev.title}</div>
                <div style={{fontSize:12, color:'#9a9793', lineHeight:1.5}}>{ev.description.substring(0, 120)}…</div>
              </div>
            ))}
          </div>

          {/* My Characters — always shown so the "+ New Character" button is
              reachable even when the user has zero characters yet. */}
          <div style={dbStyles.section}>
            <div style={dbStyles.sectionHeader}>
              <span style={dbStyles.sectionTitle}>My Characters</span>
              <button style={dbStyles.linkBtn} onClick={() => setCreatingChar(true)}>+ New Character</button>
            </div>
            {myChars.length === 0 && (
              <div style={{fontSize:12, color:'#6b6966', padding:'8px 0'}}>
                You haven't created any characters yet. Click <strong>+ New Character</strong> above to make one.
              </div>
            )}
            {myChars.map(ch => (
              <button key={ch.id} style={dbStyles.charCard}
                onClick={() => setNav({ view: 'character', characterId: ch.id })}>
                <div>
                  <div style={{fontSize:14, fontWeight:700, color:'#e8e6e3'}}>{ch.name}</div>
                  <div style={{fontSize:12, color:'#9a9793'}}>
                    {ch.race} {ch.class} · Level {ch.level}
                    {ch.campaignName
                      ? <> · <span style={{color:'#c9a227'}}>{ch.campaignName}</span></>
                      : <> · <span style={{color:'#a78bfa'}}>Unattached</span></>}
                  </div>
                </div>
                <div style={{textAlign:'right'}}>
                  <div style={{fontSize:14, fontWeight:700, color:'#c9a227'}}>{ch.gold} gp</div>
                  <div style={{fontSize:11, color:'#6b6966'}}>{ch.spellbook.length} spells</div>
                </div>
              </button>
            ))}
          </div>
          {creatingChar && (
            <NewCharacterModal
              campaigns={campaigns}
              onClose={() => setCreatingChar(false)}
              onCreated={(ch) => {
                setCreatingChar(false);
                setNav({ view: 'character', characterId: ch.id });
              }}
            />
          )}
        </div>
      </div>
    </div>
  );
};

// ── NewCharacterModal — minimal create-a-character form. Asks for the
// essentials (name + optional campaign + class/race/level) so the player can
// land on the Sheet tab and fill in the rest. Campaign picker lists the
// campaigns the user is already a member of; pick "None" for a free char.
const NewCharacterModal = ({ campaigns, onClose, onCreated }) => {
  const [form, setForm] = React.useState({
    name: '', campaignId: '',
    class: '', race: '', level: 1,
  });
  const [saving, setSaving] = React.useState(false);
  const set = (k, v) => setForm(f => ({...f, [k]: v}));
  const save = async () => {
    if (!form.name.trim()) return;
    setSaving(true);
    try {
      const ch = await api.characters.create({
        name: form.name.trim(),
        campaignId: form.campaignId || null,
        class: form.class, race: form.race,
        level: Math.max(1, Math.min(20, parseInt(form.level) || 1)),
      });
      onCreated?.(ch);
    } catch (err) {
      console.error('[NewCharacterModal] create failed:', err);
      window.dialog.alert((err.body && err.body.message) || err.message || 'Could not create character.', { title:'Create failed' });
    } finally {
      setSaving(false);
    }
  };
  return (
    <div style={dialogStyles.scrim} {...scrimDismiss(() => !saving && onClose())}>
      <div style={{...dialogStyles.box, maxWidth:520}} onClick={e => e.stopPropagation()}>
        <div style={dialogStyles.title}>New Character</div>
        <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:10, marginBottom:14 }}>
          <div style={{ gridColumn:'1/-1' }}>
            <label style={ncStyles.label}>Name<Req/></label>
            <input autoFocus value={form.name} onChange={e => set('name', e.target.value)}
              placeholder="e.g. Elara of Vaultkeep"
              style={ncStyles.input}/>
          </div>
          <div style={{ gridColumn:'1/-1' }}>
            <label style={ncStyles.label}>Campaign</label>
            <select value={form.campaignId} onChange={e => set('campaignId', e.target.value)} style={ncStyles.input}>
              <option value="">— Free / unattached (join later) —</option>
              {(campaigns || []).map(c => <option key={c.id} value={c.id}>{c.name}</option>)}
            </select>
            <div style={{ fontSize:11, color:'#6b6966', marginTop:4 }}>
              Pick a campaign you're a member of, or leave unattached and join one later from the character's sheet.
            </div>
          </div>
          <div>
            <label style={ncStyles.label}>Race</label>
            <input value={form.race} onChange={e => set('race', e.target.value)} placeholder="e.g. Half-Elf" style={ncStyles.input}/>
          </div>
          <div>
            <label style={ncStyles.label}>Class</label>
            <input value={form.class} onChange={e => set('class', e.target.value)} placeholder="e.g. Wizard" style={ncStyles.input}/>
          </div>
          <div>
            <label style={ncStyles.label}>Level</label>
            <input type="number" min={1} max={20} value={form.level} onChange={e => set('level', e.target.value)} style={ncStyles.input}/>
          </div>
        </div>
        <div style={dialogStyles.row}>
          <button style={dialogStyles.btnGhost} onClick={onClose} disabled={saving}>Cancel</button>
          <button style={dialogStyles.btn} onClick={save} disabled={saving || !form.name.trim()}>
            {saving ? 'Creating…' : 'Create'}
          </button>
        </div>
      </div>
    </div>
  );
};
const ncStyles = {
  label: { fontSize:11, color:'#6b6966', fontWeight:700, textTransform:'uppercase', letterSpacing:'0.08em' },
  input: { width:'100%', background:'#16161b', border:'1px solid #2a2a32', borderRadius:6, color:'#e8e6e3', padding:'8px 10px', fontSize:13, marginTop:4, boxSizing:'border-box', fontFamily:"'Nunito',sans-serif" },
};

const WorldEventsView = ({ user, setNav }) => {
  const [filter, setFilter] = React.useState('all');
  const [allEvents, setAllEvents] = React.useState([]);
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    let cancelled = false;
    setLoading(true);
    api.worldEvents.list().then(list => {
      if (!cancelled) { setAllEvents(list || []); setLoading(false); }
    }).catch(err => { console.error('[WorldEventsView]', err); if (!cancelled) setLoading(false); });
    return () => { cancelled = true; };
  }, []);

  const events = filter === 'all' ? allEvents : allEvents.filter(e => e.severity === filter);

  return (
    <div style={dbStyles.page}>
      <div style={dbStyles.header}>
        <div>
          <h1 style={dbStyles.title}>World Events</h1>
          <div style={dbStyles.subtitle}>Cross-campaign events that shape the realm</div>
        </div>
        <div style={{display:'flex', gap:8}}>
          {['all','catastrophic','major','moderate'].map(f => (
            <button key={f} style={{...dbStyles.filterBtn, ...(filter===f ? dbStyles.filterBtnActive : {})}}
              onClick={() => setFilter(f)}>{f}</button>
          ))}
        </div>
      </div>

      <div style={{maxWidth:800, display:'flex', flexDirection:'column', gap:16}}>
        {loading && events.length === 0 && (
          <div style={{color:'#6b6966', fontSize:13, padding:'20px 0'}}>Loading events…</div>
        )}
        {events.map(ev => (
          <div key={ev.id} style={{...dbStyles.eventCard, borderTopColor: severityColor(ev.severity)}}>
            <div style={{display:'flex', alignItems:'flex-start', justifyContent:'space-between', marginBottom:10}}>
              <div>
                <div style={{display:'flex', gap:8, marginBottom:6, flexWrap:'wrap'}}>
                  <span style={{...dbStyles.severityBadge, background: severityBg(ev.severity), color: severityColor(ev.severity)}}>
                    ● {ev.severity}
                  </span>
                  {(ev.tags || []).map(t => <span key={t} style={dbStyles.tag}>{t}</span>)}
                </div>
                <h2 style={{fontSize:18, fontWeight:800, color:'#e8e6e3', fontFamily:"'Cinzel',serif", margin:0}}>{ev.title}</h2>
              </div>
              <span style={{fontSize:12, color:'#6b6966', flexShrink:0, marginLeft:16}}>{ev.date}</span>
            </div>
            <p style={{fontSize:14, color:'#c5c3c0', lineHeight:1.7, margin:'0 0 12px'}}>{ev.description}</p>
            <div style={{display:'flex', gap:16, fontSize:12, color:'#6b6966', flexWrap:'wrap'}}>
              {ev.sourceCampaignName && <span>Source: <span style={{color:'#c9a227'}}>{ev.sourceCampaignName}</span></span>}
              <span>Affects: {(ev.affectedCampaigns || []).map(c=><span key={c.id} style={{color:'#9a9793', marginLeft:4}}>{c.name}</span>)}</span>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
};

const dbStyles = {
  page: { padding: '32px 36px', maxWidth: 1200, margin: '0 auto' },
  header: { display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', marginBottom: 32, flexWrap: 'wrap', gap: 12 },
  title: { fontFamily: "'Cinzel', serif", fontSize: 26, fontWeight: 700, color: '#e8e6e3', margin: 0, marginBottom: 4 },
  subtitle: { fontSize: 14, color: '#6b6966' },
  dateChip: { background: '#1c1c22', border: '1px solid #2a2a32', borderRadius: 6, padding: '6px 14px' },
  grid: { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24 },
  section: { background: '#1c1c22', border: '1px solid #2a2a32', borderRadius: 10, padding: 20, display: 'flex', flexDirection: 'column', gap: 12 },
  sectionHeader: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 4 },
  sectionTitle: { fontSize: 13, fontWeight: 800, color: '#e8e6e3', textTransform: 'uppercase', letterSpacing: '0.1em' },
  linkBtn: { background: 'none', border: 'none', color: '#c9a227', cursor: 'pointer', fontSize: 12, fontWeight: 700 },
  campaignCard: {
    background: '#111116', border: '1px solid #2a2a32', borderRadius: 8,
    cursor: 'pointer', textAlign: 'left', padding: 0, overflow: 'hidden',
    transition: 'border-color 0.15s',
  },
  campaignBanner: { padding: '14px 16px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' },
  statusBadge: { fontSize: 11, color: '#4ade80', fontWeight: 600, letterSpacing: '0.05em' },
  campaignBody: { padding: '12px 16px', display: 'flex', flexDirection: 'column', gap: 6 },
  campaignMeta: { display: 'flex', justifyContent: 'space-between' },
  campaignDesc: { fontSize: 12, color: '#9a9793', lineHeight: 1.5, margin: 0 },
  lastSession: { fontSize: 11, color: '#6b6966', fontStyle: 'italic' },
  eventRow: { borderLeft: '3px solid #c53030', paddingLeft: 12, paddingBottom: 8, borderBottom: '1px solid #2a2a32' },
  severityBadge: { fontSize: 10, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', padding: '2px 8px', borderRadius: 20 },
  charCard: {
    display: 'flex', justifyContent: 'space-between', alignItems: 'center',
    background: '#111116', border: '1px solid #2a2a32', borderRadius: 8,
    padding: '12px 16px', cursor: 'pointer', textAlign: 'left',
    transition: 'border-color 0.15s',
  },
  eventCard: {
    background: '#1c1c22', border: '1px solid #2a2a32', borderTop: '3px solid #c53030',
    borderRadius: 10, padding: '20px 24px',
  },
  filterBtn: {
    background: 'none', border: '1px solid #2a2a32', borderRadius: 20,
    color: '#9a9793', padding: '5px 14px', fontSize: 11, cursor: 'pointer',
    textTransform: 'capitalize', fontFamily: "'Nunito',sans-serif",
  },
  filterBtnActive: { background: 'rgba(197,48,48,0.15)', borderColor: '#c53030', color: '#e8e6e3' },
  tag: { fontSize: 10, background: '#2a2a32', color: '#9a9793', borderRadius: 20, padding: '2px 8px' },
};

Object.assign(window, { Dashboard, WorldEventsView, dbStyles, severityColor, severityBg });
