/**
 * Software offer (LogShip Cloud SaaS) business logic — the second offer type
 * next to the 3PL fulfillment quote (useOfferQuote.ts). Same contract: the
 * client owns the numbers (defaults, forecast, plan comparison); the server
 * routes (server/api/offers/quote/{preview,send} with `offerType: 'software'`)
 * only render/send what they receive.
 *
 * The CATALOG (packages, feature matrix, add-ons) is maintained in iDempiere
 * (CUST_SoftwarePlan / CUST_SoftwareCatalogItem, Settings → Software-Katalog)
 * and loaded via GET /api/offers/software-catalog; every function here takes it
 * as a parameter and falls back to DEFAULT_SOFTWARE_CATALOG
 * (app/utils/softwareCatalogDefaults.ts). The payload carries a snapshot of the
 * catalog used, so the PDF and the saved conditions stay reproducible.
 */
import { quoteBusinessModelFromLabel } from './useOfferQuote'
import {
  defaultSoftwareCatalog, normalizeSoftwareCatalog, activePlans, planByKey, planRank,
  featureDefaultState, seedFeatureStates, catalogLabel, catalogNote, catalogSnapshot,
  type SoftwareCatalog, type CatalogPlan, type FeatureState
} from '~/utils/softwareCatalogDefaults'

export type { SoftwareCatalog, CatalogPlan, FeatureState }
export { defaultSoftwareCatalog, normalizeSoftwareCatalog, activePlans, planByKey, planRank, featureDefaultState, seedFeatureStates, catalogLabel, catalogNote }

export const SOFTWARE_FEATURE_GROUPS = ['core', 'warehouse', 'advanced', 'enterprise'] as const
export const ONBOARDING_DEFAULT_PRICE = 3250

// Onboarding package: six phases from process analysis to 30 days hypercare
// (logship.de FAQ) + four highlight tiles. Both lists are editable per offer.
export const ONBOARDING_DEFAULTS = (language: string) => (language === 'en'
  ? {
      intro: 'Sit back — we take you live. Our team sets up LogShip Cloud for you, trains your team and stays by your side until the go-live has succeeded, so you ship faster, more accurately and cheaper from day one.',
      phases: [
        { title: 'Kickoff & process analysis', text: 'We get to know your warehouse, your processes and your goals and define the LogShip roadmap together — tailored to your business.' },
        { title: 'Setup & data import', text: 'We configure LogShip step by step: sales channels, carriers, warehouse structure and bins, printers, automations — including the import of your articles and stock.' },
        { title: 'Test phase & acceptance', text: 'Test orders through the whole chain (order · pick · pack · label · tracking) with your real data; open points are closed before the go-live.' },
        { title: 'Live training for your team', text: 'An interactive session on the everyday functions — pick & pack, scanning, goods receipt, shipping, returns — with all your questions answered.' },
        { title: 'Go-live support', text: 'We accompany the first live days closely: a fixed contact person, short reaction times, daily check-ins.' },
        { title: '30 days hypercare', text: 'After the go-live we stay on board for 30 days: regular check-ins, fine-tuning of automations and reports, appointments whenever you need them.' }
      ],
      highlights: [
        { value: '1:1', label: 'personal contact' },
        { value: '30 days', label: 'hypercare after go-live' },
        { value: '99.92 %', label: 'pick accuracy in our own warehouse' },
        { value: '80+', label: 'brands live on LogShip' }
      ]
    }
  : {
      intro: 'Lehnen Sie sich zurück — wir bringen Sie live. Unser Team richtet LogShip Cloud komplett für Sie ein, schult Ihr Team und begleitet Sie bis zum erfolgreichen Go-Live — damit Sie vom ersten Tag an schneller, genauer und günstiger versenden.',
      phases: [
        { title: 'Kickoff & Prozessaufnahme', text: 'Wir lernen Ihr Lager, Ihre Prozesse und Ihre Ziele kennen und legen gemeinsam den LogShip-Fahrplan fest — abgestimmt auf Ihr Business.' },
        { title: 'Einrichtung & Datenimport', text: 'Wir richten LogShip Schritt für Schritt ein: Verkaufskanäle, Versanddienstleister, Lagerstruktur und Lagerplätze, Drucker, Automatisierungen — inklusive Import Ihrer Artikel und Bestände.' },
        { title: 'Testphase & Abnahme', text: 'Testbestellungen durch die gesamte Kette (Auftrag · Pick · Pack · Label · Tracking) mit Ihren echten Daten; offene Punkte werden vor dem Go-Live geschlossen.' },
        { title: 'Live-Schulung für Ihr Team', text: 'In einer interaktiven Schulung zeigen wir die wichtigsten Funktionen für den Alltag — Pick & Pack, Scannen, Wareneingang, Versand, Retouren — und beantworten alle Fragen.' },
        { title: 'Go-Live-Begleitung', text: 'Die ersten Live-Tage begleiten wir eng: fester Ansprechpartner, kurze Reaktionszeiten, tägliche Check-ins.' },
        { title: '30 Tage Hypercare', text: 'Auch nach dem Go-Live bleiben wir 30 Tage an Ihrer Seite: regelmäßige Check-ins, Feinjustierung von Automatisierungen und Auswertungen, Termine, wann immer Sie sie brauchen.' }
      ],
      highlights: [
        { value: '1:1', label: 'persönliche Betreuung' },
        { value: '30 Tage', label: 'Hypercare nach Go-Live' },
        { value: '99,92 %', label: 'Pick-Genauigkeit im eigenen Lager' },
        { value: '80+', label: 'Brands live mit LogShip' }
      ]
    })

