import manifest from "./source-manifest.json";
import { REPOSITORY_PATH } from "./site-config";

const encoder = new TextEncoder();
const decoder = new TextDecoder();
const pkt = (text: string) => `${(encoder.encode(text).length + 4).toString(16).padStart(4, "0")}${text}`;
const headers = (type: string) => ({ "Content-Type": type, "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" });

/** Read-only smart HTTP, protocol v0. Packs are prepared by Git at build time. */
export async function serveGit(request: Request, assets: { fetch(request: Request): Promise<Response> }): Promise<Response> {
  const url = new URL(request.url);
  const path = url.pathname.slice(REPOSITORY_PATH.length);
  const text = (message: string, status = 200) => new Response(message, { status, headers: headers("text/plain; charset=utf-8") });
  if (!manifest.head || !manifest.packPath) return text("Run npm run source:package to publish this fork's repository.\n", 503);
  if (request.method === "GET" && path === "/HEAD") return text("ref: refs/heads/main\n");
  if (request.method === "GET" && path === "/info/refs") {
    const service = url.searchParams.get("service");
    if (service !== "git-upload-pack") return text("Read-only repository. Use git clone with a current Git client.\n", 403);
    const body = pkt("# service=git-upload-pack\n") + "0000" +
      pkt(`${manifest.head} HEAD\0side-band-64k ofs-delta no-progress symref=HEAD:refs/heads/main\n`) +
      pkt(`${manifest.head} refs/heads/main\n`) + "0000";
    return new Response(body, { headers: headers("application/x-git-upload-pack-advertisement") });
  }
  if (request.method === "POST" && path === "/git-upload-pack") {
    if (!request.headers.get("content-type")?.startsWith("application/x-git-upload-pack-request")) return text("Invalid Git content type.\n", 415);
    const reader = request.body?.getReader();
    if (!reader) return text("Missing Git request.\n", 400);
    const chunks: Uint8Array[] = [];
    let size = 0;
    for (;;) {
      const { value, done } = await reader.read();
      if (done) break;
      size += value.length;
      if (size > 65536) { await reader.cancel(); return text("Git request too large.\n", 413); }
      chunks.push(value);
    }
    const bytes = new Uint8Array(size);
    let offset = 0;
    for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.length; }
    const commands: string[] = [];
    for (let index = 0; index < bytes.length;) {
      const prefix = decoder.decode(bytes.slice(index, index + 4));
      if (!/^[0-9a-f]{4}$/.test(prefix)) return text("Invalid pkt-line.\n", 400);
      const length = Number.parseInt(prefix, 16);
      if (length === 0) { index += 4; continue; }
      if (length < 4 || index + length > bytes.length) return text("Invalid pkt-line length.\n", 400);
      commands.push(decoder.decode(bytes.slice(index + 4, index + length)).trim());
      index += length;
    }
    const wants = commands.filter(command => command.startsWith("want "));
    if (!wants.length || wants.some(command => command.split(" ")[1] !== manifest.head)) return text("Unknown repository revision. Retry git clone.\n", 400);
    const common = commands.filter(command => command.startsWith("have ")).map(command => command.slice(5)).find(id => manifest.commits.includes(id));
    const acknowledgment = common ? `ACK ${common}\n` : "NAK\n";
    if (!commands.includes("done")) return new Response(pkt(acknowledgment), { headers: headers("application/x-git-upload-pack-result") });
    const packResponse = await assets.fetch(new Request(new URL(manifest.packPath, request.url)));
    if (!packResponse.ok) return text("Repository pack unavailable.\n", 503);
    const pack = new Uint8Array(await packResponse.arrayBuffer());
    if (decoder.decode(pack.slice(0, 4)) !== "PACK") return text("Invalid repository pack.\n", 503);
    const sideband = wants[0].split(" ").includes("side-band-64k");
    const body = new ReadableStream<Uint8Array>({ start(controller) {
      controller.enqueue(encoder.encode(pkt(acknowledgment)));
      if (sideband) {
        if (!wants[0].split(" ").includes("no-progress")) controller.enqueue(encoder.encode(pkt("\x02Receiving AMM correlated-pairs source…\n")));
        for (let index = 0; index < pack.length; index += 65515) {
          const chunk = pack.subarray(index, index + 65515);
          controller.enqueue(encoder.encode((chunk.length + 5).toString(16).padStart(4, "0") + "\x01"));
          controller.enqueue(chunk);
        }
        controller.enqueue(encoder.encode("0000"));
      } else controller.enqueue(pack);
      controller.close();
    } });
    return new Response(body, { headers: headers("application/x-git-upload-pack-result") });
  }
  return text("Read-only Git endpoint.\n", path === "/git-receive-pack" ? 403 : 404);
}
