/**
 * Content model conversion functions.
 *
 * Converts between raw markdown string and ProseMirror document nodes.
 * Single \n within block content becomes hard_break; \n\n separates paragraphs.
 * Fenced code blocks and math blocks are detected first.
 */

import type { Node as ProsemirrorNode } from "prosemirror-model"
import { createLogger } from "@/lib/logger"
import { schema } from "@/lib/schema"

const log = createLogger("ContentModel")

/**
 * Serialize a node's inline content to a string.
 * hard_break nodes emit "\n"; text nodes emit their text.
 */
function inlineToString(node: ProsemirrorNode): string {
  let out = ""
  node.forEach((child: ProsemirrorNode) => {
    if (child.isText) {
      out += child.text ?? ""
    } else if (child.type.name === "hard_break") {
      out += "\n"
    }
  })
  return out
}

/**
 * Convert markdown string to ProseMirror document.
 * Detects fenced code blocks (``` / ~~~) and creates code_block nodes.
 * Blank lines (\n\n) separate paragraphs; single \n within a group of
 * consecutive non-empty lines becomes a hard_break.
 */
export function contentToDoc(content: string): ProsemirrorNode {
  const nodes: ProsemirrorNode[] = []

  if (!content) {
    return schema.nodes.doc.create(null, [schema.nodes.paragraph.create()])
  }

  // Normalize non-breaking spaces to regular spaces to ensure
  // GFM heading parser and tag parser work correctly.
  const normalized = content.replace(/\u00a0/g, " ")
  const lines = normalized.split("\n")
  let i = 0
  let codeBlockCount = 0
  let mathBlockCount = 0
  let paraCount = 0
  while (i < lines.length) {
    // Detect $$...$$ display math blocks ($$ on a line by itself)
    const line = lines[i]
    if (line != null && line.trim() === "$$") {
      mathBlockCount++
      const mathLines: string[] = [line]
      i++
      while (i < lines.length) {
        const ml = lines[i]
        if (ml != null && ml.trim() === "$$") {
          mathLines.push(ml)
          i++
          break
        }
        mathLines.push(ml ?? "")
        i++
      }
      const mathText = mathLines.join("\n")
      nodes.push(
        mathText
          ? schema.nodes.math_block.create(null, [schema.text(mathText)])
          : schema.nodes.math_block.create(),
      )
      continue
    }

    const fence = lines[i]?.match(/^(```|~~~)\s*(\w*)/)
    if (fence) {
      codeBlockCount++
      log.debug("contentToDoc fence", { language: fence[2], line: i })
      const marker = fence[1]!
      const language = fence[2] ?? ""
      const codeLines: string[] = [lines[i]!]
      i++
      while (i < lines.length && !lines[i]?.startsWith(marker)) {
        codeLines.push(lines[i] ?? "")
        i++
      }
      // Include the closing fence line
      if (i < lines.length) {
        codeLines.push(lines[i]!)
      }
      i++
      const codeText = codeLines.join("\n")
      nodes.push(
        schema.nodes.code_block.create(
          { language },
          codeText ? [schema.text(codeText)] : [],
        ),
      )
    } else {
      // Non-empty line: collect consecutive non-empty lines into one paragraph
      // with hard_break nodes between them. Blank lines separate paragraphs.
      if (lines[i]?.length === 0) {
        i++
        continue
      }
      const paraLines: string[] = []
      while (i < lines.length && lines[i]!.length > 0) {
        // Guard: if we hit a potential fence, stop grouping
        const l = lines[i]!
        if (l.match(/^(```|~~~)/) || l.trim() === "$$") break
        paraLines.push(l)
        i++
      }
      paraCount++
      const inline: ProsemirrorNode[] = []
      for (let j = 0; j < paraLines.length; j++) {
        if (j > 0) {
          inline.push(schema.nodes.hard_break.create())
        }
        inline.push(schema.text(paraLines[j]!))
      }
      nodes.push(schema.nodes.paragraph.create(null, inline))
    }
  }

  // Ensure doc has at least one paragraph (e.g. content was all blank lines)
  if (nodes.length === 0) {
    nodes.push(schema.nodes.paragraph.create())
  }

  log.debug("contentToDoc result", {
    paragraphs: paraCount,
    codeBlocks: codeBlockCount,
    mathBlocks: mathBlockCount,
    totalLines: lines.length,
  })
  return schema.nodes.doc.create(null, nodes)
}

/**
 * Convert ProseMirror document to markdown string.
 * code_block nodes are serialized as fenced code blocks.
 * Paragraphs use inlineToString which emits "\n" for hard_break nodes.
 */
export function docToContent(doc: ProsemirrorNode): string {
  const lines: string[] = []
  let codeBlockCount = 0

  doc.forEach((node: ProsemirrorNode) => {
    if (node.type.name === "code_block") {
      codeBlockCount++
      lines.push(node.textContent)
    } else {
      lines.push(inlineToString(node))
    }
  })

  const result = lines.join("\n\n")
  log.debug("docToContent", {
    nodeCount: doc.childCount,
    codeBlocks: codeBlockCount,
    resultLen: result.length,
  })
  return result
}

export { inlineToString }
