/**
 * Content Types Generator
 *
 * Fetches content models from the Content API and generates TypeScript
 * type definitions, similar to `supabase gen types typescript`.
 *
 * Usage:
 *   better-i18n content:types --project acme/landing --api-key <key>
 *   better-i18n content:types --output src/content-types.ts
 */

import { writeFileSync } from "node:fs";
import { resolve } from "node:path";

interface ContentTypesOptions {
  project?: string;
  apiKey?: string;
  output?: string;
  dir?: string;
}

interface ContentModelField {
  name: string;
  displayName: string;
  type: string;
  required: boolean;
  localized: boolean;
  enumValues?: { label: string; value: string }[];
  fieldConfig?: { targetModel?: string };
}

interface ContentModel {
  slug: string;
  displayName: string;
  description: string | null;
  kind: string;
  includeBody: boolean;
  fields: ContentModelField[];
}

// ── Field Type Mapping ──────────────────────────────────────────────

export function fieldToTypeScript(field: ContentModelField): string {
  switch (field.type) {
    case "enum":
      if (field.enumValues && field.enumValues.length > 0) {
        const union = field.enumValues
          .map((e) => `"${e.value}"`)
          .join(" | ");
        return field.required ? union : `${union} | null`;
      }
      return field.required ? "string" : "string | null";

    case "relation":
      return field.required
        ? "RelationValue"
        : "RelationValue | null";

    case "number":
    case "boolean":
    case "text":
    case "textarea":
    case "richtext":
    case "date":
    case "datetime":
    case "media":
    case "user_select":
    default:
      return field.required ? "string" : "string | null";
  }
}

// ── Slug → PascalCase ───────────────────────────────────────────────

export function slugToPascal(slug: string): string {
  return slug
    .split(/[-_]/)
    .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
    .join("");
}

// ── Code Generation ─────────────────────────────────────────────────

export function generateTypeScript(models: ContentModel[]): string {
  const lines: string[] = [];
  const hasRelations = models.some((m) =>
    m.fields.some((f) => f.type === "relation"),
  );

  // Header
  lines.push("/**");
  lines.push(
    " * Auto-generated by @better-i18n/cli — do not edit manually.",
  );
  lines.push(
    ` * Generated at: ${new Date().toISOString()}`,
  );
  lines.push(
    ` * Models: ${models.length}`,
  );
  lines.push(" */");
  lines.push("");

  // Imports — runtime createClient + all types for the typed factory
  const typeImports = [
    "ContentEntry",
    "ContentEntryListItem",
    "ClientConfig",
    "PaginatedResponse",
    "ListEntriesOptions",
  ];
  if (hasRelations) typeImports.push("RelationValue");
  lines.push(
    `import { createClient, type ContentQueryBuilder } from "@better-i18n/sdk";`,
  );
  lines.push(
    `import type { ${typeImports.join(", ")} } from "@better-i18n/sdk";`,
  );
  lines.push("");

  // Generate per model
  for (const model of models) {
    const pascal = slugToPascal(model.slug);
    const customFields = model.fields.filter(
      (f) => !["title", "body", "slug"].includes(f.name),
    );

    // JSDoc
    if (model.description) {
      lines.push(`/** ${model.displayName} — ${model.description} */`);
    } else {
      lines.push(`/** Content model: ${model.slug} */`);
    }

    // Custom fields interface
    if (customFields.length > 0) {
      lines.push(
        `export interface ${pascal}Fields extends Record<string, string | null> {`,
      );
      for (const field of customFields) {
        const tsType = fieldToTypeScript(field);
        const comment = field.displayName !== field.name
          ? ` // ${field.displayName}`
          : "";
        lines.push(`  ${field.name}: ${tsType};${comment}`);
      }
      lines.push("}");
    } else {
      lines.push(
        `export type ${pascal}Fields = Record<string, string | null>;`,
      );
    }

    // Convenience aliases
    lines.push(`export type ${pascal} = ContentEntry<${pascal}Fields>;`);
    lines.push(
      `export type ${pascal}ListItem = ContentEntryListItem<${pascal}Fields>;`,
    );
    lines.push("");
  }

  // Model slug union type
  lines.push("/** All content model slugs. */");
  lines.push(
    `export type ContentModelSlug = ${models.map((m) => `"${m.slug}"`).join(" | ")};`,
  );
  lines.push("");

  // Type map for typed client helpers
  lines.push("/** Map from model slug to its custom fields type. */");
  lines.push("export interface ContentTypeMap {");
  for (const model of models) {
    const pascal = slugToPascal(model.slug);
    lines.push(`  "${model.slug}": ${pascal}Fields;`);
  }
  lines.push("}");
  lines.push("");

  // ── Typed Client Interface ──────────────────────────────────────
  lines.push("// ── Typed Client ─────────────────────────────────────────────────");
  lines.push("");
  lines.push("/** Typed content client — model slugs auto-resolve to their field types. */");
  lines.push("export interface TypedContentClient {");

  // from() overloads per model
  for (const model of models) {
    const pascal = slugToPascal(model.slug);
    lines.push(
      `  from(model: "${model.slug}"): ContentQueryBuilder<ContentEntryListItem<${pascal}Fields>>;`,
    );
  }
  lines.push(
    "  from(model: string): ContentQueryBuilder;",
  );
  lines.push("");

  // getEntries() overloads per model
  for (const model of models) {
    const pascal = slugToPascal(model.slug);
    lines.push(
      `  getEntries(model: "${model.slug}", opts?: ListEntriesOptions): Promise<PaginatedResponse<${pascal}ListItem>>;`,
    );
  }
  lines.push(
    "  getEntries(model: string, opts?: ListEntriesOptions): Promise<PaginatedResponse<ContentEntryListItem>>;",
  );
  lines.push("");

  // getEntry() overloads per model
  for (const model of models) {
    const pascal = slugToPascal(model.slug);
    lines.push(
      `  getEntry(model: "${model.slug}", slug: string, opts?: { language?: string }): Promise<${pascal}>;`,
    );
  }
  lines.push(
    "  getEntry(model: string, slug: string, opts?: { language?: string }): Promise<ContentEntry>;",
  );

  lines.push("}");
  lines.push("");

  // createContentClient factory
  lines.push("/**");
  lines.push(" * Create a typed content client.");
  lines.push(" * All model methods are pre-typed — no generics needed.");
  lines.push(" *");
  lines.push(" * @example");
  lines.push(" * ```typescript");
  lines.push(` * const client = createContentClient({ project: "acme/landing", apiKey: "..." });`);
  if (models.length > 0) {
    const first = models[0];
    lines.push(
      ` * const { items } = await client.getEntries("${first.slug}");`,
    );
    lines.push(
      ` * // items[0] is fully typed as ${slugToPascal(first.slug)}ListItem`,
    );
  }
  lines.push(" * ```");
  lines.push(" */");
  lines.push(
    "export function createContentClient(config: ClientConfig): TypedContentClient {",
  );
  lines.push("  return createClient(config) as unknown as TypedContentClient;");
  lines.push("}");
  lines.push("");

  return lines.join("\n");
}

