import { ICruiseOptions } from "./options";
import { IFlattenedRuleSet } from "./rule-set";
import { DependencyType, ModuleSystemType, ProtocolType } from "./shared-types";
import { IViolation } from "./violations";
import { IRuleSummary } from "./rule-summary";
import { IChange } from "watskeburt";

export interface IRevisionChange extends IChange {
  // optional because
  // - in content strategy 'ignored' and 'deleted' files can't have a checksum
  // - in metadata strategy we don't calculate a checksum because ~we're lazy~
  //   it's an unnecessary expense
  checksum?: string;
}

/**
 * caching revisionData. doc and proper naming will follow
 */
export interface IRevisionData {
  SHA1: string;
  changes: IRevisionChange[];
}

export interface ICruiseResult {
  /**
   * A list of modules, with for each module the modules it depends upon
   */
  modules: IModule[];
  /**
   * A list of folders, as derived from the detected modules, with for each
   * folder a bunch of metrics (adapted from 'Agile software development:
   * principles, patterns, and practices' by Robert C Martin (ISBN 0-13-597444-5).
   * Note: these metrics substitute 'components' and 'classes' from that book
   * with 'folders' and 'modules'; the closest relatives that work for the most
   * programming styles in JavaScript (and its derivative languages).
   */
  folders?: IFolder[];
  /**
   * Data summarizing the found dependencies
   */
  summary: ISummary;
  /**
   * caching revisionData. doc and proper naming will follow
   */
  revisionData?: IRevisionData;
}

export interface IModule {
  /**
   * The (resolved) file name of the module, e.g. 'src/main/index.js'
   */
  source: string;
  /**
   * 'true' if this module violated a rule; 'false' in all other cases. The violated rule will
   * be in the 'rule' object at the same level.
   */
  valid: boolean;
  /**
   * list of modules this module depends on
   */
  dependencies: IDependency[];
  /**
   * list of modules that depend on this module (values are _resolved_ names of
   * those modules)
   */
  dependents: string[];
  /**
   * Whether or not this is a node.js core module
   */
  coreModule?: boolean;
  /**
   * 'true' if dependency-cruiser could not resolve the module name in the source code to a
   * file name or core module. 'false' in all other cases.
   */
  couldNotResolve?: boolean;
  /**
   * the type of inclusion - local, core, unknown (= we honestly don't know), undetermined (=
   * we didn't bother determining it) or one of the npm dependencies defined in a package.json
   * ('npm' for 'dependencies', 'npm-dev', 'npm-optional', 'npm-peer', 'npm-no-pkg' for
   * development, optional, peer dependencies and dependencies in node_modules but not in
   * package.json respectively)
   */
  dependencyTypes?: DependencyType[];
  /**
   * Whether or not this is a dependency that can be followed any further. This will be
   * 'false' for for core modules, json, modules that could not be resolved to a file and
   * modules that weren't followed because it matches the doNotFollow expression.
   */
  followable?: boolean;
  /**
   * the license, if known (usually known for modules pulled from npm, not for local ones)
   */
  license?: string;
  /**
   * 'true' if the file name of this module matches the 'doNotFollow' regular expression
   */
  matchesDoNotFollow?: boolean;
  /**
   * 'true' if the file name of this module matches the 'focus' filter regular expression
   */
  matchesFocus?: boolean;
  /**
   * 'true' if the file name of this module matches the 'reaches' filter regular expression
   */
  matchesReaches?: boolean;
  /**
   * 'true' if the file name of this module matches the 'highlight' regular expression
   */
  matchesHighlight?: boolean;
  /**
   * 'true' if this module does not have dependencies, and no module has it as a dependency
   */
  orphan?: boolean;
  /**
   * An array of objects that tell whether this module is 'reachable', and according to rule
   * in which this reachability was defined
   */
  reachable?: IReachable[];
  /**
   * An array of objects that tell which other modules it reaches,
   * and that falls within the definition of the passed rule.
   */
  reaches?: IReaches[];
  /**
   * an array of rules violated by this module - left out if the module is valid
   */
  rules?: IRuleSummary[];
  /**
   * true if the module was 'consolidated'. Consolidating implies the
   * entity a Module represents might be several modules at the same time.
   * This attribute is set by tools that consolidate modules for reporting
   * purposes - it will not be present after a regular cruise.
   */
  consolidated?: boolean;
  /**
   * "number of dependents/ (number of dependents + number of dependencies)
   * A measure for how stable the module is; ranging between 0 (completely
   * stable module) to 1 (completely instable module). Derived from Uncle
   * Bob's instability metric - but applied to a single module instead of
   * to a group of them. This attribute is only present when dependency-cruiser
   * was asked to calculate metrics.
   */
  instability?: number;
  /**
   * checksum of the contents of the module. This attribute is currently only
   * available when the cruise was executed with caching and the cache strategy
   * is 'content'.
   */
  checksum?: string;
}

