const std = @import("std");

/// Calculate Levenshtein distance between two strings
pub fn levenshteinDistance(allocator: std.mem.Allocator, s1: []const u8, s2: []const u8) !usize {
    const m = s1.len + 1;
    const n = s2.len + 1;

    // Create a 2D array for dynamic programming
    var dp = try allocator.alloc(usize, m * n);
    defer allocator.free(dp);

    // Initialize first row and column
    for (0..m) |i| {
        dp[i * n] = i;
    }
    for (0..n) |j| {
        dp[j] = j;
    }

    // Fill the matrix
    for (1..m) |i| {
        for (1..n) |j| {
            const cost: usize = if (s1[i - 1] == s2[j - 1]) 0 else 1;
            const deletion = dp[(i - 1) * n + j] + 1;
            const insertion = dp[i * n + (j - 1)] + 1;
            const substitution = dp[(i - 1) * n + (j - 1)] + cost;
            dp[i * n + j] = @min(@min(deletion, insertion), substitution);
        }
    }

    return dp[(m - 1) * n + (n - 1)];
}

/// Find suggestions for a typo from a list of candidates
pub fn findSuggestions(
    allocator: std.mem.Allocator,
    input: []const u8,
    candidates: []const []const u8,
    max_distance: usize,
    max_suggestions: usize,
) ![][]const u8 {
    var suggestions = std.ArrayList([]const u8).empty;
    defer suggestions.deinit(allocator);

    var distances = std.ArrayList(usize).empty;
    defer distances.deinit(allocator);

    for (candidates) |candidate| {
        const dist = try levenshteinDistance(allocator, input, candidate);
        if (dist <= max_distance) {
            try suggestions.append(allocator, candidate);
            try distances.append(allocator, dist);
        }
    }

    // Sort by distance (bubble sort for simplicity with small lists)
    const n = distances.items.len;
    for (0..n) |i| {
        for (0..n - i - 1) |j| {
            if (distances.items[j] > distances.items[j + 1]) {
                // Swap distances
                const temp_dist = distances.items[j];
                distances.items[j] = distances.items[j + 1];
                distances.items[j + 1] = temp_dist;
                // Swap corresponding suggestions
                const temp_sugg = suggestions.items[j];
                suggestions.items[j] = suggestions.items[j + 1];
                suggestions.items[j + 1] = temp_sugg;
            }
        }
    }

    // Return top suggestions
    const count = @min(suggestions.items.len, max_suggestions);
    const result = try allocator.alloc([]const u8, count);
    for (0..count) |i| {
        result[i] = try allocator.dupe(u8, suggestions.items[i]);
    }

    return result;
}

/// Suggest commands based on prefix matching
pub fn suggestCommands(input: []const u8) ?[]const []const u8 {
    const all_commands = [_][]const u8{
        "init",       "sync",      "queue",   "requeue", "status",
        "monitor",    "cancel",    "prune",   "watch",   "dataset",
        "experiment", "narrative", "outcome", "info",    "logs",
        "annotate",   "validate",  "compare", "find",    "export",
    };

    // Exact match - no suggestion needed
    for (all_commands) |cmd| {
        if (std.mem.eql(u8, input, cmd)) return null;
    }

    // Find prefix matches
    var matches: [5][]const u8 = undefined;
    var match_count: usize = 0;

    for (all_commands) |cmd| {
        if (std.mem.startsWith(u8, cmd, input)) {
            matches[match_count] = cmd;
            match_count += 1;
            if (match_count >= 5) break;
        }
    }

    if (match_count == 0) return null;

    // Return static slice - caller must not free
    return matches[0..match_count];
}

