/* ============================================================
   WORKFLOW ADMINISTRATION
   Approval templates, freeze windows and the email outbox.
   ============================================================ */

function WorkflowAdmin({state, idx, act, toast}){
  const [tab, setTab] = useState('templates');
  if (!state.permissions.isAdmin) {
    return <EmptyState icon={<I.warn size={30}/>} title="Administrators only"/>;
  }
  return (
    <div className="stack">
      <div className="tabs">
        <button className={'tab'+(tab==='templates'?' active':'')} onClick={()=>setTab('templates')}>
          <I.stamp size={15}/>Approval templates
          <span className="n">{state.approvalTemplates.length}</span></button>
        <button className={'tab'+(tab==='freezes'?' active':'')} onClick={()=>setTab('freezes')}>
          <I.calendar size={15}/>Freeze windows
          <span className="n">{state.freezeWindows.length}</span></button>
        <button className={'tab'+(tab==='outbox'?' active':'')} onClick={()=>setTab('outbox')}>
          <I.mail size={15}/>Email outbox</button>
      </div>
      {tab === 'templates' && <ApprovalTemplateAdmin state={state} idx={idx} act={act}/>}
      {tab === 'freezes' && <FreezeWindowAdmin state={state} act={act}/>}
      {tab === 'outbox' && <OutboxAdmin toast={toast}/>}
    </div>
  );
}

/* ------------------------------------------------------ approval templates */

