// https://nuxt.com/docs/api/configuration/nuxt-config

// Sentry release name = the build's git hash (injected into GITHASH at deploy time).
// Skip the literal '<GIT_HASH>' placeholder used in local/un-deployed envs.
const sentryRelease = process.env.GITHASH && !process.env.GITHASH.includes('<')
    ? process.env.GITHASH
    : undefined

// Second build target: a static, mobile-only SPA bundled into the Capacitor Android app.
// Activated with BUILD_TARGET=capacitor. Every branch below is gated on this flag, so when
// it is unset the web/SSR build is byte-identical to before.
const isCapacitor = process.env.BUILD_TARGET === 'capacitor'

// The bundled app talks to ONE backend host for everything — /api, /media-api and /files-api are all
// served from this origin via nginx. Strapi base/token follow it so the app never points at the local
// .env's dev Strapi host (that mismatch broke product images + media uploads). Override per channel
// with CAP_API_BASE / CAP_STRAPI_BASE / CAP_STRAPI_TOKEN.
const capApiBase = process.env.CAP_API_BASE || 'https://app.logship.de'

// Non-/mobile routes the bundled app needs: the login flow + the staff chat page (/chat).
const capAuthPaths = ['/signin', '/select-role', '/login', '/register', '/verify-email', '/forgot-password', '/reset-password', '/chat']

