import { useCallback, useEffect, useState, Fragment } from 'react'
import {
  AlertTriangle,
  CheckCircle2,
  FileSearch,
  FileText,
  Image,
  Layers,
  Loader2,
  RefreshCw,
  Table2,
  Upload,
  XCircle,
  Gauge,
} from 'lucide-react'
import { cn } from '../../lib/utils'
import { subTabActive, subTabIdle } from '../../lib/tabActive'
import { DqMonitoringPanel } from './DqMonitoringPanel'

type Dimension = {
  id: string
  label: string
  description: string
  score: number
  level: string
  findings: string[]
  recommended_actions?: string[]
}

type ColumnProfile = {
  name: string
  dtype: string
  null_pct: number
  unique_count: number
  quality_flags: string[]
  sample_values?: string[]
  numeric?: { min: number; max: number; mean: number; outliers: number }
  text?: { avg_length: number; empty_strings: number }
  top_values?: { value: string; count: number }[]
}

type Violations = {
  violation_count?: number
  violation_pct?: number
  sample_rows?: { row_index: number; values: Record<string, unknown> }[]
  location_hint?: string
  affected_columns?: string[]
}

type DqCheck = {
  id?: string
  suite: string
  success?: boolean
  status?: string
  outcome?: string
  expectation?: string
  name?: string
  metric?: string
  check?: string
  test?: string
  rule?: string
  monitor?: string
  result?: string
  detail?: string
  column?: string
  violations?: Violations
  meta?: Record<string, unknown>
}

type GxCheck = DqCheck & { expectation: string; success: boolean; result: string }
type SodaCheck = DqCheck & { name: string; check: string; outcome: string; detail: string }

type DocStructure = {
  pages: number
  pictures: number
  tables: number
  text_blocks: number
  headings: number
  paragraphs: number
  list_items?: number
  form_items: number
  key_value_pairs: number
  label_counts?: Record<string, number>
  table_details?: { index: number; rows: number; cols: number; cells: number; preview?: string }[]
  picture_details?: { index: number; label: string; has_image: boolean; captions: number }[]
  outline?: { type: string; text: string; level?: number }[]
}

type AssessResult = {
  ok: boolean
  report_id: string
  overall_score: number
  maturity_level: string
  maturity_description?: string
  rows: number
  columns: number
  dimensions: Dimension[]
  column_profiles: ColumnProfile[]
  action_items: { priority: string; dimension: string; score: number; action: string }[]
  checks?: {
    great_expectations: GxCheck[]
    soda_core: SodaCheck[]
    deequ?: DqCheck[]
    pandera?: DqCheck[]
    dbt_expectations?: DqCheck[]
    monte_carlo?: DqCheck[]
    affirm?: DqCheck[]
  }
  checks_summary: Record<string, { total: number; passed?: number; warnings?: number }>
  executive_summary?: {
    headline: string
    dataset: string
    checks_run: number
    issues_found: number
    flagged_columns: number
    tools: string[]
    recommendation: string
  }
  docling?: { used: boolean; parse_id?: string; document_structure?: DocStructure; stats?: Record<string, number>; images?: DocImage[] }
  rag_ingest?: { ok: boolean; duplicate?: boolean; chunks?: number; message?: string; error?: string }
  report_url: string
}

type DocImage = {
  index: number
  label: string
  available: boolean
  url?: string
  width?: number
  height?: number
  mimetype?: string
  dpi?: number
  bytes?: number
  captions?: string[]
}

type ParseResult = {
  ok: boolean
  parse_id: string
  filename: string
  status: string
  processing_time_sec?: number
  formats_available: string[]
  document_structure: DocStructure
  images?: DocImage[]
  stats: Record<string, number>
  content: { preview_markdown?: string; preview_html?: string; markdown?: string; html?: string }
  table_preview?: string[]
  errors?: string[]
  parse_json_url?: string
}

type Capabilities = {
  maturity_dimensions: { id: string; label: string; description: string }[]
  maturity_levels: { min_score: number; label: string; description: string }[]
  supported_data_formats: string[]
  supported_document_formats: string[]
  tools: Record<string, { status: string; capabilities?: string[] }>
  docling_online: boolean
}

type ReportSummary = {
  id: string
  filename: string
  ts: string
  overall_score: number
  maturity_level: string
  rows: number
  columns: number
}

type Tab = 'assess' | 'docling' | 'reports' | 'monitoring'

const SCORE_COLOR = (s: number) => (s >= 80 ? 'text-success' : s >= 60 ? 'text-warning' : 'text-danger')
const BAR_COLOR = (s: number) => (s >= 80 ? 'bg-success' : s >= 60 ? 'bg-warning' : 'bg-danger')

