import { createAst } from '@iteria-app/generator'
import {
  appendElementsToAst,
  removeElementFromAst,
} from '@iteria-app/generator'
import ts, { ScriptKind, ScriptTarget, SourceFile } from 'typescript'

export interface SourceLineCol {
  fileName: string
  lineNumber: number
  columnNumber: number
}

export const codeStart = (code: string, source: SourceLineCol) => {
  const identifierStart = startOfJsxIdentifier(code, source)
  if (!identifierStart) return null
  const identifierNode = astFindStart(code, identifierStart)
  if (!identifierNode) return null
  // return pos of parent if element is self closing element, otherwise return position of grandparent
  if (ts.isJsxSelfClosingElement(identifierNode.parent))
    return identifierNode.parent.pos
  if (ts.isJsxOpeningElement(identifierNode.parent))
    return identifierNode.parent.parent.pos
}

export const cloneElementInAst = (code: string, source: SourceLineCol) => {
  const ast = createAst(code)
  const node = astFindSource(code, source)
  if (!node || !ast) return null
  // TODO this should return only when the component is root component of map function(it has a key)

  // If node is parenthesized expression, strip parentheses so cloned node won't be wrapped in them
  const finalNode = ts.isParenthesizedExpression(node) ? node.expression : node

  const alteredAst = appendElementsToAst(
    ast,
    { pos: finalNode.pos, end: finalNode.end },
    [finalNode]
  )
  return alteredAst
}

export const cloneAndRenameElementInAst = (
  code: string,
  source: SourceLineCol,
  newName: string
) => {
  const ast = createAst(code)
  const node = astFindSource(code, source)
  if (!node || !ast) return null

  const finalNode = ts.isParenthesizedExpression(node) ? node.expression : node

  const nodeString = code.substring(finalNode.pos, finalNode.end)
  const registerStart = nodeString.indexOf('register("')
  const registerEnd = nodeString.indexOf('")}')

  let finalString =
    nodeString.substring(0, registerStart) +
    `register("${newName}` +
    nodeString.substring(registerEnd)

  const fieldNameStart = finalString.indexOf('fieldName')
  const fieldNameEnd =
    finalString.substring(fieldNameStart).indexOf('/>') + fieldNameStart

  finalString =
    finalString.substring(0, fieldNameStart) +
    `fieldName="${newName}"` +
    finalString.substring(fieldNameEnd)

  const alteredAst = createAst(
    code.substring(0, finalNode.end) +
      finalString +
      code.substring(finalNode.end)
  )

  return alteredAst
}

export function astFindSource(code: string, source: SourceLineCol) {
  const start = codeStart(code, source)
  if (start) {
    const found = astFindStart(code, start)
    return found
    //sourceLines.splice(lineIndex, 0, sourceLine);
    //const newContent = sourceLines.join('\n');
    //return newContent;
  }

  return null
}

export function startOfJsxIdentifier(code: string, source: SourceLineCol) {
  const sourceLines = code.split('\n')
  if (source.lineNumber > 0 && source.lineNumber < sourceLines.length) {
    const lineIndex = source.lineNumber - 1
    let start = 0
    for (let i = 0; i < lineIndex; ++i) {
      const line = sourceLines[i]
      start += line.length + 1
    }
    start += source.columnNumber - 1
    // Here i intentionaly add 1 to the start variable to move from start of JSXElement
    // to start of StringLiteral, because of discovered bug in typescript AST
    start += 1

    return start
  }

  return null
}

export function astFindStart(code: string, start: number) {
  const ast = createAst(code)
  const callback = (node: ts.Node) => {
    const nodeStart = node.pos
    if (nodeStart <= start && start <= node.end) {
      if (nodeStart === start) {
        // node.kind == ts.SyntaxKind.JsxElement
        return node
      }
      return ts.forEachChild<ts.Node>(node, callback)
    }

    return null
  }
  if (ast) {
    const found = ts.forEachChild(ast, callback)
    return found
  }

  return null
}

export const removeElementInAst = (code: string, source: SourceLineCol) => {
  const ast = createAst(code)
  const { pos, end } = astFindSource(code, source)
  if (!pos || !end || !ast) return null

  const alteredAst = removeElementFromAst(ast, { pos, end })
  return alteredAst
}

export interface InspectedElement {
  displayName: string
  props: { [key: string]: any }
}

export interface ChangedMessage {
  messageId: string
  newMessageValue: string
}

export function parseLocaleJSON(
  localeSourceCode: string,
  languageLocale = 'en'
) {
  const localeAst = createAst(
    localeSourceCode,
    ScriptTarget.ESNext,
    ScriptKind.JSON
  )
  if (localeAst) {
    return parseLocaleAST(localeAst, languageLocale)
  }
}

export function parseLocaleAST(ast: SourceFile, languageLocale = 'en') {
  let localeMessages: Message[] = []
  ast?.forEachChild((child: any) => {
    child?.expression?.properties?.forEach((property: any) => {
      let locale = {
        id: property.name.text,
        value: property.initializer?.text,
        locale: languageLocale,
        position: {
          pos: property.initializer?.pos,
          end: property.initializer?.end,
        },
      }
      localeMessages = [...localeMessages, locale]
    })
  })
  return localeMessages
}

export interface Message {
  id: string
  value: string
  locale: string
  position: CharRange
}

export interface CharRange {
  pos: number
  end: number
}
