// HTTP Framework for Lux
//
// Provides helpers for building web applications.
//
// Note: Due to current type system limitations, this module provides
// helper functions rather than abstract types. Use the HttpServer effect
// directly for the server loop.

// ============================================================
// Response Builder Functions
// ============================================================

// Create a 200 OK response
fn httpOk(body: String): { status: Int, body: String } =
    { status: 200, body: body }

// Create a 201 Created response
fn httpCreated(body: String): { status: Int, body: String } =
    { status: 201, body: body }

// Create a 204 No Content response
fn httpNoContent(): { status: Int, body: String } =
    { status: 204, body: "" }

// Create a 400 Bad Request response
fn httpBadRequest(body: String): { status: Int, body: String } =
    { status: 400, body: body }

// Create a 401 Unauthorized response
fn httpUnauthorized(body: String): { status: Int, body: String } =
    { status: 401, body: body }

// Create a 403 Forbidden response
fn httpForbidden(body: String): { status: Int, body: String } =
    { status: 403, body: body }

// Create a 404 Not Found response
fn httpNotFound(body: String): { status: Int, body: String } =
    { status: 404, body: body }

// Create a 500 Server Error response
fn httpServerError(body: String): { status: Int, body: String } =
    { status: 500, body: body }

// Create a 429 Too Many Requests response
fn httpTooManyRequests(body: String): { status: Int, body: String } =
    { status: 429, body: body }

// ============================================================
// Path Matching
// ============================================================

// Check if a path matches a pattern with wildcards
// Pattern "/users/:id" matches "/users/42"
fn pathMatches(path: String, pattern: String): Bool = {
    let pathParts = String.split(path, "/")
    let patternParts = String.split(pattern, "/")
    if List.length(pathParts) != List.length(patternParts) then false
    else matchPathParts(pathParts, patternParts)
}

fn matchPathParts(pathParts: List<String>, patternParts: List<String>): Bool = {
    if List.length(pathParts) == 0 then true
    else {
        match List.head(pathParts) {
            None => true,
            Some(pathPart) => {
                match List.head(patternParts) {
                    None => true,
                    Some(patternPart) => {
                        let isMatch = if String.startsWith(patternPart, ":") then true else pathPart == patternPart
                        if isMatch then {
                            let restPath = Option.getOrElse(List.tail(pathParts), [])
                            let restPattern = Option.getOrElse(List.tail(patternParts), [])
                            matchPathParts(restPath, restPattern)
                        } else false
                    }
                }
            }
        }
    }
}

// Extract a path segment by position (0-indexed, skipping leading empty segment)
// For path "/users/42", getPathSegment(path, 1) returns Some("42")
fn getPathSegment(path: String, index: Int): Option<String> = {
    let parts = String.split(path, "/")
    List.get(parts, index + 1)
}

// Extract path parameters from a matched route pattern
// For path "/users/42/posts/5" and pattern "/users/:userId/posts/:postId"
// returns [("userId", "42"), ("postId", "5")]
fn getPathParams(path: String, pattern: String): List<(String, String)> = {
    let pathParts = String.split(path, "/")
    let patternParts = String.split(pattern, "/")
    extractParamsHelper(pathParts, patternParts, [])
}

fn extractParamsHelper(pathParts: List<String>, patternParts: List<String>, acc: List<(String, String)>): List<(String, String)> = {
    if List.length(pathParts) == 0 || List.length(patternParts) == 0 then
        List.reverse(acc)
    else {
        match List.head(pathParts) {
            None => List.reverse(acc),
            Some(p) => match List.head(patternParts) {
                None => List.reverse(acc),
                Some(pat) => {
                    let restPath = Option.getOrElse(List.tail(pathParts), [])
                    let restPattern = Option.getOrElse(List.tail(patternParts), [])
                    if String.startsWith(pat, ":") then {
                        let paramName = String.substring(pat, 1, String.length(pat))
                        let newAcc = List.concat([(paramName, p)], acc)
                        extractParamsHelper(restPath, restPattern, newAcc)
                    } else {
                        extractParamsHelper(restPath, restPattern, acc)
                    }
                }
            }
        }
    }
}