export interface IDependency {
  /**
   * 'true' if following this dependency will ultimately return to the source, false in all
   * other cases
   */
  circular: boolean;
  /**
   * Whether or not this is a node.js core module - deprecated in favor of dependencyType ===
   * core
   */
  coreModule: boolean;
  /**
   * 'true' if dependency-cruiser could not resolve the module name in the source code to a
   * file name or core module. 'false' in all other cases.
   */
  couldNotResolve: boolean;
  /**
   * 'true' if the dependency between this dependency and its parent only
   * exists before compilation takes place. 'false in all other cases.
   * Dependency-cruiser will only specify this attribute for TypeScript and
   * then only when the option 'tsPreCompilationDeps' has the value 'specify'.
   */
  preCompilationOnly?: boolean;
  /**
   * 'true' when the module included the module explicitly as type only with the
   * `type` keyword e.g. `import type { IThingus } from 'thing'`. Dependency-cruiser
   * will only specify this attribute for TypeScript and when the 'tsPreCompilationDeps'
   * option has either the value `true` or `"specify"`.
   */
  typeOnly?: boolean;
  /**
   * If following this dependency will ultimately return to the source (circular === true),
   * this attribute will contain an (ordered) array of module names that shows (one of) the
   * circular path(s)
   */
  cycle?: string[];
  /**
   * the type of inclusion - local, core, unknown (= we honestly don't know), undetermined (=
   * we didn't bother determining it) or one of the npm dependencies defined in a package.json
   * ('npm' for 'dependencies', 'npm-dev', 'npm-optional', 'npm-peer', 'npm-no-pkg' for
   * development, optional, peer dependencies and dependencies in node_modules but not in
   * package.json respectively)
   */
  dependencyTypes: DependencyType[];
  /**
   * true if this dependency is dynamic, false in all other cases
   */
  dynamic: boolean;
  /**
   * true if the dependency was defined by a require not named 'require'
   * false in all other cases
   */
  exoticallyRequired: boolean;
  /**
   * If this dependency was defined by a require not named require (as defined in the
   * exoticRequireStrings option): the string that was used
   */
  exoticRequire?: string;
  /**
   * Whether or not this is a dependency that can be followed any further. This will be
   * 'false' for for core modules, json, modules that could not be resolved to a file and
   * modules that weren't followed because it matches the doNotFollow expression.
   */
  followable: boolean;
  /**
   * the license, if known (usually known for modules pulled from npm, not for local ones)
   */
  license?: string;
  /**
   * 'true' if the file name of this module matches the doNotFollow regular expression
   */
  matchesDoNotFollow?: boolean;
  /**
   * The name of the module as it appeared in the source code, e.g. './main'
   */
  module: string;
  /**
   * If the module specification is an URI with a protocol in it (e.g.
   * `import * as fs from 'node:fs'` or
   * `import stuff from 'data:application/json,some-thing'`) -
   * this attribute holds the protocol part (e.g. 'node:', 'data:', 'file:').
   *
   * Also see https://nodejs.org/api/esm.html#esm_urls
   */
  protocol: ProtocolType;
  /**
   * If the module specification is an URI and contains a mime type, this
   * attribute holds the mime type (e.g. in `import stuff from 'data:application/json,some-thing'
   * `this would be data:application/json)
   *
   * Also see https://nodejs.org/api/esm.html#esm_urls
   */
  mimeType: string;
  moduleSystem: ModuleSystemType;
  /**
   * The (resolved) file name of the module, e.g. 'src/main//index.js'
   */
  resolved: string;
  /**
   * an array of rules violated by this dependency - left out if the dependency is valid
   */
  rules?: IRuleSummary[];
  /**
   * 'true' if this dependency violated a rule; 'false' in all other cases. The violated rule
   * will be in the 'rule' object at the same level.
   */
  valid: boolean;
  /**
   * the (de-normalized) instability of the dependency - also available in
   * the module on the 'to' side of this dependency
   */
  instability: number;
}

