/**
 * Build the Loxia Autopilot "Deep-Understanding" onboarding Word document.
 *
 * Run:  node scripts/build-onboarding-doc.js
 * Out:  docs/Loxia-Autopilot-Onboarding-Deep-Track.docx
 *
 * Uses docx-js (installed globally as `docx` v9.6.1). Generates a polished,
 * multi-section document with cover page, headings, lists, code spans,
 * call-out boxes, and tables. Intended to be handed straight to a new hire.
 */

'use strict';

const fs = require('fs');
const path = require('path');

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

/* ------------------------------------------------------------------ */
/* Style + helper primitives                                          */
/* ------------------------------------------------------------------ */

const FONT = 'Calibri';
const HEADING_FONT = 'Calibri';
const MONO_FONT = 'Consolas';

const COLORS = {
  navy: '1F3864',
  blue: '2E75B6',
  steel: '595959',
  silver: '7F7F7F',
  bandHeader: 'D9E2F3',
  bandStripe: 'F2F6FC',
  rule: 'CCCCCC',
  codeBg: 'F4F4F4',
  codeRule: 'D0D0D0',
  warn: 'BF8F00',
  danger: 'C00000',
  tip: '2E7D32',
};

const text = (s, opts = {}) => new TextRun({ text: s, font: FONT, ...opts });
const bold = (s) => new TextRun({ text: s, bold: true, font: FONT });
const italic = (s) => new TextRun({ text: s, italics: true, font: FONT });
const code = (s) => new TextRun({ text: s, font: MONO_FONT, size: 20 });

/** Body paragraph. Accepts either a string or an array of TextRuns. */
const para = (input) => {
  const children = typeof input === 'string'
    ? [new TextRun({ text: input, font: FONT })]
    : input;
  return new Paragraph({
    children,
    spacing: { after: 140, line: 312 }, // ~1.3 line height
  });
};

const h1 = (s) => new Paragraph({
  heading: HeadingLevel.HEADING_1,
  children: [new TextRun({ text: s, bold: true, font: HEADING_FONT })],
});

const h2 = (s) => new Paragraph({
  heading: HeadingLevel.HEADING_2,
  children: [new TextRun({ text: s, bold: true, font: HEADING_FONT })],
});

const h3 = (s) => new Paragraph({
  heading: HeadingLevel.HEADING_3,
  children: [new TextRun({ text: s, bold: true, font: HEADING_FONT })],
});

/** Bullet item. Accepts a string or an array of TextRuns. */
const bullet = (input, level = 0) => new Paragraph({
  numbering: { reference: 'bullets', level },
  children: typeof input === 'string'
    ? [new TextRun({ text: input, font: FONT })]
    : input,
  spacing: { after: 80 },
});

/** Numbered list item. */
const numbered = (input, level = 0) => new Paragraph({
  numbering: { reference: 'numbers', level },
  children: typeof input === 'string'
    ? [new TextRun({ text: input, font: FONT })]
    : input,
  spacing: { after: 80 },
});

/** Block-quote-style call-out with a coloured left bar. */
const callout = (label, content, color = COLORS.blue) => new Paragraph({
  children: [
    new TextRun({ text: label, bold: true, font: FONT, color }),
    new TextRun({ text: '  —  ', bold: true, font: FONT, color }),
    typeof content === 'string'
      ? new TextRun({ text: content, font: FONT })
      : content,
  ].flat(),
  spacing: { before: 100, after: 160, line: 312 },
  border: {
    left: { style: BorderStyle.SINGLE, size: 24, color, space: 12 },
  },
  indent: { left: 240 },
});

const spacer = () => new Paragraph({ children: [new TextRun({ text: '' })], spacing: { after: 80 } });
const pageBreak = () => new Paragraph({ children: [new PageBreak()] });

/** Table cell with consistent borders + padding. */
const cell = (content, opts = {}) => {
  const children = (Array.isArray(content) ? content : [content]).map(item => {
    if (item instanceof Paragraph) return item;
    return new Paragraph({
      children: [new TextRun({
        text: String(item),
        font: FONT,
        ...(opts.bold ? { bold: true } : {}),
        ...(opts.color ? { color: opts.color } : {}),
      })],
    });
  });
  return new TableCell({
    borders: {
      top: { style: BorderStyle.SINGLE, size: 4, color: COLORS.rule },
      bottom: { style: BorderStyle.SINGLE, size: 4, color: COLORS.rule },
      left: { style: BorderStyle.SINGLE, size: 4, color: COLORS.rule },
      right: { style: BorderStyle.SINGLE, size: 4, color: COLORS.rule },
    },
    margins: { top: 100, bottom: 100, left: 140, right: 140 },
    width: opts.width ? { size: opts.width, type: WidthType.DXA } : undefined,
    shading: opts.shading
      ? { fill: opts.shading, type: ShadingType.CLEAR, color: 'auto' }
      : undefined,
    children,
  });
};

