/**
 * ZUGFeRD / Factur-X CII XML builder (EN16931 / "COMFORT" profile).
 *
 * Pure, side-effect-free: takes a normalized invoice object and returns the
 * Cross-Industry-Invoice (CII) XML string that gets embedded into the iDempiere
 * PDF as `factur-x.xml`. No iDempiere/HTTP calls here — the orchestrator
 * (`maybeZugferdPdf.ts`) gathers the data and maps it into `ZugferdInvoiceData`.
 *
 * Profile URN: urn:cen.eu:en16931:2017 (full EN16931 — the standard B2B level).
 * Pragmatic v1 scope (see CLAUDE.md plan): tax-exclusive invoices, VAT category
 * derived from the rate (S for >0, Z for 0). Harden later if a validator demands.
 */

export interface ZugferdParty {
  name: string
  /** BT-29 / BT-46 party identifier (any string; satisfies BR-CO-26 for the seller). */
  id?: string
  /** BT-30 legal registration id, e.g. German Handelsregisternummer (HRB …). */
  legalRegId?: string
  vatId?: string
  street?: string
  city?: string
  postcode?: string
  /** ISO 3166-1 alpha-2, e.g. "DE" */
  countryCode?: string
}

export interface ZugferdLine {
  lineId: number
  name: string
  qty: number
  /** UN/ECE Rec 20 unit code; "C62" = one/piece (pragmatic default) */
  unitCode: string
  netPrice: number
  lineTotal: number
  taxRate: number
  /** VAT category code: S (standard), Z (zero-rated) */
  taxCategory: string
}

export interface ZugferdTaxBreakdown {
  rate: number
  basis: number
  amount: number
  category: string
}

export interface ZugferdInvoiceData {
  documentNo: string
  /** YYYY-MM-DD or ISO date string */
  issueDate: string
  /** YYYY-MM-DD or ISO date string (optional) — BT-9 payment due date */
  dueDate?: string
  /** BT-20 free-text payment terms (satisfies BR-CO-25 even without a due date) */
  paymentTerms?: string
  /** BT-72 actual delivery / service date (YYYY-MM-DD); defaults to issueDate */
  deliveryDate?: string
  /** 380 = commercial invoice, 381 = credit note */
  typeCode: '380' | '381'
  /** ISO 4217, e.g. "EUR" */
  currency: string
  /** BT-10 buyer reference (optional; required for XRechnung, optional for EN16931) */
  buyerReference?: string
  /** BT-13 purchase order number (optional) */
  orderRef?: string
  seller: ZugferdParty
  buyer: ZugferdParty
  lines: ZugferdLine[]
  taxes: ZugferdTaxBreakdown[]
  /** BT-106 sum of line net amounts */
  lineTotal: number
  /** BT-109 tax basis total */
  taxBasisTotal: number
  /** BT-110 tax total */
  taxTotal: number
  /** BT-112 grand total incl. tax */
  grandTotal: number
  /** BT-115 amount due for payment */
  duePayable: number
  /** optional SEPA credit-transfer payee account */
  paymentIban?: string
}

const escapeXml = (value: any): string =>
  String(value ?? '')
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&apos;')

/** Money / percent: fixed 2 decimals, "." separator, no thousands. */
const amt = (n: any): string => (Number(n) || 0).toFixed(2)

/** Unit net price (BT-146): up to 4 decimals so price × quantity reconciles to the
 *  2-decimal line amount (EN16931 permits >2 decimals on the item net price). */
const priceAmt = (n: any): string => (Number(n) || 0).toFixed(4)

/** Quantity: integer stays integer, else up to 4 decimals. */
const qtyFmt = (n: any): string => {
  const x = Number(n) || 0
  return Number.isInteger(x) ? String(x) : x.toFixed(4)
}

/** Any date-ish string -> YYYYMMDD (CII format code 102). */
const toYmd = (value: any): string => {
  const s = String(value ?? '')
  const m = s.match(/(\d{4})-(\d{2})-(\d{2})/)
  if (m) return `${m[1]}${m[2]}${m[3]}`
  const digits = s.replace(/[^0-9]/g, '')
  return digits.slice(0, 8)
}

