"""
Create / update the custom Print Format "Facture TARGO" on ERPNext.

Design
------
- Letter-size, #10 envelope-window compatible
- 2-column layout: left = client + per-location charges, right = summary + QR
- Items grouped by Sales Invoice Item.service_location (Link → Service Location).
  Identical (name, rate) rows within a location are consolidated with a "(xN)"
  suffix, matching the reference preview.
- QR code is embedded as base64 data URI via the whitelisted method
  `gigafibre_utils.api.invoice_qr_base64`. Depends on the custom app
  `gigafibre_utils` being installed on the bench (see /opt/erpnext/custom/
  on the ERPNext host). wkhtmltopdf does NOT fetch the QR over HTTP.
- SVG logo paths use inline `fill="#019547"` (wkhtmltopdf / QtWebKit does not
  honour `<defs><style>` CSS inside SVG).

Run (inside erpnext-backend-1)
------------------------------
    docker cp scripts/migration/setup_invoice_print_format.py \\
        erpnext-backend-1:/tmp/setup_invoice_print_format.py
    docker exec -u frappe -w /home/frappe/frappe-bench erpnext-backend-1 \\
        env/bin/python /tmp/setup_invoice_print_format.py

The script is idempotent: it updates the Print Format if it already exists.
"""
import os

os.chdir("/home/frappe/frappe-bench/sites")
import frappe

frappe.init(site="erp.gigafibre.ca", sites_path=".")
frappe.connect()
print("Connected:", frappe.local.site)

# Print Settings: Letter + canonical number format
frappe.db.set_single_value("Print Settings", "pdf_page_size", "Letter")
frappe.db.set_single_value("System Settings", "number_format", "#,###.##")
frappe.db.set_single_value("System Settings", "currency_precision", "2")
frappe.db.commit()
print("  PDF page size set to Letter, number format set to #,###.##")

PRINT_FORMAT_NAME = "Facture TARGO"

