import {Attributes, ComponentType, createElement, ReactElement} from "react";
import { Token, Tokens, TokensList } from "./src/Tokens";


const unescapeTest = /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig

export function unescape(html: string) {
  // explicitly match decimal, hex, and named HTML entities
  return html.replace(unescapeTest, (_, n) => {
    n = n.toLowerCase()
    if (n === 'colon') return ':'
    if (n.charAt(0) === '#') {
      return n.charAt(1) === 'x'
        ? String.fromCharCode(parseInt(n.substring(2), 16))
        : String.fromCharCode(+n.substring(1))
    }
    return ''
  })
}

// This alphabet uses `A-Za-z0-9_-` symbols.
// The order of characters is optimized for better gzip and brotli compression.
// Same as in non-secure/index.js
const urlAlphabet = 'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict'

export function hash(str: string) {
  const parts = Math.abs(str.split("")
    .reduce((a, b) => {
      a = ((a << 5) - a) + b.charCodeAt(0);
      return a & a;
    }, 0))
    .toString()
    .split('')

  let id = ''
  // We are reading directly from the random pool to avoid creating new array
  for (const part of parts) {
    const size = Number.parseInt(part)
    if (!Number.isNaN(size)) {
      // It is incorrect to use bytes exceeding the alphabet size.
      // The following mask reduces the random byte in the 0-255 value
      // range to the 0-63 value range. Therefore, adding hacks, such
      // as empty string fallback or magic numbers, is unneccessary because
      // the bitmask trims bytes down to the alphabet size.
      id += urlAlphabet[size & 63]
    }
  }
  return id.replace(/^-+|-+$/g, '')
}

export function walkTokens<T = void>(tokens: Token[] | TokensList, callback: (token: Token) => T | T[]) {
  let values: T[] = [];
  for (const token of tokens) {
    values = values.concat(callback.call(this, token))
  }
  return values
}

export function parseHeadingId(tk: Tokens.Heading) {
  const index = tk.tokens.length - 1;
  if (tk.tokens[index].type !== 'text') {
    return hash(tk.raw)
  }

  // https://github.com/markedjs/marked-custom-heading-id/blob/main/src/index.js
  const child = tk.tokens[index] as Tokens.Text
  const headingIdRegex = /(?: +|^)\{#([a-z][\w-]*)\}(?: +|$)/i;
  const hasId = child.text.match(headingIdRegex);

  if (!hasId) {
    return hash(tk.raw)
  }

  child.text = child.text.replace(headingIdRegex, '')
  tk.text = tk.text.replace(headingIdRegex, '')

  return hasId[1]
}

let nextKey = 0

export function h<Props>(type: ComponentType<Props>, props?: Attributes & Props): ReactElement<Props> {
  props ??= {} as Attributes & Props
  props.key ??= ++nextKey
  return createElement(type, props)
}
