#!/usr/bin/env node
import * as TJS from 'typescript-json-schema'; // https://github.com/YousefED/typescript-json-schema
import { resolve, relative, join } from 'path';
import fs from 'fs-extra';
import $RefParser, {
  type JSONSchema,
} from '@apidevtools/json-schema-ref-parser';

const { writeFile, readdirSync, remove, ensureDirSync } = fs;

console.log('Start: converting TypeScript types to JSON Schemas');

// To generate JSON schemas in the `distDir` folder, place any types in the `typesFile`, then add them to the `typeNamesToExportToJsonSchema` below.
// https://unpkg.com/@knapsack/types@3.24.12/json-schemas/schemaKnapsackAssetSetsConfig.json
const distDir = join(__dirname, `./json-schemas/`);
ensureDirSync(distDir);
const typesFile = join(
  __dirname,
  './src/app-client-data/types-to-json-schemas.ts',
);

/**
 * Keys: TypeScript name
 * Values: Filenames to export to
 * https://unpkg.com/@knapsack/types/json-schemas/FILENAME.json
 */
const typeNamesToExportToJsonSchema = {
  KnapsackAssetSetsConfig: 'knapsack.asset-sets.json',
  KnapsackNavsConfig: 'knapsack.navs.json',
  KsTemplateSpecSlots: 'KsTemplateSpecSlots.json',
  KnapsackPatternsConfig: 'knapsack.patterns.json',
  KsFiles: 'knapsack.files.json',
};
const fileNamePrefix = 'schema';
const deleteUnusedFilesAfter = false;

const oldFilesToDeleteAfter = new Set(readdirSync(distDir));

const settings: TJS.PartialArgs = {
  required: true,
  titles: false,
  noExtraProps: true,
  ignoreErrors: true,
  typeOfKeyword: false,
  ref: false,
  // aliasRefs: false,
};

// see validationKeywords: https://github.com/YousefED/typescript-json-schema/blob/master/typescript-json-schema.ts#L254

// optionally pass ts compiler options
const compilerOptions: TJS.CompilerOptions = {};

// optionally pass a base path
const basePath = resolve(__dirname, './src');

const program = TJS.getProgramFromFiles([typesFile], compilerOptions, basePath);

const generator = TJS.buildGenerator(program, settings);

class CustomError extends Error {
  type: any;
  constructor(type: any, ...params: any) {
    // Pass remaining arguments (including vendor specific ones) to parent constructor
    super(...params);

    // Maintains proper stack trace for where our error was thrown (only available on V8)
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, CustomError);
    }

    this.name = 'CustomError';
    // Custom debugging information
    this.type = type;
  }
}

async function go() {
  const schemas = await Promise.all(
    Object.keys(typeNamesToExportToJsonSchema).map(async (type) => {
      try {
        const schema = generator?.getSchemaForSymbol(type);
        return {
          type: type as keyof typeof typeNamesToExportToJsonSchema,
          schema,
        };
      } catch (e) {
        throw new CustomError(type, e);
      }
    }),
  ).catch((e) => {
    console.log();
    if (e instanceof CustomError) {
      console.error(`${e.message}
please fix by doing one of the below resolutions:
1) Ensure "${e.type}" is available in the types file at:
${relative(process.cwd(), typesFile)}

2) Remove "${
        e.type
      }" from the "typeNamesToExportToJsonSchema" object in this file:
${relative(process.cwd(), __filename)}
`);
    } else {
      // console.log('b');
    }
    console.log('Failed: converting TypeScript types to JSON Schemas');
    process.exit(1);
  });

  await Promise.all(
    schemas.map(async ({ type, schema }) => {
      const fullSchema = await $RefParser.dereference(schema as JSONSchema);
      const schemaString = JSON.stringify(fullSchema, null, '  ');
      const fileNameBase = `${fileNamePrefix}${type}`;
      const filePath = resolve(distDir, fileNameBase);
      const jsonPath = `${filePath}.json`;
      await Promise.all([
        writeFile(jsonPath, schemaString),
        writeFile(
          resolve(distDir, typeNamesToExportToJsonSchema[type]),
          schemaString,
        ),
      ]);
      console.log(
        `Used typescript type "${type}" to generate JSON Schema at: "${relative(
          __dirname,
          jsonPath,
        )}"`,
      );
      // remove the file we just made from the cleanup list
      oldFilesToDeleteAfter.delete(jsonPath);
    }),
  );

  if (deleteUnusedFilesAfter) {
    await Promise.all(
      [...oldFilesToDeleteAfter]
        .map((f) => resolve(distDir, f))
        .map((f) => remove(f)),
    );
  }

  console.log('Done: converting TypeScript types to JSON Schemas');
}

go();
