import { getUriFileExtension } from "../../util/uri"
import { BracketMatchingService } from "../filtering/BracketMatchingService"

type LineStream = AsyncGenerator<string>
type LineFilter = (args: { lines: LineStream; fullStop: () => void }) => LineStream
type CharacterFilter = (args: {
  chars: AsyncGenerator<string>
  prefix: string
  suffix: string
  filepath: string
  multiline: boolean
}) => AsyncGenerator<string>

export interface AutocompleteLanguageInfo {
  /**
   * The display name of the language
   */
  name: string
  /**
   * Keywords that show up at the top-level of files. We often use these as stop words or soft breaks
   */
  topLevelKeywords: string[]
  /**
   * The character that prefixes a single line comment
   * We use this to format snippets from other files as large comment blocks
   */
  singleLineComment?: string
  /**
   * Semi-colon or other end of line indicator that may be used as a stop char
   * or other parsing
   */
  endOfLine: string[]
  /**
   * Strings that indicate we should filter out a line from completion
   * Primarily currently used for .ipynb files
   */
  lineFilters?: LineFilter[]
  /**
   * Similar to line filters, but characters. I think.
   */
  charFilters?: CharacterFilter[]
  /**
   * Function that allows cusotmization of whether to use a multi-line completion on a per-language and completion basis
   */
  useMultiline?: (args: { prefix: string; suffix: string }) => boolean
}

// TypeScript
export const Typescript = {
  name: "TypeScript",
  topLevelKeywords: ["function", "class", "module", "export", "import"],
  singleLineComment: "//",
  endOfLine: [";"],
}

// JavaScript
export const JavaScript = {
  name: "JavaScript",
  topLevelKeywords: ["function", "class", "module", "export", "import"],
  singleLineComment: "//",
  endOfLine: [";"],
}

// Python
export const Python = {
  name: "Python",
  // """"#" is for .ipynb files, where we add '"""' surrounding markdown blocks.
  // This stops the model from trying to complete the start of a new markdown block
  topLevelKeywords: ["def", "class", '"""#'],
  singleLineComment: "#",
  endOfLine: [],
}

// Java
export const Java = {
  name: "Java",
  topLevelKeywords: ["class", "function"],
  singleLineComment: "//",
  endOfLine: [";"],
}

// C++
export const Cpp = {
  name: "C++",
  topLevelKeywords: ["class", "namespace", "template"],
  singleLineComment: "//",
  endOfLine: [";"],
}

// C#
export const CSharp = {
  name: "C#",
  topLevelKeywords: ["class", "namespace", "void"],
  singleLineComment: "//",
  endOfLine: [";"],
}

// C
export const C = {
  name: "C",
  topLevelKeywords: ["if", "else", "while", "for", "switch", "case"],
  singleLineComment: "//",
  endOfLine: [";"],
}

// Scala
export const Scala = {
  name: "Scala",
  topLevelKeywords: ["def", "val", "var", "class", "object", "trait"],
  singleLineComment: "//",
  endOfLine: [";"],
}

// Go
export const Go = {
  name: "Go",
  topLevelKeywords: ["func", "package", "import", "type"],
  singleLineComment: "//",
  endOfLine: [],
}

// Rust
export const Rust = {
  name: "Rust",
  topLevelKeywords: ["fn", "mod", "pub", "struct", "enum", "trait"],
  singleLineComment: "//",
  endOfLine: [";"],
}

// Haskell
export const Haskell = {
  name: "Haskell",
  topLevelKeywords: ["data", "type", "newtype", "class", "instance", "let", "in", "where"],
  singleLineComment: "--",
  endOfLine: [],
}

// PHP
export const PHP = {
  name: "PHP",
  topLevelKeywords: ["function", "class", "namespace", "use"],
  singleLineComment: "//",
  endOfLine: [";"],
}

// Ruby on Rails
export const RubyOnRails = {
  name: "Ruby on Rails",
  topLevelKeywords: ["def", "class", "module"],
  singleLineComment: "#",
  endOfLine: [],
}

// Swift
export const Swift = {
  name: "Swift",
  topLevelKeywords: ["func", "class", "struct", "import"],
  singleLineComment: "//",
  endOfLine: [";"],
}

// Kotlin
export const Kotlin = {
  name: "Kotlin",
  topLevelKeywords: ["fun", "class", "package", "import"],
  singleLineComment: "//",
  endOfLine: [";"],
}

// Ruby
export const Ruby = {
  name: "Ruby",
  topLevelKeywords: ["class", "module", "def"],
  singleLineComment: "#",
  endOfLine: [],
}

// Clojure
export const Clojure = {
  name: "Clojure",
  topLevelKeywords: ["def", "fn", "let", "do", "if", "defn", "ns", "defmacro"],
  singleLineComment: ";",
  endOfLine: [],
}

