
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { Alert, Box, Button, Chip, CircularProgress, Collapse, Divider, IconButton, Paper, Snackbar, Stack, Tooltip, Typography, useTheme } from '@mui/material'
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
import HelpOutlineIcon from '@mui/icons-material/HelpOutline'
import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'
import SaveIcon from '@mui/icons-material/Save'
import MouseIcon from '@mui/icons-material/Mouse'
import '@tldraw/tldraw/tldraw.css'
import { Editor, Tldraw, createShapeId, TLShape } from '@tldraw/tldraw'
import axios from 'axios'

import { ErrorBoundary } from '../../../components/ErrorBoundary'
import { logger } from '../../../debugConfig'
import { examRepository } from '../../../services/exam/examRepository'
import type { AutoMapJobStatus, ExamTemplateDetail } from '../../../types/exam.types'
import { CanvasPageGeometry, ExamCanvasShapeModel, PAGE_HEIGHT, PAGE_WIDTH, isUuid, newDomainId, serializeCanvasShapes, shapesFromTemplate } from '../../../utils/exam-canvas/model'
import { PDF_PAGE_SHAPE_TYPE, canvasShapePalette, examCanvasShapeUtils, examCanvasTools, ExamCanvasTLShape, SHAPE_TYPES, isPdfPageShape, shapeTypeToKind } from './examCanvasShapes'
import { loadPdfPageImages, PdfPageImage } from './pdfLoader'

const TOOLS = [
  { id: 'select', label: 'Select', icon: '↖', tip: 'Move, resize, delete, or inspect the Attached pill on a region.', color: 'inherit' as const },
  { id: SHAPE_TYPES.boundary, label: 'Boundary', icon: canvasShapePalette.boundary.icon, tip: 'Draw Q start and Q end horizontal rules; an end rule on a later page creates a multi-page question span.', color: 'error' as const },
  { id: SHAPE_TYPES.part, label: 'Part', icon: canvasShapePalette.part.icon, tip: 'Draw the markable sub-question box inside a boundary pair; it becomes the leaf question/part.', color: 'warning' as const },
  { id: SHAPE_TYPES.response, label: 'Response', icon: canvasShapePalette.response.icon, tip: 'Draw around where the student writes; blue regions save as response areas.', color: 'primary' as const },
  { id: SHAPE_TYPES.context, label: 'Context', icon: canvasShapePalette.context.icon, tip: 'Draw stimulus, figures, tables, or prompt text; purple dashed regions save as context.', color: 'secondary' as const },
  { id: SHAPE_TYPES.question_number, label: 'Q Number', icon: canvasShapePalette.question_number.icon, tip: 'Box the printed question number for OCR/structure extraction.', color: 'success' as const },
  { id: SHAPE_TYPES.mark_area, label: 'Mark Area', icon: canvasShapePalette.mark_area.icon, tip: 'Box printed marks such as [2] or Total for Question text.', color: 'success' as const },
  { id: SHAPE_TYPES.reference, label: 'Reference', icon: canvasShapePalette.reference.icon, tip: 'Box formulae, data sheets, appendices, or other resources the student may use.', color: 'info' as const },
  { id: SHAPE_TYPES.furniture, label: 'Furniture', icon: canvasShapePalette.furniture.icon, tip: 'Mark page numbers, margins, blank space, or decoration to exclude from extraction.', color: 'inherit' as const },
]

const PAGE_START_X = 0
const PDF_PAGE_IDS_PREFIX = 'pdf-page-'

function pageGeometryFromImages(pages: PdfPageImage[]): CanvasPageGeometry[] {
  // S5 coordinate contract: use the actual pdf.js raster dimensions that feed each page src.
  // Server mapper emits canvas coordinates against the same PAGE_START_X=0 and stacked page heights.
  let y = 0
  return pages.map((page) => {
    const geometry = { pageNumber: page.pageNumber, x: PAGE_START_X, y, w: page.width, h: page.height }
    y += page.height
    return geometry
  })
}

