const { useState, useEffect, useRef } = React

const CODEMIRROR = window.CM
  ? Promise.resolve(window.CM)
  : new Promise((resolve) => addEventListener('codemirror', () => resolve(window.CM)))

const SAMPLE_CSS = `@font-face {
  font-family: "IBM Plex Sans";
  src: url("/assets/IBMPlexSans-100-700-latin-ext.woff2") format("woff2");
  font-weight: 100 700;
  font-style: normal;
  font-display: block;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "IBM Plex Sans";
  src: url("/assets/IBMPlexSans-100-700-latin.woff2") format("woff2");
  font-weight: 100 700;
  font-style: normal;
  font-display: block;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@page {
  size: A4;
  margin: 32mm 16mm 30mm 16mm;

  @top-left { content: element(header); width: 100%; }
  @bottom-left { content: element(footer); width: 100%; }
  @bottom-right {
    content: "Page " counter(page) " sur " counter(pages);
    white-space: nowrap;
    font-size: 7.5pt;
    color: #666;
  }
}

body { font-family: "IBM Plex Sans", sans-serif; font-size: 10pt; line-height: 1.45; color: #111; }

.header { position: running(header); width: 178mm; padding-bottom: 4mm; }
.header .row { display: flex; align-items: flex-end; justify-content: space-between; }
.footer { position: running(footer); width: 178mm; padding-top: 4mm; font-size: 7.5pt; line-height: 1.4; color: #555; }
.page + .page { break-before: page; }

th, td { border: 0.5pt solid #444; padding: 3pt 5pt; vertical-align: top; }
thead { display: table-header-group; }
tr { break-inside: avoid; }`

const SAMPLE_DATA = `{
  "emetteur": {
    "nom": "Société Exemple SA",
    "adresse": "Rue de l'Exemple 1",
    "ville": "1000 Bruxelles",
    "tva": "BE 0123 456 789",
    "email": "contact@exemple.be",
    "directeur": "Prénom Nom",
    "technique": "Prénom Nom"
  },
  "client": {
    "service": "Service Travaux",
    "contact": "M. Prénom Nom",
    "adresse": "Avenue Centrale 12",
    "ville": "5000 Namur"
  },
  "devis": {
    "reference": "DEV-2026-001",
    "lieu": "Bruxelles",
    "date": "30/03/2026",
    "objet": "Aménagement d'un espace extérieur",
    "adresseTravaux": "Avenue Centrale 12, 5000 Namur",
    "suivi": "Prénom Nom",
    "validite": "1 mois",
    "delai": "3 semaines",
    "acompte": 50,
    "echeance": 15
  },
  "postes": [
    { "libelle": "Installation et préparation du chantier", "quantite": 1, "unite": "FFT", "prix": 400 },
    { "libelle": "Préparation des sols sur environ 20 cm d'épaisseur", "quantite": 75.62, "unite": "m²", "prix": 12 },
    { "libelle": "Fourniture et pose d'un géotextile", "quantite": 75.62, "unite": "m²", "prix": 3.06 },
    { "libelle": "Fourniture et pose d'une bordure 100/40/10 cm", "quantite": 75.26, "unite": "m", "prix": 59.57 },
    { "libelle": "Fourniture et pose d'une fondation - épaisseur 15 cm", "quantite": 11.34, "unite": "m³", "prix": 199.71 },
    { "libelle": "Fourniture et pose d'une couche de finition - épaisseur 5 cm", "quantite": 3.78, "unite": "m³", "prix": 534.4 },
    { "libelle": "Évacuation des terres excédentaires", "quantite": 27.22, "unite": "T", "prix": 42.02 }
  ],
  "compris": [
    "Tout ce qui est défini au point 1.2",
    "La fourniture des matériaux et du matériel nécessaires aux travaux décrits",
    "La main d'œuvre qualifiée pour les travaux décrits au point 1.2",
    "Les équipements de protection collective pour le travail demandé",
    "Les équipements de protection individuelle pour un travail normal"
  ],
  "exclus": [
    "Tout ce qui n'est pas défini au point 2.1",
    "Le chantier sera accessible à nos véhicules équipés",
    "La taille des arbres et arbustes",
    "Le client fournira les plans et schémas précis des impétrants sur le domaine privé",
    "L'entreprise est limitée à l'exécution des travaux repris ci-dessus"
  ],
  "remarques": [
    "Les accès sont supposés suffisamment dégagés pour permettre un déchargement à pied de camion",
    "Les quantités indiquées sont présumées ; un mesurage contradictoire sera réalisé en fin de chantier",
    "Les prix correspondent aux volumes réellement exécutés",
    "Les prix d'évacuation ne sont valables que pour des terres non contaminées, analyses à charge du client",
    "Tout élément découvert en cours de chantier et non prévisible entraînera une majoration du prix",
    "Aucune responsabilité ne saurait être engagée en cas de force majeure"
  ]
}`

