// =============================================================================
// Boko — Async Web Framework for Bux (inspired by FastAPI)
// Rewritten with modern Bux: methods on structs, generic StringMap, for-in
// loops, algebraic enums, StringBuilder, and string interpolation.
// =============================================================================
module Boko {

    import Std::Io::{PrintLine, Print, PrintInt};
    import Std::Net::{Net_Create, Net_SetReuse, Net_Bind, Net_Listen, Net_Accept, Net_Send, Net_Recv, Net_Close, Net_LastError};
    import Std::String::{
        String_Len, String_Eq, String_IsNull,
        String_StartsWith, String_Contains,
        String_Find, String_Offset, String_Slice,
        String_SplitCount, String_SplitPart,
        StringBuilder, StringBuilder_New, StringBuilder_Append,
        StringBuilder_AppendInt, StringBuilder_Build, StringBuilder_Free
    };
    import Std::Map::{StringMap, StringMap_New, StringMap_Set, StringMap_Get, StringMap_Has};


    // =============================================================================
    // HTTP Methods
    // =============================================================================
    enum HttpVerb {
        GET,
        POST,
        PUT,
        DELETE,
        PATCH,
        HEAD,
        OPTIONS,
    }

    func HttpVerb_MethodName(verb: HttpVerb) -> String {
        if verb.tag == HttpVerb_GET     { return "GET"; }
        if verb.tag == HttpVerb_POST    { return "POST"; }
        if verb.tag == HttpVerb_PUT     { return "PUT"; }
        if verb.tag == HttpVerb_DELETE  { return "DELETE"; }
        if verb.tag == HttpVerb_PATCH   { return "PATCH"; }
        if verb.tag == HttpVerb_HEAD    { return "HEAD"; }
        if verb.tag == HttpVerb_OPTIONS { return "OPTIONS"; }
        return "?";
    }

    func HttpVerb_Parse(methodStr: String) -> HttpVerb {
        if String_Eq(methodStr, "GET")     { return HttpVerb { tag: HttpVerb_GET }; }
        if String_Eq(methodStr, "POST")    { return HttpVerb { tag: HttpVerb_POST }; }
        if String_Eq(methodStr, "PUT")     { return HttpVerb { tag: HttpVerb_PUT }; }
        if String_Eq(methodStr, "DELETE")  { return HttpVerb { tag: HttpVerb_DELETE }; }
        if String_Eq(methodStr, "PATCH")   { return HttpVerb { tag: HttpVerb_PATCH }; }
        if String_Eq(methodStr, "HEAD")    { return HttpVerb { tag: HttpVerb_HEAD }; }
        if String_Eq(methodStr, "OPTIONS") { return HttpVerb { tag: HttpVerb_OPTIONS }; }
        return HttpVerb { tag: HttpVerb_GET };
    }

    // =============================================================================
    // Request — parsed incoming HTTP request
    // =============================================================================
    struct Request {
        method: HttpVerb,
        path: String,
        body: String,
        headers: StringMap<String>,
        query: StringMap<String>,
        pathParams: StringMap<String>,
    }

    extend Request {
        func GetHeader(self: Request, name: String) -> String {
            if StringMap_Has<String>(&self.headers, name) {
                return StringMap_Get<String>(&self.headers, name);
            }
            return "";
        }

        func GetQuery(self: Request, name: String) -> String {
            if StringMap_Has<String>(&self.query, name) {
                return StringMap_Get<String>(&self.query, name);
            }
            return "";
        }

        func HasQuery(self: Request, name: String) -> bool {
            return StringMap_Has<String>(&self.query, name);
        }

        func GetPathParam(self: Request, name: String) -> String {
            if StringMap_Has<String>(&self.pathParams, name) {
                return StringMap_Get<String>(&self.pathParams, name);
            }
            return "";
        }
    }

    // =============================================================================
    // Response — outgoing HTTP response
    // =============================================================================
    struct Response {
        statusCode: int,
        contentType: String,
        body: String,
        extraHeaders: String,
    }

