// Build ANSLIB Logging Guide.docx — examples document for developers.
// Run: node build_anslib_logging_guide.js

const fs = require('fs');
const path = require('path');
const {
  Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell,
  AlignmentType, LevelFormat, HeadingLevel, BorderStyle, WidthType,
  ShadingType, PageOrientation
} = require('docx');

// -------- helpers ----------------------------------------------------------
const FONT_BODY = "Calibri";
const FONT_CODE = "Consolas";

function p(text, opts = {}) {
  return new Paragraph({
    spacing: { after: 120 },
    ...opts,
    children: [new TextRun({ text, font: FONT_BODY, size: 22, ...(opts.run || {}) })],
  });
}

function h1(text) {
  return new Paragraph({
    heading: HeadingLevel.HEADING_1,
    spacing: { before: 360, after: 200 },
    children: [new TextRun({ text, font: FONT_BODY, size: 36, bold: true, color: "1F3864" })],
  });
}

function h2(text) {
  return new Paragraph({
    heading: HeadingLevel.HEADING_2,
    spacing: { before: 280, after: 160 },
    children: [new TextRun({ text, font: FONT_BODY, size: 28, bold: true, color: "2E74B5" })],
  });
}

function h3(text) {
  return new Paragraph({
    heading: HeadingLevel.HEADING_3,
    spacing: { before: 200, after: 100 },
    children: [new TextRun({ text, font: FONT_BODY, size: 24, bold: true, color: "404040" })],
  });
}

// Single-line inline-code style (uses gray-ish background via shading isn't applied to runs;
// we just use a monospace font + light shading via a wrapping single-cell table when block).
function codeLine(text) {
  return new TextRun({ text, font: FONT_CODE, size: 20 });
}

// Multi-line code block — render as a one-cell table with light-gray fill,
// each source line becomes a paragraph inside the cell.
function codeBlock(source) {
  const lines = source.replace(/\r\n/g, "\n").split("\n");
  const paras = lines.map(line =>
    new Paragraph({
      spacing: { after: 0, line: 260 },
      children: [new TextRun({ text: line || " ", font: FONT_CODE, size: 20 })],
    })
  );
  return new Table({
    width: { size: 9360, type: WidthType.DXA },
    columnWidths: [9360],
    rows: [
      new TableRow({
        children: [
          new TableCell({
            width: { size: 9360, type: WidthType.DXA },
            shading: { fill: "F4F4F4", type: ShadingType.CLEAR },
            margins: { top: 120, bottom: 120, left: 180, right: 180 },
            borders: {
              top:    { style: BorderStyle.SINGLE, size: 4, color: "DDDDDD" },
              bottom: { style: BorderStyle.SINGLE, size: 4, color: "DDDDDD" },
              left:   { style: BorderStyle.SINGLE, size: 4, color: "DDDDDD" },
              right:  { style: BorderStyle.SINGLE, size: 4, color: "DDDDDD" },
            },
            children: paras,
          }),
        ],
      }),
    ],
  });
}

function bullet(text, runs = null) {
  return new Paragraph({
    numbering: { reference: "bullets", level: 0 },
    spacing: { after: 80 },
    children: runs || [new TextRun({ text, font: FONT_BODY, size: 22 })],
  });
}

function spacer() {
  return new Paragraph({ spacing: { after: 120 }, children: [new TextRun(" ")] });
}

// ---- table builder for the cheat sheets -----------------------------------
const CELL_BORDER = { style: BorderStyle.SINGLE, size: 4, color: "BFBFBF" };
const CELL_BORDERS = { top: CELL_BORDER, bottom: CELL_BORDER, left: CELL_BORDER, right: CELL_BORDER };

function headerCell(text, width) {
  return new TableCell({
    width: { size: width, type: WidthType.DXA },
    borders: CELL_BORDERS,
    shading: { fill: "1F3864", type: ShadingType.CLEAR },
    margins: { top: 100, bottom: 100, left: 140, right: 140 },
    children: [new Paragraph({
      children: [new TextRun({ text, font: FONT_BODY, size: 22, bold: true, color: "FFFFFF" })],
    })],
  });
}

function bodyCell(text, width, opts = {}) {
  const runs = Array.isArray(text)
    ? text
    : [new TextRun({ text, font: opts.code ? FONT_CODE : FONT_BODY, size: 20 })];
  return new TableCell({
    width: { size: width, type: WidthType.DXA },
    borders: CELL_BORDERS,
    shading: opts.shade ? { fill: opts.shade, type: ShadingType.CLEAR } : undefined,
    margins: { top: 80, bottom: 80, left: 140, right: 140 },
    children: [new Paragraph({ children: runs })],
  });
}