export function DataQualityView() {
  const [tab, setTab] = useState<Tab>('monitoring')
  const [caps, setCaps] = useState<Capabilities | null>(null)
  const [loading, setLoading] = useState(false)
  const [assess, setAssess] = useState<AssessResult | null>(null)
  const [parse, setParse] = useState<ParseResult | null>(null)
  const [parseFormat, setParseFormat] = useState<'markdown' | 'html'>('markdown')
  const [reports, setReports] = useState<ReportSummary[]>([])
  const [error, setError] = useState<string | null>(null)
  const [expandedCol, setExpandedCol] = useState<string | null>(null)
  const [expandedCheck, setExpandedCheck] = useState<string | null>(null)
  const [expandedDim, setExpandedDim] = useState<string | null>(null)
  const [activeTool, setActiveTool] = useState<string>('great_expectations')

  const loadMeta = useCallback(async () => {
    try {
      const [c, r] = await Promise.all([fetch('/dq/capabilities'), fetch('/dq/reports')])
      if (c.ok) setCaps(await c.json())
      if (r.ok) {
        const j = await r.json()
        setReports(j.reports || [])
      }
    } catch {
      /* ignore */
    }
  }, [])

  useEffect(() => {
    loadMeta()
  }, [loadMeta])

  const onAssess = async (file: File) => {
    setLoading(true)
    setError(null)
    setAssess(null)
    const fd = new FormData()
    fd.append('file', file)
    try {
      const r = await fetch('/dq/assess', { method: 'POST', body: fd })
      const j = await r.json()
      if (!r.ok || !j.ok) {
        setError(j.error || j.detail || 'Assessment failed')
        return
      }
      setAssess(j as AssessResult)
      loadMeta()
    } catch {
      setError('Connection failed — check DQ API')
    } finally {
      setLoading(false)
    }
  }

  const onParse = async (file: File) => {
    setLoading(true)
    setError(null)
    setParse(null)
    const fd = new FormData()
    fd.append('file', file)
    fd.append('to_formats', 'md,html,json')
    const ctrl = new AbortController()
    const timer = setTimeout(() => ctrl.abort(), 300000)
    try {
      const r = await fetch('/dq/parse', { method: 'POST', body: fd, signal: ctrl.signal })
      const j = await r.json()
      if (!r.ok || !j.ok) {
        setError(typeof j.error === 'string' ? j.error : JSON.stringify(j.error || j).slice(0, 200) || 'Docling parse failed')
        return
      }
      setParse(j as ParseResult)
      loadMeta()
    } catch (e) {
      setError(e instanceof Error && e.name === 'AbortError' ? 'Timeout — document too large or Docling overloaded' : 'Docling unavailable')
    } finally {
      clearTimeout(timer)
      setLoading(false)
    }
  }

  const tabs: { id: Tab; label: string; icon: typeof FileSearch }[] = [
    { id: 'monitoring', label: 'Live Monitoring', icon: Gauge },
    { id: 'assess', label: 'Maturity Assessment', icon: FileSearch },
    { id: 'docling', label: 'Docling Parser', icon: FileText },
    { id: 'reports', label: 'Reports', icon: CheckCircle2 },
  ]

  return (
    <div className="flex h-full min-h-[calc(100vh-140px)] flex-col rounded-lg border border-border bg-surface-raised">
      <header className="shrink-0 border-b border-border bg-surface-overlay/30 px-4 py-3">
        <div className="flex flex-wrap items-start justify-between gap-3">
          <div>
            <h2 className="text-base font-semibold text-foreground">Data Quality & Maturity Platform</h2>
            <p className="text-[11px] text-foreground-muted">
              Full data maturity assessment for customer data — Docling, Great Expectations, Soda Core
            </p>
          </div>
          <div className="flex items-center gap-2">
            <span className={cn('rounded-full px-2.5 py-1 text-[10px] font-medium', caps?.docling_online ? 'bg-success/20 text-success' : 'bg-danger/20 text-danger')}>
              Docling {caps?.docling_online ? '● online' : '○ offline'}
            </span>
            <button type="button" onClick={loadMeta} className="rounded border border-border p-1.5 hover:bg-surface-overlay">
              <RefreshCw className="h-4 w-4" />
            </button>
            <a href={`http://${window.location.hostname}:5001/ui/`} target="_blank" rel="noreferrer" className="rounded border border-docker/40 bg-docker/15 px-2 py-1 text-[10px] text-docker">
              Docling UI ↗
            </a>
          </div>
        </div>

        {caps && (
          <div className="mt-3 grid gap-2 sm:grid-cols-2 lg:grid-cols-4">
            <CapCard title="Maturity Engine" items={caps.maturity_dimensions.map((d) => d.label)} icon={Layers} />
            <CapCard title="Data Quality Tools" items={['Great Expectations', 'Soda Core', 'Deequ', 'Pandera', 'dbt', 'Monte Carlo', 'Affirm', 'Pandas']} icon={CheckCircle2} />
            <CapCard title="Document Parsing" items={caps.tools.docling?.capabilities || ['PDF', 'PPTX', 'DOCX']} icon={FileText} />
            <CapCard title="File formats" items={[...caps.supported_data_formats.slice(0, 4), ...caps.supported_document_formats.slice(0, 3)]} icon={Upload} />
          </div>
        )}
      </header>

      <div className="flex shrink-0 gap-1 border-b border-border bg-surface-overlay/20 px-3 py-2">
        {tabs.map(({ id, label, icon: Icon }) => (
          <button
            key={id}
            type="button"
            onClick={() => setTab(id)}
            className={cn('flex items-center gap-1.5 rounded-md px-3 py-2 text-[11px] font-medium transition-all', tab === id ? subTabActive : subTabIdle)}
          >
            <Icon className="h-4 w-4" />
            {label}
          </button>
        ))}
      </div>

      <div className="scrollbar-thin flex-1 overflow-y-auto p-4">
        {error && (
          <div className="mb-4 flex items-start gap-2 rounded-lg border border-danger/40 bg-danger/10 px-4 py-3 text-[11px] text-danger">
            <XCircle className="mt-0.5 h-4 w-4 shrink-0" />
            <span>{error}</span>
          </div>
        )}

        {tab === 'monitoring' && <DqMonitoringPanel />}

        {tab === 'assess' && (
          <div className="space-y-5">
            <UploadZone
              loading={loading}
              label="Upload customer data for full maturity assessment"
              hint="CSV · Excel · JSON · Parquet · PDF · PPTX · DOCX"
              accept=".csv,.tsv,.xlsx,.xls,.json,.parquet,.pdf,.pptx,.ppt,.docx"
              onFile={onAssess}
            />

            {loading && <LoadingMsg text="Analyzing: 6 maturity dimensions · GE checks · Soda checks · column profiles…" />}

            {assess && (
              <div className="space-y-5">
                <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-5">
                  <StatCard label="Overall Score" value={`${assess.overall_score}`} sub="/100" accent />
                  <StatCard label="Maturity Level" value={assess.maturity_level} sub={assess.maturity_description} />
                  <StatCard label="Dataset" value={`${assess.rows.toLocaleString()}`} sub={`${assess.columns} columns`} />
                  <StatCard label="DQ Checks" value={String(assess.executive_summary?.checks_run || Object.values(assess.checks_summary).reduce((s, x) => s + (x.total || 0), 0))} sub="across 7 tools" />
                  <StatCard label="Issues" value={String(assess.executive_summary?.issues_found ?? 0)} sub="failed/warn checks" warn={(assess.executive_summary?.issues_found ?? 0) > 0} />
                </div>

                {assess.docling?.used && assess.docling.document_structure && (
                  <>
                    <DocStructurePanel structure={assess.docling.document_structure} title="Document structure (via Docling)" />
                    {assess.docling.images && assess.docling.images.length > 0 && assess.docling.parse_id && (
                      <ImageGallery images={assess.docling.images} parseId={assess.docling.parse_id} />
                    )}
                  </>
                )}

                {assess.rag_ingest && (
                  <div className={cn(
                    'rounded-lg border px-3 py-2 text-[11px]',
                    assess.rag_ingest.ok ? 'border-success/30 bg-success/10 text-success' : 'border-warning/30 bg-warning/10 text-warning',
                  )}>
                    <p className="font-medium">Knowledge Chat sync</p>
                    <p className="text-foreground-muted">
                      {assess.rag_ingest.ok
                        ? (assess.rag_ingest.duplicate
                          ? `Already in Knowledge Chat — ${assess.rag_ingest.message || 'you can chat immediately.'}`
                          : `Indexed for chat: ${assess.rag_ingest.chunks ?? '?'} text chunks. Open Knowledge Chat to ask questions.`)
                        : (assess.rag_ingest.error || 'Could not sync to Knowledge Chat')}
                    </p>
                  </div>
                )}

                {assess.executive_summary && (
                  <section className="rounded-lg border border-docker/30 bg-docker/5 p-4">
                    <h3 className="mb-2 text-[11px] font-semibold uppercase tracking-wider text-docker">Executive Summary — Customer Report</h3>
                    <p className="text-[13px] font-semibold text-foreground">{assess.executive_summary.headline}</p>
                    <p className="mt-1 text-[11px] text-foreground-muted">{assess.executive_summary.dataset}</p>
                    <div className="mt-2 flex flex-wrap gap-3 text-[10px]">
                      <span>{assess.executive_summary.checks_run} checks run</span>
                      <span className={assess.executive_summary.issues_found > 0 ? 'text-warning' : 'text-success'}>{assess.executive_summary.issues_found} issues found</span>
                      <span>{assess.executive_summary.flagged_columns} flagged columns</span>
                    </div>
                    <p className="mt-2 border-t border-border/50 pt-2 text-[11px] text-foreground-muted">
                      <strong className="text-foreground">Recommendation:</strong> {assess.executive_summary.recommendation}
                    </p>
                  </section>
                )}

                <div className="flex flex-wrap gap-2">
                  <a href={assess.report_url} target="_blank" rel="noreferrer" className={cn('rounded-md px-3 py-1.5 text-[11px] font-medium', subTabActive)}>
                    Full HTML report ↗
                  </a>
                  <a href={assess.report_url + '/json'} target="_blank" rel="noreferrer" className={cn('rounded-md px-3 py-1.5 text-[11px]', subTabIdle)}>
                    JSON export ↗
                  </a>
                </div>

                <section>
                  <h3 className="mb-2 text-[11px] font-semibold uppercase tracking-wider text-foreground-faint">Data Quality Tools — click any row for error details</h3>
                  <div className="mb-2 flex flex-wrap gap-1">
                    {([
                      ['great_expectations', 'Great Expectations'],
                      ['soda_core', 'Soda Core'],
                      ['deequ', 'AWS Deequ'],
                      ['pandera', 'Pandera'],
                      ['dbt_expectations', 'dbt Expectations'],
                      ['monte_carlo', 'Monte Carlo'],
                      ['affirm', 'Affirm'],
                    ] as const).map(([key, label]) => {
                      const items = assess.checks?.[key] || []
                      if (!items.length) return null
                      const summary = assess.checks_summary?.[key]
                      const badge = summary?.passed !== undefined ? `${summary.passed}/${summary.total}` : `${items.length}`
                      return (
                        <button key={key} type="button" onClick={() => setActiveTool(key)} className={cn('rounded-md px-2.5 py-1 text-[10px]', activeTool === key ? subTabActive : subTabIdle)}>
                          {label} ({badge})
                        </button>
                      )
                    })}
                  </div>
                  {assess.checks && (
                    <CheckTable
                      title={activeTool.replace(/_/g, ' ')}
                      checks={normalizeChecks(assess.checks[activeTool as keyof typeof assess.checks] || [])}
                      expandedId={expandedCheck}
                      onToggle={setExpandedCheck}
                    />
                  )}
                </section>

                <section>
                  <h3 className="mb-3 text-[11px] font-semibold uppercase tracking-wider text-foreground-faint">6 Maturity Dimensions</h3>
                  <div className="grid gap-3 lg:grid-cols-2 xl:grid-cols-3">
                    {assess.dimensions.map((d) => (
                      <DimensionCard key={d.id} dimension={d} expanded={expandedDim === d.id} onToggle={() => setExpandedDim(expandedDim === d.id ? null : d.id)} />
                    ))}
                  </div>
                </section>

                {assess.action_items.length > 0 && (
                  <section>
                    <h3 className="mb-2 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wider text-foreground-faint">
                      <AlertTriangle className="h-3.5 w-3.5 text-warning" /> Remediation Roadmap
                    </h3>
                    <div className="space-y-1.5">
                      {assess.action_items.map((a, i) => (
                        <div key={i} className={cn('rounded-lg border px-3 py-2 text-[11px]', a.priority === 'high' ? 'border-danger/40 bg-danger/10' : a.priority === 'medium' ? 'border-warning/40 bg-warning/10' : 'border-border bg-surface-overlay/40')}>
                          <span className="font-bold uppercase text-foreground-faint">{a.priority}</span>
                          {' · '}<strong>{a.dimension}</strong> ({a.score}): {a.action}
                        </div>
                      ))}
                    </div>
                  </section>
                )}

                <section>
                  <h3 className="mb-2 text-[11px] font-semibold uppercase tracking-wider text-foreground-faint">
                    Column profiles ({assess.column_profiles.length})
                  </h3>
                  <ColumnTable profiles={assess.column_profiles} expandedCol={expandedCol} onToggle={setExpandedCol} />
                </section>
              </div>
            )}
          </div>
        )}

        {tab === 'docling' && (
          <div className="space-y-5">
            <p className="text-[12px] leading-relaxed text-foreground-muted">
              Docling extracts text, tables, images and document structure from PDF, PowerPoint, Word, Excel and images.
              Output: Markdown, HTML, JSON with pages, images, tables and document outline.
            </p>
            <UploadZone
              loading={loading}
              label="Upload document for Docling parsing"
              hint="PDF · PPTX · DOCX · XLSX · PNG · JPG · TIFF · MD · HTML"
              accept=".pdf,.pptx,.ppt,.docx,.doc,.xlsx,.png,.jpg,.jpeg,.tiff,.txt,.md,.html"
              onFile={onParse}
            />

            {loading && <LoadingMsg text="Docling processing document — OCR, table detection, images (30–180 sec)…" />}

            {parse && (
              <div className="space-y-4">
                <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4 xl:grid-cols-6">
                  <StatCard label="File" value={parse.filename.length > 20 ? parse.filename.slice(0, 18) + '…' : parse.filename} sub={parse.status} />
                  <StatCard label="Processing" value={`${(parse.processing_time_sec || 0).toFixed(1)}s`} sub={`Formats: ${parse.formats_available.join(', ')}`} />
                  <StatCard label="Pages" value={String(parse.document_structure?.pages ?? parse.stats.pages ?? 0)} icon={Layers} />
                  <StatCard label="Images" value={String(parse.document_structure?.pictures ?? 0)} icon={Image} accent />
                  <StatCard label="Tables" value={String(parse.document_structure?.tables ?? 0)} icon={Table2} />
                  <StatCard label="Text blocks" value={String(parse.document_structure?.text_blocks ?? 0)} sub={`${parse.stats.words?.toLocaleString() ?? 0} words`} />
                </div>

                <DocStructurePanel structure={parse.document_structure} title="Document analysis" />

                {parse.images && parse.images.filter((i) => i.available).length > 0 && (
                  <ImageGallery images={parse.images} parseId={parse.parse_id} />
                )}

                {parse.document_structure?.outline && parse.document_structure.outline.length > 0 && (
                  <section className="rounded-lg border border-border bg-surface-overlay/30 p-3">
                    <h3 className="mb-2 text-[11px] font-semibold uppercase text-foreground-faint">Document outline</h3>
                    <ul className="space-y-1 text-[11px]">
                      {parse.document_structure.outline.map((o, i) => (
                        <li key={i} className="flex gap-2" style={{ paddingLeft: (o.level || 0) * 12 }}>
                          <span className="shrink-0 rounded bg-docker/20 px-1 font-mono text-[9px] text-docker">{o.type}</span>
                          <span className="text-foreground-muted">{o.text}</span>
                        </li>
                      ))}
                    </ul>
                  </section>
                )}

                <div className="flex gap-1">
                  {(['markdown', 'html'] as const).map((f) => (
                    <button key={f} type="button" onClick={() => setParseFormat(f)} className={cn('rounded-md px-3 py-1.5 text-[11px] font-medium', parseFormat === f ? subTabActive : subTabIdle)}>
                      {f.toUpperCase()}
                    </button>
                  ))}
                  {parse.parse_json_url && (
                    <a href={parse.parse_json_url} target="_blank" rel="noreferrer" className={cn('rounded-md px-3 py-1.5 text-[11px]', subTabIdle)}>
                      Full JSON ↗
                    </a>
                  )}
                </div>

                {parse.table_preview && parse.table_preview.length > 0 && (
                  <section>
                    <h3 className="mb-1 text-[11px] font-semibold uppercase text-foreground-faint">Tables (markdown preview)</h3>
                    <pre className="scrollbar-thin max-h-40 overflow-auto rounded-lg border border-border bg-surface-overlay p-3 font-mono text-[10px]">
                      {parse.table_preview.join('\n')}
                    </pre>
                  </section>
                )}

                <section>
                  <h3 className="mb-2 text-[11px] font-semibold uppercase text-foreground-faint">Extracted content</h3>
                  {parseFormat === 'html' && (parse.content.preview_html || parse.content.html) ? (
                    <div className="scrollbar-thin max-h-[500px] overflow-auto rounded-lg border border-border bg-surface-overlay p-2">
                      <div className="rounded bg-white p-4 text-black" dangerouslySetInnerHTML={{ __html: parse.content.preview_html || parse.content.html || '' }} />
                    </div>
                  ) : (
                    <pre className="scrollbar-thin max-h-[500px] overflow-auto rounded-lg border border-border bg-surface-overlay p-4 font-mono text-[11px] leading-relaxed text-foreground">
                      {parse.content.preview_markdown || parse.content.markdown || '(no content)'}
                    </pre>
                  )}
                </section>
              </div>
            )}
          </div>
        )}

        {tab === 'reports' && (
          <div className="space-y-2">
            {reports.length === 0 ? (
              <p className="py-12 text-center text-sm text-foreground-muted">No reports yet — upload customer data in Maturity Assessment.</p>
            ) : (
              reports.map((r) => (
                <a key={r.id} href={`/dq/report/${r.id}`} target="_blank" rel="noreferrer"
                  className="flex items-center justify-between rounded-lg border border-border bg-surface-overlay/30 px-4 py-3 transition-all hover:border-docker/40 hover:bg-docker/10">
                  <div>
                    <p className="text-[12px] font-medium">{r.filename}</p>
                    <p className="text-[10px] text-foreground-faint">{r.ts} · {r.rows?.toLocaleString()} rows · {r.columns} cols</p>
                  </div>
                  <div className="text-right">
                    <p className={cn('text-xl font-bold', SCORE_COLOR(r.overall_score))}>{r.overall_score}</p>
                    <p className="text-[10px] text-foreground-muted">{r.maturity_level}</p>
                  </div>
                </a>
              ))
            )}
          </div>
        )}
      </div>
    </div>
  )
}