// Julia
export const Julia = {
  name: "Julia",
  topLevelKeywords: ["function", "macro", "if", "else", "elseif", "while", "for", "begin", "end", "module"],
  singleLineComment: "#",
  endOfLine: [";"],
}

// F#
export const FSharp = {
  name: "F#",
  topLevelKeywords: ["let", "type", "module", "namespace", "open", "if", "then", "else", "match", "with"],
  singleLineComment: "//",
  endOfLine: [],
}

// R
export const R = {
  name: "R",
  topLevelKeywords: ["function", "if", "else", "for", "while", "repeat", "library", "require"],
  singleLineComment: "#",
  endOfLine: [],
}

// Dart
export const Dart = {
  name: "Dart",
  topLevelKeywords: ["class", "import", "void", "enum"],
  singleLineComment: "//",
  endOfLine: [";"],
}

// Solidity
export const Solidity = {
  name: "Solidity",
  topLevelKeywords: [
    "contract",
    "event",
    "modifier",
    "function",
    "constructor",
    "for",
    "require",
    "emit",
    "interface",
    "error",
    "library",
    "struct",
    "enum",
    "type",
  ],
  singleLineComment: "//",
  endOfLine: [";"],
}

// Lua
export const Lua = {
  name: "Lua",
  topLevelKeywords: ["function"],
  singleLineComment: "--",
  endOfLine: [],
}

// YAML
export const YAML: AutocompleteLanguageInfo = {
  name: "YAML",
  topLevelKeywords: [],
  singleLineComment: "#",
  endOfLine: [],
  lineFilters: [
    // Only display one list item at a time
    async function* ({ lines, fullStop }) {
      let seenListItem = false
      for await (const line of lines) {
        if (line.trim().startsWith("- ")) {
          if (seenListItem) {
            fullStop()
            break
          } else {
            seenListItem = true
          }
        }
        yield line
      }
    },
    // Don't allow consecutive lines of same key
    async function* ({ lines }) {
      let lastKey = undefined
      for await (const line of lines) {
        if (line.includes(":")) {
          const key = line.split(":")[0]
          if (key === lastKey) {
            break
          } else {
            yield line
            lastKey = key
          }
        } else {
          yield line
        }
      }
    },
  ],
}

export const Json: AutocompleteLanguageInfo = {
  name: "JSON",
  topLevelKeywords: [],
  singleLineComment: "//",
  endOfLine: [",", "}", "]"],
  charFilters: [
    function matchBrackets({ chars, prefix, suffix, filepath, multiline }) {
      const bracketMatchingService = new BracketMatchingService()
      return bracketMatchingService.stopOnUnmatchedClosingBracket(chars, prefix, suffix, filepath, multiline)
    },
  ],
}

export const Markdown: AutocompleteLanguageInfo = {
  name: "Markdown",
  topLevelKeywords: [],
  singleLineComment: "",
  endOfLine: [],
  useMultiline: ({ prefix }) => {
    const singleLineStarters = ["- ", "* ", /^\d+\. /, "> ", "```", /^#{1,6} /]
    let currentLine = prefix.split("\n").pop()
    if (!currentLine) {
      return true
    }
    currentLine = currentLine.trim()
    for (const starter of singleLineStarters) {
      if (typeof starter === "string" ? currentLine.startsWith(starter) : starter.test(currentLine)) {
        return false
      }
    }
    return true
  },
}

export const LANGUAGES: { [extension: string]: AutocompleteLanguageInfo } = {
  ts: Typescript,
  js: JavaScript,
  tsx: Typescript,
  json: Json,
  jsx: Typescript,
  ipynb: Python,
  py: Python,
  pyi: Python,
  java: Java,
  cpp: Cpp,
  cxx: Cpp,
  h: Cpp,
  hpp: Cpp,
  cs: CSharp,
  c: C,
  scala: Scala,
  sc: Scala,
  go: Go,
  rs: Rust,
  hs: Haskell,
  php: PHP,
  rb: Ruby,
  rails: RubyOnRails,
  swift: Swift,
  kt: Kotlin,
  clj: Clojure,
  cljs: Clojure,
  cljc: Clojure,
  jl: Julia,
  fs: FSharp,
  fsi: FSharp,
  fsx: FSharp,
  fsscript: FSharp,
  r: R,
  R: R,
  dart: Dart,
  sol: Solidity,
  yaml: YAML,
  yml: YAML,
  md: Markdown,
  lua: Lua,
  luau: Lua,
}

export function languageForFilepath(fileUri: string): AutocompleteLanguageInfo {
  const extension = getUriFileExtension(fileUri)
  return LANGUAGES[extension] || Typescript
}
