-- ido.lua
-- ============================================================================
-- Interactive Do - Enhanced completion with fuzzy matching and inline display.
-- Similar to Emacs ido-mode.
-- ============================================================================

local ido = {}

-- Configuration
ido.config = {
    enabled = true,
    separator = " | ",
    max_candidates = 10,
    show_count = true,
    fuzzy = true,
    case_sensitive = false,
    -- Display format: "inline" shows in minibuffer, "vertical" would need popup
    display = "inline",
}

-- State
ido.active = false
ido.candidates = {}
ido.filtered = {}
ido.selected_index = 1
ido.input = ""

-- Fuzzy match score function
-- Returns score (higher is better) or nil for no match
function ido.fuzzy_match(pattern, candidate)
    if pattern == "" then
        return 1000  -- Empty pattern matches everything
    end

    local p = ido.config.case_sensitive and pattern or pattern:lower()
    local c = ido.config.case_sensitive and candidate or candidate:lower()

    -- Exact match is best
    if c == p then
        return 10000
    end

    -- Prefix match is very good
    if c:sub(1, #p) == p then
        return 5000 + (1000 / #c)
    end

    -- Substring match
    local substr_pos = c:find(p, 1, true)
    if substr_pos then
        return 3000 - substr_pos + (1000 / #c)
    end

    -- Fuzzy match: all chars in pattern appear in order in candidate
    if ido.config.fuzzy then
        local score = 0
        local j = 1
        local consecutive = 0
        local last_match = -1

        for i = 1, #p do
            local char = p:sub(i, i)
            local found = false

            while j <= #c do
                if c:sub(j, j) == char then
                    found = true
                    -- Bonus for consecutive matches
                    if j == last_match + 1 then
                        consecutive = consecutive + 1
                        score = score + consecutive * 10
                    else
                        consecutive = 0
                    end
                    -- Bonus for matching at word boundaries
                    if j == 1 or c:sub(j-1, j-1):match("[%s_%-/\\.]") then
                        score = score + 50
                    end
                    last_match = j
                    j = j + 1
                    break
                end
                j = j + 1
            end

            if not found then
                return nil  -- No match
            end
        end

        -- Penalty for length difference
        score = score + 1000 - (#c - #p) * 5
        return math.max(1, score)
    end

    return nil
end

-- Filter and sort candidates based on input
function ido.filter_candidates(input)
    local results = {}

    for _, candidate in ipairs(ido.candidates) do
        local text = type(candidate) == "table" and candidate.text or candidate
        local score = ido.fuzzy_match(input, text)
        if score then
            table.insert(results, {
                candidate = candidate,
                text = text,
                score = score
            })
        end
    end

    -- Sort by score (descending)
    table.sort(results, function(a, b)
        return a.score > b.score
    end)

    -- Extract candidates
    ido.filtered = {}
    for i, r in ipairs(results) do
        if i > ido.config.max_candidates * 2 then break end
        table.insert(ido.filtered, r.candidate)
    end

    return ido.filtered
end

-- Format candidates for display in minibuffer
function ido.format_display(input)
    local parts = {}

    -- Show input
    if input ~= "" then
        table.insert(parts, input)
    end

    -- Show candidates
    local shown = 0
    for i, candidate in ipairs(ido.filtered) do
        if shown >= ido.config.max_candidates then
            if #ido.filtered > ido.config.max_candidates then
                table.insert(parts, "...")
            end
            break
        end

        local text = type(candidate) == "table" and candidate.text or candidate
        if i == ido.selected_index then
            text = "[" .. text .. "]"
        end
        table.insert(parts, text)
        shown = shown + 1
    end

    -- Add count if enabled
    if ido.config.show_count and #ido.filtered > ido.config.max_candidates then
        table.insert(parts, "(" .. #ido.filtered .. " total)")
    end

    return table.concat(parts, ido.config.separator)
end

-- Select next candidate
function ido.next()
    if #ido.filtered > 0 then
        ido.selected_index = (ido.selected_index % #ido.filtered) + 1
    end
end

-- Select previous candidate
function ido.prev()
    if #ido.filtered > 0 then
        ido.selected_index = ((ido.selected_index - 2) % #ido.filtered) + 1
    end
end

-- Get selected candidate
function ido.get_selected()
    if ido.selected_index <= #ido.filtered then
        return ido.filtered[ido.selected_index]
    end
    return nil
end

-- Complete buffer names with ido
function ido.complete_buffer()
    local buf_names = editor:get_buffer_names()
    ido.candidates = buf_names
    ido.filtered = ido.filter_candidates("")
    ido.selected_index = 1

    local display = ido.format_display("")
    editor:message("Switch to buffer: " .. display)
end

-- Complete commands with ido
function ido.complete_command()
    local cmd_names = get_command_names()
    ido.candidates = cmd_names
    ido.filtered = ido.filter_candidates("")
    ido.selected_index = 1

    local display = ido.format_display("")
    editor:message("M-x " .. display)
end

-- Register commands
editor:register_command("ido-switch-buffer", "Switch buffer with ido completion", function(args)
    ido.complete_buffer()
    return {success = true}
end)

editor:register_command("ido-execute-command", "Execute command with ido completion", function(args)
    ido.complete_command()
    return {success = true}
end)

editor:register_command("ido-mode", "Toggle ido-mode for enhanced completion", function(args)
    ido.config.enabled = not ido.config.enabled
    if ido.config.enabled then
        editor:message("ido-mode enabled")
    else
        editor:message("ido-mode disabled")
    end
    return {success = true}
end)

editor:register_command("ido-toggle-fuzzy", "Toggle fuzzy matching in ido", function(args)
    ido.config.fuzzy = not ido.config.fuzzy
    editor:message("Fuzzy matching: " .. (ido.config.fuzzy and "on" or "off"))
    return {success = true}
end)

-- Utility: simple buffer selection demo
editor:register_command("ido-demo", "Demonstrate ido buffer selection", function(args)
    local buf_names = editor:get_buffer_names()
    ido.candidates = buf_names
    local results = ido.filter_candidates(args[1] or "")
    ido.selected_index = 1

    if #results > 0 then
        local display = ido.format_display(args[1] or "")
        return {success = true, message = display}
    else
        return {success = false, message = "No matching buffers"}
    end
end, {}, true, "s")

-- Store in lumacs namespace
lumacs.ido = ido

return ido
