/**
 * Block markdown rules — matched against Lezer AST block nodes.
 *
 * Covers: Blockquote (with callout detection), Heading.
 *
 * Block rules fire on `enter` during tree traversal. The engine
 * intentionally does NOT skip children, so inline rules (bold, links,
 * etc.) can still fire inside blockquotes and headings.
 * Decoration positions are absolute within the parsed docText.
 */

import type { SyntaxNode } from "@lezer/common"
import { getComarkRenderer } from "@/lib/comark-registry"
import { findChildByTypeName } from "@/lib/markdown-rules/inline-rules"
import type {
  BlockDecoration,
  BlockRule,
  CalloutType,
  ParseContext,
  TaskState,
} from "@/lib/markdown-rules/types"

const CALLOUT_TYPES: ReadonlySet<string> = new Set([
  "NOTE",
  "WARNING",
  "TIP",
  "DANGER",
  "INFO",
])

// ── Block quote rule ───────────────────────────────────────────────

export function createBlockquoteRule(): BlockRule {
  return {
    name: "blockquote",
    nodeTypes: ["Blockquote"],
    match(node) {
      return node.type.name === "Blockquote"
    },
    run(node, ctx) {
      const text = ctx.doc.slice(node.from, node.to)
      // biome-ignore lint/style/noNonNullAssertion: split on non-empty string always returns at least one element
      const firstLineText = text.split("\n")[0]!
      const trimmed = firstLineText.trimStart()

      const calloutMatch = trimmed.match(/>\s*\[!([A-Z]+)\]\s*(.*)/)

      // biome-ignore lint/style/noNonNullAssertion: match confirmed by regex above
      if (calloutMatch && CALLOUT_TYPES.has(calloutMatch[1]!)) {
        const calloutType = calloutMatch[1] as CalloutType
        return createCalloutDecoration(node, ctx, calloutType, firstLineText)
      }

      return createPlainBlockquoteDecoration(node, ctx, text)
    },
  }
}

function createPlainBlockquoteDecoration(
  node: SyntaxNode,
  _ctx: ParseContext,
  text: string,
): BlockDecoration {
  const decorationRanges: {
    type: "hidden" | "content"
    from: number
    to: number
    className: string
  }[] = []

  let offset = node.from
  for (const line of text.split("\n")) {
    const trimmed = line.trimStart()
    const leadingOffset = line.length - trimmed.length
    const gtPos = trimmed.indexOf(">")
    if (gtPos >= 0) {
      const markerStart = offset + leadingOffset + gtPos
      const markerEnd = markerStart + 1 // just the ">"
      decorationRanges.push({
        type: "hidden",
        from: markerStart,
        to: markerEnd,
        className: "md-marker",
      })
    }
    offset += line.length + 1 // +1 for the newline character
  }

  return {
    type: "blockquote",
    nodeWrapper: { className: "md-blockquote" },
    wrapperAttrs: { "data-md-type": "blockquote" },
    decorationRanges,
  }
}

function createCalloutDecoration(
  node: SyntaxNode,
  _ctx: ParseContext,
  calloutType: CalloutType,
  text: string,
): BlockDecoration {
  const decorationRanges: {
    type: "hidden" | "content"
    from: number
    to: number
    className: string
  }[] = []

  let offset = node.from
  const lines = text.split("\n")

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i]!
    const trimmed = line.trimStart()
    const leadingOffset = line.length - trimmed.length

    if (i === 0) {
      // First line: hide "[!NOTE]" marker only (keep ">" visible so it
      // matches the ">" on continuation lines after a hard_break).
      const bracketStart = trimmed.indexOf("[!")
      const bracketEnd = trimmed.indexOf("]")
      if (bracketStart >= 0 && bracketEnd > bracketStart) {
        decorationRanges.push({
          type: "hidden",
          from: offset + leadingOffset + bracketStart,
          to: offset + leadingOffset + bracketEnd + 1,
          className: "md-marker",
        })
      }
    } else {
      // Continuation lines: hide just the ">" marker
      const gtPos = trimmed.indexOf(">")
      if (gtPos >= 0) {
        decorationRanges.push({
          type: "hidden",
          from: offset + leadingOffset + gtPos,
          to: offset + leadingOffset + gtPos + 1,
          className: "md-marker",
        })
      }
    }

    offset += line.length + 1
  }

  return {
    type: "callout",
    nodeWrapper: {
      className: "md-callout",
      attrs: { "data-callout-type": calloutType },
    },
    decorationRanges,
    metadata: { calloutType },
  }
}

// ── Heading rule ──────────────────────────────────────────────────