function sheetTable(columnWidths, headers, rows) {
  const total = columnWidths.reduce((a, b) => a + b, 0);
  return new Table({
    width: { size: total, type: WidthType.DXA },
    columnWidths,
    rows: [
      new TableRow({
        tableHeader: true,
        children: headers.map((h, i) => headerCell(h, columnWidths[i])),
      }),
      ...rows.map(row =>
        new TableRow({
          children: row.map((c, i) => {
            if (c && typeof c === 'object' && c.runs) {
              return bodyCell(c.runs, columnWidths[i], c.opts || {});
            }
            return bodyCell(c, columnWidths[i]);
          }),
        })
      ),
    ],
  });
}

// =========================================================================
// Document content
// =========================================================================
const children = [];

// ---------- Title ----------
children.push(new Paragraph({
  spacing: { after: 80 },
  alignment: AlignmentType.LEFT,
  children: [new TextRun({ text: "ANSLIB Logging API", font: FONT_BODY, size: 48, bold: true, color: "1F3864" })],
}));
children.push(new Paragraph({
  spacing: { after: 360 },
  children: [new TextRun({ text: "Developer guide — usage examples for ANSLIB_DBG, LogInfo, LogError, LogFatal", font: FONT_BODY, size: 24, italics: true, color: "595959" })],
}));

// ---------- Overview ----------
children.push(h1("Overview"));
children.push(p("ANSLIB exposes a small logging API that consumers can use without linking against ANSLicensingSystem.dll or any spdlog headers. All symbols are dynamically loaded inside ANSLIB; users only need to include ANSLIB.h and link ANSLIB.lib."));
children.push(p("Two flavours are provided:"));
children.push(bullet("ANSLIB_DBG — a printf-style macro for trace breadcrumbs that can be turned on or off remotely (DebugView-style logging)."));
children.push(bullet("ANSCENTER::ANSLIB::LogInfo / LogError / LogFatal — leveled logging that writes through SPDLogger to the rolling log file."));

// ---------- Setup ----------
children.push(h1("Setup"));
children.push(p("Same prerequisites for every example below. Add this to any consumer translation unit:"));
children.push(codeBlock(
`#include "ANSLIB.h"   // brings everything: static methods + ANSLIB_DBG macro

// Optional alias to shorten call sites:
using LOG = ANSCENTER::ANSLIB;`));
children.push(p("No other includes, no extra .lib to link beyond ANSLIB.lib."));

// ---------- Example 1 ----------
children.push(h1("Example 1 — Function entry/exit (ANSLIB_DBG, the workhorse)"));
children.push(codeBlock(
`int LoadConfig(const std::string& path) {
    ANSLIB_DBG("Config", "LoadConfig entry path=%s", path.c_str());

    if (path.empty()) {
        ANSLIB_DBG("Config", "empty path, bailing");
        return -1;
    }

    int rc = ParseFile(path);
    ANSLIB_DBG("Config", "ParseFile returned rc=%d", rc);
    return rc;
}`));
children.push(p("DebugView output (only when the gate is on):"));
children.push(codeBlock(
`[Config] LoadConfig entry path=C:\\models\\car.cfg
[Config] ParseFile returned rc=0`));
children.push(p("This is the drop-in replacement for ANS_DBG. Use it for breadcrumb logging that you want to be able to flip on/off remotely."));

// ---------- Example 2 ----------
children.push(h1("Example 2 — Error path (LogError, persisted to spdlog file)"));
children.push(p("When something goes wrong and you want it in the rolling log file (not just DebugView), use LogError:"));
children.push(codeBlock(
`int InitEngine(const std::string& modelPath) {
    auto* engine = ANSCENTER::ANSLIB::Create();
    if (!engine) {
        LOG::LogError("InitEngine", "ANSLIB::Create returned null", __FILE__, __LINE__);
        return -1;
    }

    std::string labels;
    int rc = engine->Initialize("license-key", modelPath.c_str(), "",
                                0.5f, 0.5f, 0.4f, 8, 5, 1, labels);
    if (rc < 0) {
        char detail[256];
        snprintf(detail, sizeof(detail),
                 "Initialize failed rc=%d model=%s", rc, modelPath.c_str());
        LOG::LogError("InitEngine", detail, __FILE__, __LINE__);
        ANSCENTER::ANSLIB::Destroy(engine);
        return rc;
    }

    LOG::LogInfo("InitEngine", "engine ready", __FILE__, __LINE__);
    return 0;
}`));
children.push(p("Rolling log file output (ANSLIB.log or whichever name spdlog has):"));
children.push(codeBlock(
`[2026-04-26 14:32:11.483] [error] [foo.cpp] [42] InitEngine: Initialize failed rc=-1 model=C:\\models\\car.zip
[2026-04-26 14:32:11.484] [info]  [foo.cpp] [50] InitEngine: engine ready`));

