import * as ESTree from "estree";
import { JSONSchema4 } from "json-schema";
import { LegacyESLint } from "./use-at-your-own-risk";

export namespace AST {
    type TokenType =
        | "Boolean"
        | "Null"
        | "Identifier"
        | "Keyword"
        | "Punctuator"
        | "JSXIdentifier"
        | "JSXText"
        | "Numeric"
        | "String"
        | "RegularExpression";

    interface Token {
        type: TokenType;
        value: string;
        range: Range;
        loc: SourceLocation;
    }

    interface SourceLocation {
        start: ESTree.Position;
        end: ESTree.Position;
    }

    type Range = [number, number];

    interface Program extends ESTree.Program {
        comments: ESTree.Comment[];
        tokens: Token[];
        loc: SourceLocation;
        range: Range;
    }
}

export namespace Scope {
    interface ScopeManager {
        scopes: Scope[];
        globalScope: Scope | null;

        acquire(node: ESTree.Node, inner?: boolean): Scope | null;

        getDeclaredVariables(node: ESTree.Node): Variable[];
    }

    interface Scope {
        type:
            | "block"
            | "catch"
            | "class"
            | "for"
            | "function"
            | "function-expression-name"
            | "global"
            | "module"
            | "switch"
            | "with"
            | "TDZ";
        isStrict: boolean;
        upper: Scope | null;
        childScopes: Scope[];
        variableScope: Scope;
        block: ESTree.Node;
        variables: Variable[];
        set: Map<string, Variable>;
        references: Reference[];
        through: Reference[];
        functionExpressionScope: boolean;
    }

    interface Variable {
        name: string;
        scope: Scope;
        identifiers: ESTree.Identifier[];
        references: Reference[];
        defs: Definition[];
    }

    interface Reference {
        identifier: ESTree.Identifier;
        from: Scope;
        resolved: Variable | null;
        writeExpr: ESTree.Node | null;
        init: boolean;

        isWrite(): boolean;

        isRead(): boolean;

        isWriteOnly(): boolean;

        isReadOnly(): boolean;

        isReadWrite(): boolean;
    }

    type DefinitionType =
        | { type: "CatchClause"; node: ESTree.CatchClause; parent: null }
        | { type: "ClassName"; node: ESTree.ClassDeclaration | ESTree.ClassExpression; parent: null }
        | { type: "FunctionName"; node: ESTree.FunctionDeclaration | ESTree.FunctionExpression; parent: null }
        | { type: "ImplicitGlobalVariable"; node: ESTree.Program; parent: null }
        | {
            type: "ImportBinding";
            node: ESTree.ImportSpecifier | ESTree.ImportDefaultSpecifier | ESTree.ImportNamespaceSpecifier;
            parent: ESTree.ImportDeclaration;
        }
        | {
            type: "Parameter";
            node: ESTree.FunctionDeclaration | ESTree.FunctionExpression | ESTree.ArrowFunctionExpression;
            parent: null;
        }
        | { type: "TDZ"; node: any; parent: null }
        | { type: "Variable"; node: ESTree.VariableDeclarator; parent: ESTree.VariableDeclaration };

    type Definition = DefinitionType & { name: ESTree.Identifier };
}

// #region SourceCode

export class SourceCode {
    text: string;
    ast: AST.Program;
    lines: string[];
    hasBOM: boolean;
    parserServices: SourceCode.ParserServices;
    scopeManager: Scope.ScopeManager;
    visitorKeys: SourceCode.VisitorKeys;

    constructor(text: string, ast: AST.Program);
    constructor(config: SourceCode.Config);

    static splitLines(text: string): string[];

    getText(node?: ESTree.Node, beforeCount?: number, afterCount?: number): string;

    getLines(): string[];

    getAllComments(): ESTree.Comment[];

    getAncestors(node: ESTree.Node): ESTree.Node[];

    getDeclaredVariables(node: ESTree.Node): Scope.Variable[];

    getJSDocComment(node: ESTree.Node): ESTree.Comment | null;

    getNodeByRangeIndex(index: number): ESTree.Node | null;

    isSpaceBetweenTokens(first: AST.Token, second: AST.Token): boolean;

    getLocFromIndex(index: number): ESTree.Position;

    getIndexFromLoc(location: ESTree.Position): number;

    // Inherited methods from TokenStore
    // ---------------------------------

    getTokenByRangeStart(offset: number, options?: { includeComments: false }): AST.Token | null;
    getTokenByRangeStart(offset: number, options: { includeComments: boolean }): AST.Token | ESTree.Comment | null;

    getFirstToken: SourceCode.UnaryNodeCursorWithSkipOptions;

    getFirstTokens: SourceCode.UnaryNodeCursorWithCountOptions;

    getLastToken: SourceCode.UnaryNodeCursorWithSkipOptions;

    getLastTokens: SourceCode.UnaryNodeCursorWithCountOptions;

    getTokenBefore: SourceCode.UnaryCursorWithSkipOptions;

    getTokensBefore: SourceCode.UnaryCursorWithCountOptions;

    getTokenAfter: SourceCode.UnaryCursorWithSkipOptions;

    getTokensAfter: SourceCode.UnaryCursorWithCountOptions;

    getFirstTokenBetween: SourceCode.BinaryCursorWithSkipOptions;

    getFirstTokensBetween: SourceCode.BinaryCursorWithCountOptions;

    getLastTokenBetween: SourceCode.BinaryCursorWithSkipOptions;

    getLastTokensBetween: SourceCode.BinaryCursorWithCountOptions;

    getTokensBetween: SourceCode.BinaryCursorWithCountOptions;