/* ------------------------------------------------------------------ */
/* Document content                                                   */
/* ------------------------------------------------------------------ */

const sections = [];

/* ---- Cover page ---- */
sections.push(
  spacer(), spacer(), spacer(), spacer(),
  new Paragraph({
    alignment: AlignmentType.CENTER,
    children: [new TextRun({
      text: 'LOXIA AUTOPILOT',
      size: 72, bold: true, color: COLORS.navy, font: HEADING_FONT,
      characterSpacing: 80,
    })],
    spacing: { after: 220 },
  }),
  new Paragraph({
    alignment: AlignmentType.CENTER,
    children: [new TextRun({
      text: 'Deep-Understanding Onboarding Track',
      size: 36, color: COLORS.blue, font: HEADING_FONT,
    })],
    spacing: { after: 600 },
  }),
  new Paragraph({
    alignment: AlignmentType.CENTER,
    children: [new TextRun({
      text: 'For new engineers joining the team',
      size: 24, italics: true, color: COLORS.steel, font: FONT,
    })],
    spacing: { after: 1200 },
  }),
  new Paragraph({
    alignment: AlignmentType.CENTER,
    children: [new TextRun({
      text: 'Loxia Engineering  ·  Internal',
      size: 20, color: COLORS.silver, font: FONT,
    })],
  }),
  pageBreak(),
);

/* ---- 1. Welcome ---- */
sections.push(
  h1('1. Welcome'),
  para(
    'You are about to spend two weeks getting your environment running, meeting ' +
    'the team, and reading code. This document is your companion for the second ' +
    'of those weeks — the one where you stop pattern-matching against files ' +
    'and start understanding why the codebase looks the way it does.'
  ),
  para(
    'The codebase is small enough that you could read every file. That is not ' +
    'what we want you to do. We want you to internalise four or five mental ' +
    'models that make the rest of the architecture feel obvious. Once you have ' +
    'those, new code reads itself.'
  ),

  h2('How to read this document'),
  bullet('Read it linearly the first time. Sections build on each other.'),
  bullet('Each section ends with a checklist. Do those before turning the page.'),
  bullet('Re-read it after your first two weeks. It will land differently.'),
  bullet('If something here does not match what you see in the code, the document is wrong and we want to know. Tell your onboarding buddy.'),

  h2('What you should know by the end'),
  numbered('What Loxia Autopilot does, and who uses it.'),
  numbered('How agents, the orchestrator, and tools relate to each other.'),
  numbered('The plural-canonical envelope pattern and why we settled on it.'),
  numbered('What the model catalog is and why we are removing hardcoded model names.'),
  numbered('How code gets to production (it is not what you think).'),
  numbered('Where to find the components you will most often modify.'),
  numbered('The conventions our tests follow and how to run them.'),
  pageBreak(),
);

/* ---- 2. What Loxia Autopilot Is ---- */
sections.push(
  h1('2. What Loxia Autopilot Is'),
  para(
    'Loxia Autopilot is an autonomous-agent platform. A user describes a task. ' +
    'The platform spawns one or more LLM-powered agents that execute that task ' +
    'using tools — terminal, web search, file system, calendar, code ' +
    'repositories, document creation, and third-party SaaS via Composio. ' +
    'Agents run for minutes to hours. Users either watch them work in real ' +
    'time or come back later to review what happened.'
  ),
  para(
    'The product surface is a web application called My Squadron, plus a CLI ' +
    'for power users and CI environments. The backend is Node.js. The agent ' +
    'runtime is built around a single orchestrator service. The UI is React.'
  ),

  h2('Who uses it'),
  para('Two primary user segments. Their needs shape every design decision:'),
  bullet([
    bold('Power users and automation engineers'),
    text(' who run multiple agents in parallel, edit prompts and tool configurations, and care about reliability and observability above polish.'),
  ]),
  bullet([
    bold('Knowledge workers'),
    text(' who launch a single agent for a specific task (“plan my Q3 OKRs”), watch it work, intervene when it gets stuck.'),
  ]),
  para(
    'When in doubt about a UX trade-off, the power user wins. The UI should ' +
    'be beautiful, but it should never sacrifice information density for a ' +
    'knowledge worker who is seeing it for the first time.'
  ),

  h2('What it competes with'),
  para(
    'We are not a chatbot. We are not a single-shot code generator. The ' +
    'reference point is a fleet of long-running agents, more like a CI/CD ' +
    'system or a job queue than a chat interface. If a design choice feels ' +
    'like it comes from chat-UX intuition, double-check that it still makes ' +
    'sense for a process that runs for an hour and reports back later.'
  ),
  pageBreak(),
);