// Get a specific path parameter by name from a list of params
fn getParam(params: List<(String, String)>, name: String): Option<String> = {
    if List.length(params) == 0 then None
    else {
        match List.head(params) {
            None => None,
            Some(pair) => match pair {
                (pName, pValue) =>
                    if pName == name then Some(pValue)
                    else getParam(Option.getOrElse(List.tail(params), []), name)
            }
        }
    }
}

// ============================================================
// JSON Helpers
// ============================================================

// Escape a string for JSON (handles quotes and backslashes)
fn jsonEscape(s: String): String = {
    String.replace(String.replace(s, "\\", "\\\\"), "\"", "\\\"")
}

// Create a JSON string field: "key": "value"
fn jsonString(key: String, value: String): String = {
    "\"" + jsonEscape(key) + "\":\"" + jsonEscape(value) + "\""
}

// Create a JSON number field: "key": 42
fn jsonNumber(key: String, value: Int): String = {
    "\"" + jsonEscape(key) + "\":" + toString(value)
}

// Create a JSON boolean field: "key": true
fn jsonBool(key: String, value: Bool): String = {
    let boolStr = if value then "true" else "false"
    "\"" + jsonEscape(key) + "\":" + boolStr
}

// Wrap content in JSON object braces
fn jsonObject(content: String): String =
    "{" + content + "}"

// Wrap content in JSON array brackets
fn jsonArray(content: String): String =
    "[" + content + "]"

// Join multiple JSON fields/items with commas
fn jsonJoin(items: List<String>): String =
    String.join(items, ",")

// Create a JSON error object: {"error": "message"}
fn jsonErrorMsg(message: String): String =
    jsonObject(jsonString("error", message))

// Create a JSON message object: {"message": "text"}
fn jsonMessage(text: String): String =
    jsonObject(jsonString("message", text))

// ============================================================
// Header Helpers
// ============================================================

// Get a header value from request headers (case-insensitive)
fn getHeader(headers: List<(String, String)>, name: String): Option<String> = {
    let lowerName = String.toLower(name)
    getHeaderHelper(headers, lowerName)
}

fn getHeaderHelper(headers: List<(String, String)>, lowerName: String): Option<String> = {
    if List.length(headers) == 0 then None
    else {
        match List.head(headers) {
            None => None,
            Some(header) => match header {
                (hName, hValue) =>
                    if String.toLower(hName) == lowerName then Some(hValue)
                    else getHeaderHelper(Option.getOrElse(List.tail(headers), []), lowerName)
            }
        }
    }
}

// ============================================================
// Routing Helpers
// ============================================================
//
// Route matching pattern:
//
// fn router(method: String, path: String, body: String): { status: Int, body: String } = {
//     if method == "GET" && path == "/" then httpOk("Home")
//     else if method == "GET" && pathMatches(path, "/users/:id") then {
//         let params = getPathParams(path, "/users/:id")
//         match getParam(params, "id") {
//             Some(id) => httpOk(jsonObject(jsonString("id", id))),
//             None => httpNotFound(jsonErrorMsg("User not found"))
//         }
//     }
//     else if method == "POST" && path == "/users" then
//         httpCreated(body)
//     else
//         httpNotFound(jsonErrorMsg("Not found"))
// }

// Helper to check if request is a GET to a specific path
fn isGet(method: String, path: String, pattern: String): Bool =
    method == "GET" && pathMatches(path, pattern)

// Helper to check if request is a POST to a specific path
fn isPost(method: String, path: String, pattern: String): Bool =
    method == "POST" && pathMatches(path, pattern)

// Helper to check if request is a PUT to a specific path
fn isPut(method: String, path: String, pattern: String): Bool =
    method == "PUT" && pathMatches(path, pattern)

// Helper to check if request is a DELETE to a specific path
fn isDelete(method: String, path: String, pattern: String): Bool =
    method == "DELETE" && pathMatches(path, pattern)

