const { useState, useEffect, useRef } = React

const TABS = [
  { id: 'marches', label: '1. Extraction' },
  { id: 'analyse', label: '2. Évaluation' },
  { id: 'offre', label: '3. Offre' },
  { id: 'concurrence', label: 'Concurrence' },
]

const TYPES = [
  { id: '', label: 'Tout' },
  { id: 'PLANNING', label: 'Avis de préinformation' },
  { id: 'COMPETITION', label: 'Avis de marché' },
  { id: 'AWARD', label: "Avis d'attribution" },
]

const NATURES = [
  { id: '', label: 'Toutes natures' },
  { id: 'WORKS', label: 'Travaux' },
  { id: 'SUPPLIES', label: 'Fournitures' },
  { id: 'SERVICES', label: 'Services' },
]

const LANGUAGES = [
  { id: '', label: 'Toutes langues' },
  { id: 'FR', label: 'Français' },
  { id: 'NL', label: 'Néerlandais' },
  { id: 'DE', label: 'Allemand' },
]

const NATURE_LABELS = { WORKS: 'Travaux', SUPPLIES: 'Fournitures', SERVICES: 'Services' }

const NOTICE_TYPES = {
  pmc: 'Avis de préinformation',
  'pin-only': 'Préinformation',
  'pin-profile': "Préinformation sur profil d'acheteur",
  'pin-time-limit': 'Préinformation avec réduction des délais',
  'pin-cfc-general': 'Préinformation valant appel à la concurrence',
  'pin-cfc-social': 'Préinformation valant appel à la concurrence, régime allégé',
  'cn-general': 'Avis de marché',
  'cn-social': 'Avis de marché, régime allégé',
  'cn-desg': 'Avis de concours',
  'can-general': "Avis d'attribution",
  'can-social': "Avis d'attribution, régime allégé",
  'can-desg': 'Résultat de concours',
  'contract-modification': 'Modification de marché',
}

const CELEX = {
  '32014L0023': 'Directive 2014/23/UE',
  '32014L0024': 'Directive 2014/24/UE',
  '32014L0025': 'Directive 2014/25/UE',
  '32009L0081': 'Directive 2009/81/CE',
}

const LEGAL_BASIS = {
  D23: 'Directive 2014/23/UE',
  D24: 'Directive 2014/24/UE',
  D25: 'Directive 2014/25/UE',
  D81: 'Directive 2009/81/CE',
  OTHER: 'Autre',
  IN_HOUSE: 'In house',
}

const STATUSES = [
  { id: '', label: 'Tous' },
  { id: 'true', label: 'Remise en cours' },
]

const PROCEDURE_OPTIONS = [
  { id: '', label: 'Toutes procédures' },
  { id: 'open', label: 'Ouverte' },
  { id: 'restricted', label: 'Restreinte' },
  { id: 'comp-dial', label: 'Dialogue compétitif' },
  { id: 'innovation', label: "Partenariat d'innovation" },
  { id: 'low-value-t', label: 'Faible montant' },
  { id: 'neg-w-call-24', label: 'Négociée avec publication' },
  { id: 'neg-wo-call-24', label: 'Négociée sans publication' },
  { id: 's-neg-w-call-24-81', label: 'Négociée simplifiée' },
  { id: 'neg-w-call-25', label: 'Négociée avec publication · secteurs spéciaux' },
  { id: 'neg-wo-call-25', label: 'Négociée sans publication · secteurs spéciaux' },
  { id: 's-neg-w-call-25', label: 'Négociée simplifiée · secteurs spéciaux' },
  { id: 'neg-w-call-23', label: 'Négociée avec publication · concessions' },
  { id: 'neg-wo-call-23', label: 'Négociée sans publication · concessions' },
  { id: 'neg-w-call-81', label: 'Négociée avec publication · défense' },
  { id: 'neg-wo-call-81', label: 'Négociée sans publication · défense' },
  { id: 'open-cons', label: 'Consultation ouverte' },
  { id: 'open-def', label: 'Ouverte · défense' },
  { id: 'purchase-fadps', label: 'Achat via SAD' },
  { id: 'suigen-other', label: 'Sui generis' },
  { id: 'suigen-social', label: 'Sui generis · social' },
]

const PROCEDURE_LABELS = Object.fromEntries(PROCEDURE_OPTIONS.filter((o) => o.id).map((o) => [o.id, o.label]))

const RESERVED_OPTIONS = [
  { id: '', label: 'Tous marchés' },
  { id: 'RESERVED_TO_WORKSHOPS_AND_ECONOMIC_OPERATORS', label: 'Réservé ateliers protégés' },
  { id: 'RESERVED_TO_ORGANISATIONS', label: 'Réservé à des organisations' },
  { id: 'EXECUTION_IN_FRAMEWORK_OF_SHELTERED_EMPLOYMENT_PROGRAMMES', label: 'Exécution en emploi protégé' },
]

const TECHNIQUE_OPTIONS = [
  { id: '', label: 'Toutes techniques' },
  { id: 'FRAMEWORK_AGREEMENT', label: 'Accord-cadre' },
  { id: 'DYNAMIC_PURCHASING_SYSTEM', label: "Système d'acquisition dynamique" },
  { id: 'QUALIFICATION_SYSTEM', label: 'Système de qualification' },
  { id: 'LIST_OF_SELECTED_CANDIDATES', label: 'Liste de candidats sélectionnés' },
]

const CLASS_OPTIONS = [
  { id: '', label: 'toutes classes' },
  ...Array.from({ length: 8 }, (_, i) => ({ id: String(i + 1), label: `classe ${i + 1}` })),
]

const PROCEDURES = {
  OPEN: 'Procédure ouverte',
  RESTRICTED: 'Procédure restreinte',
  NEGOTIATED_WITH_PRIOR_PUBLICATION: 'Négociée avec publication',
  NEGOTIATED_WITHOUT_PRIOR_PUBLICATION: 'Négociée sans publication',
  COMPETITIVE_DIALOGUE: 'Dialogue compétitif',
}

const fmt = (d, withTime) =>
  d
    ? new Date(d).toLocaleDateString('fr-BE', {
        day: '2-digit',
        month: 'short',
        year: 'numeric',
        ...(withTime ? { hour: '2-digit', minute: '2-digit' } : {}),
      })
    : '—'

const daysLeft = (d) => (d ? Math.ceil((new Date(d) - Date.now()) / 86400000) : null)

function Tag({ tone, children, muted }) {
  const tones = {
    teal: 'bg-teal/10 text-teal',
    green: 'bg-green/10 text-green',
    orange: 'bg-orange/10 text-orange',
    navy: 'bg-navy/5 text-black/60',
  }
  return (
    <span className={`rounded-full px-2.5 py-1 text-xs ${tones[tone]}`}>
      {children}
      {muted && <span className="ml-1.5 opacity-50">{muted}</span>}
    </span>
  )
}

const STATES = {
  EN_COURS: 'En cours',
  CLOTURE: 'Clôturé',
  ATTRIBUE: 'Attribué',
}

function State({ state }) {
  return <span>{STATES[state] ?? STATES.CLOTURE}</span>
}

function Tags({ t }) {
  const left = daysLeft(t.deadline)
  return (
    <div className="flex flex-wrap items-center gap-2">
      {t.accreditations?.map((a) => (
        <Tag key={a.category} tone="orange">
          Agréation {a.category} · classe {a.level}
        </Tag>
      ))}
      {t.cpvLabel && <Tag tone="teal" muted={t.cpv}>{t.cpvLabel}</Tag>}
      {t.nuts.map((n) => (
        <Tag key={n.code} tone="green" muted={n.label && n.code}>{n.label ?? n.code}</Tag>
      ))}
      {t.lots.length > 0 && <Tag tone="navy">{t.lots.length} lots</Tag>}
      {t.deadline && t.state === 'EN_COURS' && <Tag tone="navy">J-{left} · {fmt(t.deadline)}</Tag>}
    </div>
  )
}

const kb = (n) => (n > 1048576 ? `${(n / 1048576).toFixed(1)} MB` : `${Math.round(n / 1024)} KB`)

