const assert = require("assert");
const fs = require("fs");
const os = require("os");
const path = require("path");
const Database = require("better-sqlite3");
const { PRESERVED_PATHS } = require("../../../src/services/update-repository");
const {
  accessForUser,
  createEntry,
  ensureTables,
  getEditableEntry,
  getEntryBySlug,
  grantPermission,
  listEntries,
  listPermissions,
  listVersions,
  revokePermission,
  restoreVersion,
  searchForAi,
  setEntryWorkflow,
  updateEntry
} = require("../backend/okf_store");
const {
  ensureKnowledgeDirs,
  getCommunityKnowledgeFile,
  getVisibleCommunityKnowledge,
  listCommunityKnowledgeFiles,
  listCorrectionKnowledgeFiles,
  listVisibleCommunityKnowledge,
  loadKnowledgeEntries,
  listKnowledgePlaceholders,
  saveCorrectionKnowledgeFile,
  saveCommunityKnowledgeFile,
  migrateSingleBracePlaceholders,
  searchFileKnowledge
} = require("../backend/file_knowledge");
const { generateKnowledgeFiles } = require("../backend/generate_knowledge");
const { renderMarkdown } = require("../backend/markdown");

const db = new Database(":memory:");
db.exec(`
  CREATE TABLE user_profiles (
    id TEXT PRIMARY KEY,
    internal_username TEXT NOT NULL UNIQUE,
    created_at INTEGER NOT NULL,
    updated_at INTEGER NOT NULL
  );
`);
ensureTables(db);

const now = Date.now();
db.prepare("INSERT INTO user_profiles (id, internal_username, created_at, updated_at) VALUES (?, ?, ?, ?)").run("admin-1", "Admin", now, now);
db.prepare("INSERT INTO user_profiles (id, internal_username, created_at, updated_at) VALUES (?, ?, ?, ?)").run("mod-1", "Mod", now, now);
db.prepare("INSERT INTO user_profiles (id, internal_username, created_at, updated_at) VALUES (?, ?, ?, ?)").run("user-1", "User", now, now);
db.prepare("INSERT INTO user_profiles (id, internal_username, created_at, updated_at) VALUES (?, ?, ?, ?)").run("editor-1", "Editor", now, now);
db.prepare("INSERT INTO user_profiles (id, internal_username, created_at, updated_at) VALUES (?, ?, ?, ?)").run("reviewer-1", "Reviewer", now, now);
db.prepare("INSERT INTO user_profiles (id, internal_username, created_at, updated_at) VALUES (?, ?, ?, ?)").run("implementer-1", "Implementer", now, now);

const admin = { id: "admin-1", isAdmin: true, isMod: true };
const mod = { id: "mod-1", isMod: true };
const user = { id: "user-1" };
const editor = { id: "editor-1" };
const reviewer = { id: "reviewer-1" };
const implementer = { id: "implementer-1" };

