import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import React from 'react';
import { Breadcrumbs } from '@workday/canvas-kit-react/breadcrumbs';
import { Hyperlink } from '@workday/canvas-kit-react/button';
import { createComponent } from '@workday/canvas-kit-react/common';
import { Dialog, useDialogModel } from '@workday/canvas-kit-react/dialog';
import { createStencil, handleCsProp, px2rem } from '@workday/canvas-kit-styling';
import { base, system } from '@workday/canvas-tokens-web';
import { MDX, MdxJSToJSX } from './MDXElements';
import { Value } from './Value';
import { docs } from './docs';
/**
 * This context allows us to keep track if we're within a nested stack of dialog
 */
const NestedContext = React.createContext(false);
/**
 * Context to help keep track of breadcrumbs and update them
 */
const SymbolDocBreadcrumbsContext = React.createContext(undefined);
/** React context to track the current rendering context to avoid tables inside tables */
export const RenderContext = React.createContext('table');
/**
 * React context to track the current heading level. This is useful to relatively change heading
 * levels of a subtree
 */
export const HeadingLevelContext = React.createContext(3);
/**
 * React context to keep track of the current indent level
 */
export const IndentLevelContext = React.createContext(0);
/**
 * Utility function for getting non-breaking space for code formatting. It will print a string of
 * non-breaking spaces according to the level provided. These characters are safe for a React render
 * function.
 */
export function indent(level) {
    return [...Array(level * 2)].map(v => '\u00A0').join('');
}
export function capitalize(input) {
    return input.charAt(0).toUpperCase() + input.slice(1);
}
/**
 * Special heading component that uses @mdx-js/react heading components, but is also aware of
 * relative heading levels.
 */
export const Heading = createComponent('h4')({
    displayName: 'MDXHeading',
    Component({ headingOffset = 0, ...elemProps }) {
        const headingLevel = React.useContext(HeadingLevelContext);
        const as = `h${headingLevel + headingOffset}`; // Make Typescript happy
        return _jsx(MDX, { as: as, ...elemProps });
    },
});
export function renderTypeParameters(typeParameters) {
    return typeParameters && typeParameters.length ? (_jsxs(RenderContext.Provider, { value: "inline", children: [_jsx("span", { children: "<" }), typeParameters.map((p, index) => {
                return (_jsxs(React.Fragment, { children: [index !== 0 && _jsx("span", { className: "token punctuation", children: ", " }), _jsx(Value, { value: p })] }, index));
            }), _jsx("span", { children: ">" })] })) : ('');
}
const ButtonHyperLink = Hyperlink.as('button');
export const SymbolDialog = ({ value }) => {
    const [symbol, setSymbol] = React.useState(undefined);
    const { breadcrumbsList, updateBreadcrumbs } = React.useContext(SymbolDocBreadcrumbsContext);
    const nestedContext = React.useContext(NestedContext);
    const model = useDialogModel({
        onShow() {
            setSymbol(docs.find(d => value.name === d.name) || undefined);
        },
        onHide() {
            // Reset breadcrumbs when the dialog is closed
            updateBreadcrumbs([]);
        },
    });
    const handleTargetClick = (e) => {
        e.preventDefault();
        updateBreadcrumbs(breadcrumbsList.concat(value.name));
    };
    const handleBreadcrumbClick = (e, index) => {
        e.preventDefault();
        if (e.currentTarget.textContent &&
            breadcrumbsList.length &&
            breadcrumbsList.indexOf(e.currentTarget.textContent) > -1) {
            // Slice breadcrumbs list up to but not including the current item clicked
            updateBreadcrumbs(breadcrumbsList.slice(0, index + 1));
        }
    };
    // If we're inside a nested context, we render a Hyperlink. This button will update the dialog instead of rendering a new one.
    if (nestedContext) {
        return (_jsx(ButtonHyperLink, { cs: {
                border: 'none',
                background: 'transparent',
                fontSize: 'inherit',
                fontFamily: 'inherit',
            }, onClick: handleTargetClick, children: value.displayName || value.name }));
    }
    return (_jsx(NestedContext.Provider, { value: true, children: _jsxs(Dialog, { model: model, children: [_jsx(Dialog.Target, { as: ButtonHyperLink, className: "token symbol", onClick: handleTargetClick, cs: {
                        border: 'none',
                        background: 'transparent',
                        fontSize: 'inherit',
                        fontFamily: 'inherit',
                    }, "aria-haspopup": "true", children: value.displayName || value.name }), renderTypeParameters(value.typeParameters), _jsx(Dialog.Popper, { children: _jsxs(Dialog.Card, { cs: { maxHeight: '50vh', maxWidth: '90vh', minWidth: px2rem(600) }, children: [_jsx(Dialog.CloseIcon, {}), _jsxs(Dialog.Heading, { children: [value.name, " "] }), breadcrumbsList.length > 1 && (_jsx(Breadcrumbs, { "aria-label": "Breadcrumbs", children: _jsx(Breadcrumbs.List, { cs: { paddingInline: system.padding.xs }, children: breadcrumbsList.map((item, index) => {
                                        return (_jsx(_Fragment, { children: index === breadcrumbsList.length - 1 ? (_jsx(Breadcrumbs.CurrentItem, { children: item }, index)) : (_jsx(Breadcrumbs.Item, { children: _jsx(Breadcrumbs.Link, { onClick: e => handleBreadcrumbClick(e, index), href: '#', children: item }) }, index)) }));
                                    }) }) })), _jsx(Dialog.Body, { children: _jsx(RenderContext.Provider, { value: "table", children: _jsx(IndentLevelContext.Provider, { value: 0, children: symbol ? (_jsx(SymbolDoc, { name: breadcrumbsList.length >= 1
                                                ? breadcrumbsList[breadcrumbsList.length - 1]
                                                : value.name, headingStart: 3, hideHeading: true })) : (_jsxs(_Fragment, { children: [_jsx("p", { children: "Basic type information:" }), _jsx("pre", { children: _jsx("code", { children: value.name }) })] })) }) }) })] }) })] }) }));
};
const symbolDocStencil = createStencil({
    base: {
        marginBlockEnd: system.gap.lg,
        'button[data-symbol]': {
            border: 'none',
            background: 'transparent',
            fontSize: 'inherit',
            fontFamily: 'inherit',
        },
        code: {
            ...system.type.subtext.lg,
            fontFamily: system.fontFamily.mono,
            whiteSpace: 'nowrap',
            '.token': {
                '&.string': { color: base.green700 },
                '&.symbol': { color: base.purple800 },
                '&.property': { color: base.blue800 },
                '&.primitive': { color: base.red800 },
                '&.number': { color: base.red800 },
                '&.boolean': { color: base.red800 },
                '&.variable': { color: base.red800 },
                '&.keyword': { color: base.red800 },
                '&.punctuation': { color: base.neutral1000 },
                '&.operator': { color: base.neutral1000 },
                '&.bold': { fontWeight: system.fontWeight.bold },
            },
        },
    },
});
const StyledSymbolDoc = (props) => {
    return _jsx("div", { ...handleCsProp(props, symbolDocStencil()) });
};
function getSymbolDocChildren(doc, meta, name) {
    if (!doc) {
        // We're within  a symbol doc context, but there's no information, so we still want to render the basic type information
        return (_jsxs(_Fragment, { children: [_jsx("p", { children: "Basic type information:" }), _jsx("pre", { children: _jsx("code", { children: name }) })] }));
    }
    if (doc && doc.type) {
        return _jsx(Value, { value: doc.type, doc: doc, meta: meta });
    }
    return _jsx("div", { children: "Not found" });
}
function findDoc({ name, fileName }) {
    const doc = (docs || []).find(d => {
        return d.name === name && (fileName ? d.fileName.includes(fileName) : true);
    });
    if ((doc === null || doc === void 0 ? void 0 : doc.type.kind) === 'alias') {
        return findDoc({ name: doc.type.name });
    }
    return doc;
}
export const useDoc = (criteria) => {
    // Listen to criteria.name and criteria.fileName so that we can re-fetch docs in the dialog
    // eslint-disable-next-line react-hooks/exhaustive-deps
    const doc = React.useMemo(() => findDoc(criteria), [criteria.name, criteria.fileName || '']);
    return doc;
};
/**
 * Renders the `<Value>` of a exported symbol without any wrapper. This should be used when nesting
 * inside a `<SymbolDoc>` component.
 */