function Documents({ id, asOf, diff }) {
  const [data, setData] = useState(null)
  const [open, setOpen] = useState(null)

  const load = () => {
    setData(null)
    fetch(`/api/tenders/${id}/documents${asOf ? `?asOf=${encodeURIComponent(asOf)}` : ''}`)
      .then((r) => r.json())
      .then(setData)
      .catch(() => setData({ documents: [] }))
  }

  useEffect(load, [id, asOf])

  const section = (body) => (
    <section className="mt-8">
      <h3 className="text-sm font-medium text-black/40">Documents</h3>
      <div className="mt-3">{body}</div>
    </section>
  )

  if (!data) return section(<p className="text-sm text-black/40">Chargement…</p>)

  if (data.locked)
    return section(
      <div className="rounded-lg bg-orange/5 p-4">
        <p className="text-sm text-black/70">Documents hébergés sur 3P — vérification demandée pour ce dossier.</p>
        <div className="mt-3 flex gap-2">
          <button
            onClick={() => window.open(data.unlockUrl, '3p', 'width=900,height=700')}
            className="rounded-lg bg-orange px-4 py-2 text-sm font-medium text-white transition hover:opacity-90"
          >
            Débloquer
          </button>
          <button onClick={load} className="rounded-lg bg-white px-4 py-2 text-sm text-black/60 transition hover:text-black">
            J'ai terminé
          </button>
        </div>
      </div>,
    )

  if (!data.documents.length) return null

  return section(
    <div>
      {data.archive && <p className="mb-2 text-xs text-black/40">via {data.archive}</p>}

      <ul className="space-y-px">
        {(diff?.removed ?? []).map((name) => (
          <li key={name} className="flex items-center gap-3 rounded-lg bg-red-500/5 px-3 py-2 text-red-600/80">
            <span className="w-4 shrink-0 text-center text-xs">−</span>
            <span className="min-w-0 flex-1 truncate text-sm line-through">{name}</span>
          </li>
        ))}

        {data.documents.map((d) => (
          <li key={d.versionId}>
            <button
              onClick={() => setOpen(open?.versionId === d.versionId ? null : d)}
              className={`flex w-full items-center gap-3 rounded-lg px-3 py-2 text-left transition hover:bg-white ${
                d.superseded
                  ? 'bg-red-500/5 text-black/40'
                  : diff?.added?.includes(d.name) || diff?.superseded?.includes(d.name)
                    ? 'bg-green/10'
                    : ''
              } ${open?.versionId === d.versionId ? 'bg-white' : ''}`}
            >
              <span className={`w-4 shrink-0 text-center text-xs ${d.superseded ? 'text-red-600/60' : 'text-green'}`}>
                {d.superseded ? '−' : diff?.added?.includes(d.name) ? '+' : diff?.superseded?.includes(d.name) ? '±' : ''}
              </span>
              <span className="rounded bg-navy/5 px-1.5 py-1 text-[10px] font-medium text-black/50">{d.kind}</span>
              <span className={`min-w-0 flex-1 truncate text-sm ${d.superseded ? 'line-through' : ''}`}>{d.name}</span>
              <span className="text-xs text-black/30">{d.size ? kb(d.size) : ''}</span>
            </button>
          </li>
        ))}
      </ul>

      {open && (
        <div className="mt-3 overflow-hidden rounded-lg bg-white">
          <div className="flex items-center justify-between gap-4 px-4 py-3">
            <span className="min-w-0 truncate text-sm font-medium">{open.name}</span>
            <a
              href={`/api/documents/${open.versionId}`}
              target="_blank"
              rel="noreferrer"
              className="shrink-0 text-xs text-black/50 transition hover:text-black"
            >
              Ouvrir ↗
            </a>
          </div>

          {open.kind === 'PDF' ? (
            <iframe src={`/api/documents/${open.versionId}`} className="h-[70vh] w-full" title={open.name} />
          ) : (
            <div className="px-4 py-8 text-center text-sm text-black/50">
              Aperçu indisponible —{' '}
              <a href={`/api/documents/${open.versionId}`} className="text-black underline">
                télécharger
              </a>
            </div>
          )}
        </div>
      )}
    </div>
  )
}

const euro = (n) =>
  n == null ? null : n.toLocaleString('fr-BE', { style: 'currency', currency: 'EUR', maximumFractionDigits: 2 })

const COMPANY_SIZES = {
  micro: 'Micro-entreprise',
  small: 'Petite entreprise',
  medium: 'Moyenne entreprise',
  sme: 'PME',
  large: 'Grande entreprise',
}

function Award({ id }) {
  const [data, setData] = useState(null)

  useEffect(() => {
    setData(null)
    fetch(`/api/tenders/${id}/award`)
      .then((r) => r.json())
      .then(setData)
      .catch(() => setData({ lots: [], notice: null }))
  }, [id])

  if (!data) return <p className="py-2 text-sm text-black/40">Chargement…</p>
  if (!data.lots.length) return <p className="py-2 text-sm text-black/40">Aucun résultat publié.</p>

  return (
    <div>
      {data.notice && (data.notice.totalAmount != null || data.notice.justification) && (
        <dl className="mb-3 space-y-1 text-sm">
          {data.notice.totalAmount != null && (
            <CardRow label="Montant total">{euro(data.notice.totalAmount)}</CardRow>
          )}
          {data.notice.justification && (
            <CardRow label="Justification">
              {data.notice.justification}
              {data.notice.justificationDetail && (
                <span className="ml-2 text-black/40">{data.notice.justificationDetail}</span>
              )}
            </CardRow>
          )}
        </dl>
      )}

      <ol className="space-y-2">
        {data.lots.map((l) => (
          <li key={l.lot} className="rounded-lg bg-surface p-4">
            <div className="flex items-baseline justify-between gap-4">
              <span className="min-w-0 flex-1 text-sm font-medium">
                {l.lotNumber && <span className="mr-2 text-black/35">Lot {l.lotNumber}</span>}
                {l.project?.title ?? l.lotTitle ?? l.lot}
              </span>
              <span className="shrink-0 text-xs text-black/40">{l.resultLabel}</span>
            </div>

            <dl className="mt-2 space-y-1 text-sm">
              {l.received != null && <CardRow label="Offres reçues">{l.received}</CardRow>}
              {l.candidates != null && <CardRow label="Candidatures">{l.candidates}</CardRow>}
              {(l.lowerAmount != null || l.higherAmount != null) && (
                <CardRow label="Fourchette">
                  {euro(l.lowerAmount) ?? '—'} → {euro(l.higherAmount) ?? '—'}
                </CardRow>
              )}
              {l.noAwardReason && <CardRow label="Motif">{l.noAwardReason}</CardRow>}
              {(l.tender?.organisation?.name ?? l.tender?.winner) && (
                <CardRow label="Attributaire">
                  {l.tender.organisation?.name ?? l.tender.winner}
                  {l.tender.organisation?.companyId && (
                    <span className="ml-2 text-black/35">{l.tender.organisation.companyId.split('_')[0]}</span>
                  )}
                  {l.tender.organisation?.city && (
                    <span className="ml-2 text-black/35">{l.tender.organisation.city}</span>
                  )}
                </CardRow>
              )}
              {l.tender?.organisation?.size && (
                <CardRow label="Taille">
                  {COMPANY_SIZES[l.tender.organisation.size] ?? l.tender.organisation.size}
                </CardRow>
              )}
              {l.tender?.amount != null && <CardRow label="Montant">{euro(l.tender.amount)}</CardRow>}
              {l.frameworkMax != null && <CardRow label="Accord-cadre">max. {euro(l.frameworkMax)}</CardRow>}
              {l.project?.duration && (
                <CardRow label="Durée">
                  {l.project.duration}
                  {l.project.startDate && (
                    <span className="ml-2 text-black/35">dès le {fmt(l.project.startDate)}</span>
                  )}
                </CardRow>
              )}
              {l.project?.cpv && (
                <CardRow label="CPV du lot">
                  {l.project.cpv.label ?? l.project.cpv.code}
                  {l.project.cpv.label && <span className="ml-2 text-black/35">{l.project.cpv.code}</span>}
                </CardRow>
              )}
              {l.tender?.variant && <CardRow label="Variante">offre variante retenue</CardRow>}
              {l.tender?.subcontracted && <CardRow label="Sous-traitance">déclarée</CardRow>}
            </dl>
          </li>
        ))}
      </ol>

    </div>
  )
}

