import Console from "./console"

const frontMatter = require("front-matter")
import * as MarkdownIt from "markdown-it"
import { syllable } from "syllable"

import * as path from "path"
import * as fs from "fs"
import { homedir } from "os"
import { join } from "path"
import { exec } from "child_process"
import { promisify } from "util"

import * as yaml from "js-yaml"
import * as prompts from "prompts"

type TEstimateReadingTimeReturns = {
  minutes: number
  words: number
}

export const estimateReadingTime = (
  text: string,
  wordsPerMinute = 150
): TEstimateReadingTimeReturns => {
  const words = text.trim().split(/\s+/).length
  const minutes = words / wordsPerMinute

  if (minutes < 1) {
    if (words === 0)
      return {
        minutes: 1,
        words,
      }
  } else {
    return {
      minutes,
      words,
    }
  }

  return {
    minutes: 1,
    words,
  }
}

export type PackageInfo = {
  grading: string
  difficulty: string
  duration: number
  description: {
    us: string
  }
  title: {
    us: string
  }
}

type TFKGLResult = {
  text: string
  fkgl: number
}

export function checkReadability(
  markdown: string,
  wordsPerMinute = 200,
  maxMinutes = 1
): {
  newMarkdown: string
  exceedsThreshold: boolean
  minutes: number
  body: string
  fkglResult: TFKGLResult
  // readingEase: number
} {
  const parsed = frontMatter(markdown)

  const fkglResult = fleschKincaidGrade(parsed.body)

  const readingTime = estimateReadingTime(parsed.body, wordsPerMinute)

  // const readingEase = estimateReadingEase(parsed.body)
  let attributes = parsed.attributes ? parsed.attributes : {}

  if (typeof parsed.attributes !== "object") {
    attributes = {}
  }

  const updatedAttributes = {
    ...attributes,
    readingTime,
    fkglResult: fkglResult.fkgl,
  }

  let yamlFrontMatter = ""
  try {
    yamlFrontMatter = yaml.dump(updatedAttributes).trim()
  } catch {
    Console.error("Error dumping YAML front matter")
    return {
      newMarkdown: "",
      exceedsThreshold: false,
      minutes: 0,
      body: "",
      fkglResult,
      // readingEase: 0,
    }
  }

  const newMarkdown = `---\n${yamlFrontMatter}\n---\n\n${parsed.body}`

  return {
    newMarkdown,
    exceedsThreshold: readingTime.minutes > maxMinutes,
    minutes: readingTime.minutes,
    body: parsed.body,
    fkglResult,
  }
}

export const slugify = (text: string) => {
  return text
    .toString()
    .normalize("NFD") // Remove accents
    .replace(/[\u0300-\u036F]/g, "") // Remove diacritics
    .toLowerCase()
    .trim()
    .replace(/[^\d\s._a-z-]/g, "") // Hyphen at the end, no escape needed
    .replace(/\s+/g, "-") // Replace spaces with hyphens
    .replace(/-+/g, "-") // Remove duplicate hyphens
    .replace(/^-+|-+$/g, "") // Trim hyphens from start/end
}

export const getExInfo = (title: string) => {
  // Example title: '1.0 - Introduction to AI [READ: Small introduction to important concepts such as AI, machine learning, and their applications]'
  let [exNumber, exTitle] = title.split(" - ")

  // Extract kind and description
  const kindMatch = exTitle.match(/\[(.*?):(.*?)]/)
  const kind = kindMatch ? kindMatch[1].trim().toLowerCase() : "read"
  const description = kindMatch ? kindMatch[2].trim() : ""

  exNumber = exNumber.trim()
  // Clean title
  exTitle = exTitle.replace(kindMatch?.[0] || "", "").trim()
  exTitle = slugify(exTitle)

  return {
    exNumber,
    kind,
    description,
    exTitle,
  }
}

export function extractImagesFromMarkdown(markdown: string) {
  const imageRegex = /!\[([^\]]*)]\(([^)]+)\)/g
  const images = []
  let match

  while ((match = imageRegex.exec(markdown)) !== null) {
    const altText = match[1]
    const url = match[2]
    images.push({ alt: altText, url: url })
  }

  return images
}

export function getFilenameFromUrl(url: string) {
  return path.basename(url)
}

export const makePackageInfo = (choices: any) => {
  const packageInfo = {
    grading: choices.grading,
    difficulty: "beginner",
    duration: 5,
    description: {
      us: choices.description,
    },
    title: {
      us: choices.title,
    },
    slug: choices.title
      .toLowerCase()
      .replace(/ /g, "-")
      .replace(/[^\w-]+/g, ""),
    telemetry: {
      batch: "https://breathecode.herokuapp.com/v1/assignment/me/telemetry",
    },
  }
  return packageInfo
}

