/* CMS type definitions shared by:
 *   - The marketing-side renderers (`marketing/shared.tsx`, page files)
 *   - The admin-side editors (`Pages/Admin/Marketing/*`)
 *   - The TS↔PHP boundary (server emits JSON matching these shapes via
 *     HandleInertiaRequests + MarketingDefaults.php)
 *
 * Adding a new section type:
 *   1. Add the discriminated case to `Section` below
 *   2. Add a default value to `cms-defaults.ts` so fresh installs render
 *   3. Add a renderer to `marketing/sections/` (or inline in the page)
 *   4. Add an admin editor to `resources/js/admin/marketing/sections/`
 *   5. Mirror the default on the PHP side in MarketingDefaults
 */

/* ─────────────────────────────────────────────── Schema versions ──
 *
 * Storage shape for `marketing.{nav, footer, welcome}` Settings keys
 * is wrapped in `{ version: number, ...payload }`. Every read boundary
 * runs the doc through MarketingMigrator (PHP) / cms-migrator.ts (TS)
 * to upgrade legacy versions to current. Lazy upgrade on save: reads
 * upgrade in-memory, writes stamp the current version.
 *
 * v1 (2026-05-16, commit 57b0ee2):
 *   - Initial schema. 8 section types: hero, features, templates_teaser,
 *     stats, how_it_works, testimonials, faq, final_cta.
 *   - Phase B (additive, no version bump): adds the `custom` section type —
 *     a vertical stack of 7 atomic block variants (heading, subhead,
 *     eyebrow, text, button, image, spacer). Purely opt-in: not seeded into
 *     DEFAULT_WELCOME, so older docs without a `custom` section need no
 *     migration. Unknown block types render as null (renderer fallback).
 *
 * Forward-compat rules (from marketing-cms-block-editor-plan.md):
 *   - NEVER rename a field. Add `newField` alongside `oldField`,
 *     keep both readable, mark `oldField` deprecated in comments.
 *   - NEVER remove a section type. Deprecate + keep renderer.
 *   - New required fields ALWAYS nullable initially. Bump to required
 *     only after migration writes the default for old docs.
 *   - Unknown section types render as `null` (silently skipped),
 *     never crash the page. Lenient server validation preserves
 *     unknown fields on round-trip.
 *
 * Adding a new section type = update BLOCK_REGISTRY in block-registry.ts
 * + WELCOME_SECTION_TYPES in MarketingController.php. Adding a new
 * field to an existing section type = add to the discriminated union
 * (nullable) + add to validation rules (nullable) + add to renderer +
 * add to editor. Bump CURRENT_WELCOME_VERSION + add a migrator only
 * when changing the SHAPE of an existing field.
 */

/** A configurable link (nav, footer, CTA). `enabled` lets admins hide
 *  individual links without deleting them — useful when temporarily
 *  removing e.g. Pricing for a self-host install that doesn't sell. */
export type CmsLink = {
    id: string;
    label: string;
    href: string;
    enabled: boolean;
    /** When true, render as `<a target="_blank">`. Default false (Inertia link). */
    external?: boolean;
};

/** A CTA button used inside hero / final-cta sections. Same shape as
 *  CmsLink but without `enabled` (CTAs are always-on; admins remove the
 *  whole section if they don't want one). */
export type CmsCta = {
    label: string;
    href: string;
    external?: boolean;
};

/* ───────────────────────────────────────────────── Header / Footer ── */

export type HeaderConfig = {
    /** Schema version. Stamped at the current `CURRENT_NAV_VERSION` on every
     *  save; reads run through `cms-migrator.ts` to upgrade older docs. */
    version: number;
    /** Middle nav links between the logo and the auth CTA. The auth-state
     *  buttons (Sign in / Start free / Open dashboard) stay hardcoded
     *  because they're tied to runtime auth state, not config. */
    links: CmsLink[];
};

export type FooterColumn = {
    id: string;
    heading: string;
    links: CmsLink[];
};

export type FooterConfig = {
    /** Schema version. Stamped at the current `CURRENT_FOOTER_VERSION` on
     *  every save; reads run through `cms-migrator.ts`. */
    version: number;
    /** One-paragraph blurb under the brand mark. */
    tagline: string;
    columns: FooterColumn[];
};

