# retoor <retoor@molodetz.nl>

from __future__ import annotations

from typing import Any, Optional

from pydantic import BaseModel, ConfigDict


class _Out(BaseModel):
    model_config = ConfigDict(extra="ignore")


# ---------- envelopes ----------


class ActionResult(_Out):
    ok: bool = True
    redirect: Optional[str] = None
    data: Optional[Any] = None


class ErrorOut(_Out):
    error: dict


class ValidationErrorOut(_Out):
    error: str = "validation"
    fields: dict = {}
    messages: list = []


class InstanceOut(_Out):
    uid: str = ""
    project_uid: str = ""
    name: str = ""
    slug: str = ""
    container_id: Optional[str] = None
    status: Optional[str] = None
    desired_state: Optional[str] = None
    restart_policy: Optional[str] = None
    restart_count: int = 0
    ingress_slug: Optional[str] = None
    ingress_port: int = 0
    boot_command: Optional[str] = None
    boot_language: Optional[str] = None
    boot_script: Optional[str] = None
    run_as_uid: Optional[str] = None
    start_on_boot: int = 0
    cpu_limit: Optional[str] = None
    mem_limit: Optional[str] = None
    created_at: Optional[str] = None
    started_at: Optional[str] = None
    stopped_at: Optional[str] = None


class ScheduleOut(_Out):
    uid: str = ""
    instance_uid: str = ""
    action: Optional[str] = None
    enabled: int = 0
    next_run_at: Optional[str] = None
    last_run_at: Optional[str] = None
    run_count: int = 0


class ContainersOut(_Out):
    project: Optional[dict] = None
    instances: list[InstanceOut] = []


class ZipJobOut(_Out):
    uid: str = ""
    kind: str = ""
    status: str = ""
    preferred_name: Optional[str] = None
    download_url: Optional[str] = None
    error: Optional[str] = None
    bytes_in: int = 0
    bytes_out: int = 0
    item_count: int = 0
    file_count: int = 0
    dir_count: int = 0
    created_at: Optional[str] = None
    completed_at: Optional[str] = None


class PlanningJobOut(_Out):
    uid: str = ""
    kind: str = ""
    status: str = ""
    preferred_name: Optional[str] = None
    download_url: Optional[str] = None
    markdown: Optional[str] = None
    ai_used: bool = False
    error: Optional[str] = None
    issue_count: int = 0
    bytes_out: int = 0
    created_at: Optional[str] = None
    completed_at: Optional[str] = None


class ForkJobOut(_Out):
    uid: str = ""
    kind: str = ""
    status: str = ""
    preferred_name: Optional[str] = None
    project_uid: Optional[str] = None
    project_url: Optional[str] = None
    source_project_uid: Optional[str] = None
    error: Optional[str] = None
    item_count: int = 0
    created_at: Optional[str] = None
    completed_at: Optional[str] = None


class SeoJobOut(_Out):
    uid: str = ""
    kind: str = ""
    status: str = ""
    target: Optional[str] = None
    mode: Optional[str] = None
    ws_url: Optional[str] = None
    report_url: Optional[str] = None
    score: Optional[int] = None
    grade: Optional[str] = None
    page_count: int = 0
    error: Optional[str] = None
    created_at: Optional[str] = None
    completed_at: Optional[str] = None


class BackupStoragePathOut(_Out):
    key: str = ""
    label: str = ""
    path: str = ""
    size_bytes: int = 0
    size_human: str = ""
    file_count: int = 0
    exists: bool = False


class BackupOut(_Out):
    uid: str = ""
    job_uid: str = ""
    target: str = ""
    label: str = ""
    status: str = ""
    filename: str = ""
    size_bytes: int = 0
    size_human: str = ""
    bytes_in: int = 0
    file_count: int = 0
    dir_count: int = 0
    sha256: str = ""
    schedule_uid: str = ""
    created_by: str = ""
    created_at: Optional[str] = None
    completed_at: Optional[str] = None
    error: Optional[str] = None
    download_url: Optional[str] = None


class BackupJobOut(_Out):
    uid: str = ""
    kind: str = ""
    status: str = ""
    target: Optional[str] = None
    backup_uid: Optional[str] = None
    download_url: Optional[str] = None
    error: Optional[str] = None
    bytes_out: int = 0
    file_count: int = 0
    sha256: Optional[str] = None
    created_at: Optional[str] = None
    completed_at: Optional[str] = None