const SAMPLE_JS = `function Grille({ prix }) {
  const total = data.postes.reduce((somme, poste) => somme + poste.quantite * poste.prix, 0)

  return (
    <table className="w-full border-collapse text-[9pt]">
      <thead>
        <tr>
          <th className="w-[12mm]">Poste</th>
          <th>Libellé</th>
          <th className="w-[18mm]">Qté Prés</th>
          <th className="w-[14mm]">Unité</th>
          {prix && <th className="w-[24mm]">PU HTVA</th>}
          {prix && <th className="w-[26mm]">Total HTVA</th>}
        </tr>
      </thead>
      <tbody>
        {data.postes.map((poste, i) => (
          <tr key={poste.libelle}>
            <td className="text-center">{i + 1}</td>
            <td>{poste.libelle}</td>
            <td className="text-right">{poste.quantite.toFixed(2)}</td>
            <td className="text-center">{poste.unite}</td>
            {prix && <td className="text-right">{poste.prix.toFixed(2)} €</td>}
            {prix && <td className="text-right">{(poste.quantite * poste.prix).toFixed(2)} €</td>}
          </tr>
        ))}
        {prix && (
          <tr className="font-bold">
            <td colSpan={5}>Total HTVA</td>
            <td className="text-right">{total.toFixed(2)} €</td>
          </tr>
        )}
      </tbody>
    </table>
  )
}

function Liste({ items }) {
  return (
    <ul className="mb-[10pt] list-disc pl-[18pt]">
      {items.map((item) => (
        <li key={item} className="mb-[4pt] text-justify">{item}</li>
      ))}
    </ul>
  )
}

function Titre({ children }) {
  return (
    <h2 className="mb-[10pt] text-[11pt] font-bold uppercase underline">{children}</h2>
  )
}

function Sous({ children }) {
  return (
    <h3 className="mb-[6pt] mt-[14pt] text-[10pt] underline">{children}</h3>
  )
}

function Document() {
  return (
    <>
      <div className="header">
        <div className="row">
          <span className="text-[13pt] font-bold">{data.emetteur.nom}</span>
          <span className="text-[8pt] text-neutral-500">Devis n° {data.devis.reference}</span>
        </div>
      </div>

      <div className="footer">
        <div>{data.emetteur.nom} — {data.emetteur.adresse}, {data.emetteur.ville}</div>
        <div>TVA : {data.emetteur.tva} — {data.emetteur.email}</div>
      </div>

      <div className="page">
        <div className="mb-[12mm] text-right text-[11pt] font-bold leading-loose">
          <div>{data.client.service}</div>
          <div>{data.client.contact}</div>
          <div>{data.client.adresse}</div>
          <div>{data.client.ville}</div>
        </div>

        <p><u>N/Réf</u> : {data.devis.reference}</p>
        <p className="text-right">{data.devis.lieu}, le {data.devis.date}.</p>

        <p className="mt-[6mm]"><u><b>Concerne</b></u> : {data.devis.objet}</p>
        <p><u><b>Adresse précise des travaux</b></u> : {data.devis.adresseTravaux}</p>
        <p className="mb-[8mm]"><b>Affaire suivie par</b> {data.devis.suivi}</p>

        <Titre>1. Objet de l'entreprise</Titre>

        <Sous>1.1 Base de notre offre</Sous>
        <p>Notre offre fait suite à votre demande de prix et à une visite sur site.</p>
        <p>Notre offre est régie par nos conditions générales et notre plan de sécurité.</p>

        <Sous>1.2 Étendue de l'entreprise</Sous>
        <Grille />
      </div>

      <div className="page">
        <Titre>2. Limitations de l'entreprise</Titre>

        <Sous>2.1 Sont compris dans notre prix</Sous>
        <Liste items={data.compris} />

        <Sous>2.2 Ne sont pas compris dans notre offre</Sous>
        <Liste items={data.exclus} />

        <Sous>2.3 Remarques</Sous>
        <Liste items={data.remarques} />

        <Sous>2.4 Aspect sécurité</Sous>
        <Liste items={["Plan général de sécurité et de santé de " + data.emetteur.nom + "."]} />
      </div>

      <div className="page">
        <Titre>Prix unitaires</Titre>
        <Grille prix />

        <p className="mt-[4mm] text-[8.5pt]">
          Le chantier se limite aux quantités présumées maximum. Tout travail supplémentaire pourra
          faire l'objet d'une offre annexe complémentaire.
        </p>

        <div className="mt-[12mm]">
          <Titre>3. Délais</Titre>
          <Liste items={["Un délai de " + data.devis.delai + " après réception de la déclaration d'accord d'exécution et du paiement est à prévoir avant le début des travaux."]} />
        </div>
      </div>

      <div className="page">
        <Titre>4. Validité de notre offre</Titre>
        <Liste items={["Les prix repris dans la présente offre restent valables pour une période de " + data.devis.validite + "."]} />

        <Titre>5. Facturation</Titre>
        <Liste items={["Nous utiliserons vos données administratives reprises sur le document « Accord d'exécution » ci-annexé."]} />

        <Titre>6. Paiement</Titre>
        <Liste
          items={[
            "Le paiement des " + data.devis.acompte + "% facturés à la commande devra être effectué au comptant et fera office de commande.",
            "Les " + data.devis.acompte + "% restant dus seront facturés à la fin des travaux et devront être payés sous " + data.devis.echeance + " jours prenant cours à la date de facturation.",
          ]}
        />

        <p className="mt-[8mm]">
          Nous espérons que cette offre sera à votre convenance et qu'elle vous permettra de nous
          confier votre commande, à laquelle nous réserverons nos meilleurs soins.
        </p>
        <p>
          Si vous décidez de nous confier cette mission, il vous suffit de nous renvoyer un exemplaire
          dûment complété et signé de l'annexe « Accord pour exécution » par mail à {data.emetteur.email}.
        </p>
        <p>Nous vous prions de croire en l'expression de nos sentiments dévoués.</p>

        <div className="mt-[30mm] flex justify-between">
          <div>
            <div className="w-[55mm] border-t border-neutral-500 pt-[2mm]">{data.emetteur.directeur}</div>
            <div className="text-[8.5pt]">Directeur</div>
          </div>
          <div>
            <div className="w-[55mm] border-t border-neutral-500 pt-[2mm]">{data.emetteur.technique}</div>
            <div className="text-[8.5pt]">Directeur technique</div>
          </div>
        </div>
      </div>

      <div className="page">
        <h2 className="mb-[4mm] text-center text-[11pt] font-bold">Déclaration « Accord d'exécution »</h2>
        <p className="text-center text-[9pt]">À renvoyer complété et signé par mail à {data.emetteur.email}</p>

        <div className="mt-[10mm] leading-[2.4]">
          <div>Établi à : <span className="inline-block min-w-[60mm] border-b border-dotted border-neutral-600" /></div>
          <div><span className="inline-block min-w-[100mm] border-b border-dotted border-neutral-600" /></div>
          <div>représenté par <span className="inline-block min-w-[60mm] border-b border-dotted border-neutral-600" /></div>
        </div>

        <p className="mt-[8mm]">donne, par la présente, mission à</p>

        <div className="font-bold leading-snug">
          <div>{data.emetteur.nom}</div>
          <div>{data.emetteur.adresse}</div>
          <div>{data.emetteur.ville}</div>
          <div>TVA {data.emetteur.tva}</div>
        </div>

        <p className="mt-[6mm]">représentée par {data.emetteur.directeur}, Directeur,</p>
        <p className="mb-[8mm]">
          de procéder aux travaux comme décrit au point 1.2 conformément aux conditions de cette offre
          portant la référence {data.devis.reference} et aux conditions générales disponibles sur simple demande.
        </p>

        <Titre>Prix unitaires</Titre>
        <Grille prix />
      </div>

      <div className="page">
        <Sous>Délais</Sous>
        <Liste items={["Un délai de " + data.devis.delai + " avant le début des travaux."]} />

        <Sous>Validité de notre offre</Sous>
        <Liste items={[data.devis.validite]} />

        <div className="mt-[10mm]">
          <Titre>Facturation</Titre>
          <p>Nous utilisons les données administratives suivantes dans le cadre de la facturation :</p>

          <div className="ml-[10mm] leading-[2.6]">
            {["Société", "Adresse", "Code postal et commune", "N° TVA", "Personne de contact", "Téléphone", "Email"].map((champ) => (
              <div key={champ}>
                {champ} : <span className="inline-block min-w-[60mm] border-b border-dotted border-neutral-600" />
              </div>
            ))}
          </div>
        </div>

        <div className="mt-[10mm]">
          <Titre>Paiement</Titre>
          <Liste
            items={[
              "Le paiement des " + data.devis.acompte + "% facturés à la commande devra être effectué au comptant.",
              "Le solde sera facturé à la fin des travaux et payé sous " + data.devis.echeance + " jours.",
            ]}
          />
        </div>

        <div className="mt-[16mm] leading-[2.4]">
          {["Accord pour exécution", "Nom et fonction", "Signature et date"].map((champ) => (
            <div key={champ}>
              {champ} : <span className="inline-block min-w-[60mm] border-b border-dotted border-neutral-600" />
            </div>
          ))}
          <div className="text-[8.5pt]">(précédées de la mention « lu et approuvé »)</div>
        </div>
      </div>
    </>
  )
}`