/* ─────────────────────────────────────────────── Page sections ── */

export type HeroSection = {
    id: string;
    type: 'hero';
    enabled: boolean;
    /** HTML — small uppercase pill text above the headline. Empty hides the pill. */
    eyebrow?: string;
    /** HTML — headline. Inline tags only (em/strong/a). Block-level tags stripped on render. */
    headline: string;
    /** HTML — paragraph between headline and CTAs. */
    subhead?: string;
    primaryCta?: CmsCta;
    secondaryCta?: CmsCta;
    /** HTML — small disclaimer below the CTAs. */
    ctaDisclaimer?: string;
    /** Show the hero media card (video, image, or fallback placeholder).
     *  Toggling this off hides the entire card regardless of which media is set. */
    showVideoPlaceholder?: boolean;
    /** Plain text — handwritten annotation notes flanking the hero media.
     *  Rendered only by design-system skins that support them (Geometry);
     *  empty hides each note. Write them to describe the CONFIGURED media. */
    annotationLeft?: string;
    annotationRight?: string;
    /** Stored image path (relative to MediaStorage). Persisted in Settings.
     *  Uploaded via `POST /admin/marketing/hero-image`. */
    image?: string;
    /** Server-resolved URL for `image`. Derived at the Inertia boundary; never persisted. */
    imageUrl?: string;
    /** Embed or direct video URL pasted by admin (YouTube/Vimeo embed URL,
     *  or direct .mp4/.webm). No file upload — large-video hosting belongs
     *  on a video host, not the app's storage. Renderer auto-detects format:
     *  ends in .mp4/.webm → `<video>`, else → `<iframe>`. */
    videoUrl?: string;
};

export type FeaturesSection = {
    id: string;
    type: 'features';
    enabled: boolean;
    /** Uppercase pill badge above the heading. Empty hides it. */
    eyebrow?: string;
    heading: string;
    subhead?: string;
    /** Each item is a card. `icon` and `href` are optional — items without
     *  icons render as text-only cards (Contact Quicklinks pattern); items
     *  with `href` render as link cards with a per-card "Open" / "Email us"
     *  affordance (Contact Channels pattern). `linkLabel` overrides the
     *  default "Learn more" arrow text. `external` opens in new tab. */
    items: {
        icon?: string;
        title: string;
        body: string;
        href?: string;
        linkLabel?: string;
        external?: boolean;
    }[];
};

export type StatsSection = {
    id: string;
    type: 'stats';
    enabled: boolean;
    items: { value: string; label: string }[];
};

export type HowItWorksSection = {
    id: string;
    type: 'how_it_works';
    enabled: boolean;
    /** Uppercase pill badge above the heading. Empty hides it. */
    eyebrow?: string;
    heading: string;
    subhead?: string;
    items: { n: string; time: string; title: string; body: string }[];
};

export type TestimonialsSection = {
    id: string;
    type: 'testimonials';
    enabled: boolean;
    /** Uppercase pill badge above the heading. Empty hides it. Defaults to
     *  'Testimonials' to preserve the historically hardcoded badge text. */
    eyebrow?: string;
    heading: string;
    subhead?: string;
    items: {
        text: string;
        name: string;
        role: string;
        /** Either a stored MediaStorage path (e.g. `marketing/avatars/uuid.png`)
         *  uploaded via `POST /admin/marketing/testimonial-avatar`, or a
         *  public URL/path (e.g. `/marketing/avatars/sarah-kim.jpg`). Renderer
         *  prefers `imageUrl` when set; otherwise falls back to `image` so
         *  external URLs and `/public` paths still work. */
        image?: string;
        /** Server-resolved URL for `image` when it's a storage path. Derived
         *  at the Inertia boundary; never persisted. */
        imageUrl?: string;
    }[];
};

export type FaqSection = {
    id: string;
    type: 'faq';
    enabled: boolean;
    /** Uppercase pill badge above the heading. Empty hides it. */
    eyebrow?: string;
    heading: string;
    /** Optional short paragraph above/beside the questions. Supports plain text. */
    supportText?: string;
    items: { q: string; a: string }[];
};