const labelOf = (r) => (r.kind === 'RECTIFICATIF' ? `Rectificatif ${r.rank - 1}` : 'Publication initiale')

function groupByNotice(publications) {
  const groups = []
  publications.forEach((row, index) => {
    const last = groups[groups.length - 1]
    if (last && last.noticeId === row.noticeId) last.items.push({ row, index })
    else groups.push({ noticeId: row.noticeId, phaseLabel: row.phaseLabel, items: [{ row, index }] })
  })
  return groups
}

function CardRow({ label, children }) {
  return (
    <div className="flex gap-4">
      <dt className="w-24 shrink-0 text-black/35">{label}</dt>
      <dd className="min-w-0 flex-1 truncate text-black/70">{children}</dd>
    </div>
  )
}

function Row({ label, children }) {
  return (
    <div className="flex gap-6 py-2.5">
      <dt className="w-40 shrink-0 text-black/40">{label}</dt>
      <dd className="min-w-0 flex-1">{children}</dd>
    </div>
  )
}

function Detail({ t, onBack }) {
  const [rows, setRows] = useState(null)
  const [pick, setPick] = useState(0)
  const [diff, setDiff] = useState(null)
  const [notice, setNotice] = useState(null)

  useEffect(() => {
    setRows(null)
    setPick(0)
    fetch(`/api/tenders/${t.id}/history`)
      .then((r) => r.json())
      .then((h) => {
        const list = Array.isArray(h) && h.length ? h : null
        setRows(list)
        if (list) setPick(list.length - 1)
      })
      .catch(() => setRows(null))
  }, [t.id])

  const fallback = [{ noticeId: t.id, phaseLabel: t.phaseLabel, kind: 'INITIALE', rank: 1, of: 1, publishedAt: t.publicationDate, deadline: t.deadline }]
  const publications = rows ?? fallback
  const current = publications[pick] ?? publications[0]
  const previous = pick > 0 && publications[pick - 1].noticeId === publications[pick].noticeId ? publications[pick - 1] : null
  const deadline = current.deadline ?? (rows ? null : t.deadline)

  useEffect(() => {
    setDiff(null)
    if (!previous) return
    fetch(`/api/tenders/${current.noticeId}/changes?from=${encodeURIComponent(previous.publishedAt)}&to=${encodeURIComponent(current.publishedAt)}`)
      .then((r) => r.json())
      .then(setDiff)
      .catch(() => setDiff(null))
  }, [current.noticeId, previous?.publishedAt, current.publishedAt])

  useEffect(() => {
    setNotice(null)
    const version = current.versionId ? `?version=${encodeURIComponent(current.versionId)}` : ''
    fetch(`/api/tenders/${current.noticeId}/notice${version}`)
      .then((r) => r.json())
      .then((n) => setNotice(n && n.noticeId ? n : null))
      .catch(() => setNotice(null))
  }, [current.noticeId, current.versionId])

  return (
    <div>
      <button onClick={onBack} className="text-sm text-black/50 transition hover:text-black">
        ← Retour aux résultats
      </button>

      <h2 className="mt-6 text-2xl font-bold leading-snug">{notice?.title ?? t.title}</h2>

      <dl className="mt-6 text-sm">
        <Row label="Statut">
          <State state={t.state} />
        </Row>

        <Row label="Acheteur">
          {(t.buyerPath ?? []).map((node, i) => (
            <span key={i}>
              {node} <span className="text-black/25">›</span>{' '}
            </span>
          ))}
          {t.buyer}
          {t.buyerId && <span className="ml-2 text-black/35">{t.buyerId}</span>}
        </Row>

        <Row label="N° de dossier">{notice?.dossierNumber ?? t.dossierNumber ?? '—'}</Row>

        <Row label="Nature">{NATURE_LABELS[t.nature] ?? t.nature ?? '—'}</Row>

        {t.accreditations?.length > 0 && (
          <Row label="Agréation">{t.accreditations.map((a) => `${a.category} classe ${a.level}`).join(', ')}</Row>
        )}

        {t.enterpriseCategories?.length > 0 && (
          <Row label="Classes admises">
            {t.enterpriseCategories.map((c) => `${c.category} : classes ${c.levels.join(', ')}`).join(' · ')}
          </Row>
        )}
      </dl>

      <ol className="mt-10 space-y-6">
        {groupByNotice(publications).map((g) => (
          <li key={g.noticeId}>
            <div className="text-base font-medium text-black/50">{g.phaseLabel}</div>

            <ol className="mt-2 space-y-3 pl-5">
              {g.items.map(({ row, index }) => (
                <li key={index}>
                  <button onClick={() => setPick(index)} className="block w-full text-left">
                    <span className={index === pick ? 'text-lg font-bold' : 'text-base text-black/40 transition hover:text-black'}>
                      {labelOf(row)}
                    </span>
                    <span className={`ml-3 text-sm ${index === pick ? 'text-black/50' : 'text-black/30'}`}>
                      {fmt(row.publishedAt)}
                    </span>
                  </button>
                </li>
              ))}
            </ol>
          </li>
        ))}
      </ol>

      <dl className="mt-8 text-sm">
        <Row label="Type d'avis">
          {NOTICE_TYPES[notice?.noticeType] ?? notice?.noticeType ?? '—'}
          {notice?.noticeType && <span className="ml-2 text-black/35">{notice.noticeType}</span>}
          {(notice?.noticeSubType ?? t.noticeSubType) && (
            <span className="ml-2 text-black/35">{notice?.noticeSubType ?? t.noticeSubType}</span>
          )}
        </Row>

        <Row label="Référence">{notice?.reference ?? current.reference ?? t.reference ?? '—'}</Row>

        <Row label="Procédure">
          {PROCEDURE_LABELS[notice?.procedureType] ?? PROCEDURES[t.procedureType] ?? t.procedureType ?? '—'}
          {notice?.procedureType && <span className="ml-2 text-black/35">{notice.procedureType}</span>}
        </Row>

        <Row label="Cadre légal">
          {CELEX[notice?.legalBasis] ?? LEGAL_BASIS[t.legalBasis] ?? '—'}
          <span className="ml-2 text-black/35">{notice?.legalBasis ?? t.legalBasis}</span>
        </Row>

        {notice?.technique && (
          <Row label="Technique">
            {TECHNIQUE_OPTIONS.find((o) => o.id === notice.technique)?.label ?? notice.technique}
          </Row>
        )}

        <Row label="Langues">{(notice?.languages ?? t.languages)?.join(', ') || '—'}</Row>

        <Row label="Publication">{fmt(notice?.publishedAt ?? current.publishedAt)}</Row>

        <Row label="Envoi">{fmt(notice?.dispatchDate ?? t.dispatchDate)}</Row>

        {deadline && (
          <Row label="Remise">
            {current.deadlineMoved && (
              <span className="mr-2 text-red-600/70 line-through">{fmt(current.deadlineMoved.from, true)}</span>
            )}
            <span className={current.deadlineMoved ? 'text-green' : ''}>{fmt(deadline, true)}</span>
            <span className="ml-3">
              <Countdown to={deadline} />
            </span>
          </Row>
        )}

        <Row label="Lieu">
          {t.nuts.length
            ? t.nuts.map((n, i) => (
                <span key={n.code}>
                  {i > 0 && ', '}
                  {n.label ?? n.code} <span className="text-black/35">{n.code}</span>
                </span>
              ))
            : '—'}
        </Row>

        <Row label="CPV">
          {notice?.cpv?.label ?? t.cpvLabel} <span className="text-black/35">{notice?.cpv?.code ?? t.cpv}</span>
        </Row>

        {t.cpvExtra?.length > 0 && (
          <Row label="CPV additionnels">
            {t.cpvExtra.map((c, i) => (
              <span key={c.code}>
                {i > 0 && ', '}
                {c.label ?? c.code}
                {c.label && <span className="ml-1.5 text-black/35">{c.code}</span>}
              </span>
            ))}
          </Row>
        )}

        {t.bdaReferences?.length > 0 && <Row label="Référence BDA">{t.bdaReferences.join(', ')}</Row>}

        {t.tedReferences?.length > 0 && <Row label="Référence TED">{t.tedReferences.join(', ')}</Row>}
      </dl>

      {(notice?.description ?? t.description) && (
        <section className="mt-8">
          <h3 className="text-sm font-medium text-black/40">Objet</h3>
          <p className="mt-2 whitespace-pre-line text-sm leading-relaxed text-black/80">
            {notice?.description ?? t.description}
          </p>
        </section>
      )}

      {(notice?.lots ?? t.lots).length > 0 && (
        <section className="mt-8">
          <h3 className="text-sm font-medium text-black/40">Lots</h3>
          <ol className="mt-3 space-y-2">
            {(notice?.lots ?? t.lots).map((l, i) => (
              <li key={i} className="rounded-lg bg-surface p-4">
                <div className="text-sm font-medium">
                  {l.number && <span className="mr-2 text-black/35">Lot {l.number}</span>}
                  {l.title}
                </div>
                {l.description && l.description !== l.title && (
                  <p className="mt-1 text-sm text-black/60">{l.description}</p>
                )}
                {(l.reservedParticipation || l.reservedExecution) && (
                  <p className="mt-1 text-xs text-orange">Marché réservé</p>
                )}
              </li>
            ))}
          </ol>
        </section>
      )}

      {t.phase === 'AWARD' && (
        <section className="mt-8">
          <h3 className="text-sm font-medium text-black/40">Résultats</h3>
          <div className="mt-3">
            <Award id={current.noticeId} />
          </div>
        </section>
      )}

      <Documents id={current.noticeId} asOf={current.publishedAt} diff={diff} />
    </div>
  )
}

