import path from 'node:path'
import type { App } from '@vuepress/core'
import * as chokidar from 'chokidar'
import { createFilter } from 'create-filter'
import type {
  NotesData,
  NotesDataOptions,
  NotesItem,
  NotesSidebar,
  NotesSidebarItem,
} from '../shared/index.js'
import { ensureArray, normalizePath } from './utils.js'

const HMR_CODE = `
if (import.meta.webpackHot) {
  import.meta.webpackHot.accept()
  if (__VUE_HMR_RUNTIME__.updateNotesData) {
    __VUE_HMR_RUNTIME__.updateNotesData(notesData)
  }
}

if (import.meta.hot) {
  import.meta.hot.accept(({ notesData }) => {
    __VUE_HMR_RUNTIME__.updateNotesData(notesData)
  })
}
`

interface NotePage {
  relativePath: string
  title: string
  link: string
}

export async function prepareNotesData(app: App, { include, exclude, notes, dir, link }: NotesDataOptions) {
  if (!notes || notes.length === 0)
    return
  dir = normalizePath(dir)
  const filter = createFilter(ensureArray(include), ensureArray(exclude), {
    resolve: false,
  })
  const DIR_PATTERN = new RegExp(`^${normalizePath(path.join(dir, '/'))}`)
  const notesPageList: NotePage[] = app.pages
    .filter(
      page =>
        page.filePathRelative
        && page.filePathRelative.startsWith(dir)
        && filter(page.filePathRelative),
    )
    .map((page) => {
      return {
        relativePath: page.filePathRelative?.replace(DIR_PATTERN, '') || '',
        title: page.title,
        link: page.path,
      }
    })

  const notesData: NotesData = {}
  notes.forEach((note) => {
    notesData[normalizePath(path.join('/', link, note.link))] = initSidebar(
      note,
      notesPageList.filter(page =>
        page.relativePath.startsWith(note.dir.trim().replace(/^\/|\/$/g, '')),
      ),
    )
  })
  let content = `
export const notesData = ${JSON.stringify(notesData, null, 2)}
`
  if (app.env.isDev)
    content += HMR_CODE

  await app.writeTemp('internal/notesData.js', content)
}

export function watchNotesData(app: App, watchers: any[], options: NotesDataOptions): void {
  if (!options.notes || options.notes.length === 0 || !options.dir)
    return
  const dir = path.join('pages', options.dir, '**/*')
  const watcher = chokidar.watch(dir, {
    cwd: app.dir.temp(),
    ignoreInitial: true,
  })

  watcher.on('add', () => prepareNotesData(app, options))
  watcher.on('change', () => prepareNotesData(app, options))
  watcher.on('unlink', () => prepareNotesData(app, options))
  watchers.push(watcher)
}

function initSidebar(note: NotesItem, pages: NotePage[]): NotesSidebarItem[] {
  if (!note.sidebar)
    return []
  if (note.sidebar === 'auto')
    return initSidebarByAuto(note, pages)
  return initSidebarByConfig(note, pages)
}

function initSidebarByAuto(
  note: NotesItem,
  pages: NotePage[],
): NotesSidebarItem[] {
  pages = pages.sort((prev, next) => {
    const pi = prev.relativePath.match(/\//g)?.length || 0
    const ni = next.relativePath.match(/\//g)?.length || 0
    return pi < ni ? -1 : 1
  })
  const RE_INDEX = ['index.md', 'README.md', 'readme.md']
  const result: NotesSidebarItem[] = []
  for (const page of pages) {
    const { relativePath, title, link } = page
    const paths = relativePath
      .slice(note.dir.replace(/^\/|\/$/g, '').length + 1)
      .split('/')
    let index = 0
    let dir: string
    let items = result
    // eslint-disable-next-line no-cond-assign
    while ((dir = paths[index])) {
      const text = dir.replace(/\.md$/, '')
      let current = items.find(item => item.text === text)
      if (!current) {
        current = { text, link: undefined, items: [] }
        !RE_INDEX.includes(dir) ? items.push(current) : items.unshift(current)
      }
      if (dir.endsWith('.md')) {
        current.link = link
        current.text = title
      }
      items = current.items as NotesSidebarItem[]
      index++
    }
  }
  return result
}

function initSidebarByConfig(
  { text, dir, sidebar }: NotesItem,
  pages: NotePage[],
): NotesSidebarItem[] {
  return (sidebar as NotesSidebar).map((item) => {
    if (typeof item === 'string') {
      const current = findNotePage(item, dir, pages)
      return {
        text: current?.title || text,
        link: current?.link,
        items: [],
      }
    }
    else {
      const current = findNotePage(item.link || '', dir, pages)
      return {
        text: item.text || item.dir || current?.title,
        collapsed: item.collapsed,
        link: item.link,
        items: initSidebarByConfig(
          {
            link: item.link || '',
            text: item.text || '',
            sidebar: item.items,
            dir: normalizePath(path.join(dir, item.dir || '')),
          },
          pages,
        ),
      }
    }
  })
}

function findNotePage(
  sidebar: string,
  dir: string,
  notePageList: NotePage[],
): NotePage | undefined {
  if (sidebar === '' || sidebar === 'README.md' || sidebar === 'index.md') {
    return notePageList.find((page) => {
      const relative = page.relativePath
      return (
        relative === normalizePath(path.join(dir, 'README.md'))
        || relative === normalizePath(path.join(dir, 'index.md'))
      )
    })
  }
  else {
    return notePageList.find((page) => {
      const relative = page.relativePath
      return (
        relative === normalizePath(path.join(dir, sidebar))
        || relative === normalizePath(path.join(dir, `${sidebar}.md`))
        || page.link === sidebar
      )
    })
  }
}