export type TemplatesTeaserSection = {
    id: string;
    type: 'templates_teaser';
    enabled: boolean;
    /** Uppercase pill badge above the heading. Empty hides it. */
    eyebrow?: string;
    heading: string;
    subhead?: string;
    ctaLabel: string;
};

export type LogoWallSection = {
    id: string;
    type: 'logo_wall';
    enabled: boolean;
    /** Plain-text label in the leading cell (e.g. "Powering teams at").
     *  Empty hides the label cell. */
    label: string;
    /** Customer / partner logos. `image` is either a stored MediaStorage path
     *  (uploaded via `POST /admin/marketing/hero-image`) or a public/external
     *  URL (`/marketing/logo1.png`, `https://…`). Renderer prefers `imageUrl`
     *  (server-resolved storage path) then falls back to `image`, mirroring the
     *  testimonial-avatar resolution. Section auto-hides when the list is empty
     *  — fresh installs don't ship placeholder "customer" logos (same
     *  legal/ethics posture as the empty-by-default testimonials section). */
    items: {
        image?: string;
        /** Server-resolved URL for `image` when it's a storage path. Derived
         *  at the Inertia boundary; never persisted. */
        imageUrl?: string;
        alt?: string;
        /** Optional link — wraps the logo in an anchor when set. */
        href?: string;
        external?: boolean;
    }[];
};

export type FinalCtaSection = {
    id: string;
    type: 'final_cta';
    enabled: boolean;
    /** Uppercase pill badge above the heading. Empty hides it. */
    eyebrow?: string;
    heading: string;
    subhead?: string;
    primaryCta?: CmsCta;
    secondaryCta?: CmsCta;
};

/* ───────────────────────────────────────── Atomic blocks (Custom) ──
 *
 * Atomic content blocks composable inside a `custom` section (Phase B).
 * Each block has a discriminator `type` + `id` + type-specific fields.
 * No nesting (blocks can't contain blocks) — keeps the data model and
 * the editor UX simple. Layout containers (Row/Column/Grid) are Phase C,
 * deferred. */

export type HeadingBlock = {
    id: string;
    type: 'heading';
    /** HTML (inline tags only — em/strong/a). Sanitized on render. */
    html: string;
    /** Visual size — maps to h2/h3 in renderer. h1 reserved for page hero. */
    size?: 'h2' | 'h3';
    align?: 'left' | 'center';
};

export type SubheadBlock = {
    id: string;
    type: 'subhead';
    /** HTML (inline tags only). Renders as a lead paragraph. */
    html: string;
    align?: 'left' | 'center';
};

export type EyebrowBlock = {
    id: string;
    type: 'eyebrow';
    /** Plain text. Renders as a small uppercase pill. */
    text: string;
    align?: 'left' | 'center';
};

export type TextBlock = {
    id: string;
    type: 'text';
    /** Full HTML body — paragraphs, bold/italic, lists, links, quotes, code. */
    html: string;
};

export type ButtonBlock = {
    id: string;
    type: 'button';
    label: string;
    href: string;
    variant?: 'primary' | 'secondary';
    external?: boolean;
    align?: 'left' | 'center';
};

export type ImageBlock = {
    id: string;
    type: 'image';
    /** Stored path. Uploaded via existing `/admin/marketing/hero-image` endpoint
     *  (reused — same MediaStorage pipeline, same size cap, same sanitization). */
    image: string;
    /** Server-resolved URL. Never persisted. */
    imageUrl?: string;
    alt?: string;
    /** Max width in px; null = section's max-width. */
    maxWidth?: number;
    align?: 'left' | 'center';
};

export type SpacerBlock = {
    id: string;
    type: 'spacer';
    size?: 'sm' | 'md' | 'lg' | 'xl';
};

export type AtomicBlock =
    | HeadingBlock
    | SubheadBlock
    | EyebrowBlock
    | TextBlock
    | ButtonBlock
    | ImageBlock
    | SpacerBlock;

export const ATOMIC_BLOCK_TYPES = [
    'heading',
    'subhead',
    'eyebrow',
    'text',
    'button',
    'image',
    'spacer',
] as const;

