/* ============================================================
   OBSERVABILITY & MONITORING MODULE
   Two unrelated things share this module because they're both about keeping
   an eye on the estate: a manual register of things that expire (Environment
   items), and an opt-in uptime checker built from the URLs already recorded
   against a component's linked tools (Availability).
   ============================================================ */

const NEW_ENV_ITEM = () => ({
  id:'', name:'', itemType:'Other', description:'', owner:null,
  issuedAt:'', expiresAt:'', rotationNotes:'', status:'Active', replacedById:null, componentIds:[]
});

const EXPIRY_WARNING_DAYS = 30;

function expiryTone(expiresAt){
  const n = daysUntil(expiresAt);
  if (n == null) return 'slate';
  if (n < 0) return 'red';
  if (n <= EXPIRY_WARNING_DAYS) return 'amber';
  return 'green';
}
function expiryLabel(expiresAt){
  const n = daysUntil(expiresAt);
  if (n == null) return '—';
  if (n < 0) return `Expired ${-n}d ago`;
  if (n === 0) return 'Expires today';
  return `${n}d left`;
}

function ObservabilityModule({state, idx, go, act, toast, route}){
  const [tab, setTab] = useState('items');
  const [editing, setEditing] = useState(null);
  const canEdit = state.permissions.canEditObservability;

  useEffect(()=>{
    if (!route) return;
    if (route.action === 'new') { setTab('items'); setEditing(NEW_ENV_ITEM()); }
    else if (route.tab) setTab(route.tab);
  }, [route]);

  const expired = state.envItems.filter(e => e.status==='Active' && daysUntil(e.expiresAt) < 0).length;
  const expiringSoon = state.envItems.filter(e => e.status==='Active' &&
    daysUntil(e.expiresAt) >= 0 && daysUntil(e.expiresAt) <= EXPIRY_WARNING_DAYS).length;
  const monitoredComponents = new Set(state.monitorTargets.filter(t=>t.enabled).map(t=>t.componentId)).size;
  const down = new Set(state.monitorTargets.filter(t=>t.enabled && t.status==='Down').map(t=>t.componentId)).size;

  return (
    <div className="mod">
      <div className="mod-head">
        <div className="stats-row">
          <div className="stat"><div className="n" style={{color: expired ? 'var(--red)' : undefined}}>{expired}</div>
            <div className="l">Expired</div></div>
          <div className="stat"><div className="n" style={{color: expiringSoon ? 'var(--amber)' : undefined}}>{expiringSoon}</div>
            <div className="l">Expiring soon</div></div>
          <div className="stat"><div className="n">{monitoredComponents}</div><div className="l">Monitored components</div></div>
          <div className="stat"><div className="n" style={{color: down ? 'var(--red)' : undefined}}>{down}</div>
            <div className="l">Currently down</div></div>
        </div>
        <div className="tabs" style={{margin:0}}>
          <button className={'tab'+(tab==='items'?' active':'')} onClick={()=>setTab('items')}>
            <I.pulse size={15}/>Environment items <span className="n">{state.envItems.length}</span></button>
          <button className={'tab'+(tab==='availability'?' active':'')} onClick={()=>setTab('availability')}>
            <I.chart size={15}/>Availability</button>
        </div>
      </div>

      {tab === 'items' &&
        <EnvItemsTab state={state} idx={idx} act={act} toast={toast} canEdit={canEdit}
          editing={editing} setEditing={setEditing}/>}
      {tab === 'availability' &&
        <div className="mod-body"><div className="mod-scroll">
          <AvailabilityTab state={state} idx={idx} go={go} act={act} toast={toast} canEdit={canEdit}/>
        </div></div>}
    </div>
  );
}

/* ============================================================
   ENVIRONMENT ITEMS
   ============================================================ */
