/**
 * Escape HTML special characters (`&<>"'`) so text is safe for `innerHTML` / `v-html`.
 * Used before injecting user-written excerpts into the backlinks panel.
 */
export function escapeHtml(text: string): string {
  return text
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;")
}

/** Simple content hash for change detection (djb2 variant, base-36 encoded). */
export function contentHash(content: string): string {
  let hash = 0
  for (let i = 0; i < content.length; i++) {
    const chr = content.charCodeAt(i)
    hash = (hash << 5) - hash + chr
    hash |= 0
  }
  return hash.toString(36)
}

/**
 * Classify a block's type from its first line content.
 * Matches the block_type CHECK constraint in the schema.
 */
export function detectBlockType(content: string): string {
  const first = content.trimStart()
  if (/^#{1,6}\s/.test(first)) return "heading"
  if (
    /^TODO\s|^DOING\s|^DONE\s|^LATER\s|^NOW\s|^WAITING\s|^CANCELLED\s/i.test(
      first,
    )
  )
    return "task"
  if (/^>\s*\[!/.test(first)) return "callout"
  if (/^>\s/.test(first)) return "blockquote"
  if (/^```/.test(first)) return "code"
  if (/^\|.+\|/.test(first)) return "table"
  if (/^\$\$/.test(first)) return "math"
  if (/^\w+::\s/.test(first)) return "property"
  return "paragraph"
}

/** Extract the first `# Heading 1` from markdown content. */
export function extractTitle(content: string): string {
  const match = content.match(/^# (.+)/m)
  return match ? match[1]?.trim() : ""
}

/**
 * Derive a page's display title from its content, falling back to the
 * filename stem when there's no `# Heading 1`. Guarantees every indexed
 * page has a non-empty title — downstream code (sidebar, link navigation)
 * depends on this.
 *
 * @param content — Raw markdown content
 * @param filePath — Path relative to workspace root, e.g. `"pages/Foo.md"`
 * @returns A non-empty title string
 *
 * @remarks The filename stem is derived from the last path segment, with
 * `.md` extension stripped. Underscores and dashes are preserved as-is
 * (they're common in page names). The sidebar's `useSidebarIndex` also
 * replaces underscores with spaces for display — that's a separate concern
 * from the indexer's stored title.
 */
export function computePageTitle(content: string, filePath: string): string {
  const rawTitle = extractTitle(content)
  const stem = filePath.split("/").pop()?.replace(/\.md$/i, "") ?? ""
  return rawTitle || stem
}

export interface ExtractedLink {
  target: string
  linkType: "page-ref" | "block-ref" | "tag"
  offset: number
}

/**
 * Extract [[Page Name]], ((block-id)), and #tag references from block content.
 * Returns target text, link type, and character offset within the block.
 */
export function extractLinks(content: string): ExtractedLink[] {
  const links: ExtractedLink[] = []

  for (const m of content.matchAll(/\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g)) {
    links.push({
      target: String(m[1]),
      linkType: "page-ref",
      offset: m.index,
    })
  }
  for (const m of content.matchAll(/\(\(([a-zA-Z0-9_-]+)\)\)/g)) {
    links.push({ target: String(m[1]), linkType: "block-ref", offset: m.index })
  }
  for (const m of content.matchAll(/(?:\s|^)#([a-zA-Z0-9_-]+)/g)) {
    links.push({
      target: String(m[1]).toLowerCase(),
      linkType: "tag",
      offset: m.index + 1,
    })
  }

  return links
}

export interface ExtractedProperty {
  key: string
  value: string
}

/**
 * Extract `key:: value` properties from block content.
 * Matches the same pattern as the decoration engine's parseProperties
 * in packages/editor/src/lib/markdown-rules/engine.ts.
 */
// Mirrors TASK_RE in packages/editor/src/lib/markdown-rules/block-classifier.ts.
// Keep both in sync if adding/changing task states.
const TASK_STATES = [
  "TODO",
  "DOING",
  "DONE",
  "LATER",
  "NOW",
  "WAITING",
  "CANCELLED",
]

/**
 * Extract the task state keyword from block content if present.
 * Returns the state keyword (e.g. "TODO") or null.
 */
export function extractTaskState(content: string): string | null {
  const trimmed = content.trimStart()
  for (const state of TASK_STATES) {
    if (trimmed.startsWith(`${state} `) || trimmed === state) {
      return state
    }
  }
  return null
}

export function extractProperties(content: string): ExtractedProperty[] {
  const results: ExtractedProperty[] = []
  const lines = content.split("\n")
  const re = /^(\w[\w-]*)::([^\n]*)/
  for (const line of lines) {
    const match = re.exec(line)
    if (match) {
      results.push({ key: match[1], value: match[2].trim() })
    }
  }
  return results
}

/**
 * Convert an absolute file path to a workspace-relative path by stripping
 * the workspace root prefix. If the path is already relative, returns it
 * unchanged.
 *
 * This is used at the boundary between the filesystem layer (which returns
 * absolute paths from Tauri's `list_directory`) and the indexer (which
 * stores relative paths so they can be passed directly to the router for
 * backlink navigation).
 *
 * @param absolutePath — A path (absolute or already relative)
 * @param workspacePath — Workspace root directory (absolute)
 * @returns The path relative to workspacePath
 */
export function toRelativePath(
  absolutePath: string,
  workspacePath: string,
): string {
  const prefix = workspacePath.endsWith("/")
    ? workspacePath
    : `${workspacePath}/`
  return absolutePath.startsWith(prefix)
    ? absolutePath.slice(prefix.length)
    : absolutePath
}
