import { Fragment } from '@yankeeinlondon/happy-wrapper';
import { Grammar } from 'prismjs';
import * as _yankeeinlondon_builder_api from '@yankeeinlondon/builder-api';
import { Pipeline, PipelineStage } from '@yankeeinlondon/builder-api';

type HSL = `hsl(${number}${string}, ${number}${string}, ${number}${string})`;
type HSLA = `hsla(${number}${string}, ${number}${string}, ${number}${string}, ${number}${string})`;
type RGB = `rgb(${number}${string}, ${number}${string}, ${number}${string})`;
type RGBA = `hsla(${number}${string}, ${number}${string}, ${number}${string}, ${number}${string})`;
type HEX = `#${string}`;
type Opacity = `/${number}` | "";
type Color = `${HSL | HSLA | RGB | RGBA | HEX | ""}${Opacity}`;
type ColorByMode = [light: Color, dark: Color];
/**
 * Coloration/Theming of a code block
 */
interface CodeColorTheme<T extends Color | ColorByMode> {
    foreground: T;
    background: T;
    lineNumber: T;
    lineNumberGutter: T;
    highlight: T;
    /** the background color for text selection */
    textSelection: T;
    /**
     * used in some grammars like SCSS where you might have `@apply`
     * but will same color as
     */
    atrule?: T;
    keyword: T;
    attribute?: T;
    deleted?: T;
    inserted?: T;
    url?: T;
    function?: T;
    functionName?: T;
    class?: T;
    className?: T;
    builtin: T;
    tag?: T;
    comment: T;
    blockComment?: T;
    doctype?: T;
    cdata?: T;
    prolog?: T;
    pseudoElement?: T;
    pseudoClass?: T;
    property?: T;
    /** will use same as "property" if not expressed separately */
    constant?: T;
    /** will use same as "property" if not expressed separately */
    variable?: T;
    /** string values */
    string: T;
    /** literal value; will use string if not specified */
    literal?: T;
    /** uses the same as "string" by default */
    char?: T;
    /** a boolean value; uses `builtin` if not specified */
    boolean?: T;
    /** uses `builtin` if not specified */
    selector?: T;
    placeholder?: T;
    /** uses `builtin` if not specified */
    important?: T;
    /** uses `builtin` if not specified */
    delimiter?: T;
    /** any language symbol not otherwise matched, will use keyword if not stated */
    symbol?: T;
    /** will use "symbol" if not defined */
    entity?: T;
    /** numeric values; will use symbol if not expressed separately  */
    number?: T;
    namespace?: T;
    /**
     * A form of recognized punctuation in the code
     */
    punctuation?: T;
    punctuationFirstChild?: T;
    /** will use same as "punctuation" if not expressed separately */
    operator?: T;
    /** will use same as "punctuation" if not expressed separately */
    attrName?: T;
    attrValue?: T;
    attrValueForPunctuation?: T;
    attrValueForPunctuationFirstChild?: T;
    decorator?: T;
    regex: T;
    hexcode?: T;
    unit?: T;
    id?: T;
    jsonProperty?: T;
    /** .language-markup .token.tag */
    markupTag?: T;
    /** .language-markup .token.attr-name */
    markupAttrName?: T;
    /** .language-markup .token.punctuation  */
    markupPunctuation?: T;
    headingText?: T;
    footerText?: T;
    lineHighlightBackground?: T;
    /**
     * the text color for a highlighted line
     * ```css
     * .line-highlight.line-highlight:before,
     * .line-highlight.line-highlight[data-end]:after {}
     * ```
     */
    lineHighlightBeforeAfter?: T;
    /** the background color for a highlighted line */
    lineHighlightBackgroundBeforeAfter?: T;
}

/**
 * A callback called for each line of a code block and responsible
 * for stating any _additional_ classes to add for this given line
 */
type LineCallback = (
/** the code, _only_ for the given line */
line: string, 
/** the full code block */
code: string, 
/** the language being converted to */
lang: string) => string;
/**
 * Modifiers are single character tokens which are allowed
 * to precede the "language" in a fence statement to provide
 * some binary instruction to how to handle the code block.
 */
declare enum Modifier {
    /**
     * the pound symbol is used to indicate that line numbers
     * for a code block should _always_ be used regardless of
     * the option set
     */
    "#" = "#",
    /**
     * using an asterisk modifier will force the line numbers
     * of a code block to NOT be used regardless of configuration
     */
    "*" = "*",
    /**
     * the exclamation modifier indicates that a code block should
     * _toggle_ the normal configuration for escape code interpolation
     */
    "!" = "!"
}
type CodeParsingStage = "code" | "dom" | "complete";
interface HighlightLine {
    kind: "line";
    line: number;
}
interface HighlightRange {
    kind: "range";
    from: number;
    to: number;
}
interface HighlightSymbol {
    kind: "symbol";
    symbol: string;
}
/**
 * Tokens representing intent to highlight code; originating from any source
 * [highlight prop, csv, VuePress/Vitepress syntax]
 */