function EnvItemsTab({state, idx, act, toast, canEdit, editing, setEditing}){
  const [q, setQ] = useState('');
  const [dlg, setDlg] = useState(null);
  const {sort, sorted, Th} = useSort('expires', {
    name: e => e.name, type: e => e.itemType,
    owner: e => (resolveParty(e.owner, idx)||{}).name || 'zzz',
    expires: e => e.expiresAt || '9999', importance: e => e.importanceTier == null ? 99 : e.importanceTier,
    status: e => e.status
  });

  const term = q.trim().toLowerCase();
  const rows = useMemo(()=>{
    const list = state.envItems.filter(e => !term ||
      [e.name, e.itemType, e.description, (resolveParty(e.owner, idx)||{}).name]
        .filter(Boolean).join(' ').toLowerCase().includes(term));
    return sorted(list);
  }, [state.envItems, term, sort, idx]);

  return (
    <>
      <div className="mod-head">
        <div className="row wrap">
          <SearchBox value={q} onChange={setQ} aria="Search environment items"
                     title="Search name, type, description or owner"/>
          {q && <button className="btn ghost sm" onClick={()=>setQ('')}>Clear</button>}
          <div className="spacer"/>
          <span className="xsmall dim">{rows.length} shown</span>
        </div>
      </div>

      <div className="mod-body">
        <div className="card">
          {!canEdit && <ReadOnlyBanner message="You have read-only access to environment items. Editors and administrators can make changes."/>}
          <div className="tbl-wrap">
            <table>
              <thead><tr>
                <Th k="name" label="Name"/>
                <Th k="type" label="Type"/>
                <Th k="owner" label="Owner"/>
                <Th k="expires" label="Expires"/>
                <Th k="importance" label="Importance"/>
                <Th k="status" label="Status"/>
                {canEdit && <th style={{width:70}}></th>}
              </tr></thead>
              <tbody>{rows.length === 0
                ? <tr><td colSpan={canEdit?7:6}>
                    <EmptyState icon={<I.pulse size={26}/>} title="No environment items match"
                      action={q ? <button className="btn sm" onClick={()=>setQ('')}>Clear search</button>
                        : canEdit ? <button className="btn primary sm" onClick={()=>setEditing(NEW_ENV_ITEM())}>
                            Add the first item</button> : null}/>
                  </td></tr>
                : rows.map(e => {
                  const owner = resolveParty(e.owner, idx);
                  return (
                    <tr key={e.id}>
                      <td><span className="cell-name">{e.name}</span>
                        {e.description && <span className="cell-sub" style={{display:'block'}}>{e.description}</span>}</td>
                      <td className="small muted">{e.itemType}</td>
                      <td className="small">{owner ? owner.name : <span className="dim">Not assigned</span>}</td>
                      <td><Badge tone={expiryTone(e.expiresAt)}>{expiryLabel(e.expiresAt)}</Badge>
                        <div className="xsmall dim">{fmtDate(e.expiresAt)}</div></td>
                      <td>{e.importanceTier != null ? <TierBadge tier={e.importanceTier}/> : <span className="dim">—</span>}</td>
                      <td><Badge tone={e.status==='Active'?'accent':'slate'}>{e.status}</Badge></td>
                      {canEdit && <td><div className="row" style={{gap:2}}>
                        <button className="icon-btn" onClick={()=>setEditing(e)}><I.edit size={14}/></button>
                        <button className="icon-btn" onClick={()=>setDlg(e)}><I.trash size={14}/></button>
                      </div></td>}
                    </tr>
                  );
                })}</tbody>
            </table>
          </div>
        </div>
      </div>

      {editing &&
        <EnvItemForm value={editing} state={state} onClose={()=>setEditing(null)}
          onSave={async (v)=>{
            const isNew = !v.id;
            const ok = await act(isNew ? API.post('/api/env-items', v) : API.put(`/api/env-items/${v.id}`, v),
              isNew ? 'Environment item added' : 'Environment item saved');
            if (ok) setEditing(null);
          }}/>}

      {dlg &&
        <ConfirmDialog title="Delete environment item" onClose={()=>setDlg(null)}
          message={`Delete "${dlg.name}"? You'll have a few seconds to undo.`}
          onConfirm={()=>{ deleteWithUndo(act, toast, `/api/env-items/${dlg.id}`, dlg.name); setDlg(null); }}/>}
    </>
  );
}