function applyDocViewConstraints(editor: Editor, pages: PdfPageImage[]) {
  const maxW = pages.length ? Math.max(...pages.map((p) => p.width)) : PAGE_WIDTH
  const totalH = pages.reduce((sum, p) => sum + p.height, 0) || PAGE_HEIGHT
  editor.setCameraOptions({
    constraints: {
      bounds: { x: -64, y: -64, w: maxW + 128, h: totalH + 128 },
      padding: { x: 64, y: 64 },
      origin: { x: 0.5, y: 0 },
      initialZoom: 'fit-x-100',
      baseZoom: 'default',
      behavior: 'contain',
    },
    isLocked: false,
  })
}

function apiMessage(err: unknown): { message: string; conflict: boolean } {
  if (axios.isAxiosError(err)) {
    const detail = (err.response?.data as { detail?: string } | undefined)?.detail
    if (err.response?.status === 409) return { conflict: true, message: detail ?? 'Template has recorded marks; structural full-replace is blocked.' }
    return { conflict: false, message: detail ?? err.message }
  }
  return { conflict: false, message: err instanceof Error ? err.message : String(err) }
}


function reviewSummary(template: ExamTemplateDetail | null) {
  if (!template) return { ai: 0, unconfirmed: 0, lowConfidence: 0 }
  const rows = [...(template.questions ?? []), ...(template.response_areas ?? []), ...(template.boundaries ?? []), ...(template.layout ?? [])]
  return rows.reduce((acc, row) => {
    if (row.source === 'ai') acc.ai += 1
    if (row.source === 'ai' && row.confirmed === false) acc.unconfirmed += 1
    if (typeof row.confidence === 'number' && row.confidence < 0.7) acc.lowConfidence += 1
    return acc
  }, { ai: 0, unconfirmed: 0, lowConfidence: 0 })
}

function stripShapePrefix(id: string) {
  return id.startsWith('shape:') ? id.slice('shape:'.length) : id
}

function domainIdForShape(shape: ExamCanvasTLShape): string {
  const fromProps = shape.props.domainId
  if (isUuid(fromProps)) return fromProps
  const fromShapeId = stripShapePrefix(shape.id)
  return isUuid(fromShapeId) ? fromShapeId : newDomainId()
}

function ensureDomainIds(editor: Editor) {
  const updates = editor.getCurrentPageShapes()
    .filter((shape): shape is ExamCanvasTLShape => !!shapeTypeToKind(shape.type))
    .filter((shape) => !isUuid(shape.props.domainId))
    .map((shape) => ({ id: shape.id, type: shape.type, props: { domainId: domainIdForShape(shape) } }))
  if (updates.length) editor.updateShapes(updates)
}

function modelFromTLShape(shape: TLShape): ExamCanvasShapeModel | null {
  const kind = shapeTypeToKind(shape.type)
  if (!kind) return null
  const s = shape as ExamCanvasTLShape
  return {
    id: domainIdForShape(s),
    kind,
    x: Number(s.x ?? 0),
    y: Number(s.y ?? 0),
    w: Number(s.props.w ?? 1),
    h: Number(s.props.h ?? 1),
    label: s.props.label,
    maxMarks: s.props.maxMarks,
    responseForm: s.props.responseForm as ExamCanvasShapeModel['responseForm'],
    contextType: s.props.contextType,
    questionId: s.props.questionId ?? null,
    source: s.props.source ?? 'manual',
    confirmed: s.props.confirmed ?? s.props.source !== 'ai',
    confidence: typeof s.props.confidence === 'number' ? s.props.confidence : null,
    derivation: s.props.derivation ?? null,
  }
}

function bringDomainShapesToFront(editor: Editor) {
  const ids = editor.getCurrentPageShapes().filter((s) => !!shapeTypeToKind(s.type)).map((s) => s.id)
  if (ids.length) try { editor.bringToFront(ids as any) } catch { /* */ }
}

