import {
  type Expression,
  type Node,
  type ObjectLiteralExpression,
  Project,
  type SourceFile,
  ts,
} from "ts-morph"

export function codemod(paths: string) {
  const project = new Project()
  project.addSourceFilesAtPaths(paths)

  project.getSourceFiles().forEach((file) => {
    processSourceFile(file)
    file.saveSync()
  })
}

export function processSourceFile(file: SourceFile): void {
  file.getDescendantsOfKind(ts.SyntaxKind.CallExpression).forEach((call) => {
    const expression = call.getExpression().getText()

    if (expression === "intl.formatMessage") {
      const formatMessageArgs = call.getArguments()

      if (formatMessageArgs.length > 0) {
        const firstFormatMessageArg = formatMessageArgs[0]

        // Handle object literal in the first argument
        if (
          firstFormatMessageArg.getKind() ===
          ts.SyntaxKind.ObjectLiteralExpression
        ) {
          const expressionVariables = getVariableArguments(
            firstFormatMessageArg.asKindOrThrow(
              ts.SyntaxKind.ObjectLiteralExpression
            )
          )
          if (expressionVariables === "{}") {
            // No variables
            transformObjectLiteral(
              firstFormatMessageArg.asKindOrThrow(
                ts.SyntaxKind.ObjectLiteralExpression
              )
            )
          } else {
            // Handle variables
            const expressionReplacement = `intl.formatMessage(${transformObjectLiteralAndReturn(
              firstFormatMessageArg.asKindOrThrow(
                ts.SyntaxKind.ObjectLiteralExpression
              )
            )}, ${expressionVariables})`

            call.replaceWithText(expressionReplacement)
          }
        }

        // Handle ternary expressions in the first argument
        else if (
          firstFormatMessageArg.getKind() ===
          ts.SyntaxKind.ConditionalExpression
        ) {
          const conditional = firstFormatMessageArg.asKindOrThrow(
            ts.SyntaxKind.ConditionalExpression
          )

          const whenTrue = conditional.getWhenTrue()
          const whenFalse = conditional.getWhenFalse()

          // Check for variables in message
          const varArgsWhenTrue = getVariableArguments(whenTrue)
          const varArgsWhenFalse = getVariableArguments(whenFalse)

          // Replacements
          const whenTrueReplacement =
            varArgsWhenTrue !== "{}"
              ? `intl.formatMessage(${transformObjectLiteralAndReturn(whenTrue)}, ${varArgsWhenTrue})`
              : `intl.formatMessage(${transformObjectLiteralAndReturn(whenTrue)})`

          const whenFalseReplacement =
            varArgsWhenFalse !== "{}"
              ? `intl.formatMessage(${transformObjectLiteralAndReturn(whenFalse)}, ${varArgsWhenFalse})`
              : `intl.formatMessage(${transformObjectLiteralAndReturn(whenFalse)})`

          // Replace the ternary expression
          call.replaceWithText(
            `${conditional.getCondition().getText()} ? ${whenTrueReplacement} : ${whenFalseReplacement}`
          )
        }
      }
    }
  })

  // Format the file using its existing formatting rules
  file.formatText({
    indentSize: 2,
  })
}

// Helper function to transform object literals
function transformObjectLiteral(objectLiteral: ObjectLiteralExpression): void {
  const idProperty = objectLiteral
    .getProperties()
    .find((prop) => {
      const p = prop.asKindOrThrow(ts.SyntaxKind.PropertyAssignment)
      return p.getName() === "id"
    })
    ?.asKindOrThrow(ts.SyntaxKind.PropertyAssignment)

  if (idProperty) {
    const idValue = idProperty.getInitializer()?.getText()

    if (idValue) {
      // Add defaultMessage
      if (
        !objectLiteral
          .getProperties()
          .some(
            (prop) => "getName" in prop && prop.getName() === "defaultMessage"
          )
      ) {
        objectLiteral.addPropertyAssignment({
          name: "defaultMessage",
          initializer: idValue,
        })
      }

      // Remove the id property
      idProperty.remove()
    }
  }

  // Add description if not present
  // if (
  //   !objectLiteral
  //     .getProperties()
  //     .some((prop) => "getName" in prop && prop.getName() === "description")
  // ) {
  //   objectLiteral.addPropertyAssignment({
  //     name: "description",
  //     initializer: `{}`,
  //   })
  // }

  // Extract variables from the defaultMessage if present
  const defaultMessageProp = objectLiteral
    .getProperties()
    .find((prop) => "getName" in prop && prop.getName() === "defaultMessage")
    ?.asKindOrThrow(ts.SyntaxKind.PropertyAssignment)

  if (defaultMessageProp) {
    const defaultMessageValue = defaultMessageProp.getInitializer()?.getText()

    if (defaultMessageValue) {
      const extractedVariables =
        extractVariablesFromTemplateString(defaultMessageValue)
      if (extractedVariables.length > 0) {
        // Replace the variables in the defaultMessage with FormatJS placeholders
        const transformedMessage = replaceWithFormatJSPlaceholders(
          defaultMessageValue,
          extractedVariables
        )
        defaultMessageProp.setInitializer(`${transformedMessage}`)
      }
    }
  }
}

// Helper function to extract variables from a template string
function extractVariablesFromTemplateString(templateString: string): string[] {
  const regex = /\${(.*?)}/g
  const variables: string[] = []
  let match

  // Find all variable references in the template literal
  while ((match = regex.exec(templateString)) !== null) {
    variables.push(match[1].trim())
  }

  return variables
}

// Helper function to replace variables with FormatJS placeholders
function replaceWithFormatJSPlaceholders(
  templateString: string,
  variables: string[]
): string {
  let transformedMessage = templateString

  variables.forEach((variable) => {
    transformedMessage = transformedMessage.replace(
      `\${${variable}}`,
      `{${variable}}`
    )
  })

  return transformedMessage
}

// Helper function to get the variables from the ternary branch
function getVariableArguments(exp: Expression): string {
  const idProp = exp
    .asKindOrThrow(ts.SyntaxKind.ObjectLiteralExpression)
    .getProperties()
    .find((prop) => "getName" in prop && prop.getName() === "id")
    ?.asKindOrThrow(ts.SyntaxKind.PropertyAssignment)

  if (idProp) {
    const extractedVariables = extractVariablesFromTemplateString(
      idProp.getText()
    )
    if (extractedVariables.length > 0) {
      return `{ ${extractedVariables.map((v) => `${v}: ${v}`).join(", ")} }`
    }
  }
  return "{}" // Return empty if no variables found
}

// Helper function to transform object literals and return text
function transformObjectLiteralAndReturn(object: Node): string {
  if (object.getKind() === ts.SyntaxKind.ObjectLiteralExpression) {
    const obj = object.asKindOrThrow(ts.SyntaxKind.ObjectLiteralExpression)
    transformObjectLiteral(obj)
    return obj.getText() // Return the transformed object literal as text
  }
  return object.getText()
}