# ── Jinja template ────────────────────────────────────────────────────────
# Kept in-file so the script is self-contained and version-controlled.
# Source of truth: this string. Regenerate PDFs via
#   /api/method/frappe.utils.print_format.download_pdf?doctype=Sales+Invoice&name=<SINV>&format=Facture+TARGO
html_template = r"""{#- ══════════════════════════════════════════════════════════════════════════
  Facture TARGO — ERPNext Print Format (Sales Invoice)

  The HTML/CSS below is a verbatim copy of scripts/migration/invoice_preview.jinja
  (the canonical design that renders correctly under Chromium/Antigravity).
  Only the top Jinja prelude is new: it resolves `doc.*` + whitelisted helpers
  into the plain context variables the preview template expects.

  Depends on:
    - custom app `gigafibre_utils` (QR + logo + short_item_name helpers)
    - PDF generator set to `chrome` on the Print Format (Chromium in the image)
  ══════════════════════════════════════════════════════════════════════════ -#}

{%- macro money(v) -%}{{ "%.2f"|format((v or 0)|float) }}{%- endmacro -%}

{%- macro _clean(s) -%}{{ (s or "") | replace("&#039;","'") | replace("&amp;","&") | replace("&lt;","<") | replace("&gt;",">") | replace("&quot;",'"') }}{%- endmacro -%}

{%- set mois_fr = {"January":"janvier","February":"février","March":"mars","April":"avril","May":"mai","June":"juin","July":"juillet","August":"août","September":"septembre","October":"octobre","November":"novembre","December":"décembre"} -%}
{%- macro date_short(d) -%}
  {%- if d -%}
    {%- set dt = frappe.utils.getdate(d) -%}
    {{ "%02d/%02d/%04d" | format(dt.day, dt.month, dt.year) }}
  {%- else -%}—{%- endif -%}
{%- endmacro -%}
{%- macro date_fr(d) -%}
  {%- if d -%}
    {%- set dt = frappe.utils.getdate(d) -%}
    {{ dt.day }} {{ mois_fr.get(dt.strftime("%B"), dt.strftime("%B")) }} {{ dt.year }}
  {%- else -%}—{%- endif -%}
{%- endmacro -%}

{#- ── Preview context variables resolved from the Sales Invoice ───────────── -#}
{%- set invoice_number = doc.name -%}
{%- set invoice_date = date_short(doc.posting_date) -%}
{%- set account_number = doc.customer -%}
{%- set client_name = doc.customer_name -%}

{%- set client_address_line1 = "" -%}
{%- set client_address_line2 = "" -%}
{%- if doc.customer_address -%}
  {%- set a = frappe.get_doc("Address", doc.customer_address) -%}
  {%- set client_address_line1 = a.address_line1 or "" -%}
  {%- set _city = (a.city or "") -%}
  {%- set _state = (a.state or "") -%}
  {%- set _pin = (a.pincode or "") -%}
  {%- set client_address_line2 = (_city ~ (", " if _city and _state else "") ~ _state ~ ("  " if _pin else "") ~ _pin) | trim -%}
{%- endif -%}

{#- ── Language detection ─────────────────────────────────────────────────────
     Reads Customer.language (set on the Customer record → More Info tab).
     Frappe stores values like "fr", "fr-CA", "en", "en-US".
     Anything starting with "en" is treated as English; all else → French.
     This controls every user-visible label: document title, meta table, totals,
     legal text.  The QUOTE template reuses the same block with lbl_doc_quote. -#}
{%- set _cust_lang = (frappe.db.get_value("Customer", doc.customer, "language") or "fr") | lower -%}
{%- set _is_en = _cust_lang[:2] == "en" -%}

{#- Document title — FACTURE / INVOICE or NOTE DE CRÉDIT / CREDIT NOTE -#}
{%- if doc.is_return -%}
  {%- set doc_title = "CREDIT NOTE" if _is_en else "NOTE DE CR\u00c9DIT" -%}
{%- else -%}
  {%- set doc_title = "INVOICE" if _is_en else "FACTURE" -%}
{%- endif -%}

{#- ── TPS / TVQ split ──────────────────────────────────────────────────────── -#}
{#- Jinja {% set %} inside a {% for %} loop does NOT mutate outer scope —
    must use a namespace object. Also accumulate (not assign) in case the
    Sales Invoice has multiple TPS/TVQ rows. Match on description OR account_head
    so renamed Tax Templates (e.g. "GST/HST", "QST") still classify correctly. -#}
{%- set ns = namespace(tps=0, tvq=0) -%}
{%- for t in doc.taxes or [] -%}
  {%- set _desc = ((t.description or "") ~ " " ~ (t.account_head or "")) | upper -%}
  {%- if "TPS" in _desc or "GST" in _desc or "HST" in _desc -%}
    {%- set ns.tps = ns.tps + (t.tax_amount or 0) -%}
  {%- elif "TVQ" in _desc or "QST" in _desc or "PST" in _desc -%}
    {%- set ns.tvq = ns.tvq + (t.tax_amount or 0) -%}
  {%- endif -%}
{%- endfor -%}
{#- Fallback: if no tax row matched the name heuristics but the invoice
    DOES have total taxes, split 1/3 TPS, 2/3 TVQ (Québec standard 5%/9.975%
    ≈ 1:2 ratio). Keeps the displayed sum equal to total_taxes_and_charges. -#}
{%- if (ns.tps + ns.tvq) == 0 and (doc.total_taxes_and_charges or 0) > 0 -%}
  {%- set _tot = doc.total_taxes_and_charges -%}
  {%- set ns.tps = (_tot * 5.0 / 14.975) | round(2) -%}
  {%- set ns.tvq = _tot - ns.tps -%}
{%- endif -%}
{%- set tps_amount = money(ns.tps) -%}
{%- set tvq_amount = money(ns.tvq) -%}
{%- set total_taxes = money(doc.total_taxes_and_charges) -%}
{%- set subtotal_before_taxes = money(doc.net_total) -%}
{%- set current_charges_total = money(doc.grand_total) -%}
{%- set due_date = date_fr(doc.due_date) -%}

{#- ── Service period window (used both by the items loop for per-line
     period labels and by any block that needs the overall period). ── -#}
{%- set svc_start = frappe.utils.getdate(doc.posting_date) -%}
{%- set svc_end = frappe.utils.add_days(frappe.utils.add_months(doc.posting_date, 1), -1) -%}

{#- ── Group items by Service Location (address label). Consolidate duplicates. -#}
{%- set _groups = {} -%}
{%- set _order = [] -%}
{%- for it in doc.items -%}
  {%- set loc_key = it.get("service_location") or "_default" -%}
  {%- if loc_key not in _groups -%}
    {%- set _ = _order.append(loc_key) -%}
    {%- set _label = "" -%}
    {%- if loc_key == "_default" -%}
      {%- set _label = client_address_line1 or "Tous les services" -%}
    {%- else -%}
      {%- set _ld = frappe.get_cached_doc("Service Location", loc_key) -%}
      {%- set _street = _ld.address_line or "" -%}
      {%- set _city = _ld.city or "" -%}
      {%- set _label = _street -%}
      {%- if _city and _street -%}{%- set _label = _label ~ ", " ~ _city -%}
      {%- elif _city -%}{%- set _label = _city -%}{%- endif -%}
      {%- set _label = _label or _ld.location_name or loc_key -%}
    {%- endif -%}
    {%- set _ = _groups.update({loc_key: {"name": _label, "items": [], "index": {}, "subtotal_val": 0}}) -%}
  {%- endif -%}
  {%- set g = _groups[loc_key] -%}
  {%- set _ikey = _clean(it.item_name or it.description) ~ "||" ~ ("%.4f" | format((it.rate or 0)|float)) -%}
  {%- set _eidx = g["index"].get(_ikey) -%}
  {%- if _eidx is not none -%}
    {%- set _r = g["items"][_eidx] -%}
    {%- set _ = _r.update({"qty": (_r.qty or 0) + (it.qty or 0), "amount_val": (_r.amount_val or 0) + (it.amount or 0), "amount": money((_r.amount_val or 0) + (it.amount or 0))}) -%}
  {%- else -%}
    {%- set _short = frappe.call("gigafibre_utils.api.short_item_name", name=(it.item_name or it.description), max_len=48) -%}
    {%- set _desc_base = _short or _clean(it.item_name or it.description) -%}

    {#- Per-item service period. Only set when the item CARRIES EXPLICIT
        service_start_date + service_end_date (ERPNext deferred-revenue items).
        No more defaulting to the current billing window — most items don't
        need to repeat "16 avril au 15 mai" on every line. -#}
    {%- set _period_str = "" -%}
    {%- if it.get("service_start_date") and it.get("service_end_date") -%}
      {%- set _ps = frappe.utils.getdate(it.service_start_date) -%}
      {%- set _pe = frappe.utils.getdate(it.service_end_date) -%}
      {%- set _psm = mois_fr.get(_ps.strftime("%B"), "") -%}
      {%- set _pem = mois_fr.get(_pe.strftime("%B"), "") -%}
      {%- if _ps.month == _pe.month -%}
        {%- set _period_str = (("1er " if _ps.day == 1 else (_ps.day|string ~ " "))) ~ _psm ~ " au " ~ _pe.day|string ~ " " ~ _pem -%}
      {%- else -%}
        {%- set _period_str = _ps.day|string ~ " " ~ _psm ~ " au " ~ _pe.day|string ~ " " ~ _pem -%}
      {%- endif -%}
    {%- endif -%}
    {#- Period is stored SEPARATELY (not appended to description) so the
        template can render it once per location block instead of once per
        item — much less noise when 5 items share the same billing window. -#}
    {%- set _ = g["items"].append({
          "description": _desc_base,
          "period_str": _period_str,
          "qty": (it.qty | int if it.qty == it.qty|int else it.qty),
          "unit_price": money(it.rate),
          "amount": money(it.amount),
          "amount_val": (it.amount or 0),
          "is_credit": (it.amount or 0) < 0,
        }) -%}
    {%- set _ = g["index"].update({_ikey: g["items"]|length - 1}) -%}
  {%- endif -%}
  {%- set _ = g.update({"subtotal_val": g["subtotal_val"] + (it.amount or 0)}) -%}
{%- endfor -%}
{%- set current_charges_locations = [] -%}
{%- for k in _order -%}
  {%- set g = _groups[k] -%}
  {%- set _ = current_charges_locations.append({"name": g["name"], "items": g["items"], "subtotal": money(g["subtotal_val"])}) -%}
{%- endfor -%}

{#- ── Previous invoice + recent payments ──────────────────────────────────── -#}
{%- set _prev_list = frappe.get_all("Sales Invoice",
      filters={"customer": doc.customer, "docstatus": 1, "name": ["!=", doc.name], "posting_date": ["<", doc.posting_date]},
      fields=["name","posting_date","grand_total","outstanding_amount"],
      order_by="posting_date desc", limit_page_length=1) or [] -%}
{%- set _prev = _prev_list[0] if _prev_list else None -%}
{%- set prev_invoice_date = date_short(_prev.posting_date) if _prev else "—" -%}
{%- set prev_invoice_total = money(_prev.grand_total) if _prev else "0.00" -%}
{%- set remaining_balance = money(_prev.outstanding_amount) if _prev else "0.00" -%}
{%- set recent_payments = [] -%}
{%- if _prev -%}
  {%- set _refs = frappe.get_all("Payment Entry Reference",
        filters={"reference_doctype":"Sales Invoice","reference_name": _prev.name},
        fields=["allocated_amount"]) or [] -%}
  {%- for r in _refs -%}
    {%- set _ = recent_payments.append({"amount": money(r.allocated_amount)}) -%}
  {%- endfor -%}
{%- endif -%}

{%- set total_amount_due = money((doc.outstanding_amount or 0) + (_prev.outstanding_amount if _prev else 0)) -%}

{#- ── QR code (base64 data URI generated server-side) ─────────────────────── -#}
{%- set qr_code_base64 = frappe.call("gigafibre_utils.api.invoice_qr_base64", invoice=doc.name) -%}

{#- ── Referral code (stable 6 chars per Customer) ──────────────────────────── -#}
{%- set referral_code = frappe.call("gigafibre_utils.api.referral_code", customer=doc.customer) -%}

{#- (service-period variables moved earlier in the prelude, before the
     items loop needs them) -#}

{#- ═════════════════════════════════════════════════════════════════════════
    ORIGINAL PREVIEW TEMPLATE (invoice_preview.jinja), unmodified below.
    ═════════════════════════════════════════════════════════════════════════ -#}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Facture {{ invoice_number }}</title>
<style>
  /* @page: size + margins. Chromium honors `counter(page)` / `counter(pages)`
     inside @top-right to inject page numbers on multi-page overflow.
     We suppress the counter on page 1 via `@page:first` — CSS can't test
     "total pages == 1", but it CAN hide on the first page. So:
     - Single-page invoice (99% case): no "Page 1 de 1" clutter.
     - Multi-page invoice: page 2+ shows "Page 2 de 3", "Page 3 de 3", etc.
       (page 1 stays clean, which is acceptable — the header clearly marks
       the document's first page). */
  @page {
    size: Letter;
    margin: 10mm 15mm 8mm 20mm;
    @top-right {
      content: "Page " counter(page) " de " counter(pages);
      font-family: -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
      font-size: 7pt;
      color: #888;
      padding-top: 5mm;
    }
  }
  @page:first {
    @top-right { content: ""; }
  }
  * { box-sizing: border-box; }
  /* ⚠️ Kill Frappe's aggressive `!important` overrides:
     1. `.print-format { padding: 0.75in }` shoves content ~19mm down.
     2. `.print-format td img { width: 100% !important }` stretches inline
        QR image to the full td width, breaking its square aspect.
     3. `.print-format td { padding: 10px !important }` bloats row heights
        AND eats ~20mm of horizontal space from our 2-column main layout
        (10px each side × 2 cols × 2 edges ≈ 40px ≈ 10mm per column).
        Fix: blanket-reset ALL td/th padding to 0 with !important, then let
        each specific table add back exactly the padding it needs (again
        with !important). Same specificity (0,1,1) vs Frappe's (0,1,1),
        ours is declared later in the cascade → wins. */
  .print-format { padding: 0 !important; margin: 0 !important; max-width: none !important; }
  .print-format img, .print-format td img { width: auto !important; max-width: 100% !important; height: auto !important; }
  .print-format img.qr-img, .print-format td img.qr-img { width: 40px !important; height: 40px !important; max-width: 40px !important; }
  /* Blanket neutralisation of Frappe's default td/th padding. Every table
     below re-adds its own padding in its own rule. */
  .print-format td, .print-format th { padding: 0 !important; }
  body { font-family: -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; font-size: 8pt; color: #333; line-height: 1.2; margin: 0; }
  @media screen {
    body { padding: 40px; max-width: 8.5in; margin: 0 auto; box-shadow: 0 0 10px rgba(0,0,0,0.1); background: #fff; }
    html { background: #f0f0f0; }
  }

  /* ── Pre-envelope-window summary (previous balance + payments) ──
     Sits between the logo and the address. Hidden by the envelope when
     folded; becomes the first thing the customer reads after opening.
     Collapsed to minimum height: 3 rows + title ≈ 11-12mm so the address
     still lands at the ~2" envelope-window top. */
  .pre-window { margin-top: 4mm; }
  .pw-title { font-size: 6.5pt; font-weight: 700; color: #019547; border-bottom: 1px solid #019547; padding-bottom: 1px; margin-bottom: 1px; letter-spacing: 0.3px; line-height: 1.1; }
  .pw-table { width: 100%; font-size: 7pt; line-height: 1.25; border-collapse: collapse; }
  .pw-table td { padding: 1px 2px !important; vertical-align: baseline; line-height: 1.25; }
  .pw-table td.r { text-align: right; white-space: nowrap; }
  .pw-table tr.pmt td { color: #019547; }
  .pw-table tr.bal td { font-style: italic; color: #666; border-bottom: 1px solid #eee; }

  /* ── Metadata Elements ── */
  .doc-label { font-size: 14pt; font-weight: 700; color: #019547; }
  .doc-page { font-size: 7pt; color: #888; }

  /* ── Meta band (under logo) ── */
  .meta-band { width: 100%; padding: 6px 0; border-top: 1px solid #eee; border-bottom: 1px solid #eee; margin: 15px 0 0 0; }
  .meta-band td { vertical-align: middle; padding: 0 6px; text-align: center; }
  .meta-band .ml { color: #888; display: block; font-size: 5.5pt; text-transform: uppercase; margin-bottom: 2px; }
  .meta-band .mv { font-weight: 700; color: #333; font-size: 7pt; }

  /* ── Main 2-column layout ──
     Letter content width (after @page margins 20mm L + 15mm R) = 180.9mm.
     Items list is the MAIN focus — left column dominates at 66%. Right
     column is 1/3 of usable width (= 60.3mm ≈ 2.37"). r-meta (account/
     date/invoice#) is stacked vertically as label|value rows since 3
     side-by-side cells no longer fit in 60mm.
       col-l  = 66%  → 119.4mm (4.70")
       spacer =  1%  →   1.8mm (0.07")
       col-r  = 33%  →  59.7mm (2.35" = 1/3 usable width)
     `table-layout: fixed` forces the browser to honour declared widths
     instead of auto-sizing to content. */
  .main { width: 100%; border-collapse: separate; border-spacing: 0; table-layout: fixed; }
  .main td { vertical-align: top; }
  .col-l { width: 66%; }
  .col-spacer { width: 1%; }
  .col-r { width: 33%; }

  /* ── Envelope-window block — address only.
     Left column has logo (~7mm) + pre-window SOMMAIRE (~11-12mm, 3 rows:
     solde précédent / paiement reçu / solde reporté). margin-top is set
     inline on the element (30mm with prev, 33mm without — both target the
     ~2" / 51mm envelope-window top counting from page-margin origin);
     margin-bottom 10mm clears FRAIS COURANTS past the window bottom. */
  .cl-block { margin-bottom: 6mm; padding-left: 4px; }
  .cl-lbl { font-size: 7pt; color: #888; text-transform: uppercase; letter-spacing: 0.3px; margin-bottom: 1px; }
  .cl-name { font-size: 11pt; font-weight: 700; color: #222; line-height: 1.15; }
  .cl-addr { font-size: 10pt; color: #333; line-height: 1.2; }

  /* ── Left: summary ── */
  .summary-hdr { font-size: 8pt; font-weight: 700; color: #111;
                 background-color: #eef9f3; print-color-adjust: exact; -webkit-print-color-adjust: exact;
                 padding: 3px 6px; margin-bottom: 2px; line-height: 1; }
  .section-hdr { font-weight: 600; font-size: 7.5pt; padding-top: 3px; margin-bottom: 1px; }
  .addr-hdr { font-weight: 600; font-size: 6.5pt; color: #111;
              background-color: #eef9f3; print-color-adjust: exact; -webkit-print-color-adjust: exact;
              padding: 2px 6px; margin-top: 3px; line-height: 1.2; }
  .addr-hdr .addr-period { color: #555; font-weight: 400; font-size: 6pt; margin-left: 3px; }
  /* table-layout: auto so colspan="2" sep rows span cleanly without
     Chromium forcing a phantom column boundary at the 86% mark. */
  .dtl { width: 100%; border-collapse: collapse; font-size: 7.5pt; margin-bottom: 2px; line-height: 1.35; }
  .dtl th.r, .dtl td.r { text-align: right; white-space: nowrap; }
  /* Item rows — white background; green is reserved for section/address headers. */
  .dtl td { padding: 3px 6px 1px !important; vertical-align: baseline; line-height: 1.35; }
  /* Separator row: block-level <div class="sep-line"> draws the dotted line —
     immune to border-collapse column-splitting, one continuous vector line. */
  .dtl tr.sep td { height: 3px; padding: 0 !important; line-height: 0; font-size: 0; border: none !important; }
  .sep-line { border-top: 1px solid #e5e7eb; height: 0; margin: 0; padding: 0; }
  /* Description column wraps instead of ellipsis-truncating. */
  .dtl td:first-child { white-space: normal; word-break: break-word; }
  .qty-inline { color: #888; font-size: 7pt; }
  .dtl tr.credit td { color: #019547; }
  /* Totals block: gray background for subtotal + grand total rows. */
  .dtl tr.stot td { font-weight: 600; border-top: 1px solid #d1d5db; padding-top: 3px !important;
                    background: #f3f4f6 !important; print-color-adjust: exact; -webkit-print-color-adjust: exact; }
  .dtl tr.tax td { color: #666; font-size: 6.5pt; padding: 1px 6px !important; background: #fff !important; }
  .dtl tr.grand td { font-weight: 700; font-size: 8pt; border-top: 1px solid #019547; padding-top: 4px !important;
                     background: #e5e7eb !important; print-color-adjust: exact; -webkit-print-color-adjust: exact; }
  /* Keep totals block together so an orphan row never lands alone on page 2. */
  .dtl.totals { page-break-inside: avoid; break-inside: avoid; }
  .dtl tr.taxnums-row td { font-size: 6pt; color: #999; padding: 2px 6px 3px !important; text-align: left; background: #fff !important; }

  /* ── Right column: all content in a bordered container ── */
  .r-container { border: 1px solid #e5e7eb; border-left: 2px solid #019547; }
  .r-section { padding: 6px 8px; }
  .r-section.r-meta { padding: 4px 8px; background: #f8fafc; }
  /* r-meta: label | value rows (stacked) — fits in the narrow 45mm col-r
     where the old 3-column side-by-side layout couldn't. Label left,
     value right, each pair on its own row. */
  .r-meta-table { width: 100%; border-collapse: collapse; }
  .r-meta-table td { vertical-align: baseline; line-height: 1.25; padding: 1px 0; font-size: 7pt; }
  .r-meta-table td.ml { color: #888; font-size: 5.5pt; text-transform: uppercase; letter-spacing: 0.3px; text-align: left; }
  .r-meta-table td.mv { font-weight: 700; color: #333; white-space: nowrap; text-align: right; }
  .r-section + .r-section { border-top: 1px solid #e5e7eb; }
  .r-section.r-green { background: #e8f5ee; }
  .r-section.r-gray { background: #f8f9fa; }
  /* Amount due */
  .ab-label { font-size: 8.5pt; font-weight: 600; color: #019547; line-height: 1.1; }
  .ab-val { font-size: 18pt; font-weight: 700; color: #222; text-align: right; white-space: nowrap; line-height: 1.05; }
  .ab-date { font-size: 7pt; color: #555; margin-top: 1px; }
  .ab-table { width: 100%; border-collapse: collapse; }
  .ab-table td { padding: 0; border: none; vertical-align: middle; }
  /* QR */
  .qr-table { width: 100%; border-collapse: collapse; }
  .qr-table td { padding: 1px 6px; border: none; vertical-align: middle; font-size: 7.5pt; line-height: 1.2; }
  .qr-placeholder { width: 40px; height: 40px; background: #ccc; font-size: 5pt; color: #888; text-align: center; line-height: 40px; }
  /* Info block */
  .r-info-text { font-size: 6.5pt; color: #555; line-height: 1.25; }
  .r-info-text strong { color: #333; }
  .r-info-text .ri-title { font-weight: 700; font-size: 7pt; color: #333; margin-bottom: 2px; }
  .r-section.r-cprst { font-size: 5.5pt; padding: 3px 10px; color: #888; background: #fafbfc; border-top: 1px solid #e5e7eb; line-height: 1.2; }
  /* Referral program — light gray background, green accent on the code */
  .r-section.r-referral { background: #f8fafc; font-size: 7pt; color: #333; line-height: 1.2; }
  .r-section.r-referral .ri-title { font-weight: 700; font-size: 7.5pt; color: #019547; margin-bottom: 2px; }
  .ref-code-box { display: flex; align-items: center; gap: 8px; background: #fff; border: 1.5px solid #019547; border-radius: 4px; padding: 3px 8px; margin-top: 3px; box-shadow: 0 0 0 2px #e8f5ee; }
  .ref-code-label { font-size: 6pt; color: #888; text-transform: uppercase; letter-spacing: 0.3px; }
  .ref-code { font-size: 13pt; font-weight: 700; color: #019547; letter-spacing: 2px; font-family: 'SF Mono', 'Menlo', 'Courier New', monospace; margin-left: auto; line-height: 1.1; }
  .r-section.r-referral small { display: block; font-size: 5.5pt; color: #019547; margin-top: 3px; font-weight: 600; }

  /* ── Footer ── */
  .footer-line { font-size: 6pt; color: #bbb; text-align: center; margin-top: 8px; }
</style>
</head>
<body>

<!-- ═══════ HEADER & MAIN 2 COLUMNS ═══════ -->
<div>
  <table class="main"><tr>

  <!-- ── LEFT 58% (~105mm = 4.13") ── -->
  <td class="col-l">

    <!-- ── FIXED-HEIGHT HEADER ZONE ──
         Logo + optional SOMMAIRE DU COMPTE live here. Height is HARDCODED
         regardless of SOMMAIRE presence, so the address block below ALWAYS
         lands at the same Y. Robust to variations (no prev invoice / etc.).
         30mm + @page margin 10mm + cl-block margin 3mm + FACTURER label
         = "Les..." at ~153pt (2.13") — matches v4 reference pixel-perfect. -->
    <div class="top-zone" style="height: 30mm; overflow: hidden;">
    <div style="height: 26px;">
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 326.39 70.35" style="height: 26px; width: auto;"><defs><style>.cls-1{fill:#019547;}</style></defs><path class="cls-1" d="M25.83,15H8.41a4.14,4.14,0,0,1-3.89-2.71L0,1.18H66.59L62.07,12.27A4.14,4.14,0,0,1,58.18,15H40.76V69.19H25.81Z"/><path class="cls-1" d="M90.74.68h18.55a5.63,5.63,0,0,1,5.63,5.6l.26,62.91H99.55L99.76,54H71.87L66.41,65.51a6.45,6.45,0,0,1-5.84,3.68H49.35L73.53,12.11A18.7,18.7,0,0,1,90.74.68M100,40.73l.07-26h-8a6.75,6.75,0,0,0-6.26,4.18L76.73,40.73Z"/><path class="cls-1" d="M124.51,6.81a5.64,5.64,0,0,1,5.65-5.65H155.6c8.64,0,15.37,2.44,19.81,6.91,3.81,3.78,5.84,9.14,5.84,15.53v.18c0,11-5.92,17.87-14.59,21.1l16.61,24.29h-14a6.51,6.51,0,0,1-5.39-2.87L151.21,47.41H139.44V69.17h-15Zm30.12,27.41c7.28,0,11.45-3.89,11.45-9.62v-.21c0-6.42-4.46-9.7-11.74-9.7H139.46V34.22Z"/><path class="cls-1" d="M184.74,35.37v-.18C184.74,15.85,199.8,0,220.4,0,232.65,0,240,3.31,247.13,9.33l-6.28,7.57a5.18,5.18,0,0,1-6.84,1,23.71,23.71,0,0,0-14.11-4.13c-10.88,0-19.52,9.62-19.52,21.18v.19c0,12.43,8.54,21.57,20.6,21.57a24,24,0,0,0,14.09-4.07V42.91h-8.68A6.41,6.41,0,0,1,220,36.53V30h29.54V59.52a44,44,0,0,1-29,10.78c-21.18,0-35.74-14.8-35.74-34.93"/><path class="cls-1" d="M254.09,35.37v-.18C254.09,15.85,269.33,0,290.33,0s36.06,15.66,36.06,35v.18c0,19.34-15.27,35.19-36.24,35.19s-36.06-15.64-36.06-35m56.63,0v-.18c0-11.67-8.54-21.39-20.6-21.39S269.73,23.34,269.73,35v.18c0,11.67,8.54,21.37,20.6,21.37S310.72,47,310.72,35.37"/></svg>
    </div>
    <!-- Pre-envelope-window zone — only rendered if there is a previous
         invoice. Hidden by the envelope when folded; first thing the
         customer reads once opened. -->
    {% if prev_invoice_date and prev_invoice_date != '—' %}
    <div class="pre-window">
      <div class="pw-title">{% if _is_en %}ACCOUNT SUMMARY{% else %}SOMMAIRE DU COMPTE{% endif %}</div>
      <table class="pw-table">
        <tr>
          <td>{% if _is_en %}Previous balance{% else %}Solde précédent{% endif %} ({{ prev_invoice_date }})</td>
          <td class="r">$ {{ prev_invoice_total }}</td>
        </tr>
        {% for payment in recent_payments %}
        <tr class="pmt"><td>{% if _is_en %}Payment received — thank you{% else %}Paiement reçu — merci{% endif %}</td><td class="r">− $ {{ payment.amount }}</td></tr>
        {% endfor %}
        <tr class="bal"><td>{% if _is_en %}Carried forward{% else %}Solde reporté{% endif %}</td><td class="r">$ {{ remaining_balance }}</td></tr>
      </table>
    </div>
    {% endif %}
    </div><!-- /top-zone -->

    <!-- Address block — ALWAYS at same Y thanks to the fixed-height
         top-zone above. With @page margin-top=10mm + top-zone 38mm + this
         margin-top 3mm → "Les..." renders at ~51mm = 2" from page top
         edge (envelope window opening). -->
    <div class="cl-block" style="margin-top: 3mm;">
      <div class="cl-lbl">{% if _is_en %}Bill to{% else %}Facturer &agrave;{% endif %}</div>
      <div class="cl-name">{{ client_name }}</div>
      <div class="cl-addr">
        {{ client_address_line1 }}<br>
        {{ client_address_line2 }}
      </div>
    </div>

    <!-- Current charges -->
    <div class="summary-hdr">{% if _is_en %}CURRENT CHARGES{% else %}FRAIS COURANTS{% endif %}</div>

    {% for location in current_charges_locations %}
    {#- Collect distinct non-empty periods from this location's items. If
        they're all the same, show once in the address header. If they
        differ (rare), append to each item's description as before. -#}
    {%- set _periods = [] -%}
    {%- for _it in location['items'] -%}
      {%- if _it.period_str and _it.period_str not in _periods -%}
        {%- set _ = _periods.append(_it.period_str) -%}
      {%- endif -%}
    {%- endfor -%}
    <div class="addr-hdr">&#9678; {{ location.name }}{% if _periods|length == 1 %} <span class="addr-period">· {{ _periods[0] }}</span>{% endif %}</div>
    <table class="dtl">
      <colgroup>
        <col style="width:86%">
        <col style="width:14%">
      </colgroup>
      <tbody>
        {% for item in location['items'] %}
        <tr class="{% if item.is_credit %}credit{% endif %}">
          <td>{{ item.description }}{% if _periods|length > 1 and item.period_str %} <span class="qty-inline">({{ item.period_str }})</span>{% endif %}{% if item.qty and item.qty|string not in ('1', '1.0') %} <span class="qty-inline">(×{{ item.qty }})</span>{% endif %}</td>
          <td class="r">{{ item.amount }}</td>
        </tr>
        <tr class="sep"><td colspan="2"><div class="sep-line"></div></td></tr>
        {% endfor %}
      </tbody>
    </table>
    {% endfor %}

    <table class="dtl totals" style="margin-top:3px;">
      <colgroup><col style="width:82%"><col style="width:18%"></colgroup>
      <tr class="stot"><td>{% if _is_en %}Subtotal before tax{% else %}Sous-total avant taxes{% endif %}</td><td class="r">$ {{ subtotal_before_taxes }}</td></tr>
      <tr class="tax"><td>{% if _is_en %}Tax: GST ${{ tps_amount }} · QST ${{ tvq_amount }}{% else %}Taxes : TPS ${{ tps_amount }} · TVQ ${{ tvq_amount }}{% endif %}</td><td class="r">$ {{ total_taxes }}</td></tr>
      <tr class="grand"><td>TOTAL</td><td class="r">$ {{ current_charges_total }}</td></tr>
      <tr class="taxnums-row"><td colspan="2">TPS: 834975559RT0001 &nbsp;|&nbsp; TVQ: 1213765929TQ0001</td></tr>
    </table>
  </td>

  <!-- ── SPACER: 2% of content width ≈ 3.6mm ≈ 1/7 inch.
       Padding on this td is zeroed by the blanket `.print-format td { padding: 0 }`
       rule so the declared 2% width actually delivers the visible gap. -->
  <td class="col-spacer"></td>

  <!-- ── RIGHT 40%: all in one bordered container ── -->
  <td class="col-r">
    <!-- Document title: FACTURE / INVOICE / NOTE DE CRÉDIT / CREDIT NOTE
         driven by _is_en + doc.is_return set in the Jinja prelude above. -->
    <div style="text-align: right; margin-bottom: 3px;">
      <div class="doc-label" style="line-height:1;">{{ doc_title }}</div>
    </div>

    <div class="r-container">

      <!-- Facture identifiers: stacked label|value rows so a long account
           number ("C-041452257623184") doesn't force col-r wider than the
           60mm target. Values right-aligned so the digits line up visually. -->
      <div class="r-section r-meta">
        <table class="r-meta-table">
          <tr><td class="ml">{% if _is_en %}Invoice #{% else %}N&ordm; facture{% endif %}</td><td class="mv">{{ invoice_number }}</td></tr>
          <tr><td class="ml">Date</td><td class="mv">{{ invoice_date }}</td></tr>
          <tr><td class="ml">{% if _is_en %}Account #{% else %}N&ordm; compte{% endif %}</td><td class="mv">{{ account_number }}</td></tr>
        </table>
      </div>

      <!-- (Sommaire du compte moved to the LEFT pre-window zone —
           right column stays action-focused: amount + QR + contact.) -->

      <!-- Amount due -->
      <div class="r-section r-green">
        <table class="ab-table"><tr>
          <td><div class="ab-label">{% if _is_en %}Amount due{% else %}Montant d&ucirc;{% endif %}</div><div class="ab-date">{% if _is_en %}before{% else %}avant le{% endif %} {{ due_date }}</div></td>
          <td class="ab-val">$ {{ total_amount_due }}</td>
        </tr></table>
      </div>

      <!-- QR -->
      <div class="r-section r-green">
        <table class="qr-table"><tr>
          <td style="width:46px;">
            {% if qr_code_base64 %}
              <img class="qr-img" src="data:image/png;base64,{{ qr_code_base64 }}" style="width:40px !important; height:40px !important; max-width:40px !important;" />
            {% else %}
              <div class="qr-placeholder">QR</div>
            {% endif %}
          </td>
          <td><strong>{% if _is_en %}Pay online{% else %}Payez en ligne{% endif %}</strong><br>{% if _is_en %}Scan the QR code or visit{% else %}Scannez le code QR ou visitez{% endif %}<br><strong style="color:#019547">client.gigafibre.ca</strong></td>
        </tr></table>
      </div>

      <!-- Referral program — URL now lives INSIDE the descriptive sentence
           (as a call-to-action), not below the code box where it had no
           visual connection to the rest of the content. -->
      <div class="r-section r-referral">
        {% if _is_en %}
        Refer a friend and each receive <strong>$50 credit</strong> on your monthly bill &middot; <a href="https://targo.ca/parrainage" style="color:#019547; text-decoration:none; font-weight:600;">targo.ca/parrainage</a>
        {% else %}
        R&eacute;f&eacute;rez un ami et recevez <strong>chacun 50&nbsp;$ de cr&eacute;dit</strong> sur votre facture mensuelle &middot; <a href="https://targo.ca/parrainage" style="color:#019547; text-decoration:none; font-weight:600;">targo.ca/parrainage</a>
        {% endif %}
        <div class="ref-code-box">
          <span class="ref-code-label">{% if _is_en %}Your code{% else %}Votre code{% endif %}</span>
          <span class="ref-code">{{ referral_code }}</span>
        </div>
      </div>

      <!-- Notices & conditions -->
      <div class="r-section r-info-text">
        {% if _is_en %}
        By paying this invoice, you accept our <a href="https://www.targo.ca/conditions" style="color:#019547; text-decoration:none;">terms and conditions</a>.<br><br>
        Please note that any invoice not paid by the due date will be subject to a late payment fee.
        {% else %}
        En payant cette facture, vous acceptez nos <a href="https://www.targo.ca/conditions" style="color:#019547; text-decoration:none;">termes et conditions</a>.<br><br>
        Prendre note que toute facture non acquitt&eacute;e &agrave; la date d'&eacute;ch&eacute;ance sera assujettie &agrave; des frais de retard.
        {% endif %}
      </div>

      <!-- Contact info + TARGO coordinates -->
      <div class="r-section r-gray r-info-text">
        <div class="ri-title">{% if _is_en %}Contact us{% else %}Contactez-nous{% endif %}</div>
        <strong>TARGO Communications Inc.</strong><br>
        1867 chemin de la Rivi&egrave;re<br>
        Sainte-Clotilde, QC J0L 1W0<br>
        <strong>855 888-2746</strong> &middot; {% if _is_en %}Mon-Fri 8am-5pm{% else %}Lun-Ven 8h-17h{% endif %}<br>
        info@targo.ca &bull; www.targo.ca
      </div>

      <!-- CPRST notice (required by law, kept compact) -->
      <div class="r-section r-info-text r-cprst">
        {% if _is_en %}
        Complaint about your telecom or TV service? The CCTS can help you for free: <strong>www.ccts-cprst.ca</strong>.
        {% else %}
        Plainte relative &agrave; votre service de t&eacute;l&eacute;communication ou de t&eacute;l&eacute;vision? La CPRST peut vous aider sans frais&nbsp;: <strong>www.ccts-cprst.ca</strong>.
        {% endif %}
      </div>

    </div>
  </td>

</tr></table>
</div>

<!-- (Company address moved to the Contactez-nous section in the right column) -->

</body>
</html>

"""

