// Creates /augment-codec/augment-types.ts file with api augmentation that allows
// creating custom Joystream "Codec types" with api.createType

import common from '../common'
import members from '../members'
import council from '../council'
import roles from '../roles'
import forum from '../forum'
import stake from '../stake'
import mint from '../mint'
import recurringRewards from '../recurring-rewards'
import hiring from '../hiring'
import versionedStore from '../versioned-store'
import versionedStorePermissions from '../versioned-store/permissions'
import contentWorkingGroup from '../content-working-group'
import workingGroup from '../working-group'
import discovery from '../discovery'
import media from '../media'
import proposals from '../proposals'
import fs from 'fs'
import path from 'path'
import * as defaultDefinitions from '@polkadot/types/interfaces/definitions'
import { generateInterfaceTypes } from '@polkadot/typegen/generate/interfaceRegistry'

const OUTPUT_PATH = path.join(__dirname, '../../augment-codec/augment-types.ts')
const IMPORTS_DIR = '..'

const typesByModule = {
  'common': common,
  'members': members,
  'council': council,
  'roles': roles,
  'forum': forum,
  'stake': stake,
  'mint': mint,
  'recurring-rewards': recurringRewards,
  'hiring': hiring,
  'versioned-store': versionedStore,
  'versioned-store/permissions': versionedStorePermissions,
  'content-working-group': contentWorkingGroup,
  'working-group': workingGroup,
  'discovery': discovery,
  'media': media,
  'proposals': proposals,
}

type Imports = { [moduleName: string]: string[] }
type AugmentTypes = { [typeName: string]: string }

const imports: Imports = {}
const augmentTypes: AugmentTypes = {}

const CUSTOM_IMPORTS_TAG = 'CUSTOMIMPORTS'
const CUSTOM_TYPES_TAG = 'CUSTOMTYPES'

const populateFileByTemplateTag = (fileContent: string, tag: string, insertLines: string[]) => {
  const fileLines = fileContent.split('\n')
  const startIndex = fileLines.findIndex((line) => line.includes(`/** ${tag} **/`))
  const endIndex = fileLines.findIndex((line) => line.includes(`/** /${tag} **/`))

  if (startIndex === -1 || endIndex === -1 || endIndex <= startIndex) {
    throw new Error(`populateFileByTemplateTag: Invalid tag (${tag})`)
  }

  const [whitespace] = fileLines[startIndex].match(/^(\s)+/) || ['']
  fileLines.splice(startIndex + 1, endIndex - (startIndex + 1), ...insertLines.map((line) => `${whitespace}${line}`))

  return fileLines.join('\n')
}

const addTagsIfDontExist = (fileContent: string): string => {
  const fileLines = fileContent.split('\n')
  // Custom imports
  if (fileLines.findIndex((line) => line.includes(`/** ${CUSTOM_IMPORTS_TAG} **/`)) === -1) {
    const firstImportIndex = fileLines.findIndex((line) => line.includes('import'))
    fileLines.splice(firstImportIndex, 0, `/** ${CUSTOM_IMPORTS_TAG} **/`, `/** /${CUSTOM_IMPORTS_TAG} **/`)
  }
  // Custom types
  if (fileLines.findIndex((line) => line.includes(`/** ${CUSTOM_TYPES_TAG} **/`)) === -1) {
    const firstTypeIndex = fileLines.findIndex((line) => line.includes('export interface InterfaceTypes')) + 1
    const [whitespace] = fileLines[firstTypeIndex].match(/^(\s)+/) || ['']
    fileLines.splice(
      firstTypeIndex,
      0,
      `${whitespace}/** ${CUSTOM_TYPES_TAG} **/`,
      `${whitespace}/** /${CUSTOM_TYPES_TAG} **/`
    )
  }

  return fileLines.join('\n')
}

const updateAugmentTypesFile = (filePath: string, imports: Imports, augmentTypes: AugmentTypes) => {
  let fileContent = fs.readFileSync(filePath).toString()
  fileContent = addTagsIfDontExist(fileContent)
  fileContent = populateFileByTemplateTag(
    fileContent,
    CUSTOM_IMPORTS_TAG,
    Object.entries(imports).map(
      ([moduleName, importStatements]) =>
        // import as to avoid namespace clashes
        `import { ${importStatements.join(', ')} } from '${IMPORTS_DIR}/${moduleName}'`
    )
  )
  fileContent = populateFileByTemplateTag(
    fileContent,
    CUSTOM_TYPES_TAG,
    Object.entries(augmentTypes).map(([typeName, constructorName]) => `"${typeName}": ${constructorName};`)
  )

  fs.writeFileSync(filePath, fileContent)
}

const addAugmentTypes = (typeName: string, constructorName: string) => {
  augmentTypes[typeName] = constructorName
  augmentTypes[`Option<${typeName}>`] = `Option<${constructorName}>`
  augmentTypes[`Vec<${typeName}>`] = `Vec<${constructorName}>`
}

console.log('Generating default interface types based on current @polkadot/types definitions...')
generateInterfaceTypes({ '@polkadot/types/interfaces': defaultDefinitions }, OUTPUT_PATH)

console.log('Adding custom Joystream types...')
Object.entries(typesByModule).forEach(([moduleName, types]) => {
  console.log('Module: ', moduleName)
  console.log('Types found:', Object.keys(types))
  Object.entries(types).forEach(([typeName, codecOrName]) => {
    if (typeof codecOrName === 'function') {
      const constructorName = codecOrName.name
      if (!constructorName) {
        throw new Error(`Codec constructor doesn't have a name: ${typeName}`)
      }
      const normalizedTypeName = typeName.replace(/[^A-Za-z0-9_]/g, '_')
      // Add "as" to avoid namespace clashes
      const importStatement = `${constructorName} as ${normalizedTypeName}`
      !imports[moduleName] ? (imports[moduleName] = [importStatement]) : imports[moduleName].push(importStatement)
      addAugmentTypes(typeName, normalizedTypeName)
    } else if (typeof codecOrName === 'string') {
      addAugmentTypes(typeName, codecOrName)
    }
  })
})

updateAugmentTypesFile(OUTPUT_PATH, imports, augmentTypes)