// PDF sections the user can switch off per offer (PDF order). Address card,
// intro, Hinweise and closing always print.
export const SOFTWARE_SECTION_KEYS = ['overview', 'pricing', 'features', 'onboarding', 'addons', 'forecast'] as const
export const SOFTWARE_TEXT_ANCHORS = ['overview', 'pricing', 'features', 'onboarding', 'addons', 'positions', 'forecast', 'end'] as const
const isTextAnchor = (v: any): boolean => (SOFTWARE_TEXT_ANCHORS as readonly string[]).includes(String(v))

const num = (v: any): number => {
  const n = Number(v)
  return Number.isFinite(n) ? n : 0
}

const firstOfNextMonth = (): string => {
  const d = new Date()
  const next = new Date(d.getFullYear(), d.getMonth() + 1, 1)
  return `${next.getFullYear()}-${String(next.getMonth() + 1).padStart(2, '0')}-01`
}

/** The package a fresh offer starts with: the "popular" one, else the second, else the first. */
export const defaultPlanKey = (catalog: SoftwareCatalog): string => {
  const plans = activePlans(catalog)
  return (plans.find((p) => p.popular) || plans[1] || plans[0])?.key || ''
}

/** Plan-derived fields of the form (what "apply package" writes). */
export const planFields = (catalog: SoftwareCatalog, key: any) => {
  const p = planByKey(catalog, key) || activePlans(catalog)[0] || defaultSoftwareCatalog().plans[0]
  return {
    key: p.key,
    name: p.name,
    nameEn: p.nameEn,
    description: p.description || '', // tagline from the catalog (fee-line subtext / card line)
    popular: p.popular === true,
    priceMonthly: p.priceMonthly,
    priceYearly: p.priceYearly,
    includedOrders: p.includedOrders,
    includedUsers: p.includedUsers,
    salesChannels: p.salesChannels,
    extraUserPrice: p.extraUserPrice,
    support: p.support,
    // Overage staffel: one flat tier by default ('' upTo = unlimited); the modal
    // can add absolute thresholds (e.g. up to 5.000 → 0,15, above → 0,12).
    overageTiers: p.extraOrderPrice > 0 ? [{ upTo: '', price: p.extraOrderPrice }] : []
  }
}

