# NS.E4 — Document Artifact Block

**Casus:** Cortex No Show Afhandeling
**Epic doel:** Een block component bouwen dat de herschreven huisartsbrief toont in het artifact paneel, inclusief accordering.
**Geschatte tijd:** ~2 uur
**Afhankelijkheden:** NS.E1 (BlockType), NS.E3 (dispatch API)

---

## Context & waarschuwingen

### Bestaande `renderArtifactBlock` heeft een `default` case

`artifact-container.tsx` heeft op regel 209:
```typescript
default:
  return (
    <div className="p-4 text-slate-500">
      Onbekend artifact type: {artifact.type}
    </div>
  );
```

TypeScript heeft dit als `default` case, niet als een exhaustive check. Dat betekent: als we `register_no_show` vergeten toe te voegen aan de switch, valt de app niet over — maar toont wel "Onbekend artifact type". Visueel zichtbaar, maar geen compile-time fout.

### `getArtifactTitle` heeft ook een switch

De `getArtifactTitle` functie onderaan `artifact-container.tsx` heeft een eigen switch over `BlockType` met een `default: return 'Artifact'`. Ook hier: geen compile-time fout bij ontbrekende case, maar een generieke titel.

### Geen rich text editor in codebase

Er bestaat geen TipTap, Quill of Slate in de codebase. `dagnotitie-block.tsx` gebruikt een `<Textarea>` van shadcn/ui. Wij doen hetzelfde — dat is voldoende voor het prototype.

### BlockPrefillData is een open interface

`BlockPrefillData extends ChatEntities` — dat is een open interface. We kunnen er extra velden aan toevoegen voor no-show specifieke data (`documentId`, `originalContent`) zonder de store te wijzigen. De cast `as NoShowDocumentPrefill` in de container zorgt voor type safety binnen het block.

---

## NS.E4.S1 — NoShowDocumentBlock component

**Nieuw bestand:** `components/cortex/blocks/noshow-document-block.tsx`

### Props interface

```typescript
interface NoShowDocumentPrefill {
  documentId: string;
  content: string;           // Herschreven inhoud van LLM
  title: string;
  originalContent?: string;  // Originele inhoud voor "bekijk origineel"
  rescriptWarning?: string;  // Tonen als LLM fout had
}
```

### Component implementatie

```typescript
'use client';

import { useState } from 'react';
import { FileText, ChevronDown, ChevronUp } from 'lucide-react';
import { Textarea } from '@/components/ui/textarea';
import { Button } from '@/components/ui/button';
import { useCortexStore } from '@/stores/cortex-store';
import { cn } from '@/lib/utils';

interface NoShowDocumentPrefill {
  documentId: string;
  content: string;
  title: string;
  originalContent?: string;
  rescriptWarning?: string;
}

interface NoShowDocumentBlockProps {
  prefill: NoShowDocumentPrefill;
}

export function NoShowDocumentBlock({ prefill }: NoShowDocumentBlockProps) {
  const [content, setContent] = useState(prefill.content);
  const [showOriginal, setShowOriginal] = useState(false);
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [isDone, setIsDone] = useState(false);

  const addChatMessage = useCortexStore((s) => s.addChatMessage);
  const setNoShowStep = useCortexStore((s) => s.setNoShowStep);

  const handleDispatch = async () => {
    setIsSubmitting(true);

    try {
      const res = await fetch('/api/cortex/noshow/dispatch', {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          documentId: prefill.documentId,
          finalContent: content,
        }),
      });

      if (!res.ok) throw new Error('Dispatch mislukt');

      setIsDone(true);
      setNoShowStep('done');
      addChatMessage({
        type: 'assistant',
        content: 'Brief is verzendklaar gemaakt. De no-show afhandeling is compleet.',
      });
    } catch {
      addChatMessage({
        type: 'error',
        content: 'Opslaan mislukt. Probeer het opnieuw.',
      });
    } finally {
      setIsSubmitting(false);
    }
  };

  return (
    <div className="w-full max-w-2xl">
      {/* Header */}
      <div className="flex items-center gap-2 mb-4">
        <FileText className="w-5 h-5 text-slate-600" />
        <h2 className="text-lg font-semibold text-slate-800">
          {prefill.title || 'Huisartsbrief'}
        </h2>
        <span className="ml-auto text-xs text-amber-600 bg-amber-50 px-2 py-0.5 rounded font-medium">
          Aangepast door Cortex
        </span>
      </div>

      {/* LLM waarschuwing (bij fallback naar origineel) */}
      {prefill.rescriptWarning && (
        <div className="mb-3 p-3 bg-amber-50 border border-amber-200 rounded text-sm text-amber-800">
          ⚠️ {prefill.rescriptWarning}
        </div>
      )}

      {/* Bewerkbare tekstinhoud */}
      <Textarea
        value={content}
        onChange={(e) => setContent(e.target.value)}
        disabled={isDone}
        className={cn(
          'min-h-[320px] font-mono text-sm resize-y',
          isDone && 'opacity-60 cursor-not-allowed'
        )}
        placeholder="Brief inhoud..."
      />

      {/* Origineel bekijken — collapsible */}
      {prefill.originalContent && (
        <div className="mt-2">
          <button
            type="button"
            onClick={() => setShowOriginal(!showOriginal)}
            className="flex items-center gap-1 text-xs text-slate-500 hover:text-slate-700 transition-colors"
          >
            {showOriginal ? (
              <ChevronUp className="w-3 h-3" />
            ) : (
              <ChevronDown className="w-3 h-3" />
            )}
            {showOriginal ? 'Origineel verbergen' : 'Originele brief bekijken'}
          </button>

          {showOriginal && (
            <div className="mt-2 p-3 bg-slate-50 border border-slate-200 rounded text-sm text-slate-500 font-mono whitespace-pre-wrap">
              {prefill.originalContent}
            </div>
          )}
        </div>
      )}

      {/* Footer met Akkoord knop */}
      <div className="mt-4 flex items-center justify-between border-t border-slate-200 pt-4">
        <span className="text-xs text-slate-400">
          {isDone ? '✓ Brief is verzendklaar' : 'Controleer en pas aan indien nodig'}
        </span>

        {isDone ? (
          <span className="text-sm font-medium text-green-600 flex items-center gap-1">
            ✓ Verzendklaar
          </span>
        ) : (
          <Button
            onClick={handleDispatch}
            disabled={isSubmitting || content.trim().length === 0}
            className="bg-slate-800 hover:bg-slate-700 text-white"
          >
            {isSubmitting ? 'Opslaan...' : 'Akkoord & Verzendklaar →'}
          </Button>
        )}
      </div>
    </div>
  );
}
```