type HighlightToken = HighlightLine | HighlightRange | HighlightSymbol;
type RangeExpression = `${string}-${string}`;
/** the ways in which the user might express the "highlight" property */
type HighlightExpression = number | RangeExpression | {
    symbol: string;
} | {
    from: number;
    to: number;
};
interface CodeBlockProperties {
    /**
     * Indicates what lines to highlight, this can be:
     * - a single line number
     * - a line range
     * - a line with a give token block (TODO: figure out how to model)
     */
    highlight?: HighlightExpression | HighlightExpression[];
    /**
     * Allows pointing to an external file as the code source
     */
    filename?: string;
    /** classes to add to the heading section */
    heading?: string;
    /** classes to add to the footer section */
    footer?: string;
    /** classes to add to the codeblock section */
    class?: string;
    /** style properties to add to the codeblock section */
    style?: string;
    width?: number | string;
    height?: number | string;
    alt?: string;
    tooltip?: any;
    "data-codeLines"?: number;
    [key: string]: any;
}
type CodeFilename = boolean | "filename" | "with-path" | `name:${string}`;
/**
 * When a fence block is encountered it will be parsed
 * into the following structure for evaluation.
 */
interface CodeBlockMeta<S extends CodeParsingStage> {
    /**
     * The finalized HTML based on the code pipeline
     */
    html: S extends "complete" ? string : never;
    /** the <pre> block wrapper */
    pre: S extends "code" ? string : Fragment;
    lineNumbersWrapper: S extends "code" ? string : Fragment;
    codeBlockWrapper: S extends "code" ? string : Fragment;
    /**
     * All highlighting information will be captured as
     * an array of `HighlightToken` tokens.
     */
    highlightTokens: HighlightToken[];
    /**
     * The external filename which the code block is importing (`null` if code is
     * not external).
     */
    externalFile: string | null;
    /**
     * Specifies whether the filename should be displayed above the code block.
     * With _external code references_ the boolean turns this on/off and when
     * "on" it displays just the filename. You may also be more explicit by using
     * the `filename` or `with-path` string literals.
     *
     * Finally, should you want to explicitly state a filename -- useful for
     * non-external code -- you can put in a string value prefixed with `name:`.
     * If you are using an external file, this will override the actual file name.
     */
    showFilename: CodeFilename;
    /**
     * Typically _not_ used but when a code block references an external file
     * AND the local code block _also_ has code, then it will be placed here
     */
    aboveTheFoldCode?: S extends "code" ? string : Fragment;
    /**
     * The code block; represented as either a string or a DOM tree
     * based on lifecycle
     */
    code: S extends "code" ? string : Fragment;
    /**
     * An optional heading to put above the code block
     */
    heading?: S extends "code" ? string : Fragment;
    /**
     * An optional footer to put under the code block
     */
    footer?: S extends "code" ? string : Fragment;
    /**
     * The number of lines in the code block
     */
    codeLinesCount: S extends "code" ? never : number;
    /**
     * The tagName for the block; will be `code` except for edge cases
     */
    tag: string;
    /**
     * The nesting level
     */
    level: number;
    /**
     * The language used by the highlighter
     */
    lang: string;
    /** The originally requested language by user */
    requestedLang: string;
    /**
     * An optional message that a pipeline function can export and will
     * be picked up by trace() utility
     */
    trace?: string;
    /**
     * The properties found on the top line (to right of language and back ticks), these
     * key/value pairs will be assigned to the
     */
    props: CodeBlockProperties;
    modifiers: Modifier[];
    /**
     * not sure how useful this is yet ... currently always evaluates to three
     * back ticks
     */
    markup: string;
}
/**
 * A callback for a block node which provides build-time capability to
 * modify a property with a callback
 */
type BlockCallback<T> = <S extends CodeParsingStage>(fence: CodeBlockMeta<S>, filename: string, frontmatter: Pipeline<PipelineStage.parser>["frontmatter"]) => T;
/**
 * Options for the `code-builder` extension
 */