/** Add-on rows of a fresh offer from the catalog (labels in the offer language). */
export const seedAddons = (catalog: SoftwareCatalog, language: string) =>
  catalog.addons.filter((a) => a.active !== false).map((a) => ({
    key: a.key,
    label: catalogLabel(a, language),
    enabled: a.defaultSelected === true,
    price: a.price,
    unit: a.unit,
    note: catalogNote(a, language)
  }))

/** Fresh form from the host-page prefill (same prefill contract as the fulfillment quote) + catalog. */
export const createSoftwareQuoteForm = (prefill: any = {}, catalogIn?: SoftwareCatalog | null) => {
  const catalog = catalogIn ? normalizeSoftwareCatalog(catalogIn) : defaultSoftwareCatalog()
  const language = 'de'
  const planKey = defaultPlanKey(catalog)
  return {
    offerType: 'software',
    source: prefill?.source || 'partner',
    recordId: num(prefill?.recordId),
    recordUu: prefill?.recordUu || '',
    language,
    // Snapshot of the catalog this offer is built on (goes into the payload → PDF + saved conditions)
    catalog: catalogSnapshot(catalog),
    customer: {
      company: prefill?.company || '',
      contactName: prefill?.contactName || '',
      email: prefill?.email || '',
      phone: prefill?.phone || '',
      street: String(prefill?.street || ''),
      zip: String(prefill?.zip || ''),
      city: String(prefill?.city || ''),
      country: String(prefill?.country || '') || 'Deutschland',
      website: String(prefill?.website || '').trim()
    },
    visuals: {
      includeScreenshot: String(prefill?.website || '').trim() !== '',
      screenshotDataUrl: '',
      includeLogo: !!prefill?.logoDataUrl,
      logoDataUrl: prefill?.logoDataUrl || ''
    },
    plan: {
      ...planFields(catalog, planKey),
      // Per-offer overrides for the monthly software-fee line in the PDF; empty = automatic
      // (fee name = package name, subtext = catalog tagline + included volumes)
      feeLabel: '',
      feeSubtext: '',
      billing: 'yearly' as 'yearly' | 'monthly',
      minTermMonths: 12,
      plannedStart: /^\d{4}-\d{2}-\d{2}$/.test(String(prefill?.plannedStart || '')) && String(prefill.plannedStart) >= new Date().toISOString().slice(0, 10) ? String(prefill.plannedStart) : firstOfNextMonth()
    },
    // Customer situation — drives overage + plan recommendation
    assumptions: {
      ordersPerMonth: num(prefill?.monthlyOrders) || 1500,
      users: 2,
      salesChannels: 2,
      warehouses: 1,
      skuCount: num(prefill?.skuCount) || 300,
      shopSystem: String(prefill?.shopSystem || ''),
      businessModel: quoteBusinessModelFromLabel(prefill?.businessModel) || 'B2C'
    },
    features: seedFeatureStates(catalog, planKey),
    customFeatures: [] as Array<{ label: string; state: FeatureState }>,
    featuresCompare: true, // print all package columns (selected one highlighted) vs. only the chosen plan
    addons: seedAddons(catalog, language) as Array<{ key: string; label: string; enabled: boolean; price: number; unit: string; note: string }>,
    onboarding: {
      enabled: true,
      price: ONBOARDING_DEFAULT_PRICE,
      priceIsFrom: true, // print "ab 3.250 €" — the final price follows the scoping call
      travelExcluded: true, // "zzgl. Reise- und Übernachtungskosten" for on-site appointments
      ...ONBOARDING_DEFAULTS(language)
    },
    customPositions: [] as Array<{ description: string; dimension: string; price: string }>,
    requirements: [] as Array<{ text: string; possible: boolean; answerType: 'check' | 'text'; answerText: string }>,
    sections: Object.fromEntries(SOFTWARE_SECTION_KEYS.map((k) => [k, true])) as Record<string, boolean>,
    customTexts: [] as Array<{ title: string; text: string; after: string }>,
    forecast: { includeInPdf: true },
    meta: { validDays: 14, validUntil: '' },
    attachments: { flyer: false },
    confirmation: { enabled: true, attachPdf: true },
    email: { to: prefill?.email || '', cc: '', subject: '', message: '' }
  }
}