export function estimateDuration(listOfSteps: string[]): number {
  let duration = 0

  for (const step of listOfSteps) {
    if (step.includes("[READ:")) {
      duration += 1
    } else if (step.includes("[QUIZ:")) {
      duration += 2
    } else if (step.includes("[CODE:")) {
      duration += 3
    }
  }

  return duration
}

const writeFilePromise = promisify(fs.writeFile)
const execPromise = promisify(exec)

export async function createFileOnDesktop(fileName: string, content: string) {
  try {
    const desktopPath = join(homedir(), "Desktop")
    const filePath = join(desktopPath, fileName)

    await writeFilePromise(filePath, content)
    console.log(`File created successfully at: ${filePath}`)

    await openFile(filePath)
  } catch (error) {
    console.error("Error:", error)
  }
}

async function openFile(filePath: string) {
  const platform = process.platform
  let command

  if (platform === "win32") {
    command = `start "" "${filePath}"`
  } else if (platform === "darwin") {
    command = `open "${filePath}"`
  } else {
    command = `xdg-open "${filePath}"`
  }

  try {
    await execPromise(command)
    console.log("File opened successfully.")
  } catch (error) {
    console.error("Error opening the file:", error)
  }
}

export function getDesktopFile(fileName: string) {
  const desktopPath = join(homedir(), "Desktop")
  const filePath = join(desktopPath, fileName)

  const content = fs.readFileSync(filePath, "utf8")
  // Delete the file after reading it
  // fs.unlinkSync(filePath)
  return content
}

// export function fleschKincaidReadingEase(text: string): number {
//   const sentences = text.split(/[.!?]/).filter((s) => s.trim().length > 0)
//   const words = text.split(/\s+/).filter((w) => w.trim().length > 0)
//   const totalSyllables = words.reduce((sum, word) => sum + syllable(word), 0)

//   const ASL = words.length / sentences.length // Average Sentence Length
//   const ASW = totalSyllables / words.length // Average Syllables per Word

//   return Math.round(206.835 - 1.015 * ASL - 84.6 * ASW)
// }