assert(PRESERVED_PATHS.includes("knowledge/community"));
assert(PRESERVED_PATHS.includes("knowledge/corrections"));
const adminTemplate = fs.readFileSync(path.join(__dirname, "..", "views", "admin.ejs"), "utf8");
assert(adminTemplate.includes("General OKF"));
assert(adminTemplate.includes("Community OKF"));
assert(adminTemplate.includes("System-generated OKF"));
assert(adminTemplate.includes("tab=general"));
assert(adminTemplate.includes("tab=community"));
assert(adminTemplate.includes("tab=system"));
assert(adminTemplate.includes("selectedSystemFile"));
assert(adminTemplate.includes("System-generated OKF") && !adminTemplate.includes("Save system file"));
assert(adminTemplate.includes("data-okf-file-list"));
assert(adminTemplate.includes("data-okf-file-search"));
assert(adminTemplate.includes('data-okf-file-filter="category"'));
assert(adminTemplate.includes('data-okf-file-filter="tags"'));
assert(adminTemplate.includes("data-okf-markdown-preview-panel"));
assert(adminTemplate.includes("/plugins/okf/admin/preview"));
assert(adminTemplate.includes("No community OKF files match these filters."));
assert(adminTemplate.includes("No system-generated OKF files match these filters."));
assert(adminTemplate.includes("Feedback corrections"));
assert(adminTemplate.includes("selectedCorrection"));
const okfPluginSource = fs.readFileSync(path.join(__dirname, "..", "index.js"), "utf8");
assert(okfPluginSource.includes("preferSpecificKnowledgeChunks"));
assert(okfPluginSource.includes("Evidence tier:"));
assert(okfPluginSource.includes("admin-approved correction"));
assert(okfPluginSource.includes("trimForContext"));
assert(okfPluginSource.includes("remaining = 4200"));
assert(okfPluginSource.includes("communityFileValuesForUser"));
assert(okfPluginSource.includes("canReadOkfVisibility(access, requestedCommunity.visibility)"));
assert(okfPluginSource.includes("VISIBILITY_VALUES.filter((value) => canReadOkfVisibility(access, value))"));
assert(okfPluginSource.includes("saveCorrection(values, actor)"));
assert(okfPluginSource.includes("Only admins can create correction knowledge files"));
assert(okfPluginSource.includes('router.post("/admin/preview", requireOkfManagement(db)'));
assert(okfPluginSource.includes('router.get("/community/:slug", requireLogin'));
assert(okfPluginSource.includes("listVisibleCommunityKnowledge"));
assert(okfPluginSource.includes('label: "Contribute to OKF"'));
const fileKnowledgeSource = fs.readFileSync(path.join(__dirname, "..", "backend", "file_knowledge.js"), "utf8");
assert(fileKnowledgeSource.includes("writeTextAtomicSync(filePath, markdown)"));
const publicKnowledgeTemplate = fs.readFileSync(path.join(__dirname, "..", "views", "index.ejs"), "utf8");
assert(publicKnowledgeTemplate.includes("entry.href"));
assert(publicKnowledgeTemplate.includes("Community file"));

grantPermission(db, { user_id: "editor-1", level: "edit", notes: "Trusted writer" }, admin);
grantPermission(db, { user_id: "reviewer-1", level: "edit_review", notes: "Trusted reviewer" }, admin);
grantPermission(db, { user_id: "implementer-1", level: "edit_review_implement", notes: "Trusted implementer" }, admin);
assert.equal(accessForUser(db, editor).canEdit, true);
assert.equal(accessForUser(db, editor).capabilities.read_admin_fields, false);
assert.equal(accessForUser(db, reviewer).capabilities.review, true);
assert.equal(accessForUser(db, reviewer).capabilities.implement, false);
assert.equal(accessForUser(db, implementer).capabilities.implement, true);
assert.equal(accessForUser(db, mod).capabilities.read_mod_fields, true);
assert.equal(accessForUser(db, user).capabilities.read_mod_fields, false);
assert.equal(accessForUser(db, admin).capabilities.manage_permissions, true);
assert.equal(accessForUser(db, null).capabilities.read_user_fields, false);
assert.equal(accessForUser(db, mod).capabilities.management_view, false);
assert.equal(listPermissions(db, editor).length, 0);
assert.equal(listPermissions(db, admin).filter((grant) => !grant.revoked_at).length, 3);

const entry = createEntry(db, {
  title: "Currency basics",
  slug: "currency-basics",
  category: "Community",
  tags: "currency, coins",
  aliases: "How do coins, bonuses, and payouts work?\nWhat is currency?",
  summary: "Explains the community currency.",
  user_markdown: "Users earn **coins** through activity.",
  moderator_markdown: "Moderators may help users with missing transaction context.",
  admin_markdown: "Admin-only economy configuration details.",
  ai_facts_markdown: "The primary community currency is coins.",
  source_links: "/commands",
  visibility: "mod",
  status: "published",
  review_state: "approved"
}, admin);