/* ---- 3. The Five Mental Models ---- */
sections.push(
  h1('3. The Five Mental Models'),
  para(
    'These are the abstractions that, once internalised, make the rest of ' +
    'the codebase feel like a single coherent thing instead of a pile of files.'
  ),

  /* 3.1 */
  h2('3.1   Agents are stateful processes, not requests'),
  para(
    'An agent is a long-running stateful actor with a model, a toolset, a ' +
    'scratchpad, and a status. You do not send a request to an agent — ' +
    'you nudge it, pause it, change its model, kill it. The closest analogy ' +
    'is a Unix process.'
  ),
  para('Each agent has:'),
  bullet([bold('A model'), text(' (Claude Sonnet 4.5, GPT-5, Kimi-K2.6, etc.) chosen at creation, mutable mid-run.')]),
  bullet([bold('A toolset'), text(' (terminal, web search, office, composio, …) configurable per agent.')]),
  bullet([bold('A scratchpad and context window'), text(' its working memory.')]),
  bullet([bold('A status'), text(' running, paused, unloaded, archived.')]),
  para([
    text('Every backend endpoint either creates, mutates, or queries one of these long-lived actors. The orchestrator is the supervisor and owns the agent pool. Every action goes through '),
    code('orchestrator.processRequest({ action, payload })'),
    text('.'),
  ]),
  callout(
    'Rule of thumb',
    'When you add a feature, ask "is this a new orchestrator action?" before you design an endpoint. Most of the time the answer is yes, and the REST route is a thin wrapper.',
  ),

  /* 3.2 */
  h2('3.2   Tools speak a plural-canonical envelope'),
  para([
    text('Every tool accepts an envelope of the form '),
    code('{ actions: [ { type, ...payload } ] }'),
    text(' — a list of actions. Even when the caller wants a single action, it is wrapped in a one-element array. We call this the plural-canonical envelope.'),
  ]),
  para('Why plural?'),
  bullet('Batches are common. Web search wants to do five queries; file ops want to read, edit, write in one call. Forcing a singular envelope means making every caller fan-out by hand.'),
  bullet('Singular is a degenerate case of plural. Plural is not a degenerate case of singular. Plural is the more general shape.'),
  bullet('It mirrors how SQL, GraphQL, and most batch APIs work. People recognise the shape.'),
  para([
    text('We also accept a legacy singular envelope '),
    code('{ action, ...payload }'),
    text(' as a fallback for backward compatibility. New tools should '),
    italic('generate'),
    text(' the plural envelope. Old singular-canonical tools should '),
    italic('accept'),
    text(' either shape on input — we call this the reverse-forgiveness pattern.'),
  ]),
  para('When you add a new tool:'),
  bullet([text('Put the discriminator on every item inside the '), code('actions'), text(' array.')]),
  bullet([text('Accept both '), code('type'), text(' and '), code('action'), text(' as the discriminator key. We have been migrating between them and the next person to write a caller may use either.')]),
  bullet('Write integration tests with both singular and plural envelopes on the input side.'),

  /* 3.3 */
  h2('3.3   Catalog-driven routing replaces hardcoded model names'),
  para(
    'The model catalog is a separate Azure service that owns the answer to ' +
    'questions like "what models exist?", "what does this one cost per ' +
    'million tokens?", and "can this one use vision?". Phases one through ' +
    'three of the catalog refactor deleted every hardcoded model name from ' +
    'the orchestrator. Phase four shipped the backend bits; the UI work is ' +
    'still pending.'
  ),
  para('When you add new model logic:'),
  numbered('Search for the model name across the codebase. If you find it in any list or switch statement, the answer is almost always to move that knowledge into the catalog.'),
  numbered([
    text('The catalog is at a separate Azure URL. '),
    code('publicNetworkAccess'),
    text(' is set to '),
    code('Disabled'),
    text(', so you reach it through the VNet. See '),
    code('project_deployment_quirks.md'),
    text(' for the details.'),
  ]),
  numbered([
    text('There is a single client ('),
    code('catalogClient'),
    text(') that fetches and caches catalog responses. Use it. Do not write a second one.'),
  ]),
  callout(
    'Warning',
    'Adding a model name to a string array in source code is the single most common way to create technical debt in this codebase. It compounds quickly. The catalog is annoying to update from the outside, but it is correct.',
    COLORS.danger,
  ),

  /* 3.4 */
  h2('3.4   Deployment goes through Kudu VFS, not pipelines'),
  para(
    'The Azure App Services that host the backend are deployed via Kudu VFS ' +
    'file pushes, not GitHub Actions and not App Service deployment slots. ' +
    'A self-hosted DevOps agent does the actual file transfer. This is ' +
    'unconventional but predictable:'
  ),
  bullet([code('git push'), text(' does not deploy.')]),
  bullet('Pipelines do not deploy. They build artefacts and hand them off.'),
  bullet('Files get pushed via the Kudu API by the self-hosted agent.'),
  para('Consequences:'),
  bullet('If you see a stale binary in production, suspect that the Kudu push failed silently. The DevOps agent VM has the relevant logs.'),
  bullet('Local builds need to match the deployment artefact. If you change build flags, you change deploy behaviour.'),
  bullet('Rollback is also a Kudu push, not a slot swap. Plan accordingly.'),

  /* 3.5 */
  h2('3.5   The UI mirrors backend pluralism'),
  para(
    'The web UI has a My Squadron screen with multi-select. We recently added ' +
    'a sticky BulkActionBar that lets you select N agents and pause, resume, ' +
    'delete, change model, enable or disable tools across all of them at once. ' +
    'The same component mounts on the agent import modal — it is reusable, ' +
    'parameterised by an allowedActions set.'
  ),
  para([
    text('Behind it is '),
    code('POST /api/agents/bulk'),
    text(' with the same plural-canonical shape as the tool envelope. The endpoint runs sequentially (agent-pool state is not lock-free) and returns a partial-success summary so the UI can show "7 of 10 succeeded" instead of swallowing failures.'),
  ]),
  callout(
    'Pattern',
    'Every batch operation in the system — tool envelope, bulk agent actions, future bulk anything — uses the same { items: […], summary: { total, ok, failed }, results: […] } shape on input and output. Reuse it.',
    COLORS.tip,
  ),
  pageBreak(),
);