# Print Format options shared between create/update paths. pdf_generator="chrome"
# uses Chromium headless for pixel-accurate modern-CSS rendering (matches the
# Antigravity / browser preview). Chromium must be installed in the container;
# see /opt/erpnext/custom/Dockerfile (apt-get install chromium) and
# common_site_config.json → "chromium_path": "/usr/bin/chromium".
#
# Margins are explicit — Chrome PDF generator IGNORES `@page { margin: … }`
# in the CSS (preferCSSPageSize=False). We keep 5mm top/bottom so the client
# address stays aligned with the #10 envelope window (~2" from top edge).
_PF_OPTIONS = {
    "print_format_type": "Jinja",
    "standard": "No",
    "custom_format": 1,
    "pdf_generator": "chrome",
    "margin_top": 5,
    "margin_bottom": 5,
    "margin_left": 15,
    "margin_right": 15,
    "disabled": 0,
}

# ── Create or update the Print Format ──
if frappe.db.exists("Print Format", PRINT_FORMAT_NAME):
    pf = frappe.get_doc("Print Format", PRINT_FORMAT_NAME)
    pf.html = html_template
    for k, v in _PF_OPTIONS.items():
        setattr(pf, k, v)
    pf.save(ignore_permissions=True)
    print(f"  Updated Print Format: {PRINT_FORMAT_NAME} ({len(html_template)} bytes)")
