import prettier from "prettier";
import { Project, VariableDeclarationKind, Writers } from "ts-morph";
const RICOS_DOCUMENT_NODE_TYPE_NAME = "RicosDocumentNode";
function jtdToTs(rootTypeName, schema) {
  const project = new Project();
  const sourceFile = project.createSourceFile("MyDynamicName.ts", "", { overwrite: true });
  const rootDefinition = rootTypeName ? { [rootTypeName]: schema } : {};
  const definitions = { ...rootDefinition, ...schema.definitions };
  const ricosNodeTypes = [];
  for (const [key, value] of Object.entries(definitions)) {
    if (value.metadata?.tsRicosNode === true) {
      ricosNodeTypes.push(key);
    }
  }
  for (const [key, value] of Object.entries(definitions)) {
    const { tsType, docs, enums } = getTypeForSchema(value, ricosNodeTypes);
    if (!enums.some((e) => e.name === key)) {
      sourceFile.addTypeAlias({
        name: key,
        isExported: true,
        type: tsType,
        docs: docs ? [docs] : void 0
      });
    }
    enums.forEach((e) => {
      sourceFile.addTypeAlias({
        name: e.name,
        isExported: true,
        type: maybeUnionType(...e.values.map((x) => `'${x}'`))
      });
      sourceFile.addVariableStatement({
        declarationKind: VariableDeclarationKind.Const,
        isExported: true,
        declarations: [
          {
            name: e.name,
            // Use an object literal expression for the initializer
            initializer: (w) => {
              Writers.object(Object.fromEntries(e.values.map((x) => [x, `'${x}'`])))(w);
              w.write(" as const");
            }
          }
        ]
      });
    });
  }
  if (ricosNodeTypes.length > 0) {
    sourceFile.addTypeAlias({
      name: RICOS_DOCUMENT_NODE_TYPE_NAME,
      isExported: true,
      type: maybeUnionType(...ricosNodeTypes),
      docs: ["Union of all Ricos document node types."]
    });
  }
  return prettier.format(sourceFile.getFullText(), {
    parser: "typescript",
    // Add any Prettier options here
    trailingComma: "es5",
    printWidth: 100,
    singleQuote: true,
    arrowParens: "avoid"
  });
}
const getTypeForSchema = (schema, ricosNodeTypes = []) => {
  let docs;
  if (schema.metadata) {
    if (schema.metadata.description) {
      docs = `${schema.metadata?.description}`;
    }
  }
  if (schema.metadata?.tsRicosNodesChildren === true && "elements" in schema) {
    return {
      tsType: (w) => {
        w.write(`${RICOS_DOCUMENT_NODE_TYPE_NAME}[]`);
      },
      docs,
      enums: []
    };
  }
  if ("type" in schema) {
    let tsType;
    switch (schema.type) {
      case "boolean":
        tsType = (writer) => writer.write("boolean");
        break;
      case "string":
      case "timestamp":
        tsType = (writer) => writer.write("string");
        break;
      case "float32":
      case "float64":
      case "int8":
      case "uint8":
      case "int16":
      case "uint16":
      case "int32":
      case "uint32":
        tsType = (writer) => writer.write("number");
        break;
      default:
        tsType = (writer) => writer.write("unknown");
    }
    return { tsType, docs, enums: [] };
  }
  if ("properties" in schema || "optionalProperties" in schema) {
    const { structure, enums } = getStructureForPropertiesForm(schema, ricosNodeTypes);
    return { tsType: Writers.objectType(structure), docs, enums };
  }
  if ("enum" in schema) {
    const emitEnum = schema.metadata?.emitEnum;
    const enums = emitEnum ? [{ name: emitEnum, values: schema.enum }] : [];
    return {
      tsType: emitEnum ? emitEnum : maybeUnionType(...schema.enum.map((x) => `'${x}'`)),
      docs,
      enums
    };
  }
  if ("ref" in schema) {
    if (schema.ref === "Never") {
      return { tsType: "never", docs, enums: [] };
    }
    return { tsType: schema.ref, docs, enums: [] };
  }
  if ("elements" in schema) {
    const { tsType, docs: docs2, enums } = getTypeForSchema(schema.elements, ricosNodeTypes);
    return {
      tsType: (w) => {
        writeWriterOrValue(tsType, w);
        w.write("[]");
      },
      docs: docs2,
      enums
    };
  }
  if ("values" in schema) {
    const { tsType, docs: docs2, enums } = getTypeForSchema(schema.values, ricosNodeTypes);
    return {
      tsType: (w) => {
        w.write("Record<string, ");
        writeWriterOrValue(tsType, w);
        w.write(">");
      },
      docs: docs2,
      enums
    };
  }
  if ("discriminator" in schema) {
    const { discriminator, mapping } = schema;
    if (discriminator === void 0 || mapping === void 0) {
      throw new Error("Discriminator must have both discriminator and mapping");
    }
    const enums = [];
    const emitRootEnum = schema.metadata?.emitEnum;
    if (emitRootEnum) {
      enums.push({ name: emitRootEnum, values: Object.keys(mapping) });
    }
    if (schema.metadata?.refUnion) {
      return {
        tsType: maybeUnionType(
          ...Object.values(schema.metadata.refUnion)
        ),
        docs,
        enums
      };
    }
    const unionMembers = Object.keys(mapping).map((x) => {
      const { structure, enums: e } = getStructureForPropertiesForm(mapping[x], ricosNodeTypes);
      enums.push(...e);
      structure.properties.unshift({ name: discriminator, type: `'${x}'` });
      return Writers.objectType(structure);
    });
    return {
      tsType: maybeUnionType(...unionMembers),
      docs,
      enums
    };
  }
  if (Object.keys(schema).length === 0) {
    return {
      tsType: (writer) => writer.write("any"),
      docs,
      enums: []
    };
  }
  return { tsType: (writer) => writer.write("any"), docs, enums: [] };
};
const getStructureForPropertiesForm = (schema, ricosNodeTypes = []) => {
  const structure = { properties: [] };
  const enums = [];
  if ("properties" in schema) {
    for (const [key, value] of Object.entries(schema.properties)) {
      const { tsType, docs, enums: enumsFromProp } = getTypeForSchema(value, ricosNodeTypes);
      structure.properties.push({
        name: key,
        type: tsType,
        docs: docs ? [docs] : void 0
      });
      enums.push(...enumsFromProp);
    }
  }
  if ("optionalProperties" in schema) {
    for (const [key, value] of Object.entries(schema.optionalProperties)) {
      const { tsType, docs, enums: enumsFromProp } = getTypeForSchema(value, ricosNodeTypes);
      structure.properties.push({
        hasQuestionToken: true,
        name: key,
        type: tsType,
        docs: docs ? [docs] : void 0
      });
      enums.push(...enumsFromProp);
    }
  }
  return { structure, enums };
};
function writeWriterOrValue(tsType, writer) {
  if (typeof tsType === "function") {
    tsType(writer);
    return writer;
  } else {
    return writer.write(tsType);
  }
}
const maybeUnionType = (...members) => members.length >= 2 ? Writers.unionType(...members) : (w) => writeWriterOrValue(members[0], w);
export {
  jtdToTs
};
//# sourceMappingURL=jtd-to-ts.js.map
