// 33_graphql_api.ruchy - Building GraphQL APIs

import std::graphql
import std::http

fn main() {
    println("=== GraphQL API Development ===\n")

    // Define GraphQL schema
    println("=== Schema Definition ===")

    let schema = graphql::schema! {
        type Query {
            user(id: ID!): User
            users(limit: Int = 10): [User!]!
            post(id: ID!): Post
            posts(authorId: ID, limit: Int = 20): [Post!]!
            search(query: String!): SearchResult!
        }

        type Mutation {
            createUser(input: CreateUserInput!): User!
            updateUser(id: ID!, input: UpdateUserInput!): User!
            deleteUser(id: ID!): Boolean!
            createPost(input: CreatePostInput!): Post!
            likePost(postId: ID!): Post!
        }

        type Subscription {
            userCreated: User!
            postCreated(authorId: ID): Post!
            commentAdded(postId: ID!): Comment!
        }

        type User {
            id: ID!
            username: String!
            email: String!
            name: String
            bio: String
            avatar: String
            posts: [Post!]!
            followers: [User!]!
            following: [User!]!
            createdAt: DateTime!
            updatedAt: DateTime!
        }

        type Post {
            id: ID!
            title: String!
            content: String!
            author: User!
            tags: [String!]!
            likes: Int!
            comments: [Comment!]!
            published: Boolean!
            createdAt: DateTime!
            updatedAt: DateTime!
        }

        type Comment {
            id: ID!
            content: String!
            author: User!
            post: Post!
            createdAt: DateTime!
        }

        union SearchResult = User | Post | Comment

        input CreateUserInput {
            username: String!
            email: String!
            name: String
            bio: String
        }

        input UpdateUserInput {
            name: String
            bio: String
            avatar: String
        }

        input CreatePostInput {
            title: String!
            content: String!
            tags: [String!]
            published: Boolean = false
        }

        scalar DateTime
    }

    // Resolvers
    println("\n=== Resolvers ===")

    struct QueryResolver {}

    impl QueryResolver {
        fn user(self, ctx, args) {
            let id = args.id
            // Fetch from database
            db::find_user(id)
        }

        fn users(self, ctx, args) {
            let limit = args.limit || 10
            db::list_users(limit)
        }

        fn post(self, ctx, args) {
            let id = args.id
            db::find_post(id)
        }

        fn posts(self, ctx, args) {
            let author_id = args.author_id
            let limit = args.limit || 20

            if author_id {
                db::find_posts_by_author(author_id, limit)
            } else {
                db::list_posts(limit)
            }
        }

        fn search(self, ctx, args) {
            let query = args.query
            // Implement search logic
            let results = []

            // Search users
            let users = db::search_users(query)
            results.extend(users)

            // Search posts
            let posts = db::search_posts(query)
            results.extend(posts)

            // Search comments
            let comments = db::search_comments(query)
            results.extend(comments)

            results
        }
    }

    struct MutationResolver {}

    impl MutationResolver {
        fn create_user(self, ctx, args) {
            let input = args.input

            // Validate input
            validate_email(input.email)?
            validate_username(input.username)?

            // Check uniqueness
            if db::user_exists_by_email(input.email) {
                throw graphql::Error("Email already exists")
            }

            // Create user
            let user = User {
                id: generate_id(),
                username: input.username,
                email: input.email,
                name: input.name,
                bio: input.bio,
                created_at: datetime::now(),
                updated_at: datetime::now()
            }

            db::insert_user(user)
            user
        }

        fn update_user(self, ctx, args) {
            let id = args.id
            let input = args.input

            // Check authorization
            if ctx.user_id != id && !ctx.is_admin {
                throw graphql::Error("Unauthorized")
            }

            let user = db::find_user(id)?

            if input.name {
                user.name = input.name
            }
            if input.bio {
                user.bio = input.bio
            }
            if input.avatar {
                user.avatar = input.avatar
            }
            user.updated_at = datetime::now()

            db::update_user(user)
            user
        }

        fn create_post(self, ctx, args) {
            let input = args.input

            let post = Post {
                id: generate_id(),
                title: input.title,
                content: input.content,
                author_id: ctx.user_id,
                tags: input.tags || [],
                published: input.published,
                likes: 0,
                created_at: datetime::now(),
                updated_at: datetime::now()
            }

            db::insert_post(post)

            // Trigger subscription
            pubsub::publish("post_created", post)

            post
        }

        fn like_post(self, ctx, args) {
            let post_id = args.post_id
            let user_id = ctx.user_id

            // Check if already liked
            if db::has_liked(user_id, post_id) {
                throw graphql::Error("Already liked")
            }

            // Add like
            db::add_like(user_id, post_id)

            // Update post
            let post = db::find_post(post_id)?
            post.likes += 1
            db::update_post(post)

            post
        }
    }

    // Subscriptions
    println("\n=== Subscriptions ===")

    struct SubscriptionResolver {
        pubsub: PubSub
    }

    impl SubscriptionResolver {
        fn user_created(self, ctx) {
            self.pubsub.subscribe("user_created")
        }

        fn post_created(self, ctx, args) {
            let author_id = args.author_id

            self.pubsub.subscribe("post_created")
                .filter(|post| {
                    if author_id {
                        post.author_id == author_id
                    } else {
                        true
                    }
                })
        }

        fn comment_added(self, ctx, args) {
            let post_id = args.post_id
            self.pubsub.subscribe(f"comment_added:{post_id}")
        }
    }

    // Field resolvers
    println("\n=== Field Resolvers ===")

    impl User {
        fn posts(self, ctx, args) {
            db::find_posts_by_author(self.id)
        }

        fn followers(self, ctx, args) {
            db::find_followers(self.id)
        }

        fn following(self, ctx, args) {
            db::find_following(self.id)
        }
    }

    impl Post {
        fn author(self, ctx, args) {
            db::find_user(self.author_id)
        }

        fn comments(self, ctx, args) {
            db::find_comments_by_post(self.id)
        }
    }

    // Context and authentication
    println("\n=== Context & Auth ===")

    fn create_context(request) {
        let token = request.headers.get("Authorization")
            .map(|h| h.replace("Bearer ", ""))

        let user = if token {
            auth::verify_token(token)?
        } else {
            None
        }

        GraphQLContext {
            request: request,
            user_id: user.map(|u| u.id),
            is_admin: user.map(|u| u.is_admin).unwrap_or(false),
            dataloader: DataLoader::new()
        }
    }

    // DataLoader for N+1 query prevention
    println("\n=== DataLoader ===")

    struct DataLoader {
        user_loader: Loader,
        post_loader: Loader
    }

    impl DataLoader {
        fn new() {
            DataLoader {
                user_loader: Loader::new(batch_load_users),
                post_loader: Loader::new(batch_load_posts)
            }
        }
    }

    fn batch_load_users(ids) {
        let users = db::find_users_by_ids(ids)
        let user_map = {}
        for user in users {
            user_map[user.id] = user
        }
        ids.map(|id| user_map.get(id))
    }

    // Query complexity analysis
    println("\n=== Query Complexity ===")

    fn calculate_complexity(query) {
        let ast = graphql::parse(query)
        let mut complexity = 0

        fn visit_field(field, depth) {
            // Base complexity for field
            complexity += 1

            // Additional complexity for lists
            if field.return_type.is_list() {
                let limit = field.args.get("limit") || 10
                complexity += limit
            }

            // Depth multiplier
            complexity *= depth

            // Recursively visit selections
            for selection in field.selections {
                visit_field(selection, depth + 1)
            }
        }

        for field in ast.definitions[0].selections {
            visit_field(field, 1)
        }

        complexity
    }

    // Rate limiting
    println("\n=== Rate Limiting ===")

    struct RateLimiter {
        limits: map = {}
    }

    impl RateLimiter {
        fn check(mut self, key, limit, window) {
            let now = datetime::now().to_unix()
            let window_start = now - window

            if key not in self.limits {
                self.limits[key] = []
            }

            // Remove old entries
            self.limits[key] = self.limits[key]
                .filter(|t| t > window_start)

            // Check limit
            if self.limits[key].len() >= limit {
                throw graphql::Error("Rate limit exceeded")
            }

            // Add current request
            self.limits[key].append(now)
        }
    }

    // GraphQL server setup
    println("\n=== Server Setup ===")

    let server = graphql::Server::new(schema)
        .with_query_resolver(QueryResolver {})
        .with_mutation_resolver(MutationResolver {})
        .with_subscription_resolver(SubscriptionResolver {
            pubsub: PubSub::new()
        })
        .with_context_builder(create_context)
        .with_complexity_limit(1000)
        .with_depth_limit(10)
        .with_introspection(true)  // Enable in dev
        .with_playground(true)     // Enable GraphQL playground

    // Middleware
    server.use_middleware(|ctx, next| {
        // Logging
        log::info("GraphQL request", {
            operation: ctx.operation_name,
            variables: ctx.variables
        })

        let start = perf::now()
        let result = next(ctx)
        let duration = perf::now() - start

        log::info("GraphQL response", {
            duration: duration,
            errors: result.errors
        })

        result
    })

    // Error handling
    server.on_error(|error, ctx| {
        log::error("GraphQL error", {
            error: error,
            user_id: ctx.user_id,
            query: ctx.query
        })

        // Customize error response
        match error.extensions.code {
            "UNAUTHENTICATED" => {
                status: 401,
                message: "Authentication required"
            },
            "FORBIDDEN" => {
                status: 403,
                message: "Access denied"
            },
            _ => {
                status: 500,
                message: "Internal server error"
            }
        }
    })

    // Start server
    server.listen(4000, || {
        println("🚀 GraphQL server running at http://localhost:4000/graphql")
        println("📊 GraphQL playground at http://localhost:4000/playground")
    })

    // Example queries
    println("\n=== Example Queries ===")

    let query = r#"
        query GetUser($id: ID!) {
            user(id: $id) {
                id
                username
                posts(limit: 5) {
                    title
                    likes
                }
            }
        }
    "#

    let variables = { id: "user123" }
    let result = server.execute(query, variables)
    println(f"Result: {result}")
}