class BackupScheduleOut(_Out):
    uid: str = ""
    name: str = ""
    target: str = ""
    kind: str = ""
    every_seconds: int = 0
    cron: Optional[str] = None
    enabled: int = 0
    keep_last: int = 0
    next_run_at: Optional[str] = None
    last_run_at: Optional[str] = None
    last_job_uid: Optional[str] = None
    run_count: int = 0
    created_at: Optional[str] = None


class BackupDashboardOut(_Out):
    storage: dict = {}
    backups: list[BackupOut] = []
    schedules: list[BackupScheduleOut] = []
    targets: list[dict] = []
    metrics: dict = {}
    generated_at: Optional[str] = None
    admin_section: Optional[str] = None


class SeoReportOut(_Out):
    uid: str = ""
    status: str = ""
    target: Optional[str] = None
    mode: Optional[str] = None
    score: Optional[int] = None
    grade: Optional[str] = None
    page_count: int = 0
    counts: dict = {}
    categories: dict = {}
    pages: list = []
    checks: list = []
    site: dict = {}
    generated_at: Optional[str] = None


class DeepsearchJobOut(_Out):
    uid: str = ""
    kind: str = ""
    status: str = ""
    query: Optional[str] = None
    depth: int = 0
    max_pages: int = 0
    ws_url: Optional[str] = None
    chat_ws_url: Optional[str] = None
    session_url: Optional[str] = None
    score: Optional[int] = None
    confidence: Optional[float] = None
    source_diversity: Optional[float] = None
    page_count: int = 0
    chunk_count: int = 0
    error: Optional[str] = None
    created_at: Optional[str] = None
    completed_at: Optional[str] = None


class DeepsearchSessionOut(_Out):
    uid: str = ""
    status: str = ""
    query: Optional[str] = None
    depth: int = 0
    max_pages: int = 0
    score: Optional[int] = None
    confidence: Optional[float] = None
    source_diversity: Optional[float] = None
    page_count: int = 0
    chunk_count: int = 0
    summary: Optional[str] = None
    sources: list = []
    findings: list = []
    gaps: list = []
    timeline: list = []
    chat_ws_url: Optional[str] = None
    export_md_url: Optional[str] = None
    export_json_url: Optional[str] = None
    export_pdf_url: Optional[str] = None
    viewer_is_admin: bool = False
    viewer_owns: bool = False
    created_at: Optional[str] = None
    completed_at: Optional[str] = None


# ---------- shared resource projections ----------


class UserOut(_Out):
    uid: str = ""
    username: str = ""
    avatar_seed: Optional[str] = None
    role: Optional[str] = None
    bio: Optional[str] = None
    location: Optional[str] = None
    git_link: Optional[str] = None
    website: Optional[str] = None
    level: Optional[int] = None
    xp: Optional[int] = None
    stars: Optional[int] = None
    created_at: Optional[str] = None


class AttachmentOut(_Out):
    uid: str = ""
    filename: Optional[str] = None
    url: Optional[str] = None
    size: Optional[int] = None
    is_image: Optional[bool] = None
    is_video: Optional[bool] = None
    mime_type: Optional[str] = None
    created_at: Optional[str] = None


class VotesOut(_Out):
    up: int = 0
    down: int = 0


class ReactionsOut(_Out):
    counts: dict = {}
    mine: list = []


class PollOptionOut(_Out):
    uid: str = ""
    label: Optional[str] = None
    count: Optional[int] = None
    votes: Optional[int] = None
    pct: Optional[int] = None


class PollOut(_Out):
    uid: str = ""
    question: Optional[str] = None
    options: list[PollOptionOut] = []
    total: int = 0
    my_choice: Optional[str] = None
    voted: Optional[str] = None


class BadgeOut(_Out):
    name: Optional[str] = None
    created_at: Optional[str] = None


class PostOut(_Out):
    uid: str = ""
    slug: Optional[str] = None
    user_uid: Optional[str] = None
    title: Optional[str] = None
    content: Optional[str] = None
    topic: Optional[str] = None
    stars: Optional[int] = None
    image: Optional[str] = None
    created_at: Optional[str] = None
    updated_at: Optional[str] = None