function Countdown({ to }) {
  const [now, setNow] = useState(Date.now())

  useEffect(() => {
    const id = setInterval(() => setNow(Date.now()), 1000)
    return () => clearInterval(id)
  }, [])

  const left = new Date(to) - now
  if (left <= 0) return <span className="text-black/35">clôturé</span>

  const days = Math.floor(left / 86400000)
  const pad = (n) => String(n).padStart(2, '0')
  const clock = `${pad(Math.floor(left / 3600000) % 24)}:${pad(Math.floor(left / 60000) % 60)}:${pad(Math.floor(left / 1000) % 60)}`

  return (
    <span className="tabular-nums text-black/35">
      {days > 0 && `${days} j `}
      {clock}
    </span>
  )
}

function Popover({ anchor, field, children }) {
  const [rect, setRect] = useState(null)

  useEffect(() => {
    const measure = () => {
      const el = anchor.current
      if (!el) return
      const target = field ? (el.closest('[data-field]') ?? el) : el
      setRect(target.getBoundingClientRect())
    }
    measure()
    window.addEventListener('scroll', measure, true)
    window.addEventListener('resize', measure)
    return () => {
      window.removeEventListener('scroll', measure, true)
      window.removeEventListener('resize', measure)
    }
  }, [anchor, field])

  if (!rect) return null

  return ReactDOM.createPortal(
    <div
      style={{
        position: 'fixed',
        left: rect.left,
        top: rect.bottom + 4,
        width: rect.width,
        maxHeight: Math.max(160, window.innerHeight - rect.bottom - 24),
      }}
      className="z-50 overflow-y-auto overscroll-contain rounded-xl bg-white p-1 shadow-2xl"
    >
      {children}
    </div>,
    document.body,
  )
}

const TREE_KINDS = new Set(['cpv', 'nuts', 'categories', 'organisations'])

function Node({ kind, node, depth, onPick }) {
  const [open, setOpen] = useState(false)
  const [children, setChildren] = useState(null)

  const toggle = (e) => {
    e.preventDefault()
    setOpen(!open)
    if (!children)
      fetch(`/api/refs/${kind}?parent=${encodeURIComponent(node.code)}`)
        .then((r) => r.json())
        .then((list) => setChildren(Array.isArray(list) ? list : []))
        .catch(() => setChildren([]))
  }

  return (
    <li>
      <div className="flex items-start" style={{ paddingLeft: `${depth * 14}px` }}>
        {node.hasChildren ? (
          <button
            type="button"
            onMouseDown={toggle}
            className="mt-1.5 flex size-5 shrink-0 items-center justify-center rounded text-[10px] text-black/30 transition hover:bg-black/5 hover:text-black"
          >
            {open ? '▾' : '▸'}
          </button>
        ) : (
          <span className="size-5 shrink-0" />
        )}

        <button
          type="button"
          onMouseDown={(e) => {
            e.preventDefault()
            onPick(node)
          }}
          className="flex min-w-0 flex-1 items-start gap-3 rounded px-2 py-1.5 text-left text-sm transition hover:bg-surface"
        >
          <span className="shrink-0 tabular-nums text-black/35">{node.code}</span>
          <span className="min-w-0 flex-1 break-words">{node.label}</span>
        </button>
      </div>

      {open && children && (
        <ul>
          {children.map((c) => (
            <Node key={c.code} kind={kind} node={c} depth={depth + 1} onPick={onPick} />
          ))}
        </ul>
      )}
    </li>
  )
}

function Chip({ value, onChange, onRemove }) {
  const anchor = useRef(null)
  const [open, setOpen] = useState(false)
  const current = CLASS_OPTIONS.find((o) => o.id === (value.level ?? ''))

  return (
    <span ref={anchor} className="inline-flex items-center gap-1.5 rounded-full bg-navy px-2.5 py-1 text-xs text-white">
      <span className="tabular-nums">{value.code}</span>
      {value.label && <span className="text-white/70">{value.label}</span>}

      <button
        type="button"
        onMouseDown={(e) => {
          e.preventDefault()
          setOpen(!open)
        }}
        className="rounded text-white/80 transition hover:text-white"
      >
        {current?.label} ⌄
      </button>

      {open && (
        <Popover anchor={anchor}>
          <ul>
            {CLASS_OPTIONS.map((o) => (
              <li key={o.id}>
                <button
                  type="button"
                  onMouseDown={(e) => {
                    e.preventDefault()
                    onChange({ ...value, level: o.id })
                    setOpen(false)
                  }}
                  className={`w-full rounded px-3 py-1.5 text-left text-sm transition hover:bg-surface ${
                    o.id === (value.level ?? '') ? 'font-medium' : 'text-black/60'
                  }`}
                >
                  {o.label}
                </button>
              </li>
            ))}
          </ul>
        </Popover>
      )}

      <button type="button" onMouseDown={(e) => e.preventDefault()} onClick={onRemove} className="text-white/80 hover:text-white">
        ×
      </button>
    </span>
  )
}

function Select({ label, kind, values, onChange, levels }) {
  const [query, setQuery] = useState('')
  const [options, setOptions] = useState([])
  const [open, setOpen] = useState(false)
  const tree = TREE_KINDS.has(kind)

  useEffect(() => {
    const id = setTimeout(() => {
      const url = query
        ? `/api/refs/${kind}?q=${encodeURIComponent(query)}`
        : `/api/refs/${kind}${tree ? '?tree=true' : ''}`
      fetch(url)
        .then((r) => r.json())
        .then((list) => setOptions(Array.isArray(list) ? list : []))
        .catch(() => setOptions([]))
    }, 200)
    return () => clearTimeout(id)
  }, [kind, query, tree])

  const pick = (option) => {
    if (!values.some((v) => v.code === option.code)) onChange([...values, option])
    setQuery('')
  }

  const anchor = useRef(null)

  return (
    <div className="relative">
      <div ref={anchor} className="flex flex-wrap items-center gap-1.5 rounded-lg bg-surface px-3 py-2">
        <span className="shrink-0 text-xs leading-5 text-black/40">{label}</span>

        {values.map((v) =>
          levels ? (
            <Chip
              key={v.code}
              value={v}
              onChange={(next) => onChange(values.map((x) => (x.code === v.code ? next : x)))}
              onRemove={() => onChange(values.filter((x) => x.code !== v.code))}
            />
          ) : (
            <button
              key={v.code}
              type="button"
              onClick={() => onChange(values.filter((x) => x.code !== v.code))}
              className="rounded-full bg-navy px-2.5 py-1 text-left text-xs leading-4 text-white"
            >
              <span className="tabular-nums">{v.code}</span>
              {v.label && <span className="ml-1.5 text-white/70">{v.label}</span>}
              <span className="ml-1.5">×</span>
            </button>
          ),
        )}

        <input
          value={query}
          onChange={(e) => {
            setQuery(e.target.value)
            setOpen(true)
          }}
          onFocus={() => setOpen(true)}
          onBlur={() => setTimeout(() => setOpen(false), 150)}
          placeholder="rechercher…"
          className="min-w-32 flex-1 bg-transparent text-sm outline-none placeholder:text-black/25"
        />
      </div>

      {open && options.length > 0 && (
        <Popover anchor={anchor}>
          <ul>
            {tree ? (
              options.map((o) => <Node key={o.code} kind={kind} node={o} depth={0} onPick={pick} />)
            ) : (
              options.map((o) => (
                <li key={o.code}>
                  <button
                    type="button"
                    onMouseDown={(e) => {
                      e.preventDefault()
                      pick(o)
                    }}
                    className="flex w-full items-start gap-3 rounded px-3 py-2 text-left text-sm transition hover:bg-surface"
                  >
                    <span className="shrink-0 tabular-nums text-black/35">{o.code}</span>
                    <span className="min-w-0 flex-1 break-words">{o.label}</span>
                  </button>
                </li>
              ))
            )}
          </ul>
        </Popover>
      )}
    </div>
  )
}