export interface IReachable {
  /**
   * The name of the rule where the reachability was defined
   */
  asDefinedInRule: string;
  /**
   * The matchedFrom attribute shows what the 'from' module that causes the 'reachable'
   * information to be what it is. Sometimes the 'asDefinedInRule' is not specific enough -
   * e.g. when the from part can be many modules and/ or contains capturing groups used
   * in the to part of the rule.
   */
  matchedFrom: string;
  /**
   * 'true' if this module is reachable from any of the modules matched by the from part of a
   * reachability-rule in 'asDefinedInRule', 'false' if not.
   */
  value: boolean;
}

export interface IReachesModule {
  /**
   * The (resolved) file name of the module, e.g. 'src/main/index.js'
   */
  source: string;
  /**
   * The path along which the 'to' module is reachable from this one.
   */
  via: string[];
}

export interface IReaches {
  /**
   * The name of the rule where the reachability was defined
   */
  asDefinedInRule: string;
  /**
   * An array of modules that is (transitively) reachable from this module.
   */
  modules: IReachesModule[];
}

/**
 * Data summarizing the found dependencies
 */
export interface ISummary {
  /**
   * the number of errors in the dependencies
   */
  error: number;
  /**
   * the number of ignored notices in the dependencies
   */
  ignore: number;
  /**
   * the number of informational level notices in the dependencies
   */
  info: number;
  optionsUsed: IOptions;
  /**
   * rules used in the cruise
   */
  ruleSetUsed?: IFlattenedRuleSet;
  /**
   * the number of modules cruised
   */
  totalCruised: number;
  /**
   * the number of dependencies cruised
   */
  totalDependenciesCruised?: number;
  /**
   * A list of violations found in the dependencies. The dependencies themselves also contain
   * this information, this summary is here for convenience.
   */
  violations: IViolation[];
  /**
   * the number of warnings in the dependencies
   */
  warn: number;
}

/**
 * the (command line) options used to generate the dependency-tree
 */
export interface IOptions extends ICruiseOptions {
  /**
   * arguments passed on the command line
   */
  args?: string;
  /**
   * File the output was written to ('-' for stdout)
   */
  outputTo?: string;
  /**
   * The rules file used to validate the dependencies (if any)
   */
  rulesFile?: string;
}

export interface IFolderDependency {
  /**
   * the (resolved) name of the dependency
   */
  name: string;
  /**
   * 'true' if this folder dependency violated a rule; 'false' in all other
   * cases. The violated rule will be in the 'rules' object at the same level.
   */
  valid: boolean;
  /**
   * the instability of the dependency (de-normalized - this is a duplicate of
   * the one found in the instability of the folder with the same name)
   */
  instability?: number;
  /**
   * 'true' if following this dependency will ultimately return to the source, false in all
   * other cases
   */
  circular: boolean;
  /**
   * If following this dependency will ultimately return to the source (circular === true),
   * this attribute will contain an (ordered) array of module names that shows (one of) the
   * circular path(s)
   */
  cycle?: string[];
  /**
   * an array of rules violated by this dependency - left out if the dependency
   * is valid
   */
  rules?: IRuleSummary[];
}

export interface IFolderDependent {
  /**
   * name ('path') of the dependent
   */
  name: string;
}

export interface IFolder {
  /**
   * The name of the folder. FOlder names are normalized to posix (so
   * separated by forward slashes e.g.: src/things/morethings)
   */
  name: string;
  /**
   * List of folders depending on this folder
   */
  dependents?: IFolderDependent[];
  /**
   * List of folders this folder depends upon
   */
  dependencies?: IFolderDependency[];
  /**
   * The total number of modules detected in this folder and its sub-folders
   */
  moduleCount: number;
  /**
   * The number of modules outside this folder that depend on modules
   * within this folder. Only present when dependency-cruiser was
   * "asked to calculate it.
   */
  afferentCouplings?: number;
  /**
   * The number of modules inside this folder that depend on modules
   * outside this folder. Only present when dependency-cruiser was
   * asked to calculate it.
   */
  efferentCouplings?: number;
  /**
   * efferentCouplings/ (afferentCouplings + efferentCouplings)
   *
   * A measure for how stable the folder is; ranging between 0
   * (completely stable folder) to 1 (completely instable folder)
   * Note that while 'instability' has a negative connotation it's also
   * (unavoidable in any meaningful system. It's the basis of Martin's
   * variable component stability principle: 'the instability of a folder
   * should be larger than the folders it depends on'. Only present when
   * dependency-cruiser was asked to calculate it.,
   */
  instability?: number;
}

export * from "./violations";
export * from "./rule-summary";