/** Re-seed the plan-derived fields + feature states when the user picks another package. */
export const applySoftwarePlan = (form: any, key: any, catalog: SoftwareCatalog) => {
  Object.assign(form.plan, planFields(catalog, key))
  form.features = seedFeatureStates(catalog, key)
}

/** Language switch: swap untouched default texts (add-ons, onboarding) to the other language. */
export const applySoftwareLanguageDefaults = (form: any, prev: string, next: string, catalog: SoftwareCatalog) => {
  if (prev === next) return
  const same = (a: any, b: any) => JSON.stringify(a) === JSON.stringify(b)
  const prevAddons = seedAddons(catalog, prev)
  if (same(form.addons.map((a: any) => ({ ...a, enabled: false })), prevAddons.map((a) => ({ ...a, enabled: false })))) {
    const enabled = new Set(form.addons.filter((a: any) => a.enabled).map((a: any) => a.key))
    form.addons = seedAddons(catalog, next).map((a) => ({ ...a, enabled: enabled.has(a.key) }))
  }
  const prevOb = ONBOARDING_DEFAULTS(prev)
  const nextOb = ONBOARDING_DEFAULTS(next)
  if (same(form.onboarding.phases, prevOb.phases)) form.onboarding.phases = nextOb.phases.map((p) => ({ ...p }))
  if (same(form.onboarding.highlights, prevOb.highlights)) form.onboarding.highlights = nextOb.highlights.map((h) => ({ ...h }))
  if (form.onboarding.intro === prevOb.intro) form.onboarding.intro = nextOb.intro
}

const fillNonEmpty = (target: any, src: any) => {
  if (!target || !src || typeof src !== 'object') return
  for (const k of Object.keys(target)) {
    const v = src[k]
    if (v !== undefined && v !== null && String(v) !== '' && typeof v !== 'object') target[k] = v
  }
}

/**
 * Apply the record's saved software-offer conditions onto a fresh form (saved
 * values win). The CURRENT catalog stays the basis; a saved feature key the
 * catalog no longer knows is kept as a custom row (label from the saved snapshot).
 */