class GistOut(_Out):
    uid: str = ""
    slug: Optional[str] = None
    user_uid: Optional[str] = None
    title: Optional[str] = None
    description: Optional[str] = None
    source_code: Optional[str] = None
    language: Optional[str] = None
    stars: Optional[int] = None
    created_at: Optional[str] = None
    updated_at: Optional[str] = None


class ProjectOut(_Out):
    uid: str = ""
    slug: Optional[str] = None
    user_uid: Optional[str] = None
    title: Optional[str] = None
    description: Optional[str] = None
    project_type: Optional[str] = None
    status: Optional[str] = None
    platforms: Optional[Any] = None
    stars: Optional[int] = None
    is_private: Optional[bool] = None
    read_only: Optional[bool] = None
    release_date: Optional[str] = None
    demo_date: Optional[str] = None
    created_at: Optional[str] = None
    updated_at: Optional[str] = None


class ProjectFileOut(_Out):
    uid: str = ""
    path: str = ""
    name: Optional[str] = None
    type: Optional[str] = None
    is_binary: Optional[bool] = None
    mime_type: Optional[str] = None
    size: Optional[int] = None
    url: Optional[str] = None
    updated_at: Optional[str] = None


class ProjectFileRawOut(ProjectFileOut):
    content: Optional[str] = None


class ProjectFilesOut(_Out):
    project: ProjectOut
    files: list[ProjectFileOut] = []
    is_owner: bool = False
    read_only: bool = False
    is_private: bool = False


class NewsOut(_Out):
    uid: str = ""
    slug: Optional[str] = None
    title: Optional[str] = None
    description: Optional[str] = None
    content: Optional[str] = None
    url: Optional[str] = None
    source_name: Optional[str] = None
    author: Optional[str] = None
    grade: Optional[int] = None
    status: Optional[str] = None
    image_url: Optional[str] = None
    article_published: Optional[str] = None
    created_at: Optional[str] = None
    synced_at: Optional[str] = None


class CommentOut(_Out):
    uid: str = ""
    user_uid: Optional[str] = None
    content: Optional[str] = None
    parent_uid: Optional[str] = None
    target_type: Optional[str] = None
    target_uid: Optional[str] = None
    created_at: Optional[str] = None


class CommentEditOut(_Out):
    uid: str = ""
    content: str = ""
    url: str = ""
    updated_at: Optional[str] = None


class CommentItemOut(_Out):
    comment: CommentOut
    author: Optional[UserOut] = None
    time_ago: Optional[str] = None
    votes: VotesOut = VotesOut()
    my_vote: int = 0
    children: list["CommentItemOut"] = []
    attachments: list[AttachmentOut] = []
    reactions: ReactionsOut = ReactionsOut()


class NotificationOut(_Out):
    uid: str = ""
    type: Optional[str] = None
    message: Optional[str] = None
    read: Optional[bool] = None
    related_uid: Optional[str] = None
    target_url: Optional[str] = None
    created_at: Optional[str] = None


class MessageOut(_Out):
    uid: str = ""
    sender_uid: Optional[str] = None
    receiver_uid: Optional[str] = None
    content: Optional[str] = None
    read: Optional[bool] = None
    created_at: Optional[str] = None



# ---------- list-item wrappers ----------


class FeedItemOut(_Out):
    post: PostOut
    author: Optional[UserOut] = None
    time_ago: Optional[str] = None
    my_vote: int = 0
    comment_count: int = 0
    attachments: list[AttachmentOut] = []
    recent_comments: list[CommentItemOut] = []
    reactions: ReactionsOut = ReactionsOut()
    bookmarked: bool = False
    poll: Optional[PollOut] = None


class GistItemOut(_Out):
    gist: GistOut
    author: Optional[UserOut] = None
    time_ago: Optional[str] = None
    my_vote: int = 0
    comment_count: int = 0
    recent_comments: list[CommentItemOut] = []


class ProjectListItemOut(ProjectOut):
    author_name: Optional[str] = None
    my_vote: int = 0
    recent_comments: list[CommentItemOut] = []


class NewsListItemOut(_Out):
    article: NewsOut
    time_ago: Optional[str] = None
    image_url: Optional[str] = None
    grade: Optional[int] = None
    recent_comments: list[CommentItemOut] = []


