UNPKG

@quasiris/qsc-chatbot-ui

Version:

A plugin that injects a Chatbot UI with WS/REST support

4,393 lines 206 kB
(function () {
    'use strict';

    /**
     * marked v16.1.1 - a markdown parser
     * Copyright (c) 2011-2025, Christopher Jeffrey. (MIT Licensed)
     * https://github.com/markedjs/marked
     */

    /**
     * DO NOT EDIT THIS FILE
     * The code in this file is generated from files in ./src/
     */

    function L(){return {async:false,breaks:false,extensions:null,gfm:true,hooks:null,pedantic:false,renderer:null,silent:false,tokenizer:null,walkTokens:null}}var O=L();function H(l){O=l;}var E={exec:()=>null};function h(l,e=""){let t=typeof l=="string"?l:l.source,n={replace:(r,i)=>{let s=typeof i=="string"?i:i.source;return s=s.replace(m.caret,"$1"),t=t.replace(r,s),n},getRegex:()=>new RegExp(t,e)};return n}var m={codeRemoveIndent:/^(?: {1,4}| {0,3}\t)/gm,outputLinkReplace:/\\([\[\]])/g,indentCodeCompensation:/^(\s+)(?:```)/,beginningSpace:/^\s+/,endingHash:/#$/,startingSpaceChar:/^ /,endingSpaceChar:/ $/,nonSpaceChar:/[^ ]/,newLineCharGlobal:/\n/g,tabCharGlobal:/\t/g,multipleSpaceGlobal:/\s+/g,blankLine:/^[ \t]*$/,doubleBlankLine:/\n[ \t]*\n[ \t]*$/,blockquoteStart:/^ {0,3}>/,blockquoteSetextReplace:/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,blockquoteSetextReplace2:/^ {0,3}>[ \t]?/gm,listReplaceTabs:/^\t+/,listReplaceNesting:/^ {1,4}(?=( {4})*[^ ])/g,listIsTask:/^\[[ xX]\] /,listReplaceTask:/^\[[ xX]\] +/,anyLine:/\n.*\n/,hrefBrackets:/^<(.*)>$/,tableDelimiter:/[:|]/,tableAlignChars:/^\||\| *$/g,tableRowBlankLine:/\n[ \t]*$/,tableAlignRight:/^ *-+: *$/,tableAlignCenter:/^ *:-+: *$/,tableAlignLeft:/^ *:-+ *$/,startATag:/^<a /i,endATag:/^<\/a>/i,startPreScriptTag:/^<(pre|code|kbd|script)(\s|>)/i,endPreScriptTag:/^<\/(pre|code|kbd|script)(\s|>)/i,startAngleBracket:/^</,endAngleBracket:/>$/,pedanticHrefTitle:/^([^'"]*[^\s])\s+(['"])(.*)\2/,unicodeAlphaNumeric:/[\p{L}\p{N}]/u,escapeTest:/[&<>"']/,escapeReplace:/[&<>"']/g,escapeTestNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,escapeReplaceNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,unescapeTest:/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig,caret:/(^|[^\[])\^/g,percentDecode:/%25/g,findPipe:/\|/g,splitPipe:/ \|/,slashPipe:/\\\|/g,carriageReturn:/\r\n|\r/g,spaceLine:/^ +$/gm,notSpaceStart:/^\S*/,endingNewline:/\n$/,listItemRegex:l=>new RegExp(`^( {0,3}${l})((?:[	 ][^\\n]*)?(?:\\n|$))`),nextBulletRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ 	][^\\n]*)?(?:\\n|$))`),hrRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),fencesBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}(?:\`\`\`|~~~)`),headingBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}#`),htmlBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}<(?:[a-z].*>|!--)`,"i")},xe=/^(?:[ \t]*(?:\n|$))+/,be=/^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,Re=/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,C=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,Oe=/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,j=/(?:[*+-]|\d{1,9}[.)])/,se=/^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,ie=h(se).replace(/bull/g,j).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/\|table/g,"").getRegex(),Te=h(se).replace(/bull/g,j).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/table/g,/ {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(),F=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,we=/^[^\n]+/,Q=/(?!\s*\])(?:\\.|[^\[\]\\])+/,ye=h(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",Q).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),Pe=h(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,j).getRegex(),v="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",U=/<!--(?:-?>|[\s\S]*?(?:-->|$))/,Se=h("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|<![A-Z][\\s\\S]*?(?:>\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ 	]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ 	]*)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ 	]*)+\\n|$))","i").replace("comment",U).replace("tag",v).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),oe=h(F).replace("hr",C).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",v).getRegex(),$e=h(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",oe).getRegex(),K={blockquote:$e,code:be,def:ye,fences:Re,heading:Oe,hr:C,html:Se,lheading:ie,list:Pe,newline:xe,paragraph:oe,table:E,text:we},re=h("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",C).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code","(?: {4}| {0,3}	)[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",v).getRegex(),_e={...K,lheading:Te,table:re,paragraph:h(F).replace("hr",C).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",re).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag",v).getRegex()},Le={...K,html:h(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)|<tag(?:"[^"]*"|'[^']*'|\\s[^'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment",U).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:E,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:h(F).replace("hr",C).replace("heading",` *#{1,6} *[^
]`).replace("lheading",ie).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},Me=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,ze=/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,ae=/^( {2,}|\\)\n(?!\s*$)/,Ae=/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/,D=/[\p{P}\p{S}]/u,X=/[\s\p{P}\p{S}]/u,le=/[^\s\p{P}\p{S}]/u,Ee=h(/^((?![*_])punctSpace)/,"u").replace(/punctSpace/g,X).getRegex(),ue=/(?!~)[\p{P}\p{S}]/u,Ce=/(?!~)[\s\p{P}\p{S}]/u,Ie=/(?:[^\s\p{P}\p{S}]|~)/u,Be=/\[[^[\]]*?\]\((?:\\.|[^\\\(\)]|\((?:\\.|[^\\\(\)])*\))*\)|`[^`]*?`|<(?! )[^<>]*?>/g,pe=/^(?:\*+(?:((?!\*)punct)|[^\s*]))|^_+(?:((?!_)punct)|([^\s_]))/,qe=h(pe,"u").replace(/punct/g,D).getRegex(),ve=h(pe,"u").replace(/punct/g,ue).getRegex(),ce="^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)",De=h(ce,"gu").replace(/notPunctSpace/g,le).replace(/punctSpace/g,X).replace(/punct/g,D).getRegex(),Ze=h(ce,"gu").replace(/notPunctSpace/g,Ie).replace(/punctSpace/g,Ce).replace(/punct/g,ue).getRegex(),Ge=h("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)","gu").replace(/notPunctSpace/g,le).replace(/punctSpace/g,X).replace(/punct/g,D).getRegex(),He=h(/\\(punct)/,"gu").replace(/punct/g,D).getRegex(),Ne=h(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),je=h(U).replace("(?:-->|$)","-->").getRegex(),Fe=h("^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>").replace("comment",je).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),q=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,Qe=h(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]*(?:\n[ \t]*)?)(title))?\s*\)/).replace("label",q).replace("href",/<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),he=h(/^!?\[(label)\]\[(ref)\]/).replace("label",q).replace("ref",Q).getRegex(),de=h(/^!?\[(ref)\](?:\[\])?/).replace("ref",Q).getRegex(),Ue=h("reflink|nolink(?!\\()","g").replace("reflink",he).replace("nolink",de).getRegex(),W={_backpedal:E,anyPunctuation:He,autolink:Ne,blockSkip:Be,br:ae,code:ze,del:E,emStrongLDelim:qe,emStrongRDelimAst:De,emStrongRDelimUnd:Ge,escape:Me,link:Qe,nolink:de,punctuation:Ee,reflink:he,reflinkSearch:Ue,tag:Fe,text:Ae,url:E},Ke={...W,link:h(/^!?\[(label)\]\((.*?)\)/).replace("label",q).getRegex(),reflink:h(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",q).getRegex()},N={...W,emStrongRDelimAst:Ze,emStrongLDelim:ve,url:h(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,"i").replace("email",/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])((?:\\.|[^\\])*?(?:\\.|[^\s~\\]))\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/},Xe={...N,br:h(ae).replace("{2,}","*").getRegex(),text:h(N.text).replace("\\b_","\\b_| {2,}\\n").replace(/\{2,\}/g,"*").getRegex()},I={normal:K,gfm:_e,pedantic:Le},M={normal:W,gfm:N,breaks:Xe,pedantic:Ke};var We={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},ke=l=>We[l];function w(l,e){if(e){if(m.escapeTest.test(l))return l.replace(m.escapeReplace,ke)}else if(m.escapeTestNoEncode.test(l))return l.replace(m.escapeReplaceNoEncode,ke);return l}function J(l){try{l=encodeURI(l).replace(m.percentDecode,"%");}catch{return null}return l}function V(l,e){let t=l.replace(m.findPipe,(i,s,o)=>{let a=false,u=s;for(;--u>=0&&o[u]==="\\";)a=!a;return a?"|":" |"}),n=t.split(m.splitPipe),r=0;if(n[0].trim()||n.shift(),n.length>0&&!n.at(-1)?.trim()&&n.pop(),e)if(n.length>e)n.splice(e);else for(;n.length<e;)n.push("");for(;r<n.length;r++)n[r]=n[r].trim().replace(m.slashPipe,"|");return n}function z(l,e,t){let n=l.length;if(n===0)return "";let r=0;for(;r<n;){let i=l.charAt(n-r-1);if(i===e&&true)r++;else break}return l.slice(0,n-r)}function ge(l,e){if(l.indexOf(e[1])===-1)return  -1;let t=0;for(let n=0;n<l.length;n++)if(l[n]==="\\")n++;else if(l[n]===e[0])t++;else if(l[n]===e[1]&&(t--,t<0))return n;return t>0?-2:-1}function fe(l,e,t,n,r){let i=e.href,s=e.title||null,o=l[1].replace(r.other.outputLinkReplace,"$1");n.state.inLink=true;let a={type:l[0].charAt(0)==="!"?"image":"link",raw:t,href:i,title:s,text:o,tokens:n.inlineTokens(o)};return n.state.inLink=false,a}function Je(l,e,t){let n=l.match(t.other.indentCodeCompensation);if(n===null)return e;let r=n[1];return e.split(`
`).map(i=>{let s=i.match(t.other.beginningSpace);if(s===null)return i;let[o]=s;return o.length>=r.length?i.slice(r.length):i}).join(`
`)}var y=class{options;rules;lexer;constructor(e){this.options=e||O;}space(e){let t=this.rules.block.newline.exec(e);if(t&&t[0].length>0)return {type:"space",raw:t[0]}}code(e){let t=this.rules.block.code.exec(e);if(t){let n=t[0].replace(this.rules.other.codeRemoveIndent,"");return {type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?n:z(n,`
`)}}}fences(e){let t=this.rules.block.fences.exec(e);if(t){let n=t[0],r=Je(n,t[3]||"",this.rules);return {type:"code",raw:n,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:r}}}heading(e){let t=this.rules.block.heading.exec(e);if(t){let n=t[2].trim();if(this.rules.other.endingHash.test(n)){let r=z(n,"#");(this.options.pedantic||!r||this.rules.other.endingSpaceChar.test(r))&&(n=r.trim());}return {type:"heading",raw:t[0],depth:t[1].length,text:n,tokens:this.lexer.inline(n)}}}hr(e){let t=this.rules.block.hr.exec(e);if(t)return {type:"hr",raw:z(t[0],`
`)}}blockquote(e){let t=this.rules.block.blockquote.exec(e);if(t){let n=z(t[0],`
`).split(`
`),r="",i="",s=[];for(;n.length>0;){let o=false,a=[],u;for(u=0;u<n.length;u++)if(this.rules.other.blockquoteStart.test(n[u]))a.push(n[u]),o=true;else if(!o)a.push(n[u]);else break;n=n.slice(u);let p=a.join(`
`),c=p.replace(this.rules.other.blockquoteSetextReplace,`
    $1`).replace(this.rules.other.blockquoteSetextReplace2,"");r=r?`${r}
${p}`:p,i=i?`${i}
${c}`:c;let f=this.lexer.state.top;if(this.lexer.state.top=true,this.lexer.blockTokens(c,s,true),this.lexer.state.top=f,n.length===0)break;let k=s.at(-1);if(k?.type==="code")break;if(k?.type==="blockquote"){let x=k,g=x.raw+`
`+n.join(`
`),T=this.blockquote(g);s[s.length-1]=T,r=r.substring(0,r.length-x.raw.length)+T.raw,i=i.substring(0,i.length-x.text.length)+T.text;break}else if(k?.type==="list"){let x=k,g=x.raw+`
`+n.join(`
`),T=this.list(g);s[s.length-1]=T,r=r.substring(0,r.length-k.raw.length)+T.raw,i=i.substring(0,i.length-x.raw.length)+T.raw,n=g.substring(s.at(-1).raw.length).split(`
`);continue}}return {type:"blockquote",raw:r,tokens:s,text:i}}}list(e){let t=this.rules.block.list.exec(e);if(t){let n=t[1].trim(),r=n.length>1,i={type:"list",raw:"",ordered:r,start:r?+n.slice(0,-1):"",loose:false,items:[]};n=r?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=r?n:"[*+-]");let s=this.rules.other.listItemRegex(n),o=false;for(;e;){let u=false,p="",c="";if(!(t=s.exec(e))||this.rules.block.hr.test(e))break;p=t[0],e=e.substring(p.length);let f=t[2].split(`
`,1)[0].replace(this.rules.other.listReplaceTabs,Z=>" ".repeat(3*Z.length)),k=e.split(`
`,1)[0],x=!f.trim(),g=0;if(this.options.pedantic?(g=2,c=f.trimStart()):x?g=t[1].length+1:(g=t[2].search(this.rules.other.nonSpaceChar),g=g>4?1:g,c=f.slice(g),g+=t[1].length),x&&this.rules.other.blankLine.test(k)&&(p+=k+`
`,e=e.substring(k.length+1),u=true),!u){let Z=this.rules.other.nextBulletRegex(g),ee=this.rules.other.hrRegex(g),te=this.rules.other.fencesBeginRegex(g),ne=this.rules.other.headingBeginRegex(g),me=this.rules.other.htmlBeginRegex(g);for(;e;){let G=e.split(`
`,1)[0],A;if(k=G,this.options.pedantic?(k=k.replace(this.rules.other.listReplaceNesting,"  "),A=k):A=k.replace(this.rules.other.tabCharGlobal,"    "),te.test(k)||ne.test(k)||me.test(k)||Z.test(k)||ee.test(k))break;if(A.search(this.rules.other.nonSpaceChar)>=g||!k.trim())c+=`
`+A.slice(g);else {if(x||f.replace(this.rules.other.tabCharGlobal,"    ").search(this.rules.other.nonSpaceChar)>=4||te.test(f)||ne.test(f)||ee.test(f))break;c+=`
`+k;}!x&&!k.trim()&&(x=true),p+=G+`
`,e=e.substring(G.length+1),f=A.slice(g);}}i.loose||(o?i.loose=true:this.rules.other.doubleBlankLine.test(p)&&(o=true));let T=null,Y;this.options.gfm&&(T=this.rules.other.listIsTask.exec(c),T&&(Y=T[0]!=="[ ] ",c=c.replace(this.rules.other.listReplaceTask,""))),i.items.push({type:"list_item",raw:p,task:!!T,checked:Y,loose:false,text:c,tokens:[]}),i.raw+=p;}let a=i.items.at(-1);if(a)a.raw=a.raw.trimEnd(),a.text=a.text.trimEnd();else return;i.raw=i.raw.trimEnd();for(let u=0;u<i.items.length;u++)if(this.lexer.state.top=false,i.items[u].tokens=this.lexer.blockTokens(i.items[u].text,[]),!i.loose){let p=i.items[u].tokens.filter(f=>f.type==="space"),c=p.length>0&&p.some(f=>this.rules.other.anyLine.test(f.raw));i.loose=c;}if(i.loose)for(let u=0;u<i.items.length;u++)i.items[u].loose=true;return i}}html(e){let t=this.rules.block.html.exec(e);if(t)return {type:"html",block:true,raw:t[0],pre:t[1]==="pre"||t[1]==="script"||t[1]==="style",text:t[0]}}def(e){let t=this.rules.block.def.exec(e);if(t){let n=t[1].toLowerCase().replace(this.rules.other.multipleSpaceGlobal," "),r=t[2]?t[2].replace(this.rules.other.hrefBrackets,"$1").replace(this.rules.inline.anyPunctuation,"$1"):"",i=t[3]?t[3].substring(1,t[3].length-1).replace(this.rules.inline.anyPunctuation,"$1"):t[3];return {type:"def",tag:n,raw:t[0],href:r,title:i}}}table(e){let t=this.rules.block.table.exec(e);if(!t||!this.rules.other.tableDelimiter.test(t[2]))return;let n=V(t[1]),r=t[2].replace(this.rules.other.tableAlignChars,"").split("|"),i=t[3]?.trim()?t[3].replace(this.rules.other.tableRowBlankLine,"").split(`
`):[],s={type:"table",raw:t[0],header:[],align:[],rows:[]};if(n.length===r.length){for(let o of r)this.rules.other.tableAlignRight.test(o)?s.align.push("right"):this.rules.other.tableAlignCenter.test(o)?s.align.push("center"):this.rules.other.tableAlignLeft.test(o)?s.align.push("left"):s.align.push(null);for(let o=0;o<n.length;o++)s.header.push({text:n[o],tokens:this.lexer.inline(n[o]),header:true,align:s.align[o]});for(let o of i)s.rows.push(V(o,s.header.length).map((a,u)=>({text:a,tokens:this.lexer.inline(a),header:false,align:s.align[u]})));return s}}lheading(e){let t=this.rules.block.lheading.exec(e);if(t)return {type:"heading",raw:t[0],depth:t[2].charAt(0)==="="?1:2,text:t[1],tokens:this.lexer.inline(t[1])}}paragraph(e){let t=this.rules.block.paragraph.exec(e);if(t){let n=t[1].charAt(t[1].length-1)===`
`?t[1].slice(0,-1):t[1];return {type:"paragraph",raw:t[0],text:n,tokens:this.lexer.inline(n)}}}text(e){let t=this.rules.block.text.exec(e);if(t)return {type:"text",raw:t[0],text:t[0],tokens:this.lexer.inline(t[0])}}escape(e){let t=this.rules.inline.escape.exec(e);if(t)return {type:"escape",raw:t[0],text:t[1]}}tag(e){let t=this.rules.inline.tag.exec(e);if(t)return !this.lexer.state.inLink&&this.rules.other.startATag.test(t[0])?this.lexer.state.inLink=true:this.lexer.state.inLink&&this.rules.other.endATag.test(t[0])&&(this.lexer.state.inLink=false),!this.lexer.state.inRawBlock&&this.rules.other.startPreScriptTag.test(t[0])?this.lexer.state.inRawBlock=true:this.lexer.state.inRawBlock&&this.rules.other.endPreScriptTag.test(t[0])&&(this.lexer.state.inRawBlock=false),{type:"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:false,text:t[0]}}link(e){let t=this.rules.inline.link.exec(e);if(t){let n=t[2].trim();if(!this.options.pedantic&&this.rules.other.startAngleBracket.test(n)){if(!this.rules.other.endAngleBracket.test(n))return;let s=z(n.slice(0,-1),"\\");if((n.length-s.length)%2===0)return}else {let s=ge(t[2],"()");if(s===-2)return;if(s>-1){let a=(t[0].indexOf("!")===0?5:4)+t[1].length+s;t[2]=t[2].substring(0,s),t[0]=t[0].substring(0,a).trim(),t[3]="";}}let r=t[2],i="";if(this.options.pedantic){let s=this.rules.other.pedanticHrefTitle.exec(r);s&&(r=s[1],i=s[3]);}else i=t[3]?t[3].slice(1,-1):"";return r=r.trim(),this.rules.other.startAngleBracket.test(r)&&(this.options.pedantic&&!this.rules.other.endAngleBracket.test(n)?r=r.slice(1):r=r.slice(1,-1)),fe(t,{href:r&&r.replace(this.rules.inline.anyPunctuation,"$1"),title:i&&i.replace(this.rules.inline.anyPunctuation,"$1")},t[0],this.lexer,this.rules)}}reflink(e,t){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){let r=(n[2]||n[1]).replace(this.rules.other.multipleSpaceGlobal," "),i=t[r.toLowerCase()];if(!i){let s=n[0].charAt(0);return {type:"text",raw:s,text:s}}return fe(n,i,n[0],this.lexer,this.rules)}}emStrong(e,t,n=""){let r=this.rules.inline.emStrongLDelim.exec(e);if(!r||r[3]&&n.match(this.rules.other.unicodeAlphaNumeric))return;if(!(r[1]||r[2]||"")||!n||this.rules.inline.punctuation.exec(n)){let s=[...r[0]].length-1,o,a,u=s,p=0,c=r[0][0]==="*"?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(c.lastIndex=0,t=t.slice(-1*e.length+s);(r=c.exec(t))!=null;){if(o=r[1]||r[2]||r[3]||r[4]||r[5]||r[6],!o)continue;if(a=[...o].length,r[3]||r[4]){u+=a;continue}else if((r[5]||r[6])&&s%3&&!((s+a)%3)){p+=a;continue}if(u-=a,u>0)continue;a=Math.min(a,a+u+p);let f=[...r[0]][0].length,k=e.slice(0,s+r.index+f+a);if(Math.min(s,a)%2){let g=k.slice(1,-1);return {type:"em",raw:k,text:g,tokens:this.lexer.inlineTokens(g)}}let x=k.slice(2,-2);return {type:"strong",raw:k,text:x,tokens:this.lexer.inlineTokens(x)}}}}codespan(e){let t=this.rules.inline.code.exec(e);if(t){let n=t[2].replace(this.rules.other.newLineCharGlobal," "),r=this.rules.other.nonSpaceChar.test(n),i=this.rules.other.startingSpaceChar.test(n)&&this.rules.other.endingSpaceChar.test(n);return r&&i&&(n=n.substring(1,n.length-1)),{type:"codespan",raw:t[0],text:n}}}br(e){let t=this.rules.inline.br.exec(e);if(t)return {type:"br",raw:t[0]}}del(e){let t=this.rules.inline.del.exec(e);if(t)return {type:"del",raw:t[0],text:t[2],tokens:this.lexer.inlineTokens(t[2])}}autolink(e){let t=this.rules.inline.autolink.exec(e);if(t){let n,r;return t[2]==="@"?(n=t[1],r="mailto:"+n):(n=t[1],r=n),{type:"link",raw:t[0],text:n,href:r,tokens:[{type:"text",raw:n,text:n}]}}}url(e){let t;if(t=this.rules.inline.url.exec(e)){let n,r;if(t[2]==="@")n=t[0],r="mailto:"+n;else {let i;do i=t[0],t[0]=this.rules.inline._backpedal.exec(t[0])?.[0]??"";while(i!==t[0]);n=t[0],t[1]==="www."?r="http://"+t[0]:r=t[0];}return {type:"link",raw:t[0],text:n,href:r,tokens:[{type:"text",raw:n,text:n}]}}}inlineText(e){let t=this.rules.inline.text.exec(e);if(t){let n=this.lexer.state.inRawBlock;return {type:"text",raw:t[0],text:t[0],escaped:n}}}};var b=class l{tokens;options;state;tokenizer;inlineQueue;constructor(e){this.tokens=[],this.tokens.links=Object.create(null),this.options=e||O,this.options.tokenizer=this.options.tokenizer||new y,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:false,inRawBlock:false,top:true};let t={other:m,block:I.normal,inline:M.normal};this.options.pedantic?(t.block=I.pedantic,t.inline=M.pedantic):this.options.gfm&&(t.block=I.gfm,this.options.breaks?t.inline=M.breaks:t.inline=M.gfm),this.tokenizer.rules=t;}static get rules(){return {block:I,inline:M}}static lex(e,t){return new l(t).lex(e)}static lexInline(e,t){return new l(t).inlineTokens(e)}lex(e){e=e.replace(m.carriageReturn,`
`),this.blockTokens(e,this.tokens);for(let t=0;t<this.inlineQueue.length;t++){let n=this.inlineQueue[t];this.inlineTokens(n.src,n.tokens);}return this.inlineQueue=[],this.tokens}blockTokens(e,t=[],n=false){for(this.options.pedantic&&(e=e.replace(m.tabCharGlobal,"    ").replace(m.spaceLine,""));e;){let r;if(this.options.extensions?.block?.some(s=>(r=s.call({lexer:this},e,t))?(e=e.substring(r.raw.length),t.push(r),true):false))continue;if(r=this.tokenizer.space(e)){e=e.substring(r.raw.length);let s=t.at(-1);r.raw.length===1&&s!==void 0?s.raw+=`
`:t.push(r);continue}if(r=this.tokenizer.code(e)){e=e.substring(r.raw.length);let s=t.at(-1);s?.type==="paragraph"||s?.type==="text"?(s.raw+=`
`+r.raw,s.text+=`
`+r.text,this.inlineQueue.at(-1).src=s.text):t.push(r);continue}if(r=this.tokenizer.fences(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.heading(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.hr(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.blockquote(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.list(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.html(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.def(e)){e=e.substring(r.raw.length);let s=t.at(-1);s?.type==="paragraph"||s?.type==="text"?(s.raw+=`
`+r.raw,s.text+=`
`+r.raw,this.inlineQueue.at(-1).src=s.text):this.tokens.links[r.tag]||(this.tokens.links[r.tag]={href:r.href,title:r.title});continue}if(r=this.tokenizer.table(e)){e=e.substring(r.raw.length),t.push(r);continue}if(r=this.tokenizer.lheading(e)){e=e.substring(r.raw.length),t.push(r);continue}let i=e;if(this.options.extensions?.startBlock){let s=1/0,o=e.slice(1),a;this.options.extensions.startBlock.forEach(u=>{a=u.call({lexer:this},o),typeof a=="number"&&a>=0&&(s=Math.min(s,a));}),s<1/0&&s>=0&&(i=e.substring(0,s+1));}if(this.state.top&&(r=this.tokenizer.paragraph(i))){let s=t.at(-1);n&&s?.type==="paragraph"?(s.raw+=`
`+r.raw,s.text+=`
`+r.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=s.text):t.push(r),n=i.length!==e.length,e=e.substring(r.raw.length);continue}if(r=this.tokenizer.text(e)){e=e.substring(r.raw.length);let s=t.at(-1);s?.type==="text"?(s.raw+=`
`+r.raw,s.text+=`
`+r.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=s.text):t.push(r);continue}if(e){let s="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(s);break}else throw new Error(s)}}return this.state.top=true,t}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(e,t=[]){let n=e,r=null;if(this.tokens.links){let o=Object.keys(this.tokens.links);if(o.length>0)for(;(r=this.tokenizer.rules.inline.reflinkSearch.exec(n))!=null;)o.includes(r[0].slice(r[0].lastIndexOf("[")+1,-1))&&(n=n.slice(0,r.index)+"["+"a".repeat(r[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex));}for(;(r=this.tokenizer.rules.inline.anyPunctuation.exec(n))!=null;)n=n.slice(0,r.index)+"++"+n.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);for(;(r=this.tokenizer.rules.inline.blockSkip.exec(n))!=null;)n=n.slice(0,r.index)+"["+"a".repeat(r[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);let i=false,s="";for(;e;){i||(s=""),i=false;let o;if(this.options.extensions?.inline?.some(u=>(o=u.call({lexer:this},e,t))?(e=e.substring(o.raw.length),t.push(o),true):false))continue;if(o=this.tokenizer.escape(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.tag(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.link(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.reflink(e,this.tokens.links)){e=e.substring(o.raw.length);let u=t.at(-1);o.type==="text"&&u?.type==="text"?(u.raw+=o.raw,u.text+=o.text):t.push(o);continue}if(o=this.tokenizer.emStrong(e,n,s)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.codespan(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.br(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.del(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.autolink(e)){e=e.substring(o.raw.length),t.push(o);continue}if(!this.state.inLink&&(o=this.tokenizer.url(e))){e=e.substring(o.raw.length),t.push(o);continue}let a=e;if(this.options.extensions?.startInline){let u=1/0,p=e.slice(1),c;this.options.extensions.startInline.forEach(f=>{c=f.call({lexer:this},p),typeof c=="number"&&c>=0&&(u=Math.min(u,c));}),u<1/0&&u>=0&&(a=e.substring(0,u+1));}if(o=this.tokenizer.inlineText(a)){e=e.substring(o.raw.length),o.raw.slice(-1)!=="_"&&(s=o.raw.slice(-1)),i=true;let u=t.at(-1);u?.type==="text"?(u.raw+=o.raw,u.text+=o.text):t.push(o);continue}if(e){let u="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(u);break}else throw new Error(u)}}return t}};var P=class{options;parser;constructor(e){this.options=e||O;}space(e){return ""}code({text:e,lang:t,escaped:n}){let r=(t||"").match(m.notSpaceStart)?.[0],i=e.replace(m.endingNewline,"")+`
`;return r?'<pre><code class="language-'+w(r)+'">'+(n?i:w(i,true))+`</code></pre>
`:"<pre><code>"+(n?i:w(i,true))+`</code></pre>
`}blockquote({tokens:e}){return `<blockquote>
${this.parser.parse(e)}</blockquote>
`}html({text:e}){return e}heading({tokens:e,depth:t}){return `<h${t}>${this.parser.parseInline(e)}</h${t}>
`}hr(e){return `<hr>
`}list(e){let t=e.ordered,n=e.start,r="";for(let o=0;o<e.items.length;o++){let a=e.items[o];r+=this.listitem(a);}let i=t?"ol":"ul",s=t&&n!==1?' start="'+n+'"':"";return "<"+i+s+`>
`+r+"</"+i+`>
`}listitem(e){let t="";if(e.task){let n=this.checkbox({checked:!!e.checked});e.loose?e.tokens[0]?.type==="paragraph"?(e.tokens[0].text=n+" "+e.tokens[0].text,e.tokens[0].tokens&&e.tokens[0].tokens.length>0&&e.tokens[0].tokens[0].type==="text"&&(e.tokens[0].tokens[0].text=n+" "+w(e.tokens[0].tokens[0].text),e.tokens[0].tokens[0].escaped=true)):e.tokens.unshift({type:"text",raw:n+" ",text:n+" ",escaped:true}):t+=n+" ";}return t+=this.parser.parse(e.tokens,!!e.loose),`<li>${t}</li>
`}checkbox({checked:e}){return "<input "+(e?'checked="" ':"")+'disabled="" type="checkbox">'}paragraph({tokens:e}){return `<p>${this.parser.parseInline(e)}</p>
`}table(e){let t="",n="";for(let i=0;i<e.header.length;i++)n+=this.tablecell(e.header[i]);t+=this.tablerow({text:n});let r="";for(let i=0;i<e.rows.length;i++){let s=e.rows[i];n="";for(let o=0;o<s.length;o++)n+=this.tablecell(s[o]);r+=this.tablerow({text:n});}return r&&(r=`<tbody>${r}</tbody>`),`<table>
<thead>
`+t+`</thead>
`+r+`</table>
`}tablerow({text:e}){return `<tr>
${e}</tr>
`}tablecell(e){let t=this.parser.parseInline(e.tokens),n=e.header?"th":"td";return (e.align?`<${n} align="${e.align}">`:`<${n}>`)+t+`</${n}>
`}strong({tokens:e}){return `<strong>${this.parser.parseInline(e)}</strong>`}em({tokens:e}){return `<em>${this.parser.parseInline(e)}</em>`}codespan({text:e}){return `<code>${w(e,true)}</code>`}br(e){return "<br>"}del({tokens:e}){return `<del>${this.parser.parseInline(e)}</del>`}link({href:e,title:t,tokens:n}){let r=this.parser.parseInline(n),i=J(e);if(i===null)return r;e=i;let s='<a href="'+e+'"';return t&&(s+=' title="'+w(t)+'"'),s+=">"+r+"</a>",s}image({href:e,title:t,text:n,tokens:r}){r&&(n=this.parser.parseInline(r,this.parser.textRenderer));let i=J(e);if(i===null)return w(n);e=i;let s=`<img src="${e}" alt="${n}"`;return t&&(s+=` title="${w(t)}"`),s+=">",s}text(e){return "tokens"in e&&e.tokens?this.parser.parseInline(e.tokens):"escaped"in e&&e.escaped?e.text:w(e.text)}};var S=class{strong({text:e}){return e}em({text:e}){return e}codespan({text:e}){return e}del({text:e}){return e}html({text:e}){return e}text({text:e}){return e}link({text:e}){return ""+e}image({text:e}){return ""+e}br(){return ""}};var R=class l{options;renderer;textRenderer;constructor(e){this.options=e||O,this.options.renderer=this.options.renderer||new P,this.renderer=this.options.renderer,this.renderer.options=this.options,this.renderer.parser=this,this.textRenderer=new S;}static parse(e,t){return new l(t).parse(e)}static parseInline(e,t){return new l(t).parseInline(e)}parse(e,t=true){let n="";for(let r=0;r<e.length;r++){let i=e[r];if(this.options.extensions?.renderers?.[i.type]){let o=i,a=this.options.extensions.renderers[o.type].call({parser:this},o);if(a!==false||!["space","hr","heading","code","table","blockquote","list","html","paragraph","text"].includes(o.type)){n+=a||"";continue}}let s=i;switch(s.type){case "space":{n+=this.renderer.space(s);continue}case "hr":{n+=this.renderer.hr(s);continue}case "heading":{n+=this.renderer.heading(s);continue}case "code":{n+=this.renderer.code(s);continue}case "table":{n+=this.renderer.table(s);continue}case "blockquote":{n+=this.renderer.blockquote(s);continue}case "list":{n+=this.renderer.list(s);continue}case "html":{n+=this.renderer.html(s);continue}case "paragraph":{n+=this.renderer.paragraph(s);continue}case "text":{let o=s,a=this.renderer.text(o);for(;r+1<e.length&&e[r+1].type==="text";)o=e[++r],a+=`
`+this.renderer.text(o);t?n+=this.renderer.paragraph({type:"paragraph",raw:a,text:a,tokens:[{type:"text",raw:a,text:a,escaped:true}]}):n+=a;continue}default:{let o='Token with "'+s.type+'" type was not found.';if(this.options.silent)return console.error(o),"";throw new Error(o)}}}return n}parseInline(e,t=this.renderer){let n="";for(let r=0;r<e.length;r++){let i=e[r];if(this.options.extensions?.renderers?.[i.type]){let o=this.options.extensions.renderers[i.type].call({parser:this},i);if(o!==false||!["escape","html","link","image","strong","em","codespan","br","del","text"].includes(i.type)){n+=o||"";continue}}let s=i;switch(s.type){case "escape":{n+=t.text(s);break}case "html":{n+=t.html(s);break}case "link":{n+=t.link(s);break}case "image":{n+=t.image(s);break}case "strong":{n+=t.strong(s);break}case "em":{n+=t.em(s);break}case "codespan":{n+=t.codespan(s);break}case "br":{n+=t.br(s);break}case "del":{n+=t.del(s);break}case "text":{n+=t.text(s);break}default:{let o='Token with "'+s.type+'" type was not found.';if(this.options.silent)return console.error(o),"";throw new Error(o)}}}return n}};var $=class{options;block;constructor(e){this.options=e||O;}static passThroughHooks=new Set(["preprocess","postprocess","processAllTokens"]);preprocess(e){return e}postprocess(e){return e}processAllTokens(e){return e}provideLexer(){return this.block?b.lex:b.lexInline}provideParser(){return this.block?R.parse:R.parseInline}};var B=class{defaults=L();options=this.setOptions;parse=this.parseMarkdown(true);parseInline=this.parseMarkdown(false);Parser=R;Renderer=P;TextRenderer=S;Lexer=b;Tokenizer=y;Hooks=$;constructor(...e){this.use(...e);}walkTokens(e,t){let n=[];for(let r of e)switch(n=n.concat(t.call(this,r)),r.type){case "table":{let i=r;for(let s of i.header)n=n.concat(this.walkTokens(s.tokens,t));for(let s of i.rows)for(let o of s)n=n.concat(this.walkTokens(o.tokens,t));break}case "list":{let i=r;n=n.concat(this.walkTokens(i.items,t));break}default:{let i=r;this.defaults.extensions?.childTokens?.[i.type]?this.defaults.extensions.childTokens[i.type].forEach(s=>{let o=i[s].flat(1/0);n=n.concat(this.walkTokens(o,t));}):i.tokens&&(n=n.concat(this.walkTokens(i.tokens,t)));}}return n}use(...e){let t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach(n=>{let r={...n};if(r.async=this.defaults.async||r.async||false,n.extensions&&(n.extensions.forEach(i=>{if(!i.name)throw new Error("extension name required");if("renderer"in i){let s=t.renderers[i.name];s?t.renderers[i.name]=function(...o){let a=i.renderer.apply(this,o);return a===false&&(a=s.apply(this,o)),a}:t.renderers[i.name]=i.renderer;}if("tokenizer"in i){if(!i.level||i.level!=="block"&&i.level!=="inline")throw new Error("extension level must be 'block' or 'inline'");let s=t[i.level];s?s.unshift(i.tokenizer):t[i.level]=[i.tokenizer],i.start&&(i.level==="block"?t.startBlock?t.startBlock.push(i.start):t.startBlock=[i.start]:i.level==="inline"&&(t.startInline?t.startInline.push(i.start):t.startInline=[i.start]));}"childTokens"in i&&i.childTokens&&(t.childTokens[i.name]=i.childTokens);}),r.extensions=t),n.renderer){let i=this.defaults.renderer||new P(this.defaults);for(let s in n.renderer){if(!(s in i))throw new Error(`renderer '${s}' does not exist`);if(["options","parser"].includes(s))continue;let o=s,a=n.renderer[o],u=i[o];i[o]=(...p)=>{let c=a.apply(i,p);return c===false&&(c=u.apply(i,p)),c||""};}r.renderer=i;}if(n.tokenizer){let i=this.defaults.tokenizer||new y(this.defaults);for(let s in n.tokenizer){if(!(s in i))throw new Error(`tokenizer '${s}' does not exist`);if(["options","rules","lexer"].includes(s))continue;let o=s,a=n.tokenizer[o],u=i[o];i[o]=(...p)=>{let c=a.apply(i,p);return c===false&&(c=u.apply(i,p)),c};}r.tokenizer=i;}if(n.hooks){let i=this.defaults.hooks||new $;for(let s in n.hooks){if(!(s in i))throw new Error(`hook '${s}' does not exist`);if(["options","block"].includes(s))continue;let o=s,a=n.hooks[o],u=i[o];$.passThroughHooks.has(s)?i[o]=p=>{if(this.defaults.async)return Promise.resolve(a.call(i,p)).then(f=>u.call(i,f));let c=a.call(i,p);return u.call(i,c)}:i[o]=(...p)=>{let c=a.apply(i,p);return c===false&&(c=u.apply(i,p)),c};}r.hooks=i;}if(n.walkTokens){let i=this.defaults.walkTokens,s=n.walkTokens;r.walkTokens=function(o){let a=[];return a.push(s.call(this,o)),i&&(a=a.concat(i.call(this,o))),a};}this.defaults={...this.defaults,...r};}),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return b.lex(e,t??this.defaults)}parser(e,t){return R.parse(e,t??this.defaults)}parseMarkdown(e){return (n,r)=>{let i={...r},s={...this.defaults,...i},o=this.onError(!!s.silent,!!s.async);if(this.defaults.async===true&&i.async===false)return o(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(typeof n>"u"||n===null)return o(new Error("marked(): input parameter is undefined or null"));if(typeof n!="string")return o(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(n)+", string expected"));s.hooks&&(s.hooks.options=s,s.hooks.block=e);let a=s.hooks?s.hooks.provideLexer():e?b.lex:b.lexInline,u=s.hooks?s.hooks.provideParser():e?R.parse:R.parseInline;if(s.async)return Promise.resolve(s.hooks?s.hooks.preprocess(n):n).then(p=>a(p,s)).then(p=>s.hooks?s.hooks.processAllTokens(p):p).then(p=>s.walkTokens?Promise.all(this.walkTokens(p,s.walkTokens)).then(()=>p):p).then(p=>u(p,s)).then(p=>s.hooks?s.hooks.postprocess(p):p).catch(o);try{s.hooks&&(n=s.hooks.preprocess(n));let p=a(n,s);s.hooks&&(p=s.hooks.processAllTokens(p)),s.walkTokens&&this.walkTokens(p,s.walkTokens);let c=u(p,s);return s.hooks&&(c=s.hooks.postprocess(c)),c}catch(p){return o(p)}}}onError(e,t){return n=>{if(n.message+=`
Please report this to https://github.com/markedjs/marked.`,e){let r="<p>An error occurred:</p><pre>"+w(n.message+"",true)+"</pre>";return t?Promise.resolve(r):r}if(t)return Promise.reject(n);throw n}}};var _=new B;function d(l,e){return _.parse(l,e)}d.options=d.setOptions=function(l){return _.setOptions(l),d.defaults=_.defaults,H(d.defaults),d};d.getDefaults=L;d.defaults=O;d.use=function(...l){return _.use(...l),d.defaults=_.defaults,H(d.defaults),d};d.walkTokens=function(l,e){return _.walkTokens(l,e)};d.parseInline=_.parseInline;d.Parser=R;d.parser=R.parse;d.Renderer=P;d.TextRenderer=S;d.Lexer=b;d.lexer=b.lex;d.Tokenizer=y;d.Hooks=$;d.parse=d;d.options;d.setOptions;d.use;d.walkTokens;d.parseInline;R.parse;b.lex;

    /*! @license DOMPurify 3.2.6 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.6/LICENSE */

    const {
      entries,
      setPrototypeOf,
      isFrozen,
      getPrototypeOf,
      getOwnPropertyDescriptor
    } = Object;
    let {
      freeze,
      seal,
      create
    } = Object; // eslint-disable-line import/no-mutable-exports
    let {
      apply,
      construct
    } = typeof Reflect !== 'undefined' && Reflect;
    if (!freeze) {
      freeze = function freeze(x) {
        return x;
      };
    }
    if (!seal) {
      seal = function seal(x) {
        return x;
      };
    }
    if (!apply) {
      apply = function apply(fun, thisValue, args) {
        return fun.apply(thisValue, args);
      };
    }
    if (!construct) {
      construct = function construct(Func, args) {
        return new Func(...args);
      };
    }
    const arrayForEach = unapply(Array.prototype.forEach);
    const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
    const arrayPop = unapply(Array.prototype.pop);
    const arrayPush = unapply(Array.prototype.push);
    const arraySplice = unapply(Array.prototype.splice);
    const stringToLowerCase = unapply(String.prototype.toLowerCase);
    const stringToString = unapply(String.prototype.toString);
    const stringMatch = unapply(String.prototype.match);
    const stringReplace = unapply(String.prototype.replace);
    const stringIndexOf = unapply(String.prototype.indexOf);
    const stringTrim = unapply(String.prototype.trim);
    const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
    const regExpTest = unapply(RegExp.prototype.test);
    const typeErrorCreate = unconstruct(TypeError);
    /**
     * Creates a new function that calls the given function with a specified thisArg and arguments.
     *
     * @param func - The function to be wrapped and called.
     * @returns A new function that calls the given function with a specified thisArg and arguments.
     */
    function unapply(func) {
      return function (thisArg) {
        if (thisArg instanceof RegExp) {
          thisArg.lastIndex = 0;
        }
        for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
          args[_key - 1] = arguments[_key];
        }
        return apply(func, thisArg, args);
      };
    }
    /**
     * Creates a new function that constructs an instance of the given constructor function with the provided arguments.
     *
     * @param func - The constructor function to be wrapped and called.
     * @returns A new function that constructs an instance of the given constructor function with the provided arguments.
     */
    function unconstruct(func) {
      return function () {
        for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
          args[_key2] = arguments[_key2];
        }
        return construct(func, args);
      };
    }
    /**
     * Add properties to a lookup table
     *
     * @param set - The set to which elements will be added.
     * @param array - The array containing elements to be added to the set.
     * @param transformCaseFunc - An optional function to transform the case of each element before adding to the set.
     * @returns The modified set with added elements.
     */
    function addToSet(set, array) {
      let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;
      if (setPrototypeOf) {
        // Make 'in' and truthy checks like Boolean(set.constructor)
        // independent of any properties defined on Object.prototype.
        // Prevent prototype setters from intercepting set as a this value.
        setPrototypeOf(set, null);
      }
      let l = array.length;
      while (l--) {
        let element = array[l];
        if (typeof element === 'string') {
          const lcElement = transformCaseFunc(element);
          if (lcElement !== element) {
            // Config presets (e.g. tags.js, attrs.js) are immutable.
            if (!isFrozen(array)) {
              array[l] = lcElement;
            }
            element = lcElement;
          }
        }
        set[element] = true;
      }
      return set;
    }
    /**
     * Clean up an array to harden against CSPP
     *
     * @param array - The array to be cleaned.
     * @returns The cleaned version of the array
     */
    function cleanArray(array) {
      for (let index = 0; index < array.length; index++) {
        const isPropertyExist = objectHasOwnProperty(array, index);
        if (!isPropertyExist) {
          array[index] = null;
        }
      }
      return array;
    }
    /**
     * Shallow clone an object
     *
     * @param object - The object to be cloned.
     * @returns A new object that copies the original.
     */
    function clone(object) {
      const newObject = create(null);
      for (const [property, value] of entries(object)) {
        const isPropertyExist = objectHasOwnProperty(object, property);
        if (isPropertyExist) {
          if (Array.isArray(value)) {
            newObject[property] = cleanArray(value);
          } else if (value && typeof value === 'object' && value.constructor === Object) {
            newObject[property] = clone(value);
          } else {
            newObject[property] = value;
          }
        }
      }
      return newObject;
    }
    /**
     * This method automatically checks if the prop is function or getter and behaves accordingly.
     *
     * @param object - The object to look up the getter function in its prototype chain.
     * @param prop - The property name for which to find the getter function.
     * @returns The getter function found in the prototype chain or a fallback function.
     */
    function lookupGetter(object, prop) {
      while (object !== null) {
        const desc = getOwnPropertyDescriptor(object, prop);
        if (desc) {
          if (desc.get) {
            return unapply(desc.get);
          }
          if (typeof desc.value === 'function') {
            return unapply(desc.value);
          }
        }
        object = getPrototypeOf(object);
      }
      function fallbackValue() {
        return null;
      }
      return fallbackValue;
    }

    const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);
    const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);
    const svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);
    // List of SVG elements that are disallowed by default.
    // We still need to know them so that we can do namespace
    // checks properly in case one wants to add them to
    // allow-list.
    const svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']);
    const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']);
    // Similarly to SVG, we want to know all MathML elements,
    // even those that we disallow by default.
    const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
    const text = freeze(['#text']);

    const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']);
    const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
    const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
    const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);

    // eslint-disable-next-line unicorn/better-regex
    const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
    const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
    const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
    const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
    const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
    const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
    );
    const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
    const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
    );
    const DOCTYPE_NAME = seal(/^html$/i);
    const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);

    var EXPRESSIONS = /*#__PURE__*/Object.freeze({
      __proto__: null,
      ARIA_ATTR: ARIA_ATTR,
      ATTR_WHITESPACE: ATTR_WHITESPACE,
      CUSTOM_ELEMENT: CUSTOM_ELEMENT,
      DATA_ATTR: DATA_ATTR,
      DOCTYPE_NAME: DOCTYPE_NAME,
      ERB_EXPR: ERB_EXPR,
      IS_ALLOWED_URI: IS_ALLOWED_URI,
      IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
      MUSTACHE_EXPR: MUSTACHE_EXPR,
      TMPLIT_EXPR: TMPLIT_EXPR
    });

    /* eslint-disable @typescript-eslint/indent */
    // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
    const NODE_TYPE = {
      element: 1,
      text: 3,
      // Deprecated
      progressingInstruction: 7,
      comment: 8,
      document: 9};
    const getGlobal = function getGlobal() {
      return typeof window === 'undefined' ? null : window;
    };
    /**
     * Creates a no-op policy for internal use only.
     * Don't export this function outside this module!
     * @param trustedTypes The policy factory.
     * @param purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).
     * @return The policy created (or null, if Trusted Types
     * are not supported or creating the policy failed).
     */
    const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {
      if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
        return null;
      }
      // Allow the callers to control the unique policy name
      // by adding a data-tt-policy-suffix to the script element with the DOMPurify.
      // Policy creation with duplicate names throws in Trusted Types.
      let suffix = null;
      const ATTR_NAME = 'data-tt-policy-suffix';
      if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {
        suffix = purifyHostElement.getAttribute(ATTR_NAME);
      }
      const policyName = 'dompurify' + (suffix ? '#' + suffix : '');
      try {
        return trustedTypes.createPolicy(policyName, {
          createHTML(html) {
            return html;
          },
          createScriptURL(scriptUrl) {
            return scriptUrl;
          }
        });
      } catch (_) {
        // Policy creation failed (most likely another DOMPurify script has
        // already run). Skip creating the policy, as this will only cause errors
        // if TT are enforced.
        console.warn('TrustedTypes policy ' + policyName + ' could not be created.');
        return null;
      }
    };
    const _createHooksMap = function _createHooksMap() {
      return {
        afterSanitizeAttributes: [],
        afterSanitizeElements: [],
        afterSanitizeShadowDOM: [],
        beforeSanitizeAttributes: [],
        beforeSanitizeElements: [],
        beforeSanitizeShadowDOM: [],
        uponSanitizeAttribute: [],
        uponSanitizeElement: [],
        uponSanitizeShadowNode: []
      };
    };
    function createDOMPurify() {
      let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
      const DOMPurify = root => createDOMPurify(root);
      DOMPurify.version = '3.2.6';
      DOMPurify.removed = [];
      if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
        // Not running in a browser, provide a factory function
        // so that you can pass your own Window
        DOMPurify.isSupported = false;
        return DOMPurify;
      }
      let {
        document
      } = window;
      const originalDocument = document;
      const currentScript = originalDocument.currentScript;
      const {
        DocumentFragment,
        HTMLTemplateElement,
        Node,
        Element,
        NodeFilter,
        NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,
        HTMLFormElement,
        DOMParser,
        trustedTypes
      } = window;
      const ElementPrototype = Element.prototype;
      const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
      const remove = lookupGetter(ElementPrototype, 'remove');
      const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
      const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
      const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
      // As per issue #47, the web-components registry is inherited by a
      // new document created via createHTMLDocument. As per the spec
      // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
      // a new empty registry is used when creating a template contents owner
      // document, so we use that as our parent document to ensure nothing
      // is inherited.
      if (typeof HTMLTemplateElement === 'function') {
        const template = document.createElement('template');
        if (template.content && template.content.ownerDocument) {
          document = template.content.ownerDocument;
        }
      }
      let trustedTypesPolicy;
      let emptyHTML = '';
      const {
        implementation,
        createNodeIterator,
        createDocumentFragment,
        getElementsByTagName
      } = document;
      const {
        importNode
      } = originalDocument;
      let hooks = _createHooksMap();
      /**
       * Expose whether this browser supports running the full DOMPurify.
       */
      DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
      const {
        MUSTACHE_EXPR,
        ERB_EXPR,
        TMPLIT_EXPR,
        DATA_ATTR,
        ARIA_ATTR,
        IS_SCRIPT_OR_DATA,
        ATTR_WHITESPACE,
        CUSTOM_ELEMENT
      } = EXPRESSIONS;
      let {
        IS_ALLOWED_URI: IS_ALLOWED_URI$1
      } = EXPRESSIONS;
      /**
       * We consider the elements and attributes below to be safe. Ideally
       * don't add any new ones but feel free to remove unwanted ones.
       */
      /* allowed element names */
      let ALLOWED_TAGS = null;
      const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]);
      /* Allowed attribute names */
      let ALLOWED_ATTR = null;
      const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);
      /*
       * Configure how DOMPurify should handle custom elements and their attributes as well as customized built-in elements.
       * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
       * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
       * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
       */
      let CUSTOM_ELEMENT_HANDLING = Object.seal(create(null, {
        tagNameCheck: {
          writable: true,
          configurable: false,
          enumerable: true,
          value: null
        },
        attributeNameCheck: {
          writable: true,
          configurable: false,
          enumerable: true,
          value: null
        },
        allowCustomizedBuiltInElements: {
          writable: true,
          configurable: false,
          enumerable: true,
          value: false
        }
      }));
      /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
      let FORBID_TAGS = null;
      /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
      let FORBID_ATTR = null;
      /* Decide if ARIA attributes are okay */
      let ALLOW_ARIA_ATTR = true;
      /* Decide if custom data attributes are okay */
      let ALLOW_DATA_ATTR = true;
      /* Decide if unknown protocols are okay */
      let ALLOW_UNKNOWN_PROTOCOLS = false;
      /* Decide if self-closing tags in attributes are allowed.
       * Usually removed due to a mXSS issue in jQuery 3.0 */
      let ALLOW_SELF_CLOSE_IN_ATTR = true;
      /* Output should be safe for common template engines.
       * This means, DOMPurify removes data attributes, mustaches and ERB
       */
      let SAFE_FOR_TEMPLATES = false;
      /* Output should be safe even for XML used within HTML and alike.
       * This means, DOMPurify removes comments when containing risky content.
       */
      let SAFE_FOR_XML = true;
      /* Decide if document with <html>... should be returned */
      let WHOLE_DOCUMENT = false;
      /* Track whether config is already set on this instance of DOMPurify. */
      let SET_CONFIG = false;
      /* Decide if all elements (e.g. style, script) must be children of
       * document.body. By default, browsers might move them to document.head */
      let FORCE_BODY = false;
      /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
       * string (or a TrustedHTML object if Trusted Types are supported).
       * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
       */
      let RETURN_DOM = false;
      /* Decide if a DOM `DocumentFragment` should be returned, instead of a html
       * string  (or a TrustedHTML object if Trusted Types are supported) */
      let RETURN_DOM_FRAGMENT = false;
      /* Try to return a Trusted Type object instead of a string, return a string in
       * case Trusted Types are not supported  */
      let RETURN_TRUSTED_TYPE = false;
      /* Output should be free from DOM clobbering attacks?
       * This sanitizes markups named with colliding, clobberable built-in DOM APIs.
       */
      let SANITIZE_DOM = true;
      /* Achieve full DOM Clobbering protection by isolating the namespace of named
       * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.
       *
       * HTML/DOM spec rules that enable DOM Clobbering:
       *   - Named Access on Window (§7.3.3)
       *   - DOM Tree Accessors (§3.1.5)
       *   - Form Element Parent-Child Relations (§4.10.3)
       *   - Iframe srcdoc / Nested WindowProxies (§4.8.5)
       *   - HTMLCollection (§4.2.10.2)
       *
       * Namespace isolation is implemented by prefixing `id` and `name` attributes
       * with a constant string, i.e., `user-content-`
       */
      let SANITIZE_NAMED_PROPS = false;
      const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';
      /* Keep element content when removing element? */
      let KEEP_CONTENT = true;
      /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
       * of importing it into a new Document and returning a sanitized copy */
      let IN_PLACE = false;
      /* Allow usage of profiles like html, svg and mathMl */
      let USE_PROFILES = {};
      /* Tags to ignore content of when KEEP_CONTENT is true */
      let FORBID_CONTENTS = null;
      const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
      /* Tags that are safe for data: URIs */
      let DATA_URI_TAGS = null;
      const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
      /* Attributes safe for values like "javascript:" */
      let URI_SAFE_ATTRIBUTES = null;
      const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
      const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
      const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
      const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
      /* Document namespace */
      let NAMESPACE = HTML_NAMESPACE;
      let IS_EMPTY_INPUT = false;
      /* Allowed XHTML+XML namespaces */
      let ALLOWED_NAMESPACES = null;
      const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
      let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
      let HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);
      // Certain elements are allowed in both SVG and HTML
      // namespace. We need to specify them explicitly
      // so that they don't get erroneously deleted from
      // HTML namespace.
      const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
      /* Parsing of strict XHTML documents */
      let PARSER_MEDIA_TYPE = null;
      const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
      const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
      let transformCaseFunc = null;
      /* Keep a reference to config to pass to hooks */
      let CONFIG = null;
      /* Ideally, do not touch anything below this line */
      /* ______________________________________________ */
      const formElement = document.createElement('form');
      const isRegexOrFunction = function isRegexOrFunction(testValue) {
        return testValue instanceof RegExp || testValue instanceof Function;
      };
      /**
       * _parseConfig
       *
       * @param cfg optional config literal
       */
      // eslint-disable-next-line complexity
      const _parseConfig = function _parseConfig() {
        let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        if (CONFIG && CONFIG === cfg) {
          return;
        }
        /* Shield configuration object from tampering */
        if (!cfg || typeof cfg !== 'object') {
          cfg = {};
        }
        /* Shield configuration object from prototype pollution */
        cfg = clone(cfg);
        PARSER_MEDIA_TYPE =
        // eslint-disable-next-line unicorn/prefer-includes
        SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;
        // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
        transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
        /* Set configuration parameters */
        ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
        ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
        ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
        URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;
        DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;
        FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
        FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
        FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
        USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;
        ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
        ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
        ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
        ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true
        SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
        SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true
        WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
        RETURN_DOM = cfg.RETURN_DOM || false; // Default false
        RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
        RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
        FORCE_BODY = cfg.FORCE_BODY || false; // Default false
        SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
        SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
        KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
        IN_PLACE = cfg.IN_PLACE || false; // Default false
        IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
        NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
        MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS;
        HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS;
        CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};
        if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
          CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
        }
        if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
          CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
        }
        if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
          CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
        }
        if (SAFE_FOR_TEMPLATES) {
          ALLOW_DATA_ATTR = false;
        }
        if (RETURN_DOM_FRAGMENT) {
          RETURN_DOM = true;
        }
        /* Parse profile info */
        if (USE_PROFILES) {
          ALLOWED_TAGS = addToSet({}, text);
          ALLOWED_ATTR = [];
          if (USE_PROFILES.html === true) {
            addToSet(ALLOWED_TAGS, html$1);
            addToSet(ALLOWED_ATTR, html);
          }
          if (USE_PROFILES.svg === true) {
            addToSet(ALLOWED_TAGS, svg$1);
            addToSet(ALLOWED_ATTR, svg);
            addToSet(ALLOWED_ATTR, xml);
          }
          if (USE_PROFILES.svgFilters === true) {
            addToSet(ALLOWED_TAGS, svgFilters);
            addToSet(ALLOWED_ATTR, svg);
            addToSet(ALLOWED_ATTR, xml);
          }
          if (USE_PROFILES.mathMl === true) {
            addToSet(ALLOWED_TAGS, mathMl$1);
            addToSet(ALLOWED_ATTR, mathMl);
            addToSet(ALLOWED_ATTR, xml);
          }
        }
        /* Merge configuration parameters */
        if (cfg.ADD_TAGS) {
          if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
            ALLOWED_TAGS = clone(ALLOWED_TAGS);
          }
          addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
        }
        if (cfg.ADD_ATTR) {
          if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
            ALLOWED_ATTR = clone(ALLOWED_ATTR);
          }
          addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
        }
        if (cfg.ADD_URI_SAFE_ATTR) {
          addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
        }
        if (cfg.FORBID_CONTENTS) {
          if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
            FORBID_CONTENTS = clone(FORBID_CONTENTS);
          }
          addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
        }
        /* Add #text in case KEEP_CONTENT is set to true */
        if (KEEP_CONTENT) {
          ALLOWED_TAGS['#text'] = true;
        }
        /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
        if (WHOLE_DOCUMENT) {
          addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
        }
        /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
        if (ALLOWED_TAGS.table) {
          addToSet(ALLOWED_TAGS, ['tbody']);
          delete FORBID_TAGS.tbody;
        }
        if (cfg.TRUSTED_TYPES_POLICY) {
          if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {
            throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
          }
          if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
            throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
          }
          // Overwrite existing TrustedTypes policy.
          trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
          // Sign local variables required by `sanitize`.
          emptyHTML = trustedTypesPolicy.createHTML('');
        } else {
          // Uninitialized policy, attempt to initialize the internal dompurify policy.
          if (trustedTypesPolicy === undefined) {
            trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
          }
          // If creating the internal policy succeeded sign internal variables.
          if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {
            emptyHTML = trustedTypesPolicy.createHTML('');
          }
        }
        // Prevent further manipulation of configuration.
        // Not available in IE8, Safari 5, etc.
        if (freeze) {
          freeze(cfg);
        }
        CONFIG = cfg;
      };
      /* Keep track of all possible SVG and MathML tags
       * so that we can perform the namespace checks
       * correctly. */
      const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
      const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
      /**
       * @param element a DOM element whose namespace is being checked
       * @returns Return false if the element has a
       *  namespace that a spec-compliant parser would never
       *  return. Return true otherwise.
       */
      const _checkValidNamespace = function _checkValidNamespace(element) {
        let parent = getParentNode(element);
        // In JSDOM, if we're inside shadow DOM, then parentNode
        // can be null. We just simulate parent in this case.
        if (!parent || !parent.tagName) {
          parent = {
            namespaceURI: NAMESPACE,
            tagName: 'template'
          };
        }
        const tagName = stringToLowerCase(element.tagName);
        const parentTagName = stringToLowerCase(parent.tagName);
        if (!ALLOWED_NAMESPACES[element.namespaceURI]) {
          return false;
        }
        if (element.namespaceURI === SVG_NAMESPACE) {
          // The only way to switch from HTML namespace to SVG
          // is via <svg>. If it happens via any other tag, then
          // it should be killed.
          if (parent.namespaceURI === HTML_NAMESPACE) {
            return tagName === 'svg';
          }
          // The only way to switch from MathML to SVG is via`
          // svg if parent is either <annotation-xml> or MathML
          // text integration points.
          if (parent.namespaceURI === MATHML_NAMESPACE) {
            return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
          }
          // We only allow elements that are defined in SVG
          // spec. All others are disallowed in SVG namespace.
          return Boolean(ALL_SVG_TAGS[tagName]);
        }
        if (element.namespaceURI === MATHML_NAMESPACE) {
          // The only way to switch from HTML namespace to MathML
          // is via <math>. If it happens via any other tag, then
          // it should be killed.
          if (parent.namespaceURI === HTML_NAMESPACE) {
            return tagName === 'math';
          }
          // The only way to switch from SVG to MathML is via
          // <math> and HTML integration points
          if (parent.namespaceURI === SVG_NAMESPACE) {
            return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
          }
          // We only allow elements that are defined in MathML
          // spec. All others are disallowed in MathML namespace.
          return Boolean(ALL_MATHML_TAGS[tagName]);
        }
        if (element.namespaceURI === HTML_NAMESPACE) {
          // The only way to switch from SVG to HTML is via
          // HTML integration points, and from MathML to HTML
          // is via MathML text integration points
          if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
            return false;
          }
          if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
            return false;
          }
          // We disallow tags that are specific for MathML
          // or SVG and should never appear in HTML namespace
          return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
        }
        // For XHTML and XML documents that support custom namespaces
        if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
          return true;
        }
        // The code should never reach this place (this means
        // that the element somehow got namespace that is not
        // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).
        // Return false just in case.
        return false;
      };
      /**
       * _forceRemove
       *
       * @param node a DOM node
       */
      const _forceRemove = function _forceRemove(node) {
        arrayPush(DOMPurify.removed, {
          element: node
        });
        try {
          // eslint-disable-next-line unicorn/prefer-dom-node-remove
          getParentNode(node).removeChild(node);
        } catch (_) {
          remove(node);
        }
      };
      /**
       * _removeAttribute
       *
       * @param name an Attribute name
       * @param element a DOM node
       */
      const _removeAttribute = function _removeAttribute(name, element) {
        try {
          arrayPush(DOMPurify.removed, {
            attribute: element.getAttributeNode(name),
            from: element
          });
        } catch (_) {
          arrayPush(DOMPurify.removed, {
            attribute: null,
            from: element
          });
        }
        element.removeAttribute(name);
        // We void attribute values for unremovable "is" attributes
        if (name === 'is') {
          if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
            try {
              _forceRemove(element);
            } catch (_) {}
          } else {
            try {
              element.setAttribute(name, '');
            } catch (_) {}
          }
        }
      };
      /**
       * _initDocument
       *
       * @param dirty - a string of dirty markup
       * @return a DOM, filled with the dirty markup
       */
      const _initDocument = function _initDocument(dirty) {
        /* Create a HTML document */
        let doc = null;
        let leadingWhitespace = null;
        if (FORCE_BODY) {
          dirty = '<remove></remove>' + dirty;
        } else {
          /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
          const matches = stringMatch(dirty, /^[\r\n\t ]+/);
          leadingWhitespace = matches && matches[0];
        }
        if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) {
          // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
          dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
        }
        const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
        /*
         * Use the DOMParser API by default, fallback later if needs be
         * DOMParser not work for svg when has multiple root element.
         */
        if (NAMESPACE === HTML_NAMESPACE) {
          try {
            doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
          } catch (_) {}
        }
        /* Use createHTMLDocument in case DOMParser is not available */
        if (!doc || !doc.documentElement) {
          doc = implementation.createDocument(NAMESPACE, 'template', null);
          try {
            doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;
          } catch (_) {
            // Syntax error if dirtyPayload is invalid xml
          }
        }
        const body = doc.body || doc.documentElement;
        if (dirty && leadingWhitespace) {
          body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
        }
        /* Work on whole document or just its body */
        if (NAMESPACE === HTML_NAMESPACE) {
          return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
        }
        return WHOLE_DOCUMENT ? doc.documentElement : body;
      };
      /**
       * Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.
       *
       * @param root The root element or node to start traversing on.
       * @return The created NodeIterator
       */
      const _createNodeIterator = function _createNodeIterator(root) {
        return createNodeIterator.call(root.ownerDocument || root, root,
        // eslint-disable-next-line no-bitwise
        NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
      };
      /**
       * _isClobbered
       *
       * @param element element to check for clobbering attacks
       * @return true if clobbered, false if safe
       */
      const _isClobbered = function _isClobbered(element) {
        return element instanceof HTMLFormElement && (typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' || !(element.attributes instanceof NamedNodeMap) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function');
      };
      /**
       * Checks whether the given object is a DOM node.
       *
       * @param value object to check whether it's a DOM node
       * @return true is object is a DOM node
       */
      const _isNode = function _isNode(value) {
        return typeof Node === 'function' && value instanceof Node;
      };
      function _executeHooks(hooks, currentNode, data) {
        arrayForEach(hooks, hook => {
          hook.call(DOMPurify, currentNode, data, CONFIG);
        });
      }
      /**
       * _sanitizeElements
       *
       * @protect nodeName
       * @protect textContent
       * @protect removeChild
       * @param currentNode to check for permission to exist
       * @return true if node was killed, false if left alive
       */
      const _sanitizeElements = function _sanitizeElements(currentNode) {
        let content = null;
        /* Execute a hook if present */
        _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
        /* Check if element is clobbered or can clobber */
        if (_isClobbered(currentNode)) {
          _forceRemove(currentNode);
          return true;
        }
        /* Now let's check the element's type and name */
        const tagName = transformCaseFunc(currentNode.nodeName);
        /* Execute a hook if present */
        _executeHooks(hooks.uponSanitizeElement, currentNode, {
          tagName,
          allowedTags: ALLOWED_TAGS
        });
        /* Detect mXSS attempts abusing namespace confusion */
        if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
          _forceRemove(currentNode);
          return true;
        }
        /* Remove any occurrence of processing instructions */
        if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
          _forceRemove(currentNode);
          return true;
        }
        /* Remove any kind of possibly harmful comments */
        if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
          _forceRemove(currentNode);
          return true;
        }
        /* Remove element if anything forbids its presence */
        if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
          /* Check if we have a custom element to handle */
          if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
            if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
              return false;
            }
            if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
              return false;
            }
          }
          /* Keep content except for bad-listed elements */
          if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
            const parentNode = getParentNode(currentNode) || currentNode.parentNode;
            const childNodes = getChildNodes(currentNode) || currentNode.childNodes;
            if (childNodes && parentNode) {
              const childCount = childNodes.length;
              for (let i = childCount - 1; i >= 0; --i) {
                const childClone = cloneNode(childNodes[i], true);
                childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
                parentNode.insertBefore(childClone, getNextSibling(currentNode));
              }
            }
          }
          _forceRemove(currentNode);
          return true;
        }
        /* Check whether element has a valid namespace */
        if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
          _forceRemove(currentNode);
          return true;
        }
        /* Make sure that older browsers don't get fallback-tag mXSS */
        if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
          _forceRemove(currentNode);
          return true;
        }
        /* Sanitize element content to be template-safe */
        if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
          /* Get the element's text content */
          content = currentNode.textContent;
          arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
            content = stringReplace(content, expr, ' ');
          });
          if (currentNode.textContent !== content) {
            arrayPush(DOMPurify.removed, {
              element: currentNode.cloneNode()
            });
            currentNode.textContent = content;
          }
        }
        /* Execute a hook if present */
        _executeHooks(hooks.afterSanitizeElements, currentNode, null);
        return false;
      };
      /**
       * _isValidAttribute
       *
       * @param lcTag Lowercase tag name of containing element.
       * @param lcName Lowercase attribute name.
       * @param value Attribute value.
       * @return Returns true if `value` is valid, otherwise false.
       */
      // eslint-disable-next-line complexity
      const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
        /* Make sure attribute cannot clobber */
        if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
          return false;
        }
        /* Allow valid data-* attributes: At least one character after "-"
            (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
            XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
            We don't need to check the value; it's always URI safe. */
        if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
          if (
          // First condition does a very basic check if a) it's basically a valid custom element tagname AND
          // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
          // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
          _isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)) ||
          // Alternative, second condition checks if it's an `is`-attribute, AND
          // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
          lcName === 'is' && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))) ; else {
            return false;
          }
          /* Check value is safe. First, is attr inert? If so, is safe */
        } else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if (value) {
          return false;
        } else ;
        return true;
      };
      /**
       * _isBasicCustomElement
       * checks if at least one dash is included in tagName, and it's not the first char
       * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
       *
       * @param tagName name of the tag of the node to sanitize
       * @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
       */
      const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
        return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT);
      };
      /**
       * _sanitizeAttributes
       *
       * @protect attributes
       * @protect nodeName
       * @protect removeAttribute
       * @protect setAttribute
       *
       * @param currentNode to sanitize
       */
      const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
        /* Execute a hook if present */
        _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
        const {
          attributes
        } = currentNode;
        /* Check if we have attributes; if not we might have a text node */
        if (!attributes || _isClobbered(currentNode)) {
          return;
        }
        const hookEvent = {
          attrName: '',
          attrValue: '',
          keepAttr: true,
          allowedAttributes: ALLOWED_ATTR,
          forceKeepAttr: undefined
        };
        let l = attributes.length;
        /* Go backwards over all attributes; safely remove bad ones */
        while (l--) {
          const attr = attributes[l];
          const {
            name,
            namespaceURI,
            value: attrValue
          } = attr;
          const lcName = transformCaseFunc(name);
          const initValue = attrValue;
          let value = name === 'value' ? initValue : stringTrim(initValue);
          /* Execute a hook if present */
          hookEvent.attrName = lcName;
          hookEvent.attrValue = value;
          hookEvent.keepAttr = true;
          hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
          _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
          value = hookEvent.attrValue;
          /* Full DOM Clobbering protection via namespace isolation,
           * Prefix id and name attributes with `user-content-`
           */
          if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
            // Remove the attribute with this value
            _removeAttribute(name, currentNode);
            // Prefix the value and later re-create the attribute with the sanitized value
            value = SANITIZE_NAMED_PROPS_PREFIX + value;
          }
          /* Work around a security issue with comments inside attributes */
          if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title)/i, value)) {
            _removeAttribute(name, currentNode);
            continue;
          }
          /* Did the hooks approve of the attribute? */
          if (hookEvent.forceKeepAttr) {
            continue;
          }
          /* Did the hooks approve of the attribute? */
          if (!hookEvent.keepAttr) {
            _removeAttribute(name, currentNode);
            continue;
          }
          /* Work around a security issue in jQuery 3.0 */
          if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
            _removeAttribute(name, currentNode);
            continue;
          }
          /* Sanitize attribute content to be template-safe */
          if (SAFE_FOR_TEMPLATES) {
            arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
              value = stringReplace(value, expr, ' ');
            });
          }
          /* Is `value` valid for this attribute? */
          const lcTag = transformCaseFunc(currentNode.nodeName);
          if (!_isValidAttribute(lcTag, lcName, value)) {
            _removeAttribute(name, currentNode);
            continue;
          }
          /* Handle attributes that require Trusted Types */
          if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') {
            if (namespaceURI) ; else {
              switch (trustedTypes.getAttributeType(lcTag, lcName)) {
                case 'TrustedHTML':
                  {
                    value = trustedTypesPolicy.createHTML(value);
                    break;
                  }
                case 'TrustedScriptURL':
                  {
                    value = trustedTypesPolicy.createScriptURL(value);
                    break;
                  }
              }
            }
          }
          /* Handle invalid data-* attribute set by try-catching it */
          if (value !== initValue) {
            try {
              if (namespaceURI) {
                currentNode.setAttributeNS(namespaceURI, name, value);
              } else {
                /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
                currentNode.setAttribute(name, value);
              }
              if (_isClobbered(currentNode)) {
                _forceRemove(currentNode);
              } else {
                arrayPop(DOMPurify.removed);
              }
            } catch (_) {
              _removeAttribute(name, currentNode);
            }
          }
        }
        /* Execute a hook if present */
        _executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
      };
      /**
       * _sanitizeShadowDOM
       *
       * @param fragment to iterate over recursively
       */
      const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
        let shadowNode = null;
        const shadowIterator = _createNodeIterator(fragment);
        /* Execute a hook if present */
        _executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);
        while (shadowNode = shadowIterator.nextNode()) {
          /* Execute a hook if present */
          _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
          /* Sanitize tags and elements */
          _sanitizeElements(shadowNode);
          /* Check attributes next */
          _sanitizeAttributes(shadowNode);
          /* Deep shadow DOM detected */
          if (shadowNode.content instanceof DocumentFragment) {
            _sanitizeShadowDOM(shadowNode.content);
          }
        }
        /* Execute a hook if present */
        _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
      };
      // eslint-disable-next-line complexity
      DOMPurify.sanitize = function (dirty) {
        let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        let body = null;
        let importedNode = null;
        let currentNode = null;
        let returnNode = null;
        /* Make sure we have a string to sanitize.
          DO NOT return early, as this will return the wrong type if
          the user has requested a DOM object rather than a string */
        IS_EMPTY_INPUT = !dirty;
        if (IS_EMPTY_INPUT) {
          dirty = '<!-->';
        }
        /* Stringify, in case dirty is an object */
        if (typeof dirty !== 'string' && !_isNode(dirty)) {
          if (typeof dirty.toString === 'function') {
            dirty = dirty.toString();
            if (typeof dirty !== 'string') {
              throw typeErrorCreate('dirty is not a string, aborting');
            }
          } else {
            throw typeErrorCreate('toString is not a function');
          }
        }
        /* Return dirty HTML if DOMPurify cannot run */
        if (!DOMPurify.isSupported) {
          return dirty;
        }
        /* Assign config vars */
        if (!SET_CONFIG) {
          _parseConfig(cfg);
        }
        /* Clean up removed elements */
        DOMPurify.removed = [];
        /* Check if dirty is correctly typed for IN_PLACE */
        if (typeof dirty === 'string') {
          IN_PLACE = false;
        }
        if (IN_PLACE) {
          /* Do some early pre-sanitization to avoid unsafe root nodes */
          if (dirty.nodeName) {
            const tagName = transformCaseFunc(dirty.nodeName);
            if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
              throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
            }
          }
        } else if (dirty instanceof Node) {
          /* If dirty is a DOM element, append to an empty document to avoid
             elements being stripped by the parser */
          body = _initDocument('<!---->');
          importedNode = body.ownerDocument.importNode(dirty, true);
          if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === 'BODY') {
            /* Node is already a body, use as is */
            body = importedNode;
          } else if (importedNode.nodeName === 'HTML') {
            body = importedNode;
          } else {
            // eslint-disable-next-line unicorn/prefer-dom-node-append
            body.appendChild(importedNode);
          }
        } else {
          /* Exit directly if we have nothing to do */
          if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
          // eslint-disable-next-line unicorn/prefer-includes
          dirty.indexOf('<') === -1) {
            return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
          }
          /* Initialize the document to work on */
          body = _initDocument(dirty);
          /* Check we have a DOM node from the data */
          if (!body) {
            return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
          }
        }
        /* Remove first element node (ours) if FORCE_BODY is set */
        if (body && FORCE_BODY) {
          _forceRemove(body.firstChild);
        }
        /* Get node iterator */
        const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);
        /* Now start iterating over the created document */
        while (currentNode = nodeIterator.nextNode()) {
          /* Sanitize tags and elements */
          _sanitizeElements(currentNode);
          /* Check attributes next */
          _sanitizeAttributes(currentNode);
          /* Shadow DOM detected, sanitize it */
          if (currentNode.content instanceof DocumentFragment) {
            _sanitizeShadowDOM(currentNode.content);
          }
        }
        /* If we sanitized `dirty` in-place, return it. */
        if (IN_PLACE) {
          return dirty;
        }
        /* Return sanitized string or DOM */
        if (RETURN_DOM) {
          if (RETURN_DOM_FRAGMENT) {
            returnNode = createDocumentFragment.call(body.ownerDocument);
            while (body.firstChild) {
              // eslint-disable-next-line unicorn/prefer-dom-node-append
              returnNode.appendChild(body.firstChild);
            }
          } else {
            returnNode = body;
          }
          if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) {
            /*
              AdoptNode() is not used because internal state is not reset
              (e.g. the past names map of a HTMLFormElement), this is safe
              in theory but we would rather not risk another attack vector.
              The state that is cloned by importNode() is explicitly defined
              by the specs.
            */
            returnNode = importNode.call(originalDocument, returnNode, true);
          }
          return returnNode;
        }
        let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
        /* Serialize doctype if allowed */
        if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
          serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
        }
        /* Sanitize final string template-safe */
        if (SAFE_FOR_TEMPLATES) {
          arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
            serializedHTML = stringReplace(serializedHTML, expr, ' ');
          });
        }
        return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
      };
      DOMPurify.setConfig = function () {
        let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        _parseConfig(cfg);
        SET_CONFIG = true;
      };
      DOMPurify.clearConfig = function () {
        CONFIG = null;
        SET_CONFIG = false;
      };
      DOMPurify.isValidAttribute = function (tag, attr, value) {
        /* Initialize shared config vars if necessary. */
        if (!CONFIG) {
          _parseConfig({});
        }
        const lcTag = transformCaseFunc(tag);
        const lcName = transformCaseFunc(attr);
        return _isValidAttribute(lcTag, lcName, value);
      };
      DOMPurify.addHook = function (entryPoint, hookFunction) {
        if (typeof hookFunction !== 'function') {
          return;
        }
        arrayPush(hooks[entryPoint], hookFunction);
      };
      DOMPurify.removeHook = function (entryPoint, hookFunction) {
        if (hookFunction !== undefined) {
          const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
          return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0];
        }
        return arrayPop(hooks[entryPoint]);
      };
      DOMPurify.removeHooks = function (entryPoint) {
        hooks[entryPoint] = [];
      };
      DOMPurify.removeAllHooks = function () {
        hooks = _createHooksMap();
      };
      return DOMPurify;
    }
    var purify = createDOMPurify();

    const DOMPurify = purify(window);

    class QscChatbot extends HTMLElement {
      constructor() {
        super();
        this.attachShadow({mode:'open'});
        this.isOpen=false; 
        this.isFullscreen=false; 
        this.isMinimized=false;
        this.messages=[];
        this.unreadCount=0;
        this.editingId = null;
        this._initialFetched = false;
        this._copyTimers = {}; 
        this.sessionId = null;
        this.inputValue = "";
        this.receivedModels = [];      
        this.selectedModel = null;
        this.showModelMenu = false;
        this.serverStreamingEnabled = false;
        this._streamingUpdateTimeouts = {};

         this._copySVG = `
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"
           stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <rect x="9" y="9" width="13" height="13" rx="2"></rect>
        <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9"></path>
      </svg>
    `;
        this._checkSVG = `
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
           stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <path d="M20 6L9 17l-5-5"></path>
      </svg>
    `;
      }

      _isUserAtBottom() {
        const messagesDiv = this.shadowRoot.querySelector('.messages');
        if (!messagesDiv) return true;
        const threshold = 100;
        const position = messagesDiv.scrollTop + messagesDiv.clientHeight;
        const height = messagesDiv.scrollHeight;
        return position >= height - threshold;
      }

      _scrollToBottom() {
        const messagesDiv = this.shadowRoot.querySelector('.messages');
        if (messagesDiv) {
          messagesDiv.scrollTop = messagesDiv.scrollHeight;
        }
      }

      _debouncedStreamingUpdate(id, rawMarkdown, streaming = true, delay = 16) {
        if (this._streamingUpdateTimeouts[id]) {
          clearTimeout(this._streamingUpdateTimeouts[id]);
        }
        
        this._streamingUpdateTimeouts[id] = setTimeout(() => {
          requestAnimationFrame(() => {
            this._updateStreamingMessage(id, rawMarkdown, streaming);
          });
        }, delay);
      }

       _updateStreamingMessage(id, rawMarkdown, isStreaming = true) {
        const messageRow = this.shadowRoot.querySelector(`[data-msg-id="${id}"]`);
        if (!messageRow) return;

        const messageText = messageRow.querySelector('.message-text');
        const timestampContainer = messageRow.querySelector('.timestamp-container');
        
        if (!messageText) return;

        try {
          const messageIndex = this.messages.findIndex(m => m.id === id);
          if (messageIndex !== -1) {
            this.messages[messageIndex].text = rawMarkdown;
          }

          if (!rawMarkdown || rawMarkdown.trim() === '') {
            if (!messageText.querySelector('.typing-indicator')) {
              messageText.innerHTML = '<div class="typing-indicator"><span></span><span></span><span></span></div>';
            }
          } else {
            const typingIndicator = messageText.querySelector('.typing-indicator');
            if (typingIndicator) {
              typingIndicator.remove();
            }
            
            const html = this._processMarkdown(rawMarkdown, isStreaming);
            
            const currentContent = messageText.innerHTML;
            if (currentContent !== html || isStreaming) {
              messageText.innerHTML = html;
            }
            
            if (!isStreaming) {
              setTimeout(() => {
                this._splitAndRenderMultipleJsons();
                this._formatAndHighlightJson();
                this._attachCodeCopyButtons(id);
              }, 50);
            }
          }

          if (timestampContainer) {
            if (isStreaming) {
              if (!timestampContainer.querySelector('.streaming-indicator') && !messageText.querySelector('.typing-indicator')) {
                timestampContainer.innerHTML = '<div class="streaming-indicator"><span></span><span></span><span></span></div>';
              }
            } else {
              const message = this.messages.find(m => m.id === id);
              if (message) {
                timestampContainer.innerHTML = `<div class="timestamp">${this.formatTime(message.timestamp)}</div>`;
              }
            }
          }
        } catch (e) {
          console.warn("Markdown parse error:", e);
          messageText.innerHTML = `<pre>${this._escapeHtml(rawMarkdown)}</pre>`;
        }
      }
      _startNewChat() {
        this.sessionId = null;
        try {
          localStorage.removeItem('qsc_session_id');
        } catch (e) {
        }
        this.messages = [];
        this._initialFetched = false;
        this._fetchInitialMessage().catch(err => console.error(err));

        this.renderMessages({ autoScroll: 'bottom' });
        this.render();
      }

      async connectedCallback() {
        this.logoPath=this.getAttribute('logo-path');
        this.headerTitle=this.getAttribute('header-title') || 'QSC Chatbot';
        this.errMsg=this.getAttribute('error-msg') || 'Connection to the server failed';
        this.attachBtn=this.getAttribute('attach-btn') === 'true';
        this.restUrl = this.getAttribute('rest-url') || 'no-rest-url-provided';
        
        this.render();
      }

      async _fetchInitialMessage() {
        if (this._initialFetched) return;
        this._initialFetched = true;

        const loadingId = `initial-loading-${Date.now()}`;
        this.messages.push({
          id: loadingId,
          text: `
        <div class="typing-indicator">
          <span></span><span></span><span></span>
        </div>
      `,
          sender: 'bot',
          timestamp: new Date(),
          isLoading: true
        });
        this.renderMessages({ autoScroll: 'bottom' });

        const payload = {
          type: 'init',                 
          sessionId: null               
        };

        try {
          const res = await fetch(this.restUrl, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload)
          });
          if (!res.ok) {
            throw new Error(`HTTP error! Status: ${res.status}`);
          }
          const data = await res.json();

          if (Array.isArray(data.models) && data.models.length > 0) {
            this._setModels(data.models);
          }

          this.handleRestBotResponse(data);
          
        } catch (err) {
          // remove loading indicator
          this.messages = this.messages.filter(m => !m.isLoading);
          this._pushSystem(this.errMsg);
          console.error('Initial fetch failed', err);
        }
      }

      _setModels(modelsFromServer) {
        if (!modelsFromServer || !Array.isArray(modelsFromServer) || modelsFromServer.length === 0) {
          this.receivedModels = [];
          this.selectedModel = null;
          this.showModelMenu = false;
          return;
        }

        this.receivedModels = modelsFromServer.map((m, i) => {
          if (typeof m === 'string') {
            return { model: m, label: String(m), selected: m.selected || false };
          } else if (m && typeof m === 'object') {
            return { model: m.model, label: String(m.label) || String(m), selected: m.selected || false};
          } else {
            return { model: m, label: String(m),selected: m.selected || false };
          }
        });

        if (this.selectedModel) {
          const keep = this.receivedModels.find(x => String(x.model) === String(this.selectedModel.model)
                                                  || String(x.label) === String(this.selectedModel.label));
          if (keep) {
            this.selectedModel = keep;
            return;
          }
        }
        this.selectedModel =  this.receivedModels.find(x => x.selected === true) || null;
        this.render();
      }

      _selectModel(model) {
        this.selectedModel = model;
        this.showModelMenu = false;
        this.render();
      }

      _removeSelectedModel() {
        this.selectedModel = null;
        this.render();
      }

      _pushSystem(txt) {
        this.messages.push({id:Date.now(),text:txt,sender:'system',timestamp:new Date()});
        if(!this.isOpen){ this.showBroadcastPopup(txt); this.unreadCount++; }
        this.renderMessages({ autoScroll: 'bottom' });
      }

      async _sendActionPrompt(promptText, uiPrompt = null, isSilent = false) {
        if (!promptText) throw new Error('Empty prompt');
        const modelToSend = this.selectedModel ? (this.selectedModel.model || this.selectedModel.name) : undefined;
        
        if (!isSilent) {
          const displayText = uiPrompt || promptText;
          if (displayText) {
            this.messages.push({ 
              id: `action-${Date.now()}`, 
              text: displayText, 
              sender: 'user', 
              timestamp: new Date() 
            });
          }
        }

        const botMsgId = 'bot-action-' + Date.now();
        this.messages.push({
          id: botMsgId,
          text: `
        <div class="typing-indicator">
          <span></span><span></span><span></span>
        </div>
      `,
          sender: 'bot',
          timestamp: new Date(),
          isLoading: true,
          isStreaming: false
        });

        this.renderMessages({ autoScroll: 'bottom' });
        
        try {
          const res = await fetch(this.restUrl, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ 
              type: 'message', 
              text: promptText,
              model: modelToSend,
              sessionId: this.sessionId, 
              id: `rest-${Date.now()}` 
            })
          });
          
          if (!res.ok) {
            throw new Error(`HTTP error! Status: ${res.status}`);
          }

          if (this.serverStreamingEnabled && res.body) {

            // Remove loading and create streaming message
            this.messages = this.messages.filter(m => m.id !== botMsgId);
            const streamingBotMsgId = 'bot-action-stream-' + Date.now();
            this.messages.push({
              id: streamingBotMsgId,
              text: '',
              sender: 'bot',
              timestamp: new Date(),
              isLoading: false,
              isStreaming: true
            });
            this.renderMessages({ autoScroll: 'bottom' });

            const reader = res.body.getReader();
            const decoder = new TextDecoder();
            let fullText = '';

            try {
              while (true) {
                const { done, value } = await reader.read();
                if (done) break;

                const chunk = decoder.decode(value, { stream: true });

                const lines = chunk.split('\n');
                
                for (const line of lines) {
                  if (!line.trim()) continue;
                  
                  try {
                    const json = JSON.parse(line);
                    
                    if (json.type === 'broadcast') {
                      this._pushSystem(json.data);
                    } 
                    if (json.session_id) {
                      this.sessionId = json.session_id;
                      localStorage.setItem('qsc_session_id', this.sessionId);
                    } 
                    if (json.data !== undefined) {
                      fullText += json.data;
                      
                      const index = this.messages.findIndex(m => m.id === streamingBotMsgId);
                      if (index !== -1) {
                        this.messages[index].text = fullText;
                        this.messages[index].timestamp = new Date();
                        
                        this._debouncedStreamingUpdate(streamingBotMsgId, fullText, true, 16);
                        
                        if (this._isUserAtBottom()) {
                          this._scrollToBottom();
                        }
                      }
                    }
                  } catch (e) {
                    console.warn('Stream parse error in action:', e);
                  }
                }
              }
            } finally {
              // Mark as done streaming
              const index = this.messages.findIndex(m => m.id === streamingBotMsgId);
              if (index !== -1) {
                this.messages[index].isStreaming = false;
                this._updateStreamingMessage(streamingBotMsgId, fullText, false);
              }
            }
          } else {
            
            const data = await res.json();
            this.messages = this.messages.filter(m => m.id !== botMsgId);
            this.handleRestBotResponse(data);

          }
        } catch (err) {
          this.messages = this.messages.filter(m => !m.isLoading && !m.isStreaming);
          this._pushSystem(this.errMsg);
          console.error('Action fetch failed', err);
        }
      }

      _renderModelDropdown() {
        const dropdownContainer = this.shadowRoot.querySelector('.model-dropdown-container');
        if (!dropdownContainer) return;

        if (this.receivedModels && this.receivedModels.length > 0 && this.showModelMenu) {
          dropdownContainer.innerHTML = `
        <div class="model-dropdown">
            ${this.receivedModels.map(model => `
                          <button class="model-item ${this.selectedModel && this.selectedModel.model === model.model ? 'selected' : ''}" 
                                  data-model="${model.model}">
                            <span>${model.label}</span>
                            ${this.selectedModel && this.selectedModel.model === model.model ? '✓' : ''}
                          </button>
                        `).join('')}
        </div>
      `;

          dropdownContainer.querySelectorAll('.model-item').forEach(option => {
            option.addEventListener('click', (e) => {
              e.stopPropagation();
              const modelValue = option.getAttribute('data-model');
              const selected = this.receivedModels.find(m => m.model === modelValue);
              if (selected) 
                this._selectModel(selected);
            });
          });
        } else {
          dropdownContainer.innerHTML = '';
        }
      }

      async handleSend() {
        const input = this.shadowRoot.querySelector('.chat-input');
        const value = input.value.trim();
        if (!value) return;
        
        if (this.editingId) {
          await this._handleEditSend(value);
          return;
        }

        // Regular send case
        this.messages.push({ 
          id: Date.now(), 
          text: value, 
          sender: 'user', 
          timestamp: new Date() 
        });
        
        const botMsgId = 'bot-' + Date.now();
        this.messages.push({
          id: botMsgId,
          text: `
        <div class="typing-indicator">
          <span></span><span></span><span></span>
        </div>
      `,
          sender: 'bot',
          timestamp: new Date(),
          isLoading: true,
          isStreaming: false
        });

        this.renderMessages({ autoScroll: 'bottom' });
        input.value = '';
        this.inputValue = '';
        input.style.height = 'auto';

        await this._sendMessageToServer(value, botMsgId);
      }

      async _handleEditSend(value) {
        const idx = this._findIndexById(this.editingId);
        if (idx === -1) {
          this.editingId = null;
          return;
        }

        this.messages[idx].text = value;
        this.messages[idx].timestamp = new Date();
        this._removeMessagesAfterIndex(idx);

        const botMsgId = 'bot-edit-' + Date.now();
        this.messages.push({
          id: botMsgId,
          text: `
        <div class="typing-indicator">
          <span></span><span></span><span></span>
        </div>
      `,
          sender: 'bot',
          timestamp: new Date(),
          isLoading: true,
          isStreaming: false
        });

        this.renderMessages({ autoScroll: 'bottom' });

        const originalEditId = this.editingId;
        this.editingId = null;
        const input = this.shadowRoot.querySelector('.chat-input');
        input.value = '';
        input.style.height = '1px';
        input.placeholder = 'Type a message...';

        await this._sendMessageToServer(value, botMsgId, originalEditId);
      }
      

      async _sendMessageToServer(text, botMsgId, editedFrom = null) {
        const modelToSend = this.selectedModel ? (this.selectedModel.model || this.selectedModel.name) : undefined;
        const sessionId = localStorage.getItem('qsc_session_id') || this.sessionId || this._getSessionId();

        try {
          const res = await fetch(this.restUrl, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ 
              type: 'message', 
              text: text, 
              sessionId: sessionId, 
              id: `rest-${Date.now()}`, 
              model: modelToSend,
              editedFrom: editedFrom
            })
          });
          
          if (!res.ok) {
            throw new Error(`HTTP error! Status: ${res.status}`);
          }


          if (this.serverStreamingEnabled && res.body) {
            
            // Remove loading message and create streaming message
            this.messages = this.messages.filter(m => m.id !== botMsgId);
            const streamingBotMsgId = 'bot-stream-' + Date.now();
            this.messages.push({
              id: streamingBotMsgId,
              text: '',
              sender: 'bot',
              timestamp: new Date(),
              isLoading: false,
              isStreaming: true
            });
            this.renderMessages({ autoScroll: 'bottom' });

            // Get the reader
            const reader = res.body.getReader();
            const decoder = new TextDecoder();
            let fullText = '';

            try {
              while (true) {
                const { done, value } = await reader.read();
                if (done) {
                  break;
                }

                const chunk = decoder.decode(value, { stream: true });
                
                // Split by newlines and process each line
                const lines = chunk.split('\n');
                
                for (const line of lines) {
                  if (!line.trim()) continue;
                                
                  try {
                    const json = JSON.parse(line);
                    
                    // Handle different message types
                    if (json.type === 'broadcast') {
                      this._pushSystem(json.data);
                    } 
                    if (json.session_id) {
                      this.sessionId = json.session_id;
                      localStorage.setItem('qsc_session_id', this.sessionId);
                    } 
                    if (json.data !== undefined) {
                      fullText += json.data;
                      
                      const index = this.messages.findIndex(m => m.id === streamingBotMsgId);
                      if (index !== -1) {
                        this.messages[index].text = fullText;
                        this.messages[index].timestamp = new Date();
                        
                       
                        this._debouncedStreamingUpdate(streamingBotMsgId, fullText, true, 16);
                        
                        if (this._isUserAtBottom()) {
                          this._scrollToBottom();
                        }
                      }
                    }
                  } catch (e) {
                    console.warn('JSON parse error:', e);
                  }
                }
              }
            } finally {
                const index = this.messages.findIndex(m => m.id === streamingBotMsgId);
                if (index !== -1) {
                  this.messages[index].isStreaming = false;
                  
                  if (fullText.trim() !== '') {
                    this._updateStreamingMessage(streamingBotMsgId, fullText, false);
                  } else {
                    // If still empty after stream ends, show a fallback message
                    const messageText = this.shadowRoot.querySelector(`.message-text[data-msg-id="${streamingBotMsgId}"]`);
                    if (messageText) {
                      messageText.innerHTML = `<em>No response received</em>`;
                    }
                  }
                  
                  // Remove streaming styling
                  const bubble = this.shadowRoot.querySelector(`[data-msg-id="${streamingBotMsgId}"] .bubble.bot`);
                  if (bubble) {
                    bubble.classList.remove('message-streaming');
                  }
                }
              }
          } else {
           
            const data = await res.json();
            
            // Remove loading message
            this.messages = this.messages.filter(m => m.id !== botMsgId);
            this.handleRestBotResponse(data);
          }
          
        } catch (err) {
          console.error('Send fetch failed:', err);
          this.messages = this.messages.filter(m => !m.isLoading && !m.isStreaming);
          this._pushSystem(this.errMsg);
        }
      }

      handleClick(e) {
        const toggleBtn = e.target.closest('.toggle-btn');
        if (toggleBtn) {
          this.isOpen = !this.isOpen;
          if (this.isOpen) this.unreadCount = 0;
          this.render();
          if (this.isOpen) {
            this.scrollToBottom();
            this._fetchInitialMessage().catch(err => console.error(err));
          }
          return;
        }
        
        if (e.target.closest('.send-btn')) {
          this.handleSend();
        }
      }

      _autoResizeChatInput() {
        const input = this.shadowRoot && this.shadowRoot.querySelector('.chat-input');
        if (!input) return;

        let computed = window.getComputedStyle(input);
        let maxHeight = parseFloat(computed.maxHeight) || 150;

        input.style.height = 'auto';
        // measure needed height
        const needed = input.scrollHeight;

        if (needed <= maxHeight) {
          input.style.overflowY = 'hidden';
          input.style.height = needed + 'px';
        } else {
          input.style.overflowY = 'auto';
          input.style.height = maxHeight + 'px';
        }
      }

      handleKeyDown(e) {
       if (!e.target.classList.contains('chat-input')) return;
        if (e.key === 'Enter') {
          if (e.shiftKey) {
            setTimeout(() => this._autoResizeChatInput(), 0);
          } else {
            e.preventDefault();
            this.handleSend();
          }
        }
      }

      handleRestBotResponse(data) {
        
        this.messages = this.messages.filter(m => !m.isLoading && !m.isStreaming);

        if (data.session_id && !this.sessionId) {
          this.sessionId = data.session_id;
          localStorage.setItem('qsc_session_id', this.sessionId);
        }

        if (data.streaming !== undefined) {
          this.serverStreamingEnabled = data.streaming;
        }

        if (data.type === 'markdown' && data.data) {
          const html = this._processMarkdown(data.data);
          this.messages.push({ 
            id: Date.now(), 
            text: html, 
            sender: 'bot', 
            timestamp: new Date(),
            isHtml: true
          });
          this.renderMessages({ autoScroll: 'bottom' });
        } else if (data.type === 'broadcast') {
          let msg = data.text || data.message || data.data;
          this._pushSystem(msg);
        } else if (data.type === 'image') {
          const html = `<img src="${data.data}" alt="server image" style="max-width:200px;max-height:200px;">`;
          this.messages.push({ 
            id: Date.now(), 
            text: html, 
            sender: 'bot', 
            timestamp: new Date() 
          });
          this.renderMessages({ autoScroll: 'bottom' });
        } else if (data.data) {
          const html = this._processMarkdown(data.data);
          this.messages.push({ 
            id: Date.now(), 
            text: html, 
            sender: 'bot', 
            timestamp: new Date() 
          });
          this.renderMessages({ autoScroll: 'bottom' });
        }
      }

      _processMarkdown(raw, isStreaming = false) {
        try {
          // Check if we have incomplete HTML tags
          const incompleteTagRegex = /<[^>]*$/;
          
          let processedRaw = raw;
          let hasIncompleteTag = false;
          
          if (incompleteTagRegex.test(raw)) {
            // Find the last complete part
            const lastCompleteIndex = raw.lastIndexOf('>');
            if (lastCompleteIndex !== -1) {
              processedRaw = raw.substring(0, lastCompleteIndex + 1);
              hasIncompleteTag = true;
            } else {
              // No complete tags at all, use everything up to the first 
              const firstOpenIndex = raw.indexOf('<');
              if (firstOpenIndex !== -1) {
                processedRaw = raw.substring(0, firstOpenIndex);
                hasIncompleteTag = true;
              }
            }
          }
          
          // Check for incomplete QSCACTION tags
          const incompleteQscRegex = /\[\[QSCACTION[^\]]*$/;
          if (incompleteQscRegex.test(processedRaw)) {
            // Remove incomplete QSCACTION tag
            processedRaw = processedRaw.replace(incompleteQscRegex, '');
          }
          
          // Check for incomplete markdown images 
         
          const incompleteImageRegex = /!\[(?:[^\]]*(?:\][^)]*)?)?$/;
          
          if (incompleteImageRegex.test(processedRaw)) {
            processedRaw = processedRaw.replace(incompleteImageRegex, '');
          }
          
          const hasJsonCodeBlock = processedRaw.includes('```json') || processedRaw.includes('```') || 
                                  (processedRaw.trim().startsWith('{') && processedRaw.trim().endsWith('}')) ||
                                  (processedRaw.trim().startsWith('[') && processedRaw.trim().endsWith(']'));
          
          if (isStreaming && hasJsonCodeBlock) {
            let escaped = this._escapeHtml(processedRaw);
          
            return `<pre><code>${escaped}</code></pre>`;
          }
          
          function escapeAttr(s) {
            if (s == null) return "";
            return String(s)
              .replace(/&/g, "&amp;")
              .replace(/"/g, "&quot;")
              .replace(/'/g, "&#39;")
              .replace(/</g, "&lt;")
              .replace(/>/g, "&gt;");
          }

          processedRaw = processedRaw.replace(
            /\[\[QSCACTION\s+([^\]]+)\]\]/g,
            function (_, attributes) {
              
              const paramMap = {};
              
              const attrRegex = /(\w+)=("([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)')/g;
              let match;
              
              while ((match = attrRegex.exec(attributes)) !== null) {
                const key = match[1].trim();
                let value = '';
                
                if (match[2].startsWith('"')) {
                  value = match[3] || ''; 
                } else {
                  value = match[5] || ''; 
                }
                
                value = value.replace(/\\"/g, '"').replace(/\\'/g, "'");
                paramMap[key] = value.trim();
              }
              
              const type = escapeAttr(paramMap.type || '');
              const label = escapeAttr(paramMap.label || '');
              const prompt = escapeAttr(paramMap.prompt || '');
              const uiPrompt = escapeAttr(paramMap.uiPrompt || '');
              const url = escapeAttr(paramMap.url || '');
              const style = escapeAttr(paramMap.style || '');
                              
              let buttonHtml = `<button class="action-button" data-action-type="${type}"`;
              
              // Add data attributes based on type
              if (type === 'submit' || type === 'submit-silent') {
                buttonHtml += ` data-action="${prompt}"`;
                if (uiPrompt) {
                  buttonHtml += ` data-ui-prompt="${uiPrompt}"`;
                }
              } else if (type === 'url-open') {
                buttonHtml += ` data-url="${url}"`;
              }
              
              // Add style if provided
              if (style) {
                buttonHtml += ` style="${style}"`;
              }
              
              buttonHtml += `>${label}</button>`;
              
              return buttonHtml;
            }
          );

          // Parse with marked
          const rawHtml = d.parse(processedRaw);
          const safeHtml = DOMPurify.sanitize(rawHtml);
          
          let finalHtml = `<div class="markdown">${safeHtml}</div>`;
            
          return finalHtml;

        } catch (error) {
          console.warn("Markdown parse error:", error);
          // fallback for unexpected parse errors
          return `<pre class="markdown">${this._escapeHtml(raw)}</pre>`;
        }
      }

      scrollToBottom() { 
        setTimeout(() => {
          const messagesDiv = this.shadowRoot.querySelector('.messages');
          if (messagesDiv) messagesDiv.scrollTop = messagesDiv.scrollHeight;
        }, 100);
      }

      showBroadcastPopup(message) {
        const oldPopup = this.shadowRoot.querySelector('.broadcast-popup');
        if (oldPopup) oldPopup.remove();
        
        const popup = document.createElement('div');
        popup.className = 'broadcast-popup';
        popup.innerHTML = `
      <div class="broadcast-icon">📢</div>
      <span>${message}</span>
    `;
        
        this.shadowRoot.appendChild(popup);
        setTimeout(() => {
          popup.classList.add('fade-out');
          setTimeout(() => popup.remove(), 500);
        }, 4000);
      }
      
      formatTime(date) {
        return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
      }

      _stripHtml(html) {
        const tmp = document.createElement('div');
        tmp.innerHTML = html;
        return tmp.textContent || tmp.innerText || '';
      }

      _getSessionId() {
        try {
          const key = 'qsc_session_id';
          let id = localStorage.getItem(key);
          if (!id) {
            localStorage.removeItem(key);
          }
          return id;
        } catch (e) {
          return null;
        }
      }

      _splitIntoJsonAndTextSegments(raw) {
        const n = raw.length;
        let i = 0;
        let buf = '';
        const segments = [];

        const pushTextBuf = () => {
          if (buf.length) {
            segments.push({ type: 'text', content: buf });
            buf = '';
          }
        };

        while (i < n) {
          const ch = raw[i];

          if ((ch === '{' || ch === '[')) {
            pushTextBuf();

            const start = i;
            const stack = [ch];
            i++; 
            let inString = false;
            let escaped = false;

            for (; i < n; i++) {
              const c = raw[i];

              if (inString) {
                if (escaped) {
                  escaped = false;
                  continue;
                }
                if (c === '\\') {
                  escaped = true;
                  continue;
                }
                if (c === '"') {
                  inString = false;
                  continue;
                }
                continue;
              } else {
                if (c === '"') {
                  inString = true;
                  continue;
                }
                if (c === '{' || c === '[') {
                  stack.push(c);
                  continue;
                }
                if (c === '}' || c === ']') {
                  const last = stack[stack.length - 1];
                  if ((c === '}' && last === '{') || (c === ']' && last === '[')) {
                    stack.pop();
                  } else {
                    stack.pop();
                  }
                  if (stack.length === 0) {
                    i++;
                    break;
                  }
                  continue;
                }
              }
            }
            const jsonSub = raw.slice(start, i);
            segments.push({ type: 'json', content: jsonSub });
            continue;
          }

          buf += ch;
          i++;
        }

        pushTextBuf();

        return segments
          .map(s => ({ type: s.type, content: (s.content || '') }))
          .filter(s => s.content.trim().length > 0);
      }

      _splitAndRenderMultipleJsons() {
        const container = this.shadowRoot.querySelector('.messages');
        if (!container) return;
         const streamingMessages = container.querySelectorAll('.message-streaming');
          if (streamingMessages.length > 0) {
            return; // Don't process JSON during streaming
          }
        const codeNodes = Array.from(container.querySelectorAll('pre code'))
          .filter(code => {
            const cls = String(code.className || '').toLowerCase();
            const txt = (code.textContent || '').trim();
            return cls.includes('language-json') || txt.startsWith('{') || txt.startsWith('[');
          });

        const esc = s => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');

        codeNodes.forEach(codeEl => {
          const preEl = codeEl.closest('pre');
          if (!preEl) return;

          const raw = codeEl.textContent || '';
          const segments = this._splitIntoJsonAndTextSegments(raw);

          if (segments.length <= 1) return;

          let html = '';
          segments.forEach(seg => {
            if (seg.type === 'text') {
              const text = seg.content.split(/\n{2,}/).map(p => p.trim()).filter(Boolean);
              if (text.length === 0) {
                html += `<p>${esc(seg.content)}</p>`;
              } else {
                text.forEach(p => { html += `<p>${esc(p)}</p>`; });
              }
            } else if (seg.type === 'json') {
              let pretty = seg.content;
              try {
                const parsed = JSON.parse(seg.content);
                pretty = JSON.stringify(parsed, null, 2);
              } catch (e) {
                pretty = seg.content.trim();
              }
              html += `<pre style="position: relative;"><code class="language-json">${esc(pretty)}</code></pre>\n`;
            }
          });

          const wrapper = document.createElement('div');
          wrapper.innerHTML = html;
          Array.from(wrapper.childNodes).forEach(node => {
            preEl.parentNode.insertBefore(node, preEl);
          });
          // remove the original pre (and its code child)
          preEl.remove();
        });
      }

      _formatAndHighlightJson() {
        const container = this.shadowRoot.querySelector('.messages');
        if (!container) return;
         const streamingMessages = container.querySelectorAll('.message-streaming');
      if (streamingMessages.length > 0) {
        return; // Don't format JSON during streaming
      }
        const codeEls = Array.from(container.querySelectorAll('pre code'))
          .filter(code => {
            const cls = String(code.className || '').toLowerCase();
            const txt = (code.textContent || '').trim();
            return cls.includes('language-json') || txt.startsWith('{') || txt.startsWith('[');
          });

        const esc = s => String(s)
          .replace(/&/g, '&amp;')
          .replace(/</g, '&lt;')
          .replace(/>/g, '&gt;');

        codeEls.forEach(codeEl => {
          if (codeEl._qsc_json_highlighted) return;

          let raw = codeEl.textContent || '';
          let pretty = raw;

          try {
            const parsed = JSON.parse(raw);
            pretty = JSON.stringify(parsed, null, 2);
          } catch {
            pretty = raw.trim();
          }

          const keyStore = [];
          const strStore = [];

          pretty = pretty.replace(/"((?:\\.|[^"\\])*)"\s*(?=:)/g, (m) => {
            const idx = keyStore.length;
            keyStore.push(m); 
            return `@@KEY${idx}@@`;
          });

          pretty = pretty.replace(/"((?:\\.|[^"\\])*)"/g, (m) => {
            const idx = strStore.length;
            strStore.push(m); 
            return `@@STR${idx}@@`;
          });

          let html = esc(pretty);

          html = html.replace(/\b-?\d+(\.\d+)?([eE][+\-]?\d+)?\b/g, `<span class="json-number">$&</span>`);

          html = html.replace(/\b(true|false|null)\b/g, `<span class="json-boolean">$1</span>`);

          html = html.replace(/([{}\[\],:])/g, `<span class="json-punctuation">$1</span>`);

          keyStore.forEach((orig, i) => {
            const replacement = `<span class="json-key">${esc(orig)}</span>`;
            html = html.replace(new RegExp(esc(`@@KEY${i}@@`), 'g'), replacement);
          });

          strStore.forEach((orig, i) => {
            const replacement = `<span class="json-string">${esc(orig)}</span>`;
            html = html.replace(new RegExp(esc(`@@STR${i}@@`), 'g'), replacement);
          });

          codeEl.innerHTML = html;
          codeEl._qsc_json_highlighted = true;
        });
      }

      _attachCodeCopyButtons() {
        const container = this.shadowRoot.querySelector('.messages');
        if (!container) return;

        const pres = Array.from(container.querySelectorAll('.message-text .markdown pre'));

        pres.forEach(pre => {
          if (pre._qsc_copy_attached) return;

          const btn = document.createElement('button');
          btn.type = 'button';
          btn.className = 'copy-code-btn';
          btn.title = 'Copy code';

          btn.innerHTML = `
          ${this._copySVG}
      `;

          btn.addEventListener('click', async (evt) => {
            evt.stopPropagation();
            try {
              await this._copyCodeFromElement(pre);
              btn.classList.add('copied');
              const iconContainer = btn.querySelector('svg');
              if (iconContainer) iconContainer.outerHTML = this._checkSVG;

              setTimeout(() => {
                btn.classList.remove('copied');
                if (btn.querySelector('svg')) btn.querySelector('svg').outerHTML = this._copySVG;
              }, 1200);
            } catch (err) {
                btn.innerHTML = `
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
                  stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
              <line x1="18" y1="6" x2="6" y2="18"></line>
              <line x1="6" y1="6" x2="18" y2="18"></line>
            </svg>
          `;
              btn.classList.add('copy-failed');
              setTimeout(() => {
                btn.classList.remove('copy-failed');
                btn.innerHTML = this._copySVG;
              }, 1200);
            }
          });

          pre.style.position = pre.style.position || 'relative';
          pre.appendChild(btn);
          pre._qsc_copy_attached = true;
        });
      }

      async _copyCodeFromElement(preEl) {
        const codeEl = preEl.querySelector('code') || preEl;
        const text = codeEl.textContent || codeEl.innerText || '';
        if (!text) throw new Error('No code to copy');

        if (navigator.clipboard && navigator.clipboard.writeText) {
          await navigator.clipboard.writeText(text);
        } else {
          return new Promise((resolve, reject) => {
            try {
              const ta = document.createElement('textarea');
              ta.value = text;
              ta.style.position = 'fixed';
              ta.style.left = '-9999px';
              document.body.appendChild(ta);
              ta.select();
              const ok = document.execCommand('copy');
              ta.remove();
              ok ? resolve() : reject(new Error('execCommand failed'));
            } catch (e) {
              reject(e);
            }
          });
        }
      }

      _findIndexById(id) {
        return this.messages.findIndex(m => String(m.id) === String(id));
      }

      _removeMessagesAfterIndex(idx) {
        if (idx < 0) return;
        this.messages = this.messages.slice(0, idx + 1);
      }
      
      _createXSVG() {
        return `
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
          stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
        <line x1="18" y1="6" x2="6" y2="18"></line>
        <line x1="6" y1="6" x2="18" y2="18"></line>
      </svg>
    `;
      }

      _escapeHtml(str = '') {
        return String(str)
          .replace(/&/g, '&amp;')
          .replace(/</g, '&lt;')
          .replace(/>/g, '&gt;')
          .replace(/"/g, '&quot;')
          .replace(/'/g, '&#39;');
      }
      
      async _handleSave(id) {
        const idx = this._findIndexById(id);
        if (idx === -1) return;
        const ta = this.shadowRoot.querySelector(`.inline-edit-textarea[data-msg-id="${id}"]`);
        if (!ta) return;

        const newVal = ta.value.trim();
        if (!newVal) {
          return;
        }

        this.messages[idx].text = newVal;
        this.messages[idx].timestamp = new Date();

        this._removeMessagesAfterIndex(idx);

         const botMsgId = 'bot-save-' + Date.now();
        this.messages.push({
          id: botMsgId,
          text: `
        <div class="typing-indicator">
          <span></span><span></span><span></span>
        </div>
      `,
          sender: 'bot',
          timestamp: new Date(),
          isLoading: true,
          isStreaming: false
        });

        const originalEditId = id;
        this.editingId = null;
        this.renderMessages({ autoScroll: 'bottom' });
        const modelToSend = this.selectedModel ? (this.selectedModel.model || this.selectedModel.name) : undefined;

        try {
          const res = await fetch(this.restUrl, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ type: 'message', text: newVal, id: `rest-${Date.now()}`, editedFrom: originalEditId, model: modelToSend, sessionId: this.sessionId || localStorage.getItem('qsc_session_id') || this._getSessionId() })
          });
           if (!res.ok) {
              throw new Error(`HTTP error! Status: ${res.status}`);
            }
          if (this.serverStreamingEnabled && res.body) {
            
            // Remove loading message and create streaming message
            this.messages = this.messages.filter(m => m.id !== botMsgId);
            const streamingBotMsgId = 'bot-stream-' + Date.now();
            this.messages.push({
              id: streamingBotMsgId,
              text: '',
              sender: 'bot',
              timestamp: new Date(),
              isLoading: false,
              isStreaming: true
            });
            this.renderMessages({ autoScroll: 'bottom' });

            // Get the reader
            const reader = res.body.getReader();
            const decoder = new TextDecoder();
            let fullText = '';

            try {
              while (true) {
                const { done, value } = await reader.read();
                if (done) {
                  break;
                }

                const chunk = decoder.decode(value, { stream: true });
                
                // Split by newlines and process each line
                const lines = chunk.split('\n');
                
                for (const line of lines) {
                  if (!line.trim()) continue;
                                
                  try {
                    const json = JSON.parse(line);
                    
                    // Handle different message types
                    if (json.type === 'broadcast') {
                      this._pushSystem(json.data);
                    } 
                    if (json.session_id) {
                      this.sessionId = json.session_id;
                      localStorage.setItem('qsc_session_id', this.sessionId);
                    } 
                    if (json.data !== undefined) {
                      fullText += json.data;
                      
                      const index = this.messages.findIndex(m => m.id === streamingBotMsgId);
                      if (index !== -1) {
                        this.messages[index].text = fullText;
                        this.messages[index].timestamp = new Date();
                        
                       
                        this._debouncedStreamingUpdate(streamingBotMsgId, fullText, true, 16);
                        
                        if (this._isUserAtBottom()) {
                          this._scrollToBottom();
                        }
                      }
                    }
                  } catch (e) {
                    console.warn('JSON parse error:', e);
                  }
                }
              }
            } finally {
                const index = this.messages.findIndex(m => m.id === streamingBotMsgId);
                if (index !== -1) {
                  this.messages[index].isStreaming = false;
                  
                  if (fullText.trim() !== '') {
                    this._updateStreamingMessage(streamingBotMsgId, fullText, false);
                  } else {
                    // If still empty after stream ends, show a fallback message
                    const messageText = this.shadowRoot.querySelector(`.message-text[data-msg-id="${streamingBotMsgId}"]`);
                    if (messageText) {
                      messageText.innerHTML = `<em>No response received</em>`;
                    }
                  }
                  
                  // Remove streaming styling
                  const bubble = this.shadowRoot.querySelector(`[data-msg-id="${streamingBotMsgId}"] .bubble.bot`);
                  if (bubble) {
                    bubble.classList.remove('message-streaming');
                  }
                }
              }
          } else {
           
            const data = await res.json();
            
            // Remove loading message
            this.messages = this.messages.filter(m => m.id !== botMsgId);
            this.handleRestBotResponse(data);
          }
        } catch (err) {
          this.messages = this.messages.filter(m => !m.isLoading);
          this._pushSystem(this.errMsg);
          console.error('Send fetch failed', err);
        }
        
      }

      _handleCancel(id) {
        if (String(this.editingId) !== String(id)) {
          this.editingId = null;
        } else {
          this.editingId = null;
        }
        this.renderMessages({ autoScroll: 'bottom' });
      }

      _handleEdit(id) {
        const idx = this._findIndexById(id);
        if (idx === -1) return;
        const msg = this.messages[idx];
        if (msg.sender !== 'user') return;

        if (this.editingId && this.editingId !== id) {
          this.editingId = null;
        }

        this.editingId = (this.editingId === id) ? null : id;
        this.renderMessages({ autoScroll: 'intoView', targetId: id });

        const msgEl = this.shadowRoot.querySelector(`.message-text[data-msg-id="${id}"]`);
        if (!msgEl) {
          
          return;
        }
        msgEl.dataset._originalHtml = msgEl.innerHTML;

        const plain = this._stripHtml(msg.text);

        msgEl.innerHTML = '';

        const ta = document.createElement('textarea');
        ta.className = 'inline-edit';
        ta.value = plain;
        ta.rows = 4;
        ta.setAttribute('aria-label', 'Edit message');
        ta.addEventListener('keydown', (e) => {
          if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
            e.preventDefault();
            saveBtn.click();
          } else if (e.key === 'Escape') {
            e.preventDefault();
            cancelBtn.click();
          }
        });

        const actionsWrap = document.createElement('div');
        actionsWrap.className = 'inline-edit-actions';

        const saveBtn = document.createElement('button');
        saveBtn.type = 'button';
        saveBtn.className = 'message-action-btn save-btn copied'; 
        saveBtn.title = 'Save';
        saveBtn.setAttribute('aria-label', 'Save message');
        saveBtn.innerHTML = this._checkSVG;

        const cancelBtn = document.createElement('button');
        cancelBtn.type = 'button';
        cancelBtn.className = 'message-action-btn cancel-btn';
        cancelBtn.title = 'Cancel';
        cancelBtn.setAttribute('aria-label', 'Cancel edit');
        cancelBtn.innerHTML = this._createXSVG();

        actionsWrap.appendChild(saveBtn);
        actionsWrap.appendChild(cancelBtn);

        msgEl.appendChild(ta);
        msgEl.appendChild(actionsWrap);

        ta.focus();
        ta.selectionStart = ta.selectionEnd = ta.value.length;

        cancelBtn.addEventListener('click', (ev) => {
          ev.stopPropagation();
          this.editingId = null;
          // restore original content
          if (msgEl && msgEl.dataset && msgEl.dataset._originalHtml) {
            msgEl.innerHTML = msgEl.dataset._originalHtml;
            delete msgEl.dataset._originalHtml;
          }
          this.renderMessages({ autoScroll: 'intoView', targetId: id });
        });

        saveBtn.addEventListener('click', async (ev) => {
          ev.stopPropagation();
          const newVal = ta.value.trim();
          if (!newVal) {
            return;
          }

          this.messages[idx].text = newVal;
          this.messages[idx].timestamp = new Date();

          this._removeMessagesAfterIndex(idx);

           const botMsgId = 'bot-save-' + Date.now();
          this.messages.push({
            id: botMsgId,
            text: `
          <div class="typing-indicator">
            <span></span><span></span><span></span>
          </div>
        `,
            sender: 'bot',
            timestamp: new Date(),
            isLoading: true
          });

          this.renderMessages({ autoScroll: 'bottom'});

          const originalEditId = id;
          this.editingId = null;
          const modelToSend = this.selectedModel ? (this.selectedModel.model || this.selectedModel.name) : undefined;

          try {
            const res = await fetch(this.restUrl, {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({ type: 'message', text: newVal, id: `rest-${Date.now()}`, editedFrom: originalEditId,model:modelToSend, sessionId: this.sessionId || localStorage.getItem('qsc_session_id') || this._getSessionId() })
            });
             if (!res.ok) {
                throw new Error(`HTTP error! Status: ${res.status}`);
              }
             if (this.serverStreamingEnabled && res.body) {
            
            // Remove loading message and create streaming message
            this.messages = this.messages.filter(m => m.id !== botMsgId);
            const streamingBotMsgId = 'bot-stream-' + Date.now();
            this.messages.push({
              id: streamingBotMsgId,
              text: '',
              sender: 'bot',
              timestamp: new Date(),
              isLoading: false,
              isStreaming: true
            });
            this.renderMessages({ autoScroll: 'bottom' });

            // Get the reader
            const reader = res.body.getReader();
            const decoder = new TextDecoder();
            let fullText = '';

            try {
              while (true) {
                const { done, value } = await reader.read();
                if (done) {
                  break;
                }

                const chunk = decoder.decode(value, { stream: true });
                
                // Split by newlines and process each line
                const lines = chunk.split('\n');
                
                for (const line of lines) {
                  if (!line.trim()) continue;
                                
                  try {
                    const json = JSON.parse(line);
                    
                    // Handle different message types
                    if (json.type === 'broadcast') {
                      this._pushSystem(json.data);
                    } 
                    if (json.session_id) {
                      this.sessionId = json.session_id;
                      localStorage.setItem('qsc_session_id', this.sessionId);
                    } 
                    if (json.data !== undefined) {
                      fullText += json.data;
                      
                      const index = this.messages.findIndex(m => m.id === streamingBotMsgId);
                      if (index !== -1) {
                        this.messages[index].text = fullText;
                        this.messages[index].timestamp = new Date();
                        
                       
                        this._debouncedStreamingUpdate(streamingBotMsgId, fullText, true, 16);
                        
                        if (this._isUserAtBottom()) {
                          this._scrollToBottom();
                        }
                      }
                    }
                  } catch (e) {
                    console.warn('JSON parse error:', e);
                  }
                }
              }
            } finally {
                const index = this.messages.findIndex(m => m.id === streamingBotMsgId);
                if (index !== -1) {
                  this.messages[index].isStreaming = false;
                  
                  if (fullText.trim() !== '') {
                    this._updateStreamingMessage(streamingBotMsgId, fullText, false);
                  } else {
                    // If still empty after stream ends, show a fallback message
                    const messageText = this.shadowRoot.querySelector(`.message-text[data-msg-id="${streamingBotMsgId}"]`);
                    if (messageText) {
                      messageText.innerHTML = `<em>No response received</em>`;
                    }
                  }
                  
                  // Remove streaming styling
                  const bubble = this.shadowRoot.querySelector(`[data-msg-id="${streamingBotMsgId}"] .bubble.bot`);
                  if (bubble) {
                    bubble.classList.remove('message-streaming');
                  }
                }
              }
          } else {
           
            const data = await res.json();
            
            // Remove loading message
            this.messages = this.messages.filter(m => m.id !== botMsgId);
            this.handleRestBotResponse(data);
          }
          } catch (err) {
            this.messages = this.messages.filter(m => !m.isLoading);
            this._pushSystem(this.errMsg);
            console.error('Send fetch failed', err);
          }
          
        });
        if (this.editingId) {
          const bubble = this.shadowRoot.querySelector(`[data-msg-id="${id}"]`);
          if (bubble) {
            bubble.scrollIntoView({ behavior: 'smooth', block: 'center' });
          }
        }
      }

      async _handleCopy(id) {
        const idx = this._findIndexById(id);
        if (idx === -1) return;
        const msg = this.messages[idx];
        const textToCopy = this._stripHtml(msg.text);

        let success = false;
        try {
          if (navigator.clipboard && navigator.clipboard.writeText) {
            await navigator.clipboard.writeText(textToCopy);
            success = true;
          } else {
            const ta = document.createElement('textarea');
            ta.value = textToCopy;
            ta.style.position = 'fixed';
            ta.style.left = '-9999px';
            document.body.appendChild(ta);
            ta.select();
            try {
              success = document.execCommand('copy');
            } catch (e) {
              success = false;
            }
            ta.remove();
          }
        } catch (err) {
          try {
            const ta = document.createElement('textarea');
            ta.value = textToCopy;
            ta.style.position = 'fixed';
            ta.style.left = '-9999px';
            document.body.appendChild(ta);
            ta.select();
            success = document.execCommand('copy');
            ta.remove();
          } catch (e) {
            success = false;
          }
        }

        const btn = this.shadowRoot && this.shadowRoot.querySelector(`.copy-btn[data-msg-id="${id}"]`);
        if (!btn) return;

        if (this._copyTimers[id]) {
          clearTimeout(this._copyTimers[id]);
          delete this._copyTimers[id];
        }

        const originalHtml = btn.innerHTML;
        const originalClass = btn.className;

        if (success) {
          btn.innerHTML = this._checkSVG;
          btn.className = originalClass + ' copied';
        } else {
          btn.innerHTML = `
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
            stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
          <line x1="18" y1="6" x2="6" y2="18"></line>
          <line x1="6" y1="6" x2="18" y2="18"></line>
        </svg>
      `;
          btn.className = originalClass + ' copy-failed';
        }
        this._copyTimers[id] = setTimeout(() => {
          try {
            btn.innerHTML = originalHtml;
            btn.className = originalClass;
          } catch (e) {
          }
          delete this._copyTimers[id];
        }, 1000);
      }

      renderMessages(opts = {}) {
        const { autoScroll = 'bottom', targetId = null } = opts;
        const messagesDiv = this.shadowRoot.querySelector('.messages');
        if (!messagesDiv) return;
        
        // Always re-render all messages
        messagesDiv.innerHTML = this.messages.map(m => {
          const isEditing = String(m.id) === String(this.editingId);
          const isStreaming = m.isStreaming;
          
          let messageContent = '';
          
          if (m.sender === 'user' && isEditing) {
            messageContent = `<textarea class="inline-edit inline-edit-textarea" data-msg-id="${m.id}" rows="3">${this._stripHtml(m.text)}</textarea>`;
          } else if (m.sender === 'user') {
            messageContent = `<div class="message-text" data-msg-id="${m.id}">${
          m.isHtml ? m.text : `<p>${this._escapeHtml(m.text)}</p>`
        }</div>`;
          } else if (m.sender === 'bot') {
            let displayText = '';
            
            if (m.isLoading) {
              displayText = `
            <div class="typing-indicator">
              <span></span><span></span><span></span>
            </div>
          `;
            } else if (m.isStreaming && (!m.text || m.text.trim() === '')) {
              // Show typing indicator for empty streaming messages
              displayText = `
            <div class="typing-indicator">
              <span></span><span></span><span></span>
            </div>
          `;
            } else if (m.isStreaming && m.text) {
              const html = this._processMarkdown(m.text,true);
              displayText = html + `
            <div class="streaming-indicator">
              <span></span><span></span><span></span>
            </div>
          `;
            } else if (m.text) {
              displayText = this._processMarkdown(m.text,false);
            }
            
            messageContent = `<div class="message-text ${isStreaming ? 'streaming-update' : ''}" data-msg-id="${m.id}">${displayText}</div>`;
          } else {
            messageContent = `<div class="message-text" data-msg-id="${m.id}">${m.text}</div>`;
          }

          let actionsHtml = '';
          const showCopy = (!m.isLoading && !m.isStreaming) || Boolean(m.copied);

          if (m.sender === 'user' && isEditing) {
            actionsHtml = `
          <div class="message-actions" data-msg-id="${m.id}">
            <button class="message-action-btn save-btn" data-msg-id="${m.id}" data-action="save" title="Save" aria-label="Save">
              ${this._checkSVG}
            </button>
            <button class="message-action-btn cancel-btn" data-msg-id="${m.id}" data-action="cancel" title="Cancel" aria-label="Cancel">
              ${this._createXSVG()}
            </button>
          </div>
        `;
          } else {
            actionsHtml = `
          <div class="message-actions" data-msg-id="${m.id}">
            ${showCopy ? `<button class="message-action-btn copy-btn" data-msg-id="${m.id}" title="Copy" aria-label="Copy message">${this._copySVG}</button>` : ''}
            ${m.sender === 'user' && !m.isLoading && !m.isStreaming ? `<button class="message-action-btn edit-btn" data-msg-id="${m.id}" title="Edit" aria-label="Edit">
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
                  <path d="M12 20h9"></path>
                  <path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z"></path>
                </svg>
              </button>` : ''}
          </div>
        `;
          }

          if (m.sender === 'system') {
            return `
          <div class="message-row system" data-msg-id="${m.id}">
            <div class="bubble system">
              <div class="message-text" data-msg-id="${m.id}">${m.text}</div>
              <div class="timestamp">${this.formatTime(m.timestamp)}</div>
            </div>
          </div>
        `;
          }

          if (m.sender === 'bot') {
            const streamingClass = m.isStreaming ? 'message-streaming' : '';
            return `
          <div class="message-row bot" data-msg-id="${m.id}">
            <div class="bubble bot ${streamingClass}">
              ${messageContent}
              <div class="timestamp-container">
                ${m.isStreaming ? 
                  '<div class="streaming-indicator"><span></span><span></span><span></span></div>' : 
                  `<div class="timestamp">${this.formatTime(m.timestamp)}</div>`
                }
              </div>
            </div>
            ${actionsHtml}
          </div>
        `;
          }

          return `
        <div class="message-row user" data-msg-id="${m.id}">
          <div class="bubble user">
            ${messageContent}
            <div class="timestamp">${this.formatTime(m.timestamp)}</div>
          </div>
          ${actionsHtml}
        </div>
      `;
        }).join('');
        
        const container = this.shadowRoot.querySelector('.messages');
        if (container && !container._qsc_actions_bound) {
          container.addEventListener('click', (e) => {
            const saveBtn = e.target.closest('.save-btn') || e.target.closest('[data-action="save"]');
            if (saveBtn) { this._handleSave(saveBtn.dataset.msgId); return; }

            const cancelBtn = e.target.closest('.cancel-btn') || e.target.closest('[data-action="cancel"]');
            if (cancelBtn) { this._handleCancel(cancelBtn.dataset.msgId); return; }

            const copyBtn = e.target.closest('.copy-btn');
            if (copyBtn) { this._handleCopy(copyBtn.dataset.msgId); return; }
            
            const actionBtn = e.target.closest('.action-button');
            if (actionBtn) {
              const type = actionBtn.getAttribute('data-action-type');
              switch (type) {
                case 'submit':
                  const action = actionBtn.getAttribute('data-action');
                  const uiPrompt = actionBtn.getAttribute('data-ui-prompt');
                  if (action && uiPrompt) {
                    this._sendActionPrompt(action, uiPrompt, false);
                  } else if (action) {
                    this._sendActionPrompt(action, null, false);
                  }
                  break;
                  
                case 'submit-silent':
                  const silentAction = actionBtn.getAttribute('data-action');
                  if (silentAction) {
                    this._sendActionPrompt(silentAction, null, true);
                  }
                  break;
                  
                case 'url-open':
                  const url = actionBtn.getAttribute('data-url');
                  if (url) {
                    window.open(url, '_blank');
                  }
                  break;
                  
                default:
                  console.warn('Unknown action type:', type);
              }
              return;
            }

            const editBtn = e.target.closest('.edit-btn');
            if (editBtn) { this._handleEdit(editBtn.dataset.msgId); return; }
          });
          container._qsc_actions_bound = true;
        }

        if (this.editingId) {
          const ta = this.shadowRoot.querySelector(`.inline-edit-textarea[data-msg-id="${this.editingId}"]`);
          if (ta) {
            ta.focus();
            ta.selectionStart = ta.selectionEnd = ta.value.length;
            const keyHandler = (e) => {
              if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
                e.preventDefault();
                this._handleSave(this.editingId);
              } else if (e.key === 'Escape') {
                e.preventDefault();
                this._handleCancel(this.editingId);
              }
            };
            ta.removeEventListener('keydown', ta._qsc_keyHandler);
            ta.addEventListener('keydown', keyHandler);
            ta._qsc_keyHandler = keyHandler;
          }
        }
        
        this._splitAndRenderMultipleJsons(); 
        this._formatAndHighlightJson();   
        this._attachCodeCopyButtons();

        if (autoScroll === 'bottom') {
          this._scrollToBottom();
        } else if (autoScroll === 'intoView' && targetId) {
          const el = this.shadowRoot.querySelector(`[data-msg-id="${targetId}"]`);
          if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
        }
      }

      render() {
        this.shadowRoot.innerHTML = `
      <style>
      :host {
        --primary: #0078d4;
        --primary-light: #e1f0fa;
        --secondary: #605e5c;
        --background: #ffffff;
        --background-alt: #f5f5f5;
        --text-primary: #323130;
        --text-secondary: #605e5c;
        --success: #107c10;
        --warning: #d83b01;
        --error: #a80000;
        --border-radius: 12px;
        --shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
        --transition: all 0.3s ease;
        
        font-family: 'Segoe UI', system-ui, sans-serif;
      }
     .message-streaming {
        position: relative;
        animation: subtle-glow 2s ease-in-out infinite;
      }

      @keyframes subtle-glow {
        0%, 100% { box-shadow: 0 0 10px rgba(0, 120, 212, 0.1); }
        50% { box-shadow: 0 0 20px rgba(0, 120, 212, 0.2); }
      }
        
      @keyframes blink {
        0%, 100% { opacity: 1; }
        50% { opacity: 0; }
      }
      .streaming-indicator {
        display: inline-flex !important;
        margin-left: 8px !important;
        vertical-align: middle !important;
        background: rgba(0, 120, 212, 0.1);
        border-radius: 12px;
        padding: 4px 8px;
        height: 20px;
        align-items: center;
      }

      .streaming-indicator span {
        width: 4px;
        height: 4px;
        margin: 0 1px;
        background-color: var(--primary);
        border-radius: 50%;
        animation: bounce 1.4s infinite ease-in-out both;
        opacity: 0.7;
      }

      .streaming-indicator span:nth-child(1) { animation-delay: -0.32s; }
      .streaming-indicator span:nth-child(2) { animation-delay: -0.16s; }
      .streaming-indicator span:nth-child(3) { animation-delay: 0s; }

      .typing-indicator.streaming {
        display: flex;
        align-items: center;
        gap: 4px;
        color: var(--primary);
        font-size: 12px;
      }
        
        .streaming-dots {
          display: flex;
          gap: 2px;
        }
        
        .streaming-dots span {
          width: 4px;
          height: 4px;
          background-color: var(--primary);
          border-radius: 50%;
          animation: streaming-bounce 1.4s infinite ease-in-out both;
        }
        
        .streaming-dots span:nth-child(1) { animation-delay: -0.32s; }
        .streaming-dots span:nth-child(2) { animation-delay: -0.16s; }
        
        @keyframes streaming-bounce {
          0%, 80%, 100% { 
            transform: scale(0);
            opacity: 0.5;
          }
          40% { 
            transform: scale(1);
            opacity: 1;
          }
        }
           .messages::-webkit-scrollbar {
          width: 8px;
        }
        
        .messages::-webkit-scrollbar-track {
          background: var(--background-alt);
        }
        
        .messages::-webkit-scrollbar-thumb {
          background: rgba(0, 0, 0, 0.1);
          border-radius: 4px;
        }
        
        .messages::-webkit-scrollbar-thumb:hover {
          background: rgba(0, 0, 0, 0.2);
        }
           .message-text {
          transition: all 0.2s ease;
        }
        
        .message-text.streaming-update {
          animation: subtle-pulse 0.3s ease;
        }
        
        @keyframes subtle-pulse {
          0% { opacity: 0.9; }
          50% { opacity: 1; }
          100% { opacity: 0.9; }
        }
      @keyframes smoothJump {
        0%, 100% {
          transform: translateY(0);
        }
        50% {
          transform: translateY(-5px);
        }
      }

      .jumpLoop {
        animation: smoothJump 1s ease-in-out infinite;
      }
      
      .chatbot-container {
        position: fixed;
        bottom: 30px;
        right: 24px;
        z-index: 9999;
        transition: transform 0.3s ease;
      }
      .typing-indicator {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        height: 20px;
        padding: 0 10px;
        background: #f1f1f1;
        border-radius: 20px;
      }
     .action-button {
        padding: 4px 8px;
        border: none;
        border-radius: 10px;
        cursor: pointer;
        font-size: 12px;
        transition: background 0.2s ease;
        margin: 4px; 
        white-space: nowrap;
      }
      .markdown img {
        width: 220px !important;
        height: auto;
        object-fit: cover;
        border: 1px solid #e6eef9;
        border-radius: 8px;
        padding: 4px;
        display: block;
        margin: 0 auto 8px auto;
      }
      .markdown table td{
        border-radius: 14px;
        padding: 12px;
        border: none;
      }
      .markdown ul li::marker {
          content: none;
        }
      .markdown table td:has(strong) {
        border: 1px solid rgba(0, 0, 0, 0.04);
      }
      .model-selection-container {
        display: flex;
        align-items: center;
      }
      .new-chat-btn {
        background: transparent;
        border: 1.5px solid rgba(255,255,255,0.3);
        color: white;
        padding: 6px 8px;
        border-radius: 20px;
        cursor: pointer;
        font-size: 13px;
        font-weight: 500;
        display: flex;
        align-items: center;
        gap: 5px;
        transition: var(--transition);
        backdrop-filter: blur(10px);
        -webkit-backdrop-filter: blur(10px);
      }

      .new-chat-btn:hover {
        background: rgba(255,255,255,0.15);
        border-color: rgba(255,255,255,0.5);
        transform: translateY(-1px);
        box-shadow: 0 4px 12px rgba(0,0,0,0.15);
      }

      .new-chat-btn:active {
        transform: translateY(0);
      }

      .new-chat-icon {
        width: 16px;
        height: 16px;
        position: relative;
      }

      .new-chat-icon::before,
      .new-chat-icon::after {
        content: '';
        position: absolute;
        background: currentColor;
        border-radius: 1px;
      }

      .new-chat-icon::before {
        width: 10px;
        height: 2px;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
      }

      .new-chat-icon::after {
        width: 2px;
        height: 10px;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
      }

      .btn-disabled {
        opacity: 0.5;
        cursor: not-allowed;
        pointer-events: none;
      }

      .model-toggle-btn.model-selected {
        background-color: rgb(0, 120, 212) !important;
      }
      
      .model-toggle-btn {
        width: 30px;
        height: 32px;
        border-radius: 8px;
        border: 1px solid rgba(0,0,0,0.1);
        background: var(--background);
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 15px;
        color: var(--text-secondary);
        transition: var(--transition);
        flex-shrink: 0;
      }

      .model-toggle-btn:hover {
        background: var(--background-alt);
        border-color: var(--primary);
        color: var(--primary);
      }

      .model-dropdown {
        position: absolute;
        bottom: 12%;
        left: 0;
        background: var(--background);
        border: 1px solid rgba(0,0,0,0.1);
        border-radius: 8px;
        box-shadow: var(--shadow);
        padding: 6px;
        z-index: 1000;
        min-width: 180px;
        max-height: 200px;
        overflow-y: auto;
      }

      .model-item {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 8px 12px;
        border: none;
        background: transparent;
        width: 100%;
        text-align: left;
        cursor: pointer;
        border-radius: 6px;
        font-size: 13px;
        color: var(--text-primary);
        transition: background 0.2s;
      }

      .model-item:hover {
        background: rgba(0,0,0,0.04);
      }

      .model-item.selected {
        background: var(--primary-light);
        color: var(--primary);
        font-weight: 600;
      }

      .model-chip-container {
        position: relative;
        flex: 1;
      }

      .json-key { color: #a71d5d; }       
      .json-string { color: #183691; }    
      .json-number { color: #0086b3; }    
      .json-boolean { color: #795da3; }   
      .json-punctuation { color: #333; }

      .timestamp-container {
        display: flex;
        align-items: center;
        justify-content: flex-end;
        margin-top: 6px;
      }
        
      .typing-indicator span {
        display: inline-block;
        width: 5px;
        height: 5px;
        margin: 0 2px;
        background-color: var(--primary);
        border-radius: 50%;
        animation: bounce 1.4s infinite ease-in-out both;
      }

      .typing-indicator span:nth-child(1) {
        animation-delay: 0s;
      }
      .typing-indicator span:nth-child(2) {
        animation-delay: 0.2s;
      }
      .typing-indicator span:nth-child(3) {
        animation-delay: 0.4s;
      }

      @keyframes bounce {
        0%, 80%, 100% {
          transform: scale(0);
          opacity: 0.3;
        } 
        40% {
          transform: scale(1);
          opacity: 1;
        }
      }
      .message-actions {
        display: inline-flex;
        gap: 1px;
        pointer-events: none;    
        height: fit-content !important;   
        opacity: 1 !important;
        transform: translateY(4px);
        transition: opacity .14s ease, transform .14s ease;
      }
      .message-action-btn svg {
        width: 14px;
        height: 14px;
        display: block;
      }
      .message-action-btn {
        background: transparent;
        border: none;
        cursor: pointer;
        padding: 4px;
        width: 28px;
        height: 22px;
        border-radius: 6px;
        font-size: 12px;
        line-height: 1;
        display: inline-flex;
        align-items: center;
        justify-content: center;
      }
      .message-action-btn.copied {
        background: rgba(16,124,16,0.08);
        color: #107c10;
      }

      .message-action-btn.copy-failed {
        background: rgba(168,0,0,0.06);
        color: var(--error);
      }

      .message-action-btn:hover { background: rgba(0,0,0,0.04); }
      .message-row:hover .message-actions,
      .message-row:focus-within .message-actions {
        opacity: 1;
        transform: translateY(0);
        pointer-events: auto;
      }
      @media (max-width: 480px) {
        .message-actions {
          opacity: 1;
          transform: translateY(0);
          pointer-events: auto;
        }
      }
      .toggle-btn {
        position: relative;
        background: var(--primary);
        color: white;
        border: none;
        border-radius: 50%;
        width: 64px;
        height: 64px;
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        box-shadow: var(--shadow);
        transition: var(--transition);
        font-weight: 600;
        font-size: 18px;
      }
      
      .toggle-btn:hover {
        transform: scale(1.05);
        box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
      }
      
      .toggle-btn img.jumpLoop {
        width: 50px;
        height: 50px;
        border-radius: 50%;
        object-fit: cover;
        animation: smoothJump 1s ease-in-out infinite;
      }

      .toggle-btn .qsc-span.jumpLoop {
        display: inline-block;
        animation: smoothJump 1s ease-in-out infinite;
        font-size: 24px;
      }
      
      .unread-count {
        position: absolute;
        top: -8px;
        right: -8px;
        background: var(--error);
        color: white;
        border-radius: 50%;
        width: 26px;
        height: 26px;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 13px;
        font-weight: bold;
        box-shadow: 0 2px 8px rgba(0,0,0,0.2);
      }
      .inline-edit {
        width: 100%;
        min-height: 64px;
        max-height: 220px;
        resize: vertical;
        padding: 8px 10px;
        font-size: 14px;
        border-radius: 10px;
        border: 1px solid rgba(0,0,0,0.08);
        box-sizing: border-box;
        background: var(--background-alt);
        color: var(--text-primary);
        font-family: inherit;
      }

      .inline-edit:focus {
        outline: none;
        border-color: var(--primary);
        box-shadow: 0 0 0 3px rgba(0,120,212,0.08);
      }

      .inline-edit-actions {
        display: flex;
        gap: 8px;
        margin-top: 8px;
        align-items: center;
      }

      .cancel-btn {
        background: rgba(0,0,0,0.04);
        border-radius: 6px;
        padding: 6px;
        border: none;
        display: inline-flex;
        align-items: center;
        justify-content: center;
      }

      .inline-edit-actions svg,
      .inline-edit textarea svg {
        width: 14px;
        height: 14px;
        display: block;
      }

      .chat-window {
        background: var(--background);
        border-radius: var(--border-radius);
        box-shadow: var(--shadow);
        width: 380px;
        height: 480px;
        display: flex;
        flex-direction: column;
        overflow: hidden;
        transform: translateY(20px);
        opacity: 0;
        animation: fadeInUp 0.3s ease forwards;
      }
      
      @keyframes fadeInUp {
        to {
          opacity: 1;
          transform: translateY(0);
        }
      }
      
      .header {
        background: var(--primary);
        color: white;
        padding: 14px;
        display: flex;
        align-items: center;
        justify-content: space-between;
        box-shadow: 0 2px 8px rgba(0,0,0,0.1);
        z-index: 10;
      }
      
      .header-title {
        font-weight: 600;
        font-size: 18px;
      }
      
      .header-controls {
        display: flex;
        align-items: center;
        gap: 7px;
      }
      
      .close-btn {
        background: transparent;
        border: none;
        color: white;
        cursor: pointer;
        width: 32px;
        height: 32px;
        border-radius: 50%;
        display: flex;
        align-items: center;
        justify-content: center;
        transition: background 0.2s;
        font-size: 20px;
        font-weight: 300;
      }
      
      .close-btn:hover {
        background: rgba(255, 255, 255, 0.2);
      }
                
      .messages {
        flex: 1;
        overflow-y: auto;
        overflow-x: hidden;
        padding: 15px;
        display: flex;
        flex-direction: column;
        gap: 10px;
        background: var(--background-alt);
      }
      
      .message-row {
        display: flex;
      }
      .message-row.bot {
        justify-content: flex-start;
      }
      
      .message-row.user {
        justify-content: flex-end;
      }
      
      .message-row.system {
        justify-content: center;
      }
      
      .bubble {
        padding: 10px 12px;
        border-radius: 18px;
        max-width: 87%;
        position: relative;
        box-shadow: 0 2px 8px rgba(0,0,0,0.05);
        line-height: 1.5;
        animation: fadeIn 0.3s ease;
      }
      
      @keyframes fadeIn {
        from { opacity: 0; transform: translateY(10px); }
        to { opacity: 1; transform: translateY(0); }
      }
      
      .bubble.user {
        background: var(--primary);
        color: white;
        border-bottom-right-radius: 4px;
      }
      
      .bubble.bot {
        background: var(--background);
        color: var(--text-primary);
        border-bottom-left-radius: 4px;
        border: 1px solid rgba(0,0,0,0.05);
      }
      
      .bubble.system {
        background: #fff8e1;
        color: #856404;
        border-radius: var(--border-radius);
        display: flex;
        align-items: center;
        gap: 10px;
        max-width: 95%;
      }
      .message-text {
        flex: 1;
        font-size: 13px;
        font-family: monospace;
      } 
      .message-text p {
        white-space: pre-wrap;
        margin: 0 !important;
      }
      .timestamp {
        font-size: 11px;
        color: var(--text-secondary);
        margin-top: 6px;
        text-align: right;
      }
      .user .timestamp {
        color: rgba(255, 255, 255, 0.7);
      }
      .system .timestamp {
        text-align: right;
      }
      .input-area {
        display: flex;
        border-top: 1px solid rgba(0,0,0,0.08);
        padding: 11px;
        background: var(--background);
        gap: 6px;
        align-items: center;
      }
      
      .chat-input {
        flex: 1;
        border: 1px solid rgba(0,0,0,0.1);
        border-radius: 9px;
        padding: 8px;
        line-height: 18px;
        font-size: 12px;
        box-sizing: border-box;
        background: var(--background-alt);
        color: var(--text-primary);
        transition: border 0.2s;
        resize: none;
        max-height: 140px;
      }
      
      .chat-input:focus {
        outline: none;
        border-color: var(--primary);
        box-shadow: 0 0 0 3px rgba(0, 120, 212, 0.2);
      }
      
      .send-btn {
        background: var(--primary);
        color: white;
        border: none;
        border-radius: 50%;
        width: 30px;
        height: 30px;
        display: flex;
        align-items: center;
        justify-content: center;
        cursor: pointer;
        transition: background 0.2s, transform 0.2s;
      }
      
      .send-btn:hover {
        background: #106ebe;
        transform: scale(1.05);
      }
      
      .send-btn:active {
        transform: scale(0.95);
      }
      
      .send-btn svg {
        width: 17px;
        height: 17px;
      }
      
      .attach-btn {
        background: transparent;
        border: none;
        cursor: pointer;
        width: 44px;
        height: 44px;
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 0;
        margin-right: 4px;
      }
      .attach-btn svg {
        width: 20px;
        height: 20px;
        stroke: #222;
        fill: none;
        display: block;
      }
      .attach-btn:hover {
        background: rgba(0,0,0,0.04);
      }
      
      .broadcast-popup {
        position: fixed;
        bottom: 127px;
        right: 40px;
        background: #fff8e1;
        color: #856404;
        padding: 14px 20px;
        border-radius: var(--border-radius);
        box-shadow: var(--shadow);
        font-size: 14px;
        z-index: 10000;
        display: flex;
        align-items: center;
        gap: 10px;
        max-width: 300px;
        transition: opacity 0.5s ease;
        border-left: 4px solid #ffb900;
      }
      
      .broadcast-icon {
        min-width: 24px;
        height: 24px;
        background: #ffb900;
        border-radius: 50%;
        display: flex;
        align-items: center;
        justify-content: center;
        font-weight: bold;
        color: #856404;
      }
      
      .fade-out {
        opacity: 0;
      }
      
      .markdown {
        background: #fff;
        border-radius: 8px;
        padding: 2px;
        font-family: monospace;
        max-width: 100%;
        overflow-x: auto;
        color: black !important;
      }
      .markdown pre {
        position: relative;
        padding: 5px;
        background-color: #f0f0f0;
        border-radius: 15px;
        overflow-x: auto;
      }
        .copy-code-btn {
        position: absolute;
        top: 6px;
        right: 6px;
        background: rgba(0,0,0,0.06);
        border: none;
        padding: 6px 8px;
        border-radius: 6px;
        cursor: pointer;
        font-size: 12px;
        display: inline-flex;
        align-items: center;
        gap: 6px;
        z-index: 10;
      }

      .copy-code-btn svg { width: 14px; height: 14px; }

      .copy-code-btn.copied {
        background: rgba(16,124,16,0.08);
        color: #107c10;
      }
      @media (max-width: 480px) {
        .chatbot-container {
          bottom: 16px;
          right: 16px;
        }
        
        .chat-window {
          width: 100vw;
          height: 100vh;
          border-radius: 0;
          bottom: 0;
          right: 0;
          position: fixed;
        }
        
        .broadcast-popup {
          bottom: 90px;
          right: 20px;
          left: 20px;
          max-width: none;
        }
        
        .fullscreen-btn,
        .minimize-btn {
          display: none !important;
        }
      }
      .chat-window.fullscreen .message-actions {
        opacity: 1 !important;
        transform: translateY(0) !important;
        pointer-events: auto !important;
        z-index: 9999 !important;  
        gap: 6px;
        font-size: 12px;            
      }

      .chat-window.fullscreen .message-row:hover .message-actions,
      .chat-window.fullscreen .message-row:focus-within .message-actions {
        opacity: 1 !important;
        transform: translateY(0) !important;
        pointer-events: auto !important;
      }

      .chat-window.fullscreen {
        width: 100vw !important;
        height: auto !important;
        border-radius: 0 !important;
        position: fixed !important;
        top: 0;left: 0; right: 0; bottom: 0;
        z-index: 2147483647 !important;
        overflow: visible !important; 
      }
      .chat-window.minimized {
        min-height: 0 !important;
        overflow: hidden !important;
      }
      .fullscreen-btn, .minimize-btn {
        background: transparent;
        border: none;
        color: white;
        cursor: pointer;
        width: 32px;
        height: 32px;
        border-radius: 50%;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 18px;
      }
      .fullscreen-btn:hover, .minimize-btn:hover {
        background: rgba(255,255,255,0.2);
      }
      </style>
      
      <div class="chatbot-container">
        ${this.isOpen ? `
          <div class="chat-window${this.isFullscreen ? ' fullscreen' : ''}${this.isMinimized ? ' minimized' : ''}">
            <div class="header">
              <div class="header-title">${this.headerTitle}</div>
              
              <div class="header-controls">
                <button class="new-chat-btn" title="Start new chat">
                  <span class="new-chat-icon"></span>
                  <span>New</span>
                </button>
                ${this.isFullscreen
                  ? `<button class="minimize-btn" title="Minimize">&#8211;</button>`
                  : `<button class="fullscreen-btn" title="Fullscreen">&#x26F6;</button>`
                }
                <button class="close-btn" title="Close">&times;</button>
              </div>
            </div>
            <div class="messages"></div>            
            <div class="input-area" role="region" aria-label="Chat input area">
              <div class="model-selection-container">
                <button class="model-toggle-btn  ${this.selectedModel ? 'model-selected' : ''} ${!this.receivedModels || this.receivedModels.length === 0 ? 'btn-disabled' : ''}" title="Select model">
                  &#128161;
                </button>
                <div class="model-dropdown-container">
                </div>
                </div>
              
              <textarea class="chat-input" rows="1" placeholder="Type a message..." autofocus></textarea>
                ${this.attachBtn ? `
              <input class="file-input" type="file" accept="image/*,.md,.markdown" style="display:none">
              <button class="attach-btn" title="Attach file">
                <svg viewBox="0 0 24 24" fill="none" width="20" height="20" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                  <path d="M21.44 11.05l-9.19 9.19a5 5 0 01-7.07-7.07l9.19-9.19a3 3 0 014.24 4.24l-9.19 9.19a1 1 0 01-1.41-1.41l9.19-9.19" />
                </svg>
              </button> ` : ``}
              <button class="send-btn">
                <svg class="send-icon" viewBox="0 0 24 24" fill="white">
                  <path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"></path>
                </svg>
              </button>
            </div>
          </div>
        ` : `
          <button class="toggle-btn">
            ${
              this.logoPath
                ? `<img src="${this.logoPath}" alt="Qsc" class="jumpLoop">`
                : `<span class="qsc-span jumpLoop">Qsc</span>`
            }
            ${this.unreadCount > 0 ? `<div class="unread-count">${this.unreadCount}</div>` : ''}
          </button>
        `}
      </div>
    `;
        this.renderMessages();
        
        // Add event listeners for new chat buttons
        const newChatButtons = this.shadowRoot.querySelectorAll('.new-chat-btn');
        newChatButtons.forEach(button => {
          button.addEventListener('click', (e) => {
            e.stopPropagation();
            this._startNewChat();
          });
        });

        // Close dropdown when clicking outside
        if (this.showModelMenu) {
          setTimeout(() => {
            const closeDropdown = (e) => {
              if (!this.shadowRoot.contains(e.target)) {
                this.showModelMenu = false;
                this.render();
                document.removeEventListener('click', closeDropdown);
              }
            };
            document.addEventListener('click', closeDropdown);
          }, 0);
        }
        const toggleBtn = this.shadowRoot.querySelector('.toggle-btn');
        if (toggleBtn) {
          toggleBtn.addEventListener('click', this.handleClick.bind(this));
        }
        
        const sendBtn = this.shadowRoot.querySelector('.send-btn');
        if (sendBtn) {
          sendBtn.addEventListener('click', this.handleClick.bind(this));
        }
        
        const chatInput = this.shadowRoot.querySelector('.chat-input');
        if (chatInput) {
          chatInput.value = this.inputValue || '';
          if (!chatInput._qsc_handlers_attached) {
            chatInput.addEventListener('input', (e) => {
              this.inputValue = e.target.value;
              try { this._autoResizeChatInput(); } catch (err) {}
            });
            chatInput.addEventListener('keydown', this.handleKeyDown.bind(this));
            chatInput._qsc_handlers_attached = true;
          }
          // initial resize
          setTimeout(() => this._autoResizeChatInput(), 0);
          if (this.isOpen) chatInput.focus();
        }
        
        const closeBtn = this.shadowRoot.querySelector('.close-btn');
        if (closeBtn) {
          closeBtn.addEventListener('click', () => {
            this.isOpen = false;
            this.render();
          });
        }
        
        const fullscreenBtn = this.shadowRoot.querySelector('.fullscreen-btn');
        if (fullscreenBtn) {
          fullscreenBtn.addEventListener('click', () => {
            this.isFullscreen = !this.isFullscreen;
            if (this.isFullscreen) this.isMinimized = false;
            this.render();
          });
        }
        const minimizeBtn = this.shadowRoot.querySelector('.minimize-btn');
        if (minimizeBtn) {
          minimizeBtn.addEventListener('click', () => {
            this.isMinimized = !this.isMinimized;
            if (this.isMinimized) this.isFullscreen = false;
            this.render();
          });
        }
        
        const fileInput = this.shadowRoot.querySelector('.file-input');
        const attachBtn = this.shadowRoot.querySelector('.attach-btn');
        if (attachBtn && fileInput) {
          attachBtn.addEventListener('click', () => fileInput.click());
          fileInput.addEventListener('change', async (e) => {
            const file = e.target.files[0];
            if (!file) return;
            if (file.type.startsWith('image/')) {
              const reader = new FileReader();
              reader.onload = async () => {
                const base64 = reader.result;
                this.messages.push({
                  id: Date.now(),
                  text: `<img src="${base64}" alt="user upload" style="max-width:200px;max-height:200px;">`,
                  sender: 'user',
                  timestamp: new Date(),
                  isHtml: true
                });
                this.renderMessages({ autoScroll: 'bottom' });
                const sessionId = localStorage.getItem('qsc_session_id') || this.sessionId || this._getSessionId();
                const modelToSend = this.selectedModel ? (this.selectedModel.model || this.selectedModel.name) : undefined;

                try {
                  const response = await fetch(this.restUrl, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ id: `rest-${Date.now()}`, type: 'image', data: base64, filename: file.name,session_id: sessionId,model :modelToSend })
                  });
                   if (!response.ok) {
                    throw new Error(`HTTP error! Status: ${response.status}`);
                  }
                  const data = await response.json();  
                  this.handleRestBotResponse(data);
                } catch (err) {
                  this.messages = this.messages.filter(m => !m.isLoading);
                  this._pushSystem(this.errMsg);
                  console.error('Send fetch failed', err);
                }
                
              };
              reader.readAsDataURL(file);
            } else if (file.name.endsWith('.md') || file.name.endsWith('.markdown')) {
              const reader = new FileReader();
              reader.onload = async () => {
                const content = reader.result;
                let html;
                try {
                  const raw = d.parse(content);
                  html = `<div class="markdown">${DOMPurify.sanitize(raw)}</div>`;
                } catch {
                  html = `<pre class="markdown">${content}</pre>`;
                }
                this.messages.push({
                  id: Date.now(),
                  text: html,
                  sender: 'user',
                  timestamp: new Date(),
                  isHtml: true
                });
                this.renderMessages({ autoScroll: 'bottom' });
                const sessionId = localStorage.getItem('qsc_session_id') || this.sessionId || this._getSessionId();
                const modelToSend = this.selectedModel ? (this.selectedModel.model || this.selectedModel.name) : undefined;

                try {
                  const response = await fetch(this.restUrl, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ id: Date.now(), type: 'markdown', data: content, filename: file.name, session_id: sessionId , model:modelToSend})
                  });
                   if (!response.ok) {
                    throw new Error(`HTTP error! Status: ${response.status}`);
                  }
                  const data = await response.json();
                  this.handleRestBotResponse(data);
                } catch (err) {
                  this.messages = this.messages.filter(m => !m.isLoading);
                  this._pushSystem(this.errMsg);
                  console.error('Send fetch failed', err);
                }
                
              };
              reader.readAsText(file);
            }
            fileInput.value = '';
          });
        }
        // Model dropdown toggle
        const modelToggleBtn = this.shadowRoot.querySelector('.model-toggle-btn');
        if (modelToggleBtn) {
          modelToggleBtn.addEventListener('click', (e) => {
            e.stopPropagation();
            this.showModelMenu = !this.showModelMenu;
             this._renderModelDropdown(); 
          });
        }
        document.addEventListener('click', () => {
          if (this.showModelMenu) {
            this.showModelMenu = false;
            this._renderModelDropdown(); // Only render the dropdown part
          }
        });
      }
    }

    customElements.define('qsc-chatbot', QscChatbot);

})();