assert.equal(entry.slug, "currency-basics");
assert.deepEqual(entry.aliases, ["How do coins, bonuses, and payouts work?", "What is currency?"]);
assert.equal(listEntries(db, {}, user).length, 0);
assert.equal(listEntries(db, {}, mod).length, 1);
assert.equal(getEntryBySlug(db, "currency-basics", user), null);
assert.equal(getEntryBySlug(db, "currency-basics", mod).moderator_markdown.includes("Moderators"), true);
assert.equal(getEntryBySlug(db, "currency-basics", admin).admin_markdown.includes("Admin-only"), true);
assert.equal(listEntries(db, {}, editor, { management: true }).some((item) => item.slug === "currency-basics"), false);
assert(searchForAi(db, "currency", mod).some((item) => item.facts.includes("Moderators")));
const naturalCurrencySearch = searchForAi(db, "How does the community currency work?", mod);
assert.equal(naturalCurrencySearch[0].slug, "currency-basics");
assert(Number.isFinite(naturalCurrencySearch[0].source_metadata.score));
assert.equal(naturalCurrencySearch[0].source_metadata.path, "/plugins/okf/currency-basics");
assert.equal(searchForAi(db, "currency", user).length, 0);
assert.equal(searchForAi(db, "Can you help me?", mod).length, 0);

const publicEntry = createEntry(db, {
  title: "Public help",
  slug: "public-help",
  category: "Support",
  summary: "Public support answer.",
  user_markdown: "Everyone can read this.",
  admin_markdown: "Admin-only implementation notes.",
  ai_facts_markdown: "Admin AI fact.",
  visibility: "user",
  status: "published",
  review_state: "approved"
}, admin);
const publicUserContext = searchForAi(db, "public", user);
const publicAdminContext = searchForAi(db, "public", admin);
assert(publicUserContext.some((item) => item.id === publicEntry.id));
assert.equal(publicUserContext.some((item) => item.facts.includes("Admin-only")), false);
assert(publicAdminContext.some((item) => item.facts.includes("Admin-only implementation notes.")));
assert(publicAdminContext.some((item) => item.facts.includes("Admin AI fact.")));
assert.equal(searchForAi(db, "Where can everyone read public support help?", user)[0].id, publicEntry.id);
assert.equal(getEntryBySlug(db, "public-help", editor).admin_markdown, undefined);
assert.equal(getEditableEntry(db, "public-help", editor).ai_facts_markdown, undefined);
assert.equal(getEditableEntry(db, "public-help", reviewer).admin_markdown, undefined);
assert.equal(getEditableEntry(db, "public-help", implementer).ai_facts_markdown, undefined);
assert.equal(searchForAi(db, "Admin-only implementation", editor).length, 0);
updateEntry(db, "public-help", {
  title: "Public help",
  slug: "public-help",
  summary: "Contributor-safe update.",
  user_markdown: "Everyone can still read this.",
  admin_markdown: "Attempted overwrite.",
  ai_facts_markdown: "Attempted AI overwrite."
}, editor);
assert.equal(getEntryBySlug(db, "public-help", admin).admin_markdown, "Admin-only implementation notes.");
assert.equal(getEntryBySlug(db, "public-help", admin).ai_facts_markdown, "Admin AI fact.");

const proposed = createEntry(db, {
  title: "Draft from editor",
  slug: "draft-from-editor",
  summary: "Editor draft",
  user_markdown: "Draft content."
}, editor);
assert.equal(proposed.status, "draft");
assert.equal(proposed.review_state, "review_pending");
assert.equal(listEntries(db, {}, user).some((item) => item.slug === "draft-from-editor"), false);
assert.equal(listEntries(db, {}, editor, { management: true }).some((item) => item.slug === "draft-from-editor"), true);

updateEntry(db, "draft-from-editor", {
  title: "Draft from editor",
  slug: "draft-from-editor",
  summary: "Updated editor draft",
  user_markdown: "Updated draft content.",
  change_note: "Editor update."
}, editor);
assert.equal(listVersions(db, proposed.id, admin).length, 2);
const contributorVersions = listVersions(db, proposed.id, editor);
assert.equal(contributorVersions[0].next.admin_markdown, undefined);
assert.equal(contributorVersions[0].next_json, undefined);
assert.equal(contributorVersions[0].changed_by, undefined);

