import { useMemo } from 'react'
// Public CHANGELOG (human-readable, без commit hashes / scope prefixes).
// Single source of truth — `docs/changelog.md` (читается mkdocs/Diplodoc
// для /docs/changelog.html). Copy step (scripts/copy-changelog.mjs)
// генерит `src/changelog-public.md` перед dev/build/test, .gitlab-ci.yml
// делает то же перед docker build admin-ui.
//
// Filename `changelog-public.md` (не `changelog.md`!) — macOS APFS/HFS+
// case-insensitive: `changelog.md` ≡ `CHANGELOG.md` (existing internal
// semantic-release-generated file) → overwrite. Suffix устраняет конфликт.
//
// Internal CHANGELOG (semantic-release auto-generated, с commit hashes для
// engineering team) — `ordinis-admin-ui/CHANGELOG.md`. Не bundled в UI.
// eslint-disable-next-line import/no-unresolved -- Vite ?raw query resolved at build
import changelogRaw from '../../changelog-public.md?raw'

/**
 * Parsed CHANGELOG entry — one version block from semantic-release output.
 *
 * <p>Source format (semantic-release default):
 * <pre>
 * ## [2.14.0](https://git.nstart.cloud/.../compare/v2.13.0...v2.14.0) (2026-05-12)
 *
 * ### ✨ Features
 *
 * * **webhook:** time-series histogram stats endpoint ([5479142](...))
 *
 * ### 🐛 Fixes
 *
 * * **ux:** friendly error UI вместо raw AxiosError ([3984d99](...))
 * </pre>
 *
 * <p>Parsed → structured for «What's New» modal rendering.
 */
export type ChangelogEntry = {
  /** Version string без 'v' префикса (e.g. "2.14.0"). */
  version: string
  /** Compare link к git diff между previous и этой версии. */
  compareUrl?: string
  /** Release date (ISO yyyy-mm-dd) если parsed из header'а. */
  date?: string
  /** Категория → bullets. Категории: "Features", "Fixes", "Performance", etc. */
  sections: ChangelogSection[]
}

export type ChangelogSection = {
  /** Display title без emoji (e.g. "Features"). */
  title: string
  /** Emoji preserved if present (e.g. "✨"). */
  emoji?: string
  items: ChangelogItem[]
}

export type ChangelogItem = {
  /** Scope если был в commit'е (e.g. "webhook", "ui"). */
  scope?: string
  /** Description без scope + без commit hash link. */
  description: string
  /** Git commit hash (8 chars). */
  commit?: string
  /** Full commit URL. */
  commitUrl?: string
}

/**
 * Parse CHANGELOG.md text → structured ChangelogEntry[].
 *
 * <p>Format assumptions (semantic-release default):
 * <ul>
 *   <li>Version header: {@code ## [2.14.0](url) (yyyy-mm-dd)}</li>
 *   <li>Section header: {@code ### ✨ Features} (emoji optional)</li>
 *   <li>Item: {@code * **scope:** description ([hash](url))}</li>
 * </ul>
 *
 * <p>Robust against missing parts: пустые scope, missing commit URL,
 * missing date. Returns entries в порядке появления (newest first для
 * semantic-release output).
 */
export function parseChangelog(raw: string): ChangelogEntry[] {
  const entries: ChangelogEntry[] = []
  // Split на version headers — каждый ## [x.y.z] block.
  const versionRegex = /^##\s+\[([^\]]+)\](?:\(([^)]+)\))?\s*(?:\(([\d-]+)\))?/gm
  const matches: { start: number; end: number; version: string; url?: string; date?: string }[] = []
  let m: RegExpExecArray | null
  while ((m = versionRegex.exec(raw)) !== null) {
    matches.push({
      start: m.index,
      end: m.index, // tentative, finalized below
      version: m[1],
      url: m[2],
      date: m[3],
    })
  }
  // Finalize end indices — каждый block заканчивается где начинается следующий
  for (let i = 0; i < matches.length; i++) {
    matches[i].end = i + 1 < matches.length ? matches[i + 1].start : raw.length
  }

  for (const block of matches) {
    const body = raw.substring(block.start, block.end)
    const sections = parseSections(body)
    entries.push({
      version: block.version,
      compareUrl: block.url,
      date: block.date,
      sections,
    })
  }
  return entries
}

