import type { CloudflareDeployment } from '../../../config/frontmcp-config.types';
import type { AdapterTemplate } from '../types';

/**
 * Cloudflare Workers adapter - edge deployment on Cloudflare.
 * Compiles to CommonJS and adapts the Express app to Cloudflare's fetch API.
 *
 * @see https://developers.cloudflare.com/workers/
 */
export const cloudflareAdapter: AdapterTemplate = {
  moduleFormat: 'commonjs',

  getEntryTemplate: (mainModulePath: string) => `// Auto-generated Cloudflare Workers entry point
// Generated by: frontmcp build --target cloudflare
process.env.FRONTMCP_SERVERLESS = '1';
process.env.FRONTMCP_DEPLOYMENT_MODE = 'serverless';

require('${mainModulePath}');
const { getServerlessHandlerAsync } = require('@frontmcp/sdk');

let app = null;

async function handleRequest(request) {
  if (!app) {
    app = await getServerlessHandlerAsync();
  }

  // Convert Cloudflare Request to Node.js format
  const url = new URL(request.url);
  const req = {
    method: request.method,
    url: url.pathname + url.search,
    headers: Object.fromEntries(request.headers),
    body: request.body,
  };

  return new Promise((resolve) => {
    const res = {
      statusCode: 200,
      headers: {},
      body: '',
      status(code) { this.statusCode = code; return this; },
      setHeader(key, value) { this.headers[key] = value; },
      json(data) {
        this.headers['Content-Type'] = 'application/json';
        this.body = JSON.stringify(data);
        resolve(new Response(this.body, {
          status: this.statusCode,
          headers: this.headers,
        }));
      },
      send(data) {
        this.body = data;
        resolve(new Response(this.body, {
          status: this.statusCode,
          headers: this.headers,
        }));
      },
      end() {
        resolve(new Response(this.body, {
          status: this.statusCode,
          headers: this.headers,
        }));
      },
    };
    app(req, res);
  });
}

module.exports = {
  async fetch(request, env, ctx) {
    return handleRequest(request);
  }
};
`,

  // #375 — fail the build when the user's @FrontMcp config references
  // Node-only storage providers that can't run on Workers.
  //
  // Round 1 only inspected the runtime-evaluated config object. The reporter's
  // failure case was `sqlite: process.env.REDIS_HOST ? {…} : { sqlite: {…} }` —
  // a ternary that may evaluate to `undefined` at decorator-load time (so the
  // runtime check sees nothing) but still ships the Node-only branch in the
  // bundled worker. Round 2 also inspects `info.keysSeenInSource`, which is
  // collected by walking the @FrontMcp({...}) source expression — that captures
  // the property name regardless of whether its value is a literal, a ternary,
  // or a function call. If `sqlite`/`redis` appear at all, fail loud and tell
  // the user how to gate them at the source level.
  validate: (decoratorConfig, info) => {
    const errors: string[] = [];

    const sqliteIsLiteral =
      decoratorConfig?.['sqlite'] !== undefined &&
      typeof decoratorConfig['sqlite'] === 'object' &&
      decoratorConfig['sqlite'] !== null;
    const redisIsLiteral =
      decoratorConfig?.['redis'] !== undefined &&
      typeof decoratorConfig['redis'] === 'object' &&
      decoratorConfig['redis'] !== null;
    const sqliteSeen = sqliteIsLiteral || !!info?.keysSeenInSource?.includes('sqlite');
    const redisSeen = redisIsLiteral || !!info?.keysSeenInSource?.includes('redis');

    if (sqliteSeen) {
      errors.push(
        'sqlite storage is not supported on --target cloudflare (no fs / native modules on Workers). ' +
          'Even an env-gated `sqlite: process.env.X ? {...} : undefined` still ships the Node-only ' +
          'branch in the worker bundle. Use Cloudflare KV / Durable Objects, or move the sqlite ' +
          'config behind a build-time `define` so the bundler can dead-code-eliminate it.',
      );
    }
    if (redisSeen) {
      errors.push(
        'ioredis-style `redis` storage is not supported on --target cloudflare (no Node net). ' +
          'Even an env-gated `redis: process.env.X ? {...} : undefined` still ships the Node-only ' +
          'branch in the worker bundle. Use Vercel KV / Upstash Redis (HTTP), or move the redis ' +
          'config behind a build-time `define` so the bundler can dead-code-eliminate it.',
      );
    }
    if (errors.length) {
      throw new Error(
        `[--target cloudflare] config incompatible with Cloudflare Workers:\n  - ${errors.join('\n  - ')}`,
      );
    }
  },

  // #374 — always write wrangler.toml from the build output. Skipping when
  // the file already exists left users with a wrangler.toml that pointed at
  // dist/index.js while the build emitted dist/cloudflare/index.js, and
  // wrangler deploy silently failed.
  alwaysWriteConfig: true,

  // #374 round-2 — merge `frontmcp.config.deployments[].wrangler.{name,
  // compatibilityDate}` into the rendered TOML so values declared in the
  // user's config actually reach `wrangler deploy`. Defaults preserved when
  // the field is absent or no deployment was matched.
  getConfig: (_cwd, deployment) => {
    const wrangler = (deployment as CloudflareDeployment | undefined)?.wrangler ?? {};
    const name = wrangler.name ?? 'frontmcp-worker';
    const compatibilityDate = wrangler.compatibilityDate ?? '2024-01-01';
    return `name = "${name}"
main = "dist/cloudflare/index.js"
compatibility_date = "${compatibilityDate}"
`;
  },

  configFileName: 'wrangler.toml',
};