function EnvItemForm({value, state, onClose, onSave}){
  const [e, setE] = useState(()=>clone(value));
  const [busy, setBusy] = useState(false);
  const set = (k,v) => setE(x => ({...x, [k]:v}));
  const isNew = !value.id;
  const replaceable = state.envItems.filter(x => x.id !== value.id);

  return (
    <Modal title={isNew ? 'Add environment item' : 'Edit ' + value.name} onClose={onClose}
      footer={<>
        <button className="btn" onClick={onClose} disabled={busy}>Cancel</button>
        <button className="btn primary" disabled={!e.name.trim() || !e.expiresAt || busy}
          onClick={async ()=>{ setBusy(true); try { await onSave(e); } finally { setBusy(false); } }}>
          {busy ? 'Saving…' : isNew ? 'Add item' : 'Save changes'}</button>
      </>}>
      <div className="form-grid">
        <Field label="Name"><input type="text" value={e.name} autoFocus
          onChange={ev=>set('name', ev.target.value)} placeholder="e.g. Production Stripe API key"/></Field>
        <Field label="Type">
          <select value={e.itemType} onChange={ev=>set('itemType', ev.target.value)}>
            {(state.envItemTypes||[]).map(t => <option key={t}>{t}</option>)}</select></Field>
      </div>
      <Field label="Description"><textarea value={e.description}
        onChange={ev=>set('description', ev.target.value)}/></Field>

      <div className="sub-head">Dates and ownership</div>
      <div className="form-grid">
        <Field label="Issued" hint="When it was actually created, if known.">
          <input type="date" value={e.issuedAt} onChange={ev=>set('issuedAt', ev.target.value)}/></Field>
        <Field label="Expires"><input type="date" value={e.expiresAt}
          onChange={ev=>set('expiresAt', ev.target.value)}/></Field>
        <Field label="Owner"><PartySelect value={e.owner} state={state} onChange={v=>set('owner', v)}/></Field>
        <Field label="Status">
          <select value={e.status} onChange={ev=>set('status', ev.target.value)}>
            {(state.envItemStatuses||[]).map(t => <option key={t}>{t}</option>)}</select></Field>
      </div>
      {e.status === 'Rotated' &&
        <Field label="Replaced by" hint="Optional — keeps the rotation history linked instead of just disappearing.">
          <select value={e.replacedById||''} onChange={ev=>set('replacedById', ev.target.value||null)}>
            <option value="">Not linked</option>
            {replaceable.map(x => <option key={x.id} value={x.id}>{x.name}</option>)}
          </select></Field>}

      <Field label="How to rotate it" hint="Steps or a link to the runbook — whatever the next person needs.">
        <textarea value={e.rotationNotes} onChange={ev=>set('rotationNotes', ev.target.value)}/></Field>

      <div className="sub-head">Linked components</div>
      <ComponentPicker state={state} value={e.componentIds} onChange={v=>set('componentIds', v)}/>
    </Modal>
  );
}

/* ============================================================
   AVAILABILITY
   ============================================================ */

/** Every tool linked to a component, expanded into one candidate URL row
 * per primary link and per extra link — the exact set the "Manage
 * monitoring" picker offers, and what a monitor_targets row can point at. */
function candidateUrlsFor(component, state){
  const tools = state.tools.filter(t => (t.componentIds||[]).includes(component.id));
  const rows = [];
  tools.forEach(t => {
    rows.push({toolId:t.id, toolLinkId:null, toolName:t.name,
      label:t.primaryLabel||'Production', url:t.primaryUrl});
    (t.links||[]).forEach(l => rows.push({toolId:t.id, toolLinkId:l.id, toolName:t.name,
      label:l.label, url:l.url}));
  });
  return rows;
}

function statusTone2(status){
  return status==='Up' ? 'green' : status==='Degraded' ? 'amber' : status==='Down' ? 'red' : 'slate';
}