function Dropdown({ value, options, onChange }) {
  const [open, setOpen] = useState(false)
  const current = options.find((o) => o.id === value)
  const anchor = useRef(null)

  return (
    <div ref={anchor} className="relative">
      <button
        type="button"
        onClick={() => setOpen(!open)}
        onBlur={() => setTimeout(() => setOpen(false), 150)}
        className="flex w-full items-center justify-between gap-2 text-left text-sm"
      >
        {current?.label}
        <span className="text-black/30">⌄</span>
      </button>

      {open && (
        <Popover anchor={anchor} field>
        <ul>
          {options.map((o) => (
            <li key={o.id}>
              <button
                type="button"
                onMouseDown={() => {
                  onChange(o.id)
                  setOpen(false)
                }}
                className={`w-full rounded px-3 py-2 text-left text-sm transition hover:bg-surface ${
                  o.id === value ? 'font-medium' : 'text-black/60'
                }`}
              >
                {o.label}
              </button>
            </li>
          ))}
        </ul>
        </Popover>
      )}
    </div>
  )
}

function DateField({ label, value, onChange }) {
  return (
    <Field label={label}>
      <input
        type="date"
        value={value}
        onChange={(e) => onChange(e.target.value)}
        className="w-full bg-transparent text-sm outline-none"
      />
    </Field>
  )
}

function Field({ label, children }) {
  return (
    <div data-field className="flex items-center gap-2 rounded-lg bg-surface px-3 py-2">
      <span className="shrink-0 text-xs leading-5 text-black/40">{label}</span>
      <div className="min-w-0 flex-1">{children}</div>
    </div>
  )
}

function useDossierStates(results) {
  const [states, setStates] = useState({})

  useEffect(() => {
    setStates({})
    if (!results?.length) return
    let stop = false
    const queue = [...results]

    const worker = async () => {
      while (queue.length && !stop) {
        const t = queue.shift()
        try {
          const s = await (await fetch(`/api/tenders/${t.id}/state`)).json()
          if (!stop && s.state) setStates((prev) => ({ ...prev, [t.id]: s }))
        } catch {}
      }
    }

    Array.from({ length: 4 }, worker)
    return () => {
      stop = true
    }
  }, [results])

  return states
}

function TenderCard({ t, state, onClick, footer }) {
  return (
    <div className="rounded-xl transition hover:bg-surface">
      <button onClick={onClick} className="block w-full p-5 text-left">
      <div className="flex items-baseline justify-between gap-4">
        <h3 className="font-medium leading-snug">
          <span className="mr-2 font-normal text-black/35">{t.phaseLabel}</span>
          {t.title || t.reference}
        </h3>

        <span className="flex shrink-0 items-center gap-3 text-xs">
          <State state={state?.state ?? t.state} />
          <span className="text-black/35">{fmt(t.publicationDate)}</span>
        </span>
      </div>

      {t.description && <p className="mt-2 line-clamp-2 text-sm text-black/50">{t.description}</p>}

      <dl className="mt-3 space-y-1 text-sm">
        <CardRow label="Acheteur">
          {t.buyer}
          {t.buyerId && <span className="ml-2 text-black/35">{t.buyerId}</span>}
        </CardRow>
        <CardRow label="Lieu">
          {t.nuts.length
            ? t.nuts.map((n, i) => (
                <span key={n.code}>
                  {i > 0 && ', '}
                  {n.label ?? n.code}
                  {n.label && <span className="ml-1.5 text-black/35">{n.code}</span>}
                </span>
              ))
            : '—'}
        </CardRow>
        {t.deadline && (
          <CardRow label="Remise">
            {fmt(t.deadline)}
            <span className="ml-3">
        <Countdown to={t.deadline} />
            </span>
          </CardRow>
        )}
        {t.accreditations?.length > 0 && (
          <CardRow label="Agréation">
            {t.accreditations.map((a) => `${a.category} classe ${a.level}`).join(', ')}
          </CardRow>
        )}
      </dl>
      </button>

      {footer && <div className="px-5 pb-5">{footer}</div>}
    </div>
  )
}

function Filters(f) {
  return (
    <div className="space-y-2">
      <Field label="Mot-clés">
        <input
          value={f.query}
          onChange={(e) => f.setQuery(e.target.value)}
          placeholder="terrassement, câbles, égouttage…"
          className="w-full bg-transparent text-sm outline-none placeholder:text-black/25"
        />
      </Field>

      <Select label="Lieu" kind="nuts" values={f.lieux} onChange={f.setLieux} />
      <Select label="CPV" kind="cpv" values={f.metiers} onChange={f.setMetiers} />
      <Select label="Acheteur" kind="organisations" values={f.buyers} onChange={f.setBuyers} />
      <Select label="Agréation" kind="categories" values={f.agreations} onChange={f.setAgreations} levels />

      <Field label="Avis">
        <Dropdown value={f.type} options={TYPES} onChange={f.setType} />
      </Field>

      <Field label="Nature">
        <Dropdown value={f.nature} options={NATURES} onChange={f.setNature} />
      </Field>

      <Field label="Procédure">
        <Dropdown value={f.procedure} options={PROCEDURE_OPTIONS} onChange={f.setProcedure} />
      </Field>

      <Field label="Technique">
        <Dropdown value={f.technique} options={TECHNIQUE_OPTIONS} onChange={f.setTechnique} />
      </Field>

      <Field label="Réservé">
        <Dropdown value={f.reserved} options={RESERVED_OPTIONS} onChange={f.setReserved} />
      </Field>

      <Field label="Langue">
        <Dropdown value={f.language} options={LANGUAGES} onChange={f.setLanguage} />
      </Field>

      <Field label="Statut">
        <Dropdown value={f.activeOnly} options={STATUSES} onChange={f.setActiveOnly} />
      </Field>

      <div className="grid grid-cols-2 gap-2">
        <DateField label="Publié du" value={f.publishedFrom} onChange={f.setPublishedFrom} />
        <DateField label="au" value={f.publishedTo} onChange={f.setPublishedTo} />
        <DateField label="Envoyé du" value={f.dispatchedFrom} onChange={f.setDispatchedFrom} />
        <DateField label="au" value={f.dispatchedTo} onChange={f.setDispatchedTo} />
        <DateField label="Remise du" value={f.deadlineFrom} onChange={f.setDeadlineFrom} />
        <DateField label="au" value={f.deadlineTo} onChange={f.setDeadlineTo} />
      </div>
    </div>
  )
}

const RESULTS_PER_PAGE = 25

function Pager({ page, total, onPage }) {
  const pages = Math.ceil(total / RESULTS_PER_PAGE)
  if (pages <= 1) return null

  const go = (next) => {
    onPage(next)
    window.scrollTo({ top: 0, behavior: 'smooth' })
  }

  const button = (label, next, disabled) => (
    <button
      type="button"
      disabled={disabled}
      onClick={() => go(next)}
      className="rounded-lg bg-surface px-4 py-2 text-sm transition enabled:hover:bg-black/5 disabled:opacity-30"
    >
      {label}
    </button>
  )

  return (
    <div className="mt-6 flex items-center justify-between gap-4">
      <span className="text-sm text-black/40">
        {(page - 1) * RESULTS_PER_PAGE + 1}–{Math.min(page * RESULTS_PER_PAGE, total)} sur{' '}
        {total.toLocaleString('fr-BE')}
      </span>

      <div className="flex items-center gap-2">
        {button('← Précédent', page - 1, page <= 1)}
        <span className="px-2 text-sm text-black/40">
          {page} / {pages.toLocaleString('fr-BE')}
        </span>
        {button('Suivant →', page + 1, page >= pages)}
      </div>
    </div>
  )
}