const restored = restoreVersion(db, proposed.id, 1, admin, "Test restore.");
assert.equal(restored.summary, "Editor draft");
assert.equal(restored.user_markdown, "Draft content.");
assert.equal(listVersions(db, proposed.id, admin).length, 3);
assert.equal(searchForAi(db, "draft", admin).length, 0);

setEntryWorkflow(db, "draft-from-editor", "review", admin);
setEntryWorkflow(db, "draft-from-editor", "publish", admin);
assert.equal(getEntryBySlug(db, "draft-from-editor", user).summary, "Editor draft");

assert(searchForAi(db, "currency", mod).some((item) => item.slug === "currency-basics"));
assert(searchForAi(db, "currncy", mod).some((item) => item.slug === "currency-basics"));
assert.equal(searchForAi(db, "admin-only economy", user).length, 0);
assert(renderMarkdown("<script>alert(1)</script> **safe**").includes("&lt;script&gt;"));
assert.equal(renderMarkdown("[bad](javascript:alert(1))").includes("javascript:"), false);

const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-okf-"));
ensureKnowledgeDirs(tempRoot);
const commandsKnowledgePath = path.join(tempRoot, "knowledge", "core", "commands.md");
const currencyKnowledgePath = path.join(tempRoot, "knowledge", "community", "currency.md");
const correctionKnowledgePath = path.join(tempRoot, "knowledge", "corrections", "currency.md");
fs.writeFileSync(commandsKnowledgePath, [
  "---",
  "id: core.commands",
  "title: Core commands",
  "scope: core",
  "status: active",
  "priority: 1",
  "visibility: user",
  "tags: commands, help",
  "generated: true",
  "editable: false",
  "---",
  "# Commands",
  "Use !help to list available commands.",
  "The public currency is {{community.currency.primary_name}}.",
  "The admin-only reference is {{community.admin-secret.secret_value}}."
].join("\n"));
fs.writeFileSync(currencyKnowledgePath, [
  "---",
  "id: community.currency",
  "title: Community currency",
  "scope: community",
  "status: active",
  "priority: 5",
  "visibility: user",
  "tags: currency, coins",
  "primary_name: coins",
  "---",
  "# Coins",
  "The community currency is coins, not points."
].join("\n"));
fs.writeFileSync(correctionKnowledgePath, [
  "---",
  "id: correction.currency-name",
  "title: Currency correction",
  "scope: corrections",
  "status: active",
  "priority: 10",
  "visibility: user",
  "tags: currency",
  "---",
  "# Currency name",
  "Always call the currency coins."
].join("\n"));
fs.writeFileSync(path.join(tempRoot, "knowledge", "community", "admin-secret.md"), [
  "---",
  "id: community.admin-secret",
  "title: Admin secret",
  "scope: community",
  "status: active",
  "visibility: admin",
  "secret_value: hidden-admin-context",
  "---",
  "# Secret",
  "Only admins should retrieve this."
].join("\n"));
fs.writeFileSync(path.join(tempRoot, "knowledge", "community", "people.md"), [
  "---",
  "id: community.people",
  "title: Community people",
  "scope: community",
  "status: active",
  "priority: 20",
  "visibility: user",
  "tags: people, identity, ookamikuntv, jenni",
  "---",
  "# Community people",
  "## OokamiKunTV",
  "OokamiKunTV is a known community contact in Lumi's local community knowledge.",
  "## Jenni",
  "Jenni is also known as Jejeee in this community."
].join("\n"));