export const applySavedSoftwareQuoteConditions = (form: any, saved: any, catalog: SoftwareCatalog) => {
  if (!saved || typeof saved !== 'object') return form
  if (saved.language === 'en' || saved.language === 'de') {
    applySoftwareLanguageDefaults(form, form.language, saved.language, catalog)
    form.language = saved.language
  }
  fillNonEmpty(form.customer, saved.customer)
  if (saved.plan && typeof saved.plan === 'object') {
    if (planByKey(catalog, saved.plan.key)) applySoftwarePlan(form, saved.plan.key, catalog)
    fillNonEmpty(form.plan, saved.plan)
    if (Array.isArray(saved.plan.overageTiers)) {
      form.plan.overageTiers = saved.plan.overageTiers.map((t: any) => ({ upTo: t?.upTo === '' || t?.upTo == null ? '' : num(t.upTo), price: num(t?.price) }))
    }
    const today = new Date().toISOString().slice(0, 10)
    if (String(form.plan.plannedStart || '') < today) form.plan.plannedStart = firstOfNextMonth()
  }
  fillNonEmpty(form.assumptions, saved.assumptions)
  const knownKeys = new Set(Object.keys(form.features))
  const snapshotFeatures: any[] = Array.isArray(saved?.catalog?.features) ? saved.catalog.features : []
  if (saved.features && typeof saved.features === 'object') {
    for (const [k, v] of Object.entries(saved.features)) {
      if (!['included', 'excluded', 'addon', 'onRequest'].includes(String(v))) continue
      if (knownKeys.has(k)) {
        form.features[k] = v as FeatureState
      } else if (v !== 'excluded') {
        const snap = snapshotFeatures.find((f) => f?.key === k)
        form.customFeatures.push({ label: snap ? catalogLabel(snap, form.language) : k, state: v as FeatureState })
      }
    }
  }
  if (Array.isArray(saved.customFeatures)) {
    for (const r of saved.customFeatures) {
      const label = String(r?.label || '')
      if (label && !form.customFeatures.some((c: any) => c.label === label)) {
        form.customFeatures.push({ label, state: ['included', 'excluded', 'addon', 'onRequest'].includes(r?.state) ? r.state : 'included' })
      }
    }
  }
  if (typeof saved.featuresCompare === 'boolean') form.featuresCompare = saved.featuresCompare
  if (Array.isArray(saved.addons) && saved.addons.length) {
    form.addons = saved.addons.map((a: any) => ({
      key: String(a?.key || ''),
      label: String(a?.label || ''),
      enabled: a?.enabled === true,
      price: num(a?.price),
      unit: ['month', 'once', 'hour', 'text'].includes(a?.unit) ? a.unit : 'month',
      note: String(a?.note || '')
    }))
  }
  if (saved.onboarding && typeof saved.onboarding === 'object') {
    if (typeof saved.onboarding.enabled === 'boolean') form.onboarding.enabled = saved.onboarding.enabled
    if (saved.onboarding.price !== undefined) form.onboarding.price = num(saved.onboarding.price)
    if (typeof saved.onboarding.priceIsFrom === 'boolean') form.onboarding.priceIsFrom = saved.onboarding.priceIsFrom
    if (typeof saved.onboarding.travelExcluded === 'boolean') form.onboarding.travelExcluded = saved.onboarding.travelExcluded
    if (typeof saved.onboarding.intro === 'string' && saved.onboarding.intro.trim()) form.onboarding.intro = saved.onboarding.intro
    if (Array.isArray(saved.onboarding.phases) && saved.onboarding.phases.length) {
      form.onboarding.phases = saved.onboarding.phases.map((p: any) => ({ title: String(p?.title || ''), text: String(p?.text || '') }))
    }
    if (Array.isArray(saved.onboarding.highlights)) {
      form.onboarding.highlights = saved.onboarding.highlights.map((h: any) => ({ value: String(h?.value || ''), label: String(h?.label || '') }))
    }
  }
  if (Array.isArray(saved.customPositions)) {
    form.customPositions = saved.customPositions.map((r: any) => ({ description: String(r?.description || ''), dimension: String(r?.dimension || ''), price: String(r?.price ?? '') }))
  }
  if (Array.isArray(saved.requirements)) {
    form.requirements = saved.requirements.map((r: any) => ({ text: String(r?.text || ''), possible: r?.possible !== false, answerType: r?.answerType === 'text' ? 'text' : 'check', answerText: String(r?.answerText || '') }))
  }
  if (saved.sections && typeof saved.sections === 'object') {
    for (const k of SOFTWARE_SECTION_KEYS) if (typeof saved.sections[k] === 'boolean') form.sections[k] = saved.sections[k]
  }
  if (Array.isArray(saved.customTexts)) {
    form.customTexts = saved.customTexts.map((b: any) => ({ title: String(b?.title || ''), text: String(b?.text || ''), after: isTextAnchor(b?.after) ? String(b.after) : 'end' }))
  }
  if (saved.forecast && typeof saved.forecast.includeInPdf === 'boolean') form.forecast.includeInPdf = saved.forecast.includeInPdf
  if (num(saved.validDays) > 0) form.meta.validDays = num(saved.validDays)
  if (saved.confirmation && saved.confirmation.enabled === false) form.confirmation.enabled = false
  if (saved.confirmation && typeof saved.confirmation.attachPdf === 'boolean') form.confirmation.attachPdf = saved.confirmation.attachPdf
  if (!form.email.to && saved.customer?.email) form.email.to = saved.customer.email
  return form
}