export default defineNuxtConfig({
    devtools: { enabled: false },
    telemetry: false,
    // App target = client-only SPA; web target keeps SSR (the Nuxt default → identical output).
    ssr: isCapacitor ? false : true,
    modules: ['@nuxt/ui', 'nuxt-lazy-load', '@sentry/nuxt/module'],

    imports: {
        dirs: [
            'composables',
            'composables/menu/admin',
            'composables/menu/fulfillmentCustomer'
        ]
    },

    /*i18n: {
      vueI18n: './i18n.config.ts' // if you are using custom path, default
    },*/
    plugins: isCapacitor ? [] : [
        '~/plugins/amcharts.client.js'
    ],

    // App build only: (1) bundle just the mobile routes (+ auth) into the SPA; (2) drop
    // server-coupled / desktop-only client plugins. Hook object is undefined for the web build.
    hooks: {
        ...(isCapacitor ? {
        'pages:extend'(pages: any[]) {
            const allow = (p: any) => typeof p?.path === 'string'
                && (p.path.startsWith('/mobile') || capAuthPaths.includes(p.path))
            const walk = (arr: any[]) => {
                for (let i = arr.length - 1; i >= 0; i--) {
                    const r = arr[i]
                    if (r.children?.length) {
                        walk(r.children)
                        if (!r.children.length && !allow(r)) arr.splice(i, 1)
                    } else if (!allow(r)) {
                        arr.splice(i, 1)
                    }
                }
            }
            walk(pages)
        },
        } : {}),
        // App build: drop server-coupled / desktop-only plugins. Web build: drop the app-only API
        // plugin so it never loads (and @capacitor/preferences never enters) the web bundle.
        'app:resolve'(app: any) {
            if (isCapacitor) {
                // Note: 'chat-socket' is intentionally KEPT in the app build (chat works in the app).
                // 'push-notifications' drops the WEB VAPID plugin; the native FCM plugin is named
                // 'native-push' so it is NOT matched here.
                const drop = ['amcharts', 'service-worker', 'push-notifications', 'notifications-stream', 'pwa-session-resume']
                app.plugins = app.plugins.filter((p: any) => !drop.some(d => String(p.src).includes(d)))
            } else {
                // Web build: drop the app-only plugins so they (and their Capacitor deps) never load.
                app.plugins = app.plugins.filter((p: any) => !['capacitor-api', 'native-push', 'native-lifecycle'].some(d => String(p.src).includes(d)))
            }
        },
    },

    sourcemap: {
        server: false,
        // Generate client source maps (without the //# sourceMappingURL comment) so Sentry
        // can symbolicate browser stack traces. The maps are uploaded then deleted from the
        // build output (see `sentry.sourcemaps.filesToDeleteAfterUpload`), never served.
        client: 'hidden',
    },

    // Sentry error + performance monitoring. Build-time options (source-map upload +
    // server auto-injection). Runtime DSN lives in runtimeConfig.public.sentry below.
    sentry: {
        org: process.env.SENTRY_ORG,
        project: process.env.SENTRY_PROJECT,
        authToken: process.env.SENTRY_AUTH_TOKEN, // build-time only; missing token just skips upload
        // sentryUrl: process.env.SENTRY_URL,     // uncomment + set when moving to self-hosted Sentry
        release: sentryRelease ? { name: sentryRelease } : undefined,
        // Inject Sentry into the Nitro server entry at build time so the server SDK loads
        // WITHOUT a `node --import` flag — important because PM2 runs in cluster mode.
        autoInjectServerSentry: 'top-level-import',
        sourcemaps: {
            // Remove generated .map files from .output after upload so they are never served publicly.
            filesToDeleteAfterUpload: ['./.output/**/*.map'],
        },
    },

    experimental: {
        payloadExtraction: false,
    },

    vite: {
        // Build-time flag so app/router.options.ts can tree-shake the desktop-only routes out of
        // the app bundle. Only injected for the app build; web build never sees __CAP_BUILD__.
        ...(isCapacitor ? { define: { __CAP_BUILD__: 'true' } } : {}),
        build: {
            target: "es2022",
            minify: 'esbuild',
            // Vite 8 switched the CSS minifier to lightningcss, which rejects the invalid selector
            // '.steps .step-item::before .step-marker' in bulma-extensions.min.css and fails the build.
            // esbuild keeps the CSS output identical to what Vite 7 shipped.
            cssMinify: 'esbuild',
            rollupOptions: {
                maxParallelFileOps: 1,
            },
        },
        esbuild: {
            target: "es2022"
        },
        optimizeDeps: {
            include: [
                "pdfjs-dist",
                // amCharts 5 + its entry points (app/plugins/amcharts.client.js)
                "@amcharts/amcharts5",
                "@amcharts/amcharts5/xy",
                "@amcharts/amcharts5/percent",
                "@amcharts/amcharts5/themes/Animated",
                // CJS deps Vite would otherwise discover lazily on first use (causes a dev page reload)
                "pdfmake/build/pdfmake.js",
                "pdfmake/build/vfs_fonts.js",
                "alga-js",
                "bulma-toast",
                "tedir-calendar",
                "tedir-select",
                // useAuthSession.ts (pulled in by plugins/00.capacitor-api.client.ts, which
                // stays in the module graph in dev even though it early-returns on web) —
                // undiscovered until first hit, forcing the same disruptive reload.
                "@capacitor/preferences",
                // AG Grid (components/AgGrid.vue) and the PDF viewer
                // (components/PrintViewer.vue) — same issue: undiscovered until the first
                // page using them loads, and that forced mid-render reload is what left
                // AgGridVue's grid permanently un-initialised (empty ag-theme-balham div,
                // no console error) on the very first grid page hit of a dev session.
                "ag-grid-community",
                "ag-grid-enterprise",
                "ag-grid-vue3",
                "@tato30/vue-pdf",
            ],
            esbuildOptions: {
                target: "es2022",
                supported: {
                    "top-level-await": true
                },
            },
        },
        server: {
            watch: {
                ignored: [
                    '**/node_modules/**',
                    '**/.git/**',
                    '**/.nuxt/**',
                    '**/.output/**',
                    '**/dist/**',
                    '**/android/**',
                    '**/ios/**',
                    '**/public/**',
                    '**/coverage/**',
                    '**/reference/**'
                ]
            }
        },
    },

    nitro: {
        // Enable native WebSocket support (crossws) for the staff live-chat module.
        // Harmless when chat is disabled — nothing connects to /chat/ws.
        experimental: {
            websocket: true,
        },
        rollupConfig: {
            maxParallelFileOps: 1,
        },
        minify: false,
        // Keep tesseract.js external so its node worker_threads script resolves to real node_modules.
        // canvas is a native addon (Tier-2 OCR rasterize fallback); pdfjs-dist (legacy build, used
        // server-side for PDF text extraction + rasterize) is large — both resolve from node_modules.
        externals: {
            external: ['tesseract.js', 'tesseract.js-core', 'canvas', 'pdfjs-dist', 'sharp']
        },
        hooks: {
            // Nitro externalizes the deps above but only traces their JS entry — it
            // does NOT copy tesseract.js-core's .wasm or pdfjs-dist's worker into
            // .output, so they 404/ENOENT at runtime. Copy them in after build.
            async compiled(nitro: any) {
                const fs = await import('node:fs')
                const path = await import('node:path')
                const serverDir = nitro?.options?.output?.serverDir
                if (!serverDir) return
                const root = process.cwd()
                const copyDir = (rel: string) => {
                    const src = path.join(root, 'node_modules', rel)
                    const dest = path.join(serverDir, 'node_modules', rel)
                    if (!fs.existsSync(src)) { console.warn('[nitro] runtime asset missing in node_modules:', rel); return }
                    fs.mkdirSync(dest, { recursive: true })
                    fs.cpSync(src, dest, { recursive: true })
                }
                const copyFiles = (relDir: string, files: string[]) => {
                    const srcDir = path.join(root, 'node_modules', relDir)
                    const destDir = path.join(serverDir, 'node_modules', relDir)
                    if (!fs.existsSync(srcDir)) { console.warn('[nitro] runtime asset dir missing:', relDir); return }
                    fs.mkdirSync(destDir, { recursive: true })
                    for (const f of files) {
                        const s = path.join(srcDir, f)
                        if (fs.existsSync(s)) fs.copyFileSync(s, path.join(destDir, f))
                    }
                }
                try {
                    copyDir('tesseract.js-core')                                   // *.wasm + js wrappers
                    copyFiles(path.join('pdfjs-dist', 'legacy', 'build'),          // Node "fake worker"
                        ['pdf.worker.mjs', 'pdf.worker.min.mjs'])
                    console.log('[nitro] ✅ copied OCR/PDF runtime assets into .output/server/node_modules')
                } catch (e: any) {
                    console.warn('[nitro] runtime-asset copy failed:', e?.message || e)
                }
            }
        }
    },
    ui: {
        icons: ['mdi']
    },

    fonts: {
        // Fonts are self-hosted via assets/css/fonts.css — use the local provider
        // so @nuxt/fonts (bundled with @nuxt/ui v4) does not fetch any remote fonts.
        // ('none' is no longer a valid provider in the fontless-based engine.)
        provider: 'local',
        families: [
            { name: 'Inter', provider: 'local' },
            { name: 'Inconsolata', provider: 'local' },
            { name: 'VT323', provider: 'local' },
        ],
    },

    colorMode: {
        preference: 'light',
        fallback: 'light',
        classSuffix: '',
        dataValue: 'mode',
        storageKey: 'logship_theme'
    },

    lazyLoad: {
        // These are the default values
        images: true,
        videos: true,
        audios: true,
        iframes: true,
        native: false,
        directiveOnly: false,

        // Default image must be in the public folder
        defaultImage: '/assets/skeleton-loader-placeholder.jpg',

        // To remove class set value to false
        loadingClass: 'isLoading',
        loadedClass: 'isLoaded',
        appendClass: 'lazyLoad',

        observerConfig: {
            // See IntersectionObserver documentation
        }
    },

    css: [
        '~/assets/css/fonts.css',
        '~/assets/sass/main.scss',
        '~/assets/css/style.css',
        // '~/assets/css/helper.css',
        // '~/node_modules/bulma/css/bulma.css',
        '~/assets/css/main.css',
        // tedir-calendar DatePicker styles (data-v scoped to the component — safe globally;
        // without it the popup/grid is unstyled and navigation/selection appear broken).
        'tedir-calendar/css',
        'bulma/css/versions/bulma-no-dark-mode.min.css',
        'bulma-extensions/dist/css/bulma-extensions.min.css',
        '@creativebulma/bulma-tooltip/src/docs/static/css/bulma-tooltip.css',
        'bulma-responsive-tables/css/main.min.css',
        '~/assets/css/custom.css',
        // Mobile design-token system (light + dark). Loaded last so it wins.
        '~/assets/css/mobile-theme.css',
    ],

    postcss: {
        plugins: {
            'alga-css': {
                important: false,
                //extract: [
                //'./layouts/**/*.vue',
                //'./pages/**/*.vue',
                //'./components/**/*.vue'
                //],
                src: './app/assets/alga/*.alga',
                plugins: ['tedir-table', 'tedir-dropzone', 'tedir-select', 'tedir-calendar']      }
        }
    },

    app: {
        head: {
            title: 'LogShip ERP',
            link: [
                { rel: "stylesheet", href: "/assets/vendor/fonts/tabler-icons.css" },
                { rel: "stylesheet", href: "/css/styles.css" },
                { rel: "manifest", href: "/manifest.json" },
                { rel: "icon", type: "image/x-icon", href: "/favicon.ico" },
                { rel: "icon", type: "image/png", sizes: "32x32", href: "/favicon-32x32.png" },
                { rel: "icon", type: "image/png", sizes: "16x16", href: "/favicon-16x16.png" },
                { rel: "apple-touch-icon", sizes: "180x180", href: "/apple-touch-icon.png" }
            ],
            meta: [
                // theme-color + apple status-bar style are set reactively in app.vue
                // (useHead driven by useTheme().isDark) so they follow light/dark mode.
                { name: "apple-mobile-web-app-capable", content: "yes" },
                { name: "apple-mobile-web-app-title", content: "LogShip ERP" }
            ],
            htmlAttrs: {
                'data-mode': 'light',
                'lang': 'en-US'
            },
            script: [
                {
                    innerHTML: `
          let defaultLanguage = 'en-US'
          let defaultTheme = 'light'

          if(localStorage.getItem('logship_lang')) {
            defaultLanguage = localStorage.getItem('logship_lang')
          }

          if(localStorage.getItem('logship_theme_set') === '1') {
            // The user made an explicit choice via the theme toggle — honour it everywhere.
            defaultTheme = localStorage.getItem('logship_theme') || 'light'
          } else {
            // No explicit choice yet → default by context, recomputed every load so the
            // two contexts never bleed into each other: mobile = DARK, desktop = light.
            defaultTheme = location.pathname.indexOf('/mobile') === 0 ? 'dark' : 'light'
            // Keep the colorMode storage key in sync with the DOM so colorMode
            // doesn't flip it back on hydration.
            try { localStorage.setItem('logship_theme', defaultTheme) } catch(e) {}
          }

          document.documentElement.setAttribute('lang', defaultLanguage)
          document.documentElement.setAttribute('data-mode', defaultTheme)
          document.documentElement.classList.add(defaultTheme)

          // Lock screen orientation to portrait on mobile phones only (allow tablets to use landscape).
          // Math.min of screen dimensions = the smaller side, invariant under rotation.
          // Tablets typically have min dimension >= 600px, phones are smaller.
          // Note: lock() requires standalone PWA / fullscreen context. On installed PWAs the
          // dynamic manifest at /manifest.json (orientation: portrait for phones) provides
          // the OS-level lock; this JS is a belt-and-braces retry for runtime cases where the
          // first call rejects (e.g. page loaded while device was already in landscape).
          var minScreenDimension = Math.min(screen.width, screen.height)
          var isPhone = minScreenDimension < 600
          if (isPhone && screen.orientation && screen.orientation.lock) {
            var tryLock = function() {
              screen.orientation.lock('portrait').catch(function() {
                // Silently swallow — not in a context that allows locking.
              })
            }
            tryLock()
            window.addEventListener('orientationchange', tryLock)
            document.addEventListener('visibilitychange', function() {
              if (!document.hidden) tryLock()
            })
          }
          `
                }
            ]
        }
    },

    runtimeConfig: {
        // Staff live-chat kill-switch (server side). Set CHAT_ENABLED=true to turn on.
        chatEnabled: process.env.CHAT_ENABLED === 'true',
        // Merchant Onboarding wizard (server-only): AD_Role cloned for each new merchant
        // (replicated via copyRoleHelper) + the M_LocatorType for the default
        // "Wareneingang" pallet locator. Overridable per-env; safe defaults baked in.
        merchantTemplateRoleId: process.env.MERCHANT_TEMPLATE_ROLE_ID || '1000006',
        merchantPalettLocatorTypeId: process.env.MERCHANT_PALETT_LOCATOR_TYPE_ID || '1000003',
        pgHost: process.env.PG_HOST,
        pgPort: process.env.PG_PORT,
        pgDatabase: process.env.PG_DATABASE,
        pgUser: process.env.PG_USER,
        pgPassword: process.env.PG_PASSWORD,
        // Security-key (YubiKey) login — server/utils/webauthnHelper.ts. extraOrigins: comma list of
        // additional accepted origins (the Android app's `android:apk-key-hash:…`); globalAllowlist
        // '0' forces username-first login; globalAllowlistMax caps the tap-only allow-list size.
        webauthn: {
            rpId: process.env.WEBAUTHN_RP_ID,
            rpName: process.env.WEBAUTHN_RP_NAME ?? 'LogShip ERP',
            origin: process.env.WEBAUTHN_ORIGIN,
            extraOrigins: process.env.WEBAUTHN_EXTRA_ORIGINS ?? '',
            globalAllowlist: process.env.WEBAUTHN_GLOBAL_ALLOWLIST ?? '1',
            globalAllowlistMax: process.env.WEBAUTHN_GLOBAL_ALLOWLIST_MAX ?? '150',
        },
        credentialEncryptionKey: process.env.CREDENTIAL_ENCRYPTION_KEY,
        // LogShip Mobile (Android) release channel — server/utils/mobileReleases.ts.
        // downloadSecret signs the expiring public APK download URLs (the Android download
        // manager sends no auth header); publicBase is the host those URLs point at;
        // keepHistory = archived builds kept in data/mobile-releases/history; legacyFallback
        // keeps proxying www.logyou.de/logship for unauthenticated (old-build) update checks.
        mobileApp: {
            downloadSecret: process.env.MOBILE_APP_DOWNLOAD_SECRET,
            publicBase: process.env.MOBILE_APP_PUBLIC_BASE || 'https://app.logship.de',
            keepHistory: process.env.MOBILE_APP_KEEP_HISTORY || '10',
            legacyFallback: process.env.MOBILE_APP_LEGACY_FALLBACK !== 'false',
        },
        // Phone IVR (FreePBX) → return-instructions email endpoint. The shared secret
        // gates server/api/ivr/* against the localhost IVR middleware on the PBX box.
        // Click-to-dial via the FreePBX/Asterisk Manager Interface (AMI). LAN-only
        // manager user with originate + call-event rights; see server/utils/telephony/.
        asterisk: {
            enabled: process.env.CLICK_TO_DIAL_ENABLED === 'true',
            host: process.env.ASTERISK_AMI_HOST || '192.168.12.73',
            port: Number(process.env.ASTERISK_AMI_PORT || 5038),
            username: process.env.ASTERISK_AMI_USER || 'logship',
            secret: process.env.ASTERISK_AMI_SECRET,
            context: process.env.ASTERISK_DIAL_CONTEXT || 'from-internal',
            // '{ext}'/'{context}' placeholders. PJSIP/{ext} rings the device DIRECTLY: the
            // originate only counts as answered when the phone is really picked up. A
            // Local/{ext}@from-internal channel would run FreePBX Follow-Me, whose
            // announcement answers the leg early → the target gets dialed while the
            // agent's phone is still ringing (verified on the LogYou PBX). Keep PJSIP.
            channelTemplate: process.env.ASTERISK_ORIGINATE_CHANNEL || 'PJSIP/{ext}',
            agentRingTimeoutMs: Number(process.env.ASTERISK_AGENT_RING_TIMEOUT_MS || 30000),
            // Allow feature codes like *43 (echo test) — dev/verification only.
            allowFeatureCodes: process.env.ASTERISK_ALLOW_FEATURE_CODES === 'true',
        },
        // FreePBX CDR (read-only MariaDB user on the PBX) for the dashboard "recent calls" card.
        pbxCdr: {
            host: process.env.PBX_CDR_HOST || '',
            port: Number(process.env.PBX_CDR_PORT || 3306),
            database: process.env.PBX_CDR_DATABASE || 'asteriskcdrdb',
            user: process.env.PBX_CDR_USER || '',
            password: process.env.PBX_CDR_PASSWORD || '',
            // comma-separated substrings identifying external trunk channels (in/out classification)
            trunkPatterns: process.env.PBX_TRUNK_PATTERNS || 'easybell_trunk',
        },
        // SEPA direct debit mandate (contract annex / standalone send from the partner page).
        sepa: {
            creditorId: process.env.SEPA_CREDITOR_ID || '',
            creditorName: process.env.SEPA_CREDITOR_NAME || 'LogYou GmbH',
            creditorStreet: process.env.SEPA_CREDITOR_STREET || 'Mühlenweg 4',
            creditorZipCity: process.env.SEPA_CREDITOR_ZIP_CITY || '35510 Butzbach',
            creditorCountry: process.env.SEPA_CREDITOR_COUNTRY || 'Deutschland',
            prenotificationDays: Number(process.env.SEPA_PRENOTIFICATION_DAYS || 5),
        },
        ivr: {
            sharedSecret: process.env.IVR_SHARED_SECRET,
            // Pipe-separated return-address lines (RMA number injected as line 2 by the endpoint).
            returnAddress: process.env.IVR_RETURN_ADDRESS || 'LogYou GmbH|Mühlenweg 4|35110 Butzbach',
        },
        // Call recordings served by the small LAN service on the PBX (/opt/logship-ivr/recordings.js,
        // systemd logship-recordings, port 8096) — same shared secret as the IVR endpoint above.
        pbxRecordings: {
            baseUrl: process.env.PBX_RECORDINGS_URL || 'http://192.168.12.73:8096',
            secret: process.env.IVR_SHARED_SECRET,
        },
        // AI lead assessment ("KI-Analyse" on the lead page, server/utils/leads/leadAssessment.ts):
        // Anthropic API key (server-only) + model. Without the key the button reports "not configured".
        anthropic: {
            apiKey: process.env.ANTHROPIC_API_KEY || '',
            // Required for user-scoped / multi-workspace keys (sk-ant-usr-…): wrkspc_… id from Console → Workspaces
            workspaceId: process.env.ANTHROPIC_WORKSPACE_ID || '',
            model: process.env.ANTHROPIC_MODEL || 'claude-opus-5-5',
            // KI-Konsole: output-token ceiling per answer (default 32000, auto-continued when hit)
            chatMaxTokens: process.env.ANTHROPIC_CHAT_MAX_TOKENS || '',
        },
        // Contact e-mail history from the mailpiler archive: small read-only LAN service on the
        // piler host (reference/mail-archive-api, systemd logship-mail-archive, port 8097).
        mailArchive: {
            baseUrl: process.env.MAIL_ARCHIVE_URL || 'http://192.168.9.53:8097',
            token: process.env.MAIL_ARCHIVE_TOKEN,
        },
        // Master organization whose complete locator set is the template catalog for every
        // merchant org: missing bins are created per org on first use (server/utils/locatorCatalog.ts).
        locatorMasterOrgId: process.env.LOCATOR_MASTER_ORG_ID || '1000000',
        // Reverse caller-ID lookup for FreePBX's built-in "CallerID Lookup Sources" module
        // (server/api/telephony/cid-lookup/[number].get.ts). Asterisk's CURL() hits this
        // directly with HTTP Basic Auth baked into the source's URL by FreePBX itself —
        // own dedicated secret (not IVR_SHARED_SECRET) since this is configured in plain
        // text in the FreePBX admin GUI, a different trust/exposure boundary.
        cidLookup: {
            username: process.env.CID_LOOKUP_USER || 'freepbx-cidlookup',
            password: process.env.CID_LOOKUP_SECRET,
        },
        api: {
            url: process.env.URLV1,
            strapi: process.env.STRAPIV1,
            strapiupload: process.env.STRAPIUPLOAD,
            strapitoken: process.env.STRAPITOKEN,
            idempieretoken: process.env.IDEMPIERETOKEN,
            // Shared secret for the logyou.de landing-page lead webhook
            // (server/api/webhooks/landing-leads.post.ts). Must equal the Bearer token
            // the landing's contact.php sends.
            landingLeadToken: process.env.LANDING_LEAD_TOKEN,
            postgrest: process.env.POSTGREST,
            laravel: process.env.LARAVEL,
            // Shared secret for the Laravel /api/banking group (EBICS). Must equal
            // LOGSHIP_SERVICE_TOKEN in the Laravel middleware env.
            laravelServiceToken: process.env.LOGSHIP_SERVICE_TOKEN,
            // Global JTL-FFN fulfiller credentials (account-level). Used as a fallback by the
            // per-OrderSource FFN read proxies so OrderSources can omit their own token.
            jtlFulfillerUrl: process.env.JTL_FULFILLER_URL,
            jtlFulfillerToken: process.env.JTL_FULFILLER_TOKEN,
            sendcloud: process.env.SENDCLOUD,
            sendcloudpublickey: process.env.SENDCLOUDPUBLICKEY,
            sendcloudprivatekey: process.env.SENDCLOUDPRIVATEKEY,
            dhlurl: process.env.DHLURL,
            dhlkey: process.env.DHLKEY,
            dhlsecret: process.env.DHLSECRET,
            dhluser: process.env.DHLUSER,
            dhlpass: process.env.DHLPASS,
            dpdurl: process.env.DPDURL,
            dpddelisid: process.env.DPDDELISID,
            dpdpassword: process.env.DPDPASSWORD,
            dpdcustomernumber: process.env.DPDCUSTOMERNUMBER,
            dpddepot: process.env.DPDDEPOT,
            elastic: process.env.ELASTIC,
            elastickey: process.env.ELASTICKEY,
            elasticusername: process.env.ELASTIC_USER,
            elasticpass: process.env.ELASTIC_PASS,
            // Marketplace Bestellabgleich: C_Charge_ID of the difference-adjustment charge line.
            amazonAdjustmentChargeId: process.env.AMAZON_ADJUSTMENT_CHARGE_ID,
            // (legacy/unused) previous product-based adjustment line id.
            amazonAdjustmentProductId: process.env.AMAZON_ADJUSTMENT_PRODUCT_ID,
            // Amazon SP-API OAuth (public developer app): merchant "Connect Amazon
            // Account" flow on the order-source edit page (server/api/oauth/amazon/*).
            amazonAppId: process.env.AMAZON_APP_ID,
            amazonLwaClientId: process.env.AMAZON_LWA_CLIENT_ID,
            amazonLwaClientSecret: process.env.AMAZON_LWA_CLIENT_SECRET,
            amazonOauthRedirectUri: process.env.AMAZON_OAUTH_REDIRECT_URI,
            amazonAppIsDraft: process.env.AMAZON_APP_IS_DRAFT === 'true',
            // Amazon Ads API (Sponsored Ads billing → AP invoices, /accounting/amazon-ads-invoices).
            // Separate approval from SP-API; set client id/secret only once an LWA security profile
            // is linked to the approved Ads API app (the SP-API LWA values can be copied then). The
            // refresh token comes from the in-app OAuth consent (data/amazon-ads.db) — env overrides.
            amazonAdsClientId: process.env.AMAZON_ADS_CLIENT_ID,
            amazonAdsClientSecret: process.env.AMAZON_ADS_CLIENT_SECRET,
            amazonAdsRefreshToken: process.env.AMAZON_ADS_REFRESH_TOKEN,
            amazonAdsProfileId: process.env.AMAZON_ADS_PROFILE_ID,
            amazonAdsRegion: process.env.AMAZON_ADS_REGION || 'eu',
            amazonAdsRedirectUri: process.env.AMAZON_ADS_OAUTH_REDIRECT_URI,
            // fixed vendor c_bpartner for Amazon Online Germany GmbH (org *) + default charge for the lines
            amazonAdsVendorBpartnerId: process.env.AMAZON_ADS_VENDOR_BPARTNER_ID || '1043801',
            amazonAdsChargeId: process.env.AMAZON_ADS_CHARGE_ID,
            // seller FEE invoices (Tax Document Library PDFs, same page): default charge "Amazon Gebühren".
            // Their vendor is resolved per invoice by the issuer's VAT id (several Amazon entities issue them).
            amazonFeeChargeId: process.env.AMAZON_FEE_CHARGE_ID || '1000009'
        },
        synology: {
            url: process.env.SYNOLOGY_URL,
            user: process.env.SYNOLOGY_USER,
            password: process.env.SYNOLOGY_PASSWORD,
            cameraIds: process.env.SYNOLOGY_CAMERA_IDS,
            preSeconds: process.env.SYNOLOGY_PRE_SECONDS,
            postSeconds: process.env.SYNOLOGY_POST_SECONDS,
        },
        // Incoming-invoice (Eingangsrechnung) OCR engine. Default = self-hosted
        // (no LLM, nothing leaves the server). Optional LLM plug-in: set
        // AP_OCR_LLM_ENABLED=true + provider/baseUrl/apiKey/model. The key is
        // server-only; non-secret knobs can be overridden per-org in settings.
        ap: {
            llm: {
                enabled: process.env.AP_OCR_LLM_ENABLED === 'true',
                provider: process.env.AP_OCR_LLM_PROVIDER || 'compatible',
                baseUrl: process.env.AP_OCR_LLM_BASE_URL || '',
                apiKey: process.env.AP_OCR_LLM_API_KEY || '',
                model: process.env.AP_OCR_LLM_MODEL || '',
                mode: process.env.AP_OCR_LLM_MODE || 'assisted',
                threshold: Number(process.env.AP_OCR_LLM_THRESHOLD || 0.6),
            },
        },
        public: {
            // App build only: absolute API base the bundled SPA calls. Absent on the web build,
            // which keeps using same-origin /api. The app-only plugin keys all its behavior off this.
            ...(isCapacitor ? { apiBase: capApiBase } : {}),
            // In the app build, Strapi media/files (/media-api, /files-api) are served from the SAME
            // host as the API, so the base follows apiBase — NOT the local .env STRAPIPUBLIC (which may
            // be a dev host, which broke images/uploads). Token can be overridden with CAP_STRAPI_TOKEN.
            strapi:  isCapacitor ? (process.env.CAP_STRAPI_BASE || capApiBase) : process.env.STRAPIPUBLIC,
            strapitoken: isCapacitor ? (process.env.CAP_STRAPI_TOKEN || process.env.STRAPITOKEN) : process.env.STRAPITOKEN,
            githash: process.env.GITHASH,
            buildtime: process.env.BUILDTIME,
            solutionname: process.env.SOLUTIONNAME,
            doctypeid: process.env.TARGET_DOCUMENT_TYPE_ID,
            doctypevalue: process.env.TARGET_DOCUMENT_TYPE_VALUE,
            partnergroupid: process.env.BUSINESS_PARTNER_GROUP_ID,
            partnergroupvalue: process.env.BUSINESS_PARTNER_GROUP_VALUE,
            iscustomer: process.env.IS_CUSTOMER,
            pricelistid: process.env.PRICE_LIST_ID,
            pricelistvalue: process.env.PRICE_LIST_VALUE,
            purchasepricelistversionid: process.env.PURCHASE_PRICE_LIST_VERSION_ID,
            countryid: process.env.COUNTRY_ID,
            countryvalue: process.env.COUNTRY_VALUE,
            elastic: process.env.ELASTIC,
            aggrid: process.env.AGGRID,
            vapidPublicKey: process.env.VAPID_PUBLIC_KEY,
            // Staff live-chat kill-switch (client side). Mirrors the private flag.
            chatEnabled: process.env.CHAT_ENABLED === 'true',
            // Click-to-dial kill-switch (client side). Mirrors the private asterisk.enabled flag.
            clickToDialEnabled: process.env.CLICK_TO_DIAL_ENABLED === 'true',
            // Incoming-invoice OCR: expose only whether the LLM plug-in is enabled
            // (UI visibility); the API key never reaches the client.
            apOcrLlmEnabled: process.env.AP_OCR_LLM_ENABLED === 'true',
            // Sentry runtime config (client). Empty dsn = Sentry disabled (dev/kill-switch).
            sentry: {
                dsn: process.env.SENTRY_DSN,
                environment: process.env.SENTRY_ENVIRONMENT || 'production',
            },
        }
    },

    compatibilityDate: '2026-06-01',
})