function loadShapes(editor: Editor, models: ExamCanvasShapeModel[]) {
  const existing = editor.getCurrentPageShapes().filter((s) => shapeTypeToKind(s.type)).map((s) => s.id)
  if (existing.length) editor.deleteShapes(existing)
  if (!models.length) return
  editor.createShapes(models.map((m) => ({
    id: createShapeId(m.id),
    type: SHAPE_TYPES[m.kind],
    x: m.x,
    y: m.y,
    props: { w: m.w, h: m.h, label: m.label ?? m.kind, kind: m.kind, maxMarks: m.maxMarks, responseForm: m.responseForm, contextType: m.contextType, questionId: m.questionId, domainId: m.id, source: m.source ?? 'manual', confirmed: m.confirmed ?? m.source !== 'ai', confidence: m.confidence ?? undefined, derivation: m.derivation ?? undefined, reviewFlags: m.reviewFlags?.join('|') },
  })))
}

function syncPdfPages(editor: Editor, pages: PdfPageImage[]) {
  const existing = editor.getCurrentPageShapes().filter((s) => isPdfPageShape(s.type)).map((s) => s.id)
  if (existing.length) editor.deleteShapes(existing)
  if (!pages.length) return
  const geometries = pageGeometryFromImages(pages)
  editor.createShapes(geometries.map((geometry) => {
    const page = pages[geometry.pageNumber - 1]
    return {
      id: createShapeId(PDF_PAGE_IDS_PREFIX + geometry.pageNumber),
      type: PDF_PAGE_SHAPE_TYPE,
      x: geometry.x,
      y: geometry.y,
      isLocked: true,
      props: { w: geometry.w, h: geometry.h, src: page.src, pageNumber: geometry.pageNumber },
    } as any
  }))
  // z-order is enforced by the caller via bringDomainShapesToFront
}

function seedGuide(editor: Editor) {
  const current = editor.getCurrentPageShapes().filter((s) => shapeTypeToKind(s.type))
  if (current.length) return
  editor.createShapes([
    { id: createShapeId(newDomainId()), type: SHAPE_TYPES.boundary, x: 48, y: 120, props: { w: PAGE_WIDTH - 96, h: 8, kind: 'boundary', label: 'Q1 start', domainId: newDomainId() } },
    { id: createShapeId(newDomainId()), type: SHAPE_TYPES.boundary, x: 48, y: PAGE_HEIGHT + 160, props: { w: PAGE_WIDTH - 96, h: 8, kind: 'boundary', label: 'Q1 end (page 2)', domainId: newDomainId() } },
    { id: createShapeId(newDomainId()), type: SHAPE_TYPES.part, x: 92, y: 180, props: { w: 520, h: 150, kind: 'part', label: 'Q1(a)', maxMarks: 3, domainId: newDomainId() } },
    { id: createShapeId(newDomainId()), type: SHAPE_TYPES.response, x: 116, y: 355, props: { w: 470, h: 120, kind: 'response', label: 'Response', responseForm: 'lines', domainId: newDomainId() } },
    { id: createShapeId(newDomainId()), type: SHAPE_TYPES.context, x: 116, y: 495, props: { w: 470, h: 90, kind: 'context', label: 'Context', contextType: 'generic', domainId: newDomainId() } },
  ])
}


function isAutoMapAccepted(value: unknown): value is { status: 'accepted'; job_id: string } {
  return !!value && typeof value === 'object' && (value as { status?: string }).status === 'accepted' && typeof (value as { job_id?: unknown }).job_id === 'string'
}

function autoMapStatusLabel(status: AutoMapJobStatus | null): string {
  if (!status) return 'Auto-map running'
  if (status.status === 'queued') return 'Auto-map queued'
  if (status.status === 'running') return 'Auto-map running'
  if (status.status === 'completed') return 'Auto-map complete'
  if (status.status === 'failed') return 'Auto-map failed'
  return `Auto-map ${status.status}`
}