function Extraction() {
  const [query, setQuery] = useState('')
  const [terms, setTerms] = useState('')
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(false)
  const [selected, setSelected] = useState(null)
  const [lieux, setLieux] = useState([])
  const [metiers, setMetiers] = useState([])
  const [buyers, setBuyers] = useState([])
  const [agreations, setAgreations] = useState([])
  const [technique, setTechnique] = useState('')
  const [reserved, setReserved] = useState('')
  const [type, setType] = useState('')
  const [nature, setNature] = useState('')
  const [language, setLanguage] = useState('')
  const [procedure, setProcedure] = useState('')
  const [activeOnly, setActiveOnly] = useState('')
  const [publishedFrom, setPublishedFrom] = useState('')
  const [publishedTo, setPublishedTo] = useState('')
  const [dispatchedFrom, setDispatchedFrom] = useState('')
  const [dispatchedTo, setDispatchedTo] = useState('')
  const [deadlineFrom, setDeadlineFrom] = useState('')
  const [deadlineTo, setDeadlineTo] = useState('')
  const [page, setPage] = useState(1)
  const states = useDossierStates(data?.results)

  const run = (next = 1) => {
    setLoading(true)
    setSelected(null)
    setPage(next)

    const params = new URLSearchParams({ q: terms, page: String(next), pageSize: String(RESULTS_PER_PAGE) })
    if (lieux.length) params.set('nuts', lieux.map((l) => l.code).join(','))
    if (metiers.length) params.set('cpv', metiers.map((m) => m.code).join(','))
    if (buyers.length) params.set('organisations', buyers.map((b) => b.code).join(','))
    if (agreations.length)
      params.set(
        'accreditations',
        agreations
          .flatMap((a) => (a.level ? [`${a.code}_${a.level}`] : CLASS_OPTIONS.slice(1).map((c) => `${a.code}_${c.id}`)))
          .join(','),
      )
    if (technique) params.set('techniques', technique)
    if (reserved) params.set('reserved', reserved)
    if (type) params.set('phase', type)
    if (nature) params.set('natures', nature)
    if (language) params.set('languages', language)
    if (procedure) params.set('procedures', procedure)
    if (activeOnly) params.set('activeOnly', activeOnly)
    if (publishedFrom) params.set('publishedFrom', publishedFrom)
    if (publishedTo) params.set('publishedTo', publishedTo)
    if (dispatchedFrom) params.set('dispatchedFrom', dispatchedFrom)
    if (dispatchedTo) params.set('dispatchedTo', dispatchedTo)
    if (deadlineFrom) params.set('deadlineFrom', deadlineFrom)
    if (deadlineTo) params.set('deadlineTo', deadlineTo)

    fetch(`/api/tenders?${params}`)
      .then((r) => r.json())
      .then(setData)
      .finally(() => setLoading(false))
  }

  useEffect(() => {
    const id = setTimeout(() => setTerms(query), 350)
    return () => clearTimeout(id)
  }, [query])

  useEffect(run, [
    terms,
    lieux,
    metiers,
    buyers,
    agreations,
    technique,
    reserved,
    type,
    nature,
    language,
    procedure,
    activeOnly,
    publishedFrom,
    publishedTo,
    dispatchedFrom,
    dispatchedTo,
    deadlineFrom,
    deadlineTo,
  ])

  if (selected) return <Detail t={selected} onBack={() => setSelected(null)} />

  return (
    <div className="flex gap-8">
      <aside className="sticky top-24 max-h-[calc(100vh-7rem)] w-96 shrink-0 self-start overflow-y-auto overscroll-contain pr-1">
      <div>
        <Filters
          query={query}
          setQuery={setQuery}
          lieux={lieux}
          setLieux={setLieux}
          metiers={metiers}
          setMetiers={setMetiers}
          buyers={buyers}
          setBuyers={setBuyers}
          agreations={agreations}
          setAgreations={setAgreations}
          technique={technique}
          setTechnique={setTechnique}
          reserved={reserved}
          setReserved={setReserved}
          type={type}
          setType={setType}
          nature={nature}
          setNature={setNature}
          language={language}
          setLanguage={setLanguage}
          procedure={procedure}
          setProcedure={setProcedure}
          activeOnly={activeOnly}
          setActiveOnly={setActiveOnly}
          publishedFrom={publishedFrom}
          setPublishedFrom={setPublishedFrom}
          publishedTo={publishedTo}
          setPublishedTo={setPublishedTo}
          dispatchedFrom={dispatchedFrom}
          setDispatchedFrom={setDispatchedFrom}
          dispatchedTo={dispatchedTo}
          setDispatchedTo={setDispatchedTo}
          deadlineFrom={deadlineFrom}
          setDeadlineFrom={setDeadlineFrom}
          deadlineTo={deadlineTo}
          setDeadlineTo={setDeadlineTo}
        />
      </div>
      </aside>

      <div className="min-w-0 flex-1">
      <div className="text-sm text-black/40">
        {loading
          ? 'Recherche…'
          : data
            ? `${data.total.toLocaleString('fr-BE')} publications`
            : ''}
      </div>

      <div className="mt-2 -mx-5 space-y-2">
        {(data?.results ?? []).map((t) => (
          <TenderCard key={t.id} t={t} state={states[t.id]} onClick={() => setSelected(t)} />
        ))}
      </div>

      {data && <Pager page={page} total={data.total} onPage={run} />}
      </div>
    </div>
  )
}

