import {Attributes, ComponentType, createElement, ReactElement, ReactNode} from "react"
import {View, Text, Image, Video, Audio} from "@tarojs/components"
import {parseHeadingId, walkTokens, unescape} from "./utils"
import TabLink from "./components/Tablink";
import './marked.scss'
import {Token, Tokens } from "./src/Tokens";
import { lexer } from "./src/marked";

/**
 * 解析 Markdown 文本
 *
 * ```ts
 * const Markdown: FC<{ source: string }> = ({ source }) => {
 *   const { children, headings } = useMemo(() => parse(source), [source])
 *   console.log(headings) // => [{level: 1, text: 'heading', id: 'custom-id'}]
 *   return (<View>{children}</View>)
 * }
 *
 * <Markdown source={'# heading {#custom-id}'} />
 * ```
 *
 * @param {string} content 原始文本
 *
 * @see {@link https://www.markdownguide.org}
 * @see {@link https://marked.js.org}
 */
export const parse = (content: string) => {
  const headings: Heading[] = []
  let nextKey = 0

  const ctx: RenderContext = {
    createReactElement<P>(type: ComponentType<P>, props?: Props<P>): ReactElement<P> {
      props ??= {} as Props<P>
      props.key ??= ++nextKey
      return createElement(type, props)
    },

    ensureHeading(token: Tokens.Heading): string {
      // todo(hupeh): id 不能重复
      // todo(hupeh): 第一层标题级别必须一致
      const id = parseHeadingId(token)
      headings.push({id, text: token.text, level: token.depth})
      return id
    },
  }

  const tokens = lexer(content)
  const children = render(ctx, tokens)

  return {children, headings}
}

export interface Heading {
  level: number
  text: string
  id: string
}

type Props<P> = Attributes & P

interface RenderContext {
  createReactElement<P>(type: ComponentType<P>, props?: Props<P>): ReactElement<P>

  ensureHeading(token: Tokens.Heading): string
}

function render(ctx: RenderContext, tokens: Token[]) {
  return walkTokens<ReactNode>(tokens, (token) => renderToken(ctx, token))
}

function renderToken(ctx: RenderContext, token: Token) {
  return (renderer[token.type] ?? renderer.generic)(ctx, token)
}

