// Billing & Payments — Dev handoff presentation
// Regenerate with: cd docs && node build-billing-pptx.js
//
// Structure (per user feedback 2026-04-17):
//   1. Title
//   2. End-to-end flow (facture → paiement) with scripts listed under each stage
//   3. OPS app modules with scripts/components per module
//   4. PDF preview + pay-public preview (aspect ratios preserved)

const fs = require("fs");
const path = require("path");
const pptxgen = require("pptxgenjs");
const sharp = require("sharp");

function imgPath(rel) {
  const p = path.join(__dirname, rel);
  return fs.existsSync(p) ? p : null;
}
async function imgRatio(rel) {
  const p = path.join(__dirname, rel);
  if (!fs.existsSync(p)) return null;
  const m = await sharp(p).metadata();
  return { w: m.width, h: m.height, ratio: m.width / m.height, path: p };
}
// Fit an image inside a target box (tx, ty, tw, th) maintaining its native ratio
function fitImage(meta, tx, ty, tw, th) {
  const boxR = tw / th;
  let w, h;
  if (meta.ratio > boxR) { w = tw; h = tw / meta.ratio; }
  else                   { h = th; w = th * meta.ratio; }
  const x = tx + (tw - w) / 2;
  const y = ty + (th - h) / 2;
  return { path: meta.path, x, y, w, h };
}

const C = {
  darkBg:   "0D0F18",
  cardBg:   "181C2E",
  cardBg2:  "111422",
  purple:   "5C59A8",
  purpleDk: "3F3D7A",
  accent:   "818CF8",
  green:    "22C55E",
  amber:    "F59E0B",
  red:      "EF4444",
  white:    "FFFFFF",
  text:     "E2E4EF",
  muted:    "7B80A0",
  indigo:   "6366F1",
};
const shadow = () => ({ type: "outer", blur: 8, offset: 3, angle: 135, color: "000000", opacity: 0.3 });

let pres;

function header(s, title, subtitle) {
  s.background = { color: C.darkBg };
  s.addShape(pres.shapes.RECTANGLE, { x: 0, y: 0, w: 10, h: 0.06, fill: { color: C.purple } });
  s.addText(title, { x: 0.6, y: 0.22, w: 9, h: 0.6, fontSize: 26, fontFace: "Arial Black", color: C.white, margin: 0 });
  if (subtitle) s.addText(subtitle, { x: 0.6, y: 0.76, w: 9, h: 0.3, fontSize: 11, fontFace: "Calibri", color: C.muted, margin: 0 });
}

