local ts_utils = require "telescope.utils"
local strings = require "plenary.strings"
local conf = require("telescope.config").values

local Job = require "plenary.job"
local Path = require "plenary.path"

local telescope_utils = require "telescope.utils"

local utils = {}

local detect_from_shebang = function(p)
  local s = p:readbyterange(0, 256)
  if s then
    local lines = telescope_utils.split_lines(s)
    return vim.filetype.match { contents = lines }
  end
end

local parse_modeline = function(tail)
  if tail:find "vim:" then
    return tail:match ".*:ft=([^: ]*):.*$" or ""
  end
end

local detect_from_modeline = function(p)
  local s = p:readbyterange(-256, 256)
  if s then
    local lines = telescope_utils.split_lines(s)
    local idx = lines[#lines] ~= "" and #lines or #lines - 1
    if idx >= 1 then
      return parse_modeline(lines[idx])
    end
  end
end

utils.filetype_detect = function(filepath)
  if type(filepath) ~= string then
    filepath = tostring(filepath)
  end

  local match = vim.filetype.match { filename = filepath }
  if match and match ~= "" then
    return match
  end

  local p = Path:new(filepath)
  if p and p:is_file() then
    match = detect_from_shebang(p)
    if match and match ~= "" then
      return match
    end

    match = detect_from_modeline(p)
    if match and match ~= "" then
      return match
    end
  end
end

-- API helper functions for buffer previewer
--- Job maker for buffer previewer
utils.job_maker = function(cmd, bufnr, opts)
  opts = opts or {}
  opts.mode = opts.mode or "insert"
  -- bufname and value are optional
  -- if passed, they will be use as the cache key
  -- if any of them are missing, cache will be skipped
  if opts.bufname ~= opts.value or not opts.bufname or not opts.value then
    local command = table.remove(cmd, 1)
    local writer = (function()
      if opts.writer ~= nil then
        local wcommand = table.remove(opts.writer, 1)
        return Job:new {
          command = wcommand,
          args = opts.writer,
          env = opts.env,
          cwd = opts.cwd,
        }
      end
    end)()

    Job:new({
      command = command,
      args = cmd,
      env = opts.env,
      cwd = opts.cwd,
      writer = writer,
      on_exit = vim.schedule_wrap(function(j)
        if not vim.api.nvim_buf_is_valid(bufnr) then
          return
        end
        if opts.mode == "append" then
          vim.api.nvim_buf_set_lines(bufnr, -1, -1, false, j:result())
        elseif opts.mode == "insert" then
          vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, j:result())
        end
        if opts.callback then
          opts.callback(bufnr, j:result())
        end
      end),
    }):start()
  else
    if opts.callback then
      opts.callback(bufnr)
    end
  end
end

local function has_filetype(ft)
  return ft and ft ~= ""
end

--- Attach default highlighter which will choose between regex and ts
utils.highlighter = function(bufnr, ft, opts)
  opts = vim.F.if_nil(opts, {})
  opts.preview = vim.F.if_nil(opts.preview, {})
  opts.preview.treesitter = (function()
    if type(opts.preview) == "table" and opts.preview.treesitter then
      return opts.preview.treesitter
    end
    if type(conf.preview) == "table" and conf.preview.treesitter then
      return conf.preview.treesitter
    end
    if type(conf.preview) == "boolean" then
      return conf.preview
    end
    -- We should never get here
    return false
  end)()

  if type(opts.preview.treesitter) == "boolean" then
    local temp = { enable = opts.preview.treesitter }
    opts.preview.treesitter = temp
  end

  local ts_highlighting = (function()
    if type(opts.preview.treesitter.enable) == "table" then
      if vim.tbl_contains(opts.preview.treesitter.enable, ft) then
        return true
      end
      return false
    end

    if vim.tbl_contains(vim.F.if_nil(opts.preview.treesitter.disable, {}), ft) then
      return false
    end

    return opts.preview.treesitter.enable == nil or opts.preview.treesitter.enable == true
  end)()

  local ts_success
  if ts_highlighting then
    ts_success = utils.ts_highlighter(bufnr, ft)
  end
  if not ts_highlighting or ts_success == false then
    utils.regex_highlighter(bufnr, ft)
  end
end

--- Attach regex highlighter
utils.regex_highlighter = function(bufnr, ft)
  if has_filetype(ft) then
    return pcall(vim.api.nvim_buf_set_option, bufnr, "syntax", ft)
  end
  return false
end

-- Attach ts highlighter
utils.ts_highlighter = function(bufnr, ft)
  if has_filetype(ft) then
    local lang = vim.treesitter.language.get_lang(ft) or ft
    if lang and ts_utils.has_ts_parser(lang) then
      return vim.treesitter.start(bufnr, lang)
    end
  end
  return false
end

utils.set_preview_message = function(bufnr, winid, message, fillchar)
  fillchar = vim.F.if_nil(fillchar, "╱")
  local height = vim.api.nvim_win_get_height(winid)
  local width = vim.api.nvim_win_get_width(winid)
  vim.api.nvim_buf_set_lines(
    bufnr,
    0,
    -1,
    false,
    ts_utils.repeated_table(height, table.concat(ts_utils.repeated_table(width, fillchar), ""))
  )
  local anon_ns = vim.api.nvim_create_namespace ""
  local padding = table.concat(ts_utils.repeated_table(#message + 4, " "), "")
  local formatted_message = "  " .. message .. "  "
  -- Populate lines table based on height
  local lines = {}
  if height == 1 then
    lines[1] = formatted_message
  else
    for i = 1, math.min(height, 3), 1 do
      if i % 2 == 0 then
        lines[i] = formatted_message
      else
        lines[i] = padding
      end
    end
  end
  vim.api.nvim_buf_set_extmark(
    bufnr,
    anon_ns,
    0,
    0,
    { end_line = height, hl_group = "TelescopePreviewMessageFillchar" }
  )
  local col = math.floor((width - strings.strdisplaywidth(formatted_message)) / 2)
  for i, line in ipairs(lines) do
    local line_pos = math.floor(height / 2) - 2 + i
    vim.api.nvim_buf_set_extmark(
      bufnr,
      anon_ns,
      math.max(line_pos, 0),
      0,
      { virt_text = { { line, "TelescopePreviewMessage" } }, virt_text_pos = "overlay", virt_text_win_col = col }
    )
  end
end

--- Check if mime type is binary.
--- NOT an exhaustive check, may get false negatives. Ideally should check
--- filetype with `vim.filetype.match` or `filetype_detect` first for filetype
--- info.
---@param mime_type string
---@return boolean
utils.binary_mime_type = function(mime_type)
  local type_, subtype = unpack(vim.split(mime_type, "/"))
  if vim.tbl_contains({ "text", "inode" }, type_) then
    return false
  end
  if vim.tbl_contains({ "json", "javascript" }, subtype) then
    return false
  end
  return true
end

return utils
