// Shared pdfmake document definition for the "Picklist" report. Every picklist
// entry point renders through this one builder so the report looks the same
// everywhere: /generate-shipments (normal), the dashboard reprint modal
// (usePicklistPdf.ts), /integrations/picklist-desktop, /mobile/picklist and
// /mobile/generate-shipments.
//
// Layout: A4 portrait, rows roughly twice as tall as the old 6.5 pt report.
// The extra height is used on purpose — the product name may wrap to two lines
// and the codes are stacked (Value / SKU over MPN / UPC) — so the larger type
// still fits the portrait width. The locator is drawn as three bordered chips
// (X, Y, Z) in fixed-width columns so aisles line up down the page; Qty is bold.
//
// Column order follows the pick: Org, Locator, Qty, Pick Code, Name, then the
// reference columns (Weight, all codes). "Pick Code" is the code the merchant's
// pickers match — C_BPartner.PickProductIdentifier of the org-linked partner,
// delivered per line as `pickIdentifier` (see ~/utils/pickCode.ts). It stays
// EMPTY when the merchant has no identifier configured; the product-level
// PickListCode only drives the digital lists, not this report.

import { resolvePickCode, getProductCodeValue, PICK_CODE_LABELS } from './pickCode'

interface PicklistDocOptions {
  rows: any[]
  relatedDocNos?: string[]
  today: string
  title?: string
}

const NAME_MAX_CHARS = 48
const LINE_COLOR = '#9f9f9f'
const CHIP_BORDER = '#0f172a'
const CHIP_WIDTH = 20
const CHIP_GAP = 4

// Content widths (pt, without cell padding) of the fixed columns; Name takes the rest.
const ORG_WIDTH = 60
const LOCATOR_WIDTH = 3 * CHIP_WIDTH + 2 * CHIP_GAP + 18
const QTY_WIDTH = 28
const PICK_CODE_WIDTH = 106
const WEIGHT_WIDTH = 32
const CODES_WIDTH = 92

// Rough Roboto advance widths as a fraction of the font size — good enough to pick a
// font size that keeps a string on one line (pdfmake exposes no measuring API here).
const CHAR_EM = 0.54
const CHAR_EM_BOLD = 0.6
const fitFontSize = (text: string | number, width: number, max: number, min: number, em = CHAR_EM) => {
  const len = Math.max(typeof text === 'number' ? text : text.length, 1)
  return Math.max(min, Math.min(max, Math.floor((width / (len * em)) * 10) / 10))
}

// Print-friendly take on the on-screen loc-x / loc-y / loc-z chips (the report is
// mostly printed on a b/w laser): X inverted, Y shaded, Z plain — all bordered.
const CHIP_STYLES = [
  { fillColor: '#0f172a', color: '#ffffff' },
  { fillColor: '#e5e7eb', color: '#0f172a' },
  { fillColor: '#ffffff', color: '#0f172a' },
]

const truncate = (str: any, max: number) => {
  const s = (str ?? '').toString()
  if (!s) return '-'
  return s.length > max ? `${s.slice(0, max - 1)}…` : s
}

const formatWeight = (w: any) => {
  const n = Number(w)
  return Number.isFinite(n) && n > 0 ? `${Number(n.toFixed(3))} kg` : '-'
}

// pdfmake table cells have no vertical alignment. The codes cell is always two
// lines, so text that is certain to fit on ONE line gets a top margin to sit
// centred in the row; anything that may wrap starts at the top instead.
const NAME_ONE_LINE_CHARS = 22
const centredIfShort = (text: string, maxChars: number) => (text.length <= maxChars ? [0, 4, 0, 0] : [0, 0, 0, 0])