export function extractTextFromMarkdown(mdContent: string): string {
  return mdContent
    .replace(/!\[.*?]\(.*?\)/g, "") // Remove images
    .replace(/\[.*?]\(.*?\)/g, "") // Remove links
    .replace(/(```[\S\s]*?```|`.*?`)/g, "") // Remove inline & block code
    .replace(/^#.*$/gm, "") // Remove headings
    .replace(/(\*{1,2}|_{1,2})/g, "") // Remove bold/italic markers
    .replace(/>\s?/g, "") // Remove blockquotes
    .replace(/[*-]\s+/g, "") // Remove bullets from lists
    .trim()
}

const cleanReadme = (readme: string) => {
  // Replace <text> and </text> with nothing
  return readme.replace(/<text>/g, "").replace(/<\/text>/g, "")
}

export const saveTranslatedReadme = async (
  exercise: string,
  languageCode: string,
  readme: string
) => {
  const readmePath = path.join(
    process.cwd(),
    "exercises",
    exercise,
    `README.${languageCode}.md`
  )
  fs.writeFileSync(readmePath, cleanReadme(readme))
}

/**
 * Extracts paragraphs from a Markdown string.
 * @param markdownText The input Markdown string.
 * @returns An array of paragraph contents.
 */
export function extractParagraphs(markdownText: string): string[] {
  const md = new MarkdownIt()
  const tokens = md.parse(markdownText, {})
  const paragraphs: string[] = []

  for (let i = 0; i < tokens.length; i++) {
    if (
      tokens[i].type === "paragraph_open" &&
      tokens[i + 1]?.type === "inline"
    ) {
      paragraphs.push(tokens[i + 1].content)
    }
  }

  return paragraphs
}

/**
 * Splits a paragraph into words and separates each word into syllables.
 * @param paragraph The input paragraph.
 * @returns An array of words, each split into syllables.
 */
export function splitIntoSyllables(paragraph: string): string[] {
  const words = paragraph.split(/\s+/)
  const syllables: string[] = []

  for (const word of words) {
    syllables.push(...splitWordIntoSyllables(word))
  }

  return syllables
}

/**
 * Splits a word into its syllables using a basic estimation.
 * @param word The word to split.
 * @returns An array of syllables.
 */
export function splitWordIntoSyllables(word: string): string[] {
  const syllableCount = syllable(word)

  // Simple heuristic: Split word into equal parts (not perfect, better with a dictionary-based approach)
  if (syllableCount <= 1) return [word]

  const approxLength = Math.ceil(word.length / syllableCount)
  const syllables: string[] = []
  for (let i = 0; i < word.length; i += approxLength) {
    // eslint-disable-next-line
    syllables.push(word.substring(i, i + approxLength))
  }

  return syllables
}

/**
 * Extracts words from a given paragraph.
 * @param paragraph The input text.
 * @returns An array of words.
 */
export function extractWords(paragraph: string): string[] {
  const words = paragraph.match(/\b\w+\b/g) // Match words using regex
  return words ? words : [] // Return words or an empty array if none found
}

/**
 * Calculates the Flesch-Kincaid Grade Level (FKGL) for a given text.
 * @param text The input paragraph.
 * @returns The FKGL score.
 */
export function fleschKincaidGrade(text: string): TFKGLResult {
  const processableText = extractTextFromMarkdown(text)
  const words = extractWords(processableText)
  const numWords = words.length
  const numSentences = countSentences(processableText)
  const numSyllables = words.reduce((total, word) => total + syllable(word), 0)

  if (numWords === 0 || numSentences === 0) {
    return {
      text,
      fkgl: 0,
    }
  }

  const fkgl =
    // eslint-disable-next-line
    0.39 * (numWords / numSentences) + 11.8 * (numSyllables / numWords) - 15.59

  return {
    text,
    fkgl: parseFloat(fkgl.toFixed(2)),
  }
}

/**
 * Counts the number of sentences in a given text.
 * @param text The input paragraph.
 * @returns The total number of sentences.
 */
export function countSentences(text: string): number {
  const sentences = text
    .split(/[!.?]+/)
    .filter(sentence => sentence.trim().length > 0)
  return sentences.length
}

export function howManyDifficultParagraphs(
  paragraphs: TFKGLResult[],
  maxFKGL: number
): number {
  return paragraphs.filter(paragraph => paragraph.fkgl > maxFKGL).length
}

const example_content = `Write or paste your table of content below this line, each topic should be defined on a new line, here is an example:

Introduction to AI: Explain what is AI and its applications
Introduction to Machine Learning: Explain what is machine learning and its applications
What is an AI Model: Explain what is an AI model and its applications
How to use an AI Model: Different APIs, local models, etc.
How to build an AI Model: Fine-tuning, data collection, cleaning and more.
`

export const appendContentIndex = async () => {
  const choices = await prompts([
    {
      type: "confirm",
      name: "contentIndex",
      message: "Do you have a content index for this tutorial?",
    },
  ])
  if (choices.contentIndex) {
    await createFileOnDesktop("content_index.txt", example_content)
    Console.info(
      "Please make the necessary in the recently created file in your desktop, it should automatically open. Edit the file to match your expectations and save it. Keep the same name and structure as the example file. Continue when ready."
    )
    const isReady = await prompts([
      {
        type: "confirm",
        name: "isReady",
        message: "Are you ready to continue?",
      },
    ])
    if (!isReady.isReady) {
      Console.error("Please make the necessary changes and try again.")
      process.exit(1)
    }

    const contentIndex = getDesktopFile("content_index.txt")
    return contentIndex
  }

  return null
}

const example_airules = `
Write with an engaging tone, use simple words and avoid complex sentences.
Write in first person, as if you are talking to the reader.
Add mental maps to help the reader understand the content.
Add diagrams to help the reader understand the content.
No code exercises required

`

export const appendAIRules = async () => {
  const choices = await prompts([
    {
      type: "confirm",
      name: "airules",
      message:
        "Do you want to add any specific rules for the AI? (e.g. no code exercises, no quizzes, etc, a particular writting style, etc)",
    },
  ])
  if (choices.airules) {
    await createFileOnDesktop("airules.txt", example_airules)
    Console.info(
      "Please make the necessary in the recently created file in your desktop, it should automatically open. Edit the file to match your expectations and save it. Keep the same name and structure as the example file. Continue when ready."
    )
    const isReady = await prompts([
      {
        type: "confirm",
        name: "isReady",
        message: "Are you ready to continue?",
      },
    ])
    if (!isReady.isReady) {
      Console.error("Please make the necessary changes and try again.")
      process.exit(1)
    }

    const airules = getDesktopFile("airules.txt")
    return airules
  }

  return null
}
