/**
 * Software-offer catalog ("LogShip Cloud" packages, feature matrix, add-ons) —
 * shared SHAPE + DEFAULTS for client (app/composables/useOfferSoftwareQuote.ts,
 * components/offers/SoftwareQuoteModal.vue, pages/settings/software-catalog) and
 * server (server/utils/offers/softwareCatalog.ts, softwareQuotePdf.ts). Pure
 * constants/functions only — no Nuxt auto-imports — so Nitro can import it by
 * relative path.
 *
 * Persisted catalog = iDempiere tables CUST_SoftwarePlan + CUST_SoftwareCatalogItem
 * (see .claude/rules/offers/software-quote.md). These defaults are (a) the
 * fail-soft fallback while the tables are missing/empty and (b) the seed
 * ("Standardwerte importieren"). ⚠ Amounts are DRAFT list prices (logship.de
 * publishes "Preis auf Anfrage" only) — maintain real prices in the settings page.
 */

export type SoftwareSupport = 'ticket' | 'phone' | 'sla'
export type FeatureMode = 'standard' | 'onRequest' | 'quote'
export type FeatureState = 'included' | 'excluded' | 'addon' | 'onRequest'
export type AddonUnit = 'month' | 'once' | 'hour' | 'text'

export interface CatalogPlan {
  id: number | null
  key: string
  name: string
  nameEn: string
  seqNo: number
  popular: boolean
  priceMonthly: number
  priceYearly: number
  includedOrders: number // 0 = individual
  includedUsers: number // 0 = individual
  salesChannels: number // 0 = unlimited
  extraOrderPrice: number
  extraUserPrice: number
  support: SoftwareSupport
  description: string
  active?: boolean
}

export interface CatalogFeature {
  id: number | null
  key: string
  name: string
  nameEn: string
  group: string // core | warehouse | advanced | enterprise (AD_Reference CUST_SoftwareFeatureGroup)
  minPlanKey: string // key of the smallest package that includes it ('' = never included)
  mode: FeatureMode
  seqNo: number
  active?: boolean
}

export interface CatalogAddon {
  id: number | null
  key: string
  name: string
  nameEn: string
  price: number
  unit: AddonUnit
  note: string
  noteEn: string
  defaultSelected: boolean
  seqNo: number
  active?: boolean
}

export interface SoftwareCatalog {
  plans: CatalogPlan[]
  features: CatalogFeature[]
  addons: CatalogAddon[]
  source: 'idempiere' | 'defaults' | 'snapshot'
  missingTables: string[]
  empty?: boolean
  loadedAt?: string
}

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

// AD_Reference lists the two tables use (Value = code key, Name = iDempiere label).
// Same set as reference/test-scripts/software-catalog-seed-lists.mts.
export const SOFTWARE_REF_LISTS: Record<string, Array<{ value: string; name: string }>> = {
  CUST_SoftwareSupportLevel: [
    { value: 'ticket', name: 'Ticket' },
    { value: 'phone', name: 'Ticket + Telefon' },
    { value: 'sla', name: 'Ticket + Telefon + SLA' }
  ],
  CUST_SoftwareItemType: [
    { value: 'feature', name: 'Funktion' },
    { value: 'addon', name: 'Zusatzleistung' }
  ],
  CUST_SoftwareFeatureGroup: [
    { value: 'core', name: 'Kern-WMS' },
    { value: 'warehouse', name: 'Lager & Automatisierung' },
    { value: 'advanced', name: 'Erweitert & Schnittstellen' },
    { value: 'enterprise', name: 'Enterprise' }
  ],
  CUST_SoftwareFeatureMode: [
    { value: 'standard', name: 'Standard' },
    { value: 'onRequest', name: 'möglich / auf Anfrage' },
    { value: 'quote', name: 'gegen Angebot' }
  ],
  CUST_SoftwarePriceUnit: [
    { value: 'month', name: 'pro Monat' },
    { value: 'once', name: 'einmalig' },
    { value: 'hour', name: 'pro Stunde' },
    { value: 'text', name: 'Text / auf Anfrage' }
  ]
}

const plan = (p: Omit<CatalogPlan, 'id' | 'nameEn' | 'description'> & { nameEn?: string; description?: string }): CatalogPlan =>
  ({ id: null, nameEn: p.name, description: '', ...p })