/* ---- 4. Service Layout ---- */
sections.push(
  h1('4. The Service Layout'),
  para('Loxia Autopilot is split across a handful of services. Here is the shape of it:'),

  new Table({
    width: { size: 9360, type: WidthType.DXA },
    columnWidths: [2400, 3200, 3760],
    rows: [
      new TableRow({
        tableHeader: true,
        children: [
          cell('Service', { width: 2400, bold: true, shading: COLORS.bandHeader }),
          cell('Role', { width: 3200, bold: true, shading: COLORS.bandHeader }),
          cell('Notes for new joiners', { width: 3760, bold: true, shading: COLORS.bandHeader }),
        ],
      }),
      new TableRow({ children: [
        cell('Autopilot CLI and backend', { width: 2400 }),
        cell('Agent runtime. Express server, orchestrator, agent pool, tool registry.', { width: 3200 }),
        cell('Most of your day-to-day work happens here. See src/.', { width: 3760 }),
      ]}),
      new TableRow({ children: [
        cell('Web UI', { width: 2400, shading: COLORS.bandStripe }),
        cell('React app for My Squadron, agent imports, live monitoring, settings.', { width: 3200, shading: COLORS.bandStripe }),
        cell('Single-page app. Vite + React + Tailwind. See web-ui/.', { width: 3760, shading: COLORS.bandStripe }),
      ]}),
      new TableRow({ children: [
        cell('Model Catalog', { width: 2400 }),
        cell('Source of truth for models, costs, capabilities.', { width: 3200 }),
        cell('Separate Azure App Service. publicNetworkAccess=Disabled. Reach via VNet only.', { width: 3760 }),
      ]}),
      new TableRow({ children: [
        cell('DevOps agent (self-hosted)', { width: 2400, shading: COLORS.bandStripe }),
        cell('Pushes builds through Kudu VFS to App Services.', { width: 3200, shading: COLORS.bandStripe }),
        cell('You probably will not edit it. You will read its logs when deploys go wrong.', { width: 3760, shading: COLORS.bandStripe }),
      ]}),
      new TableRow({ children: [
        cell('Composio (third-party)', { width: 2400 }),
        cell('SaaS integration broker. Per-user OAuth, hosted Connect Link.', { width: 3200 }),
        cell('Wrapped behind a single tool — composioTool.js. Rotate keys on schedule.', { width: 3760 }),
      ]}),
    ],
  }),
  spacer(),
  para('Each service has its own deployment lifecycle, but they are versioned together. We do not run mixed-version pairs in production.'),

  pageBreak(),
);

