# WCO-K01: Widget configuration schema with validation
**Area:** Widget Customization Docs
**Model:** moonshotai/kimi-k2.5
**Tokens:** 8,376
**Cost:** $0.0090
**Time:** 973ms
**Timestamp:** 2026-02-16T13:56:26.641066

---

 I'll provide a comprehensive, production-ready widget configuration system with JSON Schema, Pydantic models for the FastAPI backend, and TypeScript/Ajv validation for the frontend widget.

## 1. JSON Schema (Source of Truth)

```json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://sunaiva.ai/schemas/widget-config/v1.json",
  "title": "Sunaiva Widget Configuration",
  "description": "Configuration schema for Sunaiva AI Voice Widget",
  "type": "object",
  "required": ["widgetId", "colors", "position", "size"],
  "additionalProperties": false,
  "properties": {
    "widgetId": {
      "type": "string",
      "description": "Unique identifier for the widget instance",
      "pattern": "^[a-zA-Z0-9-_]{8,64}$"
    },
    "version": {
      "type": "string",
      "description": "Schema version",
      "enum": ["1.0.0"]
    },
    "colors": {
      "type": "object",
      "description": "Color scheme configuration",
      "required": ["primary", "background", "text"],
      "additionalProperties": false,
      "properties": {
        "primary": {
          "type": "string",
          "description": "Primary brand color (buttons, highlights)",
          "pattern": "^(#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})|rgb\\((\\d{1,3},\\s*){2}\\d{1,3}\\)|rgba\\((\\d{1,3},\\s*){3}(0|1|0?\\.\\d+)\\))$"
        },
        "secondary": {
          "type": "string",
          "description": "Secondary color (borders, subtle elements)",
          "pattern": "^(#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})|rgb\\((\\d{1,3},\\s*){2}\\d{1,3}\\)|rgba\\((\\d{1,3},\\s*){3}(0|1|0?\\.\\d+)\\))$"
        },
        "accent": {
          "type": "string",
          "description": "Accent color (call-to-action, recording indicator)",
          "pattern": "^(#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})|rgb\\((\\d{1,3},\\s*){2}\\d{1,3}\\)|rgba\\((\\d{1,3},\\s*){3}(0|1|0?\\.\\d+)\\))$"
        },
        "text": {
          "type": "string",
          "description": "Primary text color",
          "pattern": "^(#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})|rgb\\((\\d{1,3},\\s*){2}\\d{1,3}\\)|rgba\\((\\d{1,3},\\s*){3}(0|1|0?\\.\\d+)\\))$"
        },
        "background": {
          "type": "string",
          "description": "Widget background color",
          "pattern": "^(#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})|rgb\\((\\d{1,3},\\s*){2}\\d{1,3}\\)|rgba\\((\\d{1,3},\\s*){3}(0|1|0?\\.\\d+)\\))$"
        },
        "overlay": {
          "type": "string",
          "description": "Overlay backdrop color",
          "pattern": "^(#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})|rgb\\((\\d{1,3},\\s*){2}\\d{1,3}\\)|rgba\\((\\d{1,3},\\s*){3}(0|1|0?\\.\\d+)\\))$"
        }
      }
    },
    "position": {
      "type": "string",
      "description": "Widget position on screen",
      "enum": ["bottom-right", "bottom-left", "bottom-center"]
    },
    "size": {
      "type": "string",
      "description": "Widget size variant",
      "enum": ["compact", "default", "large"]
    },
    "greeting": {
      "type": "object",
      "description": "Greeting message configuration",
      "required": ["enabled"],
      "additionalProperties": false,
      "properties": {
        "enabled": {
          "type": "boolean",
          "description": "Whether to show greeting bubble"
        },
        "text": {
          "type": "string",
          "description": "Greeting message text (supports {{business_name}} placeholder)",
          "maxLength": 500,
          "minLength": 1
        },
        "delay": {
          "type": "number",
          "description": "Delay in seconds before showing greeting",
          "minimum": 0,
          "maximum": 60,
          "default": 3
        },
        "audioEnabled": {
          "type": "boolean",
          "description": "Whether to play audio greeting",
          "default": true
        }
      }
    },
    "businessHours": {
      "type": "object",
      "description": "Business hours configuration for availability checking",
      "required": ["timezone"],
      "additionalProperties": false,
      "properties": {
        "timezone": {
          "type": "string",
          "description": "IANA timezone identifier",
          "pattern": "^[A-Za-z_]+/[A-Za-z_]+(/[A-Za-z_]+)?$"
        },
        "schedule": {
          "type": "object",
          "description": "Weekly schedule (24h format HH:mm)",
          "additionalProperties": false,
          "properties": {
            "monday": { "$ref": "#/definitions/daySchedule" },
            "tuesday": { "$ref": "#/definitions/daySchedule" },
            "wednesday": { "$ref": "#/definitions/daySchedule" },
            "thursday": { "$ref": "#/definitions/daySchedule" },
            "friday": { "$ref": "#/definitions/daySchedule" },
            "saturday": { "$ref": "#/definitions/daySchedule" },
            "sunday": { "$ref": "#/definitions/daySchedule" }
          }
        },
        "holidayOverrides": {
          "type": "array",
          "description": "Specific dates with custom hours or closed",
          "items": {
            "type": "object",
            "required": ["date"],
            "additionalProperties": false,
            "properties": {
              "date": {
                "type": "string",
                "format": "date",
                "description": "ISO 8601 date (YYYY-MM-DD)"
              },
              "isClosed": {
                "type": "boolean",
                "description": "Whether business is closed this day",
                "default": false
              },
              "hours": {
                "$ref": "#/definitions/daySchedule",
                "description": "Custom hours for this date (ignored if isClosed)"
              }
            }
          }
        }
      }
    },
    "voice": {
      "type": "object",
      "description": "Voice synthesis configuration",
      "required": ["voiceId"],
      "additionalProperties": false,
      "properties": {
        "voiceId": {
          "type": "string",
          "description": "Telnyx/TTS voice identifier",
          "minLength": 1,
          "maxLength": 64
        },
        "provider": {
          "type": "string",
          "description": "TTS provider",
          "enum": ["telnyx", "google", "amazon", "azure"],
          "default": "telnyx"
        },
        "speed": {
          "type": "number",
          "description": "Speech rate multiplier",
          "minimum": 0.5,
          "maximum": 2.0,
          "default": 1.0
        },
        "pitch": {
          "type": "number",
          "description": "Pitch adjustment in semitones",
          "minimum": -20,
          "maximum": 20,
          "default": 0
        },
        "language": {
          "type": "string",
          "description": "Language code (BCP 47)",
          "pattern": "^[a-z]{2}-[A-Z]{2}$",
          "default": "en-AU"
        },
        "greetingAudioUrl": {
          "type": "string",
          "format": "uri",
          "description": "Custom pre-recorded greeting audio URL"
        },
        "silencePadding": {
          "type": "number",
          "description": "Leading/trailing silence in milliseconds",
          "minimum": 0,
          "maximum": 5000,
          "default": 300
        }
      }
    },
    "behavior": {
      "type": "object",
      "description": "Widget behavior settings",
      "additionalProperties": false,
      "properties": {
        "autoOpen": {
          "type": "boolean",
          "description": "Auto-open widget after delay",
          "default": false
        },
        "autoOpenDelay": {
          "type": "number",
          "description": "Seconds before auto-open",
          "minimum": 1,
          "maximum": 300,
          "default": 10
        },
        "persistentButton": {
          "type": "boolean",
          "description": "Keep button visible when open",
          "default": true
        },
        "closeOnOutsideClick": {
          "type": "boolean",
          "description": "Close widget when clicking outside",
          "default": true
        },
        "mobileFullscreen": {
          "type": "boolean",
          "description": "Use fullscreen mode on mobile",
          "default": true
        }
      }
    },
    "metadata": {
      "type": "object",
      "description": "Business metadata for personalization",
      "additionalProperties": false,
      "properties": {
        "businessName": {
          "type": "string",
          "maxLength": 100
        },
        "businessPhone": {
          "type": "string",
          "pattern": "^[\\+]?[(]?[0-9]{3}[)]?[-\\s\\.]?[0-9]{3}[-\\s\\.]?[0-9]{4,6}$"
        },
        "businessEmail": {
          "type": "string",
          "format": "email"
        },
        "industry": {
          "type": "string",
          "enum": ["plumbing", "electrical", "hvac", "carpentry", "roofing", "landscaping", "general", "other"]
        }
      }
    }
  },
  "definitions": {
    "daySchedule": {
      "type": "array",
      "description": "Time ranges for a day (supports split shifts)",
      "items": {
        "type": "object",
        "required": ["start", "end"],
        "additionalProperties": false,
        "properties": {
          "start": {
            "type": "string",
            "pattern": "^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$",
            "description": "Start time (24h format HH:mm)"
          },
          "end": {
            "type": "string",
            "pattern": "^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$",
            "description": "End time (24h format HH:mm)"
          }
        }
      }
    }
  }
}
```