function ImageGallery({ images, parseId }: { images: DocImage[]; parseId: string }) {
  const available = images.filter((i) => i.available)
  const [lightbox, setLightbox] = useState<number | null>(null)
  if (!available.length) {
    return (
      <section className="rounded-lg border border-border bg-surface-overlay/30 p-4">
        <h3 className="mb-2 text-[11px] font-semibold uppercase tracking-wider text-foreground-faint">
          Images detected ({images.length}) — no embedded export
        </h3>
        <p className="text-[11px] text-foreground-muted">Re-upload the document to extract images (embedded mode).</p>
      </section>
    )
  }
  return (
    <section className="rounded-lg border border-border bg-surface-overlay/30 p-4">
      <h3 className="mb-3 flex items-center gap-2 text-[11px] font-semibold uppercase tracking-wider text-foreground-faint">
        <Image className="h-4 w-4 text-docker" />
        Images extracted by Docling ({available.length})
      </h3>
      <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
        {available.map((img) => (
          <button
            key={img.index}
            type="button"
            onClick={() => setLightbox(img.index)}
            className="group overflow-hidden rounded-lg border border-border bg-surface-raised text-left transition-all hover:border-docker/50 hover:shadow-docker"
          >
            <div className="flex aspect-[4/3] items-center justify-center overflow-hidden bg-black/20">
              <img
                src={img.url || `/dq/parse/${parseId}/image/${img.index}`}
                alt={img.label}
                className="max-h-full max-w-full object-contain transition-transform group-hover:scale-105"
                loading="lazy"
              />
            </div>
            <div className="p-2">
              <p className="text-[10px] font-medium text-foreground">#{img.index + 1} {img.label}</p>
              <p className="text-[9px] text-foreground-faint">
                {img.width && img.height ? `${Math.round(img.width)}×${Math.round(img.height)}` : ''}
                {img.dpi ? ` · ${img.dpi}dpi` : ''}
                {img.bytes ? ` · ${(img.bytes / 1024).toFixed(0)}KB` : ''}
              </p>
            </div>
          </button>
        ))}
      </div>
      {lightbox !== null && (
        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 p-4" onClick={() => setLightbox(null)}>
          <div className="relative max-h-[90vh] max-w-[90vw]" onClick={(e) => e.stopPropagation()}>
            <img
              src={`/dq/parse/${parseId}/image/${lightbox}`}
              alt={`Image ${lightbox + 1}`}
              className="max-h-[85vh] max-w-full rounded-lg object-contain"
            />
            <button type="button" onClick={() => setLightbox(null)} className="absolute -top-3 -right-3 rounded-full bg-surface-raised px-2 py-1 text-xs text-foreground">✕</button>
          </div>
        </div>
      )}
    </section>
  )
}