    getTokens:
        & ((node: ESTree.Node, beforeCount?: number, afterCount?: number) => AST.Token[])
        & SourceCode.UnaryNodeCursorWithCountOptions;

    commentsExistBetween(
        left: ESTree.Node | AST.Token | ESTree.Comment,
        right: ESTree.Node | AST.Token | ESTree.Comment,
    ): boolean;

    getCommentsBefore(nodeOrToken: ESTree.Node | AST.Token): ESTree.Comment[];

    getCommentsAfter(nodeOrToken: ESTree.Node | AST.Token): ESTree.Comment[];

    getCommentsInside(node: ESTree.Node): ESTree.Comment[];

    getScope(node: ESTree.Node): Scope.Scope;

    isSpaceBetween(
        first: ESTree.Node | AST.Token,
        second: ESTree.Node | AST.Token,
    ): boolean;

    markVariableAsUsed(name: string, refNode?: ESTree.Node): boolean;
}

export namespace SourceCode {
    interface Config {
        text: string;
        ast: AST.Program;
        parserServices?: ParserServices | undefined;
        scopeManager?: Scope.ScopeManager | undefined;
        visitorKeys?: VisitorKeys | undefined;
    }

    type ParserServices = any;

    interface VisitorKeys {
        [nodeType: string]: string[];
    }

    interface UnaryNodeCursorWithSkipOptions {
        <T extends AST.Token>(
            node: ESTree.Node,
            options:
                | ((token: AST.Token) => token is T)
                | {
                    filter: (token: AST.Token) => token is T;
                    includeComments?: false | undefined;
                    skip?: number | undefined;
                },
        ): T | null;
        <T extends AST.Token | ESTree.Comment>(
            node: ESTree.Node,
            options: {
                filter: (tokenOrComment: AST.Token | ESTree.Comment) => tokenOrComment is T;
                includeComments: boolean;
                skip?: number | undefined;
            },
        ): T | null;
        (
            node: ESTree.Node,
            options?:
                | {
                    filter?: ((token: AST.Token) => boolean) | undefined;
                    includeComments?: false | undefined;
                    skip?: number | undefined;
                }
                | ((token: AST.Token) => boolean)
                | number,
        ): AST.Token | null;
        (
            node: ESTree.Node,
            options: {
                filter?: ((token: AST.Token | ESTree.Comment) => boolean) | undefined;
                includeComments: boolean;
                skip?: number | undefined;
            },
        ): AST.Token | ESTree.Comment | null;
    }

    interface UnaryNodeCursorWithCountOptions {
        <T extends AST.Token>(
            node: ESTree.Node,
            options:
                | ((token: AST.Token) => token is T)
                | {
                    filter: (token: AST.Token) => token is T;
                    includeComments?: false | undefined;
                    count?: number | undefined;
                },
        ): T[];
        <T extends AST.Token | ESTree.Comment>(
            node: ESTree.Node,
            options: {
                filter: (tokenOrComment: AST.Token | ESTree.Comment) => tokenOrComment is T;
                includeComments: boolean;
                count?: number | undefined;
            },
        ): T[];
        (
            node: ESTree.Node,
            options?:
                | {
                    filter?: ((token: AST.Token) => boolean) | undefined;
                    includeComments?: false | undefined;
                    count?: number | undefined;
                }
                | ((token: AST.Token) => boolean)
                | number,
        ): AST.Token[];
        (
            node: ESTree.Node,
            options: {
                filter?: ((token: AST.Token | ESTree.Comment) => boolean) | undefined;
                includeComments: boolean;
                count?: number | undefined;
            },
        ): Array<AST.Token | ESTree.Comment>;
    }

    interface UnaryCursorWithSkipOptions {
        <T extends AST.Token>(
            node: ESTree.Node | AST.Token | ESTree.Comment,
            options:
                | ((token: AST.Token) => token is T)
                | {
                    filter: (token: AST.Token) => token is T;
                    includeComments?: false | undefined;
                    skip?: number | undefined;
                },
        ): T | null;
        <T extends AST.Token | ESTree.Comment>(
            node: ESTree.Node | AST.Token | ESTree.Comment,
            options: {
                filter: (tokenOrComment: AST.Token | ESTree.Comment) => tokenOrComment is T;
                includeComments: boolean;
                skip?: number | undefined;
            },
        ): T | null;
        (
            node: ESTree.Node | AST.Token | ESTree.Comment,
            options?:
                | {
                    filter?: ((token: AST.Token) => boolean) | undefined;
                    includeComments?: false | undefined;
                    skip?: number | undefined;
                }
                | ((token: AST.Token) => boolean)
                | number,
        ): AST.Token | null;
        (
            node: ESTree.Node | AST.Token | ESTree.Comment,
            options: {
                filter?: ((token: AST.Token | ESTree.Comment) => boolean) | undefined;
                includeComments: boolean;
                skip?: number | undefined;
            },
        ): AST.Token | ESTree.Comment | null;
    }

    interface UnaryCursorWithCountOptions {
        <T extends AST.Token>(
            node: ESTree.Node | AST.Token | ESTree.Comment,
            options:
                | ((token: AST.Token) => token is T)
                | {
                    filter: (token: AST.Token) => token is T;
                    includeComments?: false | undefined;
                    count?: number | undefined;
                },
        ): T[];
        <T extends AST.Token | ESTree.Comment>(
            node: ESTree.Node | AST.Token | ESTree.Comment,
            options: {
                filter: (tokenOrComment: AST.Token | ESTree.Comment) => tokenOrComment is T;
                includeComments: boolean;
                count?: number | undefined;
            },
        ): T[];
        (
            node: ESTree.Node | AST.Token | ESTree.Comment,
            options?:
                | {
                    filter?: ((token: AST.Token) => boolean) 