import { TAG_ID as $, NS, ATTRS, getTagID } from './html.js';
import type { TagToken, Attribute } from './token.js';

//MIME types
const MIME_TYPES = {
    TEXT_HTML: 'text/html',
    APPLICATION_XML: 'application/xhtml+xml',
};

//Attributes
const DEFINITION_URL_ATTR = 'definitionurl';
const ADJUSTED_DEFINITION_URL_ATTR = 'definitionURL';
const SVG_ATTRS_ADJUSTMENT_MAP = new Map(
    [
        'attributeName',
        'attributeType',
        'baseFrequency',
        'baseProfile',
        'calcMode',
        'clipPathUnits',
        'diffuseConstant',
        'edgeMode',
        'filterUnits',
        'glyphRef',
        'gradientTransform',
        'gradientUnits',
        'kernelMatrix',
        'kernelUnitLength',
        'keyPoints',
        'keySplines',
        'keyTimes',
        'lengthAdjust',
        'limitingConeAngle',
        'markerHeight',
        'markerUnits',
        'markerWidth',
        'maskContentUnits',
        'maskUnits',
        'numOctaves',
        'pathLength',
        'patternContentUnits',
        'patternTransform',
        'patternUnits',
        'pointsAtX',
        'pointsAtY',
        'pointsAtZ',
        'preserveAlpha',
        'preserveAspectRatio',
        'primitiveUnits',
        'refX',
        'refY',
        'repeatCount',
        'repeatDur',
        'requiredExtensions',
        'requiredFeatures',
        'specularConstant',
        'specularExponent',
        'spreadMethod',
        'startOffset',
        'stdDeviation',
        'stitchTiles',
        'surfaceScale',
        'systemLanguage',
        'tableValues',
        'targetX',
        'targetY',
        'textLength',
        'viewBox',
        'viewTarget',
        'xChannelSelector',
        'yChannelSelector',
        'zoomAndPan',
    ].map((attr) => [attr.toLowerCase(), attr])
);

const XML_ATTRS_ADJUSTMENT_MAP = new Map([
    ['xlink:actuate', { prefix: 'xlink', name: 'actuate', namespace: NS.XLINK }],
    ['xlink:arcrole', { prefix: 'xlink', name: 'arcrole', namespace: NS.XLINK }],
    ['xlink:href', { prefix: 'xlink', name: 'href', namespace: NS.XLINK }],
    ['xlink:role', { prefix: 'xlink', name: 'role', namespace: NS.XLINK }],
    ['xlink:show', { prefix: 'xlink', name: 'show', namespace: NS.XLINK }],
    ['xlink:title', { prefix: 'xlink', name: 'title', namespace: NS.XLINK }],
    ['xlink:type', { prefix: 'xlink', name: 'type', namespace: NS.XLINK }],
    ['xml:base', { prefix: 'xml', name: 'base', namespace: NS.XML }],
    ['xml:lang', { prefix: 'xml', name: 'lang', namespace: NS.XML }],
    ['xml:space', { prefix: 'xml', name: 'space', namespace: NS.XML }],
    ['xmlns', { prefix: '', name: 'xmlns', namespace: NS.XMLNS }],
    ['xmlns:xlink', { prefix: 'xmlns', name: 'xlink', namespace: NS.XMLNS }],
]);

//SVG tag names adjustment map
export const SVG_TAG_NAMES_ADJUSTMENT_MAP = new Map(
    [
        'altGlyph',
        'altGlyphDef',
        'altGlyphItem',
        'animateColor',
        'animateMotion',
        'animateTransform',
        'clipPath',
        'feBlend',
        'feColorMatrix',
        'feComponentTransfer',
        'feComposite',
        'feConvolveMatrix',
        'feDiffuseLighting',
        'feDisplacementMap',
        'feDistantLight',
        'feFlood',
        'feFuncA',
        'feFuncB',
        'feFuncG',
        'feFuncR',
        'feGaussianBlur',
        'feImage',
        'feMerge',
        'feMergeNode',
        'feMorphology',
        'feOffset',
        'fePointLight',
        'feSpecularLighting',
        'feSpotLight',
        'feTile',
        'feTurbulence',
        'foreignObject',
        'glyphRef',
        'linearGradient',
        'radialGradient',
        'textPath',
    ].map((tn) => [tn.toLowerCase(), tn])
);