function DocStructurePanel({ structure, title }: { structure: DocStructure; title: string }) {
  return (
    <section className="rounded-lg border border-border bg-surface-overlay/30 p-4">
      <h3 className="mb-3 text-[11px] font-semibold uppercase tracking-wider text-foreground-faint">{title}</h3>
      <div className="mb-3 grid grid-cols-3 gap-2 sm:grid-cols-6">
        {[
          { label: 'Pagina\'s', value: structure.pages, icon: Layers },
          { label: 'Images', value: structure.pictures, icon: Image },
          { label: 'Tables', value: structure.tables, icon: Table2 },
          { label: 'Headings', value: structure.headings },
          { label: 'Paragraphs', value: structure.paragraphs },
          { label: 'Text blocks', value: structure.text_blocks },
        ].map(({ label, value, icon: Icon }) => (
          <div key={label} className="rounded-md border border-border bg-surface-raised p-2 text-center">
            {Icon && <Icon className="mx-auto mb-1 h-4 w-4 text-docker" />}
            <p className="text-lg font-bold text-foreground">{value}</p>
            <p className="text-[9px] text-foreground-faint">{label}</p>
          </div>
        ))}
      </div>
      {structure.picture_details && structure.picture_details.length > 0 && (
        <div className="mb-3">
          <p className="mb-1 text-[10px] font-medium text-foreground-muted">Images ({structure.picture_details.length})</p>
          <div className="flex flex-wrap gap-1">
            {structure.picture_details.map((p) => (
              <span key={p.index} className="rounded border border-border bg-surface-raised px-2 py-0.5 text-[9px]">
                #{p.index + 1} {p.label} {p.has_image ? '🖼' : ''} {p.captions > 0 ? `(${p.captions} captions)` : ''}
              </span>
            ))}
          </div>
        </div>
      )}
      {structure.table_details && structure.table_details.length > 0 && (
        <div>
          <p className="mb-1 text-[10px] font-medium text-foreground-muted">Tables ({structure.table_details.length})</p>
          <div className="space-y-1">
            {structure.table_details.map((t) => (
              <div key={t.index} className="rounded border border-border bg-surface-raised px-2 py-1 text-[10px] text-foreground-muted">
                Table {t.index + 1}: {t.rows}×{t.cols} ({t.cells} cells) — {t.preview || '…'}
              </div>
            ))}
          </div>
        </div>
      )}
    </section>
  )
}