const DEFAULT_PLANS: CatalogPlan[] = [
  // name = printed package / fee name, description = tagline under it (DE; EN offers fall back to the volumes text)
  plan({ key: 'starter', name: 'Start', nameEn: 'Start', description: 'Der komplette Fulfillment-Kern für Ihren Einstieg.', seqNo: 10, popular: false, priceMonthly: 119, priceYearly: 99, includedOrders: 1000, includedUsers: 1, salesChannels: 2, extraOrderPrice: 0.20, extraUserPrice: 49, support: 'ticket' }),
  plan({ key: 'business', name: 'Wachstum', nameEn: 'Growth', description: 'Mehr Volumen, Lagerautomatisierung und Ihr ganzes Team im System.', seqNo: 20, popular: true, priceMonthly: 229, priceYearly: 189, includedOrders: 2000, includedUsers: 2, salesChannels: 0, extraOrderPrice: 0.15, extraUserPrice: 49, support: 'ticket' }),
  plan({ key: 'pro', name: 'Premium', nameEn: 'Premium', description: 'Für Mandanten, Cross-Docking, BI-Auswertungen und Buchhaltungs-Anbindung.', seqNo: 30, popular: false, priceMonthly: 399, priceYearly: 329, includedOrders: 3500, includedUsers: 3, salesChannels: 0, extraOrderPrice: 0.12, extraUserPrice: 49, support: 'phone' }),
  plan({ key: 'enterprise', name: 'Enterprise', nameEn: 'Enterprise', description: 'Individuelle Volumen, API, dedizierte Instanz und SLA.', seqNo: 40, popular: false, priceMonthly: 0, priceYearly: 0, includedOrders: 0, includedUsers: 0, salesChannels: 0, extraOrderPrice: 0, extraUserPrice: 0, support: 'sla' })
]

const F = (key: string, group: string, minPlanKey: string, name: string, nameEn: string, mode: FeatureMode = 'standard'): Omit<CatalogFeature, 'seqNo' | 'id'> =>
  ({ key, group, minPlanKey, name, nameEn, mode })

// Feature matrix (logship.de/preise). Order = print order inside each group.
const DEFAULT_FEATURES: CatalogFeature[] = [
  F('cockpit', 'core', 'starter', 'Auftrags-Cockpit (alle Kanäle in einer Ansicht)', 'Order cockpit (all channels in one view)'),
  F('scanPickPack', 'core', 'starter', 'Scanbasiertes Pick & Pack', 'Scan-based pick & pack'),
  F('labels', 'core', 'starter', 'Versandlabel DHL, DPD & GLS (UPS, Sendcloud)', 'Shipping labels DHL, DPD & GLS (UPS, Sendcloud)'),
  F('tracking', 'core', 'starter', 'Eigene Tracking-Seite im Kunden-Branding', 'Branded tracking page'),
  F('returnsBasic', 'core', 'starter', 'Retouren-Basisfunktionen', 'Basic returns handling'),
  F('b2bPortal', 'warehouse', 'business', 'B2B-Portal', 'B2B portal'),
  F('packStations', 'warehouse', 'business', 'Unbegrenzte Kommissionier-Tische', 'Unlimited packing stations'),
  F('inboundInventory', 'warehouse', 'business', 'Wareneingang, Inventur & Umlagerung', 'Goods receipt, stocktaking & transfers'),
  F('androidApp', 'warehouse', 'business', 'Native Android-App (LogShip WMS)', 'Native Android app (LogShip WMS)'),
  F('batching', 'warehouse', 'business', 'Intelligentes Batching / Sammelkommissionierung', 'Smart batching / batch picking'),
  F('lotTracking', 'warehouse', 'business', 'MHD, Chargen & Seriennummern', 'Best-before, lots & serial numbers'),
  F('crossDocking', 'advanced', 'pro', 'Cross-Docking', 'Cross-docking'),
  F('packCamera', 'advanced', 'pro', 'Packplatz-Kamera-Anbindung (VideoShip)', 'Packing-station camera (VideoShip)'),
  F('fulfillmentBilling', 'advanced', 'pro', '3PL-Fulfillment-Abrechnung (Pick/Pack/Lager pro Mandant)', '3PL fulfillment billing (pick/pack/storage per client)'),
  F('multiOrg', 'advanced', 'pro', 'Mandantenfähigkeit / Multi-Org', 'Multi-client / multi-org'),
  F('bi', 'advanced', 'pro', 'BI-Dashboards & Auszahlungsabgleich (Amazon/eBay)', 'BI dashboards & payout reconciliation (Amazon/eBay)'),
  F('accounting', 'advanced', 'pro', 'Buchhaltungs-Schnittstellen (Lexware, PayJoe)', 'Accounting interfaces (Lexware, PayJoe)'),
  F('autostore', 'enterprise', 'enterprise', 'AutoStore-Anbindung', 'AutoStore integration'),
  F('api', 'enterprise', 'enterprise', 'API-Zugang', 'API access'),
  F('dedicated', 'enterprise', 'enterprise', 'Dedizierte Instanz', 'Dedicated instance', 'onRequest'),
  F('accountManager', 'enterprise', 'enterprise', 'Persönlicher Ansprechpartner', 'Dedicated account manager'),
  F('customDev', 'enterprise', 'enterprise', 'Individuelle Entwicklungen', 'Custom development', 'quote')
].map((f, i) => ({ id: null, seqNo: (i + 1) * 10, ...f }))