/* ---- 5. Code Tour ---- */
sections.push(
  h1('5. Code Tour (in reading order)'),
  para('Read these files in this order. Most of them are big — do not try to absorb every line. Just see the shape.'),

  h3('5.1   src/orchestrator/'),
  para([
    text('Start with '),
    code('processRequest'),
    text('. Trace what happens when '),
    code('action: "runAgent"'),
    text(' arrives. Notice that everything funnels through one entry point, that side effects are written through '),
    code('agentPool'),
    text(' and not into module-level globals, and that the orchestrator is mostly a switch over action types.'),
  ]),

  h3('5.2   src/tools/webSearchTool.js'),
  para([
    text('Read it cover to cover. This is the cleanest example of the plural-canonical envelope. Notice how the input handler accepts both singular and plural shapes, normalises to plural, then runs the '),
    code('actions'),
    text(' array sequentially. Internalise this pattern; you will reproduce it whenever you add a new tool.'),
  ]),

  h3('5.3   src/tools/composioTool.js'),
  para([
    text('Read this after webSearchTool. It is the newest tool, scaffolded recently. Notice the dynamic '),
    code('import("@composio/core")'),
    text(' so the dependency is optional, the per-agent '),
    code('userId'),
    text(' mapping for OAuth isolation, and the five top-level action types ('),
    code('list-toolkits'),
    text(', '),
    code('list-tools'),
    text(', '),
    code('connect'),
    text(', '),
    code('connection-status'),
    text(', '),
    code('execute'),
    text('). The test file alongside it shows how we mock the SDK.'),
  ]),

  h3('5.4   src/interfaces/webServer.js'),
  para([
    text('The Express app. Big file. Do not read it linearly. Skim for the routes you have heard about — '),
    code('/api/agents'),
    text(', '),
    code('/api/agents/bulk'),
    text(' (near line 4500), '),
    code('/api/orchestrator'),
    text(' — and trace one request through.'),
  ]),

  h3('5.5   web-ui/src/components/AgentManager.jsx'),
  para('The My Squadron screen. Over 1600 lines. Read it the same way you read webServer.js: skim, find the parts you care about, ignore the rest until you need them. Pay attention to how it composes smaller components (AgentImportModal, BulkActionBar, AgentQuickSwitcher).'),

  h3('5.6   web-ui/src/components/BulkActionBar.jsx'),
  para('Reusable sticky-bottom action bar with confirm modal. About 230 lines. Worth reading every line: it is small, well-commented, and shows the pattern of a parameterised reusable component that callers control entirely through an onAction callback.'),

  h3('5.7   web-ui/src/components/AgentQuickSwitcher.jsx'),
  para([
    text('Ctrl+P quick-switcher modal with fuzzy search. About 200 lines. The interesting part is the '),
    code('fuzzyScore'),
    text(' function. Worth understanding even if you never touch it again — the ranking heuristic (prefix > substring > fuzzy-walk) shows up in many places.'),
  ]),

  h3('5.8   src/interfaces/__tests__/bulkAgentRoute.test.js'),
  para([
    text('The canonical example of our route test pattern. No '),
    code('supertest'),
    text(' — we removed it. Instead, route tests spin up an '),
    code('http.createServer'),
    text(' instance and use '),
    code('fetch'),
    text(' against it. Copy-paste this file as the starting point for any new route test.'),
  ]),
  pageBreak(),
);