function normalizeChecks(items: DqCheck[]): DqCheck[] {
  return items.map((c, i) => ({ ...c, id: c.id || `${c.suite}_${i}` }))
}

function DimensionCard({ dimension: d, expanded, onToggle }: { dimension: Dimension; expanded?: boolean; onToggle?: () => void }) {
  return (
    <button type="button" onClick={onToggle} className="w-full rounded-lg border border-border bg-surface-overlay/40 p-3 text-left transition-colors hover:border-docker/30">
      <div className="mb-1 flex items-center justify-between">
        <span className="text-[12px] font-semibold">{d.label}</span>
        <span className={cn('text-base font-bold', SCORE_COLOR(d.score))}>{d.score}</span>
      </div>
      <div className="mb-2 h-2 overflow-hidden rounded-full bg-border">
        <div className={cn('h-full rounded-full', BAR_COLOR(d.score))} style={{ width: `${d.score}%` }} />
      </div>
      <p className="mb-2 text-[10px] text-foreground-faint">{d.description}</p>
      <ul className="space-y-0.5 text-[10px] text-foreground-muted">
        {d.findings.map((f) => (
          <li key={f} className="flex gap-1"><span className="text-docker">▸</span>{f}</li>
        ))}
      </ul>
      {expanded && d.recommended_actions && d.recommended_actions.length > 0 && (
        <div className="mt-2 border-t border-border pt-2">
          <p className="mb-1 text-[9px] font-semibold uppercase text-foreground-faint">Recommended actions</p>
          {d.recommended_actions.map((a) => <p key={a} className="text-[9px] text-warning">→ {a}</p>)}
        </div>
      )}
      {!expanded && d.recommended_actions && d.recommended_actions.length > 0 && (
        <p className="mt-2 border-t border-border pt-2 text-[9px] text-foreground-faint">Click for {d.recommended_actions.length} recommended action(s)</p>
      )}
    </button>
  )
}