else:
    pf = frappe.get_doc({
        "doctype": "Print Format",
        "name": PRINT_FORMAT_NAME,
        "__newname": PRINT_FORMAT_NAME,
        "doc_type": "Sales Invoice",
        "module": "Accounts",
        **_PF_OPTIONS,
        "html": html_template,
        "default_print_language": "fr",
    })
    pf.insert(ignore_permissions=True)
    print(f"  Created Print Format: {PRINT_FORMAT_NAME} ({len(html_template)} bytes)")

# ── Set as default print format for Sales Invoice ──
try:
    frappe.db.set_value(
        "Property Setter", None, "value", PRINT_FORMAT_NAME,
        {
            "doctype_or_field": "DocType",
            "doc_type": "Sales Invoice",
            "property": "default_print_format",
        },
    )
except Exception as e:
    print(f"  (info) could not set default print format: {e}")

# ── Disable the legacy Server Script "invoice_qr_code" if still around ──
# QR generation now lives in the custom app `gigafibre_utils` (whitelisted
# method `gigafibre_utils.api.invoice_qr_base64`). The old Server Script
# crashed under safe_exec because it tried `import qrcode`.
if frappe.db.exists("Server Script", "invoice_qr_code"):
    ss = frappe.get_doc("Server Script", "invoice_qr_code")
    if not ss.disabled:
        ss.disabled = 1
        ss.save(ignore_permissions=True)
        print("  Disabled legacy Server Script: invoice_qr_code "
              "(superseded by gigafibre_utils.api.invoice_qr_base64)")

frappe.db.commit()
print(f"\n  Done. Print Format '{PRINT_FORMAT_NAME}' ready.")
print("  Dependency: custom app `gigafibre_utils` must be installed "
      "(bench --site erp.gigafibre.ca install-app gigafibre_utils).")
print("  Preview: ERPNext → Sales Invoice → Print → Select 'Facture TARGO'")