/** Progressive overage cost for orders above `includedOrders` (tiers = absolute monthly thresholds). */
export const computeOverage = (includedOrders: number, ordersPerMonth: number, tiers: Array<{ upTo: any; price: any }>) => {
  const included = Math.max(0, num(includedOrders))
  const extra = included > 0 ? Math.max(0, num(ordersPerMonth) - included) : 0
  const rows: Array<{ from: number; to: number | null; price: number; qty: number; cost: number }> = []
  if (!extra || !Array.isArray(tiers) || !tiers.length) {
    return { extraOrders: extra, cost: 0, rows }
  }
  const sorted = [...tiers]
    .map((t) => ({ upTo: t.upTo === '' || t.upTo == null ? null : num(t.upTo), price: num(t.price) }))
    .sort((a, b) => (a.upTo == null ? 1 : b.upTo == null ? -1 : a.upTo - b.upTo))
  let remaining = extra
  let prev = included
  let cost = 0
  for (const t of sorted) {
    if (remaining <= 0) break
    const cap = t.upTo == null ? remaining : Math.max(0, t.upTo - prev)
    const qty = Math.min(remaining, cap)
    if (qty > 0) {
      cost += qty * t.price
      rows.push({ from: prev + 1, to: t.upTo, price: t.price, qty, cost: qty * t.price })
    }
    remaining -= qty
    if (t.upTo != null) prev = Math.max(prev, t.upTo)
  }
  if (remaining > 0 && sorted.length) {
    const last = sorted[sorted.length - 1]
    cost += remaining * last.price
    rows.push({ from: prev + 1, to: null, price: last.price, qty: remaining, cost: remaining * last.price })
  }
  return { extraOrders: extra, cost, rows }
}

/** Monthly / one-time / first-year forecast of the configured offer. Pure function of the form. */
export const computeSoftwareForecast = (form: any) => {
  const pl = form.plan || {}
  const a = form.assumptions || {}
  const on = (k: string) => form.sections?.[k] !== false
  const planPrice = pl.billing === 'monthly' ? num(pl.priceMonthly) : num(pl.priceYearly)
  const overage = computeOverage(pl.includedOrders, a.ordersPerMonth, pl.overageTiers || [])
  const extraUsers = num(pl.includedUsers) > 0 ? Math.max(0, num(a.users) - num(pl.includedUsers)) : 0
  const extraUsersCost = extraUsers * num(pl.extraUserPrice)
  const addonsOn = on('addons') ? (form.addons || []).filter((x: any) => x.enabled) : []
  const addonsMonthly = addonsOn.filter((x: any) => x.unit === 'month').reduce((s: number, x: any) => s + num(x.price), 0)
  const addonsOnce = addonsOn.filter((x: any) => x.unit === 'once').reduce((s: number, x: any) => s + num(x.price), 0)
  const onboarding = on('onboarding') && form.onboarding?.enabled ? num(form.onboarding.price) : 0

  const lines: Array<{ key: string; monthly: number; qty?: number }> = [{ key: 'plan', monthly: planPrice }]
  if (overage.extraOrders > 0) lines.push({ key: 'overage', monthly: overage.cost, qty: overage.extraOrders })
  if (extraUsers > 0) lines.push({ key: 'extraUsers', monthly: extraUsersCost, qty: extraUsers })
  if (addonsMonthly > 0) lines.push({ key: 'addons', monthly: addonsMonthly })
  const totalMonthly = lines.reduce((s, l) => s + l.monthly, 0)

  const oneTime: Array<{ key: string; amount: number }> = []
  if (onboarding > 0) oneTime.push({ key: 'onboarding', amount: onboarding })
  if (addonsOnce > 0) oneTime.push({ key: 'addonsOnce', amount: addonsOnce })
  const totalOneTime = oneTime.reduce((s, l) => s + l.amount, 0)

  const orders = num(a.ordersPerMonth)
  return {
    lines,
    totalMonthly,
    oneTime,
    totalOneTime,
    firstYear: totalMonthly * 12 + totalOneTime,
    perOrder: orders > 0 ? totalMonthly / orders : 0,
    planPrice,
    overage,
    extraUsers,
    isOnRequest: planPrice <= 0
  }
}

