import * as commander from "commander";
import logger from "../helpers/logger";
import {
  generateAttributeTypes,
  generateBaseTypes,
  generateCommentTypes,
  generateCustomObjectTypes,
  generateEntryTypes,
  generateListTypes,
  generateNoteTypes,
  generateObjectTypes,
  generateRecordTypes,
  generateSdkUtilityTypes,
  generateSystemDataTypes,
  generateTaskTypes,
  generateWebhookTypes,
  generateWorkspaceMemberTypes,
} from "../generators/types";
import { ensureDirectoryExists, removeDirectoryRecursive } from "../helpers/fs";
import { fetchAttioSchema } from "../helpers/fetchAttioSchema";
import { join } from "node:path";
import { generateClient } from "../generators/client";
import ora from "ora";
import { generateIndexFile } from "../generators/indexFile";
import { generateNestjsFiles } from "../generators/nestjsFiles";

interface GenerateCommandOptions {
  apiKeyEnvVar: string;
  output: string;
  standardTypes: boolean;
  verbose: boolean;
  nestjs: boolean;
}

type AttioTypeEntry = {
  tsOutputType: string;
  tsInputType?: string; // Optional type when the input type differs from the output type
  isArray?: boolean;
} & (
  | {
      needsImport: false;
    }
  | {
      needsImport: true;
      importFrom: string;
    }
);

const ATTIO_TYPE_MAP: Record<string, AttioTypeEntry> = {
  text: { tsOutputType: "string", isArray: false, needsImport: false },
  boolean: { tsOutputType: "boolean", isArray: false, needsImport: false },
  number: { tsOutputType: "number", isArray: false, needsImport: false },
  date: { tsOutputType: "Date", isArray: false, needsImport: false },
  email: { tsOutputType: "string", isArray: false, needsImport: false },
  phone: { tsOutputType: "string", isArray: false, needsImport: false },
  currency: { tsOutputType: "number", isArray: false, needsImport: false },
  dropdown: { tsOutputType: "string", isArray: false, needsImport: false },
  url: { tsOutputType: "string", isArray: false, needsImport: false },
  person: { tsOutputType: "string", isArray: false, needsImport: false },
  company: { tsOutputType: "string", isArray: false, needsImport: false },
  deal: { tsOutputType: "string", isArray: false, needsImport: false },
  user: { tsOutputType: "string", isArray: false, needsImport: false },
  list_entry: { tsOutputType: "string", isArray: false, needsImport: false },
  object: { tsOutputType: "string", isArray: false, needsImport: false },
  status: { tsOutputType: "string", isArray: false, needsImport: false },
  "personal-name": { tsOutputType: "string", isArray: false, needsImport: false },
  "email-address": { tsOutputType: "string", isArray: false, needsImport: false },
  "phone-number": { tsOutputType: "PhoneNumber", isArray: false, needsImport: true, importFrom: "./systemDataTypes" },
  location: { tsOutputType: "Location", isArray: false, needsImport: true, importFrom: "./systemDataTypes" },
  domain: { tsOutputType: "Domain", tsInputType: "DomainInput", isArray: false, needsImport: true, importFrom: "./systemDataTypes" },
  select: { tsOutputType: "SelectOption", isArray: false, needsImport: true, importFrom: "./systemDataTypes" },
  "record-reference": { tsOutputType: "RecordReference", isArray: false, needsImport: true, importFrom: "./systemDataTypes" },
  "actor-reference": { tsOutputType: "ActorReference", isArray: false, needsImport: true, importFrom: "./systemDataTypes" },
  interaction: { tsOutputType: "Interaction", isArray: false, needsImport: true, importFrom: "./systemDataTypes" },
  timestamp: { tsOutputType: "string", isArray: false, needsImport: false },
};

async function generateCommandHandler(options: GenerateCommandOptions): Promise<void> {
  logger.level = options.verbose ? "debug" : "info";

  const apiKey = process.env[options.apiKeyEnvVar];

  if (!apiKey) {
    logger.error(`API key not found in environment variable: ${options.apiKeyEnvVar}`);
    process.exit(1);
  }

  // Remove existing output directory
  removeDirectoryRecursive(options.output);

  const clientOutputDir = options.output;
  const typesOutputDir = join(options.output, "types");

  const generateLoader = ora("Generating standard types").start();

  // Recreate client and types directory
  ensureDirectoryExists(typesOutputDir);

  // Generate standard types if requested
  if (options.standardTypes) {
    generateBaseTypes(typesOutputDir);
    generateAttributeTypes(typesOutputDir);
    generateCommentTypes(typesOutputDir);
    generateEntryTypes(typesOutputDir);
    generateListTypes(typesOutputDir);
    generateNoteTypes(typesOutputDir);
    generateObjectTypes(typesOutputDir);
    generateRecordTypes(typesOutputDir);
    generateTaskTypes(typesOutputDir);
    generateWebhookTypes(typesOutputDir);
    generateWorkspaceMemberTypes(typesOutputDir);
  }

  const { objects, attributes } = await fetchAttioSchema(apiKey);

  // Generate system data and SDK Utility types
  generateSystemDataTypes(typesOutputDir);
  generateSdkUtilityTypes(objects, options.standardTypes, typesOutputDir);

  generateLoader.text = "Generating custom object types";

  // Generate TypeScript types
  for (const object of objects) {
    const imports: Record<
      string,
      {
        file: string;
        types: Set<string>;
      }
    > = {};

    const attributesWithTSTypes = (attributes[object.api_slug] || []).map((attribute) => {
      const typeInfo = ATTIO_TYPE_MAP[attribute.type] || { tsOutputType: "any", needsImport: false };

      if (typeInfo.needsImport) {
        if (!imports[typeInfo.importFrom]) {
          imports[typeInfo.importFrom] = {
            file: typeInfo.importFrom,
            types: new Set(),
          };
        }
        imports[typeInfo.importFrom].types.add(typeInfo.tsOutputType);

        if (typeInfo.tsInputType) {
          imports[typeInfo.importFrom].types.add(typeInfo.tsInputType);
        }
      }

      return {
        ...attribute,
        tsOutputType: typeInfo.tsOutputType,
        tsInputType: typeInfo.tsInputType || typeInfo.tsOutputType,
        isArray: typeInfo.isArray || attribute.is_multiselect,
      };
    });

    generateCustomObjectTypes(typesOutputDir, { object, attributes: attributesWithTSTypes, imports: Object.values(imports) });
  }

  generateIndexFile(typesOutputDir);

  generateLoader.succeed(`TypeScript types generated successfully in ${typesOutputDir}`);

  generateClient(clientOutputDir, objects, options.standardTypes);

  if (options.nestjs) {
    generateNestjsFiles(options.output);
  }

  generateIndexFile(clientOutputDir);
}

export default function registerGenerateCommand(program: commander.Command): void {
  program
    .command("generate")
    .description("Generate a TypeScript client from your Attio workspace")
    .option("-a, --api-key-env-var <key>", "Environment variable containing the Attio API key", "ATTIO_API_KEY")
    .option("-o, --output <dir>", "Output directory for generated types", "./attio")
    .option("-s, --standard-types", "Generate types for standard Attio entities (lists, notes, tasks, etc.)", false)
    .option("-n, --nestjs", "Generate NestJS-style client", false)
    .option("--verbose", "Enable verbose logging", false)
    .action(generateCommandHandler);
}
