//! App scoping: top-level isolation boundary for scripts, routes,
//! domains, and (forward) data. Every script and route belongs to
//! exactly one app; cross-app references are not allowed.
//!
//! See blueprint §11.5. The orchestrator dispatches via two-phase
//! lookup: `Host → app_id → route trie`.

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::{AppId, GroupId};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct App {
    pub id: AppId,
    /// URL-safe identifier; appears in dashboard paths. Mutable via the
    /// slug-rename flow which preserves the old slug as a permanent 301
    /// in `app_slug_history`.
    pub slug: String,
    pub name: String,
    pub description: Option<String>,
    /// Parent group in the org tree (Phase 2). Every app has a parent
    /// from day one (§9 backfill seeds the instance root group).
    pub group_id: GroupId,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum DomainShape {
    /// Exact host: `app.example.com`.
    Exact,
    /// Wildcard suffix: `*.example.com` matches any subdomain.
    Wildcard,
    /// Parameterized wildcard: `{tenant}.example.com`. Same shape as
    /// `Wildcard` for collision purposes; the binding name surfaces in
    /// request context (future).
    Parameterized,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppDomain {
    pub id: Uuid,
    pub app_id: AppId,
    /// As the user typed it: `app.example.com`, `*.example.com`, or
    /// `{tenant}.example.com`.
    pub pattern: String,
    pub shape: DomainShape,
    /// Normalized collision key. `exact:<host>` for exact; `wildcard:<suffix>`
    /// for both wildcard and parameterized (parameter name is a binding,
    /// not a discriminator — per blueprint §11.5).
    pub shape_key: String,
    pub created_at: DateTime<Utc>,
}