    // --- Constructors ---
    func Response_New(status: int, contentType: String, body: String) -> Response {
        return Response { statusCode: status, contentType: contentType, body: body, extraHeaders: "" };
    }

    func Response_Ok(body: String) -> Response {
        return Response_New(200, "text/html; charset=utf-8", body);
    }

    func Response_Html(html: String) -> Response {
        return Response_New(200, "text/html; charset=utf-8", html);
    }

    func Response_Json(json: String) -> Response {
        return Response_New(200, "application/json; charset=utf-8", json);
    }

    func Response_Text(text: String) -> Response {
        return Response_New(200, "text/plain; charset=utf-8", text);
    }

    func Response_Redirect(url: String) -> Response {
        let sb: StringBuilder = StringBuilder_New();
        StringBuilder_Append(&sb, "Location: ");
        StringBuilder_Append(&sb, url);
        StringBuilder_Append(&sb, "\r\n");
        let headers: String = StringBuilder_Build(&sb);
        StringBuilder_Free(&sb);
        return Response { statusCode: 302, contentType: "", body: "", extraHeaders: headers };
    }

    func Response_NotFound() -> Response {
        return Response_New(404, "application/json; charset=utf-8", "{\"error\":\"not_found\"}");
    }

    func Response_Error(status: int, message: String) -> Response {
        let sb: StringBuilder = StringBuilder_New();
        StringBuilder_Append(&sb, "{\"error\":\"");
        StringBuilder_Append(&sb, message);
        StringBuilder_Append(&sb, "\"}");
    let body: String = StringBuilder_Build(&sb);
    StringBuilder_Free(&sb);
    return Response_New(status, "application/json; charset=utf-8", body);
}

func Response_NoContent() -> Response {
    return Response_New(204, "", "");
}

// --- Instance helpers ---
extend Response {
    func WithHeader(self: Response, key: String, value: String) -> Response {
        let sb: StringBuilder = StringBuilder_New();
        StringBuilder_Append(&sb, self.extraHeaders);
        StringBuilder_Append(&sb, key);
        StringBuilder_Append(&sb, ": ");
        StringBuilder_Append(&sb, value);
        StringBuilder_Append(&sb, "\r\n");
        self.extraHeaders = StringBuilder_Build(&sb);
        StringBuilder_Free(&sb);
        return self;
    }
}

// =============================================================================
// Status phrase lookup
// =============================================================================
func StatusPhrase(code: int) -> String {
    if code == 200 { return "OK"; }
    if code == 201 { return "Created"; }
    if code == 204 { return "No Content"; }
    if code == 301 { return "Moved Permanently"; }
    if code == 302 { return "Found"; }
    if code == 400 { return "Bad Request"; }
    if code == 401 { return "Unauthorized"; }
    if code == 403 { return "Forbidden"; }
    if code == 404 { return "Not Found"; }
    if code == 500 { return "Internal Server Error"; }
    if code == 502 { return "Bad Gateway"; }
    if code == 503 { return "Service Unavailable"; }
    return "OK";
}

// =============================================================================
// Build HTTP response string from Response struct
// =============================================================================
func Response_Build(resp: Response) -> String {
    let sb: StringBuilder = StringBuilder_New();

    // Status line
    StringBuilder_Append(&sb, "HTTP/1.1 ");
    StringBuilder_AppendInt(&sb, resp.statusCode as int64);
    StringBuilder_Append(&sb, " ");
    StringBuilder_Append(&sb, StatusPhrase(resp.statusCode));
    StringBuilder_Append(&sb, "\r\n");

    // Server
    StringBuilder_Append(&sb, "Server: Boko/0.2.0 (Bux)\r\n");

    // Extra headers
    if String_Len(resp.extraHeaders) > 0 {
        StringBuilder_Append(&sb, resp.extraHeaders);
    }

    // Content-Type
    if String_Len(resp.contentType) > 0 {
        StringBuilder_Append(&sb, "Content-Type: ");
        StringBuilder_Append(&sb, resp.contentType);
        StringBuilder_Append(&sb, "\r\n");
    }

    // Content-Length
    let bodyLen: uint = String_Len(resp.body);
    StringBuilder_Append(&sb, "Content-Length: ");
    StringBuilder_AppendInt(&sb, bodyLen as int64);
    StringBuilder_Append(&sb, "\r\n");

    // Connection close
    StringBuilder_Append(&sb, "Connection: close\r\n");
    StringBuilder_Append(&sb, "\r\n");

    if bodyLen > 0 {
        StringBuilder_Append(&sb, resp.body);
    }

    let result: String = StringBuilder_Build(&sb);
    StringBuilder_Free(&sb);
    return result;
}

// =============================================================================
// Parse query string: ?key=val&key2=val2 → generic StringMap
// =============================================================================
func Query_Parse(queryString: String) -> StringMap<String> {
    let map: StringMap<String> = StringMap_New<String>(8);
    if String_Len(queryString) == 0 {
        return map;
    }

    let pairCount: uint = String_SplitCount(queryString, "&");
    for i in 0..pairCount {
        let pair: String = String_SplitPart(queryString, "&", i);
        let eqPos: String = String_Find(pair, "=");
        var key: String = pair;
        var value: String = "";
        if !String_IsNull(eqPos) {
            let keyLen: uint = String_Offset(eqPos, pair);
            key = String_Slice(pair, 0, keyLen);
            let valStart: uint = keyLen + 1;
            let pairLen: uint = String_Len(pair);
            if valStart < pairLen {
                value = String_Slice(pair, valStart, pairLen - valStart);
            }
        }
        StringMap_Set<String>(&map, key, value);
    }
    return map;
}

// =============================================================================
// Parse incoming HTTP request from raw bytes
// =============================================================================
func Request_Parse(raw: String) -> Request {
    var req: Request;
    req.method = HttpVerb { tag: HttpVerb_GET };
    req.path = "/";
    req.body = "";
    req.headers = StringMap_New<String>(8);
    req.query = StringMap_New<String>(4);
    req.pathParams = StringMap_New<String>(4);

    if String_Len(raw) == 0 { return req; }

    // Find header/body boundary
    let boundary: String = String_Find(raw, "\r\n\r\n");
    let rawLen: uint = String_Len(raw);
    var headerLen: uint = rawLen;
    if !String_IsNull(boundary) {
        headerLen = String_Offset(boundary, raw);
        let bodyStart: uint = headerLen + 4;
        if bodyStart < rawLen {
            req.body = String_Slice(raw, bodyStart, rawLen - bodyStart);
        }
    }

    if headerLen == 0 { return req; }
    let headerBlock: String = String_Slice(raw, 0, headerLen);
    let lineCount: uint = String_SplitCount(headerBlock, "\r\n");
    if lineCount == 0 { return req; }

    // Parse request line
    let requestLine: String = String_SplitPart(headerBlock, "\r\n", 0);
    if String_Len(requestLine) > 0 {
        let methodStr: String = String_SplitPart(requestLine, " ", 0);
        req.method = HttpVerb_Parse(methodStr);

        let fullPath: String = String_SplitPart(requestLine, " ", 1);
        let qmark: String = String_Find(fullPath, "?");
        if !String_IsNull(qmark) {
            let pathLen: uint = String_Offset(qmark, fullPath);
            req.path = String_Slice(fullPath, 0, pathLen);
            let qsStart: uint = pathLen + 1;
            let fullLen: uint = String_Len(fullPath);
            if qsStart < fullLen {
                let qs: String = String_Slice(fullPath, qsStart, fullLen - qsStart);
                req.query = Query_Parse(qs);
            }
        } else {
            req.path = fullPath;
        }
    }

    // Parse headers
    for i in 1..lineCount {
        let line: String = String_SplitPart(headerBlock, "\r\n", i);
        let colonPos: String = String_Find(line, ": ");
        if !String_IsNull(colonPos) {
            let keyLen: uint = String_Offset(colonPos, line);
            let key: String = String_Slice(line, 0, keyLen);
            let valStart: uint = keyLen + 2;
            let lineLen: uint = String_Len(line);
            var value: String = "";
            if valStart < lineLen {
                value = String_Slice(line, valStart, lineLen - valStart);
            }
            StringMap_Set<String>(&req.headers, key, value);
        }
    }

    return req;
}

// =============================================================================
// Path pattern matching: /users/{id} against /users/42 → extracts id=42
// =============================================================================
func Path_Match(pattern: String, path: String, req: *Request) -> bool {
    let patParts: uint = String_SplitCount(pattern, "/");
    let pathParts: uint = String_SplitCount(path, "/");

    if patParts != pathParts { return false; }

    req.pathParams = StringMap_New<String>(8);

    for i in 0..patParts {
        let patPart: String = String_SplitPart(pattern, "/", i);
        let pathPart: String = String_SplitPart(path, "/", i);

        if String_StartsWith(patPart, "{") && String_Contains(patPart, "}") {
            let nameLen: uint = String_Len(patPart) - 2;
            let name: String = String_Slice(patPart, 1, nameLen);
            StringMap_Set<String>(&req.pathParams, name, pathPart);
        } else {
            if !String_Eq(patPart, pathPart) { return false; }
        }
    }

    return true;
}

// =============================================================================
// App — server instance
// =============================================================================
struct App {
    port: int,
    threadCount: int,
    serverName: String,
}

func App_New(port: int, threadCount: int) -> App {
    return App { port: port, threadCount: threadCount, serverName: "Boko/0.2.0 (Bux)" };
}

// =============================================================================
// Forward declaration — user must implement this in their module
// =============================================================================
func Boko_Router(req: Request) -> Response;

// =============================================================================
// Handle a single connection: parse → dispatch → respond
// =============================================================================
func App_HandleConnection(clientFd: int) {
    let raw: String = Net_Recv(clientFd, 8192);
    if String_Len(raw) == 0 { return; }

    let req: Request = Request_Parse(raw);
    let resp: Response = Boko_Router(req);

    // Log
    Print(HttpVerb_MethodName(req.method));
    Print(" ");
    Print(req.path);
    Print("  →  ");
    PrintInt(resp.statusCode);
    PrintLine("");

    // Send
    let respStr: String = Response_Build(resp);
    Net_Send(clientFd, respStr);
}

// =============================================================================
// Worker thread: accept loop
// =============================================================================
func App_Worker(serverFd: int) {
    while true {
        let clientFd: int = Net_Accept(serverFd);
        if clientFd < 0 { continue; }
        App_HandleConnection(clientFd);
        Net_Close(clientFd);
    }
}

// =============================================================================
// App_Run: start the server (blocking)
// =============================================================================
func App_Run(app: *App) {
    PrintLine("╔══════════════════════════════════════╗");
    PrintLine("║  Boko Framework v0.2.0               ║");
    PrintLine("║  Async web framework for Bux         ║");
    PrintLine("╚══════════════════════════════════════════════════════╝");
    PrintLine("");

    let fd: int = Net_Create();
    if fd < 0 {
        PrintLine("FATAL: socket() failed");
        return;
    }

    Net_SetReuse(fd);

    if !Net_Bind(fd, "0.0.0.0", app.port) {
        Print("FATAL: bind(:");
        PrintInt(app.port);
        Print(") failed: ");
        PrintLine(Net_LastError());
        Net_Close(fd);
        return;
    }

    if !Net_Listen(fd, 128) {
        PrintLine("FATAL: listen() failed");
        Net_Close(fd);
        return;
    }

    Print("✓ Boko running on http://0.0.0.0:");
    PrintInt(app.port);
    PrintLine("");
    Print("✓ Workers: ");
    PrintInt(app.threadCount);
    PrintLine("");
    PrintLine("");

    // Spawn workers
    for i in 0..(app.threadCount - 1) {
        spawn App_Worker(fd);
    }

    // Main thread is the last worker
    App_Worker(fd);
}

} // module Boko
