/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/quotes */
import path from 'node:path'
import MarkdownIt from 'markdown-it'
import { DomUtils, parseDOM } from 'htmlparser2'
import { Element, Text } from 'domhandler'
import { transformSync } from '@babel/core'
import frontMatter from 'front-matter'
import { toArray } from '@antfu/utils'
import type { TransformResult } from 'vite'
import type { Node as DomHandlerNode } from 'domhandler'
import type { ResolvedOptions } from './type'
import { transformAttribs } from './attribs'
import { getComponentPath, getWrapperComponent } from './wrapperComponent'
import hljs from 'highlight.js'
import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
console.log(__dirname)

const codeBlockStyle = (val: string): string => {
  return `<pre class="hljs"><code>${val}</code></pre>`
}

const highlightFormatCode = (str: string, lang: string, md): string => {
  if (lang && hljs.getLanguage(lang)) {
    try {
      return codeBlockStyle(hljs.highlight(lang, str, true).value)
    } catch (e) {
      console.error(e)
    }
  }

  return codeBlockStyle(md.utils.escapeHtml(str))
}

export function createMarkdown(useOptions: ResolvedOptions) {
  const markdown = new MarkdownIt({
    html: true,
    linkify: true, // Autoconvert URL-like text to links
    breaks: true, // Convert '\n' in paragraphs into <br>
    highlight: function (str, lang) {
      return highlightFormatCode(str, lang, markdown)
    },
    ...useOptions.markdownItOptions
  })

  useOptions.markdownItUses.forEach((e) => {
    const [plugin, options] = toArray(e)
    markdown.use(plugin, options)
  })
  useOptions.markdownItSetup(markdown)

  /**
 * Inject line number
 */
  const injectLineNumbers = (
    tokens,
    idx,
    options,
    _env,
    slf
  ) => {
    let line
    if (tokens[idx].map && tokens[idx].level === 0) {
      line = (tokens[idx].map)[0]
      tokens[idx].attrJoin('class', 'line')
      tokens[idx].attrSet('data-line', String(line))
    }
    return slf.renderToken(tokens, idx, options)
  }

  markdown.renderer.rules.heading_open = (tokens, idx, options, env, slf) => {
    injectLineNumbers(tokens, idx, options, env, slf)
    return slf.renderToken(tokens, idx, options)
  }

  markdown.renderer.rules.paragraph_open = injectLineNumbers

  return async (raw: string, id: string): Promise<TransformResult> => {
    const { body, attributes } = frontMatter(raw)
    const attributesString = JSON.stringify(attributes)
    const wrapperComponentData = await getWrapperComponent(useOptions.wrapperComponent)
    const importComponentName: string[] = []
    // partial transform code from : https://github.com/hmsk/vite-plugin-markdown/blob/main/src/index.ts
    const html = markdown.render(body, { id })
    const root = parseDOM(html, { lowerCaseTags: false })

    root.forEach(markCodeAsPre)
    const h = DomUtils.getOuterHTML(root, { selfClosingTags: true })
      .replace(/"vfm{{/g, '{{')
      .replace(/}}vfm"/g, '}}')
      .replace(/&lt;/g, '<')
      .replace(/&gt;/g, '>')
      .replace(/&quot;/g, '"')
      .replace(/&amp;/g, '&')
      // handle notes
      .replace(/<!--/g, '{/*')
      .replace(/-->/g, '*/}')

    let reactCode
    let wrapperComponent = ''
    if (useOptions.wrapperComponentPath) {
      const componentPath = getComponentPath(id, useOptions.wrapperComponentPath)
      wrapperComponent = `import ${useOptions.wrapperComponentName} from '${componentPath}'\n`
      reactCode = `
        const markdown =
          <${useOptions.wrapperComponentName} 
            attributes={${attributesString}}
            importComponentName={${JSON.stringify(importComponentName)}}
          >
            <React.Fragment>
              ${h} 
            </React.Fragment> 
          </${useOptions.wrapperComponentName}>
      `
    }
    else {
      reactCode = `
        const markdown =
          <div className='${useOptions.wrapperClasses}'>
            ${h}
          </div>
      `
    }
    let importComponent = ''
    if (wrapperComponentData && typeof wrapperComponentData === 'object' && importComponentName.length > 0) {
      importComponentName.forEach((componentName) => {
        const path = wrapperComponentData![componentName]
        if (path)
          importComponent += `import ${componentName} from '${getComponentPath(id, path)}'\n`
      })
    }

    const compiledReactCode = `
  function (props) {
    ${transformSync(reactCode, { ast: false, presets: ['@babel/preset-react'] })!.code}
    return markdown
  } 
`
    let code = 'import React from \'react\'\n'
    code += 'import \'@/plugins/vite-plugin-react-markdown/css/markdown.min.css\'\n'
    code += `${wrapperComponent}`
    code += `${importComponent}`
    code += `const ReactComponent = ${compiledReactCode}\n`
    code += 'export default ReactComponent\n'
    code += `export const attributes = ${attributesString}`
    return {
      code,
      map: { mappings: '' } as any,
    }
    function markCodeAsPre(node: DomHandlerNode): void {
      if (node instanceof Element) {
        if (node.tagName.match(/^[A-Z].+/) && !importComponentName.includes(node.tagName))
          importComponentName.push(node.tagName)

        transformAttribs(node.attribs)
        if (node.tagName === 'code') {
          const codeContent = DomUtils.getInnerHTML(node, { decodeEntities: true })
          node.attribs.dangerouslySetInnerHTML = `vfm{{ __html: \`${codeContent.replace(/([\\`])/g, '\\$1')}\`}}vfm`
          node.childNodes = []
        }
        if (node.childNodes.length > 0)
          node.childNodes.forEach(markCodeAsPre)
      }

      if (node instanceof Text) {
        if (node.type === 'text') {
          // .replace(/&lt;/g, '<')
          // .replace(/&gt;/g, '>')
          node.data = node.data.replace(/</g, '&lt;').replace(/>/g, '&gt;')
        }
      }
    }
  }
}