const iso = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`

const thisMonday = () => {
  const d = new Date()
  d.setDate(d.getDate() - ((d.getDay() + 6) % 7))
  return iso(d)
}

const PAGE_SIZE = 100

const METUBEL_PROFILE = {
  accreditations: [
    { category: 'C', level: 5 },
    { category: 'G', level: 5 },
    { category: 'C1', level: 4 },
    { category: 'G5', level: 3 },
  ],
  cpv: ['45112000-5', '45232400-6', '45233000-9', '45231000-5', '45111100-9', '45232450-1'],
  nuts: ['BE32', 'BE35', 'BE31'],
  amount: { min: 150000, max: 1800000 },
  languages: ['FR'],
}

async function fetchDay(day, stopped) {
  const all = []
  let total = 0

  for (let page = 1; !stopped(); page++) {
    const params = new URLSearchParams({
      page: String(page),
      pageSize: String(PAGE_SIZE),
      publishedFrom: day,
      publishedTo: day,
    })
    const chunk = await (await fetch(`/api/tenders?${params}`)).json()
    total = chunk.total ?? 0
    all.push(...(chunk.results ?? []))
    if (!chunk.results?.length || all.length >= total) break
  }

  return { results: all.filter((r) => (r.publicationDate ?? '').slice(0, 10) === day) }
}

const SEGMENTS = [
  { id: 'attente', label: 'Non traité' },
  { id: 'traitement', label: 'En traitement' },
  { id: 'action', label: 'Action requise' },
  { id: 'retenu', label: 'Retenu' },
  { id: 'ecarte', label: 'Écarté' },
  { id: 'hors', label: 'Hors périmètre' },
]

function Unlock({ noticeId }) {
  const [url, setUrl] = useState(null)

  useEffect(() => {
    fetch(`/api/tenders/${noticeId}/documents`)
      .then((r) => r.json())
      .then((d) => setUrl(d.unlockUrl ?? null))
      .catch(() => setUrl(null))
  }, [noticeId])

  if (!url) return null

  return (
    <button
      onClick={() => window.open(url, '3p', 'width=900,height=700')}
      className="rounded bg-white/20 px-2 py-0.5 transition hover:bg-white/30"
    >
      débloquer sur 3P
    </button>
  )
}

function Offer({ notice }) {
  const [state, setState] = useState(null)

  const create = async () => {
    setState('busy')
    try {
      const offer = await (
        await fetch('/api/offers', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ notice }),
        })
      ).json()
      if (!offer.number) throw new Error(offer.error ?? 'échec')
      setState(offer)
      window.open(offer.pdf ?? offer.html, '_blank')
    } catch {
      setState('error')
    }
  }

  if (state === 'busy') return <span className="text-white/60">rédaction de l'offre…</span>
  if (state === 'error') return <span className="text-white/60">offre indisponible</span>

  if (state)
    return (
      <a
        href={state.pdf ?? state.html}
        target="_blank"
        rel="noreferrer"
        className="rounded bg-white/20 px-2 py-0.5 transition hover:bg-white/30"
      >
        offre {state.number} · {state.items} postes
      </a>
    )

  return (
    <button onClick={create} className="rounded bg-white/20 px-2 py-0.5 transition hover:bg-white/30">
      créer l'offre
    </button>
  )
}

function Verdict({ row, busy, noticeId, notice, onAssess }) {
  if (busy) return <div className="rounded-xl bg-navy px-4 py-3 text-sm text-white/70">Évaluation…</div>

  if (!row)
    return (
      <button onClick={onAssess} className="rounded-lg bg-surface px-3 py-1.5 text-sm transition hover:bg-black/5">
        Évaluer
      </button>
    )

  const tone = row.status === 'blocked' ? 'bg-orange' : row.verdict === 'keep' ? 'bg-success' : 'bg-danger'

  return (
    <div className={`rounded-xl px-4 py-3 text-sm text-white ${tone}`}>
      <div>
        {row.verdict_verbose ?? row.status_verbose}
        {row.verdict_verbose && row.status_verbose && (
          <span className="ml-2 text-white/60">{row.status_verbose}</span>
        )}
      </div>

      <div className="mt-2 flex items-baseline gap-3 text-xs text-white/50">
        {row.duration_ms != null && <span>{(row.duration_ms / 1000).toFixed(1)} s</span>}
        {row.tokens != null && <span>{row.tokens.toLocaleString('fr-BE')} jetons</span>}
        {row.cost_usd != null && <span>{row.cost_usd < 0.01 ? '< 0,01 $' : `${row.cost_usd.toFixed(2)} $`}</span>}

        {row.status === 'blocked' && <Unlock noticeId={noticeId} />}

        {row.verdict === 'keep' && row.status === 'done' && (
          <Offer notice={notice} />
        )}

        <button onClick={onAssess} className="ml-auto transition hover:text-white">
          réévaluer
        </button>
      </div>

    </div>
  )
}

const outOfScope = (t) =>
  [
    t.phase !== 'COMPETITION' && `${t.phaseLabel}, seuls les avis de marché sont évalués`,
    t.state !== 'EN_COURS' && 'Remise clôturée',
    t.revisions !== 1 && 'Rectificatif, seule la publication initiale est évaluée',
    !(t.languages ?? []).includes('FR') &&
      `Publication en ${(t.languages ?? []).join(', ') || 'langue inconnue'}, seuls les avis en français sont évalués`,
  ]
    .filter(Boolean)
    .join(' · ')

const evaluable = (t) =>
  t.phase === 'COMPETITION' && t.state === 'EN_COURS' && t.revisions === 1 && (t.languages ?? []).includes('FR')

function Analyse() {
  const [data, setData] = useState(null)
  const [error, setError] = useState(false)
  const [selected, setSelected] = useState(null)
  const [day, setDay] = useState(thisMonday())
  const [assessments, setAssessments] = useState({})
  const [running, setRunning] = useState([])
  const [segment, setSegment] = useState('attente')

  useEffect(() => {
    let stop = false
    setData(null)
    setError(false)
    setAssessments({})
    if (!day) return

    fetchDay(day, () => stop)
      .then(async (d) => {
        if (stop) return
        setData(d)
        const ids = d.results.map((t) => t.id)
        if (!ids.length) return
        const rows = await (await fetch(`/api/assessments?notices=${ids.join(',')}`)).json()
        if (!stop && Array.isArray(rows))
          setAssessments(Object.fromEntries(rows.map((r) => [r.notice_id, r])))
      })
      .catch(() => !stop && setError(true))

    return () => {
      stop = true
    }
  }, [day])

  const evaluateMany = async (queue) => {
    if (!queue.length) return
    setRunning(queue.map((t) => t.id))

    try {
      const res = await fetch('/api/assessments/bulk', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ notices: queue }),
      })

      const reader = res.body.getReader()
      const decoder = new TextDecoder()
      let buffer = ''

      while (true) {
        const { value, done } = await reader.read()
        if (done) break
        buffer += decoder.decode(value, { stream: true })

        const lines = buffer.split('\n')
        buffer = lines.pop() ?? ''

        for (const line of lines.filter(Boolean)) {
          const event = JSON.parse(line)

          if (event.type === 'result') {
            setAssessments((prev) => ({ ...prev, [event.row.notice_id]: event.row }))
            setRunning((prev) => prev.filter((id) => id !== event.row.notice_id))
          }
        }
      }
    } catch {}
    setRunning([])
  }

  const assessAll = () => evaluateMany((data?.results ?? []).filter((t) => evaluable(t) && !assessments[t.id]))

  const segmentOf = (t) => {
    if (!evaluable(t)) return 'hors'
    if (running.includes(t.id)) return 'traitement'
    const row = assessments[t.id]
    if (!row) return 'attente'
    if (row.status === 'blocked') return 'action'
    if (!row.verdict) return 'attente'
    return row.verdict === 'keep' ? 'retenu' : 'ecarte'
  }

  const results = data?.results ?? []
  const counts = Object.fromEntries(SEGMENTS.map((s) => [s.id, results.filter((t) => segmentOf(t) === s.id).length]))
  const shown = results.filter((t) => segmentOf(t) === segment)

  if (selected) return <Detail t={selected} onBack={() => setSelected(null)} />

  return (
    <div>
      <div className="flex items-end justify-between gap-4">
        <div className="w-48">
          <Field label="Jour">
            <input
              type="date"
              value={day}
              onChange={(e) => setDay(e.target.value)}
              className="w-full bg-transparent text-sm outline-none"
            />
          </Field>
        </div>

        <button
          onClick={assessAll}
          disabled={running.length > 0 || !counts.attente}
          className="rounded-lg bg-navy px-4 py-2 text-sm font-medium text-white transition hover:bg-navy/90 disabled:opacity-30"
        >
          Tout évaluer ({counts.attente ?? 0})
        </button>
      </div>

      <div className="mt-4 flex gap-1">
        {SEGMENTS.map((s) => (
          <button
            key={s.id}
            onClick={() => setSegment(s.id)}
            className={`rounded-lg px-3 py-1.5 text-sm transition ${
              s.id === segment ? 'bg-surface font-medium' : 'text-black/50 hover:text-black'
            }`}
          >
            {s.label} <span className="ml-1 tabular-nums text-black/35">{counts[s.id] ?? 0}</span>
          </button>
        ))}
      </div>

      <div className="mt-3 text-sm text-black/40">
        {!day
          ? 'Choisissez un jour.'
          : error
            ? 'Erreur de chargement.'
            : !data
              ? 'Chargement…'
              : `${results.length.toLocaleString('fr-BE')} publications du ${fmt(day)}`}
      </div>

      <div className="mt-2 -mx-5 space-y-2">
        {shown.map((t) => {
          const row = assessments[t.id]
          const busy = running.includes(t.id)

          return (
            <TenderCard
              key={t.id}
              t={t}
              onClick={() => setSelected(t)}
              footer={
                evaluable(t) ? (
                  <Verdict row={row} busy={busy} noticeId={t.id} notice={t} onAssess={() => evaluateMany([t])} />
                ) : (
                  <div className="rounded-xl bg-navy px-4 py-3 text-sm text-white/80">{outOfScope(t)}</div>
                )
              }
            />
          )
        })}
      </div>
    </div>
  )
}

const bce = (id) => {
  const digits = String(id ?? '').replace(/\D/g, '')
  return digits.length === 10 ? `BE ${digits.slice(0, 4)}.${digits.slice(4, 7)}.${digits.slice(7)}` : (id ?? null)
}

const money = (n) =>
  n == null ? '—' : `${Math.round(n).toLocaleString('fr-BE')} €`

const compact = (n) =>
  n == null ? '—' : n >= 1e6 ? `${(n / 1e6).toFixed(1)} M€` : `${Math.round(n / 1000)} k€`

function Stat({ label, value, hint }) {
  return (
    <div className="bg-white px-5 py-4">
      <div className="text-xs uppercase tracking-wide text-black/35">{label}</div>
      <div className="mt-1.5 text-3xl font-bold tabular-nums leading-none">{value}</div>
      {hint && <div className="mt-1.5 text-xs text-black/40">{hint}</div>}
    </div>
  )
}

function Concurrence() {
  const [from, setFrom] = useState('2025-01-01')
  const [data, setData] = useState(null)
  const [error, setError] = useState(false)

  useEffect(() => {
    let stop = false
    setData(null)
    setError(false)
    fetch(`/api/competition?from=${from}`)
      .then((r) => r.json())
      .then((d) => !stop && (d.winners ? setData(d) : setError(true)))
      .catch(() => !stop && setError(true))
    return () => {
      stop = true
    }
  }, [from])

  return (
    <div>
      <div className="flex items-end justify-between gap-6">
        <p className="max-w-2xl text-sm text-black/50">
          Attributions de marchés de voirie, d'égouttage et de terrassement en Hainaut, Namur et Brabant wallon,
          dépouillées depuis les avis d'attribution publiés.
        </p>

        <div className="w-44 shrink-0">
          <Field label="Depuis">
            <input
              type="date"
              value={from}
              onChange={(e) => setFrom(e.target.value)}
              className="w-full bg-transparent text-sm outline-none"
            />
          </Field>
        </div>
      </div>

      {error && <p className="mt-8 text-sm text-black/40">Données indisponibles.</p>}
      {!data && !error && <p className="mt-8 text-sm text-black/40">Dépouillement des avis d'attribution…</p>}

      {data && (
        <div className="mt-6">
          <div className="grid gap-px overflow-hidden rounded-xl bg-black/5 md:grid-cols-4">
            <Stat label="Marchés attribués" value={data.lots.toLocaleString('fr-BE')} hint={`${data.notices} avis dépouillés`} />
            <Stat label="Valeur cumulée" value={compact(data.value)} hint="montants publiés" />
            <Stat label="Concurrents par lot" value={data.averageBidders ?? '—'} hint="offres reçues en moyenne" />
            <Stat
              label="Amplitude des offres"
              value={data.averageGap != null ? `${data.averageGap} %` : '—'}
              hint="sous la plus haute offre"
            />
          </div>

          <table className="mt-10 w-full text-sm">
            <thead>
              <tr className="text-xs uppercase tracking-wide text-black/35">
                <th className="w-8 pb-3 text-right font-medium">#</th>
                <th className="pb-3 pl-4 text-left font-medium">Entreprise</th>
                <th className="pb-3 pl-4 text-right font-medium">Marchés</th>
                <th className="pb-3 pl-4 text-right font-medium">Montant cumulé</th>
                <th className="pb-3 pl-4 text-right font-medium">Ticket moyen</th>
                <th className="pb-3 pl-4 text-right font-medium">Concurrents</th>
              </tr>
            </thead>

            <tbody>
              {data.winners.map((w, i) => (
                <tr key={w.winner} className="align-baseline transition hover:bg-surface">
                  <td className="py-2.5 text-right tabular-nums text-black/30">{i + 1}</td>

                  <td className="py-2.5 pl-4">
                    <div className="font-medium">{w.winner.replace(/\s+/g, ' ').trim()}</div>
                    <div className="mt-0.5 text-xs text-black/35">
                      {[w.city, bce(w.companyId)].filter(Boolean).join(' · ') || '—'}
                    </div>
                  </td>

                  <td className="py-2.5 pl-4 text-right tabular-nums">{w.markets}</td>
                  <td className="py-2.5 pl-4 text-right font-medium tabular-nums">{money(w.amount)}</td>


                  <td className="py-2.5 pl-4 text-right tabular-nums text-black/60">
                    {money(w.markets ? w.amount / w.markets : null)}
                  </td>

                  <td className="py-2.5 pl-4 text-right tabular-nums text-black/60">{w.bidders ?? '—'}</td>
                </tr>
              ))}
            </tbody>
          </table>

          <p className="mt-6 text-xs leading-relaxed text-black/35">
            Source : avis d'attribution publiés sur publicprocurement.be, dépouillés le {fmt(new Date().toISOString())}.
            Périmètre : marchés de travaux, CPV voirie, égouttage, terrassement et réseaux, NUTS BE31, BE32 et BE35.
            Les montants correspondent aux offres retenues telles que publiées ; les lots sans montant publié sont exclus
            du cumul. Un marché exécuté en sous-traitance n'apparaît jamais dans un avis d'attribution.
          </p>
        </div>
      )}
    </div>
  )
}

function Offre() {
  const [offers, setOffers] = useState(null)
  const [shown, setShown] = useState(null)

  useEffect(() => {
    fetch('/api/offers')
      .then((r) => r.json())
      .then((list) => {
        const rows = Array.isArray(list) ? list : []
        setOffers(rows)
        setShown(rows[0] ?? null)
      })
      .catch(() => setOffers([]))
  }, [])

  const source = shown?.pdf ?? '/api/template.pdf'

  return (
    <div className="flex gap-8">
      <aside className="w-72 shrink-0 space-y-1">
        <button
          onClick={() => setShown(null)}
          className={`w-full rounded-lg px-4 py-2.5 text-left text-sm transition ${
            shown ? 'text-black/50 hover:text-black' : 'bg-surface font-medium'
          }`}
        >
          Modèle vierge
        </button>

        {offers?.map((offer) => (
          <button
            key={offer.id}
            onClick={() => setShown(offer)}
            className={`w-full rounded-lg px-4 py-2.5 text-left transition ${
              shown?.id === offer.id ? 'bg-surface' : 'hover:bg-surface'
            }`}
          >
            <div className={`text-sm ${shown?.id === offer.id ? 'font-medium' : ''}`}>{offer.number}</div>
            <div className="mt-0.5 line-clamp-2 text-xs text-black/50">{offer.name}</div>
            <div className="mt-1 text-xs text-black/30">{fmt(offer.created_at)}</div>
          </button>
        ))}

        {offers && !offers.length && <p className="px-4 py-2 text-sm text-black/40">Aucune offre générée.</p>}
      </aside>

      <div className="min-w-0 flex-1">
        <div className="flex items-baseline justify-between gap-4">
          <p className="text-sm text-black/50">
            {shown ? shown.name : 'Modèle F-COM-03, en attente des données du marché.'}
          </p>

          <a
            href={source}
            target="_blank"
            rel="noreferrer"
            className="shrink-0 rounded-lg bg-navy px-4 py-2 text-sm font-medium text-white transition hover:bg-navy/90"
          >
            {shown ? 'Ouvrir le PDF ↗' : 'Ouvrir le modèle ↗'}
          </a>
        </div>

        <div className="mt-4 overflow-hidden rounded-xl bg-surface">
          <iframe key={source} src={source} title="Offre de prix" className="h-[80vh] w-full" />
        </div>
      </div>
    </div>
  )
}

function App() {
  const [active, setActive] = useState(TABS[0].id)

  return (
    <div className="flex min-h-screen">
      <nav className="sticky top-0 flex h-screen w-52 shrink-0 flex-col gap-1 bg-surface/50 px-4 py-8">
        <div className="mb-6 flex items-center gap-3 px-2">
          <img src="/favicon.png" alt="" className="size-8" />
          <span className="text-lg font-bold tracking-tight text-navy">Metubel</span>
        </div>

        {TABS.map((t) => (
          <button
            key={t.id}
            onClick={() => setActive(t.id)}
            className={`rounded-lg px-4 py-2.5 text-left text-sm transition ${
              t.id === active ? 'bg-surface font-medium text-navy' : 'text-black/50 hover:text-black'
            }`}
          >
            {t.label}
          </button>
        ))}
      </nav>

      <main className="mx-auto w-full min-w-0 max-w-7xl px-10 pb-24">
        <h1 className="sticky top-0 z-20 bg-white pb-6 pt-10 text-2xl font-bold">
          {TABS.find((t) => t.id === active).label}
        </h1>

        {active === 'marches' && <Extraction />}
        {active === 'analyse' && <Analyse />}
        {active === 'offre' && <Offre />}
        {active === 'concurrence' && <Concurrence />}
      </main>
    </div>
  )
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />)
