import * as path from 'path'
import chokidar from 'chokidar'
import fastglob from 'fast-glob'
import { State, Params } from '../lib/DataServer'
import { get, propEq } from 'lodash/fp'

import * as paths from '../config/paths'
import { Config } from '../config/argv'
import { docgen, unixPath } from '../utils/docgen'
import { WATCH_IGNORE } from './config'

export const getPattern = (config: Config) => {
  const {
    ignore,
    src: source,
    typescript: ts,
    docgenConfig: docgenConfig,
  } = config

  if (docgenConfig.searchPatterns) {
    return docgenConfig.searchPatterns
  }

  const searchPath = docgenConfig.searchPath ? docgenConfig.searchPath : source
  const root = paths.getRootDir(config)
  const srcDir = path.resolve(root, searchPath)
  const src = path.relative(root, srcDir)
  const filesPattern = path.join(
    src,
    ts ? '**/*.{ts,tsx,js,jsx,mjs}' : '**/*.{js,jsx,mjs}'
  )

  return ignore
    .map(entry => typeof entry === 'string' && `!**/${entry}`)
    .filter(Boolean)
    .concat([
      unixPath(filesPattern),
      '!**/node_modules',
      '!**/doczrc.js',
    ]) as string[]
}

const removeFilepath = (items: any[], filepath: string) =>
  items.filter((item: any) => item.key !== filepath)

export const initial = (config: Config, pattern: string[]) => async (
  p: Params
) => {
  const { filterComponents } = config
  const cwd = paths.getRootDir(config)
  const files = await fastglob(pattern, { cwd, caseSensitiveMatch: false })
  const filtered = filterComponents ? filterComponents(files) : files
  const metadata = await docgen(filtered, config)
  p.setState('props', metadata)
}

const change = (p: Params, config: Config) => async (filepath: string) => {
  const prev = get('props', p.getState())
  const metadata = await docgen([filepath], config)
  const filtered = metadata.filter(propEq('key', filepath))
  const next = removeFilepath(prev, filepath).concat(filtered)
  p.setState('props', next)
}

const remove = (p: Params) => async (filepath: string) => {
  const prev = get('props', p.getState())
  const next = removeFilepath(prev, filepath)
  p.setState('props', next)
}

export const state = (config: Config, dev?: boolean): State => {
  const pattern = getPattern(config)
  const ignored = config.watchIgnore || WATCH_IGNORE
  const cwd = paths.getRootDir(config)
  const watcher = chokidar.watch(pattern, {
    cwd,
    ignored,
    persistent: true,
  })

  watcher.setMaxListeners(Infinity)

  return {
    id: 'props',
    start: async params => {
      const addInitial = initial(config, pattern)
      await addInitial(params)

      if (dev) {
        watcher.on('change', change(params, config))
        watcher.on('unlink', remove(params))
      }
    },
    close: () => {
      watcher.close()
    },
  }
}
