import path from 'node:path';
import fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import type { Plugin, ViteDevServer } from 'vite';

export type LiveTsxApi = {
  getSource: () => string;
  setSource: (source: string) => void;
};

const ALLOWED_PREFIXES = ['src/app/components/', 'src/app/features/'];

function resolveAppSourceFile(projectRoot: string, relPath: string): string | null {
  const normalized = relPath.replace(/\\/g, '/').replace(/^\/+/, '');
  if (normalized.includes('..') || path.isAbsolute(normalized)) return null;
  if (!ALLOWED_PREFIXES.some((p) => normalized.startsWith(p))) return null;
  if (!/\.(tsx|ts)$/.test(normalized)) return null;
  const abs = path.resolve(projectRoot, normalized);
  const root = path.resolve(projectRoot);
  if (!abs.startsWith(root + path.sep) && abs !== root) return null;
  return abs;
}

function readBody(req: NodeJS.ReadableStream): Promise<string> {
  return new Promise((resolve, reject) => {
    const chunks: Buffer[] = [];
    req.on('data', (c) => chunks.push(c));
    req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
    req.on('error', reject);
  });
}

function bumpLivePreview(server: ViteDevServer | undefined) {
  if (!server) return;
  server.ws.send({
    type: 'custom',
    event: 'playground:live-updated',
    data: { revision: Date.now() },
  });
}

export function liveTsxPlugin(projectRoot: string, initialSource: string): Plugin {
  const liveFile = path.resolve(projectRoot, '.cache/playground-live.tsx');

  fs.mkdirSync(path.dirname(liveFile), { recursive: true });
  fs.writeFileSync(liveFile, initialSource, 'utf8');

  let source = initialSource;
  let server: ViteDevServer | undefined;

  const api: LiveTsxApi = {
    getSource: () => source,
    setSource: (next) => {
      source = next;
      fs.writeFileSync(liveFile, next, 'utf8');
      if (!server) return;
      for (const m of server.moduleGraph.urlToModuleMap.values()) {
        if (m.file === liveFile) server.moduleGraph.invalidateModule(m);
      }
      const byId = server.moduleGraph.getModuleById(liveFile);
      if (byId) server.moduleGraph.invalidateModule(byId);
      bumpLivePreview(server);
    },
  };

  return {
    name: 'playground-live-tsx',
    configureServer(devServer) {
      server = devServer;
      (devServer as ViteDevServer & { playgroundLive?: LiveTsxApi }).playgroundLive = api;

      devServer.middlewares.use('/__playground/live', (req, res, next) => {
        if (req.method === 'GET') {
          res.setHeader('Content-Type', 'text/plain; charset=utf-8');
          res.end(source);
          return;
        }
        if (req.method === 'POST') {
          void readBody(req).then((body) => {
            api.setSource(body);
            res.statusCode = 204;
            res.end();
          });
          return;
        }
        next();
      });

      // Read/write real Cinny component sources (strings, markup, etc.).
      devServer.middlewares.use('/__playground/source', (req, res, next) => {
        const url = new URL(req.url ?? '', 'http://playground.local');
        const rel = url.searchParams.get('path') ?? '';
        const abs = resolveAppSourceFile(projectRoot, rel);
        if (!abs) {
          res.statusCode = 400;
          res.end('Invalid or disallowed path');
          return;
        }

        if (req.method === 'GET') {
          if (!fs.existsSync(abs)) {
            res.statusCode = 404;
            res.end('Not found');
            return;
          }
          res.setHeader('Content-Type', 'text/plain; charset=utf-8');
          res.end(fs.readFileSync(abs, 'utf8'));
          return;
        }

        if (req.method === 'POST') {
          void readBody(req).then((body) => {
            fs.mkdirSync(path.dirname(abs), { recursive: true });
            fs.writeFileSync(abs, body, 'utf8');
            const mod =
              server?.moduleGraph.getModuleById(abs) ??
              [...(server?.moduleGraph.urlToModuleMap.values() ?? [])].find((m) => m.file === abs);
            if (mod && server) server.moduleGraph.invalidateModule(mod);
            bumpLivePreview(server);
            res.statusCode = 204;
            res.end();
          });
          return;
        }

        next();
      });
    },
    resolveId(id) {
      const clean = id.split('?')[0];
      if (
        clean === '/@playground/live.tsx' ||
        clean === 'virtual:playground-live' ||
        clean.endsWith('/.cache/playground-live.tsx')
      ) {
        return liveFile;
      }
      return undefined;
    },
  };
}

export function readDefaultLiveSource(projectRoot: string): string {
  return fs.readFileSync(path.join(projectRoot, 'src/playground/defaultLive.tsx'), 'utf8');
}

export function projectRootDir(): string {
  return path.dirname(fileURLToPath(import.meta.url));
}
