/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable @typescript-eslint/no-unsafe-argument */

import fs from 'fs';

// TODO: replace with fetch of live spec
const DISCORD_SPEC = {};

function schemaRefToName(ref) {
  if (!ref) return '';
  return `Discord${ref?.substring(ref.lastIndexOf('/') + 1)}`;
}

function snakeToPascalCase(str) {
  if (!str.includes('_')) return str;

  let result = '';
  for (let i = 0, len = str.length; i < len; ++i) {
    if (str[i] === '_') {
      result += str[++i].toUpperCase();

      continue;
    }

    result += str[i];
  }

  return result;
}

function generateFromSpec() {
  const finalTypings = [];

  for (const [key, schema] of Object.entries(DISCORD_SPEC.components.schemas)) {
    const interfacey = [`export interface Discord${key} {`];

    if (schema.properties) {
      for (const [name, property] of Object.entries(schema.properties)) {
        const type = Array.isArray(property.type) ? property.type : property.type ? [property.type] : undefined;
        let ref = property.$ref && schemaRefToName(property.$ref);
        let allOf = property.allOf?.find((a) => a.$ref);
        if (allOf) {
          allOf = schemaRefToName(allOf.$ref);
        }

        const isArray = property.type === 'array';
        if (isArray) {
          if (property.items.$ref) ref = schemaRefToName(property.items.$ref);
          else if (Array.isArray(property.oneOf)) {
            ref = property.oneOf.map((o) => schemaRefToName(o.$ref)).join(' | ');
          }
        }

        if (property.description) {
          interfacey.push(`    /** ${property.description} */`);
        }

        let cleanType =
          ref ??
          allOf ??
          type
            ?.map((type) =>
              type === 'integer'
                ? 'number'
                : type === 'array'
                  ? property.items.$ref
                    ? `${schemaRefToName(property.items.$ref)}[]`
                    : Array.isArray(property.items.oneOf)
                      ? `${property.items.oneOf.map((o) => `${schemaRefToName(o.$ref)}[]`).join(' | ')}`
                      : `unknown[]`
                  : type,
            )
            .join(' | ') ??
          property.oneOf
            ?.map(
              (o) =>
                (o.type
                  ? o.type === 'integer'
                    ? o.enum
                      ? o.allOf.map((a) => schemaRefToName(a.$ref)).join(' | ')
                      : 'number'
                    : o.type
                  : undefined) ?? schemaRefToName(o.$ref),
            )
            .join(' | ') ??
          'unknown';

        if (cleanType.startsWith('null | ')) cleanType = cleanType.substring(cleanType.indexOf('|') + 2) + ' | null';
        interfacey.push(`    ${name}${schema.required?.includes(name) ? ':' : '?:'} ${cleanType}`);
      }
    } else {
      // No properties so is enum
      if (['integer', 'string'].includes(schema.type) && Array.isArray(schema.oneOf)) {
        const enumm = [`export enum Discord${key} {`];
        for (const possible of schema.oneOf) {
          if (possible.description) enumm.push(`    /** ${possible.description} */`);
          // TODO: camel case the title
          enumm.push(
            `    ${possible.title.includes('-') ? '"' : ''}${snakeToPascalCase(
              `${possible.title[0].toUpperCase()}${possible.title.substring(1).toLowerCase()}`,
            )}${possible.title.includes('-') ? '"' : ''} = ${schema.type === 'string' ? '"' : ''}${possible.const}${
              schema.type === 'string' ? '"' : ''
            },`,
          );
        }
        enumm.push('}');

        finalTypings.push(enumm.join('\n'));
        continue;
      }

      console.log('NO PROPERTIES', key, schema);
      continue;
    }

    interfacey.push('}');

    finalTypings.push(interfacey.join('\n'));
  }

  fs.writeFileSync('./packages/types/src/discord.ts', finalTypings.join('\n\n'), function (err, _result) {
    if (err) throw err;
  });
}

generateFromSpec();