// ── Fetch Models from Content API ───────────────────────────────────

async function fetchModels(
  project: string,
  apiKey: string,
): Promise<ContentModel[]> {
  const [org, proj] = project.split("/");
  if (!org || !proj) {
    throw new Error(
      `Invalid project format: "${project}". Expected "org/project".`,
    );
  }

  const url = `https://content.better-i18n.com/v1/content/${org}/${proj}/models`;
  const response = await fetch(url, {
    headers: {
      "X-Api-Key": apiKey,
      Accept: "application/json",
    },
  });

  if (!response.ok) {
    const text = await response.text();
    throw new Error(
      `Content API error (${response.status}): ${text}`,
    );
  }

  const data = (await response.json()) as { models: ContentModel[] };
  return data.models || [];
}

// ── Command Handler ─────────────────────────────────────────────────

export async function contentTypesCommand(
  options: ContentTypesOptions,
): Promise<void> {
  // Lazy imports — keeps pure functions testable without these deps
  const ora = (await import("ora")).default;
  const { detectProjectContext } = await import("../context/detector.js");
  const { bold, cyan, dim, green } = await import("../utils/colors.js");

  const spinner = ora({ text: "Detecting project...", color: "cyan" }).start();

  // Resolve project from config or CLI flag
  let project = options.project;
  const { resolveAuth } = await import("../utils/auth.js");
  const auth = resolveAuth({ apiKey: options.apiKey });
  const apiKey = auth?.apiKey;

  if (!project) {
    const rootDir = resolve(options.dir || process.cwd());
    const context = await detectProjectContext(rootDir, false);
    if (context) {
      project = `${context.workspaceId}/${context.projectSlug}`;
      spinner.succeed(`Project: ${bold(project)}`);
    } else {
      spinner.fail(
        "No project found. Use --project org/name or create i18n.config.ts",
      );
      process.exit(1);
    }
  } else {
    spinner.succeed(`Project: ${bold(project)}`);
  }

  if (!apiKey) {
    spinner.fail(
      "Not authenticated. Run `better-i18n login` or set BETTER_I18N_API_KEY env var.",
    );
    process.exit(1);
  }

  // Fetch models
  spinner.start("Fetching content models...");

  let models: ContentModel[];
  try {
    models = await fetchModels(project, apiKey);
  } catch (error) {
    spinner.fail(
      `Failed to fetch models: ${error instanceof Error ? error.message : String(error)}`,
    );
    process.exit(1);
  }

  if (models.length === 0) {
    spinner.warn("No content models found in this project.");
    return;
  }

  spinner.succeed(`Found ${models.length} content model(s)`);

  // Generate TypeScript
  const output = generateTypeScript(models);
  const outputPath = resolve(
    options.output || "src/content-types.ts",
  );

  writeFileSync(outputPath, output, "utf-8");

  // Summary
  console.log();
  console.log(
    green(bold("  ✓ Content types generated")),
  );
  console.log(dim(`  Output: ${outputPath}`));
  console.log(
    dim(
      `  Models: ${models.map((m) => cyan(m.slug)).join(", ")}`,
    ),
  );
  console.log();

  // Usage hint
  console.log(dim("  Usage:"));
  console.log(
    dim(
      `    import type { ${slugToPascal(models[0].slug)} } from "./content-types";`,
    ),
  );
  console.log(
    dim(
      `    const { data } = await client.from<${slugToPascal(models[0].slug)}Fields>("${models[0].slug}").execute();`,
    ),
  );
  console.log();
}