// ---------- Example 3 ----------
children.push(h1("Example 3 — Fatal / unrecoverable (LogFatal)"));
children.push(p("Use when the process can't continue — e.g. license check failed, GPU lost, mandatory model missing:"));
children.push(codeBlock(
`void OnGpuDeviceLost() {
    LOG::LogFatal("Engine",
                  "CUDA device lost, cannot continue inference",
                  __FILE__, __LINE__);
    std::abort();   // or whatever your shutdown path is
}`));

// ---------- Example 4 ----------
children.push(h1("Example 4 — Mixing both (recommended pattern)"));
children.push(p("Use ANSLIB_DBG for the chatty trace breadcrumbs and LogInfo / LogError / LogFatal for things you want preserved in the file:"));
children.push(codeBlock(
`int RunBatch(const std::vector<cv::Mat>& frames) {
    ANSLIB_DBG("Batch", "RunBatch entry frames=%zu", frames.size());

    if (frames.empty()) {
        LOG::LogError("Batch", "called with empty frame list", __FILE__, __LINE__);
        return -1;
    }

    int processed = 0;
    for (const auto& f : frames) {
        ANSLIB_DBG("Batch", "processing frame %d (%dx%d)", processed, f.cols, f.rows);
        // ...inference...
        ++processed;
    }

    char summary[128];
    snprintf(summary, sizeof(summary), "RunBatch OK processed=%d", processed);
    LOG::LogInfo("Batch", summary, __FILE__, __LINE__);

    ANSLIB_DBG("Batch", "RunBatch exit");
    return processed;
}`));
children.push(p("DebugView shows the full trace; the log file keeps just the entry/exit summary. This keeps the file small but lets you toggle full tracing remotely when something goes wrong."));

// ---------- Example 5 ----------
children.push(h1("Example 5 — Gating expensive log preparation"));
children.push(p("ANSLIB_DBG already gates the snprintf for you. But if preparing the message itself is expensive (serializing a struct, dumping a Mat, walking a tree), check the gate first manually:"));
children.push(codeBlock(
`void DumpDetections(const std::vector<ANSCENTER::Object>& dets) {
    if (!ANSCENTER::ANSLIB::IsDebugViewEnabled()) return;   // skip the whole prep

    std::string dump;
    for (const auto& d : dets) {
        dump += d.className + "(" + std::to_string(d.confidence) + ") ";
    }
    ANSLIB_DBG("Detections", "%zu objects: %s", dets.size(), dump.c_str());
}`));
children.push(p("This skips the entire string-building loop when the gate is off."));

// ---------- Example 6 ----------
children.push(h1("Example 6 — Logging from a class method"));
children.push(p("The __FILE__ / __LINE__ defaults make instance methods cleaner if you don't need them:"));
children.push(codeBlock(
`class MyDetector {
public:
    void Process(const cv::Mat& img) {
        ANSLIB_DBG("MyDetector", "Process %dx%d", img.cols, img.rows);

        if (img.empty()) {
            // No file/line — defaults to "" / 0
            LOG::LogError("MyDetector", "empty image input");
            return;
        }
        // ...
    }
};`));

// ---------- Example 7 ----------
children.push(h1("Example 7 — Macro for \"log everywhere with caller info\" (optional)"));
children.push(p("If you want every LogInfo call to auto-include __FILE__ / __LINE__, define a one-liner in your own code:"));
children.push(codeBlock(
`#define MYAPP_INFO(tag, msg)  ::ANSCENTER::ANSLIB::LogInfo (tag, msg, __FILE__, __LINE__)
#define MYAPP_ERROR(tag, msg) ::ANSCENTER::ANSLIB::LogError(tag, msg, __FILE__, __LINE__)

// Usage:
MYAPP_INFO ("Boot", "starting up");
MYAPP_ERROR("Net",  "connection refused");`));
children.push(p("This keeps call sites short while preserving file/line tracking in the rolling log."));

