/* Pipeline screen */

function SchermPipeline({ gemeenten, kaartmateriaal, defaultGemeente }) {
  const [providers, setProviders] = useState({});
  const [taken, setTaken] = useState([]);
  const [form, setForm] = useState(() => ({ gemeente_code: defaultGemeente || '', ai_provider: 'anthropic', ai_model: 'claude-sonnet-4-6', detectie_types: ['contour'] }));
  const [pipelineModus, setPipelineModus] = useState('perceel_scan');
  const [perceelScan, setPerceelScan] = useState({ min_hoogte_m: '1.5', min_oppervlakte_m2: '5', pand_buffer_m: '0.8', gebruik_ahn: true });
  const [gebiedScan, setGebiedScan] = useState({ tegel_grootte_m: '160', overlap_m: '20', foto_px: '1024', max_tegels: '', min_cv_score: '0.45', ai_confirmatie: true });
  const [bezig, setBezig] = useState(false);
  const [fout, setFout] = useState(null);
  const [maxPanden, setMaxPanden] = useState('');
  const [alleenInGebruik, setAlleenInGebruik] = useState(true);
  const [luchtfotoBronId, setLuchtfotoBronId] = useState(null);
  const [promptKeuze, setPromptKeuze] = useState({});
  const [beschikbarePrompts, setBeschikbarePrompts] = useState({});
  const [pandIds, setPandIds] = useState('');
  const [perceelIds, setPerceelIds] = useState('');
  const [perceelControle, setPerceelControle] = useState(null);
  const [perceelControleBezig, setPerceelControleBezig] = useState(false);
  const [perceelBackfill, setPerceelBackfill] = useState(null);
  const [perceelBackfillBezig, setPerceelBackfillBezig] = useState(false);
  const [traceAan, setTraceAan] = useState(false);
  const [traceMaxPanden, setTraceMaxPanden] = useState('0');
  const [tracePandIds, setTracePandIds] = useState('');
  const [lopendeTaakId, setLopendeTaakId] = useState(null);
  const [lopendeTaakStatus, setLopendeTaakStatus] = useState(null);
  const [voortgang, setVoortgang] = useState({verwerkt: 0, totaal: 0});
  const [logRegels, setLogRegels] = useState([]);
  const [logSeq, setLogSeq] = useState(0);
  const [ollamaModellen, setOllamaModellen] = useState([]);
  const [ollamaLaden, setOllamaLaden] = useState(false);
  const [ollamaFout, setOllamaFout] = useState(null);
  const [providerModellen, setProviderModellen] = useState(window.MODELLEN || {});
  const logRef = React.useRef(null);
  const esRef = React.useRef(null);

  const laadTaken = useCallback(async () => {
    const data = await fetch(`${API}/pipeline`).then(r => r.json()).catch(() => []);
    setTaken(data);
  }, []);

  useEffect(() => {
    if (!lopendeTaakId || lopendeTaakStatus !== 'bezig') return;
    const timer = setInterval(async () => {
      try {
        const r = await fetch(`${API}/pipeline/${lopendeTaakId}/status`);
        if (!r.ok) return;
        const data = await r.json();
        setVoortgang(data.voortgang || {verwerkt: 0, totaal: 0});
        setLopendeTaakStatus(data.status);
        if (data.status !== 'bezig') laadTaken();
      } catch (e) {}
    }, 2000);
    return () => clearInterval(timer);
  }, [lopendeTaakId, lopendeTaakStatus, laadTaken]);

  // SSE log stream
  useEffect(() => {
    const es = new EventSource(`${API}/logs/stream?since=0`);
    esRef.current = es;
    es.onmessage = (e) => {
      try {
        const rec = JSON.parse(e.data);
        setLogRegels(prev => {
          const nieuw = [...prev, rec];
          return nieuw.length > 500 ? nieuw.slice(-500) : nieuw;
        });
        setLogSeq(rec.seq);
      } catch (_) {}
    };
    return () => es.close();
  }, []);

  useEffect(() => {
    if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight;
  }, [logRegels]);

  function exportLogs() {
    if (logRegels.length === 0) return;
    const inhoud = logRegels.map(r => {
      const tijd = r.timestamp ? r.timestamp.replace('T', ' ').slice(0, 19) : '';
      const level = r.level || 'INFO';
      return `${tijd} [${level}] ${r.message || ''}`.trim();
    }).join('\n');
    const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
    const blob = new Blob([inhoud + '\n'], {type: 'text/plain;charset=utf-8'});
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = `pipeline-logs-${stamp}.txt`;
    document.body.appendChild(link);
    link.click();
    link.remove();
    URL.revokeObjectURL(url);
  }

  useEffect(() => {
    fetch(`${API}/config/providers`).then(r => r.json()).then(setProviders).catch(() => {});
    laadTaken();
    fetch(`${API}/config/standaard`).then(r => r.ok ? r.json() : null).then(d => {
      if (d && d.provider) {
        setForm(p => ({ ...p, ai_provider: d.provider, ai_model: d.model || p.ai_model }));
      }
    }).catch(() => {});
    fetch(`${API}/config/providers/models`).then(r => r.ok ? r.json() : null).then(d => {
      if (d) { setProviderModellen(d); window.MODELLEN = { ...window.MODELLEN, ...d }; }
    }).catch(() => {});
  }, []);

  useEffect(() => {
    const bron = kaartmateriaal.find(k => k.voor_pipeline);
    if (bron && !luchtfotoBronId) setLuchtfotoBronId(bron.id);
  }, [kaartmateriaal]);

  useEffect(() => {
    form.detectie_types.forEach(type => {
      fetch(`${API}/prompts?detectie_type=${type}`).then(r => r.json())
        .then(data => setBeschikbarePrompts(prev => ({...prev, [type]: data}))).catch(() => {});
    });
  }, [form.detectie_types]);

  const laadOllamaModellen = useCallback(async () => {
    setOllamaLaden(true);
    setOllamaFout(null);
    try {
      const data = await fetch(`${API}/config/providers/ollama/models`)
      .then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)))
      const modellen = data.models || [];
      setOllamaModellen(modellen);
      if (modellen.length > 0) {
        const voorkeur = modellen.includes('qwen3-vl:latest') ? 'qwen3-vl:latest' : modellen[0];
        setForm(p => p.ai_provider === 'ollama' && !modellen.includes(p.ai_model)
          ? {...p, ai_model: voorkeur}
          : p);
      }
    } catch(e) {
      setOllamaModellen([]);
      setOllamaFout(e.message || 'Ollama-modellen ophalen mislukt');
    } finally {
      setOllamaLaden(false);
    }
  }, []);

  useEffect(() => {
    if (form.ai_provider === 'ollama') laadOllamaModellen();
  }, [form.ai_provider, laadOllamaModellen]);

  const modelOpties = form.ai_provider === 'ollama' && ollamaModellen.length > 0
    ? ollamaModellen
    : (providerModellen[form.ai_provider] || []);

  async function stopTaak(taak) {
    if (!confirm(`Taak #${taak.id} stoppen? Lopend pand wordt afgemaakt, daarna stopt de pipeline.`)) return;
    const r = await fetch(`${API}/pipeline/${taak.id}/stop`, { method: 'POST' });
    if (r.ok) { await laadTaken(); setLopendeTaakStatus('gestopt'); }
    else { const e = await r.json(); alert(e.detail || 'Stoppen mislukt'); }
  }

  async function hervatTaak(taak) {
    const r = await fetch(`${API}/pipeline/${taak.id}/hervat`, { method: 'POST' });
    if (r.ok) {
      const data = await r.json();
      setLopendeTaakId(data.taak_id);
      setLopendeTaakStatus('bezig');
      setVoortgang(taak.voortgang || { verwerkt: 0, totaal: 0 });
      setPipelineModus(taak.modus || 'pand_scan');
      await laadTaken();
    } else {
      const e = await r.json().catch(() => ({}));
      alert(e.detail || 'Hervatten mislukt');
    }
  }

  async function verwijderTaak(taak) {
    const bezig = taak.status === 'bezig';
    const msg = bezig
      ? `Taak #${taak.id} is nog bezig! Forceer verwijderen inclusief alle resultaten en afbeeldingen?`
      : `Taak #${taak.id} (${taak.gemeente_code}) verwijderen inclusief alle resultaten en afbeeldingen?`;
    if (!confirm(msg)) return;
    const url = bezig ? `${API}/pipeline/${taak.id}?force=true` : `${API}/pipeline/${taak.id}`;
    const r = await fetch(url, { method: 'DELETE' });
    if (r.ok) laadTaken();
    else { const e = await r.json(); alert(e.detail || 'Verwijderen mislukt'); }
  }
  function toggleDetectieType(type) {
    if (pipelineModus === 'gebied_scan' && type !== 'zonnepanelen') return;
    setForm(prev => ({ ...prev,
      detectie_types: prev.detectie_types.includes(type) ? prev.detectie_types.filter(t => t !== type) : [...prev.detectie_types, type]
    }));
  }

  function wijzigPipelineModus(modus) {
    setPipelineModus(modus);
    if (modus === 'gebied_scan') {
      setForm(prev => ({...prev, detectie_types: ['zonnepanelen']}));
      setPerceelIds('');
      setPerceelControle(null);
    }
  }

  function parseIdLijst(waarde) {
    return waarde.split(/[\n,;]+/).map(s => s.trim()).filter(Boolean);
  }

  function perceelBackfillScopeTekst(status) {
    const scope = status?.scope || {};
    if (scope.type === 'pand_ids') return `${fmtNum(scope.pand_ids_count || 0)} pand-ID's`;
    if (scope.type === 'max_panden') return `max. ${fmtNum(scope.max_panden || 0)} panden`;
    if (scope.type === 'hele_gemeente') return 'hele gemeente';
    return status?.gemeente_code ? 'laatste run' : 'onbekend';
  }

  async function controleerPerceelIds() {
    const perceelIdLijst = parseIdLijst(perceelIds);
    if (perceelIdLijst.length === 0) {
      setPerceelControle(null);
      setFout('Vul eerst perceel-ID\'s in');
      return;
    }
    setFout(null);
    setPerceelControleBezig(true);
    try {
      const r = await fetch(`${API}/percelen/controle`, {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({perceel_ids: perceelIdLijst}),
      });
      if (!r.ok) { const e = await r.json(); throw new Error(e.detail || 'Perceel-ID controle mislukt'); }
      setPerceelControle(await r.json());
    } catch(e) {
      setFout(e.message);
    } finally {
      setPerceelControleBezig(false);
    }
  }

  async function laadPerceelBackfillStatus() {
    try {
      const data = await fetch(`${API}/percelen/backfill/status`).then(r => r.json());
      setPerceelBackfill(data);
    } catch(e) {}
  }

  async function startPerceelBackfill() {
    if (!form.gemeente_code) { setFout('Selecteer eerst een gemeente'); return; }
    setFout(null);
    setPerceelBackfillBezig(true);
    try {
      const params = new URLSearchParams({ gemeente_code: form.gemeente_code });
      const ids = parseIdLijst(pandIds);
      if (ids.length > 0) params.append('pand_ids', JSON.stringify(ids));
      else if (maxPanden && parseInt(maxPanden, 10) > 0) params.append('max_panden', parseInt(maxPanden, 10));
      params.append('alleen_in_gebruik', alleenInGebruik ? 'true' : 'false');
      const r = await fetch(`${API}/percelen/backfill?${params}`, { method: 'POST' });
      if (!r.ok) { const e = await r.json(); throw new Error(e.detail || 'Perceelnummers vullen mislukt'); }
      await laadPerceelBackfillStatus();
    } catch(e) {
      setFout(e.message);
    } finally {
      setPerceelBackfillBezig(false);
    }
  }

  async function startPipeline() {
    if (!form.gemeente_code) { setFout('Selecteer een gemeente'); return; }
    if (form.detectie_types.length === 0) { setFout('Selecteer minimaal één detectietype'); return; }
    setFout(null); setBezig(true);
    // Alles in een JSON-body: bij honderden perceel-/pand-ID's wordt een query-string
    // te lang (URL-limiet proxy/uvicorn) → "Failed to fetch".
    const payload = {
      gemeente_code: form.gemeente_code,
      ai_provider: form.ai_provider,
      ai_model: form.ai_model,
      modus: pipelineModus,
      detectie_types: form.detectie_types,
      alleen_in_gebruik: alleenInGebruik,
      trace: traceAan,
    };
    const ids = parseIdLijst(pandIds);
    const perceelIdLijst = parseIdLijst(perceelIds);
    if (ids.length > 0 && perceelIdLijst.length === 0) {
      payload.pand_ids = ids;
    } else if (perceelIdLijst.length === 0 && maxPanden && parseInt(maxPanden, 10) > 0) {
      payload.max_panden = parseInt(maxPanden, 10);
    }
    if (luchtfotoBronId) payload.kaartmateriaal_id = luchtfotoBronId;
    if (traceAan && parseInt(traceMaxPanden, 10) > 0) {
      payload.trace_max_panden = parseInt(traceMaxPanden, 10);
    }
    const traceIds = tracePandIds.split(/[\n,;]+/).map(s => s.trim()).filter(Boolean);
    if (traceAan && traceIds.length > 0) {
      payload.trace_pand_ids = traceIds;
    }
    const promptIds = {};
    Object.entries(promptKeuze).forEach(([type, id]) => { if (id && form.detectie_types.includes(type)) promptIds[type] = id; });
    if (Object.keys(promptIds).length > 0) payload.prompt_ids = promptIds;
    if (pipelineModus === 'perceel_scan') {
      payload.selectie = perceelIdLijst.length > 0
        ? { type: 'perceel_ids', perceel_ids: perceelIdLijst }
        : { type: ids.length > 0 ? 'pand_ids' : 'gemeente', pand_ids: ids };
      payload.perceel_scan = {
        min_hoogte_m: parseFloat(perceelScan.min_hoogte_m) || 2.0,
        min_oppervlakte_m2: parseFloat(perceelScan.min_oppervlakte_m2) || 6,
        pand_buffer_m: parseFloat(perceelScan.pand_buffer_m) || 0.8,
        gebruik_ahn: perceelScan.gebruik_ahn !== false,
      };
    }
    if (pipelineModus === 'gebied_scan') {
      payload.selectie = ids.length > 0 ? { type: 'pand_ids', pand_ids: ids } : { type: 'gemeente' };
      const gebiedPayload = {
        tegel_grootte_m: parseFloat(gebiedScan.tegel_grootte_m) || 160,
        overlap_m: parseFloat(gebiedScan.overlap_m) || 20,
        foto_px: parseInt(gebiedScan.foto_px, 10) || 1024,
        min_cv_score: parseFloat(gebiedScan.min_cv_score) || 0.45,
        ai_confirmatie: !!gebiedScan.ai_confirmatie,
      };
      const maxTegels = parseInt(gebiedScan.max_tegels, 10);
      if (maxTegels > 0) gebiedPayload.max_tegels = maxTegels;
      payload.gebied_scan = gebiedPayload;
    }
    try {
      const r = await fetch(`${API}/pipeline`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload),
      });
      if (!r.ok) { const e = await r.json(); throw new Error(e.detail || 'Pipeline starten mislukt'); }
      const data = await r.json();
      setLopendeTaakId(data.taak_id); setLopendeTaakStatus('bezig'); setVoortgang({verwerkt: 0, totaal: 0});
      await laadTaken();
    } catch(e) { setFout(e.message); }
    setBezig(false);
  }

  const pct = voortgang.totaal ? Math.round(100 * voortgang.verwerkt / voortgang.totaal) : 0;

  return (
    <div className="bd-page">
      <div className="bd-page-head">
        <div><h1>Pipeline</h1><p>Start en monitor AI-detectietaken.</p></div>
      </div>

      <div className="bd-grid-sidebar">
        <div className="bd-card">
          <div className="bd-card-head"><h2><Icon name="play-circle" /> Nieuwe detectie starten</h2></div>
          {fout && <Alert kind="error">{fout}</Alert>}
          {lopendeTaakId && lopendeTaakStatus === 'bezig' && (
            <div style={{background:'var(--color-primary-light)', border:'1px solid #cfe0f4', borderRadius:'var(--radius-md)', padding:'var(--space-3) var(--space-4)', marginBottom:'var(--space-4)'}}>
              <div style={{fontSize:'var(--font-size-sm)', color:'var(--color-primary-dark)', marginBottom:8, fontWeight:600, display:'flex', alignItems:'center', gap:6}}>
                <Icon name="loader" size={14} /> Pipeline bezig — taak #{lopendeTaakId} · {voortgang.verwerkt} / {voortgang.totaal || '?'} {pipelineModus === 'gebied_scan' ? 'tegels' : 'panden'}{voortgang.totaal > 0 && ` (${pct}%)`}
                {pipelineModus === 'gebied_scan' && typeof voortgang.kandidaten === 'number' && ` · ${voortgang.kandidaten} kandidaten · ${voortgang.objecten || 0} objecten`}
              </div>
              <div className="bd-progress"><div className="bd-progress-bar" style={{width: (pct||3)+'%'}} /></div>
            </div>
          )}
          {lopendeTaakId && lopendeTaakStatus === 'klaar' && <Alert kind="success">Taak #{lopendeTaakId} klaar</Alert>}
          {lopendeTaakId && lopendeTaakStatus === 'fout'  && <Alert kind="error">Taak #{lopendeTaakId} mislukt</Alert>}

          <Field label="Scanmodus">
            <div className="bd-row">
              {[
                ['perceel_scan', 'Perceel scan', 'map'],
                ['pand_scan', 'Pand scan', 'building-2'],
                ['gebied_scan', 'Gebied scan', 'scan-search'],
              ].map(([id, label, icon]) => {
                const actief = pipelineModus === id;
                return (
                  <button key={id} type="button" onClick={() => wijzigPipelineModus(id)}
                    className={'bd-pill-tab' + (actief ? ' is-active' : '')}
                    style={{border:'1px solid var(--color-border-light)', background: actief ? 'var(--color-primary)' : 'var(--surface-card)'}}>
                    <Icon name={icon} size={14} /> {label}
                  </button>
                );
              })}
            </div>
          </Field>

          <Field label="Gemeente">
            <select className="bd-select" value={form.gemeente_code} onChange={e => setForm(p => ({...p, gemeente_code: e.target.value}))}>
              <option value="">— Selecteer gemeente —</option>
              {gemeenten.filter(g=>g.status==='klaar').map(g => <option key={g.code} value={g.code}>{g.code} — {g.naam} ({fmtNum(g.aantal_panden||0)} panden)</option>)}
            </select>
          </Field>
          <div className="bd-grid-2">
            <Field label="AI Provider">
              <select className="bd-select" value={form.ai_provider}
                onChange={e => {
                  const provider = e.target.value;
                  const opties = provider === 'ollama' && ollamaModellen.length > 0 ? ollamaModellen : (providerModellen[provider] || []);
                  setForm(p => ({...p, ai_provider: provider, ai_model: opties[0] || ''}));
                }}>
                {Object.entries(providers).map(([k,v]) => <option key={k} value={k}>{k}{v ? '' : ' (niet geconfigureerd)'}</option>)}
              </select>
            </Field>
            <Field label="Model" hint={form.ai_provider === 'ollama'
              ? (ollamaFout ? `Ollama-modellen ophalen mislukt: ${ollamaFout}` : 'Kies een lokaal Ollama-model of typ exact een modelnaam/tag.')
              : form.ai_provider === 'bedrock' ? 'Kies of typ Bedrock modelId (bv. qwen.qwen3-vl-235b-a22b)' : null}>
              {form.ai_provider === 'bedrock' ? (
                <>
                <input className="bd-input" value={form.ai_model}
                  list="bedrock-models" placeholder="qwen.qwen3-vl-235b-a22b"
                  onChange={e => setForm(p => ({...p, ai_model: e.target.value}))} />
                <datalist id="bedrock-models">
                  {(providerModellen.bedrock || []).map(m => <option key={m} value={m} />)}
                </datalist>
                </>
              ) : form.ai_provider === 'ollama' ? (
                <div style={{display:'flex', gap:8}}>
                  {ollamaModellen.length > 0 ? (
                    <select className="bd-select" style={{flex:1}} value={form.ai_model}
                      onChange={e => setForm(p => ({...p, ai_model: e.target.value}))}>
                      {ollamaModellen.map(m => <option key={m} value={m}>{m}</option>)}
                    </select>
                  ) : (
                    <input
                      className="bd-input"
                      style={{flex:1}}
                      value={form.ai_model}
                      placeholder={ollamaLaden ? 'Modellen laden...' : 'bijv. qwen3-vl:latest'}
                      onChange={e => setForm(p => ({...p, ai_model: e.target.value}))}
                    />
                  )}
                  <button type="button" className="tc-icon-btn" title="Ollama-modellen vernieuwen" onClick={laadOllamaModellen} disabled={ollamaLaden}>
                    <Icon name={ollamaLaden ? 'loader' : 'refresh-cw'} size={16} style={ollamaLaden ? {animation:'spin 1s linear infinite'} : null} />
                  </button>
                </div>
              ) : (
                <>
                  <input className="bd-input" value={form.ai_model} list="ai-model-opties"
                    placeholder="kies of typ een modelnaam"
                    onChange={e => setForm(p => ({...p, ai_model: e.target.value}))} />
                  <datalist id="ai-model-opties">
                    {modelOpties.map(m => <option key={m} value={m} />)}
                  </datalist>
                </>
              )}
            </Field>
          </div>

          <Field label="Detectietypes">
            <div className="bd-row">
              {['contour','zonnepanelen','dakkapel'].map(t => {
                const actief = form.detectie_types.includes(t);
                const disabled = pipelineModus === 'gebied_scan' && t !== 'zonnepanelen';
                return (
                  <button key={t} onClick={() => toggleDetectieType(t)}
                    className={'bd-pill-tab' + (actief ? ' is-active' : '')}
                    disabled={disabled}
                    style={{border:'1px solid var(--color-border-light)', background: actief ? 'var(--color-primary)' : 'var(--surface-card)', opacity: disabled ? 0.45 : 1}}>
                    <Icon name={DETECTIE_ICON[t]} size={14} /> {DETECTIE_LABEL[t]}
                  </button>
                );
              })}
            </div>
          </Field>

          {pipelineModus === 'gebied_scan' && (
            <div style={{border:'1px solid var(--color-border-light)', borderRadius:'var(--radius-md)', padding:'var(--space-4)', marginBottom:'var(--space-4)', background:'var(--surface-subtle)'}}>
              <div className="bd-section-title" style={{marginTop:0}}>Gebied scan instellingen</div>
              <div className="bd-grid-3">
                <Field label="Tegelgrootte (m)">
                  <input className="bd-input" type="number" min="20" step="10"
                    value={gebiedScan.tegel_grootte_m}
                    onChange={e => setGebiedScan(p => ({...p, tegel_grootte_m: e.target.value}))} />
                </Field>
                <Field label="Overlap (m)">
                  <input className="bd-input" type="number" min="0" step="5"
                    value={gebiedScan.overlap_m}
                    onChange={e => setGebiedScan(p => ({...p, overlap_m: e.target.value}))} />
                </Field>
                <Field label="Foto pixels">
                  <input className="bd-input" type="number" min="128" max="4096" step="128"
                    value={gebiedScan.foto_px}
                    onChange={e => setGebiedScan(p => ({...p, foto_px: e.target.value}))} />
                </Field>
                <Field label="Min. CV-score">
                  <input className="bd-input" type="number" min="0" max="1" step="0.05"
                    value={gebiedScan.min_cv_score}
                    onChange={e => setGebiedScan(p => ({...p, min_cv_score: e.target.value}))} />
                </Field>
                <Field label="Max. tegels">
                  <input className="bd-input" type="number" min="1" step="1" placeholder="leeg = alles"
                    value={gebiedScan.max_tegels}
                    onChange={e => setGebiedScan(p => ({...p, max_tegels: e.target.value}))} />
                </Field>
                <Field label="AI bevestiging">
                  <label className="bd-checkbox" style={{height:38}}>
                    <input type="checkbox" checked={gebiedScan.ai_confirmatie}
                      onChange={e => setGebiedScan(p => ({...p, ai_confirmatie: e.target.checked}))} />
                    <span>aan</span>
                  </label>
                </Field>
              </div>
            </div>
          )}

          {pipelineModus === 'perceel_scan' && (
            <div style={{border:'1px solid var(--color-border-light)', borderRadius:'var(--radius-md)', padding:'var(--space-4)', marginBottom:'var(--space-4)', background:'var(--surface-subtle)'}}>
              <div className="bd-section-title" style={{marginTop:0}}>Perceel scan instellingen</div>
              <div className="bd-grid-3">
                <Field label="Min. hoogte (m)" hint="DSM-drempel boven maaiveld. Lager = ook lage bergingen, meer ruis. Advies 1,5.">
                  <input className="bd-input" type="number" min="0" step="0.1"
                    value={perceelScan.min_hoogte_m}
                    onChange={e => setPerceelScan(p => ({...p, min_hoogte_m: e.target.value}))} />
                </Field>
                <Field label="Min. oppervlak vrijstaand (m2)" hint="Minimale grootte van een vrijstaand bouwwerk om als kandidaat-pand te tellen. Advies 5.">
                  <input className="bd-input" type="number" min="0" step="0.5"
                    value={perceelScan.min_oppervlakte_m2}
                    onChange={e => setPerceelScan(p => ({...p, min_oppervlakte_m2: e.target.value}))} />
                </Field>
                <Field label="Pand-buffer (m)" hint="Binnen deze afstand telt iets als 'tegen het huis' (aanbouw); daarbuiten als vrijstaand. Advies 0,8.">
                  <input className="bd-input" type="number" min="0" step="0.1"
                  value={perceelScan.pand_buffer_m}
                    onChange={e => setPerceelScan(p => ({...p, pand_buffer_m: e.target.value}))} />
                </Field>
              </div>
              <div className="bd-field" style={{marginTop:12}}>
                <label className="bd-checkbox">
                  <input type="checkbox" checked={perceelScan.gebruik_ahn}
                    onChange={e => setPerceelScan(p => ({...p, gebruik_ahn: e.target.checked}))} />
                  <span>AHN-hoogtekaart meenemen in het beeld</span>
                </label>
                {!perceelScan.gebruik_ahn && <div style={{marginTop:4, fontSize:'var(--font-size-xs)', color:'var(--text-muted)'}}>Uit: de AI ziet alleen de luchtfoto + contouren (geen hoogte-kleur). Geometrie gebruikt de AHN nog steeds.</div>}
              </div>
              <div style={{display:'flex', gap:8, alignItems:'center', flexWrap:'wrap', marginTop:12}}>
                <Btn kind="secondary" size="sm" icon={perceelBackfillBezig ? 'loader' : 'database-zap'}
                  onClick={startPerceelBackfill}
                  disabled={perceelBackfillBezig || perceelBackfill?.actief || !form.gemeente_code}>
                  {perceelBackfillBezig || perceelBackfill?.actief ? 'Vullen...' : 'Perceelnummers vullen'}
                </Btn>
                <Btn kind="secondary" size="sm" icon="refresh-cw" onClick={laadPerceelBackfillStatus}>
                  Status
                </Btn>
                {perceelBackfill?.resultaat && (
                  <span style={{fontSize:'var(--font-size-xs)', color:'var(--text-muted)'}}>
                    Scope: {perceelBackfillScopeTekst(perceelBackfill)} · {fmtNum(perceelBackfill.resultaat.panden || 0)} verwerkt · {fmtNum(perceelBackfill.resultaat.gekoppeld)} gekoppeld · {fmtNum(perceelBackfill.resultaat.missers)} missers
                  </span>
                )}
                {perceelBackfill?.fout && <span style={{fontSize:'var(--font-size-xs)', color:'var(--color-danger)'}}>{perceelBackfill.fout}</span>}
              </div>
            </div>
          )}

          {pipelineModus === 'perceel_scan' && (
            <Field label="Specifieke perceel-ID's" hint="Vul perceel-ID's in zoals ze in de perceelcache staan. Deze selectie heeft prioriteit boven pand-ID's.">
              <textarea className="bd-input" rows={3}
                placeholder={"100102\nTEST00A00001\n..."}
                value={perceelIds} onChange={e => { setPerceelIds(e.target.value); setPerceelControle(null); }}
                style={{fontFamily:'var(--font-mono)', fontSize:'var(--font-size-xs)', resize:'vertical'}} />
              {perceelIds.trim() && (
                <div style={{marginTop:4, fontSize:'var(--font-size-xs)', color:'var(--text-muted)'}}>
                  {parseIdLijst(perceelIds).length} perceel-ID('s) opgegeven; pand-ID's en max. panden worden genegeerd.
                </div>
              )}
              <div style={{marginTop:8}}>
                <Btn kind="secondary" size="sm" icon="search-check" onClick={controleerPerceelIds} disabled={perceelControleBezig || !perceelIds.trim()}>
                  {perceelControleBezig ? 'Controleren...' : "Perceel-ID's controleren"}
                </Btn>
              </div>
              {perceelControle && (
                <div style={{marginTop:8, fontSize:'var(--font-size-xs)', color: perceelControle.aantal_missend ? 'var(--color-danger)' : 'var(--color-success)'}}>
                  {perceelControle.aantal_gevonden} gevonden, {perceelControle.aantal_missend} niet gevonden
                  {perceelControle.missend?.length > 0 && (
                    <span className="bd-mono">: {perceelControle.missend.join(', ')}</span>
                  )}
                </div>
              )}
            </Field>
          )}

          {kaartmateriaal.filter(k => k.voor_pipeline).length > 0 && (
            <Field label="Luchtfotobron"
                   hint={luchtfotoBronId ? (kaartmateriaal.find(k => k.id === luchtfotoBronId) || {}).beschrijving : null}>
              <select className="bd-select" value={luchtfotoBronId ?? ''}
                onChange={e => setLuchtfotoBronId(e.target.value ? parseInt(e.target.value, 10) : null)}>
                <option value="">— Standaard (config) —</option>
                {kaartmateriaal.filter(k => k.voor_pipeline).map(k => (
                  <option key={k.id} value={k.id}>{k.naam}{k.jaar ? ` (${k.jaar})` : ''} [{k.type.toUpperCase()}]</option>
                ))}
              </select>
            </Field>
          )}

          <Field label="Specifieke pand-ID's" hint="Vul hier BAG-identificatienummers in (één per regel of komma-gescheiden). Overschrijft 'Max. panden'.">
            <textarea className="bd-input" rows={4}
              placeholder={"0344100000083246\n0344100000083247\n..."}
              value={pandIds} onChange={e => setPandIds(e.target.value)}
              style={{fontFamily:'var(--font-mono)', fontSize:'var(--font-size-xs)', resize:'vertical'}} />
            {pandIds.trim() && (
              <div style={{marginTop:4, fontSize:'var(--font-size-xs)', color:'var(--text-muted)'}}>
                {pandIds.split(/[\n,;]+/).map(s=>s.trim()).filter(Boolean).length} ID('s) opgegeven — max. panden wordt genegeerd.
              </div>
            )}
          </Field>
          <Field label="Max. panden" hint="Leeg = volledige gemeente (wordt genegeerd als ID-lijst is ingevuld)">
            <input type="number" className="bd-input" placeholder="bijv. 100 voor een testrun" min="1"
                   value={maxPanden} onChange={e => setMaxPanden(e.target.value)}
                   disabled={pandIds.trim().length > 0 || perceelIds.trim().length > 0}
                   style={{maxWidth:240, opacity: (pandIds.trim() || perceelIds.trim()) ? 0.4 : 1}} />
          </Field>
          <div className="bd-section-title" style={{marginTop:'var(--space-4)'}}>Trace mode</div>
          <label className="bd-checkbox">
            <input type="checkbox" checked={traceAan} onChange={e => setTraceAan(e.target.checked)} />
            <span>Tussenstappen en debug-artefacten bewaren</span>
          </label>
          {traceAan && (
            <div className="bd-grid-2">
              <Field label="Max trace-panden" hint="0 = alleen pand-ID lijst gebruiken">
                <input className="bd-input" type="number" min="0" value={traceMaxPanden} onChange={e => setTraceMaxPanden(e.target.value)} />
              </Field>
              <Field label="Trace pand-ID's" hint="Komma, puntkomma of nieuwe regel">
                <textarea className="bd-input" rows="3" value={tracePandIds} onChange={e => setTracePandIds(e.target.value)} />
              </Field>
            </div>
          )}
          <div className="bd-field">
            <label className="bd-checkbox">
              <input type="checkbox" checked={alleenInGebruik} onChange={e => setAlleenInGebruik(e.target.checked)} />
              <span>Alleen panden met status <strong>in gebruik</strong></span>
            </label>
            {!alleenInGebruik && <div style={{marginTop:4, fontSize:'var(--font-size-xs)', color:'var(--text-muted)'}}>Alle panden worden meegenomen, inclusief sloop, bouw gestart, enz.</div>}
          </div>
          <Btn kind="primary" icon="play" onClick={startPipeline} disabled={bezig} style={{width:'100%', justifyContent:'center'}}>
            {bezig ? 'Starten…' : 'Pipeline starten'}
          </Btn>
        </div>

        <div className="bd-card">
          <div className="bd-card-head"><h2><Icon name="key-round" /> AI Providers</h2></div>
          {Object.entries(providers).map(([k,v]) => (
            <div key={k} style={{display:'flex', justifyContent:'space-between', alignItems:'center', padding:'10px 0', borderBottom:'1px solid var(--color-border-light)'}}>
              <span style={{fontSize:'var(--font-size-sm)', fontWeight:500, textTransform:'capitalize'}}>{k}</span>
              {v ? <Badge kind="success">Geconfigureerd</Badge> : <Badge kind="neutral">Niet ingesteld</Badge>}
            </div>
          ))}
          <div style={{marginTop:10, fontSize:'var(--font-size-xs)', color:'var(--text-muted)'}}>Instellen via <code>/api/config/providers</code></div>
        </div>
      </div>

      <div className="bd-card bd-card--flush">
        <div style={{padding:'var(--space-5)', display:'flex', alignItems:'center', justifyContent:'space-between'}}>
          <h2 style={{margin:0, fontSize:'var(--font-size-md)', fontWeight:600, display:'flex', alignItems:'center', gap:8}}>
            <Icon name="terminal" /> Live logging
          </h2>
          <div style={{display:'flex', gap:8}}>
            <Btn kind="ghost" size="sm" icon="download" onClick={exportLogs} disabled={logRegels.length === 0}>Export</Btn>
            <Btn kind="ghost" size="sm" icon="trash-2" onClick={async () => { await fetch(`${API}/logs/clear`, {method:'POST'}); setLogRegels([]); }}>Wissen</Btn>
          </div>
        </div>
        <div ref={logRef} style={{
          background:'#0f172a', color:'#94a3b8', fontFamily:'var(--font-mono)',
          fontSize:'12px', lineHeight:1.6, padding:'12px 16px',
          height:240, overflowY:'auto', borderTop:'1px solid var(--color-border-light)'
        }}>
          {logRegels.length === 0
            ? <span style={{color:'#475569'}}>Nog geen logberichten…</span>
            : logRegels.map(r => (
              <div key={r.seq} style={{color: r.level==='ERROR' ? '#f87171' : r.level==='WARNING' ? '#fb923c' : r.level==='DEBUG' ? '#64748b' : '#94a3b8'}}>
                <span style={{color:'#475569', userSelect:'none'}}>{r.timestamp ? r.timestamp.slice(11,19) : ''} </span>
                <span style={{color: r.level==='ERROR'?'#f87171':r.level==='WARNING'?'#fb923c':r.level==='DEBUG'?'#64748b':'#60a5fa', marginRight:6}}>[{r.level}]</span>
                {r.message}
              </div>
            ))
          }
        </div>
      </div>

      <div className="bd-card bd-card--flush">
        <div style={{padding:'var(--space-5)', display:'flex', alignItems:'center', justifyContent:'space-between', gap:'var(--space-3)'}}>
          <h2 style={{margin:0, fontSize:'var(--font-size-md)', fontWeight:600, display:'flex', alignItems:'center', gap:8}}>
            <Icon name="list-checks" /> Taken
          </h2>
          <Btn kind="secondary" icon="refresh-cw" onClick={laadTaken}>Vernieuwen</Btn>
        </div>
        {taken.length === 0 ? (
          <Empty icon="play-circle" title="Nog geen pipeline taken gestart" />
        ) : (
          <div className="bd-table-wrap">
            <table className="bd-table">
              <thead><tr><th>Taak</th><th>Gemeente</th><th>Modus</th><th>Provider</th><th>Model</th><th>Types</th><th>Status</th><th>Voortgang</th><th>Kandidaten</th><th>Objecten</th><th>Verdacht</th><th>Zon</th><th>Gestart</th><th></th></tr></thead>
              <tbody>
                {taken.map(t => {
                  const v = t.voortgang || {};
                  const p = v.totaal ? Math.round(v.verwerkt/v.totaal*100) : 0;
                  const objectStats = t.perceel_object_stats || {};
                  const objectModus = t.modus === 'perceel_scan' || t.modus === 'gebied_scan';
                  const modusLabel = t.modus === 'perceel_scan' ? 'Perceel' : t.modus === 'gebied_scan' ? 'Gebied' : 'Pand';
                  const modusKind = t.modus === 'gebied_scan' ? 'warning' : t.modus === 'perceel_scan' ? 'info' : 'neutral';
                  return (
                    <tr key={t.id}>
                      <td className="bd-mono" style={{fontWeight:600}}>#{t.id}</td>
                      <td className="bd-mono" style={{color:'var(--color-primary)', fontWeight:600}}>{t.gemeente_code}</td>
                      <td><Badge kind={modusKind}>{modusLabel}</Badge></td>
                      <td style={{textTransform:'capitalize'}}>{t.ai_provider}</td>
                      <td className="bd-mono">{t.ai_model}</td>
                      <td><span style={{fontSize:'var(--font-size-xs)'}}>{(t.detectie_types||[]).join(', ')}</span></td>
                      <td>
                        {t.status==='klaar'   && <Badge kind="success">Klaar</Badge>}
                        {t.status==='bezig'   && <Badge kind="info">Bezig</Badge>}
                        {t.status==='wachten' && <Badge kind="neutral">Wachten</Badge>}
                        {t.status==='gestopt' && <Badge kind="warning">Gestopt</Badge>}
                        {t.status==='onderbroken' && <Badge kind="warning">Onderbroken</Badge>}
                        {t.status==='fout'    && <Badge kind="error">Fout</Badge>}
                      </td>
                      <td style={{minWidth:140}}>
                        {v.totaal != null ? (
                          <>
                            <div className="bd-progress" style={{marginBottom:4}}><div className="bd-progress-bar" style={{width:p+'%'}} /></div>
                            <div className="bd-mono">{v.verwerkt ?? 0}/{v.totaal}</div>
                          </>
                        ) : '—'}
                      </td>
                      <td className="bd-mono">{t.modus === 'gebied_scan' && typeof v.kandidaten === 'number' ? fmtNum(v.kandidaten) : '—'}</td>
                      <td className="bd-mono">{objectModus ? fmtNum(objectStats.objecten || 0) : '—'}</td>
                      <td className="bd-mono">{objectModus ? fmtNum(objectStats.contourafwijking || 0) : '—'}</td>
                      <td className="bd-mono">{objectModus ? fmtNum(objectStats.zonnepanelen || 0) : '—'}</td>
                      <td className="bd-mono">{fmtDateTime(t.gestart_op)}</td>
                      <td style={{textAlign:'right', display:'flex', gap:4, justifyContent:'flex-end'}}>
                        {t.status==='bezig' && (
                          <Btn kind="ghost" size="sm" icon="square" onClick={() => stopTaak(t)} aria-label="Stoppen" title="Pipeline stoppen na huidig pand" />
                        )}
                        {['onderbroken','gestopt','fout'].includes(t.status) && t.parameters && (
                          <Btn kind="ghost" size="sm" icon="play" onClick={() => hervatTaak(t)} aria-label="Hervatten" title="Run hervatten — gaat verder waar hij stopte" />
                        )}
                        <Btn kind="ghost" size="sm" icon="trash-2" onClick={() => verwijderTaak(t)} aria-label="Verwijderen" title={t.status==='bezig' ? 'Forceer verwijderen (taak is bezig)' : 'Verwijderen'} />
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        )}
      </div>
    </div>
  );
}

window.SchermPipeline = SchermPipeline;
