// @ts-check

import { platform } from 'os'
import { readFileSync } from 'fs'
import * as path from 'path'

import { createFilter } from '@rollup/pluginutils'
import { compile, parse, defaultConfig as etaConfig } from 'eta'

const isWindows = platform() === 'win32'
const pathSeparators =  isWindows ? '\\' : '/'

const INTERNAL_CONFIG_MODULE_NAME = '@@@eta-config'
const INTERNAL_CONFIG_SOURCE = readFileSync(require.resolve('./rollup-plugins/eta/internal-config'), 'utf-8')

/**
 * @typedef { import('@rollup/pluginutils').FilterPattern } IFilterPattern
 */

/**
 * @typedef { { include?: IFilterPattern, exclude?: IFilterPattern, templatesDir: string } } IOptions
 */

/**
 * @param { IOptions } options
 * @return { import('rollup').Plugin }
 */
export default function etaPlugin (options) {
  const templatesDir = options.templatesDir.endsWith(pathSeparators) ?
    options.templatesDir :
    `${options.templatesDir}${pathSeparators}`

  const defaultInclude = '**.eta'
  const filter = createFilter( options.include ?? defaultInclude, options.exclude )


  /**
   * @type { Set<string> }
   */
  const registeredPartials = new Set()

  return {
    name: 'eta',
    resolveId(source) {
      if (source === INTERNAL_CONFIG_MODULE_NAME) {
        return source
      }

      return null
    },
    load(id) {
      if (id === INTERNAL_CONFIG_MODULE_NAME) {
        return INTERNAL_CONFIG_SOURCE
      }

      return null
    },
    transform (code, id) {
      if (!filter(id)) {
        return undefined
      } else {
        if (!id.startsWith(templatesDir)) {
          throw new Error('can not use template outside `templatesDir`')
        }

        const ast = parse(code, etaConfig)

        const templateFn = compile(code, etaConfig)
          .toString()
          .replace('function anonymous', 'export function template')

        const partials = Array.from(new Set(
          ast
            .map(node => {
              if (typeof node === 'string') {
                return null
              }

              if (node.t === 'r') {
                let matches = node.val.match(/E\.include\('(.*)'\)/)

                if (matches != null && matches[1] != null && matches[1].length > 0) {
                  return matches[1]
                }
              } else if (node.t === 'e') {
                // check if is calling `layout` method
                const matches = node.val.match(/^layout\(('(.+)'|"(.+)")(,.+)?\)$/)
                if (matches != null) {
                  return matches[2]
                }
              }

              return null
            })
            .filter(partialName => partialName != null)
        ))
        const unregisteredPartials = partials.filter(partialName => {
          if (registeredPartials.has(partialName)) {
            return false
          } else {
            registeredPartials.add(partialName)
            return true
          }
        })

        const partialImportStatements = unregisteredPartials
          .map((partialName, index) => {
            const importPath = isWindows ? path.resolve(templatesDir, partialName).replace(/\\/g, '\\\\') : path.resolve(templatesDir, partialName)
            return `import { template as partialTemplate$${index} } from '${importPath}'`
          })
          .join('\n')

        const partialDefineStatements = unregisteredPartials
          .map((partialName, index) => `config.templates.define('${partialName}', partialTemplate$${index})`)
          .join('\n')

        const source =
`import { config } from '${INTERNAL_CONFIG_MODULE_NAME}'
${partialImportStatements}

${partialDefineStatements}

${templateFn}

export default function templateFunction (data) {
  return template(data, config)
}`

        return source
      }
    }
  }
}