function ColumnTable({ profiles, expandedCol, onToggle }: { profiles: ColumnProfile[]; expandedCol: string | null; onToggle: (n: string | null) => void }) {
  return (
    <div className="overflow-x-auto rounded-lg border border-border">
      <table className="w-full text-left text-[11px]">
        <thead className="bg-surface-overlay text-[10px] uppercase text-foreground-faint">
          <tr>
            <th className="px-3 py-2">Column</th><th className="px-3 py-2">Type</th><th className="px-3 py-2">Null%</th>
            <th className="px-3 py-2">Unique</th><th className="px-3 py-2">Flags</th>
          </tr>
        </thead>
        <tbody>
          {profiles.map((c) => (
            <Fragment key={c.name}>
              <tr className="cursor-pointer border-t border-border hover:bg-surface-overlay/50" onClick={() => onToggle(expandedCol === c.name ? null : c.name)}>
                <td className="px-3 py-2 font-mono text-docker">{c.name}</td>
                <td className="px-3 py-2">{c.dtype}</td>
                <td className={cn('px-3 py-2', c.null_pct > 10 && 'font-semibold text-warning')}>{c.null_pct}%</td>
                <td className="px-3 py-2">{c.unique_count.toLocaleString()}</td>
                <td className="px-3 py-2 text-foreground-muted">{c.quality_flags.join(', ') || '—'}</td>
              </tr>
              {expandedCol === c.name && (
                <tr className="border-t border-border bg-surface-overlay/20">
                  <td colSpan={5} className="px-4 py-2 text-[10px] text-foreground-muted">
                    {c.sample_values?.length ? <p className="mb-1">Samples: {c.sample_values.join(' · ')}</p> : null}
                    {c.numeric && <p>Range {c.numeric.min} – {c.numeric.max}, μ={c.numeric.mean}, {c.numeric.outliers} outliers</p>}
                    {c.text && <p>Avg len {c.text.avg_length}, {c.text.empty_strings} empty strings</p>}
                    {c.top_values?.map((tv) => <span key={tv.value} className="mr-3">{tv.value} ({tv.count})</span>)}
                  </td>
                </tr>
              )}
            </Fragment>
          ))}
        </tbody>
      </table>
    </div>
  )
}