const partyXml = (tag: string, p: ZugferdParty): string => {
  const lines: string[] = []
  lines.push(`      <ram:${tag}>`)
  if (p.id) lines.push(`        <ram:ID>${escapeXml(p.id)}</ram:ID>`)
  lines.push(`        <ram:Name>${escapeXml(p.name)}</ram:Name>`)
  // BT-30 legal registration id (e.g. Handelsregisternummer) — optional, only if present.
  if (p.legalRegId) {
    lines.push(`        <ram:SpecifiedLegalOrganization>`)
    lines.push(`          <ram:ID>${escapeXml(p.legalRegId)}</ram:ID>`)
    lines.push(`        </ram:SpecifiedLegalOrganization>`)
  }
  lines.push(`        <ram:PostalTradeAddress>`)
  if (p.postcode) lines.push(`          <ram:PostcodeCode>${escapeXml(p.postcode)}</ram:PostcodeCode>`)
  if (p.street) lines.push(`          <ram:LineOne>${escapeXml(p.street)}</ram:LineOne>`)
  if (p.city) lines.push(`          <ram:CityName>${escapeXml(p.city)}</ram:CityName>`)
  lines.push(`          <ram:CountryID>${escapeXml(p.countryCode || 'DE')}</ram:CountryID>`)
  lines.push(`        </ram:PostalTradeAddress>`)
  if (p.vatId) {
    lines.push(`        <ram:SpecifiedTaxRegistration>`)
    lines.push(`          <ram:ID schemeID="VA">${escapeXml(p.vatId)}</ram:ID>`)
    lines.push(`        </ram:SpecifiedTaxRegistration>`)
  }
  lines.push(`      </ram:${tag}>`)
  return lines.join('\n')
}

const lineItemXml = (line: ZugferdLine): string => `    <ram:IncludedSupplyChainTradeLineItem>
      <ram:AssociatedDocumentLineDocument>
        <ram:LineID>${escapeXml(line.lineId)}</ram:LineID>
      </ram:AssociatedDocumentLineDocument>
      <ram:SpecifiedTradeProduct>
        <ram:Name>${escapeXml(line.name || '-')}</ram:Name>
      </ram:SpecifiedTradeProduct>
      <ram:SpecifiedLineTradeAgreement>
        <ram:NetPriceProductTradePrice>
          <ram:ChargeAmount>${priceAmt(line.netPrice)}</ram:ChargeAmount>
        </ram:NetPriceProductTradePrice>
      </ram:SpecifiedLineTradeAgreement>
      <ram:SpecifiedLineTradeDelivery>
        <ram:BilledQuantity unitCode="${escapeXml(line.unitCode || 'C62')}">${qtyFmt(line.qty)}</ram:BilledQuantity>
      </ram:SpecifiedLineTradeDelivery>
      <ram:SpecifiedLineTradeSettlement>
        <ram:ApplicableTradeTax>
          <ram:TypeCode>VAT</ram:TypeCode>
          <ram:CategoryCode>${escapeXml(line.taxCategory || 'S')}</ram:CategoryCode>
          <ram:RateApplicablePercent>${amt(line.taxRate)}</ram:RateApplicablePercent>
        </ram:ApplicableTradeTax>
        <ram:SpecifiedTradeSettlementLineMonetarySummation>
          <ram:LineTotalAmount>${amt(line.lineTotal)}</ram:LineTotalAmount>
        </ram:SpecifiedTradeSettlementLineMonetarySummation>
      </ram:SpecifiedLineTradeSettlement>
    </ram:IncludedSupplyChainTradeLineItem>`

const tradeTaxXml = (t: ZugferdTaxBreakdown): string => `        <ram:ApplicableTradeTax>
          <ram:CalculatedAmount>${amt(t.amount)}</ram:CalculatedAmount>
          <ram:TypeCode>VAT</ram:TypeCode>
          <ram:BasisAmount>${amt(t.basis)}</ram:BasisAmount>
          <ram:CategoryCode>${escapeXml(t.category || 'S')}</ram:CategoryCode>
          <ram:RateApplicablePercent>${amt(t.rate)}</ram:RateApplicablePercent>
        </ram:ApplicableTradeTax>`