function ApprovalTemplateAdmin({state, idx, act}){
  const [editing, setEditing] = useState(null);
  const [stageDlg, setStageDlg] = useState(null);
  const [approverDlg, setApproverDlg] = useState(null);
  const [confirm, setConfirm] = useState(null);
  const empty = {name:'', description:'', changeType:'', risk:'', isDefault:false, isActive:true};

  return (
    <div className="stack">
      <div className="info-box">
        A template describes the route a change takes to approval: ordered stages, each with its
        own approvers and whether everyone or any one of them has to agree. When a change is
        submitted, Fuse picks the template whose type and risk fit best, and copies its stages onto
        that change. Editing a template never disturbs changes already in flight.
      </div>

      <div className="row">
        <div className="spacer"/>
        <button className="btn primary" onClick={()=>setEditing({...empty})}>
          <I.plus size={14}/>New template</button>
      </div>

      {state.approvalTemplates.length === 0 &&
        <div className="card"><EmptyState icon={<I.stamp size={28}/>} title="No approval templates"
          body="Without one, changes cannot be submitted for approval."/></div>}

      {state.approvalTemplates.map(t => (
        <div className="card" key={t.id}>
          <div className="card-head">
            <h3>{t.name}</h3>
            <Badge tone="slate">{t.changeType || 'Any type'}</Badge>
            <Badge tone="slate">{t.risk ? t.risk + ' risk' : 'Any risk'}</Badge>
            {t.isDefault && <Badge tone="accent">Default</Badge>}
            {!t.isActive && <Badge tone="amber">Inactive</Badge>}
            <div className="spacer"/>
            <button className="btn sm" onClick={()=>setEditing(t)}><I.edit size={13}/>Edit</button>
            <button className="btn sm" onClick={()=>setStageDlg({templateId:t.id, value:{name:'',description:'',rule:'all'}})}>
              <I.plus size={13}/>Add stage</button>
            <button className="icon-btn" title="Delete template" onClick={()=>setConfirm({type:'template', t})}>
              <I.trash size={14}/></button>
          </div>
          {t.description && <div className="card-body" style={{paddingBottom:0}}>
            <p className="small muted" style={{margin:0}}>{t.description}</p></div>}
          <div className="card-body">
            {t.stages.length === 0
              ? <EmptyState icon={<I.stamp size={24}/>} title="No stages yet"
                  body="A template with no stages cannot approve anything."/>
              : t.stages.map(s => (
                  <div className="approval-stage" key={s.id}>
                    <div className="approval-stage-head">
                      <span className="badge accent">{s.order + 1}</span>
                      <strong style={{fontSize:13}}>{s.name}</strong>
                      <Badge tone="slate">{s.rule === 'all' ? 'Everyone must approve' : 'Any one approver'}</Badge>
                      <div className="spacer"/>
                      <button className="btn sm ghost"
                        onClick={()=>setApproverDlg({templateId:t.id, stageId:s.id, stageName:s.name})}>
                        <I.plus size={13}/>Approver</button>
                      <button className="icon-btn" title="Edit stage"
                        onClick={()=>setStageDlg({templateId:t.id, value:{...s}})}><I.edit size={14}/></button>
                      <button className="icon-btn" title="Delete stage"
                        onClick={()=>setConfirm({type:'stage', t, s})}><I.trash size={14}/></button>
                    </div>
                    {s.description &&
                      <div className="card-body" style={{padding:'8px 14px'}}>
                        <span className="xsmall muted">{s.description}</span></div>}
                    {s.approvers.length === 0
                      ? <div className="card-body xsmall dim" style={{padding:'10px 14px'}}>
                          No approvers on this stage yet.</div>
                      : s.approvers.map(a => {
                          const party = resolveParty(a.party, idx);
                          return (
                            <div className="approver-row" key={a.id}>
                              <PartyAvatar resolved={party} size="sm"/>
                              <div style={{flex:1,minWidth:0}}>
                                <div style={{fontWeight:600,fontSize:13}}>{party ? party.name : 'Unknown'}</div>
                                <div className="xsmall dim">{party ? party.sub : ''}</div>
                              </div>
                              <button className="icon-btn" title="Remove"
                                onClick={()=>act(
                                  API.del(`/api/approval-templates/${t.id}/stages/${s.id}/approvers/${a.id}`),
                                  'Approver removed')}><I.x size={14}/></button>
                            </div>
                          );
                        })}
                  </div>
                ))}
          </div>
        </div>
      ))}

      {editing &&
        <TemplateForm value={editing} state={state} onClose={()=>setEditing(null)}
          onSave={async (v)=>{
            const ok = await act(v.id
              ? API.put(`/api/approval-templates/${v.id}`, v)
              : API.post('/api/approval-templates', v), 'Template saved');
            if (ok) setEditing(null);
          }}/>}

      {stageDlg &&
        <StageForm value={stageDlg.value} onClose={()=>setStageDlg(null)}
          onSave={async (v)=>{
            const base = `/api/approval-templates/${stageDlg.templateId}/stages`;
            const ok = await act(v.id ? API.put(`${base}/${v.id}`, v) : API.post(base, v), 'Stage saved');
            if (ok) setStageDlg(null);
          }}/>}

      {approverDlg &&
        <AddStageApproverModal state={state} stageName={approverDlg.stageName}
          onClose={()=>setApproverDlg(null)}
          onSave={async (party)=>{
            const ok = await act(API.post(
              `/api/approval-templates/${approverDlg.templateId}/stages/${approverDlg.stageId}/approvers`,
              {party}), 'Approver added');
            if (ok) setApproverDlg(null);
          }}/>}

      {confirm && confirm.type === 'template' &&
        <ConfirmDialog title="Delete template" onClose={()=>setConfirm(null)}
          message={`Delete "${confirm.t.name}"? Changes already using it keep their approvals.`}
          onConfirm={()=>act(API.del(`/api/approval-templates/${confirm.t.id}`), 'Template deleted')}/>}
      {confirm && confirm.type === 'stage' &&
        <ConfirmDialog title="Delete stage" onClose={()=>setConfirm(null)}
          message={`Delete the "${confirm.s.name}" stage and its approvers?`}
          onConfirm={()=>act(
            API.del(`/api/approval-templates/${confirm.t.id}/stages/${confirm.s.id}`), 'Stage deleted')}/>}
    </div>
  );
}