## 2. Python Pydantic Models (FastAPI Backend)

```python
"""
Widget Configuration Models for Sunaiva Talking Widget.

This module defines the Pydantic models for widget configuration validation
and serialization in the FastAPI backend.
"""

import re
from datetime import time
from decimal import Decimal
from enum import Enum
from typing import Annotated, Any, Literal, Self

from pydantic import (
    BaseModel,
    ConfigDict,
    Field,
    field_validator,
    model_validator,
)
from pydantic.types import StringConstraints


# ============================================================================
# Enums
# ============================================================================

class WidgetPosition(str, Enum):
    """Widget position on screen."""
    BOTTOM_RIGHT = "bottom-right"
    BOTTOM_LEFT = "bottom-left"
    BOTTOM_CENTER = "bottom-center"


class WidgetSize(str, Enum):
    """Widget size variant."""
    COMPACT = "compact"
    DEFAULT = "default"
    LARGE = "large"


class TTSProvider(str, Enum):
    """Text-to-speech provider."""
    TELNYX = "telnyx"
    GOOGLE = "google"
    AMAZON = "amazon"
    AZURE = "azure"


class Industry(str, Enum):
    """Business industry categories."""
    PLUMBING = "plumbing"
    ELECTRICAL = "electrical"
    HVAC = "hvac"
    CARPENTRY = "carpentry"
    ROOFING = "roofing"
    LANDSCAPING = "landscaping"
    GENERAL = "general"
    OTHER = "other"


class DayOfWeek(str, Enum):
    """Days of the week."""
    MONDAY = "monday"
    TUESDAY = "tuesday"
    WEDNESDAY = "wednesday"
    THURSDAY = "thursday"
    FRIDAY = "friday"
    SATURDAY = "saturday"
    SUNDAY = "sunday"


# ============================================================================
# Color Types
# ============================================================================

ColorString = Annotated[
    str,
    StringConstraints(
        pattern=r"^(#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})|"
        r"rgb\((\d{1,3},\s*){2}\d{1,3}\)|"
        r"rgba\((\d{1,3},\s*){3}(0|1|0?\.\d+)\))$"
    )
]


# ============================================================================
# Time Range Models
# ============================================================================

class TimeRange(BaseModel):
    """Time range for business hours."""
    model_config = ConfigDict(frozen=True)
    
    start: Annotated[str, StringConstraints(pattern=r"^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$")]
    end: Annotated[str, StringConstraints(pattern=r"^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$")]
    
    @model_validator(mode="after")
    def validate_time_order(self) -> Self:
        """Ensure end time is after start time."""
        start_parts = self.start.split(":")
        end_parts = self.end.split(":")
        
        start_minutes = int(start_parts[0]) * 60 + int(start_parts[1])
        end_minutes = int(end_parts[0]) * 60 + int(end_parts[1])
        
        if end_minutes <= start_minutes:
            raise ValueError(f"End time {self.end} must be after start time {self.start}")
        return self
    
    def to_time_objects(self) -> tuple[time, time]:
        """Convert string times to time objects."""
        start_h, start_m = map(int, self.start.split(":"))
        end_h, end_m = map(int, self.end.split(":"))
        return time(start_h, start_m), time(end_h, end_m)


# ============================================================================
# Business Hours Models
# ============================================================================

class HolidayOverride(BaseModel):
    """Special date override for business hours."""
    model_config = ConfigDict(frozen=True)
    
    date: Annotated[str, StringConstraints(pattern=r"^\d{4}-\d{2}-\d{2}$")]
    is_closed: bool = Field(default=False, alias="isClosed")
    hours: list[TimeRange] | None = None
    
    @model_validator(mode="after")
    def validate_hours(self) -> Self:
        """Ensure hours provided if not closed."""
        if not self.is_closed and not self.hours:
            raise ValueError("Hours must be provided if not closed")
        return self


class BusinessHours(BaseModel):
    """Business hours configuration."""
    model_config = ConfigDict(frozen=True)
    
    timezone: Annotated[
        str,
        StringConstraints(pattern=r"^[A-Za-z_]+/[A-Za-z_]+(/[A-Za-z_]+)?$")
    ]
    schedule: dict[DayOfWeek, list[TimeRange]] = Field(default_factory=dict)
    holiday_overrides: list[HolidayOverride] = Field(
        default_factory=list,
        alias="holidayOverrides"
    )
    
    @field_validator("timezone")
    @classmethod
    def validate_timezone(cls, v: str) -> str:
        """Validate timezone is recognized (basic check)."""
        # In production, use pytz or zoneinfo to validate
        valid_prefixes = (
            "Australia/", "Pacific/", "Europe/", "America/", "Asia/", 
            "Africa/", "Atlantic/", "Indian/", "Antarctica/", "US/", "Etc/"
        )
        if not any(v.startswith(prefix) for prefix in valid_prefixes):
            raise ValueError(f"Invalid timezone format: {v}")
        return v


# ============================================================================
# Voice Settings
# ============================================================================

class VoiceSettings(BaseModel):
    """Voice synthesis configuration."""
    model_config = ConfigDict(frozen=True)
    
    voice_id: str = Field(..., alias="voiceId", min_length=1, max_length=64)
    provider: TTSProvider = Field(default=TTSProvider.TELNYX)
    speed: Decimal = Field(default=Decimal("1.0"), ge=Decimal("0.5"), le=Decimal("2.0"))
    pitch: int = Field(default=0, ge=-20, le=20)
    language: Annotated[str, StringConstraints(pattern=r"^[a-z]{2}-[A-Z]{2}$")] = "en-AU"
    greeting_audio_url: str | None = Field(default=None, alias="greetingAudioUrl")
    silence_padding: int = Field(default=300, ge=0, le=5000, alias="silencePadding")
    
    @field_validator("greeting_audio_url")
    @classmethod
    def validate_audio_url(cls, v: str | None) -> str | None:
        """Validate audio URL if provided."""
        if v is None:
            return v
        if not v.startswith(("https://", "http://")):
            raise ValueError("Audio URL must be HTTP or HTTPS")
        return v


# ============================================================================
# Greeting Settings
# ============================================================================

class GreetingConfig(BaseModel):
    """Greeting bubble configuration."""
    model_config = ConfigDict(frozen=True)
    
    enabled: bool
    text: Annotated[str, StringConstraints(min_length=1, max_length=500)] | None = None
    delay: int = Field(default=3, ge=0, le=60)
    audio_enabled: bool = Field(default=True, alias="audioEnabled")
    
    @model_validator(mode="after")
    def validate_text_when_enabled(self) -> Self:
        """Ensure text exists if greeting is enabled."""
        if self.enabled and not self.text:
            raise ValueError("Greeting text is required when enabled")
        return self


# ============================================================================
# Color Scheme
# ============================================================================

class ColorScheme(BaseModel):
    """Widget color configuration."""
    model_config = ConfigDict(frozen=True)
    
    primary: ColorString
    secondary: ColorString | None = None
    accent: ColorString | None = None
    text: ColorString
    background: ColorString
    overlay: ColorString | None = None


# ============================================================================
# Behavior Settings
# ============================================================================

class BehaviorSettings(BaseModel):
    """Widget behavior configuration."""
    model_config = ConfigDict(frozen=True)
    
    auto_open: bool = Field(default=False, alias="autoOpen")
    auto_open_delay: int = Field(default=10, ge=1, le=300, alias="autoOpenDelay")
    persistent_button: bool = Field(default=True, alias="persistentButton")
    close_on_outside_click: bool = Field(default=True, alias="closeOnOutsideClick")
    mobile_fullscreen: bool = Field(default=True, alias="mobileFullscreen")


# ============================================================================
# Metadata
# ============================================================================

class WidgetMetadata(BaseModel):
    """Business metadata for personalization."""
    model_config = ConfigDict(frozen=True)
    
    business_name: Annotated[str, StringConstraints(max_length=100)] | None = Field(
        default=None, alias="businessName"
    )
    business_phone: Annotated[
        str, 
        StringConstraints(pattern=r"^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$")
    ] | None = Field(default=None, alias="businessPhone")
    business_email: Annotated[str, StringConstraints(pattern=r"^[^@]+@[^@]+\.[^@]+$")] | None = Field(
        default=None, alias="businessEmail"
    )
    industry: Industry | None = None


# ============================================================================
# Main Configuration
# ============================================================================

class WidgetConfig(BaseModel):
    """
    Complete widget configuration model.
    
    This is the primary model used for validating and storing widget
    configurations in the Sunaiva platform.
    """
    model_config = ConfigDict(
        frozen=True,
        populate_by_name=True,
        json_schema_extra={
            "examples": [
                {
                    "widgetId": "widget_abc123",
                    "version": "1.0.0",
                    "colors": {
                        "primary": "#FF6B35",
                        "secondary": "#F7C59F",
                        "accent": "#004E89",
                        "text": "#1A1A1A",
                        "background": "#FFFFFF",
                        "overlay": "rgba(0, 0, 0, 0.5)"
                    },
                    "position": "bottom-right",
                    "size": "default",
                    "greeting": {
                        "enabled": True,
                        "text": "Hi there! Need help with a plumbing emergency? I'm here to assist!",
                        "delay": 3,
                        "audioEnabled": True
                    },
                    "businessHours": {
                        "timezone": "Australia/Sydney",
                        "schedule": {
                            "monday": [{"start": "07:00", "end": "18:00"}],
                            "tuesday": [{"start": "07:00", "end": "18:00"}],
                            "wednesday": [{"start": "07:00", "end": "18:00"}],
                            "thursday": [{"start": "07:00", "end": "18:00"}],
                            "friday": [{"start": "07:00", "end": "17:00"}],
                            "saturday": [{"start": "08:00", "end": "14:00"}]
                        },
                        "holidayOverrides": [
                            {"date": "2024-12-25", "isClosed": True}
                        ]
                    },
                    "voice": {
                        "voiceId": "en-AU-Neural2-C",
                        "provider": "google",
                        "speed": 1.1,
                        "pitch": 0,
                        "language": "en-AU",
                        "silencePadding": 300
                    },
                    "behavior": {
                        "autoOpen": False,
                        "closeOnOutsideClick": True,
                        "mobileFullscreen": True
                    },
                    "metadata": {
                        "businessName": "Sydney Plumbing Pros",
                        "industry": "plumbing"
                    }
                }
            ]
        }
    )
    
    widget_id: Annotated[str, StringConstraints(pattern=r"^[a-zA-Z0-9-_]{8,64}$")] = Field(
        ..., alias="widgetId"
    )
    version: Literal["1.0.0"] = "1.0.0"
    colors: ColorScheme
    position: WidgetPosition
    size: WidgetSize
    greeting: GreetingConfig | None = None
    business_hours: BusinessHours | None = Field(default=None, alias="businessHours")
    voice: VoiceSettings | None = None
    behavior: BehaviorSettings = Field(default_factory=BehaviorSettings)
    metadata: WidgetMetadata | None = None
    
    def to_widget_js_config(self) -> dict[str, Any]:
        """
        Convert to JavaScript-friendly configuration format.
        
        Returns configuration optimized for the frontend widget with
        camelCase keys and serialized values.
        """
        return self.model_dump(by_alias=True, exclude_none=True)
    
    def is_within_business_hours(self, check_datetime) -> bool:
        """
        Check if given datetime falls within configured business hours.
        
        Args:
            check_datetime: Timezone-aware datetime to check
            
        Returns:
            True if within business hours, False otherwise
        """
        import pytz
        
        if not self.business_hours:
            return True  # No restrictions
        
        # Convert to business timezone
        tz = pytz.timezone(self.business_hours.timezone)
        local_dt = check_datetime.astimezone(tz)
        
        # Check holiday overrides
        date_str = local_dt.strftime("%Y-%m-%d")
        for override in self.business_hours.holiday_overrides:
            if override.date == date_str:
                return not override.is_closed
        
        # Check regular schedule
        day = DayOfWeek(local_dt.strftime("%A").lower())
        day_schedule = self.business_hours.schedule.get(day, [])
        
        if not day_schedule:
            return False
        
        current_time = local_dt.time()
        current_minutes = current_time.hour * 60 + current_time.minute
        
        for time_range in day_schedule:
            start_t, end_t = time_range.to_time_objects()
            start_m = start_t.hour * 60 + start_t.minute
            end_m = end_t.hour * 60 + end_t.minute
            
            if start_m <= current_minutes <= end_m:
                return True
        
        return False


# ============================================================================
# API Request/Response Models
# ============================================================================

class WidgetConfigCreate(BaseModel):
    """Request model for creating new widget configuration."""
    colors: ColorScheme
    position: WidgetPosition
    size: WidgetSize
    greeting: GreetingConfig | None = None
    business_hours: BusinessHours | None = None
    voice: VoiceSettings | None = None
    behavior: BehaviorSettings | None = None
    metadata: WidgetMetadata | None = None


class WidgetConfigUpdate(BaseModel):
    """Request model for updating widget configuration (partial)."""
    colors: ColorScheme | None = None
    position: WidgetPosition | None = None
    size: WidgetSize | None = None
    greeting: GreetingConfig | None = None
    business_hours: BusinessHours | None = None
    voice: VoiceSettings | None = None
    behavior: BehaviorSettings | None = None
    metadata: WidgetMetadata | None = None


class WidgetConfigResponse(WidgetConfig):
    """Response model including server-generated fields."""
    created_at: str | None = None
    updated_at: str | None = None
    is_active: bool = True
```

