local function open_new_buffer(name)
  local buf = vim.api.nvim_create_buf(true, false)
  vim.api.nvim_set_current_buf(buf)
  vim.api.nvim_buf_set_option(buf, "modifiable", true)
  if name then
    vim.api.nvim_buf_set_name(buf, name)
  end
  return buf
end

local M = {}

local main_cmd = "npm run dev" -- default

local function log(message, level)
  vim.notify(string.format("npm-dev-runner: %s", message), vim.log.levels[level])
end

-- Cache: dir -> { job_id=..., buf=... }
local job_cache = {}

local function find_cached_dir(dir)
  if not dir then
    vim.notify("npm-dev-runner: No directory provided to find_cached_dir()", vim.log.levels.ERROR)
    return
  end

  local cur = dir
  while not job_cache[cur] do
    if cur == "/" or string.match(cur, "^[A-Z]:\\$") then
      return
    end
    cur = vim.fn.fnamemodify(cur, ":h")
  end
  return cur
end

local function is_running(dir)
  local cached_dir = find_cached_dir(dir)
  return cached_dir and job_cache[cached_dir]
end

local function is_windows()
  return vim.loop.os_uname().version:match("Windows")
end

M.toggle = function(dir)
  local running = is_running(dir)
  if not running then
    M.start(dir)
    return
  end
  M.stop(dir)
end

--- Fungsi setup menerima argumen command utama, contoh: require("npmrun").setup("pnpm dev")
M.setup = function(cmd)
  main_cmd = cmd or "npm run dev"
  if not vim.fn.executable(main_cmd:match("%S+")) then
    log(main_cmd .. " is not executable. Make sure it is installed and in PATH.", "ERROR")
    return
  end

  local function find_dir(args)
    local dir = args ~= "" and args or "%:p:h"
    return vim.fn.expand(vim.fn.fnamemodify(vim.fn.expand(dir), ":p"))
  end

  vim.api.nvim_create_user_command("DevStart", function(opts)
    M.start(find_dir(opts.args))
  end, { nargs = "?" })

  vim.api.nvim_create_user_command("DevStop", function(opts)
    M.stop(find_dir(opts.args))
  end, { nargs = "?" })

  vim.api.nvim_create_user_command("DevToggle", function(opts)
    M.toggle(find_dir(opts.args))
  end, { nargs = "?" })
end

M.start = function(dir)
  if is_running(dir) then
    log(main_cmd .. " already running", "INFO")
    return
  end

  local cmd
  if is_windows() then
    cmd = { "cmd.exe", "/C" }
    for word in main_cmd:gmatch("%S+") do
      table.insert(cmd, word)
    end
  else
    cmd = {}
    for word in main_cmd:gmatch("%S+") do
      table.insert(cmd, word)
    end
  end

  local buffer_name = "npmrun.txt"
  local output_buf = open_new_buffer(buffer_name)
  vim.api.nvim_buf_set_lines(output_buf, 0, -1, false, {})

  local function append_to_buffer(lines)
    if not lines then
      return
    end
    if not vim.api.nvim_buf_is_valid(output_buf) then
      return
    end
    local filtered = {}
    for _, l in ipairs(lines) do
      if l ~= "" then
        table.insert(filtered, l)
      end
    end
    if #filtered > 0 then
      local line_count = vim.api.nvim_buf_line_count(output_buf)
      vim.api.nvim_buf_set_lines(output_buf, line_count, line_count, false, filtered)
    end
  end

  local function close_output_buffer()
    if output_buf and vim.api.nvim_buf_is_valid(output_buf) then
      vim.api.nvim_buf_delete(output_buf, { force = true })
    end
  end

  local job_id = vim.fn.jobstart(cmd, {
    cwd = dir,
    stdout_buffered = false, -- streaming mode
    stderr_buffered = false,
    on_stdout = function(_, data)
      append_to_buffer(data)
    end,
    on_stderr = function(_, data)
      append_to_buffer(vim.tbl_map(function(l)
        return "[ERR] " .. l
      end, data))
    end,
    on_exit = function(_, code)
      append_to_buffer({ string.format(main_cmd .. " exited with code %d", code) })
      close_output_buffer()
      job_cache[dir] = nil
    end,
  })

  job_cache[dir] = { job_id = job_id, buf = output_buf }
  log(main_cmd .. " started", "INFO")
end

M.stop = function(dir)
  local running = is_running(dir)
  if running then
    local cached_dir = find_cached_dir(dir)
    if cached_dir then
      local job_entry = job_cache[cached_dir]
      if job_entry then
        vim.fn.jobstop(job_entry.job_id)
        if job_entry.buf and vim.api.nvim_buf_is_valid(job_entry.buf) then
          vim.api.nvim_buf_delete(job_entry.buf, { force = true })
        end
      end
      job_cache[cached_dir] = nil
      log(main_cmd .. " stopped", "INFO")
    end
  end
end

return M