import { Awaitable } from '@ntnyq/utils';
export { unindent as $, unindent } from '@ntnyq/utils';
import Stylelint, { LinterOptions } from 'stylelint';
import * as postcss from 'postcss';
import { Warning } from 'postcss';

type StylelintLinterResult = Omit<Stylelint.LinterResult, 'cwd' | 'report'>;
/**
 * @pg
 */
type LintResultDeprecation = {
    text: string;
    reference?: string;
};
type LintResultInvalidOptionWarning = {
    text: string;
};
type LintResultParseError = Warning & {
    stylelintType: 'parseError';
};
type LintResultWarning = Stylelint.Warning;
/**
 * @pg
 */
type LintResultMessage = LintResultDeprecation | LintResultInvalidOptionWarning | LintResultParseError | LintResultWarning;
/**
 * Stylelint options
 */
interface StylelintOptions<RuleOptions = any> {
    /**
     * linter options for `stylelint.lint(options)`
     */
    linterOptions?: Stylelint.LinterOptions;
    /**
     * rule options
     */
    ruleOptions?: RuleOptions;
    /**
     * stylelint config
     */
    stylelintConfig?: Stylelint.Config;
}

/**
 * Test case options
 */
interface TestCasesOptions<RuleOptions = any> {
    /**
     * invalid cases
     */
    invalid?: (string | InvalidTestCase<RuleOptions>)[];
    /**
     * valid cases
     */
    valid?: (string | ValidTestCase<RuleOptions>)[];
    /**
     * callback to be called after each test case
     */
    onResult?: (testCase: NormalizedTestCase<RuleOptions>, result: TestExecutionResult) => Promise<void> | void;
}
/**
 * Test execution result
 */
type TestExecutionResult = StylelintLinterResult & {
    /**
     * whether the code was fixed
     */
    fixed?: boolean;
    /**
     * if the rule fixes in multiple steps, the result of each step is present here
     */
    steps?: StylelintLinterResult[];
};

/**
 * default filename when not provided
 */
interface DefaultFilenames {
    css: string;
    less: string;
    postcss: string;
    sass: string;
    scss: string;
    styl: string;
    stylus: string;
    [key: string]: string;
}

/**
 * Rule tester behavior options
 */
type RuleTesterBehaviorOptions = {
    /**
     * the number of times to recursively apply the rule
     *
     * @default 10
     */
    recursive?: false | number;
    /**
     * run verification after applying the fix
     *
     * @default true
     */
    verifyAfterFix?: boolean;
    /**
     * verify that fix allways changes the code
     *
     * @default true
     */
    verifyFixChanges?: boolean;
};
/**
 * Rule tester init options
 */
type RuleTesterInitOptions<RuleOptions = any> = RuleTesterBehaviorOptions & StylelintOptions<RuleOptions> & {
    /**
     * rule name to test
     */
    name: string;
    /**
     * default filenames to be used for tests
     */
    defaultFileNames?: Partial<DefaultFilenames>;
};
/**
 * Rule Tester
 * @pg
 */
interface RuleTester<RuleOptions = any> {
    /**
     * run multiple test cases
     */
    run: (options: TestCasesOptions<RuleOptions>) => Promise<void>;
    /**
     * run a single test case
     */
    each: (arg: TestCase) => Promise<{
        result: TestExecutionResult;
        testcase: NormalizedTestCase<RuleOptions>;
    }>;
    /**
     * run a single invalid test case
     */
    invalid: (arg: InvalidTestCase) => Promise<{
        result: TestExecutionResult;
        testcase: NormalizedTestCase<RuleOptions>;
    }>;
    /**
     * run a single valid test case
     */
    valid: (arg: ValidTestCase) => Promise<{
        result: TestExecutionResult;
        testcase: NormalizedTestCase<RuleOptions>;
    }>;
}

/**
 * Invalid test case
 */
type InvalidTestCase<RuleOptions = any> = string | InvalidTestCaseBase<RuleOptions>;
type InvalidTestCaseBase<RuleOptions = any> = ValidTestCaseBase<RuleOptions> & {
    /**
     * expect for {@link LintResultDeprecation}
     */
    deprecations?: number | (string | LintResultDeprecation)[] | ((deprecations: LintResultDeprecation[]) => Awaitable<void>);
    /**
     * expect for {@link LintResultInvalidOptionWarning}
     */
    invalidOptionWarnings?: number | (string | LintResultInvalidOptionWarning)[] | ((invalidOptionWarnings: LintResultInvalidOptionWarning[]) => Awaitable<void>);
    /**
     * Assert if output is expected.
     * Pass `null` to assert that the output is the same as the input.
     */
    output?: string | ((output: string, input: string) => Awaitable<void>) | null;
    /**
     * expect for {@link LintResultParseError}
     */
    parseErrors?: number | (string | LintResultParseError)[] | ((parseErrors: LintResultParseError[]) => Awaitable<void>);
    /**
     * expect for {@link LintResultWarning}
     */
    warnings?: number | (string | LintResultWarning)[] | ((warnings: LintResultWarning[]) => Awaitable<void>);
};
/**
 * Valid test case
 */
