import { spawn } from "node:child_process";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { dirname, extname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import type { IncomingMessage, ServerResponse } from "node:http";
import { defineConfig, type Plugin } from "vite";

const root = fileURLToPath(new URL(".", import.meta.url));
const toolsSrc = resolve(root, "../src");
const toolsDir = resolve(root, "..");
const workspaceRoot = resolve(root, "../..");
const assetsDir = resolve(workspaceRoot, "assets/characters");
const textureCli = resolve(toolsSrc, "texture/cli.ts");

function readBody(req: IncomingMessage): Promise<Buffer> {
  return new Promise((resolveBody, reject) => {
    const chunks: Buffer[] = [];
    req.on("data", (chunk: Buffer) => chunks.push(chunk));
    req.on("end", () => resolveBody(Buffer.concat(chunks)));
    req.on("error", reject);
  });
}

function sendJson(res: ServerResponse, status: number, body: unknown): void {
  res.statusCode = status;
  res.setHeader("Content-Type", "application/json");
  res.end(JSON.stringify(body));
}

function runTextureCli(payload: unknown): Promise<Record<string, unknown>> {
  return new Promise((resolvePromise, reject) => {
    const child = spawn(
      process.execPath,
      ["--import", "tsx", textureCli],
      {
        cwd: toolsDir,
        stdio: ["pipe", "pipe", "pipe"],
        env: process.env,
      },
    );

    let stdout = "";
    let stderr = "";
    child.stdout.on("data", (c: Buffer) => (stdout += c.toString()));
    child.stderr.on("data", (c: Buffer) => (stderr += c.toString()));
    child.on("error", reject);
    child.on("close", (code) => {
      const line = stdout.trim().split("\n").filter(Boolean).pop() ?? "";
      try {
        const json = JSON.parse(line) as Record<string, unknown>;
        if (code !== 0 && !json["ok"]) {
          reject(new Error(String(json["error"] ?? (stderr || `texture cli exited ${code}`))));
          return;
        }
        resolvePromise(json);
      } catch {
        reject(new Error(stderr || stdout || `texture cli exited ${code}`));
      }
    });

    child.stdin.write(JSON.stringify(payload));
    child.stdin.end();
  });
}

/** Dev-only APIs: save GLB, UV guide / Grok texture, serve character assets. */
function creatorApiPlugin(): Plugin {
  return {
    name: "psx-creator-api",
    configureServer(server) {
      server.middlewares.use(async (req, res, next) => {
        // GET /assets-characters/<file>
        if (req.method === "GET" && req.url?.startsWith("/assets-characters/")) {
          const name = decodeURIComponent(req.url.slice("/assets-characters/".length).split("?")[0] ?? "");
          if (!name || name.includes("..") || name.includes("/")) {
            res.statusCode = 400;
            res.end("bad path");
            return;
          }
          try {
            const bytes = await readFile(resolve(assetsDir, name));
            const ext = extname(name).toLowerCase();
            const mime =
              ext === ".png"
                ? "image/png"
                : ext === ".jpg" || ext === ".jpeg"
                  ? "image/jpeg"
                  : ext === ".glb"
                    ? "model/gltf-binary"
                    : "application/octet-stream";
            res.statusCode = 200;
            res.setHeader("Content-Type", mime);
            res.setHeader("Cache-Control", "no-store");
            res.end(bytes);
          } catch {
            res.statusCode = 404;
            res.end("not found");
          }
          return;
        }

        if (req.method !== "POST") {
          next();
          return;
        }

        try {
          if (req.url === "/api/save") {
            const raw = await readBody(req);
            const payload = JSON.parse(raw.toString("utf8")) as {
              id?: string;
              recipe?: unknown;
              glbBase64?: string;
            };
            const id = payload.id?.replace(/[^a-z0-9_-]/gi, "") ?? "";
            if (!id || !payload.recipe || !payload.glbBase64) {
              sendJson(res, 400, { error: "Expected { id, recipe, glbBase64 }" });
              return;
            }
            await mkdir(assetsDir, { recursive: true });
            const glbPath = resolve(assetsDir, `${id}.glb`);
            const recipePath = resolve(assetsDir, `${id}.recipe.json`);
            const glb = Buffer.from(payload.glbBase64, "base64");
            await writeFile(glbPath, glb);
            await writeFile(recipePath, `${JSON.stringify(payload.recipe, null, 2)}\n`);
            sendJson(res, 200, { ok: true, glb: glbPath, recipe: recipePath, bytes: glb.byteLength });
            return;
          }

          if (req.url === "/api/texture") {
            const raw = await readBody(req);
            const payload = JSON.parse(raw.toString("utf8")) as {
              recipe?: unknown;
              mode?: "guide" | "texture";
              force?: boolean;
              albedoBase64?: string;
            };
            if (!payload.recipe) {
              sendJson(res, 400, { error: "Expected { recipe, mode }" });
              return;
            }
            const result = await runTextureCli({
              recipe: payload.recipe,
              mode: payload.mode === "texture" ? "texture" : "guide",
              force: payload.force === true,
              ...(payload.albedoBase64 ? { albedoBase64: payload.albedoBase64 } : {}),
            });
            sendJson(res, result["ok"] ? 200 : 500, result);
            return;
          }
        } catch (error) {
          const message = error instanceof Error ? error.message : String(error);
          sendJson(res, 500, { error: message });
          return;
        }

        next();
      });
    },
  };
}

export default defineConfig({
  root,
  plugins: [creatorApiPlugin()],
  resolve: {
    alias: {
      "@creator": toolsSrc,
    },
  },
  server: {
    port: 5174,
    open: false,
    fs: {
      allow: [workspaceRoot, dirname(toolsSrc)],
    },
  },
  build: {
    outDir: resolve(root, "dist"),
    emptyOutDir: true,
    target: "es2022",
  },
  optimizeDeps: {
    include: [
      "three",
      "three/examples/jsm/loaders/GLTFLoader.js",
      "three/examples/jsm/controls/OrbitControls.js",
    ],
  },
});
