import { unified, type Plugin } from 'unified'
import remarkParse from 'remark-parse'
import remarkGfm from 'remark-gfm'
import remarkRehype from 'remark-rehype'
import rehypeRaw from 'rehype-raw'
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize'
import rehypeStringify from 'rehype-stringify'
import rehypeHighlight from 'rehype-highlight'
import type { Element, Root } from 'hast'

// 简单的路径解析（Electron renderer 中没有 path 模块）
// E1: 统一规范化 — 解析前先全部转为 /，避免混合分隔符导致的误判
function normPath(p: string): string {
  return p.replace(/\\/g, '/').replace(/\/+$/, '')
}

function resolveRelativePath(base: string, rel: string): string {
  const parts = normPath(base).split('/')
  parts.pop() // 移除文件名
  const relParts = rel.split('/')
  for (const p of relParts) {
    if (p === '.' || p === '') continue
    if (p === '..') {
      if (parts.length > 0) parts.pop()
    } else {
      parts.push(p)
    }
  }
  return parts.join('/')
}

// 自定义 rehype 插件：将相对路径图片转为 file:// 绝对路径
function rehypeFixImages(filePath: string | null): Plugin<[], Root> {
  return () => (tree: Root) => {
    if (!filePath) return

    const dir: string = filePath.replace(/[/\\][^/\\]+$/, '')

    function visit(node: Element | Root): void {
      if (!node.children) return
      for (const child of node.children) {
        if (child.type === 'element' && child.tagName === 'img') {
          const src = child.properties?.src as string | undefined
          if (src && !src.startsWith('http://') && !src.startsWith('https://') && !src.startsWith('data:') && !src.startsWith('file://')) {
            // 处理Unix风格绝对路径（以/开头）
            if (src.startsWith('/')) {
              child.properties = {
                ...child.properties,
                src: 'file://' + src
              }
              continue
            }
            // 解析完整路径并检查是否越界到 markdown 文件所在目录之外
            const resolvedPath = resolveRelativePath(dir, src)
            // E1: 统一规范化后比较，防止 ../ 路径越界
            const normalizedBase = normPath(dir)
            if (!normPath(resolvedPath).startsWith(normalizedBase)) return
            child.properties = {
              ...child.properties,
              src: 'file://' + (normPath(dir) + '/' + src).replace(/\/+/g, '/')
            }
          }
        }
        if (child.type === 'element') {
          visit(child as Element)
        }
      }
    }

    visit(tree)
  }
}

// PF-01: Markdown处理器LRU缓存
const MAX_CACHE_SIZE = 20
const processorCache = new Map<string, ReturnType<typeof buildProcessor>>()

function buildProcessor(filePath: string | null) {
  return unified()
    .use(remarkParse)
    .use(remarkGfm)
    .use(remarkRehype, { allowDangerousHtml: true })
    .use(rehypeRaw)
    // CQ-09: rehypeFixImages 必须在 rehypeSanitize 之前运行，
    // 以保证 file:// URL 接受 sanitize 协议检查而非绕过。
    .use(rehypeFixImages(filePath ?? null))
    .use(rehypeSanitize, {
      ...defaultSchema,
      attributes: {
        ...defaultSchema.attributes,
        // 允许 img 的 src 属性（fixImages 注入 file:// 后 sanitize 需要放行）
        img: [...(defaultSchema.attributes?.img ?? []), ['src']],
      },
      protocols: {
        ...(defaultSchema.protocols ?? {}),
        src: [
          ...((defaultSchema.protocols as Record<string, string[]> | undefined)?.src ?? []),
          'file:',
        ],
      },
      // H-05: 显式 strip 事件处理器属性，纵深防御
      strip: ['script', 'on*', 'javascript:'],
    })
    .use(rehypeHighlight)
    .use(rehypeStringify)
}

function getCachedProcessor(filePath: string | null): ReturnType<typeof buildProcessor> {
  const key: string = filePath ?? '__null__'

  if (processorCache.has(key)) {
    const cached = processorCache.get(key)!
    processorCache.delete(key)
    processorCache.set(key, cached)
    return cached
  }

  const processor = buildProcessor(filePath)

  if (processorCache.size >= MAX_CACHE_SIZE) {
    const oldestKey = processorCache.keys().next().value
    if (oldestKey !== undefined) {
      processorCache.delete(oldestKey)
    }
  }

  processorCache.set(key, processor)
  return processor
}

export async function renderMarkdown(content: string, filePath?: string | null): Promise<string> {
  try {
    const processor = getCachedProcessor(filePath ?? null)
    const result = await processor.process(content)
    return String(result)
  } catch (e) {
    const errorMsg: string = e instanceof Error ? e.message : String(e)
    return `<p style="color:red">渲染错误: ${errorMsg}</p>`
  }
}