function checkLabel(c: DqCheck): string {
  const base = c.expectation || c.name || c.metric || c.check || c.test || c.rule || c.monitor || 'check'
  return c.column ? `${base} [${c.column}]` : base
}

function checkStatus(c: DqCheck): string {
  return c.status || (c.success === false ? 'fail' : c.success ? 'pass' : c.outcome || 'unknown')
}

function checkDetail(c: DqCheck): string {
  return c.result || c.detail || ''
}

function CheckTable({ title, checks, expandedId, onToggle }: { title: string; checks: DqCheck[]; expandedId: string | null; onToggle: (id: string | null) => void }) {
  return (
    <div className="overflow-x-auto rounded-lg border border-border">
      <p className="border-b border-border bg-surface-overlay px-3 py-2 text-[11px] font-semibold capitalize">{title}</p>
      <table className="w-full text-[10px]">
        <tbody>
          {checks.map((c) => {
            const id = c.id || checkLabel(c)
            const status = checkStatus(c)
            const viol = c.violations
            return (
              <Fragment key={id}>
                <tr className="cursor-pointer border-t border-border hover:bg-surface-overlay/50" onClick={() => onToggle(expandedId === id ? null : id)}>
                  <td className="px-3 py-1.5">
                    <span className={cn('mr-2 rounded px-1.5 py-0.5 text-[9px] font-bold uppercase',
                      status === 'pass' ? 'bg-success/20 text-success' : status === 'warn' ? 'bg-warning/20 text-warning' : 'bg-danger/20 text-danger')}>
                      {status}
                    </span>
                    {checkLabel(c)}
                    {viol?.violation_count ? <span className="ml-2 text-[9px] text-danger">{viol.violation_count} violations</span> : null}
                  </td>
                  <td className="px-3 py-1.5 text-foreground-muted">{checkDetail(c)}</td>
                </tr>
                {expandedId === id && (
                  <tr className="border-t border-border bg-surface-overlay/20">
                    <td colSpan={2} className="px-4 py-3 text-[10px]">
                      <p className="mb-2 font-semibold text-docker">Error location &amp; samples</p>
                      {viol?.violation_count ? (
                        <>
                          <p className="text-foreground-muted">{viol.violation_count} rows affected ({viol.violation_pct}%){viol.location_hint ? ` · ${viol.location_hint}` : ''}</p>
                          {viol.sample_rows && viol.sample_rows.length > 0 && (
                            <div className="mt-2 overflow-x-auto rounded border border-border">
                              <table className="w-full text-[9px]">
                                <thead><tr className="bg-surface-overlay"><th className="px-2 py-1">Row #</th>{Object.keys(viol.sample_rows[0].values).map((k) => <th key={k} className="px-2 py-1">{k}</th>)}</tr></thead>
                                <tbody>
                                  {viol.sample_rows.map((s) => (
                                    <tr key={s.row_index} className="border-t border-border/50">
                                      <td className="px-2 py-1 font-mono text-warning">{s.row_index}</td>
                                      {Object.values(s.values).map((v, j) => <td key={j} className="max-w-[120px] truncate px-2 py-1">{v === null ? 'NULL' : String(v)}</td>)}
                                    </tr>
                                  ))}
                                </tbody>
                              </table>
                            </div>
                          )}
                        </>
                      ) : (
                        <p className="text-success">No row-level violations — check passed.</p>
                      )}
                      {c.meta && <pre className="mt-2 rounded bg-black/30 p-2 font-mono text-[9px]">{JSON.stringify(c.meta, null, 2)}</pre>}
                    </td>
                  </tr>
                )}
              </Fragment>
            )
          })}
        </tbody>
      </table>
    </div>
  )
}