const ExamTemplateSetupInner: React.FC = () => {
  const { templateId } = useParams<{ templateId: string }>()
  const navigate = useNavigate()
  const theme = useTheme()
  const editorRef = useRef<Editor | null>(null)
  const pageGeometriesRef = useRef<CanvasPageGeometry[]>([])
  const [template, setTemplate] = useState<ExamTemplateDetail | null>(null)
  const [loading, setLoading] = useState(true)
  const [saving, setSaving] = useState(false)
  const [dirty, setDirty] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [conflict, setConflict] = useState<string | null>(null)
  const [activeTool, setActiveTool] = useState('select')
  const [pdfStatus, setPdfStatus] = useState<'loading' | 'ready' | 'missing' | 'error'>('loading')
  const [pdfError, setPdfError] = useState<string | null>(null)
  const [guideOpen, setGuideOpen] = useState(false)
  const [autoMapStatus, setAutoMapStatus] = useState<AutoMapJobStatus | null>(null)
  const [autoMapBusy, setAutoMapBusy] = useState(false)
  const autoMapPollRef = useRef<number | null>(null)

  const applyTemplateToCanvas = useCallback((detail: ExamTemplateDetail) => {
    setTemplate(detail)
    const editor = editorRef.current
    if (editor) {
      const shapes = shapesFromTemplate(detail, pageGeometriesRef.current)
      loadShapes(editor, shapes)
      if (!shapes.length) seedGuide(editor)
      bringDomainShapesToFront(editor)
    }
    setDirty(false)
  }, [])

  const review = useMemo(() => reviewSummary(template), [template])

  const load = useCallback(async () => {
    if (!templateId) return
    setLoading(true); setError(null); setConflict(null)
    try {
      const detail = await examRepository.getTemplate(templateId)
      setTemplate(detail)
      let pages: PdfPageImage[] = []
      setPdfStatus('loading')
      setPdfError(null)
      try {
        const bytes = await examRepository.getTemplateSourcePdf(templateId)
        pages = await loadPdfPageImages(bytes, undefined, (partialPages) => {
          const newPage = partialPages[partialPages.length - 1]
          const allGeometries = pageGeometryFromImages(partialPages)
          pageGeometriesRef.current = allGeometries
          const ed = editorRef.current
          if (ed) {
            const geometry = allGeometries[partialPages.length - 1]
            const shapeId = createShapeId(PDF_PAGE_IDS_PREFIX + newPage.pageNumber)
            if (!ed.getCurrentPageShapes().find((s) => s.id === shapeId)) {
              ed.createShapes([{ id: shapeId, type: PDF_PAGE_SHAPE_TYPE, x: geometry.x, y: geometry.y, isLocked: true, props: { w: geometry.w, h: geometry.h, src: newPage.src, pageNumber: newPage.pageNumber } } as any])
              bringDomainShapesToFront(ed)
            }
            applyDocViewConstraints(ed, partialPages)
          }
          setPdfStatus('ready')
        })
        setPdfStatus(pages.length ? 'ready' : 'missing')
      } catch (pdfErr) {
        const pdfMsg = apiMessage(pdfErr).message
        setPdfStatus(pdfMsg.toLowerCase().includes('404') ? 'missing' : 'error')
        setPdfError(pdfMsg)
        logger.warn('cc-exam-marker', 'Template source PDF load failed', { templateId, message: pdfMsg })
      }
      const geometries = pageGeometryFromImages(pages)
      pageGeometriesRef.current = geometries
      const editor = editorRef.current
      if (editor) {
        syncPdfPages(editor, pages)
        loadShapes(editor, shapesFromTemplate(detail, geometries))
        bringDomainShapesToFront(editor)
        applyDocViewConstraints(editor, pages)
        editor.resetZoom()
      }
      setDirty(false)
    } catch (e) {
      const msg = apiMessage(e).message
      logger.warn('cc-exam-marker', 'Template setup load failed', { templateId, message: msg })
      setError(msg)
    } finally {
      setLoading(false)
    }
  }, [templateId])

  useEffect(() => { void load() }, [load])

  useEffect(() => () => {
    if (autoMapPollRef.current !== null) window.clearTimeout(autoMapPollRef.current)
  }, [])

  const pollAutoMapStatus = useCallback(async (jobId: string) => {
    if (!templateId) return
    try {
      const status = await examRepository.getAutoMapStatus(templateId, jobId)
      setAutoMapStatus(status)
      if (status.status === 'completed') {
        if (autoMapPollRef.current !== null) window.clearTimeout(autoMapPollRef.current)
        autoMapPollRef.current = null
        setAutoMapBusy(false)
        const detail = status.template ?? await examRepository.getTemplate(templateId)
        applyTemplateToCanvas(detail)
        return
      }
      if (status.status === 'failed') {
        if (autoMapPollRef.current !== null) window.clearTimeout(autoMapPollRef.current)
        autoMapPollRef.current = null
        setAutoMapBusy(false)
        setError(status.error ?? 'Auto-map failed; existing template state was preserved.')
        return
      }
      autoMapPollRef.current = window.setTimeout(() => void pollAutoMapStatus(jobId), 2500)
    } catch (e) {
      const msg = apiMessage(e).message
      setAutoMapBusy(false)
      setError(msg)
      logger.warn('cc-exam-marker', 'Auto-map status poll failed', { templateId, jobId, message: msg })
    }
  }, [applyTemplateToCanvas, templateId])

  const autoMapFromPdf = useCallback(async () => {
    if (!templateId || autoMapBusy) return
    let queued = false
    setAutoMapBusy(true); setAutoMapStatus(null); setError(null); setConflict(null)
    try {
      const result = await examRepository.autoMapTemplate(templateId)
      if (isAutoMapAccepted(result)) {
        queued = true
        setAutoMapStatus({ job_id: result.job_id, status: 'queued', template_id: templateId })
        await pollAutoMapStatus(result.job_id)
        return
      }
      setAutoMapStatus(null)
      applyTemplateToCanvas(result)
    } catch (e) {
      const msg = apiMessage(e)
      if (msg.conflict) setConflict(msg.message); else setError(msg.message)
      logger.warn('cc-exam-marker', 'Auto-map request failed', { templateId, message: msg.message })
    } finally {
      if (!queued) setAutoMapBusy(false)
    }
  }, [applyTemplateToCanvas, autoMapBusy, pollAutoMapStatus, templateId])

  const save = useCallback(async () => {
    const editor = editorRef.current
    if (!editor || !templateId || !template) return
    setSaving(true); setError(null); setConflict(null)
    try {
      ensureDomainIds(editor)
      const shapes = editor.getCurrentPageShapes().map(modelFromTLShape).filter(Boolean) as ExamCanvasShapeModel[]
      const payload = serializeCanvasShapes(template, shapes, pageGeometriesRef.current)
      const saved = await examRepository.replaceTemplate(templateId, payload)
      setTemplate(saved)
      loadShapes(editor, shapesFromTemplate(saved, pageGeometriesRef.current))
      setDirty(false)
    } catch (e) {
      const msg = apiMessage(e)
      if (msg.conflict) setConflict(msg.message); else setError(msg.message)
      logger.warn('cc-exam-marker', 'Template setup save failed', { templateId, message: msg.message })
    } finally {
      setSaving(false)
    }
  }, [template, templateId])

  const layoutSummary = useMemo(() => {
    const rows = (template?.layout ?? []).filter((row) => row.margins_enabled && row.margin_left !== null && row.margin_right !== null)
    return rows.slice(0, 4).map((row) => `P${row.page_index + 1} ${row.role ?? 'page'} L${Math.round(row.margin_left ?? 0)} R${Math.round(row.margin_right ?? 0)} T${Math.round(row.margin_top ?? 0)} B${Math.round(row.margin_bottom ?? 0)}`)
  }, [template?.layout])

  const toolButtons = useMemo(() => TOOLS.map((tool) => (
    <Tooltip title={tool.tip} key={tool.id} placement="right">
      <Button
        size="small"
        variant={activeTool === tool.id ? 'contained' : 'outlined'}
        color={tool.color}
        startIcon={tool.id === 'select' ? <MouseIcon fontSize="small" /> : <Box component="span" sx={{ minWidth: 22, textAlign: 'center', fontWeight: 900 }}>{tool.icon}</Box>}
        onClick={() => {
          const editor = editorRef.current
          if (!editor) return
          editor.setCurrentTool(tool.id === 'select' ? 'select' : tool.id)
          setActiveTool(tool.id)
        }}
        sx={{ justifyContent: 'flex-start', minWidth: 126 }}
      >
        {tool.label}
      </Button>
    </Tooltip>
  )), [activeTool])

  return (
    <Box sx={{ position: 'fixed', inset: 0, zIndex: (t) => t.zIndex.drawer + 20, bgcolor: 'background.default', display: 'flex', flexDirection: 'column' }}>

      {/* Top bar — single compact line */}
      <Paper elevation={8} sx={{ px: 1.5, py: 0.75, display: 'flex', alignItems: 'center', gap: 1, bgcolor: 'background.paper', borderRadius: 0, flexShrink: 0 }}>
        <Tooltip title="Back to exam marker">
          <IconButton onClick={() => navigate('/exam-marker')} size="small"><ArrowBackIcon fontSize="small" /></IconButton>
        </Tooltip>
        <Divider orientation="vertical" flexItem />
        <Typography variant="subtitle2" noWrap sx={{ flex: 1, minWidth: 0 }}>{template?.title ?? 'Template setup'}</Typography>
        <Chip size="small" color={review.unconfirmed ? 'warning' : review.ai ? 'info' : 'default'} label={review.ai ? `AI review: ${review.unconfirmed} unconfirmed · ${review.lowConfidence} low conf` : 'Manual template'} />
        <Chip size="small" color={dirty ? 'warning' : 'success'} label={dirty ? 'Unsaved' : 'Saved'} />
        {(autoMapBusy || autoMapStatus) && <Chip size="small" color={autoMapStatus?.status === 'failed' ? 'error' : autoMapStatus?.status === 'completed' ? 'success' : 'info'} label={autoMapStatusLabel(autoMapStatus)} />}
        <Button size="small" variant="outlined" startIcon={autoMapBusy ? <CircularProgress size={14} /> : <AutoFixHighIcon fontSize="small" />} onClick={autoMapFromPdf} disabled={autoMapBusy || saving || loading || !template || pdfStatus !== 'ready'}>Auto-map from PDF</Button>
        <Button size="small" variant="contained" startIcon={saving ? <CircularProgress size={14} color="inherit" /> : <SaveIcon fontSize="small" />} onClick={save} disabled={saving || loading || !template}>Save</Button>
      </Paper>

      {/* Body row */}
      <Box sx={{ flex: 1, display: 'flex', overflow: 'hidden' }}>

        {/* Left tool sidebar */}
        <Paper elevation={4} sx={{ width: 160, flexShrink: 0, p: 1.25, borderRadius: 0, bgcolor: 'background.paper', overflowY: 'auto', display: 'flex', flexDirection: 'column', borderRight: 1, borderColor: 'divider' }}>
          <Stack spacing={1}>{toolButtons}</Stack>
        </Paper>

        {/* Canvas area */}
        <Box sx={{ flex: 1, position: 'relative', overflow: 'hidden' }} data-testid="exam-template-setup-canvas">
          <Box sx={{ position: 'absolute', inset: 0, '& .tlui-layout': { display: 'none' } }}>
            <Tldraw
              shapeUtils={examCanvasShapeUtils as any}
              tools={examCanvasTools as any}
              hideUi
              inferDarkMode={theme.palette.mode === 'dark'}
              autoFocus
              onMount={(editor) => {
                editorRef.current = editor
                editor.user.updateUserPreferences({ colorScheme: theme.palette.mode === 'dark' ? 'dark' : 'light' })
                editor.store.listen(() => setDirty(true), { scope: 'document' })
                applyDocViewConstraints(editor, [])
                editor.resetZoom()
                // Only seed the example guide for a genuinely-empty template AFTER it has loaded.
                // (Previously `else seedGuide` fired on mount while `template` was still null during
                // the async fetch, flashing placeholder shapes before the real shapes/PDF rendered.)
                if (template) {
                  const s = shapesFromTemplate(template, pageGeometriesRef.current)
                  loadShapes(editor, s)
                  if (!s.length) seedGuide(editor)
                }
                bringDomainShapesToFront(editor)
              }}
            />
          </Box>

          {/* Guide toggle */}
          <Tooltip title={guideOpen ? 'Hide guide' : 'Show setup guide'} placement="left">
            <IconButton onClick={() => setGuideOpen((v) => !v)} size="small" sx={{ position: 'absolute', right: 16, bottom: 16, zIndex: 1001, bgcolor: 'background.paper', boxShadow: 2, '&:hover': { bgcolor: 'background.paper' } }}>
              <HelpOutlineIcon fontSize="small" color={guideOpen ? 'primary' : 'action'} />
            </IconButton>
          </Tooltip>

          {/* Guide panel — collapsible */}
          <Collapse in={guideOpen} sx={{ position: 'absolute', right: 16, bottom: 48, zIndex: 1000, maxWidth: 440 }}>
            <Paper elevation={4} sx={{ p: 2, borderRadius: 3, bgcolor: 'background.paper' }}>
              <Typography variant="subtitle2" gutterBottom>Setup guide</Typography>
              <Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
                1) Boundary start/end lines define each main question. 2) Draw amber Part boxes for markable sub-questions. 3) AI suggestions render dashed/translucent with confidence and cheap review flags; manual shapes stay solid.
              </Typography>
              <Alert severity={review.unconfirmed || review.lowConfidence ? 'warning' : 'info'} variant="outlined" sx={{ my: 1 }}>
                Review layer: {review.ai} AI suggestions, {review.unconfirmed} unconfirmed, {review.lowConfidence} below 70% confidence. Cheap flags include overlap, missing marks, uncertain labels, low confidence, and unconfirmed AI.
              </Alert>
              <Stack direction="row" spacing={0.75} useFlexGap flexWrap="wrap" sx={{ my: 1 }}>
                {(['boundary', 'part', 'response', 'context', 'question_number', 'mark_area', 'reference', 'furniture'] as const).map((kind) => {
                  const p = canvasShapePalette[kind]
                  return <Chip key={kind} size="small" label={`${p.icon} ${p.label}`} sx={{ borderColor: p.stroke, color: p.stroke, bgcolor: p.fill, fontWeight: 700 }} variant="outlined" />
                })}
              </Stack>
              <Divider sx={{ my: 1 }} />
              <Typography variant="caption" color="text.secondary" display="block">Multi-page boundary pairing</Typography>
              <Typography variant="body2" sx={{ fontWeight: 700 }}>Draw "Q start" on page N, then "Q end" on a later page; save pairs boundaries by reading order into one question span.</Typography>
              <Typography variant="caption" color={pdfStatus === 'ready' ? 'success.main' : pdfStatus === 'error' ? 'error.main' : 'text.secondary'} sx={{ display: 'block', mt: 1 }}>
                PDF: {pdfStatus === 'ready' ? 'loaded' : pdfStatus === 'loading' ? 'loading…' : pdfStatus === 'missing' ? 'no source PDF' : pdfError ?? 'failed'}
              </Typography>
              <Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 1 }}>
                Margins: {layoutSummary.length ? layoutSummary.join(' · ') : 'not detected yet'}
              </Typography>
            </Paper>
          </Collapse>

          {/* Conflict alert */}
          {conflict && <Alert severity="warning" sx={{ position: 'absolute', top: 16, right: 16, maxWidth: 560, zIndex: 1001 }} onClose={() => setConflict(null)}>{conflict}</Alert>}
        </Box>
      </Box>

      {loading && <Box sx={{ position: 'absolute', inset: 0, display: 'grid', placeItems: 'center', bgcolor: 'rgba(15,23,42,.18)', zIndex: 10 }}><CircularProgress /></Box>}
      <Snackbar open={!!error} autoHideDuration={8000} onClose={() => setError(null)}><Alert severity="error" onClose={() => setError(null)}>{error}</Alert></Snackbar>
    </Box>
  )
}

const ExamTemplateSetupPage: React.FC = () => (
  <ErrorBoundary fallback={<Box sx={{ p: 4 }}><Alert severity="error">Template setup canvas crashed. Reload the page and try again.</Alert></Box>}>
    <ExamTemplateSetupInner />
  </ErrorBoundary>
)

export default ExamTemplateSetupPage