class ConversationOut(_Out):
    other_user: Optional[UserOut] = None
    last_message: Optional[str] = None
    last_message_at: Optional[str] = None
    unread: bool = False


class MessageItemOut(_Out):
    message: MessageOut
    sender: Optional[UserOut] = None
    is_mine: bool = False
    time_ago: Optional[str] = None
    attachments: list[AttachmentOut] = []


class NotificationItemOut(_Out):
    notification: NotificationOut
    actor: Optional[UserOut] = None
    time_ago: Optional[str] = None


class NotificationGroupOut(_Out):
    label: Optional[str] = None
    entries: list[NotificationItemOut] = []


class IssueItemOut(_Out):
    number: int = 0
    title: str = ""
    state: str = "open"
    html_url: Optional[str] = None
    comments_count: int = 0
    created_at: Optional[str] = None
    updated_at: Optional[str] = None
    author_username: Optional[str] = None
    author_uid: Optional[str] = None
    author_avatar_seed: Optional[str] = None
    is_local_author: bool = False


class IssueCommentOut(_Out):
    id: int = 0
    body: str = ""
    html_url: Optional[str] = None
    created_at: Optional[str] = None
    author_username: Optional[str] = None
    author_uid: Optional[str] = None
    author_avatar_seed: Optional[str] = None
    is_local_author: bool = False


class IssueDetailOut(_Out):
    issue: IssueItemOut
    body: str = ""
    comments: list[IssueCommentOut] = []
    can_comment: bool = False
    viewer_is_admin: bool = False


class IssueJobOut(_Out):
    uid: str = ""
    kind: str = ""
    status: str = ""
    number: Optional[int] = None
    issue_url: Optional[str] = None
    enhanced: bool = False
    error: Optional[str] = None
    created_at: Optional[str] = None
    completed_at: Optional[str] = None


class LeaderboardEntryOut(_Out):
    uid: Optional[str] = None
    username: Optional[str] = None
    avatar_seed: Optional[str] = None
    stars: Optional[int] = None
    level: Optional[int] = None
    rank: Optional[int] = None


class SavedItemOut(_Out):
    target_type: Optional[str] = None
    target_uid: Optional[str] = None
    type_label: Optional[str] = None
    title: Optional[str] = None
    url: Optional[str] = None
    time_ago: Optional[str] = None


class AdminUserOut(UserOut):
    email: Optional[str] = None
    is_active: Optional[bool] = None
    posts_count: Optional[int] = None


class AdminNewsItemOut(_Out):
    article: NewsOut
    time_ago: Optional[str] = None
    synced_at: Optional[str] = None
    grade: Optional[int] = None
    has_image: Optional[bool] = None


# ---------- page envelopes ----------


class FeedOut(_Out):
    posts: list[FeedItemOut] = []
    current_tab: Optional[str] = None
    current_topic: Optional[str] = None
    search: Optional[str] = None
    next_cursor: Optional[str] = None
    total_members: Optional[int] = None
    posts_today: Optional[int] = None
    total_projects: Optional[int] = None
    total_gists: Optional[int] = None
    top_authors: list[UserOut] = []
    daily_topic: Optional[Any] = None


class PostDetailOut(_Out):
    post: PostOut
    author: Optional[UserOut] = None
    is_owner: bool = False
    star_count: int = 0
    my_vote: int = 0
    time_ago: Optional[str] = None
    comments: list[CommentItemOut] = []
    attachments: list[AttachmentOut] = []
    reactions: ReactionsOut = ReactionsOut()
    bookmarked: bool = False
    poll: Optional[PollOut] = None
    comment_count: Optional[int] = None
    related_posts: list[FeedItemOut] = []
    topics: list[str] = []


class ProjectsOut(_Out):
    projects: list[ProjectListItemOut] = []
    current_tab: Optional[str] = None
    search: Optional[str] = None
    project_type: Optional[str] = None
    total_count: Optional[int] = None
    next_cursor: Optional[str] = None
    total_members: Optional[int] = None
    top_authors: list[UserOut] = []