function parseSections(body: string): ChangelogSection[] {
  const sectionRegex = /^###\s+(?:(\p{Emoji_Presentation}|\p{Extended_Pictographic})\s+)?(.+)$/gmu
  const sections: ChangelogSection[] = []
  const matches: { start: number; end: number; emoji?: string; title: string }[] = []
  let m: RegExpExecArray | null
  while ((m = sectionRegex.exec(body)) !== null) {
    matches.push({
      start: m.index,
      end: m.index,
      emoji: m[1],
      title: m[2].trim(),
    })
  }
  for (let i = 0; i < matches.length; i++) {
    matches[i].end = i + 1 < matches.length ? matches[i + 1].start : body.length
  }
  for (const s of matches) {
    const sectionBody = body.substring(s.start, s.end)
    const items = parseItems(sectionBody)
    sections.push({ title: s.title, emoji: s.emoji, items })
  }
  return sections
}

const ITEM_REGEX = /^\*\s+(?:\*\*([^:]+?):\*\*\s+)?(.+?)(?:\s+\(\[([a-f0-9]{7,})\]\(([^)]+)\)\))?\s*$/gm

function parseItems(sectionBody: string): ChangelogItem[] {
  const items: ChangelogItem[] = []
  let m: RegExpExecArray | null
  const regex = new RegExp(ITEM_REGEX.source, ITEM_REGEX.flags)
  while ((m = regex.exec(sectionBody)) !== null) {
    items.push({
      scope: m[1]?.trim(),
      description: m[2].trim(),
      commit: m[3],
      commitUrl: m[4],
    })
  }
  return items
}

/**
 * Hook: returns parsed CHANGELOG entries. Memoized — parse runs once per
 * page load (CHANGELOG bundled into build, не fetched runtime).
 *
 * <p>Entries sorted newest-first (semantic-release output order).
 */
export function useChangelog(): ChangelogEntry[] {
  return useMemo(() => parseChangelog(changelogRaw), [])
}

/**
 * LocalStorage key для tracking last-seen version. Per-user (browser-scoped).
 * Если key empty / missing — пользователь никогда не открывал What's New →
 * unread = ALL entries (limited к recent N в UI).
 */
const LS_KEY = 'ord-whatsnew-last-seen'

/**
 * Returns version string from localStorage, или null если не set.
 */
export function readLastSeenVersion(): string | null {
  try {
    return localStorage.getItem(LS_KEY)
  } catch {
    return null
  }
}

/**
 * Persist last-seen version. Called когда user открывает What's New modal
 * AND видит latest entry. Throttle через just writing once per session
 * (modal close persists current latest version → next session checks delta).
 */
export function writeLastSeenVersion(version: string): void {
  try {
    localStorage.setItem(LS_KEY, version)
  } catch {
    // localStorage может быть disabled (Safari private mode) — fail silently
  }
}

/**
 * Compare versions для unread detection. Semantic versioning major.minor.patch.
 * Returns true если {@code candidate} newer than {@code seen}.
 *
 * <p>Robust к unknown formats: «dev», «local», missing parts. Returns false
 * (treat as not-newer) для unparseable.
 */
export function isVersionNewer(candidate: string, seen: string | null): boolean {
  if (!seen) return true // нет baseline → все entries unread
  const parse = (v: string): number[] | null => {
    const m = v.match(/^v?(\d+)\.(\d+)\.(\d+)/)
    if (!m) return null
    return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)]
  }
  const a = parse(candidate)
  const b = parse(seen)
  if (!a || !b) return false
  for (let i = 0; i < 3; i++) {
    if (a[i] > b[i]) return true
    if (a[i] < b[i]) return false
  }
  return false
}

/**
 * Compute count of entries newer than last seen — для notification dot
 * на «What's New» button.
 */
export function useUnreadCount(): number {
  const entries = useChangelog()
  return useMemo(() => {
    const seen = readLastSeenVersion()
    return entries.filter((e) => isVersionNewer(e.version, seen)).length
    // eslint-disable-next-line react-hooks/exhaustive-deps -- seen из localStorage, не trigger'ит re-render автоматически
  }, [entries])
}