//Tags that causes exit from foreign content
const EXITS_FOREIGN_CONTENT = new Set([
    $.B,
    $.BIG,
    $.BLOCKQUOTE,
    $.BODY,
    $.BR,
    $.CENTER,
    $.CODE,
    $.DD,
    $.DIV,
    $.DL,
    $.DT,
    $.EM,
    $.EMBED,
    $.H1,
    $.H2,
    $.H3,
    $.H4,
    $.H5,
    $.H6,
    $.HEAD,
    $.HR,
    $.I,
    $.IMG,
    $.LI,
    $.LISTING,
    $.MENU,
    $.META,
    $.NOBR,
    $.OL,
    $.P,
    $.PRE,
    $.RUBY,
    $.S,
    $.SMALL,
    $.SPAN,
    $.STRONG,
    $.STRIKE,
    $.SUB,
    $.SUP,
    $.TABLE,
    $.TT,
    $.U,
    $.UL,
    $.VAR,
]);

//Check exit from foreign content
export function causesExit(startTagToken: TagToken): boolean {
    const tn = startTagToken.tagID;
    const isFontWithAttrs =
        tn === $.FONT &&
        startTagToken.attrs.some(({ name }) => name === ATTRS.COLOR || name === ATTRS.SIZE || name === ATTRS.FACE);

    return isFontWithAttrs || EXITS_FOREIGN_CONTENT.has(tn);
}

//Token adjustments
export function adjustTokenMathMLAttrs(token: TagToken): void {
    for (let i = 0; i < token.attrs.length; i++) {
        if (token.attrs[i].name === DEFINITION_URL_ATTR) {
            token.attrs[i].name = ADJUSTED_DEFINITION_URL_ATTR;
            break;
        }
    }
}

export function adjustTokenSVGAttrs(token: TagToken): void {
    for (let i = 0; i < token.attrs.length; i++) {
        const adjustedAttrName = SVG_ATTRS_ADJUSTMENT_MAP.get(token.attrs[i].name);

        if (adjustedAttrName != null) {
            token.attrs[i].name = adjustedAttrName;
        }
    }
}

export function adjustTokenXMLAttrs(token: TagToken): void {
    for (let i = 0; i < token.attrs.length; i++) {
        const adjustedAttrEntry = XML_ATTRS_ADJUSTMENT_MAP.get(token.attrs[i].name);

        if (adjustedAttrEntry) {
            token.attrs[i].prefix = adjustedAttrEntry.prefix;
            token.attrs[i].name = adjustedAttrEntry.name;
            token.attrs[i].namespace = adjustedAttrEntry.namespace;
        }
    }
}

export function adjustTokenSVGTagName(token: TagToken): void {
    const adjustedTagName = SVG_TAG_NAMES_ADJUSTMENT_MAP.get(token.tagName);

    if (adjustedTagName != null) {
        token.tagName = adjustedTagName;
        token.tagID = getTagID(token.tagName);
    }
}

//Integration points
function isMathMLTextIntegrationPoint(tn: $, ns: NS): boolean {
    return ns === NS.MATHML && (tn === $.MI || tn === $.MO || tn === $.MN || tn === $.MS || tn === $.MTEXT);
}

function isHtmlIntegrationPoint(tn: $, ns: NS, attrs: Attribute[]): boolean {
    if (ns === NS.MATHML && tn === $.ANNOTATION_XML) {
        for (let i = 0; i < attrs.length; i++) {
            if (attrs[i].name === ATTRS.ENCODING) {
                const value = attrs[i].value.toLowerCase();

                return value === MIME_TYPES.TEXT_HTML || value === MIME_TYPES.APPLICATION_XML;
            }
        }
    }

    return ns === NS.SVG && (tn === $.FOREIGN_OBJECT || tn === $.DESC || tn === $.TITLE);
}

export function isIntegrationPoint(tn: $, ns: NS, attrs: Attribute[], foreignNS?: NS): boolean {
    return (
        ((!foreignNS || foreignNS === NS.HTML) && isHtmlIntegrationPoint(tn, ns, attrs)) ||
        ((!foreignNS || foreignNS === NS.MATHML) && isMathMLTextIntegrationPoint(tn, ns))
    );
}
