//! Textual-containment guards that keep the hand-written docs honest
//! (milestone-11 ruling 3, repointed at the Diátaxis reference pages by
//! milestone-13 ruling 7). They assert presence, not correctness — the same
//! contract as openapi.zig's route guard.

const std = @import("std");
const build_options = @import("build_options");
const docs = @import("docs_files");
const cli = @import("cli.zig");
const routes = @import("web/routes.zig");
const model = @import("config/model.zig");

test "every served operation has its own table row in docs/reference/api.md" {
    const gpa = std.testing.allocator;
    for (routes.table) |route| {
        // Matches one full method + path cell pair ("| GET | `/api/groups` |"),
        // so neither a same-path sibling method nor a longer-path prefix can
        // satisfy the check for a missing operation.
        const needle = try std.fmt.allocPrint(gpa, "| {s} | `{s}` |", .{
            @tagName(route.method), route.pattern,
        });
        defer gpa.free(needle);
        if (std.mem.indexOf(u8, docs.reference_api_md, needle) == null) {
            std.debug.print("operation row missing from docs/reference/api.md: {s}\n", .{needle});
            return error.OperationMissingFromApiDoc;
        }
    }
}

test "every settings key appears in docs/reference/configuration.md" {
    const gpa = std.testing.allocator;
    var pairs: std.ArrayList(model.SettingPair) = .empty;
    defer {
        model.freeSettings(gpa, pairs.items);
        pairs.deinit(gpa);
    }
    try model.toSettings(.{}, gpa, &pairs);
    for (pairs.items) |pair| {
        // Matches the key's own leading table cell ("| `dns.port` |"), not a
        // bare mention: prose and the annotated example both name most keys, so
        // a substring search would stay green after a field's row is deleted.
        const needle = try std.fmt.allocPrint(gpa, "| `{s}` |", .{pair.key});
        defer gpa.free(needle);
        if (std.mem.indexOf(u8, docs.reference_configuration_md, needle) == null) {
            std.debug.print("settings key row missing from docs/reference/configuration.md: {s}\n", .{needle});
            return error.SettingsKeyMissingFromConfigDoc;
        }
    }
}

test "every cli subcommand has its own reference heading in docs/reference/cli.md" {
    const gpa = std.testing.allocator;
    for (cli.command_names) |entry| {
        // Anchors on the reference-section heading ("## `import FILE`" starts
        // with "## `import"), so prose mentions elsewhere cannot mask a removed
        // command section.
        const needle = try std.fmt.allocPrint(gpa, "## `{s}", .{entry.name});
        defer gpa.free(needle);
        if (std.mem.indexOf(u8, docs.reference_cli_md, needle) == null) {
            std.debug.print("subcommand heading missing from docs/reference/cli.md: {s}\n", .{needle});
            return error.SubcommandMissingFromCliDoc;
        }
    }
}

test "no doc page pastes a concrete version into a transcript" {
    const gpa = std.testing.allocator;
    // Anchored on the `nxdns ` prefix the transcripts print, not on the bare
    // version: a release numbered 1.0.0 would otherwise collide with the
    // 1.0.0.1 upstream address the pages use as an example.
    const built = try std.fmt.allocPrint(gpa, "nxdns {s}", .{build_options.version_string});
    defer gpa.free(built);

    for (docs.pages) |page| {
        // The literal milestone 14 flagged, checked by name as well, so the
        // guard still bites on a page written against the old default after
        // `-Dversion-string` moves on.
        for ([_][]const u8{ built, "0.1.0-dev" }) |needle| {
            if (std.mem.indexOf(u8, page.text, needle) != null) {
                std.debug.print("version literal '{s}' in {s}: use the <version> placeholder\n", .{ needle, page.path });
                return error.VersionLiteralInDocPage;
            }
        }
    }
}

test "every version transcript prints the placeholder" {
    for (docs.version_transcript_pages) |page| {
        if (std.mem.indexOf(u8, page.text, "nxdns <version>") == null) {
            std.debug.print("transcript placeholder 'nxdns <version>' missing from {s}\n", .{page.path});
            return error.VersionPlaceholderMissingFromDocPage;
        }
    }
}