class ProjectDetailOut(_Out):
    project: ProjectOut
    author: Optional[UserOut] = None
    is_owner: bool = False
    star_count: int = 0
    my_vote: int = 0
    time_ago: Optional[str] = None
    comments: list[CommentItemOut] = []
    attachments: list[AttachmentOut] = []
    reactions: ReactionsOut = ReactionsOut()
    bookmarked: bool = False
    platforms: Optional[Any] = None
    is_private: bool = False
    read_only: bool = False
    forked_from: Optional[dict] = None
    fork_count: int = 0
    file_count: int = 0


class GistsOut(_Out):
    gists: list[GistItemOut] = []
    total_count: Optional[int] = None
    next_cursor: Optional[str] = None
    current_language: Optional[str] = None
    search: Optional[str] = None
    languages: list[tuple[str, str]] = []
    gist_language_codes: list[str] = []


class GistDetailOut(_Out):
    gist: GistOut
    author: Optional[UserOut] = None
    is_owner: bool = False
    star_count: int = 0
    my_vote: int = 0
    time_ago: Optional[str] = None
    comments: list[CommentItemOut] = []
    attachments: list[AttachmentOut] = []
    reactions: ReactionsOut = ReactionsOut()
    bookmarked: bool = False


class NewsListOut(_Out):
    articles: list[NewsListItemOut] = []
    next_cursor: Optional[str] = None


class NewsDetailOut(_Out):
    article: NewsOut
    canonical_slug: Optional[str] = None
    image_url: Optional[str] = None
    grade: Optional[int] = None
    time_ago: Optional[str] = None
    comments: list[CommentItemOut] = []
    bookmarked: bool = False


class MediaItemOut(_Out):
    uid: str = ""
    original_filename: Optional[str] = None
    file_size: Optional[int] = None
    mime_type: Optional[str] = None
    url: Optional[str] = None
    thumbnail_url: Optional[str] = None
    has_thumbnail: bool = False
    is_image: bool = False
    is_video: bool = False
    target_type: Optional[str] = None
    target_uid: Optional[str] = None
    target_url: Optional[str] = None
    created_at: Optional[str] = None


class ProfileOut(_Out):
    profile_user: UserOut
    posts: list[FeedItemOut] = []
    badges: list[BadgeOut] = []
    achievements: list[dict] = []
    badge_total: int = 0
    badge_earned: int = 0
    projects: list[ProjectOut] = []
    gists: list[GistItemOut] = []
    current_tab: Optional[str] = None
    posts_count: Optional[int] = None
    is_following: bool = False
    is_owner: bool = False
    can_view_api_key: bool = False
    api_key: Optional[str] = None
    ai_correction_enabled: bool = False
    ai_correction_sync: bool = False
    ai_correction_prompt: Optional[str] = None
    ai_modifier_enabled: bool = False
    ai_modifier_sync: bool = False
    ai_modifier_prompt: Optional[str] = None
    can_manage_customization: bool = False
    cust_disable_global: bool = False
    cust_disable_pagetype: bool = False
    ai_quota: Optional[dict] = None
    correction_usage: Optional[dict] = None
    modifier_usage: Optional[dict] = None
    activities: list[Any] = []
    rank: Optional[int] = None
    heatmap: Optional[Any] = None
    heatmap_months: Optional[Any] = None
    streak: Optional[Any] = None
    people: list[Any] = []
    follow_pagination: Optional[Any] = None
    followers_count: Optional[int] = None
    following_count: Optional[int] = None
    viewer_is_admin: bool = False
    media: list[MediaItemOut] = []
    media_pagination: Optional[Any] = None
    notification_prefs: list[Any] = []


class MessagesOut(_Out):
    conversations: list[ConversationOut] = []
    messages: list[MessageItemOut] = []
    other_user: Optional[UserOut] = None
    current_conversation: Optional[str] = None
    search: Optional[str] = None
    other_online: bool = False
    other_last_seen: Optional[str] = None


class NotificationsOut(_Out):
    notification_groups: list[NotificationGroupOut] = []
    next_cursor: Optional[str] = None


class LeaderboardOut(_Out):
    entries: list[LeaderboardEntryOut] = []
    user_rank: Optional[int] = None
    total_members: Optional[int] = None
    posts_today: Optional[int] = None
    total_projects: Optional[int] = None
    total_gists: Optional[int] = None
    top_authors: list[UserOut] = []
    featured_news: list[NewsOut] = []