async function build() {
  pres = new pptxgen();
  pres.layout = "LAYOUT_16x9";
  pres.author = "Targo";
  pres.title = "Facturation & Paiements — Handoff dev";

  // ═════════ SLIDE 1 — Title ═════════
  let s = pres.addSlide();
  s.background = { color: C.darkBg };
  s.addShape(pres.shapes.RECTANGLE, { x: 0, y: 0, w: 10, h: 0.06, fill: { color: C.purple } });
  s.addShape(pres.shapes.RECTANGLE, { x: 0.8, y: 1.2, w: 0.12, h: 1.8, fill: { color: C.indigo } });
  s.addText("Facturation", { x: 1.15, y: 1.2, w: 8, h: 0.7, fontSize: 42, fontFace: "Arial Black", color: C.white, bold: true, margin: 0 });
  s.addText("& Paiements", { x: 1.15, y: 1.85, w: 8, h: 0.6, fontSize: 36, fontFace: "Arial Black", color: C.accent, margin: 0 });
  s.addText("Handoff technique — pipeline end-to-end + modules OPS", { x: 1.15, y: 2.7, w: 8.5, h: 0.4, fontSize: 16, fontFace: "Calibri", color: C.muted, margin: 0 });

  const heroStats = [
    { v: "80",     l: "Scripts migration" },
    { v: "115 K",  l: "Factures importées" },
    { v: "1,6 M",  l: "Items (incl. backfill)" },
    { v: "15",     l: "Pages OPS" },
  ];
  heroStats.forEach((st, i) => {
    const sx = 1.15 + i * 2.1;
    s.addShape(pres.shapes.RECTANGLE, { x: sx, y: 3.7, w: 1.8, h: 1.0, fill: { color: C.cardBg }, shadow: shadow() });
    s.addText(st.v, { x: sx, y: 3.72, w: 1.8, h: 0.55, fontSize: 22, fontFace: "Arial Black", color: C.accent, align: "center", valign: "middle", margin: 0 });
    s.addText(st.l, { x: sx, y: 4.25, w: 1.8, h: 0.35, fontSize: 10, fontFace: "Calibri", color: C.muted, align: "center", margin: 0 });
  });
  s.addText("Avril 2026 • erp.gigafibre.ca + client.gigafibre.ca + ops", { x: 0.8, y: 5.1, w: 9, h: 0.3, fontSize: 10, fontFace: "Calibri", color: C.muted, margin: 0 });

  // ═════════ SLIDE 2 — End-to-end flow with scripts per stage ═════════
  s = pres.addSlide();
  header(s, "Flux facture → paiement", "Scripts & fichiers utilisés à chaque étape");

  // 6 stages laid out left-to-right, each with a card + scripts list below
  const stages = [
    {
      title: "1. Import legacy",
      tagline: "MariaDB → PostgreSQL",
      color: C.purple,
      scripts: [
        "import_invoices.py",
        "import_payments.py",
        "import_payment_methods.py",
        "import_payment_arrangements.py",
        "import_expro_payments.py",
        "backfill_service_location.py",
        "fix_invoice_outstanding.py",
        "fix_invoice_customer_names.py",
        "add_missing_custom_fields.py",
      ],
    },
    {
      title: "2. Facture",
      tagline: "ERPNext Sales Invoice",
      color: C.accent,
      scripts: [
        "setup_invoice_print_format.py",
        "test_jinja_render.py",
        "Print Format: « Facture TARGO »",
        "Jinja template (inline)",
        "pdf_generator=\"chrome\"",
        "Doctype: Sales Invoice",
        "Custom field: service_location",
      ],
    },
    {
      title: "3. Rendu PDF",
      tagline: "Chromium --print-to-pdf",
      color: C.green,
      scripts: [
        "gigafibre_utils.api.invoice_pdf",
        "Dockerfile (chromium install)",
        "common_site_config:",
        "  chromium_path=/usr/bin/chromium",
        "cairosvg → logo PNG",
        "Producer: Skia/PDF m147",
        "~1 s / facture",
      ],
    },
    {
      title: "4. QR / Magic-link",
      tagline: "Tokens HMAC-SHA256",
      color: C.indigo,
      scripts: [
        "api.invoice_qr(invoice)",
        "api.invoice_qr_datauri(invoice)",
        "api._sign_pay_token",
        "api.validate_pay_token",
        "api.request_magic_link",
        "api.pay_token  (admin)",
        "api.pay_redirect  (portal)",
        "Secret: gigafibre_pay_secret",
      ],
    },
    {
      title: "5. Landing publique",
      tagline: "client.gigafibre.ca/pay-public",
      color: C.amber,
      scripts: [
        "gigafibre_utils/www/",
        "  pay-public.html",
        "/opt/traefik/dynamic/",
        "  pay-public.yml",
        "Traefik priority ≥ 250",
        "(carve-out Authentik)",
        "Vanilla JS, pas de build",
      ],
    },
    {
      title: "6. Stripe + Payment Entry",
      tagline: "Checkout → webhook → réconciliation",
      color: C.red,
      scripts: [
        "api.create_checkout_session",
        "api.stripe_webhook",
        "api._stripe_post",
        "api._stripe_verify_signature",
        "Secret: gigafibre_stripe_secret_key",
        "Secret: gigafibre_stripe_webhook_secret",
        "Twilio: twilio_account_sid / token / from",
      ],
    },
  ];

  const cardW = 1.5, cardH = 0.7, gap = 0.08;
  const totalW = stages.length * cardW + (stages.length - 1) * gap;
  const startX = (10 - totalW) / 2;
  const cardY = 1.3;

  stages.forEach((st, i) => {
    const x = startX + i * (cardW + gap);
    s.addShape(pres.shapes.RECTANGLE, {
      x, y: cardY, w: cardW, h: cardH,
      fill: { color: C.cardBg }, line: { color: st.color, width: 1.5 }, shadow: shadow(),
    });
    s.addText(st.title, { x, y: cardY + 0.03, w: cardW, h: 0.32, fontSize: 10, fontFace: "Arial Black", color: C.white, align: "center", valign: "middle", margin: 0 });
    s.addText(st.tagline, { x, y: cardY + 0.35, w: cardW, h: 0.3, fontSize: 7.5, fontFace: "Calibri", color: st.color, align: "center", valign: "middle", margin: 0 });

    // Arrow to next stage
    if (i < stages.length - 1) {
      s.addText("→", { x: x + cardW - 0.05, y: cardY + 0.18, w: gap + 0.1, h: 0.35, fontSize: 14, color: C.muted, align: "center", valign: "middle", margin: 0, bold: true });
    }

    // Scripts list under the card
    const listY = cardY + cardH + 0.15;
    s.addShape(pres.shapes.RECTANGLE, {
      x, y: listY, w: cardW, h: 3.3,
      fill: { color: C.cardBg2 }, line: { color: st.color, width: 0.5 },
    });
    const txt = st.scripts.map(sc => "• " + sc).join("\n");
    s.addText(txt, {
      x: x + 0.04, y: listY + 0.05, w: cardW - 0.08, h: 3.2,
      fontSize: 6.8, fontFace: "Courier New", color: C.text, valign: "top", margin: 0,
      paraSpaceAfter: 2,
    });
  });

  // Footer: common secrets
  s.addShape(pres.shapes.RECTANGLE, { x: 0.4, y: 5.5, w: 9.2, h: 0.8, fill: { color: "1a1a2e" }, line: { color: C.purple, width: 0.5 } });
  s.addText("Configuration (common_site_config.json)", { x: 0.55, y: 5.55, w: 9, h: 0.2, fontSize: 9, fontFace: "Calibri", color: C.accent, bold: true, margin: 0 });
  s.addText("gigafibre_pay_secret · gigafibre_pay_host · gigafibre_stripe_secret_key · gigafibre_stripe_webhook_secret · twilio_account_sid · twilio_auth_token · twilio_from_number · chromium_path", {
    x: 0.55, y: 5.8, w: 9, h: 0.45, fontSize: 8, fontFace: "Courier New", color: C.text, margin: 0,
  });

  // ═════════ SLIDE 3 — OPS modules with scripts/components ═════════
  s = pres.addSlide();
  header(s, "Modules OPS", "Pages & composants · apps/ops/src/");

  const modules = [
    {
      title: "Clients",
      color: C.purple,
      files: [
        "pages/ClientsPage.vue",
        "pages/ClientDetailPage.vue",
        "components/customer/*",
        "components/shared/",
        "  detail-sections/",
        "    InvoiceDetail.vue",
        "    PaymentDetail.vue",
        "    SubscriptionDetail.vue",
        "    EquipmentDetail.vue",
        "    IssueDetail.vue",
      ],
    },
    {
      title: "Dispatch",
      color: C.amber,
      files: [
        "pages/DispatchPage.vue",
        "components/dispatch/",
        "pages/dispatch-styles.scss",
        "mode sombre (exception)",
      ],
    },
    {
      title: "Facturation",
      color: C.green,
      files: [
        "shared/CreateInvoiceModal.vue",
        "shared/detail-sections/",
        "  InvoiceDetail.vue",
        "    onglet Aperçu client",
        "    iframe → invoice_pdf",
        "shared/detail-sections/",
        "  PaymentDetail.vue",
      ],
    },
    {
      title: "Rapports",
      color: C.indigo,
      files: [
        "pages/RapportsPage.vue",
        "pages/ReportARPage.vue",
        "pages/ReportRevenuPage.vue",
        "pages/ReportTaxesPage.vue",
        "pages/ReportVentesPage.vue",
      ],
    },
    {
      title: "Network",
      color: C.accent,
      files: [
        "pages/NetworkPage.vue",
        "pages/TelephonyPage.vue",
        "pages/OcrPage.vue",
      ],
    },
    {
      title: "Équipe & Tickets",
      color: C.red,
      files: [
        "pages/EquipePage.vue",
        "pages/TicketsPage.vue",
        "pages/AgentFlowsPage.vue",
        "pages/DashboardPage.vue",
        "pages/SettingsPage.vue",
      ],
    },
  ];

  const cols = 3, rows = 2;
  const mW = 2.9, mH = 2.25;
  const mGapX = 0.15, mGapY = 0.15;
  const mStartX = (10 - (cols * mW + (cols - 1) * mGapX)) / 2;
  const mStartY = 1.3;

  modules.forEach((mod, i) => {
    const cx = mStartX + (i % cols) * (mW + mGapX);
    const cy = mStartY + Math.floor(i / cols) * (mH + mGapY);
    s.addShape(pres.shapes.RECTANGLE, {
      x: cx, y: cy, w: mW, h: mH,
      fill: { color: C.cardBg }, line: { color: mod.color, width: 1.5 }, shadow: shadow(),
    });
    s.addShape(pres.shapes.RECTANGLE, { x: cx, y: cy, w: mW, h: 0.35, fill: { color: mod.color } });
    s.addText(mod.title, { x: cx + 0.1, y: cy, w: mW - 0.2, h: 0.35, fontSize: 12, fontFace: "Arial Black", color: C.white, valign: "middle", margin: 0 });
    s.addText(mod.files.map(f => "• " + f).join("\n"), {
      x: cx + 0.1, y: cy + 0.42, w: mW - 0.2, h: mH - 0.5,
      fontSize: 8.5, fontFace: "Courier New", color: C.text, valign: "top", margin: 0,
    });
  });

  // Footer: deploy note
  s.addShape(pres.shapes.RECTANGLE, { x: 0.4, y: mStartY + 2 * (mH + mGapY) + 0.1, w: 9.2, h: 0.5, fill: { color: "1a1a2e" }, line: { color: C.amber, width: 0.5 } });
  s.addText("Déploiement: scp dist/spa/* → /opt/ops-app/  ·  nginx proxy API (token)  ·  Authentik SSO  ·  Traefik routing", {
    x: 0.55, y: mStartY + 2 * (mH + mGapY) + 0.18, w: 9, h: 0.4, fontSize: 9, fontFace: "Calibri", color: C.text, margin: 0,
  });

  // ═════════ SLIDE 4 — Visuels : PDF facture + landing pay-public ═════════
  s = pres.addSlide();
  header(s, "Visuels", "PDF Facture TARGO (Chrome)  +  Landing /pay-public");

  const invoiceMeta = await imgRatio("assets/screenshots/invoice-pdf.png");
  const payMeta     = await imgRatio("assets/screenshots/pay-public.png");

  // Two equal 4.5" × 4.5" boxes, images fit maintaining ratio
  const box = { w: 4.3, h: 4.4, y: 1.2 };
  const leftX  = 0.4;
  const rightX = 5.3;

  s.addShape(pres.shapes.RECTANGLE, { x: leftX, y: box.y, w: box.w, h: box.h, fill: { color: C.white }, line: { color: C.purple, width: 1 } });
  if (invoiceMeta) {
    const fit = fitImage(invoiceMeta, leftX + 0.05, box.y + 0.05, box.w - 0.1, box.h - 0.1);
    s.addImage({ path: fit.path, x: fit.x, y: fit.y, w: fit.w, h: fit.h });
  }
  s.addText("PDF Facture TARGO", { x: leftX, y: box.y + box.h + 0.05, w: box.w, h: 0.3, fontSize: 11, fontFace: "Calibri", color: C.white, bold: true, align: "center", margin: 0 });
  s.addText("Chromium --print-to-pdf · 1 page · ~1 s", { x: leftX, y: box.y + box.h + 0.32, w: box.w, h: 0.25, fontSize: 9, fontFace: "Calibri", color: C.muted, align: "center", margin: 0 });

  s.addShape(pres.shapes.RECTANGLE, { x: rightX, y: box.y, w: box.w, h: box.h, fill: { color: C.white }, line: { color: C.accent, width: 1 } });
  if (payMeta) {
    const fit = fitImage(payMeta, rightX + 0.05, box.y + 0.05, box.w - 0.1, box.h - 0.1);
    s.addImage({ path: fit.path, x: fit.x, y: fit.y, w: fit.w, h: fit.h });
  }
  s.addText("Landing /pay-public", { x: rightX, y: box.y + box.h + 0.05, w: box.w, h: 0.3, fontSize: 11, fontFace: "Calibri", color: C.white, bold: true, align: "center", margin: 0 });
  s.addText("Bypass Authentik · Stripe Checkout · Magic-link SMS/email", { x: rightX, y: box.y + box.h + 0.32, w: box.w, h: 0.25, fontSize: 9, fontFace: "Calibri", color: C.muted, align: "center", margin: 0 });

  // Save
  const out = path.join(__dirname, "Gigafibre-Billing-Handoff.pptx");
  await pres.writeFile({ fileName: out });
  console.log("Wrote:", out);
}

build().catch(e => { console.error(e); process.exit(1); });
