import fs from 'fs'
import matter from 'gray-matter'
import path, { dirname } from 'path'
import { fileURLToPath } from 'url'

import { processMarkdownContent, rewriteLinksToMd } from '../src/utils/md.js'

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

const PUBLIC_WEB_URL = (
  process.env.PUBLIC_WEB_URL || 'https://daytona.io'
).replace(/\/$/, '')
const DOCS_BASE_URL = `${PUBLIC_WEB_URL}/docs`

const BUILD_OUTPUT_DIR = path.join(
  __dirname,
  '../../../dist/apps/docs/client'
)
const STATIC_LOCALE = 'en'
const STATIC_DOCS_OUTPUT_DIR = path.join(BUILD_OUTPUT_DIR, STATIC_LOCALE)
const STATIC_DOCS_OUTPUT_DIR_ROOT = BUILD_OUTPUT_DIR

const packageJson = JSON.parse(
  fs.readFileSync(path.join(__dirname, '../../../package.json'), 'utf8')
)
const version = packageJson.version

const getCurrentDate = () => {
  const date = new Date()
  return date.toISOString().split('T')[0] // YYYY-MM-DD format
}

const getVersionHeader = () => {
  return [
    `# Daytona Documentation`,
    `# ${DOCS_BASE_URL}`,
    `# Generated on: ${getCurrentDate()}`,
  ].join('\n')
}

// Only include English docs
const DOCS_PATH = path.join(__dirname, '../src/content/docs/en')
const SIDEBAR_CONFIG_PATH = path.join(__dirname, '../src/sidebar-config.ts')
const EXCLUDE_FILES = new Set(['404.md', 'api.mdx'])

const ensureLeadingSlash = value =>
  value.startsWith('/') ? value : `/${value}`

const normalizeSlugPath = slug => {
  if (!slug) return '/'

  let value = ensureLeadingSlash(slug.replace(/\\/g, '/'))
  value = value.replace(/\/+/g, '/')

  if (value.endsWith('/index')) {
    value = value.slice(0, -'/index'.length) || '/'
  }

  if (value !== '/' && value.endsWith('/')) {
    value = value.slice(0, -1)
  }

  return value || '/'
}

