// src/typescript/project.ts
import ts2 from "typescript";
import {
  createDefaultMapFromCDN,
  createVirtualTypeScriptEnvironment
} from "@typescript/vfs";

// src/fs/fake-fs.ts
var hasLocalStorage = false;
try {
  hasLocalStorage = typeof localStorage !== `undefined`;
} catch (error) {
}
var hasProcess = typeof process !== `undefined`;
var shouldDebug = hasLocalStorage && localStorage.getItem("DEBUG") || hasProcess && process.env.DEBUG;
var debugLog = shouldDebug ? console.log : (_message, ..._optionalParams) => "";
function notImplemented(methodName) {
  throw new Error(`Method '${methodName}' is not implemented.`);
}
var libize = (path) => path.replace("/", "/lib.").toLowerCase();
function audit(name, fn) {
  return (...args) => {
    const res = fn(...args);
    const smallres = typeof res === "string" ? res.slice(0, 80) + "..." : res;
    debugLog("> " + name, ...args);
    debugLog("< " + smallres);
    return res;
  };
}
function createSystem(files) {
  return {
    args: [],
    createDirectory: () => notImplemented("createDirectory"),
    directoryExists: audit("directoryExists", (directory) => {
      return Array.from(files.keys()).some(
        (path) => path.startsWith(directory)
      );
    }),
    exit: () => notImplemented("exit"),
    fileExists: audit(
      "fileExists",
      (fileName) => files.has(fileName) || files.has(libize(fileName))
    ),
    getCurrentDirectory: () => "/",
    getDirectories: () => [],
    getExecutingFilePath: () => notImplemented("getExecutingFilePath"),
    readDirectory: audit(
      "readDirectory",
      (directory) => directory === "/" ? Array.from(files.keys()) : []
    ),
    readFile: audit(
      "readFile",
      (fileName) => files.get(fileName) || files.get(libize(fileName))
    ),
    resolvePath: (path) => path,
    newLine: "\n",
    useCaseSensitiveFileNames: true,
    write: () => notImplemented("write"),
    writeFile: (fileName, contents) => {
      files.set(fileName, contents);
    }
  };
}

