local M = {}

local preview = require('fff.file_picker.preview')
local utils = require('fff.utils')
local picker_ui_state = require('fff.picker_ui.picker_ui_state')

local canonicalize_fff_path = utils.canonicalize_fff_path

-- Parent module reference (set by picker_ui.lua during initialization).
---@type table
local P = nil

function M.init(parent_module) P = parent_module end

local S = picker_ui_state.state

function M.close_preview_timer(timer)
  timer = timer or S.preview_timer
  if not timer then return end

  if S.preview_timer == timer then S.preview_timer = nil end
  if timer:is_closing() then return end

  timer:stop()
  timer:close()
end

function M.update_preview_debounced()
  M.close_preview_timer()

  local timer = vim.uv.new_timer()
  S.preview_timer = timer
  timer:start(
    S.preview_debounce_ms,
    0,
    vim.schedule_wrap(function()
      local is_current = S.preview_timer == timer
      M.close_preview_timer(timer)
      if is_current and P.state.active then M.update_preview() end
    end)
  )
end

--- Smart preview update for cursor movement.
function M.update_preview_smart()
  if not S.preview_visible then return end
  if not P.state.active then return end

  local items = S.filtered_items
  if #items == 0 or S.cursor > #items then
    M.update_preview()
    return
  end

  local item = items[S.cursor]
  if not item then
    M.update_preview()
    return
  end

  if S.last_preview_file == item.relative_path then
    M.update_preview()
    return
  end

  M.update_preview_debounced()
end

function M.update_preview_title(item, location)
  if not S.preview_win or not vim.api.nvim_win_is_valid(S.preview_win) then return end

  local relative_path = item.relative_path
  local max_title_width = vim.api.nvim_win_get_width(S.preview_win)

  local suffix = ''
  local is_grep_item = S.mode == 'grep' or S.suggestion_source == 'grep'
  if is_grep_item and location and location.line then suffix = ':' .. tostring(location.line) end

  local display_path = relative_path .. suffix
  local title

  if #display_path + 2 <= max_title_width then
    title = string.format(' %s ', display_path)
  else
    local available_chars = max_title_width - 2

    local filename = vim.fn.fnamemodify(relative_path, ':t') .. suffix
    if available_chars <= 3 then
      title = filename
    else
      if #filename + 5 <= available_chars then
        local normalized_path = vim.fs.normalize(relative_path)
        local path_parts = vim.split(normalized_path, '[/\\]', { plain = false })

        local segments = {}
        for _, part in ipairs(path_parts) do
          if part ~= '' then table.insert(segments, part) end
        end

        segments[#segments] = vim.fn.fnamemodify(relative_path, ':t') .. suffix

        local segments_to_show = { segments[#segments] }
        local current_length = #segments_to_show[1] + 4

        for i = #segments - 1, 1, -1 do
          local segment = segments[i]
          local new_length = current_length + #segment + 1

          if new_length <= available_chars then
            table.insert(segments_to_show, 1, segment)
            current_length = new_length
          else
            break
          end
        end

        if #segments_to_show == #segments then
          title = string.format(' %s ', table.concat(segments_to_show, '/'))
        else
          title = string.format(' ../%s ', table.concat(segments_to_show, '/'))
        end
      else
        local truncated = filename:sub(1, available_chars - 3) .. '...'
        title = string.format(' %s ', truncated)
      end
    end
  end

  vim.api.nvim_win_set_config(S.preview_win, {
    title = title,
    title_pos = 'left',
  })
end

function M.update_preview()
  if not S.preview_visible then return end
  if not P.state.active then return end

  local items = S.filtered_items
  if #items == 0 or S.cursor > #items then
    M.clear_preview()
    S.last_preview_file = nil
    S.last_preview_location = nil
    return
  end

  local item = items[S.cursor]
  if not item then
    M.clear_preview()
    S.last_preview_file = nil
    S.last_preview_location = nil
    return
  end

  local effective_location = S.location

  if not effective_location and S.query and S.query ~= '' then
    local line_str = S.query:match(':(%d+)$')
    if line_str then
      local line_num = tonumber(line_str)
      if line_num and line_num > 0 then
        local l, c = S.query:match(':(%d+):(%d+)$')
        if l then
          effective_location = { line = tonumber(l), col = tonumber(c) }
        else
          effective_location = { line = line_num }
        end
      end
    end
  end

  local is_grep_item = S.mode == 'grep' or S.suggestion_source == 'grep'
  if is_grep_item and item.line_number and item.line_number > 0 then
    effective_location = { line = item.line_number }
    if item.col and item.col > 0 then effective_location.col = item.col + 1 end
    effective_location.grep_query = S.query
    if S.grep_mode == 'fuzzy' and item.match_ranges then effective_location.fuzzy_match_ranges = item.match_ranges end
  end

  local location_changed = not vim.deep_equal(S.last_preview_location, effective_location)

  if S.last_preview_file == item.relative_path and not location_changed then return end

  if S.last_preview_file == item.relative_path and location_changed then
    S.last_preview_location = effective_location and vim.deepcopy(effective_location) or nil
    preview.state.location = effective_location
    if is_grep_item and effective_location and effective_location.line then
      M.update_preview_title(item, effective_location)
    end
    if S.preview_buf and vim.api.nvim_buf_is_valid(S.preview_buf) then
      preview.apply_location_highlighting(S.preview_buf)
    end
    return
  end

  preview.clear()

  S.last_preview_file = item.relative_path
  S.last_preview_location = effective_location and vim.deepcopy(effective_location) or nil

  M.update_preview_title(item, effective_location)

  if S.file_info_buf then
    preview.update_file_info_buffer(item, S.file_info_buf, S.cursor, S.preview_win)
    if S.file_info_win and vim.api.nvim_win_is_valid(S.file_info_win) then
      local rel = item.relative_path or item.path or ''
      pcall(vim.api.nvim_win_set_config, S.file_info_win, { title = ' ' .. rel .. ' ', title_pos = 'left' })
    end
  end

  preview.set_preview_window(S.preview_win)
  preview.preview(canonicalize_fff_path(item.relative_path), S.preview_buf, effective_location, item.is_binary)
end

function M.clear_preview()
  if not P.state.active then return end
  if not S.preview_visible then return end

  vim.api.nvim_win_set_config(S.preview_win, {
    title = ' Preview ',
    title_pos = 'left',
  })

  if S.file_info_buf then
    vim.api.nvim_set_option_value('modifiable', true, { buf = S.file_info_buf })
    vim.api.nvim_buf_set_lines(S.file_info_buf, 0, -1, false, {})
    vim.api.nvim_set_option_value('modifiable', false, { buf = S.file_info_buf })
    pcall(vim.api.nvim_buf_clear_namespace, S.file_info_buf, preview.file_info_ns, 0, -1)
  end

  vim.api.nvim_set_option_value('modifiable', true, { buf = S.preview_buf })
  vim.api.nvim_buf_set_lines(S.preview_buf, 0, -1, false, { 'No preview available' })
  vim.api.nvim_set_option_value('modifiable', false, { buf = S.preview_buf })
end

return M