const DEFAULT_ADDONS: CatalogAddon[] = [
  { key: 'apiAccess', name: 'API-Zugang', nameEn: 'API access', price: 79, unit: 'month', note: '', noteEn: '' },
  { key: 'dedicated', name: 'Dedizierte Instanz (eigener Server, eigene Domain)', nameEn: 'Dedicated instance (own server, own domain)', price: 0, unit: 'text', note: 'auf Anfrage', noteEn: 'on request' },
  { key: 'customDev', name: 'Individuelle Entwicklungen', nameEn: 'Custom development', price: 120, unit: 'hour', note: 'gegen gesondertes Angebot', noteEn: 'against separate quote' },
  { key: 'training', name: 'Zusätzliche Team-Schulung (remote, 2 h)', nameEn: 'Additional team training (remote, 2 h)', price: 490, unit: 'once', note: '', noteEn: '' },
  { key: 'fulfillment', name: 'Fulfillment durch LogYou (Lager Butzbach)', nameEn: 'Fulfillment by LogYou (warehouse Butzbach)', price: 0, unit: 'text', note: 'gemäß separatem Fulfillment-Angebot', noteEn: 'see separate fulfillment quote' }
].map((a, i) => ({ id: null, defaultSelected: false, seqNo: (i + 1) * 10, ...a } as CatalogAddon))

export const DEFAULT_SOFTWARE_CATALOG: SoftwareCatalog = {
  plans: DEFAULT_PLANS,
  features: DEFAULT_FEATURES,
  addons: DEFAULT_ADDONS,
  source: 'defaults',
  missingTables: []
}

/** Deep copy of the defaults (callers mutate forms/rows). */
export const defaultSoftwareCatalog = (): SoftwareCatalog => JSON.parse(JSON.stringify(DEFAULT_SOFTWARE_CATALOG))

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

/**
 * Normalise anything catalog-shaped (API answer, payload snapshot, saved
 * conditions) into a complete SoftwareCatalog: missing parts fall back to the
 * defaults, rows are sorted by seqNo, keys trimmed, enums coerced.
 */
