import type {
  Program as EstreeProgram,
  Node as EstreeNode,
  Kind as EstreeNodeKind,
  Path as EstreeNodePath,
  SyntaxErrorCause,
} from "estree-sentry";
import type { Warning } from "./trans/prelude/warning.d.ts";
import type { Json } from "./util/util.d.ts";
import type {
  ClashErrorCause,
  InputErrorCause,
  PointcutErrorCause,
} from "./error.d.ts";
import type {
  ModuleHeader,
  ImportHeader,
  ExportHeader,
  AggregateHeader,
  DeclareHeader,
  Header,
} from "./lang/header.d.ts";
import type {
  Atom,
  Node,
  Program,
  RoutineBlock,
  SegmentBlock,
  Statement,
  Effect,
  Expression,
  SyntaxPrimitive,
  RuntimePrimitive as Primitive,
  IntrinsicRecord,
  RegularIntrinsicRcord,
  AccessorIntrinsicRecord,
  ExtraIntrinsicRecord,
  Intrinsic,
  Parameter,
} from "./lang/syntax.d.ts";
import type { Config as SetupConfig } from "./setup/config.d.ts";
import type { Config as RetroConfig } from "./retro/config.d.ts";
import type { File, Config as TransConfig } from "./trans/config.d.ts";
import type { Config as StandardWeaveConfig } from "./weave/standard/config.d.ts";
import type { Config as FlexibleWeaveConfig } from "./weave/flexible/config.d.ts";
import type { Config as InstrumentConfig } from "./instrument.d.ts";
import type { Digest, LooseEstreeProgram } from "./trans/config.d.ts";
import type {
  Situ,
  GlobalSitu,
  RootLocalSitu,
  DeepLocalSitu,
} from "./trans/source.d.ts";
import type {
  GeneratorKind,
  ProgramKind,
  ClosureKind,
  SegmentKind,
  ControlKind,
} from "./weave/parametrization.d.ts";
import type {
  TestKind,
  AspectKind as StandardAspectKind,
  AspectTyping as StandardAspectTyping,
  Pointcut as StandardPointcut,
  Advice as StandardAdvice,
} from "./weave/standard/aspect.d.ts";
import type {
  AspectTyping as FlexibleAspectTyping,
  AspectKind as FlexibleAspectKind,
  PointcutElement as FlexiblePointcutElement,
  AdviceElement as FlexibleAdviceElement,
  Aspect as FlexibleAspect,
  Pointcut as FlexiblePointcut,
  Advice as FlexibleAdvice,
} from "./weave/flexible/aspect.d.ts";

export {
  // Util //
  Json,
  // Config //
  SetupConfig,
  RetroConfig,
  TransConfig,
  StandardWeaveConfig,
  FlexibleWeaveConfig,
  InstrumentConfig,
  // Estree //
  EstreeNode,
  EstreeProgram,
  EstreeNodeKind,
  EstreeNodePath,
  LooseEstreeProgram,
  // Error //
  Warning,
  SyntaxErrorCause,
  ClashErrorCause,
  InputErrorCause,
  PointcutErrorCause,
  Digest,
  Situ,
  GlobalSitu,
  RootLocalSitu,
  DeepLocalSitu,
  // Syntax //
  Atom,
  ModuleHeader,
  ImportHeader,
  ExportHeader,
  AggregateHeader,
  DeclareHeader,
  Header,
  Node,
  Program,
  RoutineBlock,
  SegmentBlock,
  Statement,
  Effect,
  Expression,
  SyntaxPrimitive,
  Primitive,
  IntrinsicRecord,
  RegularIntrinsicRcord,
  AccessorIntrinsicRecord,
  ExtraIntrinsicRecord,
  Intrinsic,
  Parameter,
  // Aspect //
  StandardAspectKind,
  FlexibleAspectKind,
  GeneratorKind,
  ProgramKind,
  ClosureKind,
  SegmentKind,
  ControlKind,
  TestKind,
  StandardPointcut,
  StandardAdvice,
  StandardAspectTyping,
  FlexibleAspectTyping,
  FlexiblePointcut,
  FlexibleAspect,
  FlexibleAdvice,
  FlexiblePointcutElement,
  FlexibleAdviceElement,
};

///////////
// Error //
///////////

/**
 * Signals a probable bug in Aran.
 */
export class AranExecError extends Error {
  constructor(message: string, cause: unknown);
  message: string;
  cause: unknown;
}

/**
 * Signals a loophole in typescript annotations and a probable Aran bug.
 */
export class AranTypeError extends TypeError {
  constructor(cause: never);
  name: "AranTypeError";
  message: string;
  cause: unknown;
}

/**
 * Signals an invalid input.
 */
export class AranInputError extends Error {
  constructor(cause: InputErrorCause);
  name: "AranConfigError";
  message: string;
  cause: InputErrorCause;
}
/**
 * Signals a problem within the syntax of the target code.
 */
export class AranSyntaxError extends SyntaxError {
  constructor(message: string, cause: SyntaxErrorCause);
  name: "AranSyntaxError";
  message: string;
  cause: SyntaxErrorCause;
}
/**
 * Signals a clash between Aran variables and the variables of the target code.
 */
export class AranClashError extends Error {
  constructor(cause: ClashErrorCause);
  name: "AranClashError";
  message: string;
  cause: ClashErrorCause;
}

/**
 * Signals a problem with the provided pointcut.
 */
export class AranPointcutError extends Error {
  constructor(cause: PointcutErrorCause);
  name: "AranPointcutError";
  message: string;
  cause: PointcutErrorCause;
}

///////////
// Setup //
///////////