// Org name on at most two lines, broken ONLY at whitespace: pdfmake would otherwise
// split a long word mid-way ("simpeltradi / ng"). The lines are laid out here (most
// balanced split), rendered noWrap, and the type shrinks until the longest line fits.
const orgCell = (org: string) => {
  const words = org.trim().split(/\s+/).filter(Boolean)
  let lines = [words.join(' ') || '-']
  if (words.length > 1 && lines[0].length * CHAR_EM * 9 > ORG_WIDTH) {
    let best = Infinity
    for (let i = 1; i < words.length; i++) {
      const a = words.slice(0, i).join(' ')
      const b = words.slice(i).join(' ')
      const longest = Math.max(a.length, b.length)
      if (longest < best) { best = longest; lines = [a, b] }
    }
  }
  const longest = lines.reduce((m, l) => Math.max(m, l.length), 0)
  const fontSize = fitFontSize(longest, ORG_WIDTH, 9, 5.5)
  return {
    stack: lines.map(l => ({ text: l, noWrap: true })),
    fontSize,
    margin: lines.length === 1 ? [0, 4, 0, 0] : [0, 0, 0, 0],
  }
}

// The merchant's configured identifier, big and bold, with its field name underneath.
// Falls back (normal weight) to the next available code when the configured field is
// empty on this product; empty cell when the merchant has nothing configured.
const pickCodeCell = (item: any) => {
  if (!item?.pickIdentifier) return { text: '' }
  const resolved = resolvePickCode(item.M_Product_ID, item.pickIdentifier)
  const value = resolved ? getProductCodeValue(item.M_Product_ID, resolved.key) : ''
  if (!resolved || !value) return { text: '' }
  const configured = resolved.source === 'partner'
  return {
    stack: [
      {
        text: value,
        bold: configured,
        // digits are narrower than capitals in Roboto — lets an EAN-13 print at full size
        fontSize: fitFontSize(value, PICK_CODE_WIDTH, configured ? 14 : 10, 7, /^\d+$/.test(value) ? 0.57 : (configured ? 0.64 : CHAR_EM)),
      },
      { text: PICK_CODE_LABELS[resolved.key] || '', fontSize: 6, color: '#555555' },
    ],
  }
}

const qtyCell = (qty: any) => {
  const text = String(qty ?? '')
  return {
    text,
    bold: true,
    noWrap: true,
    fontSize: fitFontSize(text, QTY_WIDTH, 13, 7, CHAR_EM_BOLD),
    alignment: 'center',
    margin: [0, 2, 0, 0],
  }
}

// Locator values are normally two characters ("AF", "06"); shrink the type for the
// odd long one so it stays inside its chip instead of wrapping.
const chipFontSize = (value: string) => {
  if (value.length <= 3) return 10
  if (value.length <= 5) return 7.5
  return 6
}

const locatorCell = (loc: any) => {
  const parts = [loc?.X, loc?.Y, loc?.Z].map(p => (p ?? '').toString().trim())
  if (parts.every(p => p === '')) return { text: '-', style: 'tableCell', margin: [0, 4, 0, 0] }

  const row: any[] = []
  parts.forEach((part, idx) => {
    if (idx > 0) row.push({ text: '', border: [false, false, false, false] })
    const value = part || '–'
    row.push({
      text: value,
      bold: true,
      fontSize: chipFontSize(value),
      alignment: 'center',
      noWrap: true,
      border: [true, true, true, true],
      ...CHIP_STYLES[idx],
    })
  })

  return {
    margin: [0, 1, 0, 0],
    table: {
      widths: [CHIP_WIDTH, CHIP_GAP, CHIP_WIDTH, CHIP_GAP, CHIP_WIDTH],
      body: [row],
    },
    layout: {
      hLineWidth: () => 0.8,
      vLineWidth: () => 0.8,
      hLineColor: () => CHIP_BORDER,
      vLineColor: () => CHIP_BORDER,
      // even columns are chips, odd columns are the gaps between them
      paddingLeft: (i: number) => (i % 2 === 0 ? 2 : 0),
      paddingRight: (i: number) => (i % 2 === 0 ? 2 : 0),
      paddingTop: () => 2,
      paddingBottom: () => 2,
    },
  }
}

