/**
 * Inline markdown rules — matched against the Lezer AST.
 *
 * Covers: bold, italic, code, strikethrough, links,
 *         page references, block references, tags, highlights.
 *
 * Each factory returns a MarkdownRule whose run() produces a token
 * with pre-built decorationRanges in line-local coordinates.
 * The engine shifts them to absolute document positions before use.
 */

import { parseAttrs } from "@enesis/lezer-comark"
import type { SyntaxNode } from "@lezer/common"
import type {
  BlockRefDecoration,
  HighlightDecoration,
  ImageDecoration,
  InlineMathDecoration,
  MarkdownRule,
  PageRefDecoration,
  ParseContext,
  TagDecoration,
  TokenDecoration,
} from "@/lib/markdown-rules/types"

// ── Tree helpers ─────────────────────────────────────────────────────

export function findChildByTypeName(
  node: SyntaxNode,
  typeName: string,
): SyntaxNode | null {
  const cursor = node.cursor()
  if (!cursor.firstChild()) return null
  do {
    if (cursor.node.type.name === typeName) {
      return cursor.node
    }
  } while (cursor.nextSibling())
  return null
}

export function findChildrenByTypeName(
  node: SyntaxNode,
  typeName: string,
): SyntaxNode[] {
  const results: SyntaxNode[] = []
  const cursor = node.cursor()
  if (!cursor.firstChild()) return results
  do {
    if (cursor.node.type.name === typeName) {
      results.push(cursor.node)
    }
  } while (cursor.nextSibling())
  return results
}

// ── Inline rule factories ────────────────────────────────────────────

export function createDelimitedRule(config: {
  node: string
  mark: string
  type: string
  className: string
}): MarkdownRule<TokenDecoration> {
  return {
    name: config.type,
    nodeTypes: [config.node],
    match(node) {
      return node.type.name === config.node
    },
    run(node) {
      const marks = findChildrenByTypeName(node, config.mark)
      if (marks.length < 2) return null
      // biome-ignore lint/style/noNonNullAssertion: checked length >= 2 above
      const opener = marks[0]!
      // biome-ignore lint/style/noNonNullAssertion: checked length >= 2 above
      const closer = marks[marks.length - 1]!
      return {
        type: config.type,
        wrapperAttrs: { "data-md-type": config.type },
        decorationRanges: [
          { type: "hidden", from: opener.from, to: opener.to },
          {
            type: "content",
            from: opener.to,
            to: closer.from,
            className: config.className,
          },
          { type: "hidden", from: closer.from, to: closer.to },
        ],
      }
    },
  }
}

export function createLinkRule(): MarkdownRule<TokenDecoration> {
  return {
    name: "link",
    nodeTypes: ["Link"],
    match(node) {
      return node.type.name === "Link"
    },
    run(node) {
      const linkMarks = findChildrenByTypeName(node, "LinkMark")
      if (linkMarks.length < 2) return null
      const url = findChildByTypeName(node, "URL")
      if (!url) return null
      // biome-ignore lint/style/noNonNullAssertion: checked length >= 2 above
      const opener = linkMarks[0]!
      // biome-ignore lint/style/noNonNullAssertion: checked length >= 2 above
      const closer = linkMarks[linkMarks.length - 1]!
      // biome-ignore lint/style/noNonNullAssertion: checked length >= 2 above
      const textEnd = linkMarks[1]!
      return {
        type: "link",
        wrapperAttrs: { "data-md-type": "link" },
        decorationRanges: [
          { type: "hidden", from: opener.from, to: opener.to },
          {
            type: "content",
            from: opener.to,
            to: textEnd.from,
            className: "md-link",
          },
          { type: "hidden", from: textEnd.from, to: closer.to },
        ],
      }
    },
  }
}

export function createPageRefRule(): MarkdownRule<PageRefDecoration> {
  return {
    name: "page-ref",
    nodeTypes: ["PageRef"],
    match(node) {
      return node.type.name === "PageRef"
    },
    run(node, ctx) {
      const { from, to } = node
      const innerText = ctx.doc.slice(from + 2, to - 2)
      const pipeIdx = innerText.indexOf("|")
      const pageName = pipeIdx >= 0 ? innerText.slice(0, pipeIdx) : innerText
      const alias = pipeIdx >= 0 ? innerText.slice(pipeIdx + 1) : undefined
      return {
        type: "page-ref",
        pageName,
        alias,
        wrapperAttrs: {
          "data-page-name": pageName,
          ...(alias && { "data-alias": alias }),
          role: "link",
          tabindex: "0",
        },
        decorationRanges: [
          { type: "hidden", from, to: from + 2 },
          {
            type: "content",
            from: from + 2,
            to: to - 2,
            className: "md-page-ref",
          },
          { type: "hidden", from: to - 2, to },
        ],
      }
    },
  }
}

export function createBlockRefRule(): MarkdownRule<BlockRefDecoration> {
  return {
    name: "block-ref",
    nodeTypes: ["BlockRef"],
    match(node) {
      return node.type.name === "BlockRef"
    },
    run(node, ctx) {
      const { from, to } = node
      const blockId = ctx.doc.slice(from + 2, to - 2)
      return {
        type: "block-ref",
        blockId,
        wrapperAttrs: { "data-block-id": blockId },
        decorationRanges: [
          { type: "hidden", from, to: from + 2 },
          {
            type: "content",
            from: from + 2,
            to: to - 2,
            className: "md-block-ref",
          },
          { type: "hidden", from: to - 2, to },
        ],
      }
    },
  }
}