export const SymbolValue = (props) => {
    const doc = useDoc(props);
    return getSymbolDocChildren(doc);
};
/**
 * Renders just the description of an exported symbol.
 */
export const SymbolDescription = (props) => {
    const doc = useDoc(props);
    return doc ? _jsx(MdxJSToJSX, { children: doc.description }) : null;
};
/**
 * Renders an exported symbol as a doc object. This will render JSDoc tags, description and setup
 * rendering contexts for headers.
 */
export const SymbolDoc = ({ name, fileName, headingStart = 3, hideDescription = false, hideHeading = false, descriptionOverride, meta, ...elemProps }) => {
    const doc = useDoc({ name, fileName });
    const symbolDocBreadcrumb = React.useContext(SymbolDocBreadcrumbsContext);
    const children = getSymbolDocChildren(doc, meta, name);
    const requiresCodeWrapper = [
        'symbol',
        'generic',
        'array',
        'indexedAccess',
        'qualifiedName',
        'parenthesis',
        'typeParameter',
        'keyof',
        'type',
        'conditional',
        'indexSignature',
        'tuple',
        'external',
        'number',
        'string',
        'boolean',
        'primitive',
        'unknown',
        'union',
        'intersection',
        'function',
        'callExpression',
    ].includes((doc === null || doc === void 0 ? void 0 : doc.type.kind) || 'notFound');
    const contents = requiresCodeWrapper ? (_jsx("code", { style: { display: 'block' }, children: children })) : (children);
    const symbolDocContents = (_jsx(StyledSymbolDoc, { ...elemProps, children: _jsxs(HeadingLevelContext.Provider, { value: headingStart, children: [!hideHeading && _jsx(Heading, { id: `${name.toLowerCase()}-api`, children: name }), !hideDescription && doc && (_jsx(MdxJSToJSX, { children: descriptionOverride || doc.description })), contents] }) }));
    return symbolDocBreadcrumb === undefined ? (_jsx(SymbolDocWrapper, { children: symbolDocContents })) : (symbolDocContents);
};
const SymbolDocWrapper = ({ children }) => {
    const [breadcrumbsList, updateBreadcrumbs] = React.useState([]);
    return (_jsx(SymbolDocBreadcrumbsContext.Provider, { value: { breadcrumbsList, updateBreadcrumbs }, children: children }));
};