## 3. TypeScript Types & Ajv Validation (Frontend)

```typescript
/**
 * Sunaiva Widget Configuration Types and Validation
 * 
 * TypeScript definitions and Ajv validation for the widget configuration schema.
 * Used by the vanilla JS widget to validate configuration at runtime.
 */

import Ajv, { JSONSchemaType, ValidateFunction } from 'ajv';
import addFormats from 'ajv-formats';

// ============================================================================
// Enums
// ============================================================================

export type WidgetPosition = 'bottom-right' | 'bottom-left' | 'bottom-center';
export type WidgetSize = 'compact' | 'default' | 'large';
export type TTSProvider = 'telnyx' | 'google' | 'amazon' | 'azure';
export type Industry = 'plumbing' | 'electrical' | 'hvac' | 'carpentry' | 
                       'roofing' | 'landscaping' | 'general' | 'other';
export type DayOfWeek = 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 
                        'friday' | 'saturday' | 'sunday';

// ============================================================================
// Interfaces
// ============================================================================

export interface TimeRange {
  start: string; // HH:mm format
  end: string;   // HH:mm format
}

export interface HolidayOverride {
  date: string; // YYYY-MM-DD
  isClosed?: boolean;
  hours?: TimeRange[];
}

export interface BusinessHours {
  timezone: string; // IANA timezone
  schedule?: Record<DayOfWeek, TimeRange[]>;
  holidayOverrides?: HolidayOverride[];
}

export interface VoiceSettings {
  voiceId: string;
  provider?: TTSProvider;
  speed?: number;
  pitch?: number;
  language?: string;
  greetingAudioUrl?: string;
  silencePadding?: number;
}

export interface GreetingConfig {
  enabled: boolean;
  text?: string;
  delay?: number;
  audioEnabled?: boolean;
}

export interface ColorScheme {
  primary: string;
  secondary?: string;
  accent?: string;
  text: string;
  background: string;
  overlay?: string;
}

export interface BehaviorSettings {
  autoOpen?: boolean;
  autoOpenDelay?: number;
  persistentButton?: boolean;
  closeOnOutsideClick?: boolean;
  mobileFullscreen?: boolean;
}

export interface WidgetMetadata {
  businessName?: string;
  businessPhone?: string;
  businessEmail?: string;
  industry?: Industry;
}

export interface WidgetConfig {
  widgetId: string;
  version: '1.0.0';
  colors: ColorScheme;
  position: WidgetPosition;
  size: WidgetSize;
  greeting?: GreetingConfig;
  businessHours?: BusinessHours;
  voice?: VoiceSettings;
  behavior?: BehaviorSettings;
  metadata?: WidgetMetadata;
}

// ============================================================================
// Validation Schema
// ============================================================================

const colorPattern = '^(#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})|' + 
  'rgb\\((\\d{1,3},\\s*){2}\\d{1,3}\\)|' + 
  'rgba\\((\\d{1,3},\\s*){3}(0|1|0?\\.\\d+)\\))$';

const timePattern = '^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$';
const timezonePattern = '^[A-Za-z_]+/[A-Za-z_]+(/[A-Za-z_]+)?$';
const languagePattern = '^[a-z]{2}-[A-Z]{2}$';

const timeRangeSchema: JSONSchemaType<TimeRange> = {
  type: 'object',
  required: ['start', 'end'],
  additionalProperties: false,
  properties: {
    start: { type: 'string', pattern: timePattern },
    end: { type: 'string', pattern: timePattern }
  }
};

const holidayOverrideSchema: JSONSchemaType<HolidayOverride> = {
  type: 'object',
  required: ['date'],
  additionalProperties: false,
  properties: {
    date: { type: 'string', format: 'date' },
    isClosed: { type: 'boolean', nullable: true },
    hours: { 
      type: 'array', 
      items: timeRangeSchema,
      nullable: true 
    }
  }
};

const businessHoursSchema: JSONSchemaType<BusinessHours> = {
  type: 'object',
  required: ['timezone'],
  additionalProperties: false,
  properties: {
    timezone: { type: 'string', pattern: timezonePattern },
    schedule: {
      type: 'object',
      required: [],
      additionalProperties: false,
      properties: {
        monday: { type: 'array', items: timeRangeSchema, nullable: true },
        tuesday: { type: 'array', items: timeRangeSchema, nullable: true },
        wednesday: { type: 'array', items: timeRangeSchema, nullable: true },
        thursday: { type: 'array', items: timeRangeSchema, nullable: true },
        friday: { type: 'array', items: timeRangeSchema, nullable: true },
        saturday: { type: 'array', items: timeRangeSchema, nullable: true },
        sunday: { type: 'array', items: timeRangeSchema, nullable: true }
      }
    },
    holidayOverrides: {
      type: 'array',
      items: holidayOverrideSchema,
      nullable: true
    }
  }
};

const voiceSettingsSchema: JSONSchemaType<VoiceSettings> = {
  type: 'object',
  required: ['voiceId'],
  additionalProperties: false,
  properties: {
    voiceId: { type: 'string', minLength: 1, maxLength: 64 },
    provider: { 
      type: 'string', 
      enum: ['telnyx', 'google', 'amazon', 'azure'],
      nullable: true 
    },
    speed: { type: 'number', minimum: 0.5, maximum: 2.