function AvailabilityTab({state, idx, go, act, toast, canEdit}){
  const [managing, setManaging] = useState(null);
  const monitoredComponentIds = [...new Set(state.monitorTargets.map(t=>t.componentId))];
  const rows = monitoredComponentIds.map(cid => {
    const comp = idx.comp[cid];
    const targets = state.monitorTargets.filter(t => t.componentId === cid);
    const enabledTargets = targets.filter(t=>t.enabled);
    const worst = enabledTargets.some(t=>t.status==='Down') ? 'Down'
      : enabledTargets.some(t=>t.status==='Degraded') ? 'Degraded'
      : enabledTargets.length ? 'Up' : null;
    return {comp, targets, enabledTargets, worst};
  }).filter(r => r.comp).sort((a,b) => a.comp.name.localeCompare(b.comp.name));

  return (
    <div className="stack">
      <div className="row wrap">
        <span className="xsmall dim">Components become monitorable through the URLs recorded against their linked tools.</span>
        <div className="spacer"/>
        {canEdit && <button className="btn sm primary" onClick={()=>setManaging({pickFirst:true})}>
          <I.plus size={13}/>Monitor a component</button>}
      </div>

      <div className="card">
        <div className="tbl-wrap">
          <table>
            <thead><tr><th>Component</th><th>Status</th><th>URLs monitored</th><th>Worst 30d uptime</th>
              {canEdit && <th style={{width:110}}></th>}</tr></thead>
            <tbody>{rows.length === 0
              ? <tr><td colSpan={canEdit?5:4}>
                  <EmptyState icon={<I.chart size={28}/>} title="Nothing monitored yet"
                    body="Pick a component and choose which of its tool URLs to keep an eye on."/>
                </td></tr>
              : rows.map(({comp, targets, enabledTargets, worst}) => {
                const uptimes = enabledTargets.map(t=>t.uptimePct).filter(x=>x!=null);
                const worstUptime = uptimes.length ? Math.min(...uptimes) : null;
                return (
                  <tr key={comp.id}>
                    <td><span className="cell-name clickable" onClick={()=>go({view:'component', id:comp.id})}>
                      {comp.name}</span> <TierBadge tier={comp.tier}/></td>
                    <td>{worst ? <Badge tone={statusTone2(worst)} dot>{worst}</Badge> : <span className="dim">No checks yet</span>}</td>
                    <td className="small mono">{enabledTargets.length}/{targets.length} enabled</td>
                    <td className="small mono">{worstUptime!=null ? worstUptime+'%' : '—'}</td>
                    {canEdit && <td><button className="btn sm" onClick={()=>setManaging({component:comp})}>Manage</button></td>}
                  </tr>
                );
              })}</tbody>
          </table>
        </div>
      </div>

      {managing &&
        <ManageMonitoringModal initial={managing.component} pickFirst={managing.pickFirst}
          state={state} idx={idx} go={go} act={act} toast={toast} onClose={()=>setManaging(null)}/>}
    </div>
  );
}

function ManageMonitoringModal({initial, pickFirst, state, idx, go, act, toast, onClose}){
  const [componentId, setComponentId] = useState(initial ? initial.id : '');
  const component = state.components.find(c => c.id === componentId);
  const candidates = component ? candidateUrlsFor(component, state) : [];
  const targetFor = (cand) => state.monitorTargets.find(t =>
    t.componentId === componentId && t.toolId === cand.toolId && t.toolLinkId === cand.toolLinkId);

  const sortedComponents = [...state.components].sort((a,b)=>a.name.localeCompare(b.name));

  return (
    <Modal wide title="Manage monitoring" onClose={onClose}
      footer={<button className="btn" onClick={onClose}>Done</button>}>
      {pickFirst && !initial &&
        <Field label="Component">
          <select value={componentId} onChange={e=>setComponentId(e.target.value)}>
            <option value="">Choose a component…</option>
            {sortedComponents.map(c => <option key={c.id} value={c.id}>{c.identifier} — {c.name}</option>)}
          </select>
        </Field>}

      {component && component.status !== 'Active' &&
        <div className="warn-box" style={{marginBottom:14}}>
          {component.name} is {component.status.toLowerCase()}, so its monitoring stays disabled
          regardless of these settings until it’s Active again.
        </div>}

      {component && candidates.length === 0 &&
        <EmptyState icon={<I.link size={26}/>} title="No tools linked to this component"
          body="Link a tool with a URL on the component's Links tab first."/>}

      {component && candidates.map(cand => {
        const t = targetFor(cand);
        return (
          <MonitorTargetRow key={cand.toolId+'|'+(cand.toolLinkId||'primary')}
            componentId={componentId} cand={cand} target={t} state={state} idx={idx} go={go} act={act} toast={toast}/>
        );
      })}
    </Modal>
  );
}