/* ---- 6. Web UI Architecture ---- */
sections.push(
  h1('6. Web UI Architecture'),

  h2('Stack'),
  bullet('Vite + React 18 + Tailwind CSS.'),
  bullet('Heroicons for icons; we do not use other icon sets.'),
  bullet('react-hot-toast for transient notifications.'),
  bullet('No global state-management library. Hooks plus component-local state. Lift state up when two siblings need it.'),
  bullet('Tests with Vitest + React Testing Library.'),

  h2('Component conventions'),
  bullet([text('Reusable components live in '), code('web-ui/src/components/'), text('. One per file. PascalCase filenames.')]),
  bullet([text('Tool renderers live in '), code('web-ui/src/components/toolRenderers/'), text('. Each tool can register a custom renderer or fall back to null. The registry is the gatekeeper.')]),
  bullet([text('Service modules live in '), code('web-ui/src/services/'), text('. The API client is here. Do not call '), code('fetch()'), text(' from components — always go through '), code('api.js'), text(' so we have a single seam for auth, retry, and logging.')]),
  bullet([text('Constants live in '), code('web-ui/src/constants/'), text('. Tool IDs in particular — add new tool IDs to '), code('toolConstants.js'), text(' as well as registering them in '), code('registry.js'), text('.')]),

  h2('The bulk-action pattern'),
  para('When you build a screen that supports multi-select with bulk operations, the recipe is:'),
  numbered([text('Hold a '), code('Set<string>'), text(' of selected IDs in component state.')]),
  numbered('Render a checkbox on each row that toggles membership in the set.'),
  numbered([text('Compute a '), code('selectedAgents'), text(' array (or whatever your entity is) by filtering the full list against the set.')]),
  numbered([text('Mount '), code('<BulkActionBar />'), text(' with '), code('selectedAgents'), text(', '), code('onAction'), text(', and an '), code('allowedActions'), text(' set that filters the action menu to what makes sense for this screen.')]),
  numbered([text('On '), code('onAction'), text(', call '), code('api.bulkAgentAction(…)'), text(' (or the equivalent for your entity), show a toast with the summary, refresh the list, and clear the selection.')]),

  h2('Keyboard ergonomics'),
  para([
    text('We invest in keyboard ergonomics. The '),
    code('AgentQuickSwitcher'),
    text(' (Ctrl+P) is the model: instant open, type-to-filter, Enter to activate. Any new global action should consider a shortcut and a quick-switcher entry. Discoverability via the '),
    code('?'),
    text(' help overlay is non-negotiable.'),
  ]),
  pageBreak(),
);

/* ---- 7. Tests + Conventions ---- */
sections.push(
  h1('7. Testing Conventions'),
  para('Tests are not optional. The codebase moves fast because tests are reliable. Adding a feature without tests is rejected at review.'),

  h2('Test runners'),
  bullet([code('mocha'), text(' for backend code under '), code('src/'), text('.')]),
  bullet([code('vitest'), text(' for web-ui code under '), code('web-ui/'), text('.')]),
  bullet([text('Test files live alongside the code they test, in a '), code('__tests__/'), text(' directory.')]),

  h2('Route tests use http.createServer + fetch'),
  para([
    text('We do not depend on '),
    code('supertest'),
    text(' — we removed it. Instead, route tests spin up an '),
    code('http.createServer'),
    text(' instance with a small inline app, then use '),
    code('fetch'),
    text(' against it. See '),
    code('src/interfaces/__tests__/bulkAgentRoute.test.js'),
    text(' for the worked example. The pattern is small enough to copy-paste into a new test file.'),
  ]),

  h2('Mock at the boundary'),
  bullet('Mock the SDK or HTTP client, not your own functions. If you need to mock your own function, the seam is probably in the wrong place.'),
  bullet([text('Use dependency injection over module-level mocks where possible. Most tools accept a '), code('deps'), text(' object precisely so tests can inject fakes.')]),
  bullet('Prefer integration tests over unit tests when the cost is similar — they catch more.'),

  h2('Commit and PR conventions'),
  bullet([text('Conventional Commits prefixes ('), code('feat:'), text(', '), code('fix:'), text(', '), code('refactor:'), text(', '), code('test:'), text(', '), code('docs:'), text(', '), code('chore:'), text(').')]),
  bullet('One logical change per PR. If you find an unrelated bug, file a separate PR or task.'),
  bullet('Every PR should explain "Why" — not just "What". The code shows what changed.'),
  pageBreak(),
);