export const buildInvoiceXml = (data: ZugferdInvoiceData): string => {
  const lineItems = (data.lines || []).map(lineItemXml).join('\n')
  const tradeTaxes = (data.taxes || []).map(tradeTaxXml).join('\n')

  const orderRefXml = data.orderRef
    ? `      <ram:BuyerOrderReferencedDocument>
        <ram:IssuerAssignedID>${escapeXml(data.orderRef)}</ram:IssuerAssignedID>
      </ram:BuyerOrderReferencedDocument>\n`
    : ''

  const buyerReferenceXml = data.buyerReference
    ? `      <ram:BuyerReference>${escapeXml(data.buyerReference)}</ram:BuyerReference>\n`
    : ''

  const paymentMeansXml = data.paymentIban
    ? `        <ram:SpecifiedTradeSettlementPaymentMeans>
          <ram:TypeCode>58</ram:TypeCode>
          <ram:PayeePartyCreditorFinancialAccount>
            <ram:IBANID>${escapeXml(data.paymentIban.replace(/\s+/g, ''))}</ram:IBANID>
          </ram:PayeePartyCreditorFinancialAccount>
        </ram:SpecifiedTradeSettlementPaymentMeans>\n`
    : ''

  // SpecifiedTradePaymentTerms (CII order: Description, then DueDateDateTime).
  // Emitting BT-20 (Description) and/or BT-9 (DueDate) satisfies BR-CO-25.
  const ptInner: string[] = []
  if (data.paymentTerms) ptInner.push(`          <ram:Description>${escapeXml(data.paymentTerms)}</ram:Description>`)
  if (data.dueDate) ptInner.push(`          <ram:DueDateDateTime>
            <udt:DateTimeString format="102">${toYmd(data.dueDate)}</udt:DateTimeString>
          </ram:DueDateDateTime>`)
  const paymentTermsXml = ptInner.length
    ? `        <ram:SpecifiedTradePaymentTerms>\n${ptInner.join('\n')}\n        </ram:SpecifiedTradePaymentTerms>\n`
    : ''

  return `<?xml version="1.0" encoding="UTF-8"?>
<rsm:CrossIndustryInvoice xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100" xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
  <rsm:ExchangedDocumentContext>
    <ram:GuidelineSpecifiedDocumentContextParameter>
      <ram:ID>urn:cen.eu:en16931:2017</ram:ID>
    </ram:GuidelineSpecifiedDocumentContextParameter>
  </rsm:ExchangedDocumentContext>
  <rsm:ExchangedDocument>
    <ram:ID>${escapeXml(data.documentNo)}</ram:ID>
    <ram:TypeCode>${data.typeCode}</ram:TypeCode>
    <ram:IssueDateTime>
      <udt:DateTimeString format="102">${toYmd(data.issueDate)}</udt:DateTimeString>
    </ram:IssueDateTime>
  </rsm:ExchangedDocument>
  <rsm:SupplyChainTradeTransaction>
${lineItems}
    <ram:ApplicableHeaderTradeAgreement>
${buyerReferenceXml}${partyXml('SellerTradeParty', data.seller)}
${partyXml('BuyerTradeParty', data.buyer)}
${orderRefXml}    </ram:ApplicableHeaderTradeAgreement>
    <ram:ApplicableHeaderTradeDelivery>
      <ram:ActualDeliverySupplyChainEvent>
        <ram:OccurrenceDateTime>
          <udt:DateTimeString format="102">${toYmd(data.deliveryDate || data.issueDate)}</udt:DateTimeString>
        </ram:OccurrenceDateTime>
      </ram:ActualDeliverySupplyChainEvent>
    </ram:ApplicableHeaderTradeDelivery>
    <ram:ApplicableHeaderTradeSettlement>
      <ram:InvoiceCurrencyCode>${escapeXml(data.currency || 'EUR')}</ram:InvoiceCurrencyCode>
${paymentMeansXml}${tradeTaxes}
${paymentTermsXml}      <ram:SpecifiedTradeSettlementHeaderMonetarySummation>
        <ram:LineTotalAmount>${amt(data.lineTotal)}</ram:LineTotalAmount>
        <ram:TaxBasisTotalAmount>${amt(data.taxBasisTotal)}</ram:TaxBasisTotalAmount>
        <ram:TaxTotalAmount currencyID="${escapeXml(data.currency || 'EUR')}">${amt(data.taxTotal)}</ram:TaxTotalAmount>
        <ram:GrandTotalAmount>${amt(data.grandTotal)}</ram:GrandTotalAmount>
        <ram:DuePayableAmount>${amt(data.duePayable)}</ram:DuePayableAmount>
      </ram:SpecifiedTradeSettlementHeaderMonetarySummation>
    </ram:ApplicableHeaderTradeSettlement>
  </rsm:SupplyChainTradeTransaction>
</rsm:CrossIndustryInvoice>`
}

export default buildInvoiceXml