interface CodeOptions {
    /**
     * The language to use for code blocks that specify a language that Prism does not know.
     *
     * @default 'plain'
     */
    defaultLanguageForUnknown?: string;
    /**
     * The language to use for code blocks that do not specify a language.
     *
     * @default 'plain'
     */
    defaultLanguageForUnspecified: string;
    /**
     * Shorthand to set both {@code defaultLanguageForUnknown} and {@code defaultLanguageForUnspecified} to the same value. Will be copied
     * to each option if it is set to {@code undefined}.
     */
    defaultLanguage?: string;
    /**
     * Hook into the fence mutation process _before_ the builder
     * gets involved.
     */
    before: (fence: CodeBlockMeta<"code">, payload: Pipeline<PipelineStage.parser>, options: CodeOptions) => CodeBlockMeta<"code">;
    /**
     * Hook into the fence mutation process _after_ the builder
     * has mutated CodeFenceMeta to it's configured rules.
     */
    after: (fence: CodeBlockMeta<"dom">, payload: Pipeline<PipelineStage.parser>, options: CodeOptions) => CodeBlockMeta<"dom">;
    /**
     * By default each _line_ in the code will be given a class of "line" but you can override this
     * default behavior in one of the following ways:
     *
     * 1. if for some reason you want to _change_ the class name you may pass in a static string value
     * which will be used instead of "line".
     * 2. if you pass in a `LineCallback` function you will receive the code on that line along with the language and you can opt to _add_ additional classes (the "line" class will persist regardless of what you return)
     * 3. if you want _no classes_ then you can pass in a `false` value to indicate this
     */
    lineClass?: string | false | LineCallback;
    /**
     * The vuepress/vitepress implementation of code blocks appears to use an interesting
     * DOM structure to bring in line numbers that didn't feel naturally intuitive (but may
     * have been done for very good reason). If you wish to use this structure you can configure
     * to use the `flex-lines` style.
     *
     * By default we use the 'tabular' layout which feels more intuitive and has full testing
     * behind it (in this repo).
     *
     * @default 'tabular'
     */
    layoutStructure: "flex-lines" | "tabular";
    /**
     * Any default classes to add to the header region (when region is found to exist);
     * if you override this be aware that some styling may expect the default "heading" class
     * to exist.
     *
     * @default 'heading'
     */
    headingClasses?: string[] | BlockCallback<string[]>;
    /**
     * Any default classes to add to the footer region (when region is found to exist);
     * if you override this be aware that some styling may expect the default "footer" class
     * to exist.
     *
     * @default 'footer'
     */
    footerClasses?: string[] | BlockCallback<string[]>;
    /**
     * Allows to turn on/off the feature of highlighting lines in code; this is just a "default"
     * as individual code blocks can explicitly ask for line numbers with the `#` modifier
     *
     * @default false
     */
    lineNumbers: boolean;
    /**
     * Flag indicating whether to display the language name in the upper right
     * of the code block.
     *
     * @default true
     */
    showLanguage: boolean | BlockCallback<boolean>;
    /**
     * Allows to turn on/off the feature of _highlighting_ lines in code; lines will never
     * be highlighted unless the page has instructions to highlight particular lines but
     * this allows all highlights to be explicitly turned off
     *
     * @default true
     */
    highlightLines: boolean | BlockCallback<boolean>;
    /**
     * Adds a clipboard icon to the header row and injects the functionality to
     * copy code block contents to the clipboard.
     *
     * @default false
     */
    clipboard: boolean | BlockCallback<boolean>;
    /**
     * The `copyToClipboard()` and `clipboardAvailable()` functions are automatically
     * added to pages which have a code block on the page which requests this functionality
     * but you can also just ask for it to be included in call pages so you can use these
     * functions for your own evil plans.
     */
    provideClipboardFunctionality: boolean | BlockCallback<boolean>;
    theme?: "base" | "solarizedLight" | "material" | "dracula" | "tomorrow" | "duotone" | CodeColorTheme<any>;
    /**
     * By default light mode has code blocks with light backgrounds, and dark mode with
     * dark backgrounds. If you want to _invert_ that you can by setting this property to
     * true.
     *
     * @default false
     */
    invertColorMode?: boolean;
    /**
     * Should you want to add your own language grammar you can:
     * [Extending Prism Language Definitions](https://prismjs.com/extending.html#language-definitions)
     */
    languageGrammars?: Record<string, Grammar>;
    /**
     * Allows stating whether you want meta-data about the code block injected
     * into the Frontmatter. When resolving to a `true` the code block will be added
     * to the Frontmatter property "_codeBlocks".
     *
     * @default false
     */
    injectIntoFrontmatter?: boolean | BlockCallback<boolean>;
    /**
     * Code blocks will default to the following inline style:
     * ```css
     * .code-block {
     *    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
     * }
     * ```
     *
     * but you can override this inline style if you wish. Obviously you can also just
     * target the `.code-block` class to whatever you like
     */
    codeFont?: string;
}

/**
 * `code` Builder API
 *
 * Provides highlighting of code features to `vite-plugin-md`
 */
declare const plugin: _yankeeinlondon_builder_api.BuilderApi<Partial<CodeOptions>, "parser">;

export { CodeBlockMeta, CodeOptions, CodeParsingStage, plugin as default };