export function createTagRule(): MarkdownRule<TagDecoration> {
  return {
    name: "tag",
    nodeTypes: ["Tag"],
    match(node) {
      return node.type.name === "Tag"
    },
    run(node, ctx: ParseContext) {
      const { from, to } = node
      const tag = ctx.doc.slice(from + 1, to)
      if (tag.length === 0 || !tag.trim()) return null
      return {
        type: "tag",
        tag,
        wrapperAttrs: { "data-tag": tag },
        decorationRanges: [{ type: "content", from, to, className: "md-tag" }],
      }
    },
  }
}

export function createInlineMathRule(): MarkdownRule<InlineMathDecoration> {
  return {
    name: "inline-math",
    nodeTypes: ["InlineMath"],
    match(node) {
      return node.type.name === "InlineMath"
    },
    run(node, ctx) {
      const { from, to } = node
      const source = ctx.doc.slice(from + 1, to - 1)
      return {
        type: "inline-math",
        wrapperAttrs: { "data-math": source },
        decorationRanges: [
          { type: "hidden", from, to: from + 1 },
          {
            type: "content",
            from: from + 1,
            to: to - 1,
            className: "md-math-inline",
          },
          { type: "hidden", from: to - 1, to },
        ],
      }
    },
  }
}

export function createHighlightRule(): MarkdownRule<HighlightDecoration> {
  return {
    name: "highlight",
    nodeTypes: ["Highlight"],
    match(node) {
      return node.type.name === "Highlight"
    },
    run(node) {
      const { from, to } = node
      return {
        type: "highlight",
        wrapperAttrs: { "data-md-type": "highlight" },
        decorationRanges: [
          { type: "hidden", from, to: from + 2 },
          {
            type: "content",
            from: from + 2,
            to: to - 2,
            className: "md-highlight",
          },
          { type: "hidden", from: to - 2, to },
        ],
      }
    },
  }
}

// ── Default rule set ─────────────────────────────────────────────────

export function createImageRule(): MarkdownRule<ImageDecoration> {
  return {
    name: "image",
    nodeTypes: ["Image"],
    match(node) {
      return node.type.name === "Image"
    },
    run(node, ctx) {
      const linkMarks = findChildrenByTypeName(node, "LinkMark")
      if (linkMarks.length < 2) return null
      const urlNode = findChildByTypeName(node, "URL")
      const opener = linkMarks[0]
      const closer = linkMarks[1]
      const lastMark = linkMarks[linkMarks.length - 1]
      if (!opener || !closer || !lastMark) return null
      const altStart = opener.to
      const altEnd = closer.from
      const alt = altStart < altEnd ? ctx.doc.slice(altStart, altEnd) : ""
      const url = urlNode ? ctx.doc.slice(urlNode.from, urlNode.to) : ""
      return {
        type: "image",
        wrapperAttrs: {
          "data-src": url,
          "data-alt": alt,
        },
        decorationRanges: [
          { type: "hidden", from: opener.from, to: opener.to },
          { type: "content", from: altStart, to: altEnd },
          { type: "hidden", from: closer.from, to: lastMark.to },
        ],
      }
    },
  }
}

// ── Inline component rule (Comark: :priority{value="high"}) ─────────

export function createInlineComponentRule(): MarkdownRule<unknown> {
  return {
    name: "inline-component",
    nodeTypes: ["InlineComponent"],
    match(node) {
      return node.type.name === "InlineComponent"
    },
    run(node, ctx) {
      const nameNode = findChildByTypeName(node, "ComponentName")
      const attrsNode = findChildByTypeName(node, "ComponentAttrs")
      const bodyNode = findChildByTypeName(node, "ComponentBody")
      if (!nameNode) return null

      const name = ctx.doc.slice(nameNode.from, nameNode.to)

      // If the component has attrs, treat it as a typed property.
      // This feeds into Phase 2 property extraction in engine.ts,
      // making :priority{value="high"} queryable like key:: value.
      if (attrsNode) {
        const rawAttrs = ctx.doc.slice(attrsNode.from, attrsNode.to)
        const attrs = parseAttrs(rawAttrs)
        return {
          type: name,
          decorationRanges: [],
          properties:
            attrs.value !== undefined
              ? [
                  {
                    key: name,
                    value: String(attrs.value),
                    keyRange: [nameNode.from, nameNode.to] as [number, number],
                    delimiterRange: [0, 0] as [number, number],
                    valueRange: [0, 0] as [number, number],
                  },
                ]
              : [],
        }
      }

      // Standalone or body-only component: render as inline chip
      if (bodyNode) {
        return {
          type: name,
          decorationRanges: [
            {
              type: "content",
              from: bodyNode.from,
              to: bodyNode.to,
              className: `md-${name}`,
            },
          ],
        }
      }

      // Standalone with no attrs and no body (e.g. :icon-check): render as chip
      return {
        type: name,
        decorationRanges: [
          {
            type: "content",
            from: nameNode.from - 1,
            to: nameNode.to,
            className: `md-${name}`,
          },
        ],
      }
    },
  }
}

export function createInlineRules(): MarkdownRule<unknown>[] {
  return [
    createDelimitedRule({
      node: "StrongEmphasis",
      mark: "EmphasisMark",
      type: "strong",
      className: "md-strong",
    }),
    createDelimitedRule({
      node: "Emphasis",
      mark: "EmphasisMark",
      type: "em",
      className: "md-em",
    }),
    createDelimitedRule({
      node: "InlineCode",
      mark: "CodeMark",
      type: "code",
      className: "md-code",
    }),
    createDelimitedRule({
      node: "Strikethrough",
      mark: "StrikethroughMark",
      type: "strike",
      className: "md-strike",
    }),
    createLinkRule(),
    createPageRefRule(),
    createBlockRefRule(),
    createTagRule(),
    createHighlightRule(),
    createInlineMathRule(),
    createImageRule(),
  ]
}