const getOutputRelativeSlug = normalizedSlug =>
  !normalizedSlug || normalizedSlug === '/'
    ? 'docs'
    : normalizedSlug.replace(/^\//, '')

const getSlugFromFilePath = filePath =>
  filePath
    .replace(DOCS_PATH, '')
    .replace(/\\/g, '/')
    .replace(/\.mdx?$/, '')

const extractSubHeadings = (content, slug) => {
  const headingRegex = /^(#{2,3})\s+(.*)/gm
  const headings = []
  let match

  while ((match = headingRegex.exec(content)) !== null) {
    const headingSlug = `${slug}#${match[2]
      .toLowerCase()
      .replace(/\s+/g, '-')
      .replace(/[^a-z0-9-]/g, '')}`
    headings.push({ title: match[2].trim(), url: `/docs/en${headingSlug}` })
  }

  return headings
}

const prependTitleHeading = (content, title) => {
  if (!title) return content
  if (/^#\s+/m.test(content.split('\n', 1)[0] || '')) return content
  return `# ${title}\n\n${content}`
}

const parseMarkdownFile = (filePath, fileContent) => {
  const { content, data } = matter(fileContent)
  const processedContent = processMarkdownContent(content)
  const title =
    data.title ||
    processedContent.match(/^#\s+(.*)/)?.[1]?.trim() ||
    'Untitled'
  const cleanContent = prependTitleHeading(processedContent, title)
  const rewrittenContent = rewriteLinksToMd(cleanContent, DOCS_BASE_URL)
  const slug = getSlugFromFilePath(filePath)
  const normalizedSlug = normalizeSlugPath(slug)

  return {
    entries: [
      {
        title,
        url: normalizedSlug === '/' ? '/docs/en' : `/docs/en${normalizedSlug}`,
      },
      ...extractSubHeadings(cleanContent, normalizedSlug),
    ],
    doc: {
      slug: normalizedSlug,
      content: rewrittenContent,
    },
    cleanContent,
  }
}

const resolveSlugToFilePath = relativeSlug => {
  if (!relativeSlug) {
    const indexPath = path.join(DOCS_PATH, 'index.mdx')
    return fs.existsSync(indexPath) ? indexPath : null
  }

  const candidates = [
    path.join(DOCS_PATH, `${relativeSlug}.mdx`),
    path.join(DOCS_PATH, `${relativeSlug}.md`),
    path.join(DOCS_PATH, relativeSlug, 'index.mdx'),
    path.join(DOCS_PATH, relativeSlug, 'index.md'),
  ]
  return candidates.find(candidate => fs.existsSync(candidate)) || null
}

const sidebarHrefToRelativeSlug = href => {
  if (typeof href !== 'string' || !href.startsWith('/docs')) return null
  return href.replace(/^\/docs\/?/, '')
}

const listAutopopulateDirSlugs = autoDir => {
  const relative = autoDir.replace(/^\/docs\/?/, '')
  if (!relative) return []

  const dirPath = path.join(DOCS_PATH, relative)
  if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) {
    return []
  }

  return fs
    .readdirSync(dirPath)
    .filter(file => !['index.md', 'index.mdx'].includes(file))
    .filter(file => file.endsWith('.md') || file.endsWith('.mdx'))
    .sort()
    .map(file => `${relative}/${file.replace(/\.(md|mdx)$/, '')}`)
}

const buildOrderedFilePaths = () => {
  const orderedPaths = []
  const seenPaths = new Set()

  const addSlug = relativeSlug => {
    const filePath = resolveSlugToFilePath(relativeSlug)
    if (!filePath) return
    if (seenPaths.has(filePath)) return
    if (EXCLUDE_FILES.has(path.basename(filePath))) return
    orderedPaths.push(filePath)
    seenPaths.add(filePath)
  }

  if (fs.existsSync(SIDEBAR_CONFIG_PATH)) {
    const sidebarSource = fs.readFileSync(SIDEBAR_CONFIG_PATH, 'utf8')
    const groupChunks = sidebarSource.split(/(?=type:\s*['"]group['"])/g)

    for (const chunk of groupChunks) {
      if (!/type:\s*['"]group['"]/.test(chunk)) continue
      if (/category:\s*NavigationCategory\.MAIN\b/.test(chunk)) continue

      const hrefRegex = /href:\s*localizePath\(\s*['"]([^'"]+)['"]/g
      let match
      while ((match = hrefRegex.exec(chunk)) !== null) {
        const slug = sidebarHrefToRelativeSlug(match[1])
        if (slug !== null) addSlug(slug)
      }

      const autoMatch = chunk.match(
        /autopopulateFromDir:\s*localizePath\(\s*['"]([^'"]+)['"]/
      )
      if (autoMatch) {
        for (const slug of listAutopopulateDirSlugs(autoMatch[1])) {
          addSlug(slug)
        }
      }
    }
  } else {
    console.warn(
      `Sidebar config not found at ${SIDEBAR_CONFIG_PATH}; falling back to filesystem order.`
    )
  }

  const traverseAndAppend = directory => {
    fs.readdirSync(directory)
      .sort()
      .forEach(file => {
        const fullPath = path.join(directory, file)
        const stat = fs.statSync(fullPath)
        if (stat.isDirectory()) {
          traverseAndAppend(fullPath)
        } else if (
          stat.isFile() &&
          (file.endsWith('.md') || file.endsWith('.mdx')) &&
          !EXCLUDE_FILES.has(file) &&
          !seenPaths.has(fullPath)
        ) {
          orderedPaths.push(fullPath)
          seenPaths.add(fullPath)
        }
      })
  }
  traverseAndAppend(DOCS_PATH)

  return orderedPaths
}

const searchDocs = () => {
  const results = []
  const docs = []
  const fullContentArray = []

  const orderedFilePaths = buildOrderedFilePaths()

  for (const fullPath of orderedFilePaths) {
    const fileContent = fs.readFileSync(fullPath, 'utf8')
    const { entries, doc, cleanContent } = parseMarkdownFile(
      fullPath,
      fileContent
    )

    fullContentArray.push(cleanContent)
    results.push(...entries)
    docs.push(doc)
  }

  return {
    results,
    docs,
    fullContent: fullContentArray.join('\n\n'),
  }
}

const generateLlmsTxtFile = docsData => {
  const llmsContent = [
    getVersionHeader(),
    `# Full documentation: ${DOCS_BASE_URL}/llms-full.txt`,
    '',
    '# Daytona',
    '',
    'Daytona is an open-source, secure and elastic infrastructure for running AI-generated code. Daytona provides full composable computers — [sandboxes](https://daytona.io/docs/en/sandboxes.md) — with complete isolation, a dedicated kernel, filesystem, network stack, and allocated vCPU, RAM, and disk.',
    '',
    'Sandboxes are the core component of the Daytona platform, spinning up in under 90ms from code to execution and running any code in Python, TypeScript, and JavaScript. Built on OCI/Docker compatibility, massive parallelization, and unlimited persistence, sandboxes deliver consistent, predictable environments for agent workflows.',
    '',
    'Agents and developers interact with sandboxes programmatically using the Daytona SDKs, API, and CLI. Operations span sandbox lifecycle management, filesystem operations, process and code execution, and runtime configuration. Our stateful environment snapshots enable persistent agent operations across sessions, making Daytona the ideal foundation for AI agent architectures.',
    '',
    '## Daytona Documentation',
    '',
    ...docsData.map(doc => `- [${doc.title}](${PUBLIC_WEB_URL}${doc.url})`),
  ]
  const output = rewriteLinksToMd(llmsContent.join('\n'), DOCS_BASE_URL)
  if (!fs.existsSync(BUILD_OUTPUT_DIR)) {
    fs.mkdirSync(BUILD_OUTPUT_DIR, { recursive: true })
  }
  fs.writeFileSync(
    path.join(BUILD_OUTPUT_DIR, 'llms.txt'),
    output,
    'utf8'
  )
  console.log('llms.txt index updated')
}

const generateLlmsFullTxtFile = fullContent => {
  const content = [getVersionHeader(), fullContent].join('\n\n')
  const output = rewriteLinksToMd(content, DOCS_BASE_URL)
  if (!fs.existsSync(BUILD_OUTPUT_DIR)) {
    fs.mkdirSync(BUILD_OUTPUT_DIR, { recursive: true })
  }
  fs.writeFileSync(
    path.join(BUILD_OUTPUT_DIR, 'llms-full.txt'),
    output,
    'utf8'
  )
  console.log('llms-full.txt index updated')
}

const generateStaticMarkdownFiles = docs => {
  const writeMarkdownFile = (outputPath, content) => {
    fs.mkdirSync(path.dirname(outputPath), { recursive: true })
    fs.writeFileSync(outputPath, content, 'utf8')
    console.log(
      `Static markdown page generated for "${path.relative(
        STATIC_DOCS_OUTPUT_DIR_ROOT,
        outputPath
      )}"`
    )
  }

  docs.forEach(({ slug, content }) => {
    const relativeSlug = getOutputRelativeSlug(slug)
    const localeOutputPath = path.join(
      STATIC_DOCS_OUTPUT_DIR,
      `${relativeSlug}.md`
    )
    const rootOutputPath = path.join(
      STATIC_DOCS_OUTPUT_DIR_ROOT,
      `${relativeSlug}.md`
    )

    writeMarkdownFile(localeOutputPath, content)

    if (rootOutputPath !== localeOutputPath) {
      writeMarkdownFile(rootOutputPath, content)
    }

    if (slug === '/' || slug === '') {
      const rootLocaleAlias = path.join(
        STATIC_DOCS_OUTPUT_DIR_ROOT,
        `${STATIC_LOCALE}.md`
      )
      writeMarkdownFile(rootLocaleAlias, content)
    }
  })
}

const main = () => {
  const { results, fullContent, docs } = searchDocs()
  generateLlmsTxtFile(results)
  generateLlmsFullTxtFile(fullContent)
  generateStaticMarkdownFiles(docs)
}

main()
