import { SKIP, visit } from 'unist-util-visit'

// Require @, #, or $ not be preceded by a word char to avoid false positives on
// email addresses (user@example.com) or inline fragments (#id in URLs).
// Cashtags: $AAPL or $^GSPC (index symbols). Requires a letter after $[^] so
// bare prices like $5 or $100 are not matched.
const PATTERN = /(?<![a-zA-Z0-9])(@[a-zA-Z0-9][a-zA-Z0-9._-]*|#[a-zA-Z][a-zA-Z0-9_]*|\$\^?[A-Za-z][A-Za-z0-9._-]*)/g

// rel="noopener" prevents the opened page from accessing window.opener (tab-napping).
// rel="noreferrer" additionally suppresses the Referer header (privacy benefit).
// rehype-sanitize must explicitly allow "target" and "rel" on <a> tags for these to survive — see markdownProcessor.ts.
const NEW_TAB = { data: { hProperties: { target: '_blank', rel: 'noopener noreferrer' } } }

type TextNode = { type: 'text'; value: string }
type LinkNode = { type: 'link'; url: string; title: null; children: TextNode[]; data?: object }
type PhrNode = TextNode | LinkNode

function splitTextNode(value: string): PhrNode[] {
  const nodes: PhrNode[] = []
  let last = 0
  let match: RegExpExecArray | null

  PATTERN.lastIndex = 0
  while ((match = PATTERN.exec(value)) !== null) {
    const token = match[1]
    const start = match.index + (match[0].length - token.length)

    if (start > last) nodes.push({ type: 'text', value: value.slice(last, start) })

    if (token.startsWith('@')) {
      nodes.push({ type: 'link', url: `https://bsky.app/profile/${token.slice(1)}`, title: null, children: [{ type: 'text', value: token }], ...NEW_TAB })
    } else if (token.startsWith('#')) {
      nodes.push({ type: 'link', url: `https://bsky.app/hashtag/${encodeURIComponent(token.slice(1))}`, title: null, children: [{ type: 'text', value: token }], ...NEW_TAB })
    } else {
      // cashtag: strip leading $, encode the rest (^ becomes %5E for index symbols)
      const ticker = token.slice(1)
      nodes.push({ type: 'link', url: `https://finance.yahoo.com/quote/${encodeURIComponent(ticker)}/`, title: null, children: [{ type: 'text', value: token }], ...NEW_TAB })
    }

    last = start + token.length
  }

  if (last < value.length) nodes.push({ type: 'text', value: value.slice(last) })
  return nodes
}

export default function remarkMentionsHashtags() {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  return (tree: any) => {
    visit(tree, 'text', (node: TextNode, index: number | undefined, parent: any) => {
      if (!parent || index == null) return
      if (parent.type === 'link' || parent.type === 'linkReference') return

      const newNodes = splitTextNode(node.value)
      if (newNodes.length === 1 && newNodes[0].type === 'text') return

      parent.children.splice(index, 1, ...newNodes)
      return [SKIP, index + newNodes.length]
    })
  }
}