export type CustomSection = {
    id: string;
    type: 'custom';
    enabled: boolean;
    /** Vertical stack of atomic blocks. No nesting, no layout containers
     *  in Phase B. Renders inside a max-w-3xl centered column matching
     *  the marketing rails. */
    blocks: AtomicBlock[];
};

/* Mandatory data-driven section for /pricing. Renders plan cards +
 * period toggle + currency picker. Plan data ships via separate `pricing`
 * page prop (not Settings) — this payload only carries optional UI flags
 * and surrounding copy. Flagged `mandatory: true` in BLOCK_REGISTRY — admin
 * can reorder but can't disable/delete. */
export type PricingGridSection = {
    id: string;
    type: 'pricing_grid';
    /** Forced true; editor hides the toggle. Kept for type-uniformity. */
    enabled: boolean;
    /** Uppercase pill above the cards. Empty hides. */
    eyebrow?: string;
    /** Optional heading above the cards. HTML — inline tags only. */
    heading?: string;
    /** Optional subhead between heading and cards. HTML — paragraph context. */
    subhead?: string;
    /** Show the monthly/yearly period toggle. */
    showPeriodToggle: boolean;
    /** Show the multi-currency picker. When false the auto-detected
     *  currency stays + a small lock indicator appears next to the price. */
    showCurrencyPicker: boolean;
    /** Optional trust line under the plan cards (e.g. "7-day money-back
     *  guarantee · Cancel anytime"). HTML — inline tags only. Empty hides. */
    trustLine?: string;
};

/* Mandatory data-driven section for /templates. Renders featured kit row
 * + filter chips + kit grid + pagination. Kit data ships via separate
 * page props. Flagged `mandatory: true` in BLOCK_REGISTRY. */
export type KitGridSection = {
    id: string;
    type: 'kit_grid';
    enabled: boolean;
    /** Uppercase pill badge above the heading. Empty hides it. */
    eyebrow?: string;
    /** Optional heading above the kit list. */
    heading?: string;
    /** Optional subhead between heading and the list. */
    subhead?: string;
    /** Kit `key` to pin as featured, or null to use the kit JSON's own
     *  `is_featured` flag. Same pin-vs-auto semantics as the deferred
     *  flat config (before the sections refactor). */
    featuredKitKey: string | null;
    /** Show the category filter chip strip above the grid. */
    showCategories: boolean;
    /** Show the per-card screen-count badge. */
    showScreenCount: boolean;
};

export type Section =
    | HeroSection
    | FeaturesSection
    | StatsSection
    | HowItWorksSection
    | TestimonialsSection
    | FaqSection
    | TemplatesTeaserSection
    | LogoWallSection
    | FinalCtaSection
    | CustomSection
    | PricingGridSection
    | KitGridSection;

/** Per-page SEO override. Falls through to site-wide defaults
 *  (`marketing.seo`) when fields are empty. See seo-plan.md for the
 *  full resolution chain. */
export type PageSeoOverride = {
    title?: string;
    description?: string;
    /** Storage path for og:image upload. Server-resolved to a URL via
     *  `ogImageUrl` (never persisted; regenerated each read). */
    ogImage?: string;
    /** Server-resolved URL for `ogImage`. Stripped on save. */
    ogImageUrl?: string;
    /** When true, emits `<meta robots="noindex,nofollow">` on the page. */
    noindex?: boolean;
    /** Comma-separated keywords for `<meta name="keywords">`. Largely
     *  ignored by Google / Bing but useful for some compliance / agency
     *  workflows. Empty = no tag emitted. */
    keywords?: string;
};

export type PageConfig = {
    /** Schema version. Stamped at the current `CURRENT_WELCOME_VERSION` on
     *  every save; reads run through `cms-migrator.ts`. */
    version: number;
    sections: Section[];
    /** Publish toggle for optional pages (about / contact / templates).
     *  When false the public route returns 404. Welcome + Pricing ignore
     *  this flag (always treated as enabled — controlled by the
     *  site-level landing-mode toggle instead). Defaults to true. */
    enabled?: boolean;
    /** Per-page SEO override. See PageSeoOverride. */
    seo?: PageSeoOverride;
};