class IssuesOut(_Out):
    issues: list[IssueItemOut] = []
    pagination: Optional[Any] = None
    state: str = "open"
    configured: bool = True
    error_message: Optional[str] = None


class SavedOut(_Out):
    items: list[SavedItemOut] = []
    next_cursor: Optional[str] = None


class AdminUsersOut(_Out):
    users: list[AdminUserOut] = []
    pagination: Optional[Any] = None
    admin_section: Optional[str] = None


class AdminNewsOut(_Out):
    articles: list[AdminNewsItemOut] = []
    pagination: Optional[Any] = None
    admin_section: Optional[str] = None


class AdminSettingsOut(_Out):
    settings: dict = {}
    admin_section: Optional[str] = None


class AdminNotificationsOut(_Out):
    notification_defaults: list[Any] = []
    admin_section: Optional[str] = None


class AdminMediaItemOut(MediaItemOut):
    deleted_at: Optional[str] = None
    uploader: Optional[str] = None


class AdminMediaOut(_Out):
    media: list[AdminMediaItemOut] = []
    pagination: Optional[Any] = None
    admin_section: Optional[str] = None


class AdminContainersOut(_Out):
    instances: list = []
    projects: list = []
    boot_languages: list = []
    restart_policies: list = []
    admin_section: Optional[str] = None
    user: Optional[Any] = None


class AdminContainerEditOut(_Out):
    instance: Optional[Any] = None
    project: Optional[dict] = None
    run_as_user: Optional[dict] = None
    boot_languages: list = []
    restart_policies: list = []
    admin_section: Optional[str] = None
    user: Optional[Any] = None


class AdminContainerInstanceOut(_Out):
    instance: Optional[Any] = None
    project: Optional[dict] = None
    project_slug: Optional[str] = None
    events: list = []
    schedules: list = []
    stats: Optional[Any] = None
    runtime: Optional[Any] = None
    admin_section: Optional[str] = None
    user: Optional[Any] = None


class BotFrameOut(_Out):
    slot: int = 0
    username: str = ""
    persona: str = ""
    action: str = ""
    status: str = ""
    url: str = ""
    label: str = ""
    captured_at: int = 0
    age_seconds: int = 0
    has_image: bool = False
    active: bool = False
    frame_url: str = ""


class AdminBotsOut(_Out):
    frames: list[BotFrameOut] = []
    enabled: bool = False
    service_status: str = ""
    admin_section: Optional[str] = None
    user: Optional[Any] = None


class TrashItemOut(_Out):
    uid: str
    table: str
    label: Optional[str] = None
    deleted_at: Optional[str] = None
    deleted_by: Optional[str] = None
    target_url: Optional[str] = None


class AdminTrashOut(_Out):
    items: list[TrashItemOut] = []
    table: str = "posts"
    tables: list[dict] = []
    pagination: Optional[Any] = None
    admin_section: Optional[str] = None


class GatewayUsageOut(_Out):
    window_hours: int = 48
    generated_at: Optional[str] = None
    requests: int = 0
    volume: dict = {}
    tokens: dict = {}
    latency: dict = {}
    errors: dict = {}
    cost: dict = {}
    behavior: dict = {}
    hourly: list = []
    notes: dict = {}
    admin_section: Optional[str] = None


class UserAiUsageOut(_Out):
    owner_id: str = ""
    window_hours: int = 24
    generated_at: Optional[str] = None
    requests: int = 0
    success: int = 0
    failed: int = 0
    success_pct: float = 0.0
    error_pct: float = 0.0
    tokens: dict = {}
    cost: dict = {}
    latency: dict = {}
    first_used: Optional[str] = None
    last_used: Optional[str] = None
    by_model: list = []
    by_backend: list = []
    hourly: list = []
    notes: dict = {}


# ---------- auth pages ----------


class AuthPageOut(_Out):
    page: str = ""
    next_url: Optional[str] = None
    registration_closed: Optional[bool] = None
    sent: Optional[bool] = None
    token: Optional[str] = None
    errors: list = []


class LandingArticleOut(_Out):
    uid: str = ""
    slug: str = ""
    title: Optional[str] = None
    description: Optional[str] = None
    url: Optional[str] = None
    source_name: Optional[str] = None
    grade: int = 0
    synced_at: Optional[str] = None
    time_ago: Optional[str] = None
    image_url: Optional[str] = None