function TemplateForm({value, state, onClose, onSave}){
  const [t, setT] = useState(()=>clone(value));
  const set = (k,v) => setT(x => ({...x, [k]:v}));
  return (
    <Modal title={value.id ? 'Edit template' : 'New approval template'} onClose={onClose}
      footer={<>
        <button className="btn" onClick={onClose}>Cancel</button>
        <button className="btn primary" disabled={!t.name.trim()} onClick={()=>onSave(t)}>Save</button>
      </>}>
      <Field label="Name"><input type="text" value={t.name} autoFocus
        onChange={e=>set('name', e.target.value)} placeholder="e.g. Normal change"/></Field>
      <Field label="Description"><textarea value={t.description}
        onChange={e=>set('description', e.target.value)}/></Field>
      <div className="form-grid">
        <Field label="Applies to change type" hint="Leave as Any to match every type">
          <select value={t.changeType} onChange={e=>set('changeType', e.target.value)}>
            <option value="">Any type</option>
            {(state.changeTypes||[]).map(x => <option key={x}>{x}</option>)}</select></Field>
        <Field label="Applies to risk" hint="A risk match outranks a type match">
          <select value={t.risk} onChange={e=>set('risk', e.target.value)}>
            <option value="">Any risk</option>
            {(state.changeRisks||[]).map(x => <option key={x}>{x}</option>)}</select></Field>
      </div>
      <label className="row" style={{gap:8,cursor:'pointer',marginBottom:10}}>
        <input type="checkbox" checked={!!t.isDefault} onChange={e=>set('isDefault', e.target.checked)}/>
        <span className="small">Prefer this template when two match equally well</span>
      </label>
      <label className="row" style={{gap:8,cursor:'pointer'}}>
        <input type="checkbox" checked={t.isActive !== false} onChange={e=>set('isActive', e.target.checked)}/>
        <span className="small">Active. Inactive templates are never chosen for a new change.</span>
      </label>
    </Modal>
  );
}

function StageForm({value, onClose, onSave}){
  const [s, setS] = useState(()=>clone(value));
  const set = (k,v) => setS(x => ({...x, [k]:v}));
  return (
    <Modal title={value.id ? 'Edit stage' : 'Add a stage'} onClose={onClose}
      footer={<>
        <button className="btn" onClick={onClose}>Cancel</button>
        <button className="btn primary" disabled={!s.name.trim()} onClick={()=>onSave(s)}>Save</button>
      </>}>
      <Field label="Stage name"><input type="text" value={s.name} autoFocus
        onChange={e=>set('name', e.target.value)} placeholder="e.g. Technical review"/></Field>
      <Field label="Description" hint="Shown to approvers so they know what they are signing off.">
        <textarea value={s.description || ''} onChange={e=>set('description', e.target.value)}/></Field>
      <Field label="Rule">
        <div className="row wrap" style={{gap:8}}>
          <button type="button" className={'chip-toggle'+(s.rule==='all'?' on':'')}
            onClick={()=>set('rule','all')}>Everyone must approve</button>
          <button type="button" className={'chip-toggle'+(s.rule==='any'?' on':'')}
            onClick={()=>set('rule','any')}>Any one approver is enough</button>
        </div>
      </Field>
      {value.id !== undefined && value.order !== undefined &&
        <Field label="Order" hint="Lower numbers are asked first.">
          <input type="number" min="0" value={s.order || 0}
            onChange={e=>set('order', Number(e.target.value))}/></Field>}
    </Modal>
  );
}

function AddStageApproverModal({state, stageName, onClose, onSave}){
  const [party, setParty] = useState(null);
  return (
    <Modal title={`Add an approver to ${stageName}`} onClose={onClose}
      footer={<>
        <button className="btn" onClick={onClose}>Cancel</button>
        <button className="btn primary" disabled={!party} onClick={()=>onSave(party)}>Add</button>
      </>}>
      <p className="small muted" style={{marginTop:0}}>
        Naming a role rather than a person means the approval follows whoever currently holds it.
      </p>
      <Field label="Person or role">
        <PartySelect value={party} state={state} onChange={setParty} placeholder="Choose…"/>
      </Field>
    </Modal>
  );
}

/* --------------------------------------------------------- freeze windows */