export const buildPicklistDocDefinition = ({ rows, relatedDocNos = [], today, title = 'Picklist' }: PicklistDocOptions) => {
  const tableBody: any[] = [
    [
      { text: 'Org', style: 'tableHeader' },
      { text: 'Locator', style: 'tableHeader' },
      { text: 'Qty', style: 'tableHeader', alignment: 'center' },
      { text: 'Pick Code', style: 'tableHeader' },
      { text: 'Name', style: 'tableHeader' },
      { text: 'Weight', style: 'tableHeader', alignment: 'right' },
      { text: 'Value / SKU\nMPN / UPC', style: 'tableHeader' },
    ],
    ...(rows || []).map((item: any) => {
      const value = item.M_Product_ID?.Value || '-'
      const sku = item.M_Product_ID?.SKU || '-'
      const mpn = item.M_Product_ID?.mpn || '-'
      const upc = item.M_Product_ID?.UPC || '-'
      const org = item.AD_Org_ID?.identifier || '-'
      const name = truncate(item.M_Product_ID?.Name, NAME_MAX_CHARS)
      const weight = formatWeight(item.M_Product_ID?.Weight)
      return [
        orgCell(org),
        locatorCell(item.M_Locator_ID),
        qtyCell(item.QtyEntered),
        pickCodeCell(item),
        { text: name, style: 'tableCell', margin: centredIfShort(name, NAME_ONE_LINE_CHARS) },
        { text: weight, fontSize: fitFontSize(weight, WEIGHT_WIDTH, 8, 6), alignment: 'right', noWrap: true, margin: [0, 4, 0, 0] },
        { text: `${value} / ${sku}\n${mpn} / ${upc}`, style: 'tableCellCodes' },
      ]
    }),
  ]

  const content: any[] = [
    { text: title, style: 'title', alignment: 'center' },
    { text: today, style: 'subtitle', alignment: 'center', margin: [0, 2, 0, 8] },
    {
      table: {
        headerRows: 1,
        widths: [ORG_WIDTH, LOCATOR_WIDTH, QTY_WIDTH, PICK_CODE_WIDTH, '*', WEIGHT_WIDTH, CODES_WIDTH],
        body: tableBody,
        dontBreakRows: true,
      },
      layout: {
        hLineWidth: () => 0.5,
        vLineWidth: () => 0.5,
        hLineColor: () => LINE_COLOR,
        vLineColor: () => LINE_COLOR,
        fillColor: (rowIndex: number) => rowIndex === 0 ? '#f2f2f2' : null,
        paddingLeft: () => 3,
        paddingRight: () => 3,
        paddingTop: () => 3,
        paddingBottom: () => 3,
      },
    },
  ]

  if (relatedDocNos.length > 0) {
    content.push({
      margin: [0, 8, 0, 0],
      table: {
        widths: ['*'],
        body: [[{
          stack: [
            { text: `Related Shipments (${relatedDocNos.length})`, fontSize: 8, bold: true, margin: [0, 0, 0, 3] },
            { text: relatedDocNos.join(', '), fontSize: 7.5, lineHeight: 1.4 },
          ],
          margin: [4, 4, 4, 4],
        }]],
      },
      layout: {
        hLineWidth: () => 0.5,
        vLineWidth: () => 0.5,
        hLineColor: () => LINE_COLOR,
        vLineColor: () => LINE_COLOR,
      },
    })
  }

  return {
    pageSize: 'A4',
    pageOrientation: 'portrait',
    pageMargins: [15, 15, 15, 25],
    content,
    footer: (currentPage: number, pageCount: number) =>
      pageCount > 1
        ? { text: `${currentPage} / ${pageCount}`, alignment: 'center', fontSize: 8, margin: [0, 8, 0, 0] }
        : null,
    styles: {
      title: { fontSize: 14, bold: true },
      subtitle: { fontSize: 8 },
      tableHeader: { fontSize: 8, bold: true },
      tableCell: { fontSize: 8.5 },
      tableCellCodes: { fontSize: 6.5 },
    },
    defaultStyle: { fontSize: 9 },
  }
}