// ============================================================
// Server Loop Patterns
// ============================================================
//
// The server loop should be defined in your main file:
//
// fn serverLoop(): Unit with {HttpServer} = {
//     let req = HttpServer.accept()
//     let resp = router(req.method, req.path, req.body, req.headers)
//     HttpServer.respond(resp.status, resp.body)
//     serverLoop()
// }
//
// For testing with a fixed number of requests:
//
// fn serverLoopN(remaining: Int): Unit with {HttpServer} = {
//     if remaining <= 0 then HttpServer.stop()
//     else {
//         let req = HttpServer.accept()
//         let resp = router(req.method, req.path, req.body, req.headers)
//         HttpServer.respond(resp.status, resp.body)
//         serverLoopN(remaining - 1)
//     }
// }

// ============================================================
// Middleware Pattern
// ============================================================
//
// Middleware wraps handlers to add cross-cutting concerns.
// In Lux, middleware is implemented as function composition.
//
// Example logging middleware:
//
// fn withLogging(
//     handler: fn(String, String, String): { status: Int, body: String }
// ): fn(String, String, String): { status: Int, body: String } with {Console} = {
//     fn(method: String, path: String, body: String): { status: Int, body: String } => {
//         Console.print("[HTTP] " + method + " " + path)
//         let response = handler(method, path, body)
//         Console.print("[HTTP] " + toString(response.status))
//         response
//     }
// }
//
// Usage:
//   let myHandler = withLogging(router)

// ============================================================
// CORS Headers
// ============================================================

// Standard CORS headers for API responses
fn corsHeaders(): List<(String, String)> = [
    ("Access-Control-Allow-Origin", "*"),
    ("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"),
    ("Access-Control-Allow-Headers", "Content-Type, Authorization")
]

// CORS headers for specific origin with credentials
fn corsHeadersWithOrigin(origin: String): List<(String, String)> = [
    ("Access-Control-Allow-Origin", origin),
    ("Access-Control-Allow-Credentials", "true"),
    ("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"),
    ("Access-Control-Allow-Headers", "Content-Type, Authorization")
]

// ============================================================
// Content Type Headers
// ============================================================

fn jsonHeaders(): List<(String, String)> = [
    ("Content-Type", "application/json")
]

fn htmlHeaders(): List<(String, String)> = [
    ("Content-Type", "text/html; charset=utf-8")
]

fn textHeaders(): List<(String, String)> = [
    ("Content-Type", "text/plain; charset=utf-8")
]

// ============================================================
// Query String Parsing
// ============================================================

// Parse query string from path (e.g., "/search?q=hello&page=1")
// Returns the path without query string and a list of parameters
pub fn parseQueryString(fullPath: String): (String, List<(String, String)>) = {
    match String.indexOf(fullPath, "?") {
        None => (fullPath, []),
        Some(idx) => {
            let path = String.substring(fullPath, 0, idx)
            let queryStr = String.substring(fullPath, idx + 1, String.length(fullPath))
            let params = parseQueryParams(queryStr)
            (path, params)
        }
    }
}

fn parseQueryParams(queryStr: String): List<(String, String)> = {
    let pairs = String.split(queryStr, "&")
    List.filterMap(pairs, fn(pair: String): Option<(String, String)> => {
        match String.indexOf(pair, "=") {
            None => None,
            Some(idx) => {
                let key = String.substring(pair, 0, idx)
                let value = String.substring(pair, idx + 1, String.length(pair))
                Some((urlDecode(key), urlDecode(value)))
            }
        }
    })
}

// Get a query parameter by name
pub fn getQueryParam(params: List<(String, String)>, name: String): Option<String> =
    getParam(params, name)

// Simple URL decoding (handles %XX and +)
fn urlDecode(s: String): String = {
    // For now, just replace + with space
    // Full implementation would decode %XX sequences
    String.replace(s, "+", " ")
}

// ============================================================
// Cookie Handling
// ============================================================

// Parse cookies from Cookie header value
pub fn parseCookies(cookieHeader: String): List<(String, String)> = {
    let pairs = String.split(cookieHeader, "; ")
    List.filterMap(pairs, fn(pair: String): Option<(String, String)> => {
        match String.indexOf(pair, "=") {
            None => None,
            Some(idx) => {
                let name = String.trim(String.substring(pair, 0, idx))
                let value = String.trim(String.substring(pair, idx + 1, String.length(pair)))
                Some((name, value))
            }
        }
    })
}