function FreezeWindowAdmin({state, act}){
  const [editing, setEditing] = useState(null);
  const [confirm, setConfirm] = useState(null);
  const empty = {name:'', description:'', startsAt:'', endsAt:'', severity:'Warning', isActive:true};
  const now = new Date().toISOString();

  return (
    <div className="stack">
      <div className="info-box">
        A freeze window marks a period when changes should not land. A <strong>Warning</strong>
        {' '}window is advisory and shows on the change and the calendar. A <strong>Blocking</strong>
        {' '}window stops a change being scheduled or started in that period unless an editor
        records an override reason, which goes into the history and the audit log.
      </div>

      <div className="row">
        <div className="spacer"/>
        <button className="btn primary" onClick={()=>setEditing({...empty})}>
          <I.plus size={14}/>New freeze window</button>
      </div>

      <div className="card">
        <div className="card-head"><h3>Freeze windows</h3>
          <Badge tone="slate">{state.freezeWindows.length}</Badge></div>
        {state.freezeWindows.length === 0
          ? <EmptyState icon={<I.calendar size={28}/>} title="No freeze windows"/>
          : <div className="tbl-wrap">
              <table>
                <thead><tr><th>Name</th><th>From</th><th>To</th><th>Severity</th>
                  <th>Status</th><th style={{width:70}}></th></tr></thead>
                <tbody>
                  {state.freezeWindows.map(f => {
                    const active = f.isActive && f.startsAt <= now && f.endsAt >= now;
                    const past = f.endsAt < now;
                    return (
                      <tr key={f.id}>
                        <td>
                          <span className="cell-name">{f.name}</span>
                          {f.description && <span className="cell-sub" style={{display:'block'}}>{f.description}</span>}
                        </td>
                        <td className="small">{fmtDateTime(f.startsAt)}</td>
                        <td className="small">{fmtDateTime(f.endsAt)}</td>
                        <td><Badge tone={f.severity === 'Blocking' ? 'red' : 'amber'}>{f.severity}</Badge></td>
                        <td>{!f.isActive ? <Badge tone="slate">Off</Badge>
                          : active ? <Badge tone="red" dot>In force now</Badge>
                          : past ? <Badge tone="slate">Past</Badge>
                          : <Badge tone="green" dot>Upcoming</Badge>}</td>
                        <td><div className="row" style={{gap:2}}>
                          <button className="icon-btn" onClick={()=>setEditing(f)}><I.edit size={14}/></button>
                          <button className="icon-btn" onClick={()=>setConfirm(f)}><I.trash size={14}/></button>
                        </div></td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            </div>}
      </div>

      {editing &&
        <FreezeForm value={editing} onClose={()=>setEditing(null)}
          onSave={async (v)=>{
            const ok = await act(v.id
              ? API.put(`/api/freeze-windows/${v.id}`, v)
              : API.post('/api/freeze-windows', v), 'Freeze window saved');
            if (ok) setEditing(null);
          }}/>}
      {confirm &&
        <ConfirmDialog title="Delete freeze window" onClose={()=>setConfirm(null)}
          message={`Delete "${confirm.name}"? Changes already scheduled are not affected.`}
          onConfirm={()=>act(API.del(`/api/freeze-windows/${confirm.id}`), 'Freeze window deleted')}/>}
    </div>
  );
}

function FreezeForm({value, onClose, onSave}){
  const [f, setF] = useState(()=>clone(value));
  const set = (k,v) => setF(x => ({...x, [k]:v}));
  const valid = f.name.trim() && f.startsAt && f.endsAt && f.endsAt > f.startsAt;
  return (
    <Modal title={value.id ? 'Edit freeze window' : 'New freeze window'} onClose={onClose}
      footer={<>
        <button className="btn" onClick={onClose}>Cancel</button>
        <button className="btn primary" disabled={!valid} onClick={()=>onSave(f)}>Save</button>
      </>}>
      <Field label="Name"><input type="text" value={f.name} autoFocus
        onChange={e=>set('name', e.target.value)} placeholder="e.g. Month-end close"/></Field>
      <Field label="Description" hint="Shown to anyone whose change lands in this window.">
        <textarea value={f.description} onChange={e=>set('description', e.target.value)}/></Field>
      <div className="form-grid">
        <Field label="Starts"><input type="datetime-local" value={toLocalInput(f.startsAt)}
          onChange={e=>set('startsAt', fromLocalInput(e.target.value))}/></Field>
        <Field label="Ends"><input type="datetime-local" value={toLocalInput(f.endsAt)}
          onChange={e=>set('endsAt', fromLocalInput(e.target.value))}/></Field>
      </div>
      <Field label="Severity">
        <div className="row wrap" style={{gap:8}}>
          <button type="button" className={'chip-toggle'+(f.severity==='Warning'?' on':'')}
            onClick={()=>set('severity','Warning')}>Warning only</button>
          <button type="button" className={'chip-toggle'+(f.severity==='Blocking'?' on':'')}
            onClick={()=>set('severity','Blocking')}>Blocking, override needed</button>
        </div>
      </Field>
      <label className="row" style={{gap:8,cursor:'pointer'}}>
        <input type="checkbox" checked={f.isActive !== false} onChange={e=>set('isActive', e.target.checked)}/>
        <span className="small">Active</span>
      </label>
    </Modal>
  );
}

/* ------------------------------------------------------------ the outbox */

function OutboxAdmin({toast}){
  const [data, setData] = useState(null);
  const [filter, setFilter] = useState('');
  const [busy, setBusy] = useState(false);
  const [open, setOpen] = useState(null);

  const load = useCallback(async () => {
    try { setData(await API.get('/api/outbox' + (filter ? `?status=${filter}` : ''))); }
    catch (err) { toast(err.message, true); }
  }, [filter, toast]);
  useEffect(()=>{ load(); }, [load]);

  if (!data) return <div className="row" style={{gap:10,padding:20}}><span className="spinner"/> Loading…</div>;

  const tone = (s) => s === 'Sent' ? 'green' : s === 'Failed' ? 'red' : s === 'Suppressed' ? 'slate' : 'amber';

  return (
    <div className="stack">
      {!data.smtp.configured
        ? <div className="warn-box">
            <strong>No SMTP server is configured, so nothing is being sent.</strong> Every
            notification email is composed and kept here so you can see exactly what would have
            gone out. Set <code>SMTP_HOST</code>, <code>SMTP_PORT</code>, <code>SMTP_USER</code>
            {' '}and <code>SMTP_PASS</code> and restart to start delivering.
          </div>
        : <div className="info-box">
            Sending through <strong>{data.smtp.host}:{data.smtp.port}</strong> ({data.smtp.mode})
            {data.smtp.user ? ` as ${data.smtp.user}` : ''}, from {data.smtp.from}.
          </div>}

      <div className="row wrap">
        {['', 'Queued', 'Sent', 'Failed', 'Suppressed'].map(s =>
          <button key={s || 'all'} className={'chip-toggle' + (filter === s ? ' on' : '')}
            onClick={()=>setFilter(s)}>
            {s || 'All'}{data.counts[s] != null ? ` · ${data.counts[s]}` : ''}</button>)}
        <div className="spacer"/>
        <button className="btn sm" onClick={load}><I.refresh size={13}/>Refresh</button>
        {data.smtp.configured &&
          <button className="btn sm primary" disabled={busy}
            onClick={async ()=>{
              setBusy(true);
              try {
                const res = await API.post('/api/outbox/flush', {});
                toast(`Sent ${res.sent}, failed ${res.failed}`);
                await load();
              } catch (err) { toast(err.message, true); }
              setBusy(false);
            }}><I.up size={13}/>Send queued now</button>}
      </div>

      <div className="card">
        <div className="card-head"><h3>Messages</h3><Badge tone="slate">{data.messages.length}</Badge>
          <span className="xsmall dim">Most recent 200</span></div>
        {data.messages.length === 0
          ? <EmptyState icon={<I.mail size={28}/>} title="Nothing here yet"/>
          : <div className="tbl-wrap" style={{maxHeight:'calc(100vh - 340px)'}}>
              <table>
                <thead><tr><th style={{width:150}}>When</th><th>To</th><th>Subject</th>
                  <th>Event</th><th>Status</th></tr></thead>
                <tbody>
                  {data.messages.map(m => (
                    <tr key={m.id} className="clickable" onClick={()=>setOpen(m)}>
                      <td className="small muted">{fmtDateTime(m.createdAt)}</td>
                      <td className="small">{m.toName || m.to}
                        <span className="cell-sub" style={{display:'block'}}>{m.to}</span></td>
                      <td className="small">{m.subject}</td>
                      <td className="xsmall dim">{m.eventKey}</td>
                      <td>
                        <Badge tone={tone(m.status)} dot>{m.status}</Badge>
                        {m.attempts > 0 && <span className="xsmall dim"> ·{m.attempts}</span>}
                      </td>
                    </tr>))}
                </tbody>
              </table>
            </div>}
      </div>

      {open &&
        <Modal wide title={open.subject} onClose={()=>setOpen(null)}
          footer={<>
            <button className="btn" onClick={()=>setOpen(null)}>Close</button>
            {open.status === 'Failed' &&
              <button className="btn primary" onClick={async ()=>{
                await API.post(`/api/outbox/${open.id}/retry`, {});
                toast('Queued for another attempt');
                setOpen(null); load();
              }}>Retry</button>}
          </>}>
          <dl className="kv-block">
            <dt>To</dt><dd>{open.toName ? `${open.toName} <${open.to}>` : open.to}</dd>
            <dt>Event</dt><dd className="mono">{open.eventKey || '—'}</dd>
            <dt>Status</dt><dd><Badge tone={tone(open.status)} dot>{open.status}</Badge></dd>
            <dt>Created</dt><dd className="small muted">{fmtDateTime(open.createdAt)}</dd>
            {open.sentAt && <><dt>Sent</dt><dd className="small muted">{fmtDateTime(open.sentAt)}</dd></>}
            {open.lastError && <><dt>Last error</dt><dd className="small" style={{color:'var(--red)'}}>
              {open.lastError}</dd></>}
          </dl>
          <div className="sub-head">Message</div>
          <div className="pre-wrap small muted" style={{padding:'12px 14px',border:'1px solid var(--border)',
            borderRadius:'var(--radius-sm)',background:'var(--surface-2)'}}>{open.bodyText}</div>
        </Modal>}
    </div>
  );
}

/* ------------------------------------------------- notification preferences */

function NotificationPreferences({toast}){
  const [prefs, setPrefs] = useState(null);
  const [emailConfigured, setEmailConfigured] = useState(false);
  const [busy, setBusy] = useState(false);

  useEffect(()=>{
    API.get('/api/notifications/preferences')
      .then(res => { setPrefs(res.preferences); setEmailConfigured(res.emailConfigured); })
      .catch(() => setPrefs([]));
  }, []);

  const save = async (next) => {
    setPrefs(next);
    setBusy(true);
    try { await API.put('/api/notifications/preferences', {preferences: next}); }
    catch (err) { toast(err.message, true); }
    setBusy(false);
  };
  const toggle = (key, channel) =>
    save(prefs.map(p => p.key === key ? {...p, [channel]: !p[channel]} : p));

  if (!prefs) return <div className="row" style={{gap:10,padding:16}}><span className="spinner"/> Loading…</div>;

  return (
    <div className="card">
      <div className="card-head"><h3>What you hear about</h3>
        {busy && <span className="spinner"/>}
        <div className="spacer"/>
        <span className="xsmall dim">
          {emailConfigured ? 'Email is delivering' : 'Email is queued to the outbox only'}</span>
      </div>
      <div>
        {prefs.map(p => (
          <div className="pref-row" key={p.key}>
            <span style={{flex:1,minWidth:0}}>{p.label}</span>
            <label className="pref-toggle">
              <input type="checkbox" checked={p.inApp} onChange={()=>toggle(p.key, 'inApp')}/>
              In app
            </label>
            <label className="pref-toggle" title={emailConfigured ? '' : 'Queued in the outbox until SMTP is set up'}>
              <input type="checkbox" checked={p.email} onChange={()=>toggle(p.key, 'email')}/>
              Email
            </label>
          </div>
        ))}
      </div>
    </div>
  );
}