function MonitorTargetRow({componentId, cand, target, state, idx, go, act, toast}){
  const [override, setOverride] = useState(target ? target.overrideUrl : '');
  const [interval, setInterval_] = useState(target ? target.checkIntervalMinutes : 5);
  const [viewingDetail, setViewingDetail] = useState(false);

  useEffect(()=>{ setOverride(target ? target.overrideUrl : ''); setInterval_(target ? target.checkIntervalMinutes : 5); },
    [target && target.id, target && target.overrideUrl, target && target.checkIntervalMinutes]);

  const presets = [5,15,30,60,180,360,720,1440];
  const presetLabel = (m) => m < 60 ? `${m} min` : m < 1440 ? `${m/60}h` : `${m/1440}d`;

  const enable = () => act(API.post('/api/monitor-targets',
    {componentId, toolId:cand.toolId, toolLinkId:cand.toolLinkId, checkIntervalMinutes:interval}), 'Monitoring enabled');
  const toggle = () => act(API.put(`/api/monitor-targets/${target.id}`, {enabled: !target.enabled}),
    target.enabled ? 'Monitoring paused' : 'Monitoring resumed');
  const saveSettings = () => act(API.put(`/api/monitor-targets/${target.id}`,
    {overrideUrl: override, checkIntervalMinutes: interval}), 'Saved');

  return (
    <div className="card" style={{marginBottom:10}}>
      <div className="card-body" style={{display:'flex',flexDirection:'column',gap:8}}>
        <div className="row wrap" style={{gap:10}}>
          <span style={{flex:1,minWidth:0}}>
            <span className="cell-name">{cand.toolName} — {cand.label}</span>
            <span className="cell-sub" style={{display:'block'}}>{override || cand.url}</span>
          </span>
          {target && target.status &&
            <Badge tone={statusTone2(target.status)} dot>{target.status}</Badge>}
          {target
            ? <button className={'chip-toggle'+(target.enabled?' on':'')} onClick={toggle}>
                {target.enabled ? 'Enabled' : 'Disabled'}</button>
            : <button className="btn sm primary" onClick={enable}>Enable monitoring</button>}
        </div>

        {target && <>
          <div className="row wrap" style={{gap:10}}>
            <Field label="Override URL" hint="Optional — poll a different URL than the one above." style={{flex:1,minWidth:220}}>
              <input type="url" value={override} onChange={e=>setOverride(e.target.value)}
                onBlur={saveSettings} placeholder="https://"/></Field>
            <Field label="Check every">
              <select value={interval} onChange={e=>{ setInterval_(Number(e.target.value));
                act(API.put(`/api/monitor-targets/${target.id}`, {checkIntervalMinutes: Number(e.target.value)}), 'Saved'); }}>
                {presets.map(m => <option key={m} value={m}>{presetLabel(m)}</option>)}
              </select>
            </Field>
          </div>
          <div className="row wrap" style={{gap:14}}>
            <span className="xsmall dim">
              {target.checkedAt ? `Last checked ${relative(target.checkedAt)}` : 'Not checked yet'}
              {target.responseMs != null ? ` · ${target.responseMs}ms` : ''}
              {target.uptimePct != null ? ` · ${target.uptimePct}% up (30d)` : ''}
            </span>
            <div className="spacer"/>
            <button className="btn ghost sm" onClick={()=>setViewingDetail(true)}>View details</button>
            <button className="icon-btn" title="Remove"
              onClick={()=>deleteWithUndo(act, toast, `/api/monitor-targets/${target.id}`, cand.label)}>
              <I.trash size={14}/></button>
          </div>
        </>}
      </div>
      {target && viewingDetail &&
        <MonitorTargetDetailModal targetId={target.id} state={state} idx={idx} go={go}
          onClose={()=>setViewingDetail(false)}/>}
    </div>
  );
}

/* ------------------------------------------------------- target detail view */

const RANGE_PRESETS = [
  {k:'1h', label:'1 hour',  ms:3600000},
  {k:'6h', label:'6 hours', ms:6*3600000},
  {k:'1d', label:'1 day',   ms:86400000},
  {k:'7d', label:'7 days',  ms:7*86400000},
  {k:'30d',label:'30 days', ms:30*86400000}
];

// `toLocalInput`/`fromLocalInput` (datetime-local <-> ISO string round-trip)
// already exist as globals from 55-changes.jsx — reused here rather than
// redeclared, since a duplicate top-level const of the same name in another
// classic <script> tag would throw and silently break this whole file.