function CapCard({ title, items, icon: Icon }: { title: string; items: string[]; icon: typeof Layers }) {
  return (
    <div className="rounded-lg border border-border bg-surface-raised/80 p-2.5">
      <div className="mb-1 flex items-center gap-1.5">
        <Icon className="h-3.5 w-3.5 text-docker" />
        <span className="text-[10px] font-semibold uppercase tracking-wide text-foreground-faint">{title}</span>
      </div>
      <p className="text-[10px] leading-relaxed text-foreground-muted">{items.join(' · ')}</p>
    </div>
  )
}

function UploadZone({ label, hint, accept, loading, onFile }: { label: string; hint: string; accept: string; loading: boolean; onFile: (f: File) => void }) {
  return (
    <label className={cn('flex cursor-pointer flex-col items-center justify-center rounded-xl border-2 border-dashed border-border/80 bg-surface-overlay/30 px-8 py-10 transition-all hover:border-docker/50 hover:bg-docker/5', loading && 'pointer-events-none opacity-50')}>
      <Upload className="mb-3 h-10 w-10 text-docker opacity-60" />
      <p className="text-[13px] font-medium text-foreground">{label}</p>
      <p className="mt-1 text-[10px] text-foreground-faint">{hint}</p>
      <input type="file" accept={accept} className="hidden" disabled={loading} onChange={(e) => e.target.files?.[0] && onFile(e.target.files[0])} />
    </label>
  )
}

function LoadingMsg({ text }: { text: string }) {
  return (
    <div className="flex items-center justify-center gap-3 rounded-lg border border-docker/30 bg-docker/5 py-10 text-sm text-foreground-muted">
      <Loader2 className="h-6 w-6 animate-spin text-docker" />
      {text}
    </div>
  )
}

function StatCard({ label, value, sub, accent, warn, icon: Icon }: { label: string; value: string; sub?: string; accent?: boolean; warn?: boolean; icon?: typeof Image }) {
  return (
    <div className="rounded-lg border border-border bg-surface-overlay/40 p-3">
      <div className="flex items-center gap-1">
        {Icon && <Icon className="h-3.5 w-3.5 text-docker" />}
        <p className="text-[9px] uppercase tracking-wider text-foreground-faint">{label}</p>
      </div>
      <p className={cn('text-xl font-bold', accent ? 'text-docker' : warn ? 'text-warning' : 'text-foreground')}>{value}</p>
      {sub && <p className="text-[10px] text-foreground-muted">{sub}</p>}
    </div>
  )
}