// Get a cookie value by name from request headers
pub fn getCookie(headers: List<(String, String)>, name: String): Option<String> = {
    match getHeader(headers, "Cookie") {
        None => None,
        Some(cookieHeader) => {
            let cookies = parseCookies(cookieHeader)
            getParam(cookies, name)
        }
    }
}

// Create a Set-Cookie header value
pub fn setCookie(name: String, value: String): String =
    name + "=" + value

// Create a Set-Cookie header with options
pub fn setCookieWithOptions(
    name: String,
    value: String,
    maxAge: Option<Int>,
    path: Option<String>,
    httpOnly: Bool,
    secure: Bool
): String = {
    let base = name + "=" + value
    let withMaxAge = match maxAge {
        Some(age) => base + "; Max-Age=" + toString(age),
        None => base
    }
    let withPath = match path {
        Some(p) => withMaxAge + "; Path=" + p,
        None => withMaxAge
    }
    let withHttpOnly = if httpOnly then withPath + "; HttpOnly" else withPath
    if secure then withHttpOnly + "; Secure" else withHttpOnly
}

// ============================================================
// Static File MIME Types
// ============================================================

// Get MIME type for a file extension
pub fn getMimeType(path: String): String = {
    let ext = getFileExtension(path)
    match ext {
        "html" => "text/html; charset=utf-8",
        "htm" => "text/html; charset=utf-8",
        "css" => "text/css; charset=utf-8",
        "js" => "application/javascript; charset=utf-8",
        "json" => "application/json; charset=utf-8",
        "png" => "image/png",
        "jpg" => "image/jpeg",
        "jpeg" => "image/jpeg",
        "gif" => "image/gif",
        "svg" => "image/svg+xml",
        "ico" => "image/x-icon",
        "woff" => "font/woff",
        "woff2" => "font/woff2",
        "ttf" => "font/ttf",
        "pdf" => "application/pdf",
        "xml" => "application/xml",
        "txt" => "text/plain; charset=utf-8",
        "md" => "text/markdown; charset=utf-8",
        _ => "application/octet-stream"
    }
}

fn getFileExtension(path: String): String = {
    match String.lastIndexOf(path, ".") {
        None => "",
        Some(idx) => String.toLower(String.substring(path, idx + 1, String.length(path)))
    }
}

// ============================================================
// Request Type
// ============================================================

// Standard request record for cleaner routing
type Request = {
    method: String,
    path: String,
    query: List<(String, String)>,
    headers: List<(String, String)>,
    body: String
}

// Parse a raw request into a Request record
pub fn parseRequest(
    method: String,
    fullPath: String,
    headers: List<(String, String)>,
    body: String
): Request = {
    let (path, query) = parseQueryString(fullPath)
    { method: method, path: path, query: query, headers: headers, body: body }
}

// ============================================================
// Response Type with Headers
// ============================================================

// Response with headers support
type Response = {
    status: Int,
    headers: List<(String, String)>,
    body: String
}

// Create a response with headers
pub fn httpResponse(status: Int, body: String, headers: List<(String, String)>): Response =
    { status: status, headers: headers, body: body }

// Create a JSON response
pub fn jsonResponse(status: Int, body: String): Response =
    { status: status, headers: jsonHeaders(), body: body }

// Create an HTML response
pub fn htmlResponse(status: Int, body: String): Response =
    { status: status, headers: htmlHeaders(), body: body }

// Create a redirect response
pub fn httpRedirect(location: String): Response =
    { status: 302, headers: [("Location", location)], body: "" }

// Create a permanent redirect response
pub fn httpRedirectPermanent(location: String): Response =
    { status: 301, headers: [("Location", location)], body: "" }

// ============================================================
// Middleware Functions
// ============================================================

// Request type for middleware (simplified)
type Handler = fn(Request): Response

// Logging middleware - logs request method, path, and response status
pub fn withLogging(handler: Handler): Handler with {Console} =
    fn(req: Request): Response => {
        Console.print("[HTTP] " + req.method + " " + req.path)
        let resp = handler(req)
        Console.print("[HTTP] " + toString(resp.status))
        resp
    }

// CORS middleware - adds CORS headers to all responses
pub fn withCors(handler: Handler): Handler =
    fn(req: Request): Response => {
        // Handle preflight
        if req.method == "OPTIONS" then
            { status: 204, headers: corsHeaders(), body: "" }
        else {
            let resp = handler(req)
            { status: resp.status, headers: List.concat(resp.headers, corsHeaders()), body: resp.body }
        }
    }