type ValidTestCase<RuleOptions = any> = string | ValidTestCaseBase<RuleOptions>;
type ValidTestCaseBase<RuleOptions = any> = RuleTesterBehaviorOptions & StylelintOptions<RuleOptions> & {
    /**
     * code to test
     */
    code: string;
    /**
     * test case description
     */
    description?: string;
    /**
     * test case filename
     */
    filename?: string;
    /**
     * test case name
     */
    name?: string;
    /**
     * only run this test case
     */
    only?: boolean;
    /**
     * skip this test case
     */
    skip?: boolean;
    /**
     * lint result
     *
     * @deprecated use `after` instead
     */
    onResult?: (result: TestExecutionResult) => Awaitable<void>;
    /**
     * hook after run test case
     */
    after?: (this: NormalizedTestCase<RuleOptions>, result: TestExecutionResult) => Awaitable<void>;
    /**
     * hook before run test case
     */
    before?: (this: NormalizedTestCase<RuleOptions>, linterOptions: LinterOptions) => Awaitable<void>;
};
/**
 * Test case
 * @pg
 */
type TestCase<RuleOptions = any> = InvalidTestCase<RuleOptions> | ValidTestCase<RuleOptions>;
/**
 * Normalized test case
 * @pg
 */
type NormalizedTestCase<RuleOptions = any> = InvalidTestCaseBase<RuleOptions> & {
    code: string;
    filename: string;
    type: 'invalid' | 'valid';
};

/**
 * Shortcut to run test cases for a rule
 */
declare function run<RuleOptions = any>(options: TestCasesOptions<RuleOptions> & RuleTesterInitOptions<RuleOptions>): Promise<void>;
/**
 * Shortcut to run test cases for a rule in classic style
 */
declare function runClassic<RuleOptions = any>(ruleName: string, cases: TestCasesOptions<RuleOptions>, options?: RuleTesterInitOptions<RuleOptions>): Promise<void>;

/**
 * Resolve rule meta by tester options
 * @param options - tester options
 * @returns a promise resolved rule meta or undefined
 */
declare function resolveRuleMeta(options: RuleTesterInitOptions): Promise<Stylelint.RuleMeta | undefined>;

/**
 * Normalize test case
 *
 * @param testCase - test case
 * @param defaultFilenames - given default file name
 * @param type - case type
 * @returns normalized test case
 */
declare function normalizeTestCase(testCase: TestCase, defaultFilenames: Partial<DefaultFilenames>, type?: 'valid' | 'invalid'): NormalizedTestCase;

/**
 * Normalize rule options
 *
 * @param testCase - test case
 * @param options - tester options
 * @returns normalized rule option
 */
declare function resolveRuleOptions(testCase: NormalizedTestCase, options: RuleTesterInitOptions, ruleMeta?: Stylelint.RuleMeta): any[];

declare function validateLintResult(testCase: NormalizedTestCase, lintResult: Stylelint.LintResult): Promise<void>;

/**
 * Normalize test case message
 *
 * @param message - message string or lint result
 * @returns normalized message
 */
declare function normalizeCaseMessage(message: string | LintResultMessage): Partial<LintResultMessage>;

/**
 * Resolve linter options of stylelint
 *
 * @param options - tester init options
 * @param testCase - normalized test case
 * @returns resolved linter options
 */
declare function resolveLinterOptions(options: RuleTesterInitOptions, testCase: NormalizedTestCase, ruleOptions: any): Stylelint.LinterOptions;

/**
 * Normalize linter result
 *
 * @param result - linter result {@link Stylelint.LinterResult}
 */
declare function normalizeLinterResult(result: Stylelint.LinterResult): {
    results: {
        deprecations: {
            text: string;
            reference?: string;
        }[];
        invalidOptionWarnings: {
            text: string;
        }[];
        parseErrors: (postcss.Warning & {
            stylelintType: Extract<Stylelint.StylelintWarningType, "parseError">;
        })[];
        errored?: boolean;
        warnings: Stylelint.Warning[];
        ignored?: boolean;
    }[];
    errored: boolean;
    output: string;
    _output?: string;
    _outputWarned?: boolean;
    code?: string;
    maxWarningsExceeded?: {
        maxWarnings: number;
        foundWarnings: number;
    };
    reportedDisables: Stylelint.DisableOptionsReport;
    descriptionlessDisables?: Stylelint.DisableOptionsReport;
    needlessDisables?: Stylelint.DisableOptionsReport;
    invalidScopeDisables?: Stylelint.DisableOptionsReport;
    ruleMetadata: {
        [ruleName: string]: Partial<Stylelint.RuleMeta>;
    };
};

declare function createRuleTester<RuleOptions = any>(options: RuleTesterInitOptions<RuleOptions>): RuleTester<RuleOptions>;

export { type DefaultFilenames, type InvalidTestCase, type InvalidTestCaseBase, type LintResultDeprecation, type LintResultInvalidOptionWarning, type LintResultMessage, type LintResultParseError, type LintResultWarning, type NormalizedTestCase, type RuleTester, type RuleTesterBehaviorOptions, type RuleTesterInitOptions, type StylelintLinterResult, type StylelintOptions, type TestCase, type TestCasesOptions, type TestExecutionResult, type ValidTestCase, type ValidTestCaseBase, createRuleTester, normalizeCaseMessage, normalizeLinterResult, normalizeTestCase, resolveLinterOptions, resolveRuleMeta, resolveRuleOptions, run, runClassic, validateLintResult };