/** Groups consecutive 'Down' checks (ascending order) into outage intervals.
 * An outage still open at the end of the fetched range has no end, and its
 * duration is measured to the last fetched check. Computed client-side from
 * an already-fetched, range-bounded list — consistent with how this app
 * computes derived values on read rather than storing them. An outage that
 * began before the selected start will show a clipped start time, since
 * there's no visibility earlier than the fetched window. */
function deriveOutages(checksAsc){
  const outages = [];
  let openStart = null;
  checksAsc.forEach((c) => {
    if (c.status === 'Down') { if (openStart == null) openStart = c.checkedAt; }
    else if (openStart != null) {
      outages.push({start: openStart, end: c.checkedAt,
        minutes: Math.round((new Date(c.checkedAt) - new Date(openStart)) / 60000)});
      openStart = null;
    }
  });
  if (openStart != null) {
    const last = checksAsc[checksAsc.length-1];
    outages.push({start: openStart, end: null,
      minutes: Math.round((new Date(last.checkedAt) - new Date(openStart)) / 60000)});
  }
  return outages.reverse();
}

const STATUS_Y = {Up:1, Degraded:0.5, Down:0};
const STATUS_COLOR = {Up:'#1f9d55', Degraded:'#d97706', Down:'#dc2626'};

/** Hand-rolled step-line SVG (status over time, colored by Up/Degraded/Down,
 * with red bands under Down stretches). No charting library exists in this
 * app or is being added — package.json has zero dependencies. */
function UptimeLineChart({checks, height=180}){
  const [hoverIdx, setHoverIdx] = useState(null);
  if (!checks.length) return null;
  const width = 720, padL=8, padR=8, padT=10, padB=6;
  const innerW = width-padL-padR, innerH = height-padT-padB;
  const t0 = new Date(checks[0].checkedAt).getTime();
  const t1 = new Date(checks[checks.length-1].checkedAt).getTime();
  const span = Math.max(1, t1-t0);
  const x = (t) => padL + ((new Date(t).getTime()-t0)/span)*innerW;
  const y = (status) => padT + (1-(STATUS_Y[status] ?? 0))*innerH;

  let path = `M ${x(checks[0].checkedAt)} ${y(checks[0].status)}`;
  for (let i=1;i<checks.length;i++){
    path += ` L ${x(checks[i].checkedAt)} ${y(checks[i-1].status)} L ${x(checks[i].checkedAt)} ${y(checks[i].status)}`;
  }
  const bands = []; let bandStart = null;
  checks.forEach(c => {
    if (c.status==='Down' && bandStart==null) bandStart = c.checkedAt;
    if (c.status!=='Down' && bandStart!=null) { bands.push([bandStart, c.checkedAt]); bandStart=null; }
  });
  if (bandStart!=null) bands.push([bandStart, checks[checks.length-1].checkedAt]);
  const hovered = hoverIdx!=null ? checks[hoverIdx] : null;

  return (
    <div>
      <svg viewBox={`0 0 ${width} ${height}`} style={{width:'100%',height,display:'block'}}
        onMouseLeave={()=>setHoverIdx(null)}>
        {bands.map(([s,e],i) => <rect key={i} x={x(s)} y={padT} width={Math.max(1,x(e)-x(s))} height={innerH}
          fill="var(--red-soft)" opacity={0.6}/>)}
        <line x1={padL} y1={y('Up')} x2={width-padR} y2={y('Up')} stroke="var(--border)" strokeDasharray="2,3"/>
        <line x1={padL} y1={y('Down')} x2={width-padR} y2={y('Down')} stroke="var(--border)" strokeDasharray="2,3"/>
        <path d={path} fill="none" stroke="var(--accent)" strokeWidth="2" strokeLinejoin="round"/>
        {checks.map((c,i) => <circle key={c.id} cx={x(c.checkedAt)} cy={y(c.status)} r={hoverIdx===i?4:2.5}
          fill={STATUS_COLOR[c.status]||'#888'} onMouseEnter={()=>setHoverIdx(i)}/>)}
      </svg>
      <div className="xsmall dim" style={{display:'flex',justifyContent:'space-between'}}>
        <span>{fmtDateTime(checks[0].checkedAt)}</span><span>{fmtDateTime(checks[checks.length-1].checkedAt)}</span>
      </div>
      {hovered && <div className="xsmall" style={{marginTop:4}}>
        <Badge tone={statusTone2(hovered.status)} dot>{hovered.status}</Badge>{' '}
        {hovered.statusCode ? `HTTP ${hovered.statusCode}` : hovered.error || ''}
        {hovered.responseMs!=null ? ` · ${hovered.responseMs}ms` : ''} · {fmtDateTime(hovered.checkedAt)}
      </div>}
    </div>
  );
}