/* ---- 8. Pitfalls ---- */
sections.push(
  h1('8. Common Pitfalls'),
  para('A non-exhaustive list of mistakes that have bitten us in the past. Reading these now will save you a debugging session later.'),

  callout(
    'Promise.all on agent operations',
    'Agent-pool state mutates without locks. Sequential or carefully serialised only. Promise.all will look like it works locally and corrupt state in production.',
    COLORS.danger,
  ),
  callout(
    'Bypassing the catalog',
    'Even for "just a quick fix". Adding a model name to a string array creates debt that compounds. The catalog is annoying. Use it anyway.',
    COLORS.danger,
  ),
  callout(
    'Skipping the singular fallback',
    'When you write a new tool, keep accepting the legacy { action, … } shape for one release cycle. There may still be callers on the old format. Reject it cleanly with a deprecation warning, never with a 500.',
    COLORS.warn,
  ),
  callout(
    '"git push assumed to deploy"',
    'It does not. Verify via the Kudu API or the App Service log stream. We have had multiple incidents where the team thought a fix was live and it was not.',
    COLORS.danger,
  ),
  callout(
    'Tools that share state',
    'Tools should be pure and stateless from the orchestrator point of view. If a tool needs state, hide it behind a class that the orchestrator owns. Do not put state in module-level variables.',
    COLORS.warn,
  ),
  callout(
    'Forgetting to register a new tool',
    'A new tool needs three touch-points: (1) instantiation in src/index.js, (2) constant in web-ui/src/constants/toolConstants.js, (3) renderer entry (or null fallback) in web-ui/src/components/toolRenderers/registry.js. Miss any one and you get silent UI bugs.',
    COLORS.warn,
  ),
  callout(
    'Forwarding sensitive env to agent processes',
    'Agents run user-controlled tool calls. Treat their process env as untrusted output. Never forward Composio API keys, Azure credentials, or signing keys into an agent’s environment by default — secrets go through a vetted accessor, not process.env.',
    COLORS.danger,
  ),

  pageBreak(),
);

/* ---- 9. Glossary ---- */
sections.push(
  h1('9. Glossary'),

  new Table({
    width: { size: 9360, type: WidthType.DXA },
    columnWidths: [2400, 6960],
    rows: [
      new TableRow({
        tableHeader: true,
        children: [
          cell('Term', { width: 2400, bold: true, shading: COLORS.bandHeader }),
          cell('Meaning', { width: 6960, bold: true, shading: COLORS.bandHeader }),
        ],
      }),
      new TableRow({ children: [
        cell('Agent', { width: 2400 }),
        cell('A long-running LLM-powered actor with state, a toolset, and a status. Closest analogy: a Unix process.', { width: 6960 }),
      ]}),
      new TableRow({ children: [
        cell('Orchestrator', { width: 2400, shading: COLORS.bandStripe }),
        cell('The supervisor that owns the agent pool and routes every action through processRequest({ action, payload }).', { width: 6960, shading: COLORS.bandStripe }),
      ]}),
      new TableRow({ children: [
        cell('Tool', { width: 2400 }),
        cell('A capability exposed to agents (terminal, web search, office, composio, etc.). Speaks the plural-canonical envelope.', { width: 6960 }),
      ]}),
      new TableRow({ children: [
        cell('Catalog', { width: 2400, shading: COLORS.bandStripe }),
        cell('The model registry. Separate Azure service. Source of truth for what models exist, what they cost, and what they can do.', { width: 6960, shading: COLORS.bandStripe }),
      ]}),
      new TableRow({ children: [
        cell('Squadron', { width: 2400 }),
        cell('A user’s collection of agents. The main web UI screen is called My Squadron.', { width: 6960 }),
      ]}),
      new TableRow({ children: [
        cell('Envelope', { width: 2400, shading: COLORS.bandStripe }),
        cell('The shape of a tool input: { actions: [ { type, …payload } ] }. Always plural, even for a single action.', { width: 6960, shading: COLORS.bandStripe }),
      ]}),
      new TableRow({ children: [
        cell('Composio', { width: 2400 }),
        cell('Third-party SaaS integration broker. Wraps Gmail, Calendar, Slack, GitHub, etc. Behind a single tool in our codebase.', { width: 6960 }),
      ]}),
      new TableRow({ children: [
        cell('Kudu VFS', { width: 2400, shading: COLORS.bandStripe }),
        cell('Azure App Service file API. We deploy through it because it is the most predictable channel given our constraints.', { width: 6960, shading: COLORS.bandStripe }),
      ]}),
      new TableRow({ children: [
        cell('DevOps Agent', { width: 2400 }),
        cell('Self-hosted machine that runs deployment pushes. Logs here when deploys fail.', { width: 6960 }),
      ]}),
      new TableRow({ children: [
        cell('Reverse forgiveness', { width: 2400, shading: COLORS.bandStripe }),
        cell('Pattern where a tool generates the new (plural) envelope on output but still accepts the legacy (singular) envelope on input.', { width: 6960, shading: COLORS.bandStripe }),
      ]}),
    ],
  }),
  pageBreak(),
);