// src/typescript/wrapped-symbol.ts
import ts from "typescript";
var WrappedSymbol = class _WrappedSymbol {
  typeChecker;
  symbol;
  constructor(typeChecker, symbol) {
    this.typeChecker = typeChecker;
    this.symbol = symbol;
  }
  get isFunctionDeclaration() {
    if (this.symbol.valueDeclaration) {
      return ts.isFunctionDeclaration(this.symbol.valueDeclaration);
    }
    return false;
  }
  get isModuleDeclaration() {
    if (this.symbol.valueDeclaration) {
      return ts.isModuleDeclaration(this.symbol.valueDeclaration);
    }
    return false;
  }
  get exports() {
    const exports = [];
    if (this.symbol.exports) {
      this.symbol.exports.forEach((sym, _) => {
        exports.push(
          new _WrappedSymbol(this.typeChecker, sym).aliasedSymbolIfNecessary
        );
      });
    }
    return exports;
  }
  get aliasedSymbolIfNecessary() {
    if ((this.symbol.flags & ts.SymbolFlags.Alias) !== 0) {
      const newSymbol = this.typeChecker.getAliasedSymbol(
        this.symbol
      );
      if (newSymbol.escapedName !== "unknown") {
        return new _WrappedSymbol(this.typeChecker, newSymbol);
      }
    }
    return this;
  }
  get comment() {
    return ts.displayPartsToString(
      this.symbol.getDocumentationComment(this.typeChecker)
    );
  }
  get name() {
    return this.symbol.getName();
  }
  get parameters() {
    if (this.symbol.valueDeclaration && // @ts-ignore
    this.symbol.valueDeclaration.parameters) {
      return this.symbol.valueDeclaration.parameters.map(
        (param) => new _WrappedSymbol(this.typeChecker, param.symbol)
      );
    }
    return [];
  }
  get examples() {
    return this.jsDocTags.filter((tag) => tag.tagName.escapedText === "example").map((tag) => tag.comment);
  }
  get type() {
    return this.symbol.valueDeclaration?.type;
  }
  get hasFunctionTag() {
    return Boolean(
      this.jsDocTags.find(
        (tag) => tag.tagName.escapedText === "function"
      )
    );
  }
  get isExportAlias() {
    const parentSymbol = this.symbol.parent;
    return this.symbol.flags === ts.SymbolFlags.AliasExcludes || parentSymbol?.escapedName.match(/^\"\/node_modules\//);
  }
  get jsDocTags() {
    const tags = [];
    const jsdoc = this.symbol.valueDeclaration?.jsDoc;
    if (jsdoc) {
      for (const d of jsdoc) {
        if (d.tags) {
          tags.push(...d.tags);
        }
      }
    }
    return tags;
  }
  // public get typeString(): NodeObject {
  //   const type = this.typeChecker.getDeclaredTypeOfSymbol(
  //     this.symbol.declarations[0]
  //   );
  //   console.log(' > ', type.intrinsicName);
  //   return this.typeChecker.typeToString(type);
  // }
};

// src/typescript/project.ts
var NO_SYMBOLS_FOUND = "Expected a symbol for location of SourceFile";
var Project = class _Project {
  fsMap;
  env;
  system;
  transformers;
  transformOptions;
  ts;
  constructor(fsMap = /* @__PURE__ */ new Map()) {
    this.transformers = [];
    this.fsMap = fsMap;
    this.ts = ts2;
    this.system = createSystem(this.fsMap);
    this.env = createVirtualTypeScriptEnvironment(
      this.system,
      [],
      ts2,
      _Project.compilerOpts
    );
  }
  /**
   * Add the adaptors type definition to the compilers filesystem map
   * @param moduleName the name of the module that will be imported,
   * i.e. the adaptors name `@openfn/language-http`.
   * @param dts the .d.ts file as a string
   */
  // addTypeDefinition(moduleName: string, dts: string) {
  //   const typedModulePath = moduleName.replace('@', '').replace('/', '__');
  //   this.fsMap.set(`/node_modules/@types/${typedModulePath}/index.d.ts`, dts);
  //   this.env.createFile(`${typedModulePath}.d.ts`, dts);
  // }
  addTypeDefinition(moduleName, dts, fileName = "index.d.ts") {
    const typedModulePath = moduleName.replace("@", "").replace("/", "__");
    this.fsMap.set(`/node_modules/@types/${typedModulePath}/${fileName}`, dts);
    this.env.createFile(`${typedModulePath}__${fileName}`, dts);
  }
  // addAdaptorFSMap(adaptorPackagePath: string) {
  //   addAdaptorFSMap(this.fsMap, adaptorPackagePath)
  // }
  // wrap(func: (sf: ts.SourceFile, ...args: any[]) => ts.SourceFile, ...args: any[]) {
  //   return func(this.sourceFile, ...args)
  // }
  get program() {
    return this.env.languageService.getProgram();
  }
  get sourceFile() {
    return this.program.getSourceFile("index.ts");
  }
  get typeChecker() {
    return this.program.getTypeChecker();
  }
  getSourceFile(filename) {
    return this.program.getSourceFile(filename);
  }
  /**
   * Add a file to the environment.
   *
   * Note that files added via the constructor are not considered part of the
   * 'project', rather files available for reading during diagnostics and
   * compilation. To add packages or type definitions use `addFile`.
   */
  createFile(expression, filename = "index.ts") {
    this.env.createFile(filename, expression);
    return this.getSourceFile(filename);
  }
  /**
   * Add a file to the system.
   *
   * Differs from `createFile` in that these files are not considered part of
   * the project - but part of the file system.
   */
  addToFS(mapOrContent, path) {
    if (path) {
      this.system.writeFile(path, mapOrContent);
    } else {
      for (const [path2, content] of mapOrContent) {
        this.system.writeFile(path2, content);
      }
    }
  }
  // useTransform(func: () => ts.TransformerFactory<ts.SourceFile>) {
  //   this.transformers.push(func)
  // }
  /**
   * Return a list of symbols that have been marked for export.
   * In the case of aliases (`export {a, b}`), these aliases are followed
   * to their original symbol.
   */
  getSymbol(sourceFile) {
    const sfSymbol = this.typeChecker.getSymbolAtLocation(sourceFile);
    if (!sfSymbol) {
      throw new Error(NO_SYMBOLS_FOUND);
    }
    return new WrappedSymbol(this.typeChecker, sfSymbol);
  }
  getDiagnostics(sourceFile) {
    return ts2.getPreEmitDiagnostics(
      this.program,
      sourceFile || this.sourceFile
    );
  }
  formatDiagnostics(sourceFile) {
    return this.getDiagnostics(sourceFile).map((diagnostic) => {
      if (diagnostic.file) {
        let { line, character } = ts2.getLineAndCharacterOfPosition(
          diagnostic.file,
          diagnostic.start
        );
        let message = ts2.flattenDiagnosticMessageText(
          diagnostic.messageText,
          "\n"
        );
        return `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`;
      } else {
        return ts2.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
      }
    });
  }
  compile(sourceFile) {
    sourceFile = sourceFile || this.program.getSourceFile("index.ts");
    return this.program.emit(
      sourceFile,
      this.system.writeFile,
      void 0,
      false,
      {
        before: this.transformers.map(
          (t) => t(this.program, this.transformOptions)
        ),
        // [transformer(this.program, {})],
        after: [],
        afterDeclarations: []
      }
    );
  }
  static compilerOpts = {
    allowJs: true,
    experimentalDecorators: true,
    module: ts2.ModuleKind.ES2020,
    moduleResolution: ts2.ModuleResolutionKind.NodeJs,
    noEmitOnError: false,
    noUnusedLocals: true,
    noUnusedParameters: true,
    stripInternal: true,
    declaration: false,
    baseUrl: "/",
    // outDir: "/dist",
    lib: ["ES2020"],
    target: ts2.ScriptTarget.ES2020
  };
  // transform(): ts.TransformationResult<ts.Node> {
  //   const transformedSourceFile = ts.transform(
  //     this.sourceFile,
  //     [transformer(this.program, this.transformOptions)],
  //     BaseCompiler.compilerOpts
  //   )
  //   return transformedSourceFile
  //   // const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
  //   // return printer.printFile(transformedSourceFile[0]);
  // }
};

// src/describe-project.ts
var describeParameter = (project, symbol) => {
  const typeNode = project.typeChecker.getTypeFromTypeNode(symbol.type);
  const typeString = project.typeChecker.typeToString(typeNode);
  return {
    name: symbol.name,
    optional: false,
    type: typeString
  };
};
var describeNamespace = (_project, symbol) => {
  return {
    name: symbol.name,
    type: "namespace"
  };
};
var describeFunction = (project, symbol, moduleName) => {
  return {
    type: "function",
    name: moduleName ? `${moduleName}.${symbol.name}` : symbol.name,
    description: symbol.comment,
    parameters: symbol.parameters.map((p) => describeParameter(project, p)),
    magic: symbol.jsDocTags.some((tag) => tag.tagName.escapedText === "magic"),
    isOperation: false,
    examples: symbol.examples.filter((eg) => typeof eg === "string").map((eg) => {
      if (eg.startsWith("<caption>")) {
        let [caption, code] = eg.split("</caption>");
        caption = caption.replace("<caption>", "");
        return { caption: caption.trim(), code: code.trim() };
      }
      return { code: eg.trim() };
    })
  };
};
var describeProject = (project, typesEntry = "index.d.ts", options = {}) => {
  const sourceFile = project.getSourceFile(typesEntry);
  if (!sourceFile) {
    throw new Error(`Couldn't find a SourceFile for: ${typesEntry}`);
  }
  let symbols;
  try {
    symbols = project.getSymbol(sourceFile);
  } catch (e) {
    if (e.messsage = NO_SYMBOLS_FOUND) {
      symbols = { exports: [] };
    } else {
      throw e;
    }
  }
  return symbols.exports.filter((symbol) => {
    if (options.includePrivate) {
      return true;
    }
    return symbol.isExportAlias || symbol.hasFunctionTag;
  }).reduce((symbols2, symbol) => {
    if (symbol.isFunctionDeclaration) {
      symbols2.push(describeFunction(project, symbol));
    } else if (symbol.isModuleDeclaration) {
      symbol.exports.map((modSymbol) => {
        if (modSymbol.isFunctionDeclaration) {
          symbols2.push(describeFunction(project, modSymbol, symbol.name));
        }
      });
    } else if (symbol.isExportAlias) {
      symbols2.push(describeNamespace(project, symbol));
    }
    return symbols2;
  }, []);
};
var describe_project_default = describeProject;

// src/util.ts
var getNameAndVersion = (specifier) => {
  let name;
  let version;
  const atIndex = specifier.lastIndexOf("@");
  if (atIndex > 0) {
    name = specifier.substring(0, atIndex);
    version = specifier.substring(atIndex + 1);
  } else {
    name = specifier;
  }
  return { name, version };
};

// src/fs/package-fs.ts
import fetch from "cross-fetch";
var NotFound = class extends Error {
  constructor(message) {
    super(message);
    this.name = "NotFound";
  }
};
async function fetchFileListing(packageName) {
  const response = await fetch(
    `https://data.jsdelivr.com/v1/package/npm/${packageName}/flat`
  );
  if (response.status != 200) {
    throw new Error(
      `Failed getting file listing for: ${packageName} got: ${response.status} ${response.statusText}`
    );
  }
  const listing = await response.json();
  return listing.files?.map(({ name }) => name);
}
var dtsExtension = /\.d\.ts$/;
async function* fetchDTSListing(packageName) {
  for (const f of await fetchFileListing(packageName)) {
    if (dtsExtension.test(f)) {
      yield f;
    }
  }
}
async function fetchFile(path) {
  const response = await fetch(`https://cdn.jsdelivr.net/npm/${path}`);
  switch (response.status) {
    case 200:
      const contents = await response.text();
      return contents;
    case 404:
      throw new NotFound(`Got 404 from Unpkg for: ${path}`);
    default:
      throw new Error(
        `Failed getting file at: ${path} got: ${response.status} ${response.statusText}`
      );
  }
}

// src/api.ts
var describePackage = async (specifier, _options = {}) => {
  const { name, version } = getNameAndVersion(specifier);
  const project = new Project();
  const commonFunctions = {};
  if (name != "@openfn/language-common") {
    const pkgStr = await fetchFile(`${specifier}/package.json`);
    const pkg = JSON.parse(pkgStr);
    if (pkg.dependencies["@openfn/language-common"]) {
      const commonSpecifier = `@openfn/language-common@${pkg.dependencies["@openfn/language-common"].replace("^", "")}`;
      const common = await fetchDTSListing(commonSpecifier);
      for await (const fileName of common) {
        const f = await fetchFile(`${commonSpecifier}${fileName}`);
        const relativeFileName = fileName.split("/").pop();
        project.addTypeDefinition(
          "@openfn/language-common",
          f,
          relativeFileName
        );
        project.createFile(f, fileName);
        describe_project_default(project, fileName).forEach((member) => {
          if (member.type === "function") {
            member.package = "@openfn/language-common";
            commonFunctions[member.name] = member;
          }
        });
      }
    }
  }
  const files = await fetchDTSListing(specifier);
  const functions = [];
  const namespaces = [];
  for await (const fileName of files) {
    if (!/beta\.d\.ts$/.test(fileName)) {
      const f = await fetchFile(`${specifier}${fileName}`);
      project.createFile(f, fileName);
      describe_project_default(project, fileName).forEach((member) => {
        if (member.type === "function") {
          member.package = name;
          functions.push(member);
        } else if (member.type === "namespace") {
          if (commonFunctions[member.name]) {
            functions.push(commonFunctions[member.name]);
          } else {
            namespaces.push(member);
          }
        }
      });
    }
  }
  return {
    name,
    version,
    functions,
    namespaces
  };
};
var loadDTS = async (_specifier, _options) => ["declare module {}"];

// src/pack.ts
import urlJoin from "url-join";
var Pack = class _Pack {
  path;
  _packageJson;
  _fileListing;
  fsMap;
  _packageBase;
  constructor(params) {
    this.path = params.path;
    this._packageJson = params.packageJson;
    this._fileListing = params.fileListing;
    this.fsMap = params.fsMap || /* @__PURE__ */ new Map();
    this._packageBase = params.packageBase;
  }
  get packageJson() {
    if (!this._packageJson) {
      throw new Error("packageJson not available.");
    }
    return this._packageJson;
  }
  /**
   * The file contents of the package according to the source.
   *
   * _Different to `fsMap`, which includes the `root`._
   */
  get fileListing() {
    if (!this._fileListing) {
      throw new Error("fileListing not available.");
    }
    return this._fileListing;
  }
  /**
   * The absolute name and version of the package.
   *
   * i.e. `@<org>/<package>@<version>`
   *
   * This may differ from the `path` passed in during creation.
   * For example, if the package path was `mypackage@latest` and the latest
   * version is `1.2.3` as stated by the `package.json` the resulting
   * specifier will be `mypackage@1.2.3`.
   */
  get specifier() {
    return `${this.packageJson.name}@${this.packageJson.version}`;
  }
  /**
   * The version of the package specified in the `package.json`
   */
  get version() {
    return this.packageJson.version;
  }
  get packageBase() {
    if (this._packageBase) {
      return this._packageBase;
    }
    this._packageBase = urlJoin("/node_modules", this.packageJson.name);
    return this.packageBase;
  }
  /**
   * Get the "types" property from `package.json`, and ensure it's path is
   * relative to `/`.
   */
  get types() {
    if (!this.packageJson.types)
      return null;
    return urlJoin(this.packageBase, this.packageJson.types);
  }
  async getFiles(files) {
    await Promise.allSettled(
      (files || this.fileListing).map(this.getFile, this)
    );
    return this.fsMap;
  }
  async getFile(file) {
    const contents = await fetchFile(this.specifier + file);
    this.fsMap.set(urlJoin(this.packageBase, file), contents);
  }
  static async fetch(specifier) {
    const pack = new _Pack({
      path: specifier,
      packageJson: JSON.parse(await fetchFile(specifier + "/package.json")),
      fileListing: Array.from(await fetchFileListing(specifier))
    });
    return pack;
  }
};
export {
  NotFound,
  Pack,
  Project,
  describe_project_default as describeDts,
  describePackage,
  fetchDTSListing,
  fetchFile,
  fetchFileListing,
  loadDTS
};