export function createHeadingRule(): BlockRule {
  return {
    name: "heading",
    nodeTypes: [
      "ATXHeading1",
      "ATXHeading2",
      "ATXHeading3",
      "ATXHeading4",
      "ATXHeading5",
      "ATXHeading6",
    ],
    match(node) {
      return node.type.name.startsWith("ATXHeading")
    },
    run(node) {
      const level = Number.parseInt(
        node.type.name.replace("ATXHeading", ""),
        10,
      )

      const markerEnd = Math.min(node.from + level + 1, node.to)

      return {
        type: "heading",
        wrapperAttrs: { "data-heading-level": String(level) },
        decorationRanges: [
          {
            type: "hidden",
            from: node.from,
            to: Math.min(node.from + level, node.to),
            className: "md-heading-marker",
          },
          {
            type: "content",
            from: markerEnd,
            to: node.to,
            className: "md-heading-content",
          },
        ],
        metadata: { level },
      }
    },
  }
}

// ── Task rule ─────────────────────────────────────────────────────

export function createTaskRule(): BlockRule {
  return {
    name: "task",
    nodeTypes: ["Task"],
    match(node) {
      return node.type.name === "Task"
    },
    run(node, ctx) {
      const markerNode = findChildByTypeName(node, "TaskMarker")
      if (!markerNode) return null
      const stateText = ctx.doc.slice(markerNode.from, markerNode.to)
      let contentStart = markerNode.to
      while (contentStart < node.to && ctx.doc[contentStart] === " ") {
        contentStart++
      }
      const taskState = stateText.toUpperCase() as TaskState
      return {
        type: "task",
        wrapperAttrs: { "data-task-state": taskState },
        nodeWrapper: { className: "md-task" },
        decorationRanges: [
          {
            type: "hidden",
            from: markerNode.from,
            to: markerNode.to,
            className: "md-task-marker",
          },
          {
            type: "content",
            from: contentStart,
            to: node.to,
            className: "md-task-content",
          },
        ],
        metadata: { taskState },
      }
    },
  }
}

// ── Horizontal rule rule ─────────────────────────────────────────

export function createHorizontalRuleRule(): BlockRule {
  return {
    name: "horizontal-rule",
    nodeTypes: ["HorizontalRule"],
    match(node, ctx) {
      if (node.type.name !== "HorizontalRule") return false
      // Reject even-length *-based HRs (e.g. ****) — they're likely
      // bold toggle pairs, not thematic breaks.
      const text = ctx.doc.slice(node.from, node.to)
      if (
        text.length >= 4 &&
        text[0] === "*" &&
        text.split("").every((c) => c === "*")
      ) {
        return text.length % 2 !== 0
      }
      return true
    },
    run(node) {
      return {
        type: "horizontal-rule",
        nodeWrapper: {
          className: "md-hr",
          attrs: { "data-md-type": "horizontal-rule" },
        },
        wrapperAttrs: { "data-md-type": "horizontal-rule" },
        decorationRanges: [
          {
            type: "hidden",
            from: node.from,
            to: node.to,
            className: "md-hr-marker",
          },
        ],
      }
    },
  }
}

// ── Table rule ──────────────────────────────────────────────────

export function createTableRule(): BlockRule {
  return {
    name: "table",
    nodeTypes: ["Table"],
    match(node) {
      return node.type.name === "Table"
    },
    run(node, ctx) {
      const source = ctx.doc.slice(node.from, node.to)
      return {
        type: "table",
        nodeWrapper: {
          className: "md-table",
        },
        wrapperAttrs: { "data-table-source": source },
        decorationRanges: [
          {
            type: "hidden",
            from: node.from,
            to: node.to,
            className: "md-table-hidden",
          },
        ],
      }
    },
  }
}

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

// ── Comark block component rule ────────────────────────────────────

export function createBlockComponentRule(): BlockRule {
  return {
    name: "block-component",
    nodeTypes: ["BlockComponent"],
    match(node) {
      return node.type.name === "BlockComponent"
    },
    run(node, ctx) {
      const nameNode = findChildByTypeName(node, "ComponentName")
      if (!nameNode) return null
      const name = ctx.doc.slice(nameNode.from, nameNode.to)
      const renderer = getComarkRenderer(name)
      if (!renderer) return null
      return renderer(node, ctx) as BlockDecoration | null
    },
  }
}

export function createBlockRules(): BlockRule[] {
  return [
    createBlockquoteRule(),
    createHeadingRule(),
    createTaskRule(),
    createHorizontalRuleRule(),
    createTableRule(),
    createBlockComponentRule(),
  ]
}