const renderer = {
  space(_: RenderContext, tk: Tokens.Space) {
    // todo(hupeh): 测试是否需要 Text 实现
    return tk.raw
  },

  code(ctx: RenderContext, tk: Tokens.Code) {
    return ctx.createReactElement(View, {
      className: 'code',
      children: tk.escaped ? escape(tk.text) : tk.text,
    })
  },

  heading(ctx: RenderContext, tk: Tokens.Heading) {
    const id = ctx.ensureHeading(tk)

    // todo(hupeh): 是否需要调整标题级别保证顺序一致
    return ctx.createReactElement(View, {
      id,
      className: `h${tk.depth}`,
      children: render(ctx, tk.tokens),
    })
  },

  hr(ctx: RenderContext, _: Tokens.Hr) {
    return ctx.createReactElement(View, {
      className: 'hr',
    })
  },

  blockquote(ctx: RenderContext, tk: Tokens.Blockquote) {
    return ctx.createReactElement(View, {
      className: 'blockquote',
      children: render(ctx, tk.tokens),
    })
  },

  list(ctx: RenderContext, tk: Tokens.List) {
    const isTaskList = tk.items[0]?.task == true;
    // todo(hupeh): 什么是 tk.loose
    return ctx.createReactElement(View, {
      className: isTaskList ? 'taskList' : tk.ordered ? 'orderedList' : 'list',
      children: tk.items.map((item, index) => {
        return ctx.createReactElement(View, {
          className: isTaskList ? 'taskListItem' : tk.ordered ? 'orderedListItem' : "listItem",
          children: [
            !isTaskList ? ctx.createReactElement(View, {
              className: 'listItemIndicator',
              children: tk.ordered ? index + 1 : '•',
            }) : null,
            item.task ? ctx.createReactElement(View, {
              className: item.checked ? 'checkedTaskIndicator' : 'uncheckTaskIndicator',
              children: item.checked ? ctx.createReactElement(Text, {
                children: '✔',
              }) : null,
            }) : null,
            ...render(ctx, item.tokens),
          ],
        });
      })
    });
  },

  paragraph(ctx: RenderContext, tk: Tokens.Paragraph) {
    // todo(hupeh): tk.pre
    return ctx.createReactElement(View, {
      className: 'paragraph',
      children: render(ctx, tk.tokens),
    });
  },

  html(ctx: RenderContext, tk: Tokens.HTML) {
    // todo(hupeh): 什么是 tk.pre ? 对应 <pre> 标签吗？
    // if (tk.pre) {}
    if (tk.block) {
      return ctx.createReactElement(View, {
        className: 'blockHtml',
        children: tk.text.split(/\r?\n/).map(line => {
          return ctx.createReactElement(View, {
            className: 'blockHtmlItem',
            children: ctx.createReactElement(Text, {
              space: 'nbsp', // 空格符号渲染方式
              decode: true, // 正确显示实体
              children: line.trimEnd(),
            }),
          })
        }),
      })
    }
    return ctx.createReactElement(Text, {
      className: 'inlineHtml',
      children: tk.text,
    })
  },

  text(ctx: RenderContext, tk: Tokens.Text) {
    if (tk.tokens?.length) {
      return render(ctx, tk.tokens)
    }
    // todo 转义 HTML 实体
    return ctx.createReactElement(Text, {
      className: 'text',
      children: unescape(tk.text),
      decode: true,
    })
  },

  def(ctx: RenderContext, tk: Tokens.Def) {
    return ctx.createReactElement(Text, {
      className: 'def',
      children: tk.title,
    })
  },

  escape(ctx: RenderContext, tk: Tokens.Escape) {
    return ctx.createReactElement(Text, {
      className: 'escape',
      children: escape(tk.text),
    })
  },

  link(ctx: RenderContext, tk: Tokens.Link) {
    // todo(hupeh): 页面内部跳转，根据 heading 的 ID 实现
    return ctx.createReactElement(TabLink, {
      className: 'link',
      url: tk.href,
      children: render(ctx, tk.tokens),
    })
  },

  image(ctx: RenderContext, tk: Tokens.Image) {
    // todo(hupeh): 相关控制属性可以通过 QueryString 解析出来
    const ext = tk.href.split('?').shift()!.split('.').pop()!.toLowerCase();
    switch (ext) {
      case 'mp4':
      case 'm4v':
      case 'mov':
      case 'qt':
      case 'avi':
      case 'flv':
      case 'wmv':
      case 'asf':
      case 'mkv':
      case 'rm':
      case 'rmvb':
      case 'vob':
      case 'ts':
      case 'dat':
        return ctx.createReactElement(Video, {
          className: 'video',
          src: tk.href,
        })
      case 'mp3':
      case 'wma':
      case 'flac':
      case 'wav':
        // todo(hupeh): 使用 Taro.createInnerAudioContext 接口封装组件
        return ctx.createReactElement(Audio, {
          className: 'audio',
          src: tk.href
        })
      default:
        return ctx.createReactElement(Image, {
          className: 'image',
          src: tk.href,
          mode: "widthFix",
          webp: ext === 'web',
        })
    }
  },

  strong(ctx: RenderContext, tk: Tokens.Strong) {
    return ctx.createReactElement(Text, {
      className: 'strong',
      children: render(ctx, tk.tokens),
    })
  },

  em(ctx: RenderContext, tk: Tokens.Em) {
    return ctx.createReactElement(Text, {
      className: 'em',
      children: render(ctx, tk.tokens),
    })
  },

  codespan(ctx: RenderContext, tk: Tokens.Codespan) {
    return ctx.createReactElement(Text, {
      className: 'codespan',
      children: tk.text,
    })
  },

  br(ctx: RenderContext, _: Tokens.Br) {
    return ctx.createReactElement(View, {
      className: 'br',
    })
  },

  del(ctx: RenderContext, tk: Tokens.Del) {
    return ctx.createReactElement(Text, {
      className: 'del',
      children: render(ctx, tk.tokens),
    })
  },

  table(ctx: RenderContext, tk: Tokens.Table) {
    // todo(hupeh): 使用自定义 table 组件，检测单元格宽度
    // return "暂不支持表格渲染";
    return ctx.createReactElement(View, {
      className: 'table',
      children: [
        ctx.createReactElement(View, {
          className: 'tableHeader',
          children: tk.header.map((cell, index) => {
            return ctx.createReactElement(View, {
              className: 'tableHeaderCell',
              style: {textAlign: tk.align[index] || 'left'},
              children: render(ctx, cell.tokens),
            })
          }),
        }),
        ctx.createReactElement(View, {
          className: 'tableBody',
          children: tk.rows.map(rows => {
            return ctx.createReactElement(View, {
              className: 'tableBodyRow',
              children: rows.map((cell, index) => {
                return ctx.createReactElement(View, {
                  className: 'tableBodyCell',
                  style: {textAlign: tk.align[index] || 'left'},
                  children: render(ctx, cell.tokens),
                })
              })
            })
          }),
        }),
      ],
    })
  },

  generic(ctx: RenderContext, tk: Tokens.Generic) {
    // todo(hupeh): 验证 tk.type
    // todo(hupeh): 调试模式下不返回 null
    return tk.tokens?.length ? render(ctx, tk.tokens) : null
  },
}
