import { z } from 'zod';

import type { SettingEntry } from './types';

/**
 * Central registry of every tenant-configurable setting. One entry per setting,
 * consumed by the resolver, the admin form generator, the validator, and the
 * encryption helper. Adding a new integration is a registry entry - no new
 * schema, no new resolver, no new admin page wiring.
 *
 * Do NOT register boot-time / build-time secrets here (DATABASE_URL,
 * BETTER_AUTH_SECRET, NEXT_PUBLIC_*, etc.). Those stay in env.ts because
 * they're needed before the DB is reachable or get baked into the client
 * bundle at build time.
 *
 * Section naming convention: `<integration>.<group>` (e.g. `documenso.api`,
 * `documenso.signers`, `email.smtp`). The admin form generator filters by
 * section name, so keep them stable.
 */
export const REGISTRY: SettingEntry[] = [
  // ─── Documenso API ────────────────────────────────────────────────────────
  // Keys keep the existing `_override` suffix for the env-fallback fields so
  // existing data + per-domain readers (`getPortDocumensoConfig` etc.) don't
  // need a rename migration. Brand-new fields (webhook secret) use plain
  // suffix-free keys.
  {
    key: 'documenso_api_url_override',
    section: 'documenso.api',
    label: 'API URL',
    description:
      'Bare host only - never include /api/v1. The client appends versioned paths based on the API version below.',
    type: 'url',
    scope: 'port',
    envFallback: 'DOCUMENSO_API_URL',
    placeholder: 'https://documenso.example.com',
  },
  {
    key: 'documenso_api_key_override',
    section: 'documenso.api',
    label: 'API key',
    description: 'AES-encrypted at rest. Only stored when set explicitly.',
    type: 'password',
    scope: 'port',
    encrypted: true,
    sensitive: true,
    envFallback: 'DOCUMENSO_API_KEY',
  },
  {
    key: 'documenso_api_version_override',
    section: 'documenso.api',
    label: 'API version',
    description:
      'v1 = Documenso 1.13.x stable. v2 = Documenso 2.x with the envelope model. Test the connection after switching.',
    type: 'select',
    options: [
      { value: 'v1', label: 'v1 - Documenso 1.13.x (default, stable)' },
      { value: 'v2', label: 'v2 - Documenso 2.x (envelope, recommended for new ports)' },
    ],
    scope: 'port',
    envFallback: 'DOCUMENSO_API_VERSION',
    defaultValue: 'v1',
  },
  {
    key: 'documenso_webhook_secret',
    section: 'documenso.api',
    label: 'Webhook secret',
    description:
      'Verifies inbound webhook deliveries via the X-Documenso-Secret header (timing-safe compare). Generate with `openssl rand -hex 16`.',
    type: 'password',
    scope: 'port',
    encrypted: true,
    sensitive: true,
    envFallback: 'DOCUMENSO_WEBHOOK_SECRET',
    validator: z.string().min(16),
  },

  // ─── Documenso signers ────────────────────────────────────────────────────
  {
    key: 'documenso_developer_name',
    section: 'documenso.signers',
    label: 'Developer signer - name',
    description:
      "Override the name on the developer recipient slot. Leave blank to use whatever's set on the Documenso template.",
    type: 'string',
    scope: 'port',
  },
  {
    key: 'documenso_developer_email',
    section: 'documenso.signers',
    label: 'Developer signer - email',
    description:
      "Override the email on the developer recipient slot. Leave blank to use whatever's set on the Documenso template.",
    type: 'email',
    scope: 'port',
  },
  {
    key: 'documenso_developer_label',
    section: 'documenso.signers',
    label: 'Developer signer - label',
    description: 'Display label shown on the signing screen (defaults to "Developer").',
    type: 'string',
    scope: 'port',
    placeholder: 'Developer',
  },
  {
    key: 'documenso_developer_recipient_id',
    section: 'documenso.signers',
    label: 'Developer Documenso recipient ID',
    description:
      'Numeric Documenso recipient slot ID for the developer signer. Set automatically by "Sync from Documenso" - you rarely set this by hand.',
    type: 'number',
    scope: 'port',
    envFallback: 'DOCUMENSO_DEVELOPER_RECIPIENT_ID',
  },
  {
    key: 'documenso_developer_user_id',
    section: 'documenso.signers',
    label: 'Developer signer - linked CRM user (optional)',
    description:
      "Project Director RBAC binding. When set, the webhook handler fires an in-CRM notification for this user when it's their turn to sign - alongside the branded email. Leave blank if the developer slot doesn't map to a CRM user (e.g. external developer).",
    type: 'user-select',
    scope: 'port',
  },
  {
    key: 'documenso_approver_name',
    section: 'documenso.signers',
    label: 'Approver signer - name',
    description:
      "Override the name on the approver recipient slot. Leave blank to use whatever's set on the Documenso template.",
    type: 'string',
    scope: 'port',
  },
  {
    key: 'documenso_approver_email',
    section: 'documenso.signers',
    label: 'Approver signer - email',
    description:
      "Override the email on the approver recipient slot. Leave blank to use whatever's set on the Documenso template.",
    type: 'email',
    scope: 'port',
  },
  {
    key: 'documenso_approver_label',
    section: 'documenso.signers',
    label: 'Approver signer - label',
    description: 'Display label shown on the signing screen (defaults to "Approver").',
    type: 'string',
    scope: 'port',
    placeholder: 'Approver',
  },
  {
    key: 'documenso_approval_recipient_id',
    section: 'documenso.signers',
    label: 'Approver Documenso recipient ID',
    description:
      'Numeric Documenso recipient slot ID for the approver. Set automatically by "Sync from Documenso" - you rarely set this by hand.',
    type: 'number',
    scope: 'port',
    envFallback: 'DOCUMENSO_APPROVAL_RECIPIENT_ID',
  },
  {
    key: 'documenso_approver_user_id',
    section: 'documenso.signers',
    label: 'Approver - linked CRM user (optional)',
    description:
      "Same as developer's linked user - when set, fires an in-CRM notification when it's the approver's turn to sign.",
    type: 'user-select',
    scope: 'port',
  },
  {
    key: 'documenso_client_recipient_id',
    section: 'documenso.signers',
    label: 'Client recipient ID',
    description:
      'Documenso recipient ID for the client slot. Maps to DOCUMENSO_CLIENT_RECIPIENT_ID in env.',
    type: 'number',
    scope: 'port',
    envFallback: 'DOCUMENSO_CLIENT_RECIPIENT_ID',
  },

  // ─── Documenso templates ──────────────────────────────────────────────────
  {
    key: 'documenso_eoi_template_id',
    section: 'documenso.templates',
    label: 'EOI Documenso template ID',
    description:
      'Numeric template ID used by the Documenso EOI pathway. Populated automatically by "Sync from Documenso" below.',
    type: 'number',
    scope: 'port',
    envFallback: 'DOCUMENSO_TEMPLATE_ID_EOI',
    placeholder: '12345',
  },
  {
    key: 'eoi_default_pathway',
    section: 'documenso.templates',
    label: 'Default EOI pathway',
    description:
      'Which pathway is used when an EOI is generated without an explicit choice. Documenso = signed via Documenso, In-app = filled locally with pdf-lib.',
    type: 'select',
    options: [
      { value: 'documenso-template', label: 'Documenso template' },
      { value: 'inapp', label: 'In-app (pdf-lib)' },
    ],
    scope: 'port',
    defaultValue: 'documenso-template',
  },
  {
    key: 'eoi_fill_method',
    section: 'documenso.templates',
    label: 'EOI form fill method',
    description:
      "How the EOI's detail fields (name, address, yacht, berth) get filled in. Local fill (recommended) = the CRM fills + flattens the PDF itself at a fixed 12pt with multiline address wrapping, then sends it to Documenso for signatures only — text renders cleanly. Documenso template fill = Documenso fills the template's form fields and auto-sizes the text, which oversizes/clips long values. Both still go through Documenso for signing, branded emails, and embedded signing.",
    type: 'radio',
    options: [
      { value: 'local', label: 'Local fill — clean text + address wrapping (recommended)' },
      { value: 'documenso', label: 'Documenso template fill — legacy (may clip long values)' },
    ],
    scope: 'port',
    defaultValue: 'local',
  },
  {
    key: 'eoi_send_mode',
    section: 'documenso.templates',
    label: 'Initial signing-invitation email behaviour',
    description:
      'Auto = the system sends the branded "please sign" email immediately when an EOI/contract/reservation is generated. Manual = the document is generated and the signing URL appears in the UI; a rep clicks "Send invitation" to dispatch. Applies to all document types, not just EOI.',
    type: 'radio',
    options: [
      { value: 'manual', label: 'Manual — rep clicks Send after generation' },
      { value: 'auto', label: 'Auto — send branded email on generate' },
    ],
    scope: 'port',
    defaultValue: 'manual',
  },
  {
    key: 'documenso_reservation_template_id',
    section: 'documenso.templates',
    label: 'Reservation template ID',
    description: 'Template ID used for reservation agreements (optional).',
    type: 'number',
    scope: 'port',
  },
  {
    key: 'documenso_contract_template_id',
    section: 'documenso.templates',
    label: 'Contract template ID',
    description: 'Template ID used for the final purchase / lease contract (optional).',
    type: 'number',
    scope: 'port',
  },

  // ─── Documenso behavior ───────────────────────────────────────────────────
  {
    key: 'documenso_signing_order',
    section: 'documenso.behavior',
    label: 'Signing order',
    description:
      'Default flow when this port creates an envelope. Leave on "Use template default" to honour each Documenso template\'s own setting; pick PARALLEL or SEQUENTIAL to override every envelope this port creates (v2 only - v1 is always parallel).',
    type: 'radio',
    options: [
      {
        value: 'TEMPLATE_DEFAULT',
        label: 'Use template default (recommended)',
      },
      { value: 'PARALLEL', label: 'Parallel - all recipients sign concurrently' },
      { value: 'SEQUENTIAL', label: 'Sequential - order matters (v2 only)' },
    ],
    scope: 'port',
    defaultValue: 'TEMPLATE_DEFAULT',
  },
  {
    key: 'documenso_redirect_url',
    section: 'documenso.behavior',
    label: 'Post-sign redirect URL',
    description: 'Where signers land after completing their signature. Both v1 and v2 honour it.',
    type: 'url',
    scope: 'port',
  },

  // ─── Pipeline auto-advance ───────────────────────────────────────────────
  // JSON map keyed by trigger name; value is one of 'auto' | 'suggest' |
  // 'off'. Read by `getStageAdvanceMode` in port-config.ts. The registry
  // entry uses the generic `string` type because the form generator's
  // schemas don't have a JSON variant - the admin UI is a dedicated page
  // (/admin/pipeline-rules) that renders 3-way toggles per trigger.
  {
    key: 'stage_advance_rules',
    section: 'pipeline.auto_advance',
    label: 'Pipeline auto-advance rules',
    description:
      'Per-trigger control for whether lifecycle events (EOI sent/signed, deposit received, etc.) auto-advance the deal stage, only suggest the move via a notification, or do nothing.',
    type: 'string',
    scope: 'port',
    validator: z.record(z.string(), z.enum(['auto', 'suggest', 'off'])),
  },

  // ─── AI / OpenAI ──────────────────────────────────────────────────────────
  {
    key: 'ai_enabled',
    section: 'ai.master',
    label: 'AI features enabled',
    description:
      'Master switch. When OFF, every AI surface (receipt OCR, berth-PDF AI parse) is bypassed. Provider keys stay configured but unused.',
    type: 'boolean',
    scope: 'port',
    defaultValue: true,
  },
  {
    key: 'ai_monthly_token_cap',
    section: 'ai.master',
    label: 'Monthly token cap (this port)',
    description:
      'Soft cap on total AI tokens consumed per calendar month. When exceeded, AI features fall back to non-AI paths and surface a banner. Set 0 for no cap.',
    type: 'number',
    scope: 'port',
    defaultValue: 0,
  },
  {
    key: 'openai_api_key',
    section: 'ai.providers',
    label: 'OpenAI API key',
    description: 'Used by Receipt OCR fallback and berth-PDF AI parse. AES-encrypted at rest.',
    type: 'password',
    scope: 'port',
    encrypted: true,
    sensitive: true,
    envFallback: 'OPENAI_API_KEY',
    placeholder: 'sk-…',
  },
  {
    key: 'openai_default_model',
    section: 'ai.providers',
    label: 'Default OpenAI model',
    description: 'Used when a feature does not specify an explicit model.',
    type: 'select',
    options: [
      { value: 'gpt-4o-mini', label: 'gpt-4o-mini - cheap, fast, vision-capable' },
      { value: 'gpt-4o', label: 'gpt-4o - full-strength multimodal' },
      { value: 'gpt-4-turbo', label: 'gpt-4-turbo - legacy text reasoning' },
    ],
    scope: 'port',
    defaultValue: 'gpt-4o-mini',
  },

  // ─── Email - From / Reply-To ──────────────────────────────────────────────
  {
    key: 'email_from_name',
    section: 'email.from',
    label: 'From name',
    description: 'Display name shown in the From: header on outgoing email.',
    type: 'string',
    scope: 'port',
    placeholder: 'Port Nimara',
  },
  {
    key: 'email_from_address',
    section: 'email.from',
    label: 'From address',
    description: 'Sender email address. Falls back to SMTP_FROM env when blank.',
    type: 'email',
    scope: 'port',
    envFallback: 'SMTP_FROM',
    placeholder: 'noreply@example.com',
  },
  {
    key: 'email_reply_to',
    section: 'email.from',
    label: 'Reply-to address',
    description: 'Optional Reply-To: header for replies (e.g. sales@example.com).',
    type: 'email',
    scope: 'port',
    placeholder: 'sales@example.com',
  },
  {
    key: 'supplemental_form_url',
    section: 'email.from',
    label: 'Supplemental info form URL (optional)',
    description:
      "When set, the supplemental-info email links to this URL with ?token=… appended (typically the marketing site's hosted form). Leave blank to use the built-in CRM form at /public/supplemental-info/<token>. Useful when you want the client to land on a branded marketing-site page instead of the CRM domain.",
    type: 'string',
    scope: 'port',
    placeholder: 'https://portnimara.com/supplemental',
  },

  // ─── Email - SMTP overrides ───────────────────────────────────────────────
  {
    key: 'smtp_host_override',
    section: 'email.smtp',
    label: 'SMTP host override',
    description: 'Falls back to SMTP_HOST env when blank.',
    type: 'string',
    scope: 'port',
    envFallback: 'SMTP_HOST',
    placeholder: 'mail.example.com',
  },
  {
    key: 'smtp_port_override',
    section: 'email.smtp',
    label: 'SMTP port override',
    description: 'Falls back to SMTP_PORT env when blank.',
    type: 'number',
    scope: 'port',
    envFallback: 'SMTP_PORT',
    placeholder: '587',
  },
  {
    key: 'smtp_user_override',
    section: 'email.smtp',
    label: 'SMTP user override',
    description: 'Falls back to SMTP_USER env when blank.',
    type: 'string',
    scope: 'port',
    envFallback: 'SMTP_USER',
  },
  {
    key: 'smtp_pass_override',
    section: 'email.smtp',
    label: 'SMTP password override',
    description: 'AES-encrypted at rest. Falls back to SMTP_PASS env when blank.',
    type: 'password',
    scope: 'port',
    encrypted: true,
    sensitive: true,
    envFallback: 'SMTP_PASS',
  },

  // ─── Storage - S3 / MinIO ─────────────────────────────────────────────────
  {
    key: 'storage_s3_endpoint',
    section: 'storage.s3',
    label: 'S3 endpoint URL',
    description:
      'Full URL including scheme and port (e.g. https://s3.amazonaws.com or http://localhost:9000 for MinIO).',
    type: 'url',
    scope: 'global',
    envFallback: 'MINIO_ENDPOINT',
  },
  {
    key: 'storage_s3_region',
    section: 'storage.s3',
    label: 'S3 region',
    description: 'AWS region or "auto" for many S3-compatible providers.',
    type: 'string',
    scope: 'global',
    defaultValue: 'us-east-1',
  },
  {
    key: 'storage_s3_bucket',
    section: 'storage.s3',
    label: 'S3 bucket name',
    description: 'The bucket to read/write file content.',
    type: 'string',
    scope: 'global',
    envFallback: 'MINIO_BUCKET',
    placeholder: 'crm-files',
  },
  {
    // Stored under the new `_encrypted` suffix to mirror the existing
    // `storage_s3_secret_key_encrypted` convention. The migration script
    // moves the legacy plaintext row at `storage_s3_access_key` into this
    // key (fixes audit finding S-23).
    key: 'storage_s3_access_key_encrypted',
    section: 'storage.s3',
    label: 'S3 access key',
    description:
      'IAM access key id. AES-encrypted at rest (was previously stored plaintext - fixed in this migration).',
    type: 'password',
    scope: 'global',
    encrypted: true,
    sensitive: true,
    envFallback: 'MINIO_ACCESS_KEY',
  },
  {
    key: 'storage_s3_secret_key_encrypted',
    section: 'storage.s3',
    label: 'S3 secret key',
    description: 'IAM secret access key. AES-encrypted at rest.',
    type: 'password',
    scope: 'global',
    encrypted: true,
    sensitive: true,
    envFallback: 'MINIO_SECRET_KEY',
  },
  {
    key: 'storage_s3_force_path_style',
    section: 'storage.s3',
    label: 'Force path-style URLs',
    description:
      'On for MinIO and most self-hosted S3-compatible servers. Off for AWS S3 (which uses virtual-hosted-style by default).',
    type: 'boolean',
    scope: 'global',
    defaultValue: false,
  },

  // ─── Storage - Filesystem (single-node only) ──────────────────────────────
  {
    key: 'storage_filesystem_root',
    section: 'storage.filesystem',
    label: 'Filesystem root path',
    description:
      'Absolute directory where files land when the active backend is filesystem. Single-node deployments only - multi-node MUST use S3.',
    type: 'string',
    scope: 'global',
    placeholder: '/var/lib/pn-crm/files',
  },

  // ─── App URLs ─────────────────────────────────────────────────────────────
  {
    key: 'app_url',
    section: 'app.urls',
    label: 'App URL (this CRM)',
    description:
      'Public URL of this CRM instance. Used in outbound emails and webhook URL construction.',
    type: 'url',
    scope: 'global',
    envFallback: 'APP_URL',
    placeholder: 'https://crm.example.com',
  },
  {
    key: 'public_site_url',
    section: 'app.urls',
    label: 'Marketing site URL',
    description: 'The public marketing website URL. Used by some templates and CTAs.',
    type: 'url',
    scope: 'global',
    envFallback: 'PUBLIC_SITE_URL',
    placeholder: 'https://example.com',
  },

  // ─── Deal Pulse (Phase 2) ─────────────────────────────────────────────────
  // Per-port admin controls for the deal-pulse chip on interest lists +
  // detail headers. Master toggle hides the chip entirely; per-signal
  // toggles let admins quiet specific signal types; label overrides
  // rename tier labels for ports that prefer their own vocabulary.
  {
    key: 'pulse_enabled',
    section: 'pulse',
    label: 'Show deal pulse chips',
    description:
      'Master toggle. When off, the pulse chip is hidden on every interest list row + detail header for this port. Useful when a port prefers to triage pipelines without the AI-tinted chip.',
    type: 'boolean',
    scope: 'port',
  },
  {
    key: 'pulse_signal_eoi_sent_recent_enabled',
    section: 'pulse',
    label: 'Signal: recent EOI sent (positive)',
    description: 'Default on. Brightens chip when EOI was sent in last 14 days.',
    type: 'boolean',
    scope: 'port',
  },
  {
    key: 'pulse_signal_deposit_received_enabled',
    section: 'pulse',
    label: 'Signal: deposit received (positive)',
    description: 'Default on. Strong forward signal once a deposit invoice flips to paid.',
    type: 'boolean',
    scope: 'port',
  },
  {
    key: 'pulse_signal_contract_signed_enabled',
    section: 'pulse',
    label: 'Signal: contract signed (positive)',
    description: 'Default on. Reinforces closed-loop progress until outcome flips to won.',
    type: 'boolean',
    scope: 'port',
  },
  {
    key: 'pulse_signal_document_declined_enabled',
    section: 'pulse',
    label: 'Signal: document declined (risk)',
    description:
      'Default on. Strongest cooling signal - client refused to sign an EOI / contract / reservation. Requires the risk-data wiring shipped alongside Phase 2 to populate.',
    type: 'boolean',
    scope: 'port',
  },
  {
    key: 'pulse_signal_reservation_cancelled_enabled',
    section: 'pulse',
    label: 'Signal: reservation cancelled (risk)',
    description: 'Default on. Booked-then-cancelled signals require rep attention.',
    type: 'boolean',
    scope: 'port',
  },
  {
    key: 'pulse_signal_berth_sold_to_other_enabled',
    section: 'pulse',
    label: 'Signal: berth resold (risk)',
    description: 'Default on. Primary berth got linked to a different completed interest.',
    type: 'boolean',
    scope: 'port',
  },
  {
    key: 'pulse_label_hot',
    section: 'pulse',
    label: 'Custom label: Hot tier',
    description: 'Leave blank to use the built-in "Hot" label.',
    type: 'string',
    scope: 'port',
    placeholder: 'Hot',
  },
  {
    key: 'pulse_label_warm',
    section: 'pulse',
    label: 'Custom label: Warm tier',
    description: 'Leave blank to use the built-in "Warm" label.',
    type: 'string',
    scope: 'port',
    placeholder: 'Warm',
  },
  {
    key: 'pulse_label_cold',
    section: 'pulse',
    label: 'Custom label: Cold tier',
    description: 'Leave blank to use the built-in "Cold" label.',
    type: 'string',
    scope: 'port',
    placeholder: 'Cold',
  },

  // ─── Operations - Tenancies module ────────────────────────────────────────
  // Platform-wide gate for the Tenancies (occupancy-record) surface area.
  // Disabled by default. A first row INSERT on the `tenancies` table flips
  // this on automatically (`pg_advisory_xact_lock` per port keeps the flip
  // race-safe). Admins can also enable explicitly from Admin -> Operations,
  // and disabling with existing rows is a soft hide (data is preserved but
  // invisible until re-enabled).
  {
    key: 'tenancies_module_enabled',
    section: 'operations.tenancies',
    label: 'Tenancies module',
    description:
      'When enabled, the platform tracks who occupies each berth (Tenancies). Without it, sold berths stay sold but the platform does not model the occupancy record. Auto-enables on the first tenancy created (e.g. via a signed Reservation Agreement).',
    type: 'boolean',
    scope: 'port',
    defaultValue: false,
  },

  // ─── Operations - Expenses module ─────────────────────────────────────────
  // Port-scoped gate for the entire Expenses + receipt-upload surface.
  // Defaults to enabled so existing ports keep the feature on deploy.
  // Disabling hides both sidebar entries (Expenses + How to upload
  // receipts) AND swaps the routes for a "Module disabled" placeholder so
  // bookmarks land on a meaningful page (not a 404) and direct API hits
  // are rejected at the layout boundary.
  {
    key: 'expenses_module_enabled',
    section: 'operations.expenses',
    label: 'Expenses module',
    description:
      'When enabled, reps can record expenses and upload receipts (mobile scanner + manual entry). Turning this off hides Expenses + receipt-upload from the sidebar and blocks the routes with a "module disabled" page. Disabling does not delete previously-recorded expense rows.',
    type: 'boolean',
    scope: 'port',
    defaultValue: true,
  },

  // ─── Operations - Maintenance module ──────────────────────────────────────
  // Port-scoped gate for the per-berth Maintenance surface (the berth
  // "Maintenance" tab + its log API + the maintenance-reminder notification
  // preference). Defaults to enabled so existing ports keep the feature on
  // deploy. Disabling hides the Maintenance tab and blocks the maintenance
  // log routes; previously-recorded maintenance logs are preserved.
  {
    key: 'maintenance_module_enabled',
    section: 'operations.maintenance',
    label: 'Berth maintenance module',
    description:
      'When enabled, reps can log maintenance work against individual berths (the "Maintenance" tab on each berth) and receive maintenance reminders. Turning this off hides the Maintenance tab everywhere and blocks its routes. Disabling does not delete previously-recorded maintenance logs — they reappear on re-enable.',
    type: 'boolean',
    scope: 'port',
    defaultValue: true,
  },

  // ─── Operations - Invoices module ─────────────────────────────────────────
  // Port-scoped gate for the standalone `/invoices` flow. Audit conclusion
  // (2026-05-27, Initiative 1c): the schema is rich (invoices + invoice_line_items
  // + invoice_expenses + send/payment routes + PDF) but the dev DB has zero
  // rows. The canonical "money received" path goes via `payments`
  // (auto-advances pipeline) and the canonical expense-report path goes via
  // `expenses → invoices` only for the employee-expense use case. The sidebar
  // nav entry was removed earlier; this toggle hides the route too so
  // bookmarks land on a clear "module disabled" page instead of an orphaned
  // form. Default OFF for new ports; existing ports keep the surface visible
  // until an admin explicitly turns it off.
  {
    key: 'invoices_module_enabled',
    section: 'operations.invoices',
    label: 'Standalone invoicing module',
    description:
      'When enabled, the standalone /invoices flow (create invoice → line items → PDF → send → mark paid) is reachable. The canonical "we received money" path in this CRM goes through the Payments tab on an interest (auto-advances pipeline); the standalone invoicing surface is a separate flow primarily for employee expense reports. Disabling hides /invoices entirely (route renders a "module disabled" page); existing rows are preserved.',
    type: 'boolean',
    scope: 'port',
    defaultValue: false,
  },

  // Operations - Website intake emails. Port-scoped gate for CRM-owned
  // website-inquiry emails. OFF by default so the marketing website keeps
  // sending its own confirmation + staff-alert emails; flip ON at cutover
  // (and turn the website's own sending off) so the CRM is the single owner.
  {
    key: 'website_intake_email_enabled',
    section: 'operations.intake',
    label: 'CRM-owned website inquiry emails',
    description:
      'When enabled, the CRM sends the registrant confirmation + staff alert for inquiries captured from the marketing website (/api/public/website-inquiries), reusing the branded inquiry templates and the per-port From address. Leave OFF until cutover so the website keeps sending its own emails and we never double-send. Recipients come from inquiry_notification_recipients (berth) / contact_notification_recipients (contact form) / residential_notification_recipients (residences), each falling back to inquiry_contact_email.',
    type: 'boolean',
    scope: 'port',
    defaultValue: false,
  },

  // Operations - Website berth auto-promote. Port-scoped gate that decides
  // what a website berth registration does to the public map. OFF (default):
  // captures land in the triage inbox and a rep promotes them, which marks
  // the berth (Option 1, the safe posture - matches the old NocoDB manual
  // flow). ON: a berth_inquiry naming a specific, currently-available berth
  // is auto-promoted to a prospect (client + optional yacht + interest) with
  // the berth linked is_specific_interest=true, immediately flipping the
  // public map to "Under Offer" (Option 2, hybrid). General/residence/contact
  // submissions stay capture-only either way. Intended to be flipped ON only
  // post-cutover so it never races the NocoDB-primary migration window.
  {
    key: 'website_berth_autopromote_enabled',
    section: 'operations.intake',
    label: 'Auto-mark berths from website registrations',
    description:
      'When enabled, a website registration for a SPECIFIC, currently-available berth is auto-promoted into a prospect (client + interest, with the berth marked "Under Offer" on the public map) instead of waiting in the triage inbox for a rep. Skips berths that are already Sold or Under Offer, and skips general inquiries with no specific berth (those still go to triage). Leave OFF for the safe behavior where a rep reviews and promotes each registration. Recommended to keep OFF until after the website cutover.',
    type: 'boolean',
    scope: 'port',
    defaultValue: false,
  },

  // ─── Operations - Residential module ──────────────────────────────────────
  // Port-scoped gate for the entire Residential surface (sidebar
  // "Residential" section, /residential/clients + /residential/interests
  // pages, the admin residential-stages page, the global-search
  // residential buckets, and the public residential-inquiry intake
  // endpoint). Defaults to ENABLED so existing ports keep the feature on
  // deploy — residential is in active use, unlike Tenancies / Invoices
  // which are opt-in. Disabling hides the sidebar section + mobile entry,
  // swaps the routes for a "module disabled" page, drops residential out
  // of search, and hard-fails the public intake endpoint. Disabling does
  // not delete residential clients/interests — they reappear on re-enable.
  {
    key: 'residential_module_enabled',
    section: 'operations.residential',
    label: 'Residential module',
    description:
      'When enabled, the platform tracks residential (non-berth) clients and their interests through a configurable pipeline, accepts residential inquiries from the website, and surfaces them in search. Turning this off hides the Residential section from the sidebar and blocks its routes with a "module disabled" page, and stops the public residential-inquiry endpoint from accepting new leads. Disabling does not delete previously-recorded residential records.',
    type: 'boolean',
    scope: 'port',
    defaultValue: true,
  },

  // ─── Residential - partner forwarding ──────────────────────────────────────
  {
    key: 'residential_partner_recipients',
    section: 'residential.partner',
    label: 'Partner forwarding recipients',
    description:
      'Comma-separated list of email addresses that receive a copy of every new residential inquiry the moment it lands. Leave blank to disable partner forwarding. Reps still see every inquiry in the CRM; this is an outbound courtesy notification for an external partner who handles residential leads on the port’s behalf.',
    type: 'string',
    scope: 'port',
    placeholder: 'partner@example.com, partner2@example.com',
  },
];

/** Quick lookup index keyed by setting key. */
const REGISTRY_INDEX = new Map<string, SettingEntry>(REGISTRY.map((e) => [e.key, e]));

export function registryFor(key: string): SettingEntry | undefined {
  return REGISTRY_INDEX.get(key);
}

export function entriesForSection(section: string): SettingEntry[] {
  return REGISTRY.filter((e) => e.section === section);
}

export function entriesForSections(sections: string[]): SettingEntry[] {
  const set = new Set(sections);
  return REGISTRY.filter((e) => set.has(e.section));
}