class LandingPostOut(_Out):
    post: Optional[Any] = None
    author: Optional[UserOut] = None
    time_ago: Optional[str] = None
    comment_count: int = 0
    stars: int = 0
    slug: str = ""


class LandingOut(_Out):
    is_authenticated: bool = False
    user_post_count: int = 0
    user_stars: int = 0
    landing_articles: list[LandingArticleOut] = []
    landing_posts: list[LandingPostOut] = []


class DeviiPageOut(_Out):
    page_title: Optional[str] = None
    meta_description: Optional[str] = None
    meta_robots: Optional[str] = None
    canonical_url: Optional[str] = None
    og_title: Optional[str] = None
    og_description: Optional[str] = None
    og_image: Optional[str] = None
    og_type: Optional[str] = None
    breadcrumbs: list = []
    page_schema: Optional[Any] = None
    prev_url: Optional[str] = None
    next_url: Optional[str] = None
    user: Optional[Any] = None


CommentItemOut.model_rebuild()


# ---------- audit log ----------


class AuditLinkOut(_Out):
    uid: str = ""
    relation: str = ""
    object_type: str = ""
    object_uid: str = ""
    object_label: Optional[str] = None
    created_at: Optional[str] = None


class AuditEntryOut(_Out):
    uid: str = ""
    created_at: Optional[str] = None
    event_key: str = ""
    category: str = ""
    actor_kind: str = ""
    actor_uid: Optional[str] = None
    actor_username: Optional[str] = None
    actor_role: str = ""
    origin: str = ""
    via_agent: int = 0
    request_method: Optional[str] = None
    request_path: Optional[str] = None
    actor_ip: Optional[str] = None
    user_agent: Optional[str] = None
    target_type: Optional[str] = None
    target_uid: Optional[str] = None
    target_label: Optional[str] = None
    old_value: Optional[str] = None
    new_value: Optional[str] = None
    summary: Optional[str] = None
    metadata: Optional[Any] = None
    result: str = "success"


class AuditLogOut(_Out):
    entries: list[AuditEntryOut] = []
    pagination: Optional[dict] = None
    pagination_query: Optional[str] = None
    filters: dict = {}
    options: dict = {}
    admin_section: Optional[str] = None


class AuditEventOut(_Out):
    event: Optional[AuditEntryOut] = None
    links: list[AuditLinkOut] = []
    admin_section: Optional[str] = None


# ---------- database API ----------


class DbTableOut(_Out):
    name: str = ""
    row_count: int = 0
    soft_delete: bool = False


class DbTableListOut(_Out):
    tables: list[DbTableOut] = []
    count: int = 0


class DbColumnOut(_Out):
    name: str = ""
    type: str = ""


class DbSchemaOut(_Out):
    table: str = ""
    columns: list[DbColumnOut] = []
    soft_delete: bool = False
    row_count: int = 0


class DbRowsOut(_Out):
    table: str = ""
    rows: list[dict] = []
    count: int = 0
    next_cursor: Optional[Any] = None


class DbRowOut(_Out):
    table: str = ""
    row: Optional[dict] = None


class DbMutationOut(_Out):
    table: str = ""
    ok: bool = True
    mode: Optional[str] = None
    row: Optional[dict] = None


class DbQueryOut(_Out):
    sql: str = ""
    valid: bool = False
    statement_type: str = ""
    rows: list[dict] = []
    row_count: int = 0
    truncated: bool = False
    suspicious: list[str] = []
    error: Optional[str] = None


class DbQueryJobOut(_Out):
    uid: str = ""
    kind: str = ""
    status: str = ""
    ws_url: Optional[str] = None
    result_url: Optional[str] = None
    row_count: Optional[int] = None
    truncated: Optional[bool] = None
    error: Optional[str] = None
    created_at: Optional[str] = None
    completed_at: Optional[str] = None


class NlQueryOut(_Out):
    question: str = ""
    table: str = ""
    sql: str = ""
    valid: bool = False
    attempts: int = 0
    dialect: str = "sqlite"
    applied_soft_delete: bool = False
    suspicious: list[str] = []
    tables: list[str] = []
    executed: bool = False
    rows: list[dict] = []
    row_count: int = 0
    truncated: bool = False
    error: Optional[str] = None