/**
 * Generates a `estree.Program` that should be executed before executing any
 * instrumented code. In the standalone mode, the setup code is bundled with the
 * instrumented code and this function should not be used. Alternatively, the
 * setup can be directly importated at the entry point aran/runtime.
 * @param conf Setup generation options.
 * @returns The setup script program. Can be passed to an estree code generator
 * such as `astring`.
 * @throws {@link AranInputError} If the configuration is invalid.
 */
export const setupile: (
  conf?: Partial<SetupConfig>,
) => EstreeProgram<{}> & { sourceType: "script" };

///////////////
// Transpile //
///////////////

/**
 * Transpile a JavaScript program to an Aran program.
 * @template hash The branded type for the result of `conf.digest`
 * @template atom The branded types for the leafs of the output program.
 * @template path The type of `file.path`.
 * @param file The parsed JavaScript program to transpile.
 * @param conf Transpilation options.
 * @returns The transpiled aran program along with warnings.
 * @throws {@link AranInputError} If `conf` is invalid or if the top level
 * properties of `file` are invalid.
 * @throws {@link AranSyntaxError} If there is problem with the AST at
 * `file.root` either because there is an early syntax error or because it is
 * not a valid ESTree program.
 */
export const transpile: <
  hash extends string | number = string | number,
  atom extends Atom & { Tag: hash } = Atom & { Tag: hash },
  path = unknown,
>(
  file: Partial<File<{ FilePath: path }>>,
  conf?:
    | null
    | undefined
    | Partial<TransConfig<{ FilePath: path; NodeHash: hash }>>,
) => Program<atom> & { warnings: Warning[] };

///////////
// Weave //
///////////

/**
 * Insert calls to advice functions in an Aran program with the standard API.
 * @template conf_param Type parameters for the configuration object.
 * @template arg_atom The branded types for the AST leafs of the input program.
 * @template res_atom The branded types for the AST leafs of the output program.
 * @param root The Aran program to weave.
 * @param conf Standard weaving options.
 * @returns The woven program.
 * @throws {@link AranInputError} If `conf` or `root` are invalid.
 * @throws {@link AranPointcutError} If there is a problem with the provided
 * pointcut.
 */
export const weaveStandard: <
  tag extends Json,
  conf_param extends Atom & { Tag: tag },
  arg_atom extends Pick<conf_param, keyof Atom>,
  res_atom extends Atom & { Tag: tag },
>(
  root: Program<arg_atom>,
  conf?: null | undefined | Partial<StandardWeaveConfig<conf_param>>,
) => Program<res_atom>;

/**
 * Insert calls to advice functions in an Aran program with the flexible API.
 * @template conf_param Type parameters for the configuration object.
 * @template arg_atom The branded types for the AST leafs of the input program.
 * @template res_atom The branded types for the AST leafs of the output program.
 * @param root The Aran program to weave.
 * @param conf Flexible weaving options.
 * @returns The woven program.
 * @throws {@link AranInputError} If `conf` or `root` are invalid.
 * @throws {@link AranPointcutError} If there is a problem with the provided
 * pointcut.
 */
export const weaveFlexible: <
  conf_param extends Atom,
  arg_atom extends Pick<conf_param, keyof Atom>,
  res_atom extends Atom & { Tag: arg_atom["Tag"] },
>(
  root: Program<arg_atom>,
  conf?: null | undefined | Partial<FlexibleWeaveConfig<conf_param>>,
) => Program<res_atom>;

///////////////
// Retropile //
///////////////

/**
 * Transpile an Aran program back to an ESTree JavaScript program.
 * @param root The Aran program to retropile.
 * @param conf Retropilation options.
 * @returns An ESTree program that can be fed to a estree code generator like
 * `astring`.
 * @throws {@link AranInputError} If `conf` or `root` are invalid.
 * @throws {@link AranPointcutError} If there is a problem with the provided
 * pointcut.
 * @throws {@link AranClashError} If there is a clash between Aran variables and
 * the variable in `file.root`.
 */
export const retropile: (
  root: Program<Atom>,
  conf?: null | undefined | Partial<RetroConfig>,
) => EstreeProgram<null>;

////////////////
// Instrument //
////////////////

/**
 * Instrument a parsed JavaScript program. It chains `transpile`,
 * `weaveStandard` or `weaveFlexible`, and `retropile`.
 * @template path The type of `file.path`.
 * @template hash The type returned by `conf.digest`.
 * @param file The parsed JavaScript program to instrument.
 * @param conf Instrumentation options.
 * @returns The instrumented program along with warnings. Can be fed to a estree
 * code generator like `astring`.
 * @throws {@link AranInputError} If `conf` is invalid or if the top
 * level properties of `file` are invalid.
 * @throws {@link AranSyntaxError} If there is problem with the AST at
 * `file.root` either because there is an early syntax error or because it is
 * not a valid ESTree program.
 * @throws {@link AranPointcutError} If there is a problem with the provided
 * pointcut.
 * @throws {@link AranClashError} If there is a clash between Aran variables and
 * the variable in `file.root`.
 */
export const instrument: <path = string, hash extends string | number = string>(
  file: Partial<File<{ FilePath: path }>>,
  conf?:
    | null
    | undefined
    | Partial<
        InstrumentConfig<{
          Tag: hash;
          FilePath: path;
          NodeHash: hash;
        }>
      >,
) => EstreeProgram<{}> & {
  warnings: Warning[];
};

/////////////
// Runtime //
/////////////

/**
 * Compile the intrinsic record. This function is also available at the
 * aran/runtime entry point which is much lightweight as it does not contain
 * code related to instrumentation.
 * @param global The global object.
 * @returns The intrinsic record.
 */
export const compileIntrinsicRecord: (
  global: typeof globalThis,
) => IntrinsicRecord;