function MonitorTargetDetailModal({targetId, state, idx, go, onClose}){
  const target = state.monitorTargets.find(t => t.id === targetId);
  const comp = target ? idx.comp[target.componentId] : null;
  const [preset, setPreset] = useState('1d');
  const [start, setStart] = useState(()=>toLocalInput(new Date(Date.now()-86400000).toISOString()));
  const [end, setEnd] = useState(()=>toLocalInput(new Date().toISOString()));
  const [checks, setChecks] = useState(null);

  const applyPreset = (k) => {
    setPreset(k);
    const p = RANGE_PRESETS.find(r=>r.k===k); if (!p) return;
    const now = Date.now();
    setEnd(toLocalInput(new Date(now).toISOString())); setStart(toLocalInput(new Date(now-p.ms).toISOString()));
  };
  useEffect(()=>{
    if (!targetId) return;
    setChecks(null);
    const qs = new URLSearchParams({start: fromLocalInput(start), end: fromLocalInput(end)});
    API.get(`/api/monitor-targets/${targetId}/checks?${qs}`).then(r=>setChecks(r.checks)).catch(()=>setChecks([]));
  }, [targetId, start, end]);
  const outages = useMemo(()=> checks ? deriveOutages(checks) : [], [checks]);
  if (!target) return null;

  return (
    <Modal wide title={target.label} onClose={onClose}
      footer={<button className="btn" onClick={onClose}>Close</button>}>
      <div className="row wrap" style={{gap:10,marginBottom:10}}>
        {comp && <span className="clink" style={{cursor:'pointer'}}
          onClick={()=>{ onClose(); go({view:'component', id:comp.id}); }}>{comp.name}</span>}
        {target.status && <Badge tone={statusTone2(target.status)} dot>{target.status}</Badge>}
        <span className="xsmall dim mono">{target.url}</span>
        <div className="spacer"/>
        {target.uptimePct!=null && <span className="xsmall dim">{target.uptimePct}% up (30d)</span>}
      </div>
      <div className="row wrap" style={{gap:8,marginBottom:12}}>
        {RANGE_PRESETS.map(p => <button key={p.k} type="button" className={'chip-toggle'+(preset===p.k?' on':'')}
          onClick={()=>applyPreset(p.k)}>{p.label}</button>)}
        <div className="spacer"/>
        <Field label="Start"><input type="datetime-local" value={start}
          onChange={e=>{ setPreset(''); setStart(e.target.value); }}/></Field>
        <Field label="End"><input type="datetime-local" value={end}
          onChange={e=>{ setPreset(''); setEnd(e.target.value); }}/></Field>
      </div>
      <div className="card">
        <div className="card-head"><h3>Uptime</h3>
          <span className="xsmall dim">{checks ? `${checks.length} checks` : 'Loading…'}</span></div>
        <div className="card-body">
          {checks && !checks.length ? <EmptyState icon={<I.chart size={26}/>} title="No checks in this range"/>
            : <UptimeLineChart checks={checks||[]}/>}
        </div>
      </div>
      <div className="card" style={{marginTop:14}}>
        <div className="card-head"><h3>Outages</h3><Badge tone="slate">{outages.length}</Badge></div>
        {outages.length === 0
          ? <div className="card-body"><span className="xsmall dim">No Down periods in this range.</span></div>
          : <div className="tbl-wrap"><table>
              <thead><tr><th>Start</th><th>End</th><th>Duration</th></tr></thead>
              <tbody>{outages.map((o,i) => <tr key={i}>
                <td className="small">{fmtDateTime(o.start)}</td>
                <td className="small">{o.end ? fmtDateTime(o.end) : <span className="dim">Ongoing</span>}</td>
                <td className="small mono">{o.minutes!=null ? `${o.minutes} min` : '—'}</td>
              </tr>)}</tbody>
            </table></div>}
      </div>
    </Modal>
  );
}