// ---------- Cheat sheet 1 ----------
children.push(h1("Cheat sheet — pick the right call"));
children.push(sheetTable(
  [3000, 3360, 3000],
  ["Want to log…", "Use", "Goes to"],
  [
    ["Trace / breadcrumb that's toggled per-incident",
     { runs: [codeLine('ANSLIB_DBG("Tag", "fmt %d", x)')] },
     "DebugView + stderr (when gate is on)"],
    ["Notable event (started, finished, config loaded)",
     { runs: [codeLine('LOG::LogInfo("Tag", "msg", __FILE__, __LINE__)')] },
     "spdlog file + console"],
    ["Recoverable error (will retry, will skip, etc.)",
     { runs: [codeLine('LOG::LogError("Tag", "msg", __FILE__, __LINE__)')] },
     "spdlog file + console"],
    ["Unrecoverable (about to crash / abort)",
     { runs: [codeLine('LOG::LogFatal("Tag", "msg", __FILE__, __LINE__)')] },
     "spdlog file + console (critical level)"],
  ]
));
children.push(spacer());

// ---------- Cheat sheet 2 ----------
children.push(h1("Toggling the DebugView gate at runtime"));
children.push(p("LogInfo / LogError / LogFatal are not gated — they always go to the spdlog file. Only ANSLIB_DBG is gated by the mechanism below."));
children.push(spacer());
children.push(sheetTable(
  [4680, 4680],
  ["Action", "Effect"],
  [
    [{ runs: [new TextRun({ text: "Drop file ", font: FONT_BODY, size: 20 }),
              codeLine("C:\\ProgramData\\ANSCENTER\\ansvisdebugview.txt")] },
     { runs: [new TextRun({ text: "Turns ", font: FONT_BODY, size: 20 }),
              codeLine("ANSLIB_DBG"), new TextRun({ text: " (and ", font: FONT_BODY, size: 20 }),
              codeLine("ANS_DBG"), new TextRun({ text: ") ON within ~2s", font: FONT_BODY, size: 20 })] }],
    ["Delete that file", "Turns them OFF within ~2s"],
    [{ runs: [codeLine("set ANSCENTER_DBGVIEW=1"), new TextRun({ text: " (or ", font: FONT_BODY, size: 20 }),
              codeLine("=0"), new TextRun({ text: ")", font: FONT_BODY, size: 20 })] },
     "Forces ON or OFF — overrides the file"],
  ]
));

// ---------- Operational notes ----------
children.push(h1("Operational notes"));
children.push(bullet("All four entry points (ANSLIB_DBG, LogInfo, LogError, LogFatal) are thread-safe and require no instance — they're static methods that lazy-resolve their underlying DLL symbols on first call."));
children.push(bullet("If ANSLicensingSystem.dll is missing on the target machine, every logging call silently no-ops (no crash, no exception). The application keeps running."));
children.push(bullet("ANSLIB_DBG output format is identical to the original ANS_DBG: \"[tag] formatted\\n\" emitted to both OutputDebugStringA and stderr."));
children.push(bullet("Don't log from static destructors. DLL unload order during process shutdown is undefined on Windows — calling into the licensing DLL after it has been unmapped will crash."));
children.push(bullet("To strip ANSLIB_DBG entirely at compile time (for performance-critical builds), define -DANSCORE_DEBUGVIEW=0 when compiling the consumer translation unit. The macro becomes ((void)0)."));

// =========================================================================
// Document assembly
// =========================================================================
const doc = new Document({
  styles: {
    default: { document: { run: { font: FONT_BODY, size: 22 } } },
    paragraphStyles: [
      { id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
        run: { size: 36, bold: true, font: FONT_BODY, color: "1F3864" },
        paragraph: { spacing: { before: 360, after: 200 }, outlineLevel: 0 } },
      { id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
        run: { size: 28, bold: true, font: FONT_BODY, color: "2E74B5" },
        paragraph: { spacing: { before: 280, after: 160 }, outlineLevel: 1 } },
      { id: "Heading3", name: "Heading 3", basedOn: "Normal", next: "Normal", quickFormat: true,
        run: { size: 24, bold: true, font: FONT_BODY, color: "404040" },
        paragraph: { spacing: { before: 200, after: 100 }, outlineLevel: 2 } },
    ],
  },
  numbering: {
    config: [
      { reference: "bullets",
        levels: [{ level: 0, format: LevelFormat.BULLET, text: "•", alignment: AlignmentType.LEFT,
          style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
    ],
  },
  sections: [{
    properties: {
      page: {
        size: { width: 12240, height: 15840 },
        margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 },
      },
    },
    children,
  }],
});

const outPath = path.join(__dirname, "ANSLIB_Logging_Guide.docx");
Packer.toBuffer(doc).then(buf => {
  fs.writeFileSync(outPath, buf);
  console.log("Wrote", outPath, "(" + buf.length + " bytes)");
});
