# mq-http standard library
# HTTP response builders, request helpers, and router utilities
#
# Usage:
# mq-http my_handler.mq -L ./lib
# Then in my_handler.mq: include "http"

# ---------------------------------------------------------------------------
# Response builders
# ---------------------------------------------------------------------------

def ok(body):
  {"status": 200, "body": body}
end

def created(body):
  {"status": 201, "body": body}
end

def no_content():
  {"status": 204, "body": ""}
end

def bad_request(msg):
  {"status": 400, "body": msg}
end

def not_found():
  {"status": 404, "body": "Not Found"}
end

def method_not_allowed():
  {"status": 405, "body": "Method Not Allowed"}
end

def internal_error(msg):
  {"status": 500, "body": msg}
end

def unauthorized(msg):
  {"status": 401, "body": msg}
end

def forbidden(msg):
  {"status": 403, "body": msg}
end

# ---------------------------------------------------------------------------
# Redirection builders
# ---------------------------------------------------------------------------

def redirect(url):
  {"status": 302, "headers": {"location": url}}
end

def redirect_permanent(url):
  {"status": 301, "headers": {"location": url}}
end

def see_other(url):
  {"status": 303, "headers": {"location": url}}
end

# ---------------------------------------------------------------------------
# JSON response builders
# ---------------------------------------------------------------------------

def json_response(status, body):
  {"status": status, "headers": {"content-type": "application/json"}, "body": body}
end

def json_ok(body):
  json_response(200, body)
end

def json_created(body):
  json_response(201, body)
end

def json_error(status, msg):
  json_response(status, {"error": msg})
end

# Add or update a header in the response object
def with_header(res, name, value):
  var r = res
  | var headers = r["headers"]
  | if (is_none(headers)):
      headers = {}
  | headers[name] = value
  | r["headers"] = headers
  | r
end

# Add or update a cookie in the response object
# value can be a string or a dict with options
def with_cookie(res, name, value):
  var r = res
  | var cookies = r["cookies"]
  | if (is_none(cookies)):
      cookies = {}
  | cookies[name] = value
  | r["cookies"] = cookies
  | r
end

# ---------------------------------------------------------------------------
# HTML response builder
# ---------------------------------------------------------------------------

def html_ok(body):
  {"status": 200, "headers": {"content-type": "text/html; charset=utf-8"}, "body": body}
end

# ---------------------------------------------------------------------------
# Request helpers
# ---------------------------------------------------------------------------

# Get a query parameter; returns default if absent
def query_param(r, name, default):
  let val = get(r["query"], name)
  | if (is_none(val)): default else: val
end

# Get a request header by lowercase name
def req_header(r, name):
  get(r["headers"], name)
end

# Method predicates
def is_get(r): r["method"] == "GET";
def is_post(r): r["method"] == "POST";
def is_put(r): r["method"] == "PUT";
def is_patch(r): r["method"] == "PATCH";
def is_delete(r): r["method"] == "DELETE";

# ---------------------------------------------------------------------------
# Path helpers
# ---------------------------------------------------------------------------

# Exact path match
def path_eq(r, p):
  r["path"] == p
end

# Prefix match
def path_prefix(r, prefix):
  starts_with(r["path"], prefix)
end

# Split path into segments: "/a/b/c" -> ["a", "b", "c"]
def path_segments(r):
  filter(split(r["path"], "/"), fn(s): !is_empty(s);)
end

# ---------------------------------------------------------------------------
# Route matchers
# ---------------------------------------------------------------------------

# Match method + exact path; return handler(r) or None
def route(r, method, path, handler):
  if (r["method"] == method && r["path"] == path):
    handler(r)
  else:
    None
end

# Match method + path prefix; return handler(r) or None
def route_prefix(r, method, prefix, handler):
  if (r["method"] == method && starts_with(r["path"], prefix)):
    handler(r)
  else:
    None
end

# Shorthand per-method helpers
def get_route(r, path, handler): route(r, "GET", path, handler);
def post_route(r, path, handler): route(r, "POST", path, handler);
def put_route(r, path, handler): route(r, "PUT", path, handler);
def patch_route(r, path, handler): route(r, "PATCH", path, handler);
def delete_route(r, path, handler): route(r, "DELETE", path, handler);

# ---------------------------------------------------------------------------
# Server-Sent Events
# ---------------------------------------------------------------------------

# Return a Server-Sent Events stream response.
# events: array of sse_event() / sse_event_named() dicts
def sse(events):
  {"sse": events}
end

# Build a plain data event: {data: value}
def sse_event(data):
  {"data": data}
end

# Build a named event with an event type: {event: name, data: value}
def sse_event_named(event, data):
  {"event": event, "data": data}
end

# ---------------------------------------------------------------------------
# Dispatch
# ---------------------------------------------------------------------------

# Try each handler in order; return the first non-None result.
# Falls back to not_found() when no route matches.
#
# Example:
# dispatch(req, [
# fn(r): get_route(r,  "/",      fn(_): ok("hello"););,
# fn(r): post_route(r, "/echo",  fn(r): ok(r["body"]););,
# ])
def dispatch(r, handlers):
  var result = None
  | var i = 0
  | while (i < len(handlers) && is_none(result)):
      result = handlers[i](r)
      | i += 1
    end
  | if (is_none(result)): not_found() else: result
end

# ---------------------------------------------------------------------------
# Templates
# ---------------------------------------------------------------------------

# Escape HTML entities in a string
def escape_html(s):
  to_string(s)
  | replace("&", "&amp;")
  | replace("<", "&lt;")
  | replace(">", "&gt;")
  | replace("\"", "&quot;")
  | replace("'", "&#39;")
end

# Simple, secure template engine.
# Replaces {{key}} with HTML-escaped value from data,
# and {{{key}}} with raw value from data.
# One-pass replacement to prevent template injection.
def render(template, data):
  let ks = keys(data)
  | var i = 0
  | var result = template
  | var placeholders = {}
  | while (i < len(ks)):
      let k = ks[i]
      | let v = data[k]
      | let escaped_v = escape_html(v)
      | let raw_v = to_string(v)
      | let p_esc = "\x00ESC" + to_string(i) + "\x00"
      | let p_raw = "\x00RAW" + to_string(i) + "\x00"
      | result = replace(result, "{{{" + k + "}}}", p_raw)
      | result = replace(result, "{{" + k + "}}", p_esc)
      | placeholders[p_esc] = escaped_v
      | placeholders[p_raw] = raw_v
      | i += 1
    end
  | var p_ks = keys(placeholders)
  | var j = 0
  | while (j < len(p_ks)):
      let pk = p_ks[j]
      | result = replace(result, pk, placeholders[pk])
      | j += 1
    end
  | result
end

