// Shared DOCX building blocks for the copilot-export module (Demo-067 C,
// KAR-982). Server-only (the `docx` package is only ever imported from
// server actions / this module, never from a client component — mirrors how
// lib/qaf-differences/internal/export.ts dynamic-imports `exceljs`).
//
// Color literals below are ARGB/hex used inside a generated Office document,
// not a Tailwind class or React inline style — scripts/check-hardcoded-
// colors.mjs only forbids specific Tailwind utility patterns and one
// specific logo-only roundel-blue literal (see script source for the exact
// value), so these are unaffected; same precedent as lib/assessment-
// export.ts's BMW_BLUE_ARGB and lib/qaf-differences/internal/xlsx-style-
// helpers.ts's FILL_* constants. Chosen to match the CURRENT CI primary
// (CLAUDE.md's color-system section, UI-Primary #037493) rather than either
// of those older exports' legacy blue.

import {
  Document,
  HeadingLevel,
  Packer,
  Paragraph,
  ShadingType,
  Table,
  TableCell,
  TableRow,
  TextRun,
  WidthType,
} from 'docx'
import type { AiInstructionBlockContent } from './types'

export const DOCX_PRIMARY_HEX = '037493'
export const DOCX_PRIMARY_DARK_HEX = '035970'
export const DOCX_HEADER_TEXT_HEX = 'FFFFFF'
export const DOCX_MUTED_TEXT_HEX = '69707A'
export const DOCX_BORDER_HEX = 'C1C5CB'
export const DOCX_DISCLAIMER_FILL_HEX = 'FFE082'
export const DOCX_SOFT_FILL_HEX = 'E0F2FF'

const FULL_WIDTH = { size: 100, type: WidthType.PERCENTAGE } as const

// Usable A4 width in DXA twips (21cm page minus 2×2.5cm default margins).
// Passing explicit columnWidths prevents the docx library's degenerate
// tblGrid fallback (100 twips per column) when only per-cell percentage
// widths are set — some renderers honor the grid over the cell widths.
const TABLE_WIDTH_DXA = 9026

function evenColumnWidths(columnCount: number): number[] {
  return Array.from({ length: columnCount }, () => Math.floor(TABLE_WIDTH_DXA / columnCount))
}

export function docTitle(text: string): Paragraph {
  return new Paragraph({ text, heading: HeadingLevel.TITLE })
}

export function heading1(text: string): Paragraph {
  return new Paragraph({ text, heading: HeadingLevel.HEADING_1, spacing: { before: 240, after: 120 } })
}

export function heading2(text: string): Paragraph {
  return new Paragraph({ text, heading: HeadingLevel.HEADING_2, spacing: { before: 200, after: 100 } })
}

export function heading3(text: string): Paragraph {
  return new Paragraph({ text, heading: HeadingLevel.HEADING_3, spacing: { before: 160, after: 80 } })
}

export interface BodyTextOptions {
  bold?: boolean
  italics?: boolean
  color?: string
}

export function bodyText(text: string, opts: BodyTextOptions = {}): Paragraph {
  return new Paragraph({
    spacing: { after: 100 },
    children: [new TextRun({ text, bold: opts.bold, italics: opts.italics, color: opts.color })],
  })
}

export function bulletItem(text: string): Paragraph {
  return new Paragraph({ text, bullet: { level: 0 }, spacing: { after: 60 } })
}

/** Empty spacer paragraph — used between sections instead of magic blank
 * `new Paragraph('')` calls scattered through builders. */
export function spacer(): Paragraph {
  return new Paragraph({ text: '' })
}

function headerCell(text: string, widthPct: number): TableCell {
  return new TableCell({
    width: { size: widthPct, type: WidthType.PERCENTAGE },
    shading: { fill: DOCX_PRIMARY_HEX, type: ShadingType.CLEAR },
    children: [new Paragraph({ children: [new TextRun({ text, bold: true, color: DOCX_HEADER_TEXT_HEX })] })],
  })
}

function bodyCell(text: string, widthPct: number, opts: { shadeHex?: string; muted?: boolean } = {}): TableCell {
  return new TableCell({
    width: { size: widthPct, type: WidthType.PERCENTAGE },
    shading: opts.shadeHex ? { fill: opts.shadeHex, type: ShadingType.CLEAR } : undefined,
    children: [new Paragraph({ children: [new TextRun({ text, color: opts.muted ? DOCX_MUTED_TEXT_HEX : undefined })] })],
  })
}