/* ---- 10. First Two Weeks ---- */
sections.push(
  h1('10. Your First Two Weeks'),
  para('A concrete plan so you do not have to invent one. Your onboarding buddy will adapt this if needed.'),

  h2('Days 1–2  ·  Setup and orientation'),
  bullet('Get keys, accounts, VPN, repository access.'),
  bullet('Clone the repo. Run the full test suite locally. Both backend and web UI should be green before you write any code.'),
  bullet('Read this document end to end. Take notes on anything that surprises you.'),
  bullet('Pair with your onboarding buddy for one hour to walk through the My Squadron screen.'),

  h2('Days 3–5  ·  First read'),
  bullet('Do the Code Tour in Section 5 in order. Do not skip ahead.'),
  bullet([text('Write ~200 words on how a single request flows from '), code('POST /api/agents'), text(' to a running agent process. Keep this draft — you will compare it to reality at the end of week one.')]),
  bullet('Shadow a senior engineer on a real change. Ask why, not what.'),

  h2('Days 6–7  ·  First contribution'),
  bullet('Pick a starter task from the bench. Ask your team lead — there is always one waiting.'),
  bullet('Open a draft PR within two days, even if the change is not finished. Get feedback early.'),
  bullet('Ship it. Watch it through deploy. Stay on call for an hour after it goes live.'),

  h2('Week 2  ·  Stretch and integrate'),
  bullet('Take on a second task, ideally one that touches a second area (e.g. backend plus web UI).'),
  bullet('Re-read this document. The parts that did not land the first time should land now.'),
  bullet('Write a "what surprised me" doc. Share it with the team. We use these to improve onboarding.'),

  spacer(),
  h2('Final note'),
  para(
    'The team is small, the codebase is small, and most of what feels ' +
    'mysterious right now is the result of a few specific decisions we made ' +
    'for specific reasons. If something in the code makes no sense, the first ' +
    'hypothesis should be “there is context I do not have yet” — ' +
    'but the second hypothesis should be “this is wrong and I should fix ' +
    'it.” Both are valid. Both are common. Welcome aboard.'
  ),
  spacer(),
  new Paragraph({
    alignment: AlignmentType.RIGHT,
    children: [new TextRun({
      text: '— The Loxia Autopilot team',
      italics: true, font: FONT, color: COLORS.steel,
    })],
  }),
);

/* ------------------------------------------------------------------ */
/* Document assembly                                                  */
/* ------------------------------------------------------------------ */

const doc = new Document({
  creator: 'Loxia Autopilot Engineering',
  title: 'Loxia Autopilot — Deep-Understanding Onboarding Track',
  description: 'Onboarding document for new programmers joining the Loxia Autopilot team',
  styles: {
    default: {
      document: { run: { font: FONT, size: 22 } }, // 11pt
    },
    paragraphStyles: [
      { id: 'Heading1', name: 'Heading 1', basedOn: 'Normal', next: 'Normal', quickFormat: true,
        run: { size: 36, bold: true, font: HEADING_FONT, color: COLORS.navy },
        paragraph: { spacing: { before: 320, after: 200 }, outlineLevel: 0 } },
      { id: 'Heading2', name: 'Heading 2', basedOn: 'Normal', next: 'Normal', quickFormat: true,
        run: { size: 28, bold: true, font: HEADING_FONT, color: COLORS.blue },
        paragraph: { spacing: { before: 280, after: 120 }, outlineLevel: 1 } },
      { id: 'Heading3', name: 'Heading 3', basedOn: 'Normal', next: 'Normal', quickFormat: true,
        run: { size: 24, bold: true, font: HEADING_FONT, 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 } } } },
          { level: 1, format: LevelFormat.BULLET, text: '◦', alignment: AlignmentType.LEFT,
            style: { paragraph: { indent: { left: 1440, hanging: 360 } } } },
        ],
      },
      { reference: 'numbers',
        levels: [
          { level: 0, format: LevelFormat.DECIMAL, text: '%1.', alignment: AlignmentType.LEFT,
            style: { paragraph: { indent: { left: 720, hanging: 360 } } } },
        ],
      },
    ],
  },
  sections: [{
    properties: {
      page: {
        size: { width: 12240, height: 15840 }, // US Letter
        margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 }, // 1"
      },
    },
    children: sections,
  }],
});

const outDir = path.join(__dirname, '..', 'docs');
const outPath = path.join(outDir, 'Loxia-Autopilot-Onboarding-Deep-Track.docx');

Packer.toBuffer(doc)
  .then(buf => {
    fs.writeFileSync(outPath, buf);
    console.log('Wrote', outPath, '(' + buf.length + ' bytes)');
  })
  .catch(err => {
    console.error('Failed:', err);
    process.exit(1);
  });