const fileEntries = loadKnowledgeEntries(tempRoot);
assert.equal(fileEntries.length, 5);
assert(fileEntries.every((item) => item.chunks.length >= 1));
const userPlaceholders = listKnowledgePlaceholders({ user, rootDir: tempRoot, includeHidden: true });
assert(userPlaceholders.includes("{{community.currency.primary_name}}"));
assert.equal(userPlaceholders.includes("{{community.currency.scope}}"), false);
assert.equal(userPlaceholders.includes("{{plugins.plugin-throne-wishlist.scope}}"), false);
assert.equal(userPlaceholders.includes("{{community.admin-secret.secret_value}}"), false);
const adminPlaceholders = listKnowledgePlaceholders({ user: admin, rootDir: tempRoot, includeHidden: true });
assert(adminPlaceholders.includes("{{community.admin-secret.secret_value}}"));
const currencyResults = searchFileKnowledge({ query: "currency coins", user, rootDir: tempRoot, limit: 5 });
assert.equal(currencyResults[0].id, "correction.currency-name");
assert(currencyResults[0].source_metadata.path.endsWith("corrections/currency.md"));
assert(currencyResults[0].source_metadata.heading);
assert(Number.isFinite(currencyResults[0].source_metadata.score));
const identityResults = searchFileKnowledge({ query: "Who is OokamiKunTV?", user, rootDir: tempRoot, limit: 5 });
assert.equal(identityResults[0].id, "community.people");
assert(identityResults[0].facts.includes("OokamiKunTV is a known community contact"));
const typoIdentityResults = searchFileKnowledge({ query: "Who is jejee?", user, rootDir: tempRoot, limit: 5 });
assert.equal(typoIdentityResults[0].id, "community.people");
assert.equal(typoIdentityResults[0].source_metadata.heading, "Jenni");
assert(typoIdentityResults[0].facts.includes("Jejeee"));
assert.equal(searchFileKnowledge({ query: "communitypeople", user, rootDir: tempRoot, limit: 5 })[0].id, "community.people");
assert.equal(searchFileKnowledge({ query: "Can you help me?", user, rootDir: tempRoot, limit: 5 }).length, 0);
const commandUserResult = searchFileKnowledge({ query: "commands", user, rootDir: tempRoot, limit: 5 })[0];
assert(commandUserResult.facts.includes("The public currency is coins."));
assert(commandUserResult.facts.includes("[missing OKF reference]"));
const commandAdminResult = searchFileKnowledge({ query: "commands", user: admin, rootDir: tempRoot, limit: 5 })[0];
assert(commandAdminResult.facts.includes("The admin-only reference is hidden-admin-context."));
assert.equal(searchFileKnowledge({ query: "secret", user, rootDir: tempRoot, limit: 5 }).length, 0);
assert.equal(searchFileKnowledge({ query: "secret", user: admin, rootDir: tempRoot, limit: 5 })[0].id, "community.admin-secret");
const visibleCommunity = listVisibleCommunityKnowledge({ user, rootDir: tempRoot });
assert(visibleCommunity.some((item) => item.id === "community.people"));
assert(visibleCommunity.some((item) => item.id === "community.currency"));
assert.equal(visibleCommunity.some((item) => item.id === "community.admin-secret"), false);
assert(listVisibleCommunityKnowledge({ user: admin, rootDir: tempRoot }).some((item) => item.id === "community.admin-secret"));
assert.deepEqual(
  listVisibleCommunityKnowledge({ user, filters: { q: "jejeee" }, rootDir: tempRoot }).map((item) => item.id),
  ["community.people"]
);
assert.deepEqual(
  listVisibleCommunityKnowledge({ user, filters: { q: "Who is jejee?" }, rootDir: tempRoot }).map((item) => item.id),
  ["community.people"]
);
assert.deepEqual(listVisibleCommunityKnowledge({ user: null, rootDir: tempRoot }), []);
assert.equal(getVisibleCommunityKnowledge({ user, slug: "people", rootDir: tempRoot }).id, "community.people");
assert(getVisibleCommunityKnowledge({ user, slug: "community.people", rootDir: tempRoot }).user_markdown.includes("Jejeee"));
assert.equal(getVisibleCommunityKnowledge({ user, slug: "admin-secret", rootDir: tempRoot }), null);
assert.equal(getVisibleCommunityKnowledge({ user: admin, slug: "admin-secret", rootDir: tempRoot }).visibility, "admin");
assert.equal(getVisibleCommunityKnowledge({ user, slug: "people", rootDir: tempRoot }).href, "/plugins/okf/community/people");
const savedCommunity = saveCommunityKnowledgeFile(tempRoot, {
  title: "Community roles",
  slug: "community-roles",
  id: "community.roles",
  category: "Community",
  visibility: "user",
  status: "active",
  priority: "7",
  tags: "roles, community",
  body: "# Roles\nCommunity roles are documented here."
});
assert.equal(savedCommunity.id, "community.roles");
assert.equal(getCommunityKnowledgeFile(tempRoot, "community-roles").title, "Community roles");
assert(listCommunityKnowledgeFiles(tempRoot).some((item) => item.slug === "community-roles"));
const renamedCommunity = saveCommunityKnowledgeFile(tempRoot, {
  existing_slug: "community-roles",
  title: "Community role guide",
  slug: "role-guide",
  id: "community.roles",
  category: "Community",
  visibility: "user",
  status: "active",
  tags: "roles",
  body: "# Roles\nRenamed community role guide."
});
assert.equal(renamedCommunity.slug, "role-guide");
assert(fs.existsSync(path.join(tempRoot, "knowledge", "community", "role-guide.md")));
assert.equal(fs.existsSync(path.join(tempRoot, "knowledge", "community", "community-roles.md")), false);
fs.writeFileSync(path.join(tempRoot, "knowledge", "community", "generated.md"), [
  "---",
  "id: community.generated",
  "title: Generated community file",
  "scope: community",
  "generated: true",
  "editable: false",
  "---",
  "# Generated"
].join("\n"));
assert.throws(() => saveCommunityKnowledgeFile(tempRoot, {
  existing_slug: "community-generated",
  title: "Blocked",
  body: "# Blocked"
}), /not editable/);
const savedCorrection = saveCorrectionKnowledgeFile(tempRoot, {
  title: "Feedback correction",
  slug: "feedback-correction",
  source_feedback_id: "feedback-1",
  source_feedback_url: "/admin/feedback?feedback=feedback-1",
  visibility: "user",
  priority: "120",
  body: "# Feedback correction\nUse the corrected behavior from reviewed feedback."
});
assert.equal(savedCorrection.id, "correction.feedback-correction");
assert.equal(savedCorrection.path, "knowledge/corrections/feedback-correction.md");
assert.equal(savedCorrection.frontmatter.source_feedback_id, "feedback-1");
assert.equal(listCorrectionKnowledgeFiles(tempRoot)[0].id, "correction.feedback-correction");
assert.equal(searchFileKnowledge({ query: "corrected behavior", user, rootDir: tempRoot, limit: 5 })[0].id, "correction.feedback-correction");
fs.writeFileSync(currencyKnowledgePath, [
  "---",
  "id: community.currency",
  "title: Community currency",
  "scope: community",
  "status: active",
  "priority: 5",
  "visibility: user",
  "tags: currency, coins",
  "primary_name: gold stars",
  "---",
  "# Coins",
  "The community currency is gold stars, not points."
].join("\n"));
assert(searchFileKnowledge({ query: "commands", user, rootDir: tempRoot, limit: 5 })[0].facts.includes("The public currency is gold stars."));
fs.rmSync(correctionKnowledgePath, { force: true });
assert.equal(searchFileKnowledge({ query: "currency coins", user, rootDir: tempRoot, limit: 5 }).some((item) => item.id === "correction.currency-name"), false);
fs.rmSync(tempRoot, { recursive: true, force: true });