/** Label/value ("Steckbrief") table — two columns, no header row. */
export function kvTable(rows: ReadonlyArray<readonly [string, string]>): Table {
  return new Table({
    width: FULL_WIDTH,
    columnWidths: [Math.floor(TABLE_WIDTH_DXA * 0.32), Math.floor(TABLE_WIDTH_DXA * 0.68)],
    rows: rows.map(
      ([label, value]) =>
        new TableRow({
          children: [
            new TableCell({
              width: { size: 32, type: WidthType.PERCENTAGE },
              shading: { fill: 'F7F9FB', type: ShadingType.CLEAR },
              children: [new Paragraph({ children: [new TextRun({ text: label, bold: true })] })],
            }),
            new TableCell({
              width: { size: 68, type: WidthType.PERCENTAGE },
              children: [new Paragraph(value || '—')],
            }),
          ],
        }),
    ),
  })
}

/** Header-row data table (brand-shaded header, plain body rows). Every row
 * must have the same length as `headers`. */
export function dataTable(headers: readonly string[], rows: ReadonlyArray<readonly string[]>): Table {
  const cellPct = Math.floor(100 / headers.length)
  return new Table({
    width: FULL_WIDTH,
    columnWidths: evenColumnWidths(headers.length),
    rows: [
      new TableRow({ tableHeader: true, children: headers.map((h) => headerCell(h, cellPct)) }),
      ...rows.map((r) => new TableRow({ children: r.map((cell) => bodyCell(cell, cellPct)) })),
    ],
  })
}

/** Render the shared AI-instruction block (heading, about-text, usage
 * hints, glossary, optional fictional-data disclaimer) as DOCX content. */
export function renderAiInstructionBlock(content: AiInstructionBlockContent): (Paragraph | Table)[] {
  const out: (Paragraph | Table)[] = [
    heading1(content.heading),
    bodyText(content.aboutText),
    heading3(content.usageHeading),
    ...content.usageHints.map((hint) => bulletItem(hint)),
    heading3(content.glossaryHeading),
    dataTable(
      ['Begriff', 'Definition'],
      content.glossary.map((g) => [g.term, g.definition] as const),
    ),
  ]
  if (content.disclaimer) {
    out.push(spacer())
    out.push(
      new Table({
        width: FULL_WIDTH,
        rows: [
          new TableRow({
            children: [
              new TableCell({
                width: FULL_WIDTH,
                shading: { fill: DOCX_DISCLAIMER_FILL_HEX, type: ShadingType.CLEAR },
                children: [
                  new Paragraph({
                    children: [new TextRun({ text: 'Demo-Hinweis', bold: true })],
                  }),
                  new Paragraph({ text: content.disclaimer }),
                ],
              }),
            ],
          }),
        ],
      }),
    )
  }
  out.push(spacer())
  return out
}

/** Build a single-section Document from an ordered list of body elements. */
export function buildDocument(title: string, children: (Paragraph | Table)[]): Document {
  return new Document({
    creator: 'SupplierPulse',
    title,
    description: 'Copilot-Export — für die KI-gestützte Weiterverarbeitung erzeugt.',
    sections: [{ properties: {}, children }],
  })
}

/** Pack a Document into a Buffer — same "builder returns a buffer, the
 * server action base64-encodes it" split as lib/qaf-differences/internal/
 * export.ts's buildQafExportWorkbook (ArrayBuffer) — keeps DOCX and XLSX
 * builders symmetric and lets tests unzip the result directly (JSZip:
 * word/document.xml) without an extra decode step. */
export async function packDocumentToBuffer(doc: Document): Promise<Buffer> {
  return Packer.toBuffer(doc)
}

/** German-locale number formatting for DOCX/XLSX table cells. Returns the
 * em dash for null/undefined — never fabricates a 0. */
export function formatNumberDe(value: number | null | undefined, fractionDigits = 2): string {
  if (value === null || value === undefined || Number.isNaN(value)) return '—'
  return new Intl.NumberFormat('de-DE', {
    minimumFractionDigits: fractionDigits,
    maximumFractionDigits: fractionDigits,
  }).format(value)
}

export function formatPercentDe(fraction: number | null | undefined, fractionDigits = 1): string {
  if (fraction === null || fraction === undefined || Number.isNaN(fraction)) return '—'
  return `${new Intl.NumberFormat('de-DE', { minimumFractionDigits: fractionDigits, maximumFractionDigits: fractionDigits }).format(fraction * 100)} %`
}

/** Sanitize a value for use inside a generated filename — same discipline
 * as app/qaf-differences/actions.ts's sanitizeSheetFilePart (local copy,
 * not imported: that function lives in app/qaf-differences/export-filename.ts (moved out of actions.ts in Loop-10-Schnitt 4)). */
export function sanitizeFilenamePart(value: string | null | undefined, fallback: string): string {
  return (value ?? '').replace(/[^0-9A-Za-z_-]+/g, '_').replace(/^_+|_+$/g, '') || fallback
}