export const normalizeSoftwareCatalog = (raw: any): SoftwareCatalog => {
  const d = DEFAULT_SOFTWARE_CATALOG
  const plans: CatalogPlan[] = (Array.isArray(raw?.plans) && raw.plans.length ? raw.plans : d.plans).map((p: any, i: number) => ({
    id: p?.id != null && Number(p.id) > 0 ? Number(p.id) : null,
    key: String(p?.key || '').trim(),
    name: String(p?.name || '').trim(),
    nameEn: String(p?.nameEn || '').trim(),
    seqNo: num(p?.seqNo) || (i + 1) * 10,
    popular: p?.popular === true,
    priceMonthly: num(p?.priceMonthly),
    priceYearly: num(p?.priceYearly),
    includedOrders: num(p?.includedOrders),
    includedUsers: num(p?.includedUsers),
    salesChannels: num(p?.salesChannels),
    extraOrderPrice: num(p?.extraOrderPrice),
    extraUserPrice: num(p?.extraUserPrice),
    support: (['ticket', 'phone', 'sla'] as const).includes(p?.support) ? p.support : 'ticket',
    description: String(p?.description || ''),
    active: p?.active !== false
  })).filter((p: CatalogPlan) => p.key).sort((a: CatalogPlan, b: CatalogPlan) => a.seqNo - b.seqNo)
  const features: CatalogFeature[] = (Array.isArray(raw?.features) && raw.features.length ? raw.features : d.features).map((f: any, i: number) => ({
    id: f?.id != null && Number(f.id) > 0 ? Number(f.id) : null,
    key: String(f?.key || '').trim(),
    name: String(f?.name ?? f?.de ?? '').trim(),
    nameEn: String(f?.nameEn ?? f?.en ?? '').trim(),
    group: String(f?.group || 'core'),
    minPlanKey: String(f?.minPlanKey ?? f?.minPlan ?? '').trim(),
    mode: f?.mode === 'onRequest' || f?.mode === 'quote' ? f.mode : 'standard',
    seqNo: num(f?.seqNo) || (i + 1) * 10,
    active: f?.active !== false
  })).filter((f: CatalogFeature) => f.key).sort((a: CatalogFeature, b: CatalogFeature) => a.seqNo - b.seqNo)
  const addons: CatalogAddon[] = (Array.isArray(raw?.addons) ? raw.addons : d.addons).map((a: any, i: number) => ({
    id: a?.id != null && Number(a.id) > 0 ? Number(a.id) : null,
    key: String(a?.key || '').trim(),
    name: String(a?.name || a?.label || '').trim(),
    nameEn: String(a?.nameEn || '').trim(),
    price: num(a?.price),
    unit: (['month', 'once', 'hour', 'text'] as const).includes(a?.unit) ? a.unit : 'month',
    note: String(a?.note || ''),
    noteEn: String(a?.noteEn || ''),
    defaultSelected: a?.defaultSelected === true,
    seqNo: num(a?.seqNo) || (i + 1) * 10,
    active: a?.active !== false
  })).filter((a: CatalogAddon) => a.key).sort((a: CatalogAddon, b: CatalogAddon) => a.seqNo - b.seqNo)
  return {
    plans,
    features,
    addons,
    source: raw?.source === 'idempiere' || raw?.source === 'snapshot' ? raw.source : 'defaults',
    missingTables: Array.isArray(raw?.missingTables) ? raw.missingTables : [],
    empty: raw?.empty === true,
    loadedAt: raw?.loadedAt
  }
}

/** Plans in upgrade order (seqNo asc), active only. */
export const activePlans = (c: SoftwareCatalog): CatalogPlan[] => c.plans.filter((p) => p.active !== false)
export const planByKey = (c: SoftwareCatalog, key: any): CatalogPlan | undefined => c.plans.find((p) => p.key === String(key))
/** Upgrade rank of a package (index in seqNo order); -1 when unknown. */
export const planRank = (c: SoftwareCatalog, key: any): number => activePlans(c).findIndex((p) => p.key === String(key))

/** Catalog default state of a feature inside a given package (what the comparison columns print). */
export const featureDefaultState = (c: SoftwareCatalog, feature: Pick<CatalogFeature, 'minPlanKey' | 'mode'>, planKey: any): FeatureState => {
  const r = planRank(c, planKey)
  const min = planRank(c, feature.minPlanKey)
  const incl = r >= 0 && min >= 0 && r >= min
  if (incl) return feature.mode === 'onRequest' ? 'onRequest' : 'included'
  return feature.mode === 'quote' ? 'addon' : 'excluded'
}

export const seedFeatureStates = (c: SoftwareCatalog, planKey: any): Record<string, FeatureState> =>
  Object.fromEntries(c.features.filter((f) => f.active !== false).map((f) => [f.key, featureDefaultState(c, f, planKey)]))

/** Label in the offer language (EN falls back to DE). */
export const catalogLabel = (row: { name: string; nameEn?: string }, language: string): string =>
  language === 'en' && String(row?.nameEn || '').trim() ? String(row.nameEn).trim() : String(row?.name || '').trim()
export const catalogNote = (row: { note: string; noteEn?: string }, language: string): string =>
  language === 'en' && String(row?.noteEn || '').trim() ? String(row.noteEn).trim() : String(row?.note || '').trim()

/** The subset of the catalog that goes into an offer payload / saved conditions (what the PDF renders). */
export const catalogSnapshot = (c: SoftwareCatalog) => ({
  source: 'snapshot',
  plans: activePlans(c).map(({ id, key, name, nameEn, seqNo, popular, support }) => ({ id, key, name, nameEn, seqNo, popular, support })),
  features: c.features.filter((f) => f.active !== false).map(({ id, key, name, nameEn, group, minPlanKey, mode, seqNo }) => ({ id, key, name, nameEn, group, minPlanKey, mode, seqNo })),
  addons: c.addons.filter((a) => a.active !== false).map(({ id, key, name, nameEn, price, unit, note, noteEn, defaultSelected, seqNo }) => ({ id, key, name, nameEn, price, unit, note, noteEn, defaultSelected, seqNo }))
})