### Done criteria
- Component rendert de herschreven inhoud in een bewerkbare textarea
- "Originele brief bekijken" toggle werkt (collapsible)
- Textarea is editeerbaar — aanpassingen blijven behouden
- "Akkoord & Verzendklaar" knop roept dispatch API aan
- Na succesvolle dispatch: knop verdwijnt, "✓ Verzendklaar" verschijnt, chat ontvangt bevestiging
- Bij leeg content: knop is disabled
- Bij API fout: error message in chat, knop hergebruikbaar

---

## NS.E4.S2 — ArtifactContainer uitbreiden

**Bestand:** `components/cortex/artifacts/artifact-container.tsx`

### Wijziging 1 — Import toevoegen

Voeg toe na de bestaande block imports:
```typescript
import { NoShowDocumentBlock } from '../blocks/noshow-document-block';
```

### Wijziging 2 — Case toevoegen in `renderArtifactBlock`

Voeg toe in de switch, vóór de `default` case:

```typescript
case 'register_no_show': {
  const nsPrefill = artifact.prefill as {
    documentId: string;
    content: string;
    title: string;
    originalContent?: string;
    rescriptWarning?: string;
  };
  return (
    <NoShowDocumentBlock
      key={artifact.id}
      prefill={nsPrefill}
    />
  );
}
```

### Wijziging 3 — Case toevoegen in `getArtifactTitle`

Voeg toe in de switch, vóór de `default` case:

```typescript
case 'register_no_show':
  return prefill?.title
    ? `Brief — ${prefill.title}`
    : 'Huisartsbrief';
```

### Done criteria
- `openArtifact({ type: 'register_no_show', title: 'Huisartsbrief n.a.v. intake', prefill: { documentId: '...', content: '...', title: '...' } })` opent het `NoShowDocumentBlock`
- Tab titel in multi-artifact view toont "Brief — Huisartsbrief n.a.v. intake"
- Geen TypeScript errors in `artifact-container.tsx`
- Geen "Onbekend artifact type" fallback zichtbaar

---

## NS.E4.S3 — `openArtifact` aanroep in `handleNoShowRescriptStep`

**Bestand:** `components/cortex/chat/chat-panel.tsx`

Dit is de koppeling tussen de rescript API (NS.E3.S4) en het artifact (NS.E4.S1).

Voeg `handleNoShowRescriptStep` toe als `useCallback` in `ChatPanel`:

```typescript
const handleNoShowRescriptStep = useCallback(async () => {
  const { noShowFlow } = useCortexStore.getState();
  setNoShowStep('brief_open');

  addChatMessage({ type: 'assistant', content: 'Huisartsbrief aanpassen...' });

  try {
    const res = await fetch('/api/cortex/noshow/rescript', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        documentId: noShowFlow.documentId ?? 'mock-brief-noshow-001',
        originalContent: noShowFlow.originalContent ?? '',
        patientId: activePatient?.id ?? 'demo-patient-001',
      }),
    });

    const result = await res.json();

    openArtifact({
      type: 'register_no_show',
      title: 'Huisartsbrief n.a.v. intake',
      prefill: {
        documentId: result.documentId,
        content: result.rescriptedContent,
        title: 'Huisartsbrief n.a.v. intake',
        originalContent: result.originalContent,
        rescriptWarning: result.warning, // undefined als LLM succesvol was
      },
    });

    addChatMessage({
      type: 'assistant',
      content: 'Huisartsbrief is aangepast. Kijk of je hiermee akkoord bent.',
    });
  } catch {
    setNoShowStep('waiting_brief'); // Terug naar vorige stap
    addChatMessage({
      type: 'error',
      content: 'Herschrijven mislukt. Probeer het opnieuw.',
    });
  }
}, [activePatient, setNoShowStep, addChatMessage, openArtifact]);
```

**Let op:** `useCortexStore.getState()` wordt hier gebruikt (niet de hook) omdat we binnen een `useCallback` zitten en de state op het moment van uitvoering nodig hebben, niet de state van de laatste render.

### Done criteria
- Klikken op `[Ja, pas brief aan]` → rescript API aangeroepen → artifact paneel opent met `NoShowDocumentBlock`
- Brief inhoud is de AI-herschreven versie (of origineel bij LLM fout)
- Als `rescriptWarning` aanwezig is: gele waarschuwingsbalk zichtbaar in het block

---

## Validatie na NS.E4

**Visuele test:**
1. Doorloop de flow tot en met nudge 2 acceptatie
2. Verwacht: artifact paneel schuift open met `NoShowDocumentBlock`
3. Inhoud is de AI-herschreven brief
4. Textarea is editeerbaar
5. Klik `[Akkoord & Verzendklaar]`
6. Verwacht: "✓ Verzendklaar" in het block + bevestiging in chat
7. `pnpm build` — geen errors