/**
 * Side-panel helper: monthly total for EVERY catalog package at the customer's
 * volume (catalog list prices, not the edited ones) + the recommended package
 * (smallest one whose included orders AND users cover the assumptions).
 */
export const computePlanComparison = (form: any, catalog: SoftwareCatalog) => {
  const a = form.assumptions || {}
  const billing = form.plan?.billing === 'monthly' ? 'monthly' : 'yearly'
  const rows = activePlans(catalog).map((p) => {
    const price = billing === 'monthly' ? p.priceMonthly : p.priceYearly
    const ov = computeOverage(p.includedOrders, a.ordersPerMonth, p.extraOrderPrice > 0 ? [{ upTo: '', price: p.extraOrderPrice }] : [])
    const extraUsers = p.includedUsers > 0 ? Math.max(0, num(a.users) - p.includedUsers) : 0
    const channelsOk = p.salesChannels === 0 || num(a.salesChannels) <= p.salesChannels
    const individual = p.includedOrders === 0 && p.includedUsers === 0
    const fits = individual || (p.includedOrders >= num(a.ordersPerMonth) && p.includedUsers >= num(a.users) && channelsOk)
    return { key: p.key, name: catalogLabel(p, form.language), onRequest: price <= 0, monthly: price + ov.cost + extraUsers * p.extraUserPrice, extraOrders: ov.extraOrders, extraUsers, fits, channelsOk }
  })
  const recommended = rows.find((r) => r.fits)?.key || rows[rows.length - 1]?.key || ''
  const priced = rows.filter((r) => !r.onRequest)
  const cheapest = priced.length ? priced.reduce((m, r) => (r.monthly < m.monthly ? r : m), priced[0]).key : null
  return { rows, recommended, cheapest }
}

export const softwareQuoteEmailDefaults = (language: string, contactName: string, company: string, validDays: number, planName: string, onlineConfirm = false, attachPdf = true) => {
  const linkOnly = onlineConfirm && !attachPdf
  if (language === 'en') {
    return {
      subject: `Your LogShip Cloud offer (${planName}) – ${company}`,
      message: `Dear ${contactName || 'Sir or Madam'},\n\nthank you for your interest in LogShip Cloud. ${linkOnly ? `Your individual offer is ready for you online — simply click "Open quote" in this email to read it and download it as PDF. Clicking the button does not accept anything. The offer is valid for ${validDays} days.` : `Please find attached your individual offer for the ${planName} package as PDF — it is valid for ${validDays} days.`}\n\nHow it continues:\n1. Accept the offer: ${onlineConfirm ? 'once you have read it, you can accept it on the same page in a separate step — or simply reply to this email with "Offer accepted".' : 'simply reply to this email with "Offer accepted".'}\n2. Kickoff: we schedule the onboarding kickoff within one business day and agree on your go-live date.\n3. Go-live: your channels, carriers and warehouse are set up, your team is trained — and you ship with LogShip.\n\nAny questions first? Just reply to this email or call us at +49 60 33 / 91 60 57-0 — we are happy to show you LogShip in a live demo.\n\nBest regards\nYour LogShip team`
    }
  }
  return {
    subject: `Ihr Angebot LogShip Cloud (${planName}) – ${company}`,
    message: `Guten Tag ${contactName || 'Damen und Herren'},\n\nvielen Dank für Ihr Interesse an LogShip Cloud. ${linkOnly ? `Ihr individuelles Angebot steht für Sie online bereit — klicken Sie einfach in dieser E-Mail auf "Angebot öffnen", um es zu lesen und als PDF herunterzuladen. Der Klick ist unverbindlich. Das Angebot ist ${validDays} Tage gültig.` : `Anbei erhalten Sie Ihr individuelles Angebot für das Paket ${planName} als PDF — es ist ${validDays} Tage gültig.`}\n\nSo geht es weiter:\n1. Angebot annehmen: ${onlineConfirm ? 'Wenn Sie das Angebot gelesen haben, können Sie es auf derselben Seite in einem separaten Schritt online annehmen — oder antworten Sie einfach formlos mit "Angebot angenommen".' : 'Antworten Sie auf diese E-Mail einfach mit "Angebot angenommen".'}\n2. Kickoff: Wir vereinbaren innerhalb von einem Werktag den Onboarding-Kickoff und legen gemeinsam Ihren Go-Live-Termin fest.\n3. Go-Live: Kanäle, Versanddienstleister und Lager sind eingerichtet, Ihr Team ist geschult — und Sie versenden mit LogShip.\n\nSie haben vorab noch Fragen? Antworten Sie einfach auf diese E-Mail oder rufen Sie uns an: +49 60 33 / 91 60 57-0 — gerne zeigen wir Ihnen LogShip in einer Live-Demo.\n\nMit freundlichen Grüßen\nIhr LogShip Team`
  }
}