/// Suggest flags for a command
pub fn suggestFlags(command: []const u8, input: []const u8) ?[]const []const u8 {
    // Common flags for all commands
    const common_flags = [_][]const u8{ "--help", "--verbose", "--quiet", "--json" };

    // Command-specific flags
    const queue_flags = [_][]const u8{
        "--commit",           "--priority",   "--cpu",     "--memory",   "--gpu",
        "--gpu-memory",       "--hypothesis", "--context", "--intent",   "--expected-outcome",
        "--experiment-group", "--tags",       "--dry-run", "--validate", "--explain",
        "--force",
    };

    const find_flags = [_][]const u8{
        "--tag",    "--outcome", "--dataset", "--experiment-group",
        "--author", "--after",   "--before",  "--limit",
    };

    const compare_flags = [_][]const u8{
        "--json", "--all", "--fields",
    };

    const export_flags = [_][]const u8{
        "--bundle", "--anonymize", "--anonymize-level", "--base",
    };

    // Select flags based on command
    const flags: []const []const u8 = switch (std.meta.stringToEnum(Command, command) orelse .unknown) {
        .queue => &queue_flags,
        .find => &find_flags,
        .compare => &compare_flags,
        .export_cmd => &export_flags,
        else => &common_flags,
    };

    // Find prefix matches
    var matches: [5][]const u8 = undefined;
    var match_count: usize = 0;

    // Check common flags first
    for (common_flags) |flag| {
        if (std.mem.startsWith(u8, flag, input)) {
            matches[match_count] = flag;
            match_count += 1;
            if (match_count >= 5) break;
        }
    }

    // Then check command-specific flags
    if (match_count < 5) {
        for (flags) |flag| {
            if (std.mem.startsWith(u8, flag, input)) {
                // Avoid duplicates
                var already_added = false;
                for (0..match_count) |i| {
                    if (std.mem.eql(u8, matches[i], flag)) {
                        already_added = true;
                        break;
                    }
                }
                if (!already_added) {
                    matches[match_count] = flag;
                    match_count += 1;
                    if (match_count >= 5) break;
                }
            }
        }
    }

    if (match_count == 0) return null;
    return matches[0..match_count];
}

const Command = enum {
    init,
    sync,
    queue,
    requeue,
    status,
    monitor,
    cancel,
    prune,
    watch,
    dataset,
    experiment,
    narrative,
    outcome,
    info,
    logs,
    annotate,
    validate,
    compare,
    find,
    export_cmd,
    unknown,
};

/// Format suggestions into a helpful message
pub fn formatSuggestionMessage(
    allocator: std.mem.Allocator,
    input: []const u8,
    suggestions: []const []const u8,
) ![]u8 {
    if (suggestions.len == 0) return allocator.dupe(u8, "");

    var buf = std.ArrayList(u8).empty;
    defer buf.deinit(allocator);

    const writer = buf.writer(allocator);
    try writer.print("Did you mean for '{s}': ", .{input});

    for (suggestions, 0..) |sugg, i| {
        if (i > 0) {
            if (i == suggestions.len - 1) {
                try writer.writeAll(" or ");
            } else {
                try writer.writeAll(", ");
            }
        }
        try writer.print("'{s}'", .{sugg});
    }

    try writer.writeAll("?\n");

    return buf.toOwnedSlice(allocator);
}

/// Test the suggestion system
pub fn testSuggestions() !void {
    const allocator = std.testing.allocator;

    // Test Levenshtein distance
    const dist1 = try levenshteinDistance(allocator, "queue", "quee");
    std.debug.assert(dist1 == 1);

    const dist2 = try levenshteinDistance(allocator, "status", "statis");
    std.debug.assert(dist2 == 1);

    // Test suggestions
    const candidates = [_][]const u8{ "queue", "query", "quiet", "quit" };
    const suggestions = try findSuggestions(allocator, "quee", &candidates, 2, 3);
    defer {
        for (suggestions) |s| allocator.free(s);
        allocator.free(suggestions);
    }
    std.debug.assert(suggestions.len > 0);
    std.debug.assert(std.mem.eql(u8, suggestions[0], "queue"));

    std.debug.print("Suggestion tests passed!\n", .{});
}
