import {
  compilerOptions
} from "./chunk-CLKKQB2X.mjs";

// src/server/functionsTypesWorker.ts
import * as path from "path";
import invariant from "invariant";
import ts from "typescript";
import { glob } from "glob";
import chalk from "chalk";
import { asArray } from "@mui/toolpad-utils/collections";
var NULL_PROP_VALUE = {
  type: "object",
  schema: { type: "null" },
  default: null
};
function propValueFromJsonSchema(schema) {
  if (!schema) {
    return NULL_PROP_VALUE;
  }
  if (Array.isArray(schema.type)) {
    return propValueFromJsonSchema({
      ...schema,
      type: schema.type[0]
    });
  }
  switch (schema.type) {
    case "integer":
      return { type: "number" };
    case "string": {
      if (schema.enum) {
        return { type: "string", enum: schema.enum.map((value) => String(value)) };
      }
      return { type: "string" };
    }
    case "null":
    case null:
    case void 0:
      return NULL_PROP_VALUE;
    default:
      return { type: schema.type, schema };
  }
}
function formatDiagnostic(diagnostic) {
  const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
  if (diagnostic.file) {
    const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
    return `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`;
  }
  return message;
}
function hasTypeFlag(type, flag) {
  return (type.getFlags() & flag) !== 0;
}
function asUnionTypes(type) {
  return type.isUnion() ? type.types : [type];
}
function toJsonSchema(tsType, checker, seenTypes) {
  if (seenTypes.has(tsType)) {
    return { const: "[Circular]" };
  }
  seenTypes.add(tsType);
  try {
    if (hasTypeFlag(tsType, ts.TypeFlags.Undefined) || hasTypeFlag(tsType, ts.TypeFlags.Void) || hasTypeFlag(tsType, ts.TypeFlags.VoidLike)) {
      return null;
    }
    if (hasTypeFlag(tsType, ts.TypeFlags.Null)) {
      return { type: "null" };
    }
    if (tsType === checker.getTrueType()) {
      return { type: "boolean", const: true };
    }
    if (tsType === checker.getFalseType()) {
      return { type: "boolean", const: false };
    }
    if (tsType.isNumberLiteral()) {
      return { type: "number", const: tsType.value };
    }
    if (tsType.isStringLiteral()) {
      return { type: "string", const: tsType.value };
    }
    if (tsType.isUnion()) {
      const anyOf = [];
      const withoutUndefined = tsType.types.filter(
        (type) => !hasTypeFlag(type, ts.TypeFlags.Undefined)
      );
      if (withoutUndefined.length <= 0) {
        return null;
      }
      if (withoutUndefined.length === 1) {
        return toJsonSchema(withoutUndefined[0], checker, seenTypes);
      }
      for (const type of withoutUndefined) {
        const itemType = toJsonSchema(type, checker, seenTypes);
        if (itemType) {
          anyOf.push(itemType);
        }
      }
      const typeNames = /* @__PURE__ */ new Set();
      const enumValues = /* @__PURE__ */ new Set();
      let hasTrue = false;
      let hasFalse = false;
      let hasOther = false;
      let isEnum = true;
      for (const itemType of anyOf) {
        if (typeof itemType.const !== "undefined") {
          enumValues.add(itemType.const);
        } else {
          isEnum = false;
        }
        if (itemType.const === true) {
          hasTrue = true;
        } else if (itemType.const === false) {
          hasFalse = true;
        } else {
          hasOther = true;
        }
        for (const typeName of asArray(itemType.type)) {
          if (typeName) {
            typeNames.add(typeName);
          }
        }
      }
      if (hasTrue && hasFalse && !hasOther) {
        return { type: "boolean" };
      }
      if (isEnum) {
        let type;
        if (typeNames.size === 1) {
          type = Array.from(typeNames)[0];
        }
        return { type, enum: Array.from(enumValues) };
      }
      if (anyOf.length === 1) {
        return anyOf[0];
      }
      return { anyOf };
    }
    if (hasTypeFlag(tsType, ts.TypeFlags.BooleanLike) || hasTypeFlag(tsType, ts.TypeFlags.Boolean) || hasTypeFlag(tsType, ts.TypeFlags.BooleanLiteral)) {
      return { type: "boolean" };
    }
    if (hasTypeFlag(tsType, ts.TypeFlags.NumberLike) || hasTypeFlag(tsType, ts.TypeFlags.Number) || hasTypeFlag(tsType, ts.TypeFlags.NumberLiteral) || hasTypeFlag(tsType, ts.TypeFlags.BigIntLike) || hasTypeFlag(tsType, ts.TypeFlags.BigInt) || hasTypeFlag(tsType, ts.TypeFlags.BigIntLiteral)) {
      return { type: "number" };
    }
    if (hasTypeFlag(tsType, ts.TypeFlags.StringLike) || hasTypeFlag(tsType, ts.TypeFlags.String) || hasTypeFlag(tsType, ts.TypeFlags.StringLiteral)) {
      return { type: "string" };
    }
    if (hasTypeFlag(tsType, ts.TypeFlags.Object)) {
      if (checker.isArrayLikeType(tsType)) {
        let items;
        const indexType = tsType.getNumberIndexType();
        if (indexType) {
          items = toJsonSchema(indexType, checker, seenTypes) ?? void 0;
        }
        return {
          type: "array",
          items
        };
      }
      const required = [];
      const properties = Object.fromEntries(
        tsType.getProperties().flatMap((propertySymbol) => {
          const propertyName = propertySymbol.getName();
          const propertyType = checker.getTypeOfSymbol(propertySymbol);
          const isOptional = asUnionTypes(propertyType).some(
            (type) => hasTypeFlag(type, ts.TypeFlags.Undefined)
          );
          if (!isOptional) {
            required.push(propertyName);
          }
          const propertySchema = toJsonSchema(propertyType, checker, seenTypes);
          return propertySchema ? [[propertyName, propertySchema]] : [];
        })
      );
      return { type: "object", properties, required };
    }
    return {};
  } finally {
    seenTypes.delete(tsType);
  }
}
function getParameters(callSignatures, checker) {
  invariant(callSignatures.length > 0, "Expected at least 1 call signature");
  const paramEntries = callSignatures[0].getParameters().map((parameter) => {
    const paramType = checker.getTypeOfSymbolAtLocation(parameter, parameter.valueDeclaration);
    const schema = toJsonSchema(paramType, checker, /* @__PURE__ */ new Set());
    return [
      parameter.getName(),
      {
        ...propValueFromJsonSchema(schema),
        required: !checker.isOptionalParameter(
          parameter.valueDeclaration
        )
      }
    ];
  });
  return paramEntries;
}
function getAwaitedType(type, checker) {
  const awaitedType = type.getNonNullableType();
  const then = awaitedType.getProperty("then");
  if (hasTypeFlag(awaitedType, ts.TypeFlags.Object) && then) {
    const thenType = checker.getTypeOfSymbolAtLocation(then, then.valueDeclaration);
    for (const thenCallSignature of thenType.getCallSignatures()) {
      const [onfulfilled] = thenCallSignature.getParameters();
      if (onfulfilled) {
        const onfulfilledType = checker.getTypeOfSymbolAtLocation(onfulfilled, onfulfilled.valueDeclaration).getNonNullableType();
        for (const onFulfilledCallSignature of onfulfilledType.getCallSignatures()) {
          const [value] = onFulfilledCallSignature.getParameters();
          if (value) {
            const valueType = checker.getTypeOfSymbolAtLocation(value, value.valueDeclaration);
            return getAwaitedType(valueType, checker);
          }
        }
      }
    }
  }
  return type;
}
function isToolpadCreateFunction(exportType) {
  const properties = exportType.getProperties();
  for (const property of properties) {
    if (ts.isPropertySignature(property.valueDeclaration)) {
      if (property.valueDeclaration.name.getText() === "[TOOLPAD_FUNCTION]") {
        return true;
      }
    }
  }
  return false;
}
function isToolpadCreateDataProvider(exportType) {
  const properties = exportType.getProperties();
  for (const property of properties) {
    if (ts.isPropertySignature(property.valueDeclaration)) {
      if (property.valueDeclaration.name.getText() === "[TOOLPAD_DATA_PROVIDER_MARKER]") {
        return true;
      }
    }
  }
  return false;
}
function getCreateFunctionParameters(callSignatures, checker) {
  invariant(callSignatures.length > 0, "Expected at least 1 call signature");
  const [callSignature] = callSignatures;
  const [firstParameter] = callSignature.getParameters();
  invariant(firstParameter, "Expected at least 1 parameter");
  const firstParameterType = checker.getTypeOfSymbolAtLocation(
    firstParameter,
    firstParameter.valueDeclaration
  );
  const parametersProperty = firstParameterType.getProperty("parameters");
  invariant(parametersProperty, "Expected parameters property");
  const parametersPropertyType = checker.getTypeOfSymbolAtLocation(
    parametersProperty,
    parametersProperty.valueDeclaration
  );
  return parametersPropertyType.getProperties().map((property) => {
    const propertyType = checker.getTypeOfSymbol(property);
    const schema = toJsonSchema(propertyType, checker, /* @__PURE__ */ new Set());
    return [
      property.getName(),
      {
        ...propValueFromJsonSchema(schema),
        required: false
      }
    ];
  });
}
function getReturnType(callSignatures, checker) {
  invariant(callSignatures.length > 0, "Expected at least 1 call signature");
  const returnType = callSignatures[0].getReturnType();
  const awaitedReturnType = getAwaitedType(returnType, checker);
  const schema = toJsonSchema(awaitedReturnType, checker, /* @__PURE__ */ new Set());
  return {
    schema
  };
}
var createFunctionWarningShown = false;
async function extractTypes({
  resourcesFolder
}) {
  const entryPoints = await glob(path.join(resourcesFolder, "./*.ts"), {
    windowsPathsNoEscape: true
  });
  const program = ts.createProgram(entryPoints, compilerOptions);
  const checker = program.getTypeChecker();
  const usingCreateFunction = [];
  const files = entryPoints.map((entrypoint) => {
    const sourceFile = program.getSourceFile(entrypoint);
    const relativeEntrypoint = path.relative(resourcesFolder, entrypoint);
    if (!sourceFile) {
      return null;
    }
    const diagnostics = program.getSemanticDiagnostics(sourceFile);
    for (const diagnostic of diagnostics) {
      console.log(`${chalk.blue("info")}  - ${formatDiagnostic(diagnostic)}`);
    }
    const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
    if (!moduleSymbol) {
      return null;
    }
    const exports = checker.getExportsOfModule(moduleSymbol);
    const handlers = exports.map((symbol) => {
      const exportType = checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration);
      const callSignatures = exportType.getCallSignatures();
      if (callSignatures.length <= 0) {
        return null;
      }
      const isCreateFunction = isToolpadCreateFunction(exportType);
      if (isCreateFunction) {
        usingCreateFunction.push(symbol.name);
      }
      return {
        name: symbol.name,
        isCreateFunction,
        parameters: isCreateFunction ? getCreateFunctionParameters(callSignatures, checker) : getParameters(callSignatures, checker),
        returnType: getReturnType(callSignatures, checker)
      };
    }).filter(Boolean);
    const dataProviders = exports.map((symbol) => {
      const exportType = checker.getTypeOfSymbolAtLocation(symbol, symbol.valueDeclaration);
      if (isToolpadCreateDataProvider(exportType)) {
        return {
          name: symbol.name
        };
      }
      return null;
    }).filter(Boolean);
    return {
      name: relativeEntrypoint,
      errors: diagnostics.filter((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error).map((diagnostic) => ({ message: formatDiagnostic(diagnostic) })),
      warnings: diagnostics.filter((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Warning).map((diagnostic) => ({ message: formatDiagnostic(diagnostic) })),
      handlers,
      dataProviders
    };
  }).filter(Boolean).sort((a, b) => a.name.localeCompare(b.name));
  if (usingCreateFunction.length > 0 && !createFunctionWarningShown) {
    console.warn(
      `${chalk.yellow("warn")} - ${chalk.bold(usingCreateFunction.length)} function${usingCreateFunction.length === 1 ? " is" : "s are"} using the deprecated ${chalk.red(
        "createFunction"
      )} API. This will be removed from Toolpad in a future release. Please see ${chalk.underline(
        chalk.blue("https://mui.com/toolpad/reference/api/create-function/")
      )} for migration information and updates.`
    );
    createFunctionWarningShown = true;
  }
  return { files };
}
export {
  extractTypes as default
};
//# sourceMappingURL=functionsTypesWorker.mjs.map