/** Payload for the preview/send routes (offerType 'software' selects the SaaS renderer). */
export const buildSoftwareQuotePayload = (form: any, forecast: any, withEmail = false) => {
  const payload: any = {
    docType: 'quote',
    offerType: 'software',
    source: form.source,
    recordId: form.recordId,
    recordUu: form.recordUu || undefined,
    catalog: JSON.parse(JSON.stringify(form.catalog || {})),
    customer: { ...form.customer, country: form.customer.country || 'Deutschland' },
    visuals: {
      includeScreenshot: form.visuals.includeScreenshot === true && !!form.visuals.screenshotDataUrl,
      screenshotDataUrl: form.visuals.includeScreenshot ? form.visuals.screenshotDataUrl : '',
      includeLogo: form.visuals.includeLogo === true && !!form.visuals.logoDataUrl,
      logoDataUrl: form.visuals.includeLogo ? form.visuals.logoDataUrl : '',
      websiteUrl: form.customer.website || ''
    },
    plan: JSON.parse(JSON.stringify(form.plan)),
    assumptions: JSON.parse(JSON.stringify(form.assumptions)),
    features: { ...form.features },
    customFeatures: form.customFeatures.filter((f: any) => String(f?.label || '').trim() !== ''),
    featuresCompare: form.featuresCompare !== false,
    addons: JSON.parse(JSON.stringify(form.addons)),
    onboarding: JSON.parse(JSON.stringify(form.onboarding)),
    customPositions: form.customPositions.filter((r: any) => String(r?.description || '').trim() !== ''),
    requirements: form.requirements.filter((r: any) => String(r?.text || '').trim() !== ''),
    sections: { ...form.sections },
    customTexts: form.customTexts.filter((b: any) => String(b?.text || '').trim() !== ''),
    forecast: {
      includeInPdf: form.forecast.includeInPdf === true,
      lines: forecast.lines,
      totalMonthly: forecast.totalMonthly,
      oneTime: forecast.oneTime,
      totalOneTime: forecast.totalOneTime,
      firstYear: forecast.firstYear,
      perOrder: forecast.perOrder,
      overageRows: forecast.overage?.rows || [],
      extraUsers: forecast.extraUsers
    },
    meta: {
      language: form.language,
      date: new Date().toISOString().slice(0, 10),
      validDays: form.meta.validDays,
      validUntil: form.meta.validUntil || undefined
    },
    attachments: { flyer: form.attachments?.flyer === true },
    confirmation: { enabled: form.confirmation?.enabled !== false, attachPdf: form.confirmation?.attachPdf === true }
  }
  if (withEmail) payload.email = { ...form.email }
  return payload
}