// JSON content-type middleware - ensures JSON content type on responses
pub fn withJson(handler: Handler): Handler =
    fn(req: Request): Response => {
        let resp = handler(req)
        { status: resp.status, headers: List.concat(resp.headers, jsonHeaders()), body: resp.body }
    }

// Error handling middleware - catches failures and returns 500
pub fn withErrorHandling(handler: Handler): Handler =
    fn(req: Request): Response => {
        // In a real implementation, this would use effect handling
        // For now, just call the handler
        handler(req)
    }

// Rate limiting check (returns remaining requests or 0 if limited)
// Note: Actual rate limiting requires state/effects
pub fn checkRateLimit(key: String, limit: Int, window: Int): Int with {Time} = {
    // Placeholder - real implementation would track requests
    limit
}

// ============================================================
// Router DSL
// ============================================================

// Route definition
type Route = {
    method: String,
    pattern: String,
    handler: fn(Request): Response
}

// Create a GET route
pub fn get(pattern: String, handler: fn(Request): Response): Route =
    { method: "GET", pattern: pattern, handler: handler }

// Create a POST route
pub fn post(pattern: String, handler: fn(Request): Response): Route =
    { method: "POST", pattern: pattern, handler: handler }

// Create a PUT route
pub fn put(pattern: String, handler: fn(Request): Response): Route =
    { method: "PUT", pattern: pattern, handler: handler }

// Create a DELETE route
pub fn delete(pattern: String, handler: fn(Request): Response): Route =
    { method: "DELETE", pattern: pattern, handler: handler }

// Create a PATCH route
pub fn patch(pattern: String, handler: fn(Request): Response): Route =
    { method: "PATCH", pattern: pattern, handler: handler }

// Match request against a list of routes
pub fn matchRoute(req: Request, routes: List<Route>): Option<(Route, List<(String, String)>)> = {
    matchRouteHelper(req, routes)
}

fn matchRouteHelper(req: Request, routes: List<Route>): Option<(Route, List<(String, String)>)> = {
    match List.head(routes) {
        None => None,
        Some(route) => {
            if route.method == req.method && pathMatches(req.path, route.pattern) then {
                let params = getPathParams(req.path, route.pattern)
                Some((route, params))
            } else {
                matchRouteHelper(req, Option.getOrElse(List.tail(routes), []))
            }
        }
    }
}

// Create a router from a list of routes
pub fn router(routes: List<Route>, notFound: fn(Request): Response): Handler =
    fn(req: Request): Response => {
        match matchRoute(req, routes) {
            Some((route, _params)) => route.handler(req),
            None => notFound(req)
        }
    }

// ============================================================
// Example Usage
// ============================================================
//
// fn main(): Unit with {Console, HttpServer} = {
//     // Define routes
//     let routes = [
//         get("/", fn(req: Request): Response => jsonResponse(200, jsonMessage("Welcome!"))),
//         get("/users/:id", fn(req: Request): Response => {
//             let params = getPathParams(req.path, "/users/:id")
//             match getParam(params, "id") {
//                 Some(id) => jsonResponse(200, jsonObject(jsonString("id", id))),
//                 None => jsonResponse(404, jsonErrorMsg("User not found"))
//             }
//         }),
//         post("/users", fn(req: Request): Response => jsonResponse(201, jsonMessage("Created")))
//     ]
//
//     // Create router with middleware
//     let app = withLogging(withCors(router(routes, fn(req: Request): Response =>
//         jsonResponse(404, jsonErrorMsg("Not found"))
//     )))
//
//     // Start server
//     HttpServer.listen(8080)
//     Console.print("Server running on http://localhost:8080")
//
//     // Server loop
//     fn serverLoop(): Unit with {HttpServer} = {
//         let rawReq = HttpServer.accept()
//         let req = parseRequest(rawReq.method, rawReq.path, rawReq.headers, rawReq.body)
//         let resp = app(req)
//         HttpServer.respond(resp.status, resp.body)
//         serverLoop()
//     }
//     serverLoop()
// }