const migrationRoot = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-okf-migrate-"));
ensureKnowledgeDirs(migrationRoot);
const legacyPlaceholderPath = path.join(migrationRoot, "knowledge", "community", "legacy.md");
fs.writeFileSync(legacyPlaceholderPath, [
  "---",
  "id: community.legacy",
  "title: Legacy placeholder",
  "scope: community",
  "status: active",
  "visibility: user",
  "---",
  "# Legacy",
  "The old token is {community.currency.primary_name}.",
  "The new token stays {{community.currency.primary_name}}."
].join("\n"));
const migratedPlaceholders = migrateSingleBracePlaceholders(migrationRoot);
assert.equal(migratedPlaceholders.changed, 1);
const migratedPlaceholderContent = fs.readFileSync(legacyPlaceholderPath, "utf8");
assert(migratedPlaceholderContent.includes("The old token is {{community.currency.primary_name}}."));
assert(migratedPlaceholderContent.includes("The new token stays {{community.currency.primary_name}}."));
fs.rmSync(migrationRoot, { recursive: true, force: true });

const generatedRoot = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-okf-generated-"));
fs.mkdirSync(path.join(generatedRoot, "plugins", "test-plugin"), { recursive: true });
fs.writeFileSync(path.join(generatedRoot, "package.json"), JSON.stringify({
  name: "lumi-test",
  version: "1.2.3",
  description: "Generated test core."
}, null, 2));
fs.writeFileSync(path.join(generatedRoot, "README.md"), "# Lumi Test\n\nGenerated README.");
fs.mkdirSync(path.join(generatedRoot, "src", "web"), { recursive: true });
fs.writeFileSync(path.join(generatedRoot, "src", "web", "server.js"), "app.get('/admin/test', handler);\napp.post('/api/test', handler);\n");
fs.writeFileSync(path.join(generatedRoot, "plugins", "test-plugin", "plugin.json"), JSON.stringify({
  id: "test-plugin",
  name: "Test Plugin",
  version: "0.0.1",
  description: "Generated plugin knowledge."
}, null, 2));
fs.writeFileSync(path.join(generatedRoot, "plugins", "test-plugin", "index.js"), [
  "router.get('/settings', handler);",
  "commandRouter.registerCommands('test-plugin', [{ trigger: 'test', description: 'Test command.' }]);"
].join("\n"));
const generated = generateKnowledgeFiles(generatedRoot);
const normalizedGenerated = generated.map((file) => file.replace(/\\/g, "/"));
assert(normalizedGenerated.some((file) => file.endsWith("knowledge/core/lumi-core.md")));
assert(normalizedGenerated.some((file) => file.endsWith("knowledge/plugins/test-plugin.md")));
const generatedCore = fs.readFileSync(path.join(generatedRoot, "knowledge", "core", "lumi-core.md"), "utf8");
assert(generatedCore.includes("GET /admin/test"));
assert(generatedCore.includes("## Route Reference"));
assert(generatedCore.includes("### GET /admin/test"));
assert(generatedCore.includes("Response format:"));
const generatedPlugin = fs.readFileSync(path.join(generatedRoot, "knowledge", "plugins", "test-plugin.md"), "utf8");
assert(generatedPlugin.includes("Test Plugin"));
assert(generatedPlugin.includes("### GET /plugins/test-plugin/settings"));
assert(generatedPlugin.includes("Inputs:"));
assert(generatedPlugin.includes("test"));
assert.equal(generatedCore.includes("updated_at:"), false);
assert.deepEqual(generateKnowledgeFiles(generatedRoot), []);
assert.equal(
  searchFileKnowledge({ query: "testplugin settings", user, rootDir: generatedRoot, limit: 5 })[0].source_metadata.heading,
  "GET /plugins/test-plugin/settings"
);
fs.writeFileSync(path.join(generatedRoot, "knowledge", "plugins", "test-plugin.md"), [
  "---",
  "id: plugin.test-plugin",
  "title: Manual Test Plugin",
  "generated: false",
  "editable: true",
  "---",
  "# Manual"
].join("\n"));
generateKnowledgeFiles(generatedRoot);
assert(fs.readFileSync(path.join(generatedRoot, "knowledge", "plugins", "test-plugin.md"), "utf8").includes("Manual Test Plugin"));
fs.rmSync(generatedRoot, { recursive: true, force: true });

const grant = listPermissions(db, admin).find((row) => row.user_id === "editor-1" && !row.revoked_at);
revokePermission(db, grant.id, admin);
assert.equal(accessForUser(db, editor).canEdit, false);

db.close();
console.log("OKF plugin verification passed.");