function Code({ label, value, onChange, lang }) {
  const host = useRef(null)
  const view = useRef(null)

  useEffect(() => {
    let live = true

    CODEMIRROR.then(({ EditorView, basicSetup, langs }) => {
      if (!live) return

      view.current = new EditorView({
        doc: value,
        parent: host.current,
        extensions: [
          basicSetup,
          langs[lang](),
          EditorView.lineWrapping,
          EditorView.theme({
            '&': { height: 'auto', fontSize: '12px' },
            '.cm-scroller': { overflow: 'visible', fontFamily: '"IBM Plex Mono", ui-monospace, monospace' },
            '&.cm-focused': { outline: 'none' },
          }),
          EditorView.updateListener.of((update) => {
            if (update.docChanged) onChange(update.state.doc.toString())
          }),
        ],
      })
    })

    return () => {
      live = false
      view.current?.destroy()
    }
  }, [])

  return (
    <div className="shrink-0 pb-8">
      <div className="sticky top-0 z-10 bg-white pb-2 text-xs font-medium uppercase tracking-wider text-black/35">
        {label}
      </div>
      <div ref={host} />
    </div>
  )
}

function Devis() {
  const [templates, setTemplates] = useState([])
  const [id, setId] = useState(null)
  const [name, setName] = useState('Devis')
  const [css, setCss] = useState(SAMPLE_CSS)
  const [js, setJs] = useState(SAMPLE_JS)
  const [data, setData] = useState(SAMPLE_DATA)
  const [pages, setPages] = useState(null)
  const [error, setError] = useState(null)
  const [busy, setBusy] = useState(false)

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

  async function refresh() {
    setTemplates(await lib.pdf.templates())
  }

  function reset() {
    setId(null)
    setName('Devis')
  }

  async function load(value) {
    if (!value) return reset()

    const template = await lib.pdf.template(value)
    setId(template.id)
    setName(template.name)
    setCss(template.css)
    setJs(template.js)
    setData(template.data)
  }

  async function save() {
    const fields = { name, css, js, data }
    const template = id ? await lib.pdf.patchTemplate(id, fields) : await lib.pdf.createTemplate(fields)

    setId(template.id)
    refresh()
  }

  async function remove() {
    await lib.pdf.deleteTemplate(id)
    reset()
    refresh()
  }

  async function download() {
    setBusy(true)

    try {
      const blob = await lib.pdf.generate(id ? { id } : { css, js, data })
      const url = URL.createObjectURL(blob)

      Object.assign(document.createElement('a'), { href: url, download: `${name}.pdf` }).click()
      URL.revokeObjectURL(url)
    } catch (err) {
      setError(String(err.message))
    } finally {
      setBusy(false)
    }
  }

  return (
    <div className="flex h-[calc(100vh-9rem)] flex-col gap-4">
      <div className="flex shrink-0 items-center gap-3">
        <select value={id ?? ''} onChange={(event) => load(event.target.value)} className="bg-surface px-3 py-2 text-sm">
          <option value="">Nouveau modèle</option>
          {templates.map((template) => (
            <option key={template.id} value={template.id}>{template.name}</option>
          ))}
        </select>

        <input value={name} onChange={(event) => setName(event.target.value)} className="bg-surface px-3 py-2 text-sm" />

        <button onClick={save} className="bg-surface px-4 py-2 text-sm">Enregistrer</button>
        {id && <button onClick={remove} className="px-2 py-2 text-sm text-danger">Supprimer</button>}

        <button onClick={download} disabled={busy} className="bg-accent px-4 py-2 text-sm text-white">
          {busy ? 'Génération…' : 'Télécharger le PDF'}
        </button>

        {pages && <span className="text-xs text-black/40">{pages} pages</span>}
        {error && <span className="truncate text-xs text-danger">{error}</span>}
      </div>

      <div className="grid min-h-0 flex-1 grid-cols-2 gap-6">
        <div className="min-h-0 overflow-auto">
          <Code label="CSS" value={css} onChange={setCss} lang="css" />
          <Code label="JS" value={js} onChange={setJs} lang="jsx" />
          <Code label="Données d'exemple" value={data} onChange={setData} lang="json" />
        </div>

        <lib.pdf.Preview
          css={css}
          js={js}
          data={data}
          onPages={setPages}
          onError={setError}
          className="min-h-0 bg-[#e9ebf0]"
        />
      </div>
    </div>
  )
}

const TOOLS = [{ id: 'devis', label: 'Devis', Component: Devis }]

function Empty() {
  return <p className="text-sm text-black/50">Aucun outil. Ajoute une entrée dans TOOLS.</p>
}

function App() {
  const [active, setActive] = useState(TOOLS[0]?.id ?? null)
  const tool = TOOLS.find((t) => t.id === active)

  return (
    <div className="flex min-h-screen">
      <nav className="sticky top-0 flex h-screen w-56 shrink-0 flex-col gap-1 p-4">
        <div className="px-4 pb-6 pt-4 text-sm font-bold">Playground</div>

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

      <main className="flex w-full min-w-0 flex-col px-10 pb-10">
        <h1 className="shrink-0 pb-6 pt-10 text-2xl font-bold">{tool?.label ?? 'Playground'}</h1>
        {tool ? <tool.Component /> : <Empty />}
      </main>
    </div>
  )
}

async function mount() {
  await use('pdf')
  ReactDOM.createRoot(document.getElementById('root')).render(<App />)
}

window.CM ? mount() : addEventListener('codemirror', mount)
