/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/

"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __esm = (fn, res) => function __init() {
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// src/ui/panel-utils.ts
function getDisplayPath(fullPath) {
  const cleanPath = fullPath.startsWith("/") ? fullPath.slice(1) : fullPath;
  const parts = cleanPath.split("/");
  if (parts.length <= 1) {
    return cleanPath;
  }
  return parts.slice(-2).join("/");
}
function getVaultFoldersInfo(scanDirectories, fileCount) {
  let baseText = "Vault folders: ";
  if (!scanDirectories || scanDirectories.trim() === "") {
    baseText += "all";
  } else {
    const folders = scanDirectories.split(",").map((f) => f.trim()).filter((f) => f.length > 0);
    baseText += folders.join(", ");
  }
  if (fileCount !== void 0) {
    baseText += `; ${fileCount} files analyzed`;
  }
  return baseText;
}
function sortFiles(files, sortType) {
  const sortedFiles = [...files];
  switch (sortType) {
    case "warnings-desc":
      sortedFiles.sort((a, b) => {
        if (b.warningsCount !== a.warningsCount) {
          return b.warningsCount - a.warningsCount;
        }
        if (b.issuesCount !== a.issuesCount) {
          return b.issuesCount - a.issuesCount;
        }
        if (b.noticesCount !== a.noticesCount) {
          return b.noticesCount - a.noticesCount;
        }
        const aFileName = a.file.split("/").pop() || "";
        const bFileName = b.file.split("/").pop() || "";
        return aFileName.localeCompare(bFileName);
      });
      break;
    case "warnings-asc":
      sortedFiles.sort((a, b) => {
        if (a.warningsCount !== b.warningsCount) {
          return a.warningsCount - b.warningsCount;
        }
        if (a.issuesCount !== b.issuesCount) {
          return a.issuesCount - b.issuesCount;
        }
        if (a.noticesCount !== b.noticesCount) {
          return a.noticesCount - b.noticesCount;
        }
        const aFileName = a.file.split("/").pop() || "";
        const bFileName = b.file.split("/").pop() || "";
        return aFileName.localeCompare(bFileName);
      });
      break;
    case "issues-desc":
      sortedFiles.sort((a, b) => {
        if (b.issuesCount !== a.issuesCount) {
          return b.issuesCount - a.issuesCount;
        }
        if (b.warningsCount !== a.warningsCount) {
          return b.warningsCount - a.warningsCount;
        }
        if (b.noticesCount !== a.noticesCount) {
          return b.noticesCount - a.noticesCount;
        }
        const aFileName = a.file.split("/").pop() || "";
        const bFileName = b.file.split("/").pop() || "";
        return aFileName.localeCompare(bFileName);
      });
      break;
    case "issues-asc":
      sortedFiles.sort((a, b) => {
        if (a.issuesCount !== b.issuesCount) {
          return a.issuesCount - b.issuesCount;
        }
        if (a.warningsCount !== b.warningsCount) {
          return a.warningsCount - b.warningsCount;
        }
        if (a.noticesCount !== b.noticesCount) {
          return a.noticesCount - b.noticesCount;
        }
        const aFileName = a.file.split("/").pop() || "";
        const bFileName = b.file.split("/").pop() || "";
        return aFileName.localeCompare(bFileName);
      });
      break;
    case "notices-desc":
      sortedFiles.sort((a, b) => {
        if (b.noticesCount !== a.noticesCount) {
          return b.noticesCount - a.noticesCount;
        }
        if (b.issuesCount !== a.issuesCount) {
          return b.issuesCount - a.issuesCount;
        }
        if (b.warningsCount !== a.warningsCount) {
          return b.warningsCount - a.warningsCount;
        }
        const aFileName = a.file.split("/").pop() || "";
        const bFileName = b.file.split("/").pop() || "";
        return aFileName.localeCompare(bFileName);
      });
      break;
    case "notices-asc":
      sortedFiles.sort((a, b) => {
        if (a.noticesCount !== b.noticesCount) {
          return a.noticesCount - b.noticesCount;
        }
        if (a.issuesCount !== b.issuesCount) {
          return a.issuesCount - b.issuesCount;
        }
        if (a.warningsCount !== b.warningsCount) {
          return a.warningsCount - b.warningsCount;
        }
        const aFileName = a.file.split("/").pop() || "";
        const bFileName = b.file.split("/").pop() || "";
        return aFileName.localeCompare(bFileName);
      });
      break;
    case "filename-asc":
      sortedFiles.sort((a, b) => {
        const aFileName = a.file.split("/").pop() || "";
        const bFileName = b.file.split("/").pop() || "";
        const nameCompare = aFileName.localeCompare(bFileName);
        if (nameCompare !== 0) return nameCompare;
        const aParts = a.file.split("/");
        const bParts = b.file.split("/");
        const aParent = aParts.length > 1 ? aParts[aParts.length - 2] || "" : "";
        const bParent = bParts.length > 1 ? bParts[bParts.length - 2] || "" : "";
        const parentCompare = aParent.localeCompare(bParent);
        if (parentCompare !== 0) return parentCompare;
        if (b.issuesCount !== a.issuesCount) {
          return b.issuesCount - a.issuesCount;
        }
        if (b.warningsCount !== a.warningsCount) {
          return b.warningsCount - a.warningsCount;
        }
        return b.noticesCount - a.noticesCount;
      });
      break;
    case "filename-desc":
      sortedFiles.sort((a, b) => {
        const aFileName = a.file.split("/").pop() || "";
        const bFileName = b.file.split("/").pop() || "";
        const nameCompare = bFileName.localeCompare(aFileName);
        if (nameCompare !== 0) return nameCompare;
        const aParts = a.file.split("/");
        const bParts = b.file.split("/");
        const aParent = aParts.length > 1 ? aParts[aParts.length - 2] || "" : "";
        const bParent = bParts.length > 1 ? bParts[bParts.length - 2] || "" : "";
        const parentCompare = bParent.localeCompare(aParent);
        if (parentCompare !== 0) return parentCompare;
        if (b.issuesCount !== a.issuesCount) {
          return b.issuesCount - a.issuesCount;
        }
        if (b.warningsCount !== a.warningsCount) {
          return b.warningsCount - a.warningsCount;
        }
        return b.noticesCount - a.noticesCount;
      });
      break;
  }
  return sortedFiles;
}
var init_panel_utils = __esm({
  "src/ui/panel-utils.ts"() {
    "use strict";
  }
});

// src/checks/utils/content-parser.ts
function removeCodeBlocks(content) {
  return content.replace(/```[\s\S]*?```/g, "").replace(/~~~[\s\S]*?~~~/g, "").replace(/`[^`\n]+`/g, "").replace(/<[^>]*>/g, "").replace(/^---\n[\s\S]*?\n---\n/, "").replace(/::\w+\{[^}]*\}/g, "");
}
function removeHtmlAttributes(content) {
  return content.replace(/```[\s\S]*?```/g, "").replace(/~~~[\s\S]*?~~~/g, "").replace(/`[^`\n]+`/g, "").replace(/<[^>]*>/g, "").replace(/^---\n[\s\S]*?\n---\n/, "").replace(/::\w+\{[^}]*\}/g, "");
}
var init_content_parser = __esm({
  "src/checks/utils/content-parser.ts"() {
    "use strict";
  }
});

// src/checks/utils/position-utils.ts
function findLineNumberForImage(content, imageMatch) {
  const lines = content.split("\n");
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    if (line && line.includes(imageMatch)) {
      return i + 1;
    }
  }
  return 1;
}
function getContextAroundLine(content, lineNumber, contextLines = 2) {
  const lines = content.split("\n");
  const start = Math.max(0, lineNumber - 1 - contextLines);
  const end = Math.min(lines.length, lineNumber - 1 + contextLines + 1);
  return lines.slice(start, end).join("\n");
}
var init_position_utils = __esm({
  "src/checks/utils/position-utils.ts"() {
    "use strict";
  }
});

// src/checks/alt-text-check.ts
function checkAltText(content, file, settings) {
  const results = [];
  if (!settings.checkAltText) {
    return Promise.resolve([]);
  }
  const cleanContent = removeCodeBlocks(content);
  const markdownImages = cleanContent.match(/!\[([^\]]*)\]\([^)]+\)/g);
  if (markdownImages) {
    const missingAltText = markdownImages.filter((match) => {
      var _a;
      const altText = (_a = match.match(/!\[([^\]]*)\]/)) == null ? void 0 : _a[1];
      return !altText || altText.trim() === "";
    });
    if (missingAltText.length > 0) {
      missingAltText.forEach((match, index) => {
        const pathMatch = match.match(/!\[[^\]]*\]\(([^)]+)\)/);
        const imagePath = pathMatch ? pathMatch[1] : match;
        const lineNumber = findLineNumberForImage(content, match);
        results.push({
          passed: false,
          message: `Media missing alt text: ${imagePath}`,
          suggestion: "Add descriptive alt text for accessibility",
          severity: "error",
          position: {
            line: lineNumber,
            searchText: match,
            context: getContextAroundLine(content, lineNumber)
          }
        });
      });
    }
  }
  const wikilinkImages = cleanContent.match(/!\[\[([^\]]+)\]\]/g);
  if (wikilinkImages) {
    const missingAltText = wikilinkImages.filter((match) => !match.includes("|"));
    if (missingAltText.length > 0) {
      missingAltText.forEach((match, index) => {
        const pathMatch = match.match(/!\[\[([^\]]+)\]\]/);
        const imagePath = pathMatch ? pathMatch[1] : match;
        const lineNumber = findLineNumberForImage(content, match);
        results.push({
          passed: false,
          message: `Wikilink media missing alt text: ${imagePath}`,
          suggestion: "Add alt text using ![[media.png|alt text]] syntax or consider using standard markdown media syntax",
          severity: "error",
          position: {
            line: lineNumber,
            searchText: match,
            context: getContextAroundLine(content, lineNumber)
          }
        });
      });
    }
  }
  const htmlImages = cleanContent.match(/<img[^>]*>/g);
  if (htmlImages) {
    const missingAlt = htmlImages.filter((img) => {
      const altMatch = img.match(/alt\s*=\s*["']([^"']*)["']/);
      return !altMatch || !altMatch[1] || altMatch[1].trim() === "";
    });
    if (missingAlt.length > 0) {
      missingAlt.forEach((img, index) => {
        const srcMatch = img.match(/src\s*=\s*["']([^"']*)["']/);
        const imagePath = srcMatch ? srcMatch[1] : "HTML img tag";
        const lineNumber = findLineNumberForImage(content, img);
        results.push({
          passed: false,
          message: `HTML media missing alt attribute: ${imagePath}`,
          suggestion: "Add alt attribute to HTML media tag",
          severity: "error",
          position: {
            line: lineNumber,
            searchText: img,
            context: getContextAroundLine(content, lineNumber)
          }
        });
      });
    }
  }
  if (results.length === 0) {
    const hasMedia = markdownImages && markdownImages.length > 0 || htmlImages && htmlImages.length > 0 || wikilinkImages && wikilinkImages.length > 0;
    if (hasMedia) {
      results.push({
        passed: true,
        message: "All media elements have alt text",
        severity: "info"
      });
    } else {
      results.push({
        passed: true,
        message: "No media elements in this post",
        severity: "info"
      });
    }
  }
  return Promise.resolve(results);
}
var init_alt_text_check = __esm({
  "src/checks/alt-text-check.ts"() {
    "use strict";
    init_content_parser();
    init_position_utils();
  }
});

// src/checks/link-checks.ts
function checkNakedLinks(content, file, settings) {
  const results = [];
  if (!settings.checkNakedLinks) {
    return Promise.resolve([]);
  }
  const cleanContent = removeHtmlAttributes(content);
  const nakedLinks = cleanContent.match(/(?<!\]\()(?<!https?:\/\/[^\s)]*\/)https?:\/\/[^\s)]+/g);
  if (nakedLinks) {
    nakedLinks.forEach((link, index) => {
      if (link.includes("web.archive.org/web/") || link.includes("archive.today/") || link.includes("archive.is/") || link.includes("web.archive.org/save/")) {
        return;
      }
      const lineNumber = findLineNumberForImage(content, link);
      results.push({
        passed: false,
        message: `Naked link found: ${link}`,
        suggestion: "Convert to markdown link format: [link text](url)",
        severity: "warning",
        position: {
          line: lineNumber,
          searchText: link,
          context: getContextAroundLine(content, lineNumber)
        }
      });
    });
  }
  if (results.length === 0) {
    results.push({
      passed: true,
      message: "No naked links found",
      severity: "info"
    });
  }
  return Promise.resolve(results);
}
function checkBrokenLinks(content, file, settings, app) {
  const results = [];
  if (!settings.checkBrokenLinks) {
    return Promise.resolve([]);
  }
  const allWikilinks = content.match(/\[\[([^\]]+)\]\]/g);
  if (allWikilinks) {
    const brokenLinks = [];
    for (const wikilink of allWikilinks) {
      const linkStart = content.indexOf(wikilink);
      if (linkStart === -1) continue;
      const contentBeforeLink = content.substring(0, linkStart);
      const codeBlockMatches = contentBeforeLink.match(/```[\s\S]*?```|~~~[\s\S]*?~~~/g);
      const openCodeBlocks = (contentBeforeLink.match(/```/g) || []).length - (codeBlockMatches || []).length;
      const openTildeBlocks = (contentBeforeLink.match(/~~~/g) || []).length - (codeBlockMatches || []).length;
      if (openCodeBlocks > 0 || openTildeBlocks > 0) {
        continue;
      }
      const linkMatch = wikilink.match(/\[\[([^\]]+)\]\]/);
      if (!linkMatch || !linkMatch[1]) continue;
      const linkText = linkMatch[1];
      let linkPath;
      let displayText;
      let anchor = null;
      if (linkText.includes("|")) {
        const parts = linkText.split("|");
        const linkPart = parts[0] || "";
        displayText = parts[1] || linkPart;
        if (linkPart.includes("#")) {
          const anchorParts = linkPart.split("#");
          linkPath = anchorParts[0] || "";
          anchor = anchorParts[1] || null;
        } else {
          linkPath = linkPart;
        }
      } else {
        if (linkText.includes("#")) {
          const anchorParts = linkText.split("#");
          linkPath = anchorParts[0] || "";
          anchor = anchorParts[1] || null;
          displayText = linkPath;
        } else {
          linkPath = linkText;
          displayText = linkText;
        }
      }
      if (app) {
        const resolvedLink = app.metadataCache.getFirstLinkpathDest(linkPath, file.path);
        if (!resolvedLink) {
          brokenLinks.push({
            link: wikilink,
            path: linkPath,
            displayText,
            anchor
          });
        }
      }
    }
    if (brokenLinks.length > 0) {
      brokenLinks.forEach(({ link, path, displayText, anchor }) => {
        const lineNumber = findLineNumberForImage(content, link);
        let suggestion = `Check if the file "${path}.md" exists or update the link`;
        if (anchor) {
          suggestion += ` (anchor: #${anchor})`;
        }
        results.push({
          passed: false,
          message: `Broken internal link: ${displayText}`,
          suggestion,
          severity: "error",
          position: {
            line: lineNumber,
            searchText: link,
            context: getContextAroundLine(content, lineNumber)
          }
        });
      });
    }
  }
  const allMarkdownLinks = content.match(/\[([^\]]+)\]\(([^)]+)\)/g);
  if (allMarkdownLinks) {
    for (const markdownLink of allMarkdownLinks) {
      const linkStart = content.indexOf(markdownLink);
      if (linkStart === -1) continue;
      const contentBeforeLink = content.substring(0, linkStart);
      const codeBlockMatches = contentBeforeLink.match(/```[\s\S]*?```|~~~[\s\S]*?~~~/g);
      const openCodeBlocks = (contentBeforeLink.match(/```/g) || []).length - (codeBlockMatches || []).length;
      const openTildeBlocks = (contentBeforeLink.match(/~~~/g) || []).length - (codeBlockMatches || []).length;
      if (openCodeBlocks > 0 || openTildeBlocks > 0) {
        continue;
      }
      const linkMatch = markdownLink.match(/\[([^\]]+)\]\(([^)]+)\)/);
      if (!linkMatch || !linkMatch[1] || !linkMatch[2]) continue;
      const linkText = linkMatch[1];
      const linkUrl = linkMatch[2];
      if (markdownLink.startsWith("![") || linkText.startsWith("![")) {
        continue;
      }
      if (!linkUrl.startsWith("http://") && !linkUrl.startsWith("https://") && !linkUrl.startsWith("mailto:") && !linkUrl.startsWith("#")) {
        if (settings.publishMode && linkUrl.startsWith("/")) {
          continue;
        }
        if (app) {
          let linkPath = linkUrl;
          let anchor = null;
          if (linkPath.includes("#")) {
            const anchorParts = linkPath.split("#");
            linkPath = anchorParts[0] || "";
            anchor = anchorParts[1] || null;
          }
          const resolvedLink = app.metadataCache.getFirstLinkpathDest(linkPath, file.path);
          if (!resolvedLink) {
            const lineNumber = findLineNumberForImage(content, markdownLink);
            let suggestedPath = linkPath.endsWith(".md") ? linkPath : linkPath + ".md";
            let suggestion = `Check if the file "${suggestedPath}" exists or update the link`;
            if (anchor) {
              suggestion += ` (anchor: #${anchor})`;
            }
            results.push({
              passed: false,
              message: `Broken internal link: ${linkText}`,
              suggestion,
              severity: "error",
              position: {
                line: lineNumber,
                searchText: markdownLink,
                context: getContextAroundLine(content, lineNumber)
              }
            });
          }
        }
      }
    }
  }
  if (results.length === 0) {
    results.push({
      passed: true,
      message: "No broken internal links found",
      severity: "info"
    });
  }
  return Promise.resolve(results);
}
async function checkPotentiallyBrokenLinks(content, file, settings, app) {
  const results = [];
  if (!settings.checkPotentiallyBrokenLinks || !settings.publishMode) {
    return Promise.resolve([]);
  }
  const brokenLinksResults = await checkBrokenLinks(content, file, settings, app);
  const definitelyBrokenLinks = /* @__PURE__ */ new Set();
  brokenLinksResults.forEach((result) => {
    var _a;
    if (!result.passed && ((_a = result.position) == null ? void 0 : _a.searchText)) {
      definitelyBrokenLinks.add(result.position.searchText);
    }
  });
  let pos = 0;
  while (pos < content.length) {
    const openBracket = content.indexOf("[", pos);
    if (openBracket === -1) break;
    const contentBeforeBracket = content.substring(0, openBracket);
    const codeBlockMatches = contentBeforeBracket.match(/```[\s\S]*?```|~~~[\s\S]*?~~~/g);
    const openCodeBlocks = (contentBeforeBracket.match(/```/g) || []).length - (codeBlockMatches || []).length;
    const openTildeBlocks = (contentBeforeBracket.match(/~~~/g) || []).length - (codeBlockMatches || []).length;
    if (openCodeBlocks > 0 || openTildeBlocks > 0) {
      pos = openBracket + 1;
      continue;
    }
    let bracketCount = 1;
    let closeBracket = openBracket + 1;
    while (closeBracket < content.length && bracketCount > 0) {
      if (content[closeBracket] === "[") {
        bracketCount++;
      } else if (content[closeBracket] === "]") {
        bracketCount--;
      }
      closeBracket++;
    }
    if (bracketCount > 0) {
      pos = openBracket + 1;
      continue;
    }
    if (closeBracket < content.length && content[closeBracket] === "(") {
      let parenCount = 1;
      let closeParen = closeBracket + 1;
      while (closeParen < content.length && parenCount > 0) {
        if (content[closeParen] === "(") {
          parenCount++;
        } else if (content[closeParen] === ")") {
          parenCount--;
        }
        closeParen++;
      }
      if (parenCount === 0) {
        const linkText = content.substring(openBracket + 1, closeBracket - 1);
        const linkUrl = content.substring(closeBracket + 1, closeParen - 1);
        const fullLink = content.substring(openBracket, closeParen);
        if (linkText.startsWith("![")) {
          pos = closeParen;
          continue;
        }
        if (settings.publishMode && linkUrl.startsWith("/") && !linkUrl.startsWith("http://") && !linkUrl.startsWith("https://")) {
          const lineNumber = findLineNumberForImage(content, fullLink);
          results.push({
            passed: true,
            message: `Relative path link: ${linkText}`,
            suggestion: "This link may be valid for static site generators but may not work in Obsidian",
            severity: "notice",
            position: {
              line: lineNumber,
              searchText: fullLink,
              context: getContextAroundLine(content, lineNumber)
            }
          });
        }
        pos = closeParen;
      } else {
        pos = closeBracket;
      }
    } else {
      pos = closeBracket;
    }
  }
  const allWikilinks = content.match(/\[\[([^\]]+)\]\]/g);
  if (allWikilinks) {
    const potentiallyBroken = [];
    for (const wikilink of allWikilinks) {
      const linkStart = content.indexOf(wikilink);
      if (linkStart === -1) continue;
      const contentBeforeLink = content.substring(0, linkStart);
      const codeBlockMatches = contentBeforeLink.match(/```[\s\S]*?```|~~~[\s\S]*?~~~/g);
      const openCodeBlocks = (contentBeforeLink.match(/```/g) || []).length - (codeBlockMatches || []).length;
      const openTildeBlocks = (contentBeforeLink.match(/~~~/g) || []).length - (codeBlockMatches || []).length;
      if (openCodeBlocks > 0 || openTildeBlocks > 0) {
        continue;
      }
      const linkMatch = wikilink.match(/\[\[([^\]]+)\]\]/);
      if (!linkMatch || !linkMatch[1]) continue;
      if (definitelyBrokenLinks.has(wikilink)) {
        continue;
      }
      const linkText = linkMatch[1];
      let linkPath;
      let displayText;
      let anchor = null;
      if (linkText.includes("|")) {
        const parts = linkText.split("|");
        const linkPart = parts[0] || "";
        displayText = parts[1] || linkPart;
        if (linkPart.includes("#")) {
          const anchorParts = linkPart.split("#");
          linkPath = anchorParts[0] || "";
          anchor = anchorParts[1] || null;
        } else {
          linkPath = linkPart;
        }
      } else {
        if (linkText.includes("#")) {
          const anchorParts = linkText.split("#");
          linkPath = anchorParts[0] || "";
          anchor = anchorParts[1] || null;
          displayText = linkPath;
        } else {
          linkPath = linkText;
          displayText = linkText;
        }
      }
      if (app && linkPath) {
        const resolvedLink = app.metadataCache.getFirstLinkpathDest(linkPath, file.path);
        if (!resolvedLink) {
          const allFiles = app.vault.getMarkdownFiles();
          const similarFiles = allFiles.filter(
            (f) => f.path.toLowerCase().includes(linkPath.toLowerCase()) || f.basename.toLowerCase().includes(linkPath.toLowerCase())
          );
          if (similarFiles.length > 0) {
            potentiallyBroken.push({
              link: wikilink,
              path: linkPath,
              displayText,
              anchor,
              suggestions: similarFiles.map((f) => f.path)
            });
          }
        }
      }
    }
    if (potentiallyBroken.length > 0) {
      potentiallyBroken.forEach(({ link, path, displayText, suggestions }) => {
        const lineNumber = findLineNumberForImage(content, link);
        results.push({
          passed: false,
          message: `Potentially broken link: ${displayText}`,
          suggestion: `Consider these similar files: ${suggestions.slice(0, 3).join(", ")}`,
          severity: "warning",
          position: {
            line: lineNumber,
            searchText: link,
            context: getContextAroundLine(content, lineNumber)
          }
        });
      });
    }
  }
  if (results.length === 0) {
    results.push({
      passed: true,
      message: "No potentially broken links found",
      severity: "info"
    });
  }
  return Promise.resolve(results);
}
var init_link_checks = __esm({
  "src/checks/link-checks.ts"() {
    "use strict";
    init_content_parser();
    init_position_utils();
  }
});

// src/checks/external-link-checks.ts
function isValidUrl(url) {
  if (url.includes("&gt;") || url.includes("&lt;") || url.includes("&amp;") || url.includes("&quot;") || url.includes("&apos;") || url.includes("&nbsp;")) {
    return false;
  }
  if (url.includes("<") || url.includes(">") || url.includes('"') || url.includes("'")) {
    return false;
  }
  if (url.endsWith("&gt;") || url.endsWith("&lt;") || url.endsWith("&amp;") || url.endsWith("&quot;") || url.endsWith("&apos;") || url.endsWith("&nbsp;")) {
    return false;
  }
  if (url.includes("/a&gt;") || url.includes("/p&gt;") || url.includes("/div&gt;") || url.includes("/span&gt;") || url.includes("/img&gt;") || url.includes("/script&gt;")) {
    return false;
  }
  try {
    const urlObj = new URL(url);
    if (urlObj.protocol !== "http:" && urlObj.protocol !== "https:") {
      return false;
    }
    if (!urlObj.hostname || urlObj.hostname.length === 0) {
      return false;
    }
    if (urlObj.hostname.includes("<") || urlObj.hostname.includes(">") || urlObj.hostname.includes('"') || urlObj.hostname.includes("'")) {
      return false;
    }
    return true;
  } catch (e) {
    return false;
  }
}
function extractValidExternalLinks(content) {
  const cleanContent = removeCodeBlocks(content);
  const externalLinks = [];
  let pos = 0;
  while (pos < cleanContent.length) {
    const openBracket = cleanContent.indexOf("[", pos);
    if (openBracket === -1) break;
    let bracketCount = 1;
    let closeBracket = openBracket + 1;
    while (closeBracket < cleanContent.length && bracketCount > 0) {
      if (cleanContent[closeBracket] === "[") {
        bracketCount++;
      } else if (cleanContent[closeBracket] === "]") {
        bracketCount--;
      }
      closeBracket++;
    }
    if (bracketCount > 0) {
      pos = openBracket + 1;
      continue;
    }
    if (closeBracket < cleanContent.length && cleanContent[closeBracket] === "(") {
      let parenCount = 1;
      let closeParen = closeBracket + 1;
      while (closeParen < cleanContent.length && parenCount > 0) {
        if (cleanContent[closeParen] === "(") {
          parenCount++;
        } else if (cleanContent[closeParen] === ")") {
          parenCount--;
        }
        closeParen++;
      }
      if (parenCount === 0) {
        const linkUrl = cleanContent.substring(closeBracket + 1, closeParen - 1);
        if (linkUrl.startsWith("http://") || linkUrl.startsWith("https://")) {
          if (isValidUrl(linkUrl)) {
            externalLinks.push(linkUrl);
          }
        }
        pos = closeParen;
      } else {
        pos = closeBracket;
      }
    } else {
      pos = closeBracket;
    }
  }
  const nakedUrls = cleanContent.match(/https?:\/\/[^\s)\]]+/g);
  if (nakedUrls) {
    const markdownLinkUrls = new Set(externalLinks);
    nakedUrls.forEach((url) => {
      if (markdownLinkUrls.has(url)) {
        return;
      }
      if (url.includes("web.archive.org/web/") || url.includes("archive.today/") || url.includes("archive.is/") || url.includes("web.archive.org/save/")) {
        return;
      }
      if (isValidUrl(url)) {
        externalLinks.push(url);
      }
    });
  }
  return [...new Set(externalLinks)];
}
function checkExternalLinks(content, file, settings) {
  const results = [];
  if (!settings.checkExternalLinks) {
    return Promise.resolve([]);
  }
  const uniqueLinks = extractValidExternalLinks(content);
  if (uniqueLinks.length > 0) {
    uniqueLinks.forEach((url, index) => {
      const lineNumber = findLineNumberForImage(content, url);
      results.push({
        passed: true,
        message: `External link: ${url}`,
        suggestion: "To find if any of these are broken, use the 'Check external links for 404s' button to trigger it.",
        severity: "notice",
        position: {
          line: lineNumber,
          searchText: url,
          context: getContextAroundLine(content, lineNumber)
        }
      });
    });
  } else {
    results.push({
      passed: true,
      message: "No external links found",
      severity: "info"
    });
  }
  return Promise.resolve(results);
}
async function checkExternalBrokenLinks(content, file, settings, abortController) {
  const results = [];
  if (!settings.enableExternalLinkVaultCheck) {
    return Promise.resolve([]);
  }
  const uniqueLinks = extractValidExternalLinks(content);
  if (uniqueLinks.length === 0) {
    results.push({
      passed: true,
      message: "No external links found",
      severity: "info"
    });
    return Promise.resolve(results);
  }
  const checkTasks = uniqueLinks.map(async (url) => {
    if (abortController == null ? void 0 : abortController.signal.aborted) {
      return null;
    }
    const cached = externalLinkCache.get(url);
    if (cached && Date.now() - cached.timestamp < EXTERNAL_LINK_CACHE_TTL) {
      return cached.result;
    }
    return await limiter.schedule(async () => {
      if (abortController == null ? void 0 : abortController.signal.aborted) return null;
      let linkIsBroken = false;
      let errorMessage = "";
      let suggestion = "";
      let severity = "error";
      try {
        const response = await (0, import_obsidian2.requestUrl)({
          url,
          headers: {
            "User-Agent": "Mozilla/5.0 (compatible; Obsidian-SEO-Plugin/1.0)"
          },
          throw: false
          // Handle status codes manually
        });
        if (response.status === 429) {
          linkIsBroken = true;
          errorMessage = `External link rate limited (429): ${url}`;
          suggestion = "The server is limiting requests. Try again later or run the check on fewer links.";
          severity = "warning";
        } else if (response.status === 403) {
          linkIsBroken = true;
          errorMessage = `External link access restricted (403): ${url}`;
          suggestion = "This link returned a 403 Forbidden error. This may indicate bot protection rather than a broken link.";
          severity = "warning";
        } else if (response.status >= 400) {
          linkIsBroken = true;
          errorMessage = `External link error (${response.status}): ${url}`;
          suggestion = `This link returned a ${response.status} error.`;
        }
      } catch (error) {
        linkIsBroken = true;
        errorMessage = `External link unreachable: ${url}`;
        suggestion = "Check if the URL is correct or if the server is down.";
        severity = "warning";
      }
      let result = null;
      if (linkIsBroken) {
        const lines = content.split("\n");
        let lineNumber = 1;
        for (let i = 0; i < lines.length; i++) {
          const line = lines[i];
          if (line && line.includes(url)) {
            lineNumber = i + 1;
            break;
          }
        }
        result = {
          passed: false,
          message: errorMessage,
          suggestion,
          severity,
          position: {
            line: lineNumber,
            searchText: url,
            context: getContextAroundLine(content, lineNumber)
          }
        };
      }
      externalLinkCache.set(url, { result, timestamp: Date.now() });
      return result;
    });
  });
  const taskResults = await Promise.all(checkTasks);
  taskResults.forEach((r) => {
    if (r) results.push(r);
  });
  if (results.length === 0 && uniqueLinks.length > 0) {
    results.push({
      passed: true,
      message: `Found ${uniqueLinks.length} external link(s), all appear to resolve and are working`,
      severity: "info"
    });
  } else if (uniqueLinks.length > 0) {
    const brokenCount = results.filter((r) => !r.passed).length;
    const workingLinks = uniqueLinks.length - brokenCount;
    if (workingLinks > 0) {
      results.push({
        passed: true,
        message: `${workingLinks} of ${uniqueLinks.length} external link(s) are working`,
        severity: "info"
      });
    }
  }
  return results;
}
var import_obsidian2, EXTERNAL_LINK_CACHE_TTL, externalLinkCache, MAX_CONCURRENT_REQUESTS, REQUEST_DELAY_MS, RateLimiter, limiter;
var init_external_link_checks = __esm({
  "src/checks/external-link-checks.ts"() {
    "use strict";
    import_obsidian2 = require("obsidian");
    init_position_utils();
    init_content_parser();
    EXTERNAL_LINK_CACHE_TTL = 36e5;
    externalLinkCache = /* @__PURE__ */ new Map();
    MAX_CONCURRENT_REQUESTS = 2;
    REQUEST_DELAY_MS = 600;
    RateLimiter = class {
      constructor() {
        this.queue = [];
        this.activeRequests = 0;
        this.lastRequestTime = 0;
      }
      async schedule(fn) {
        return new Promise((resolve, reject) => {
          this.queue.push({ fn, resolve, reject });
          this.processQueue();
        });
      }
      async processQueue() {
        if (this.activeRequests >= MAX_CONCURRENT_REQUESTS || this.queue.length === 0) {
          return;
        }
        const now = Date.now();
        const timeSinceLastRequest = now - this.lastRequestTime;
        if (timeSinceLastRequest < REQUEST_DELAY_MS) {
          setTimeout(() => this.processQueue(), REQUEST_DELAY_MS - timeSinceLastRequest);
          return;
        }
        const { fn, resolve, reject } = this.queue.shift();
        this.activeRequests++;
        this.lastRequestTime = Date.now();
        try {
          const result = await fn();
          resolve(result);
        } catch (error) {
          reject(error);
        } finally {
          this.activeRequests--;
          setTimeout(() => this.processQueue(), REQUEST_DELAY_MS);
        }
      }
    };
    limiter = new RateLimiter();
  }
});

// src/checks/utils/reading-level.ts
function countSyllables(word) {
  const cleanWord = word.toLowerCase().replace(/[^a-z]/g, "");
  if (cleanWord.length === 0) return 0;
  let syllables = 0;
  let previousWasVowel = false;
  for (let i = 0; i < cleanWord.length; i++) {
    const char = cleanWord[i];
    if (char) {
      const isVowel = /[aeiouy]/.test(char);
      if (isVowel && !previousWasVowel) {
        syllables++;
      }
      previousWasVowel = isVowel;
    }
  }
  if (cleanWord.endsWith("e") && syllables > 1) {
    syllables--;
  }
  return Math.max(1, syllables);
}
function getReadingLevelDescription(level) {
  if (level <= 6) return "Very easy to read";
  if (level <= 9) return "Easy to read";
  if (level <= 12) return "Moderately easy to read";
  if (level <= 15) return "Moderately difficult to read";
  if (level <= 18) return "Difficult to read";
  return "Very difficult to read";
}
var init_reading_level = __esm({
  "src/checks/utils/reading-level.ts"() {
    "use strict";
  }
});

// src/checks/content-checks.ts
function checkContentLength(content, file, settings) {
  const results = [];
  if (!settings.checkContentLength) {
    return Promise.resolve([]);
  }
  let bodyContent = content.replace(/^---\n[\s\S]*?\n---\n/, "");
  bodyContent = bodyContent.replace(/```[\s\S]*?```/g, "");
  bodyContent = bodyContent.replace(/~~~[\s\S]*?~~~/g, "");
  const wordCount = bodyContent.split(/\s+/).filter((word) => word.length > 0).length;
  if (wordCount < settings.minContentLength) {
    results.push({
      passed: false,
      message: `Content too short: ${wordCount} words`,
      suggestion: `Aim for at least ${settings.minContentLength} words`,
      severity: "warning"
    });
  } else {
    results.push({
      passed: true,
      message: `Good content length: ${wordCount} words`,
      severity: "info"
    });
  }
  return Promise.resolve(results);
}
function checkReadingLevel(content, file, settings) {
  const results = [];
  if (!settings.checkReadingLevel) {
    return Promise.resolve([]);
  }
  const cleanContent = removeCodeBlocks(content);
  const sentences = cleanContent.split(/[.!?]+/).filter((s) => s.trim().length > 0);
  const words = cleanContent.split(/\s+/).filter((word) => word.length > 0);
  if (sentences.length === 0 || words.length === 0) {
    results.push({
      passed: true,
      message: "No readable content found for reading level analysis",
      severity: "info"
    });
    return Promise.resolve(results);
  }
  const totalSyllables = words.reduce((sum, word) => sum + countSyllables(word), 0);
  const avgWordsPerSentence = words.length / sentences.length;
  const avgSyllablesPerWord = totalSyllables / words.length;
  const readingLevel = 0.39 * avgWordsPerSentence + 11.8 * avgSyllablesPerWord - 15.59;
  const description = getReadingLevelDescription(readingLevel);
  if (readingLevel > 12) {
    results.push({
      passed: false,
      message: `Reading level too high: ${readingLevel.toFixed(1)} (${description})`,
      suggestion: "Consider simplifying sentence structure and using shorter words",
      severity: "warning"
    });
  } else {
    results.push({
      passed: true,
      message: `Good reading level: ${readingLevel.toFixed(1)} (${description})`,
      severity: "info"
    });
  }
  return Promise.resolve(results);
}
function checkDuplicateContent(content, file, settings) {
  const results = [];
  if (!settings.checkDuplicateContent) {
    return Promise.resolve([]);
  }
  const cleanContent = removeCodeBlocks(content);
  const paragraphs = cleanContent.split(/\n\s*\n/).filter((p) => p.trim().length > 20);
  const duplicateParagraphs = [];
  for (let i = 0; i < paragraphs.length; i++) {
    for (let j = i + 1; j < paragraphs.length; j++) {
      const paragraph1 = paragraphs[i];
      const paragraph2 = paragraphs[j];
      if (paragraph1 && paragraph2) {
        const similarity = calculateSimilarity(paragraph1, paragraph2);
        if (similarity > settings.duplicateThreshold) {
          duplicateParagraphs.push({
            paragraph1: i + 1,
            paragraph2: j + 1,
            similarity
          });
        }
      }
    }
  }
  if (duplicateParagraphs.length > 0) {
    duplicateParagraphs.forEach(({ paragraph1, paragraph2, similarity }) => {
      results.push({
        passed: false,
        message: `Duplicate content detected between paragraphs ${paragraph1} and ${paragraph2}`,
        suggestion: `Similarity: ${similarity.toFixed(1)}% - consider rewriting one of these paragraphs`,
        severity: "warning"
      });
    });
  } else {
    results.push({
      passed: true,
      message: "No duplicate content detected",
      severity: "info"
    });
  }
  return Promise.resolve(results);
}
function calculateSimilarity(text1, text2) {
  const words1 = text1.toLowerCase().split(/\s+/);
  const words2 = text2.toLowerCase().split(/\s+/);
  const set1 = new Set(words1);
  const set2 = new Set(words2);
  const intersection = new Set([...set1].filter((x) => set2.has(x)));
  const union = /* @__PURE__ */ new Set([...set1, ...set2]);
  return intersection.size / union.size * 100;
}
async function checkDuplicateTitles(content, file, settings, vaultDetector) {
  return await vaultDetector.checkDuplicateTitles(file, settings);
}
async function checkDuplicateDescriptions(content, file, settings, vaultDetector) {
  return await vaultDetector.checkDuplicateDescriptions(file, settings);
}
async function checkVaultDuplicateContent(content, file, settings, vaultDetector) {
  return await vaultDetector.checkDuplicateContent(file, settings);
}
function checkNotices(content, file, settings) {
  return Promise.resolve([]);
}
var init_content_checks = __esm({
  "src/checks/content-checks.ts"() {
    "use strict";
    init_content_parser();
    init_reading_level();
  }
});

// src/checks/utils/note-utils.ts
function getDisplayName(file, content, settings) {
  if (settings.useNoteTitles && settings.titleProperty.trim()) {
    let frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
    if (!frontmatterMatch) {
      frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
    }
    if (!frontmatterMatch) {
      frontmatterMatch = content.match(/^---\s*\n([\s\S]*?)\n\s*---/);
    }
    if (frontmatterMatch && frontmatterMatch[1]) {
      const frontmatter = frontmatterMatch[1];
      const lines = frontmatter.split("\n");
      for (const line of lines) {
        if (!line) continue;
        const trimmedLine = line.trim();
        if (trimmedLine.startsWith(settings.titleProperty + ":")) {
          const colonIndex = trimmedLine.indexOf(":");
          if (colonIndex !== -1) {
            let title = trimmedLine.substring(colonIndex + 1).trim();
            if (title.startsWith('"') && title.endsWith('"') || title.startsWith("'") && title.endsWith("'")) {
              title = title.slice(1, -1);
            }
            if (title) {
              return title;
            }
          }
        }
      }
    }
  }
  return getDisplayPath(file.path);
}
function getSlugFromFile(file, content, settings) {
  var _a, _b;
  if ((_a = settings.slugProperty) == null ? void 0 : _a.trim()) {
    const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
    if (frontmatterMatch == null ? void 0 : frontmatterMatch[1]) {
      const lines = frontmatterMatch[1].split("\n");
      const prefix = settings.slugProperty + ":";
      for (const line of lines) {
        const trimmed = line.trim();
        if (trimmed.startsWith(prefix)) {
          let value = trimmed.slice(prefix.length).trim();
          if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
            value = value.slice(1, -1);
          }
          if (value) return value;
          break;
        }
      }
    }
  }
  const base = file.basename.toLowerCase();
  const useParentFolder = base === "index" || ((_b = settings.parentFolderSlugFilename) == null ? void 0 : _b.trim()) && base === settings.parentFolderSlugFilename.trim().toLowerCase();
  if (useParentFolder) {
    const parts = file.path.replace(/\\/g, "/").split("/");
    if (parts.length >= 2) {
      const parentFolder = parts[parts.length - 2];
      if (parentFolder) return parentFolder;
    }
  }
  return file.basename;
}
function checkSlugFormat(content, file, settings) {
  return Promise.resolve([]);
}
var init_note_utils = __esm({
  "src/checks/utils/note-utils.ts"() {
    "use strict";
    init_panel_utils();
  }
});

// src/checks/meta-checks.ts
function stripYamlScalarQuotes(value) {
  const t = value.trim();
  if (t.length >= 2 && (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'"))) {
    return t.slice(1, -1);
  }
  return t;
}
function keywordMatchesInPlainText(text, keyword) {
  const kw = stripYamlScalarQuotes(keyword).trim();
  if (!kw) return false;
  const kwLower = kw.toLowerCase();
  const textLower = text.toLowerCase();
  if (textLower.includes(kwLower)) return true;
  const words = kwLower.split(/\s+/).filter((w) => w.length > 0);
  return words.every((w) => {
    const escaped = w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    return new RegExp(`\\b${escaped}\\b`, "i").test(text);
  });
}
function checkMetaDescription(content, file, settings) {
  const results = [];
  if (!settings.descriptionProperty) {
    return Promise.resolve([]);
  }
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
  if (!frontmatterMatch) {
    results.push({
      passed: false,
      message: "No properties found",
      suggestion: "Add properties with description property",
      severity: "warning"
    });
    return Promise.resolve(results);
  }
  const frontmatter = frontmatterMatch[1];
  if (!frontmatter) {
    results.push({
      passed: false,
      message: "No properties content found",
      suggestion: "Add properties with description property",
      severity: "warning"
    });
    return Promise.resolve(results);
  }
  const descriptionMatch = frontmatter.match(new RegExp(`^${settings.descriptionProperty}:\\s*(.+)$`, "m"));
  if (!descriptionMatch || !descriptionMatch[1]) {
    results.push({
      passed: false,
      message: `No ${settings.descriptionProperty} found in properties`,
      suggestion: `Add ${settings.descriptionProperty} to properties`,
      severity: "warning"
    });
    return Promise.resolve(results);
  }
  let description = descriptionMatch[1].trim();
  if (description.startsWith('"') && description.endsWith('"') || description.startsWith("'") && description.endsWith("'")) {
    description = description.slice(1, -1);
  }
  const length = description.length;
  if (length < 120) {
    results.push({
      passed: false,
      message: `Description too short: ${length} characters`,
      suggestion: "Aim for 120-160 characters",
      severity: "warning"
    });
  } else if (length > 160) {
    results.push({
      passed: false,
      message: `Description too long: ${length} characters`,
      suggestion: "Aim for 120-160 characters",
      severity: "warning"
    });
  } else {
    results.push({
      passed: true,
      message: `Good description length: ${length} characters`,
      severity: "info"
    });
  }
  return Promise.resolve(results);
}
function checkTitleLength(content, file, settings) {
  const results = [];
  if (!settings.checkTitleLength) {
    return Promise.resolve([]);
  }
  let title = "";
  if (settings.titleProperty) {
    const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
    if (frontmatterMatch && frontmatterMatch[1]) {
      const frontmatter = frontmatterMatch[1];
      const titleMatch = frontmatter.match(new RegExp(`^${settings.titleProperty}:\\s*(.+)$`, "m"));
      if (titleMatch && titleMatch[1]) {
        title = titleMatch[1].trim();
      }
    }
  }
  if (!title && settings.useFilenameAsTitle) {
    title = file.basename;
  }
  if (!title) {
    const h1Match = content.match(/^#\s+(.+)$/m);
    if (h1Match && h1Match[1]) {
      title = h1Match[1].trim();
    }
  }
  if (!title) {
    return Promise.resolve(results);
  }
  const fullTitle = settings.titlePrefixSuffix ? `${title} ${settings.titlePrefixSuffix}` : title;
  const length = fullTitle.length;
  if (length < 30) {
    const message = settings.titlePrefixSuffix ? `Title too short: ${length} characters (${title} + " ${settings.titlePrefixSuffix}")` : `Title too short: ${length} characters`;
    results.push({
      passed: false,
      message,
      suggestion: "Aim for 30-60 characters",
      severity: "warning"
    });
  } else if (length > 60) {
    const message = settings.titlePrefixSuffix ? `Title too long: ${length} characters (${title} + " ${settings.titlePrefixSuffix}")` : `Title too long: ${length} characters`;
    results.push({
      passed: false,
      message,
      suggestion: "Aim for 30-60 characters",
      severity: "warning"
    });
  } else {
    const message = settings.titlePrefixSuffix ? `Good title length: ${length} characters (${title} + " ${settings.titlePrefixSuffix}")` : `Good title length: ${length} characters`;
    results.push({
      passed: true,
      message,
      severity: "info"
    });
  }
  return Promise.resolve(results);
}
function checkKeywordDensity(content, file, settings) {
  const results = [];
  if (!settings.keywordProperty) {
    return Promise.resolve([]);
  }
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
  if (!frontmatterMatch) {
    results.push({
      passed: false,
      message: "No properties found",
      suggestion: "Add properties with keyword property",
      severity: "warning"
    });
    return Promise.resolve(results);
  }
  const frontmatter = frontmatterMatch[1];
  if (!frontmatter) {
    results.push({
      passed: false,
      message: "No properties content found",
      suggestion: "Add properties with keyword property",
      severity: "warning"
    });
    return Promise.resolve(results);
  }
  const lines = frontmatter.split("\n");
  let keyword = "";
  let foundKeywordLine = false;
  for (const line of lines) {
    if (line.startsWith(settings.keywordProperty + ":")) {
      foundKeywordLine = true;
      keyword = stripYamlScalarQuotes(line.substring(settings.keywordProperty.length + 1).trim());
      break;
    }
  }
  if (!foundKeywordLine) {
    results.push({
      passed: true,
      message: `No ${settings.keywordProperty} defined in properties`,
      severity: "notice"
    });
    return Promise.resolve(results);
  }
  if (!keyword || keyword === "false" || keyword === "true" || keyword === "null" || keyword === "undefined") {
    results.push({
      passed: true,
      message: `No valid keyword defined in properties`,
      severity: "notice"
    });
    return Promise.resolve(results);
  }
  let cleanContent = removeCodeBlocks(content);
  const titleFrontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
  if (titleFrontmatterMatch && titleFrontmatterMatch[1] && settings.titleProperty) {
    const frontmatter2 = titleFrontmatterMatch[1];
    const lines2 = frontmatter2.split("\n");
    for (const line of lines2) {
      if (line.startsWith(settings.titleProperty + ":")) {
        let title = stripYamlScalarQuotes(line.substring(settings.titleProperty.length + 1).trim());
        cleanContent = title + " " + cleanContent;
        break;
      }
    }
  }
  const stripApostrophes = (s) => s.replace(/[''\u2018\u2019\u201A\u201B]/g, "");
  const normalizeContentWordToken = (token) => {
    let w = stripApostrophes(token).toLowerCase();
    w = w.replace(/^\*+/, "").replace(/\*+$/, "");
    w = w.replace(/^_{1,2}/, "").replace(/_{1,2}$/, "");
    try {
      w = w.replace(/^[^\p{L}\p{N}]+/u, "").replace(/[^\p{L}\p{N}]+$/u, "");
    } catch (e) {
      w = w.replace(/^[^a-z0-9]+/i, "").replace(/[^a-z0-9]+$/i, "");
    }
    return w;
  };
  const keywordLower = keyword.toLowerCase();
  const cleanContentLower = cleanContent.toLowerCase();
  const keywordWords = keywordLower.split(/\s+/).filter((word) => word.length > 0).map((w) => stripApostrophes(w)).filter((w) => w.length > 0);
  if (keywordWords.length === 0) {
    return Promise.resolve(results);
  }
  let keywordCount = 0;
  const contentWords = cleanContentLower.split(/\s+/);
  for (let i = 0; i <= contentWords.length - keywordWords.length; i++) {
    const slice = contentWords.slice(i, i + keywordWords.length).map(normalizeContentWordToken);
    if (slice.length !== keywordWords.length) continue;
    const consecutiveMatch = keywordWords.every((kw, j) => slice[j] === kw);
    if (consecutiveMatch) {
      keywordCount++;
    }
  }
  const words = cleanContentLower.split(/\s+/).map(normalizeContentWordToken).filter((w) => w.length > 0);
  const totalWords = words.length;
  if (totalWords === 0) {
    results.push({
      passed: false,
      message: "No content found for keyword analysis",
      severity: "warning"
    });
    return Promise.resolve(results);
  }
  const density = keywordCount / totalWords * 100;
  if (density < settings.keywordDensityMin) {
    results.push({
      passed: false,
      message: `Keyword density too low: ${density.toFixed(1)}%`,
      suggestion: `Aim for at least ${settings.keywordDensityMin}% density`,
      severity: "warning"
    });
  } else if (density > settings.keywordDensityMax) {
    results.push({
      passed: false,
      message: `Keyword density too high: ${density.toFixed(1)}%`,
      suggestion: `Aim for no more than ${settings.keywordDensityMax}% density`,
      severity: "warning"
    });
  } else {
    results.push({
      passed: true,
      message: `Good keyword density: ${density.toFixed(1)}%`,
      severity: "info"
    });
  }
  return Promise.resolve(results);
}
function checkKeywordInDescription(content, file, settings) {
  const results = [];
  if (!settings.keywordProperty || !settings.descriptionProperty) {
    return Promise.resolve([]);
  }
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
  if (!frontmatterMatch) {
    return Promise.resolve(results);
  }
  const frontmatter = frontmatterMatch[1];
  if (!frontmatter) {
    return Promise.resolve(results);
  }
  const lines = frontmatter.split("\n");
  let keyword = "";
  let foundKeywordLine = false;
  for (const line of lines) {
    if (line.startsWith(settings.keywordProperty + ":")) {
      foundKeywordLine = true;
      keyword = stripYamlScalarQuotes(line.substring(settings.keywordProperty.length + 1).trim());
      break;
    }
  }
  if (!foundKeywordLine || !keyword || keyword === "false" || keyword === "true" || keyword === "null" || keyword === "undefined") {
    return Promise.resolve(results);
  }
  let description = "";
  let foundDescriptionLine = false;
  for (const line of lines) {
    if (line.startsWith(settings.descriptionProperty + ":")) {
      foundDescriptionLine = true;
      description = stripYamlScalarQuotes(line.substring(settings.descriptionProperty.length + 1).trim());
      break;
    }
  }
  if (!foundDescriptionLine) {
    results.push({
      passed: false,
      message: "No description found",
      suggestion: "Add a meta description to your properties",
      severity: "warning"
    });
    return Promise.resolve(results);
  }
  const keywordFound = keywordMatchesInPlainText(description, keyword);
  if (keywordFound) {
    results.push({
      passed: true,
      message: `Keyword found in description`,
      severity: "info"
    });
  } else {
    results.push({
      passed: false,
      message: `Keyword not found in description`,
      suggestion: "Include your target keyword in the description",
      severity: "warning"
    });
  }
  return Promise.resolve(results);
}
function checkTitleH1Uniqueness(content, file, settings) {
  const results = [];
  if (!settings.titleProperty) {
    return Promise.resolve(results);
  }
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
  if (!frontmatterMatch) {
    return Promise.resolve(results);
  }
  const frontmatter = frontmatterMatch[1];
  if (!frontmatter) {
    return Promise.resolve(results);
  }
  let metaTitle = "";
  const lines = frontmatter.split("\n");
  for (const line of lines) {
    if (line.startsWith(settings.titleProperty + ":")) {
      metaTitle = line.substring(settings.titleProperty.length + 1).trim();
      if (metaTitle.startsWith('"') && metaTitle.endsWith('"') || metaTitle.startsWith("'") && metaTitle.endsWith("'")) {
        metaTitle = metaTitle.slice(1, -1);
      }
      break;
    }
  }
  if (!metaTitle) {
    return Promise.resolve(results);
  }
  let h1Heading = "";
  if (settings.skipH1Check) {
    if (settings.titlePrefixSuffix && settings.titlePrefixSuffix.trim()) {
      results.push({
        passed: true,
        message: "Meta title and H1 are unique (prefix/suffix configured)",
        severity: "info"
      });
      return Promise.resolve(results);
    }
    h1Heading = metaTitle;
    if (settings.titlePrefixSuffix) {
      const prefixSuffix = settings.titlePrefixSuffix;
      if (h1Heading.startsWith(prefixSuffix)) {
        h1Heading = h1Heading.substring(prefixSuffix.length).trim();
      }
      if (h1Heading.endsWith(prefixSuffix)) {
        h1Heading = h1Heading.substring(0, h1Heading.length - prefixSuffix.length).trim();
      }
    }
  } else {
    const contentLines = content.split("\n");
    for (const line of contentLines) {
      if (!line) continue;
      const h1Match = line.match(/^#\s+(.+)$/);
      if (h1Match && h1Match[1]) {
        h1Heading = h1Match[1].trim();
        break;
      }
    }
  }
  if (!h1Heading) {
    results.push({
      passed: false,
      message: "No H1 heading found to compare with meta title",
      suggestion: "Add an H1 heading to your content, or enable 'Title property is H1' if your static site generator creates H1s from the title",
      severity: "warning"
    });
    return Promise.resolve(results);
  }
  const metaTitleLower = metaTitle.toLowerCase().trim();
  const h1Lower = h1Heading.toLowerCase().trim();
  if (metaTitleLower === h1Lower) {
    results.push({
      passed: false,
      message: "Meta title and H1 are identical",
      suggestion: "Modify one to be unique, or specify a suffix/prefix in plugin settings",
      severity: "warning"
    });
  } else {
    results.push({
      passed: true,
      message: "Meta title and H1 are unique",
      severity: "info"
    });
  }
  return Promise.resolve(results);
}
function checkKeywordInHeadings(content, file, settings) {
  const results = [];
  if (!settings.keywordProperty) {
    return Promise.resolve(results);
  }
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
  if (!frontmatterMatch) {
    return Promise.resolve(results);
  }
  const frontmatter = frontmatterMatch[1];
  if (!frontmatter) {
    return Promise.resolve(results);
  }
  const lines = frontmatter.split("\n");
  let keyword = "";
  let foundKeywordLine = false;
  for (const line of lines) {
    if (line.startsWith(settings.keywordProperty + ":")) {
      foundKeywordLine = true;
      keyword = stripYamlScalarQuotes(line.substring(settings.keywordProperty.length + 1).trim());
      break;
    }
  }
  if (!foundKeywordLine) {
    results.push({
      passed: true,
      message: `No ${settings.keywordProperty} defined in properties`,
      severity: "notice"
    });
    return Promise.resolve(results);
  }
  if (!keyword || keyword === "false" || keyword === "true" || keyword === "null" || keyword === "undefined") {
    results.push({
      passed: true,
      message: `No valid keyword defined in properties`,
      severity: "notice"
    });
    return Promise.resolve(results);
  }
  const h1Headings = [];
  if (settings.skipH1Check && settings.titleProperty) {
    const titleFrontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
    if (titleFrontmatterMatch && titleFrontmatterMatch[1]) {
      const frontmatter2 = titleFrontmatterMatch[1];
      const lines2 = frontmatter2.split("\n");
      for (const line of lines2) {
        if (line.startsWith(settings.titleProperty + ":")) {
          let title = line.substring(settings.titleProperty.length + 1).trim();
          if (title.startsWith('"') && title.endsWith('"') || title.startsWith("'") && title.endsWith("'")) {
            title = title.slice(1, -1);
          }
          if (settings.titlePrefixSuffix) {
            const prefixSuffix = settings.titlePrefixSuffix;
            if (title.startsWith(prefixSuffix)) {
              title = title.substring(prefixSuffix.length).trim();
            }
            if (title.endsWith(prefixSuffix)) {
              title = title.substring(0, title.length - prefixSuffix.length).trim();
            }
          }
          h1Headings.push({ text: title });
          break;
        }
      }
    }
  }
  const contentLines = content.split("\n");
  for (const line of contentLines) {
    if (!line) continue;
    const h1Match = line.match(/^#\s+(.+)$/);
    if (h1Match && h1Match[1]) {
      h1Headings.push({ text: h1Match[1].trim() });
    }
  }
  if (h1Headings.length === 0) {
    results.push({
      passed: false,
      message: "No H1 heading found in content",
      suggestion: "Add an H1 heading to structure your content and include your target keyword",
      severity: "warning"
    });
    return Promise.resolve(results);
  }
  let keywordFoundInH1 = false;
  for (const h1 of h1Headings) {
    if (keywordMatchesInPlainText(h1.text, keyword)) {
      keywordFoundInH1 = true;
      break;
    }
  }
  if (keywordFoundInH1) {
    results.push({
      passed: true,
      message: `Keyword found in H1`,
      severity: "info"
    });
  } else {
    results.push({
      passed: false,
      message: `Keyword not found in H1`,
      suggestion: "Include your target keyword in the H1 heading for better SEO",
      severity: "warning"
    });
  }
  return Promise.resolve(results);
}
function checkKeywordInTitle(content, file, settings) {
  const results = [];
  if (!settings.keywordProperty || !settings.titleProperty) {
    return Promise.resolve([]);
  }
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
  if (!frontmatterMatch) {
    return Promise.resolve(results);
  }
  const frontmatter = frontmatterMatch[1];
  if (!frontmatter) {
    return Promise.resolve(results);
  }
  const lines = frontmatter.split("\n");
  let keyword = "";
  let foundKeywordLine = false;
  let title = "";
  let foundTitleLine = false;
  for (const line of lines) {
    if (line.startsWith(settings.keywordProperty + ":")) {
      foundKeywordLine = true;
      keyword = stripYamlScalarQuotes(line.substring(settings.keywordProperty.length + 1).trim());
      break;
    }
  }
  for (const line of lines) {
    if (line.startsWith(settings.titleProperty + ":")) {
      foundTitleLine = true;
      title = stripYamlScalarQuotes(line.substring(settings.titleProperty.length + 1).trim());
      break;
    }
  }
  if (!foundKeywordLine) {
    results.push({
      passed: true,
      message: `No ${settings.keywordProperty} defined in properties`,
      severity: "notice"
    });
    return Promise.resolve(results);
  }
  if (!keyword || keyword === "false" || keyword === "true" || keyword === "null" || keyword === "undefined") {
    results.push({
      passed: true,
      message: `No valid keyword defined in properties`,
      severity: "notice"
    });
    return Promise.resolve(results);
  }
  if (!foundTitleLine || !title) {
    return Promise.resolve(results);
  }
  const keywordFound = keywordMatchesInPlainText(title, keyword);
  if (keywordFound) {
    results.push({
      passed: true,
      message: `Keyword found in title`,
      severity: "info"
    });
  } else {
    results.push({
      passed: false,
      message: `Keyword not found in title`,
      suggestion: "Include your target keyword in the title",
      severity: "warning"
    });
  }
  return Promise.resolve(results);
}
function checkKeywordInSlug(content, file, settings) {
  const results = [];
  if (!settings.keywordProperty) {
    return Promise.resolve(results);
  }
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
  if (!frontmatterMatch) {
    return Promise.resolve(results);
  }
  const frontmatter = frontmatterMatch[1];
  if (!frontmatter) {
    return Promise.resolve(results);
  }
  const lines = frontmatter.split("\n");
  let keyword = "";
  let foundKeywordLine = false;
  for (const line of lines) {
    if (line.startsWith(settings.keywordProperty + ":")) {
      foundKeywordLine = true;
      keyword = stripYamlScalarQuotes(line.substring(settings.keywordProperty.length + 1).trim());
      break;
    }
  }
  if (!foundKeywordLine) {
    results.push({
      passed: true,
      message: `No ${settings.keywordProperty} defined in properties`,
      severity: "notice"
    });
    return Promise.resolve(results);
  }
  if (!keyword || keyword === "false" || keyword === "true" || keyword === "null" || keyword === "undefined") {
    results.push({
      passed: true,
      message: `No valid keyword defined in properties`,
      severity: "notice"
    });
    return Promise.resolve(results);
  }
  let slug = getSlugFromFile(file, content, settings).toLowerCase();
  if (slug === "index") {
    const parts = file.path.replace(/\\/g, "/").split("/");
    if (parts.length >= 2) {
      const parent = parts[parts.length - 2];
      if (parent) slug = parent.toLowerCase();
    }
  }
  const keywordNormalized = keyword.toLowerCase().replace(/[''\u2018\u2019\u201A\u201B]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
  const found = keywordNormalized.length > 0 && slug.includes(keywordNormalized);
  if (found) {
    results.push({
      passed: true,
      message: `Target keyword "${keyword}" found in slug`,
      severity: "info"
    });
  } else {
    results.push({
      passed: false,
      message: `Target keyword "${keyword}" not found in slug`,
      suggestion: "Include your target keyword in the file name",
      severity: "warning"
    });
  }
  return Promise.resolve(results);
}
var init_meta_checks = __esm({
  "src/checks/meta-checks.ts"() {
    "use strict";
    init_content_parser();
    init_note_utils();
  }
});

// src/checks/heading-checks.ts
function checkHeadingOrder(content, file, settings) {
  const results = [];
  if (!settings.checkHeadingOrder) {
    return Promise.resolve([]);
  }
  const originalLines = content.split("\n");
  let lastHeadingLevel = null;
  let hasHeading = false;
  let hasH1 = false;
  let inCodeBlock = false;
  for (let i = 0; i < originalLines.length; i++) {
    const line = originalLines[i];
    if (!line) continue;
    const codeBlockMatch = line.match(/^```(\w+)?/);
    if (codeBlockMatch) {
      if (inCodeBlock) {
        inCodeBlock = false;
      } else {
        inCodeBlock = true;
      }
      continue;
    }
    if (inCodeBlock) {
      continue;
    }
    const headingMatch = line.match(/^(#{1,6})\s+(.+)/);
    if (headingMatch && headingMatch[1] && headingMatch[2]) {
      hasHeading = true;
      const currentLevel = headingMatch[1].length;
      const headingText = headingMatch[2].trim();
      const lineNumber = i + 1;
      if (currentLevel === 1) {
        hasH1 = true;
      }
      if (lastHeadingLevel === null) {
        lastHeadingLevel = currentLevel;
        continue;
      }
      if (currentLevel > lastHeadingLevel + 1) {
        results.push({
          passed: false,
          message: `"${headingText}" (H${currentLevel}) skips heading level(s) after H${lastHeadingLevel}`,
          suggestion: "Use heading levels in order (H1 \u2192 H2 \u2192 H3, etc.)",
          severity: "warning",
          position: {
            line: lineNumber,
            searchText: line,
            context: getContextAroundLine(content, lineNumber)
          }
        });
      }
      if (currentLevel === 1 && lastHeadingLevel > 1) {
        results.push({
          passed: false,
          message: `"${headingText}" (H1) appears after H${lastHeadingLevel}`,
          suggestion: "H1 should be the first heading or not used at all",
          severity: "warning",
          position: {
            line: lineNumber,
            searchText: line,
            context: getContextAroundLine(content, lineNumber)
          }
        });
      }
      lastHeadingLevel = currentLevel;
    }
  }
  if (hasHeading && !settings.skipH1Check && !hasH1) {
    results.unshift({
      passed: false,
      message: "No H1 heading found",
      suggestion: "Add an H1 heading at the beginning of your content",
      severity: "warning"
      // No position info since there's no H1 to jump to
    });
  }
  if (!hasHeading) {
    results.push({
      passed: true,
      message: "No heading structure issues found",
      severity: "info"
    });
  } else if (results.length === 0) {
    results.push({
      passed: true,
      message: "Heading structure is correct",
      severity: "info"
    });
  }
  return Promise.resolve(results);
}
var init_heading_checks = __esm({
  "src/checks/heading-checks.ts"() {
    "use strict";
    init_position_utils();
  }
});

// src/checks/image-checks.ts
function checkImageNaming(content, file, settings) {
  const results = [];
  if (!settings.checkImageNaming) {
    return Promise.resolve([]);
  }
  const cleanContent = removeCodeBlocks(content);
  function isExternalUrl(path) {
    const trimmed = path.trim();
    return trimmed.startsWith("http://") || trimmed.startsWith("https://") || trimmed.startsWith("//");
  }
  const imageMatches = cleanContent.match(/!\[[^\]]*\]\(([^)]+)\)/g);
  if (imageMatches) {
    imageMatches.forEach((match, index) => {
      var _a;
      const imagePath = (_a = match.match(/!\[[^\]]*\]\(([^)]+)\)/)) == null ? void 0 : _a[1];
      if (imagePath && !isExternalUrl(imagePath)) {
        const fileName = imagePath.split("/").pop() || "";
        const lineNumber = findLineNumberForImage(content, match);
        if (fileName.includes(" ") || fileName.includes("%20")) {
          results.push({
            passed: false,
            message: `Image ${index + 1} has spaces in file name: ${fileName}`,
            suggestion: "Use kebab-case or underscores instead of spaces",
            severity: "warning",
            position: {
              line: lineNumber,
              searchText: match,
              context: getContextAroundLine(content, lineNumber)
            }
          });
        } else if (fileName.match(/^[a-f0-9]{8,}$/) || fileName.match(/^[a-f0-9]{8,}_[A-Z0-9]+\./) || fileName.match(/^[a-f0-9]{8,}_MD5\./) || fileName.match(/^[a-f0-9]{20,}\./)) {
          results.push({
            passed: false,
            message: `Image ${index + 1} has random file name: ${fileName}`,
            suggestion: "Use descriptive file names",
            severity: "warning",
            position: {
              line: lineNumber,
              searchText: match,
              context: getContextAroundLine(content, lineNumber)
            }
          });
        } else if (fileName.toLowerCase().includes("pasted") || fileName.toLowerCase().includes("untitled") || fileName.toLowerCase().includes("photo") || // Only flag generic screenshots (no descriptive content)
        fileName.match(/^screenshot\d*\.(png|jpg|jpeg|gif|webp)$/i) || // Flag screenshots with only random characters
        fileName.toLowerCase().includes("screenshot") && fileName.match(/^screenshot[a-f0-9]{6,}\.(png|jpg|jpeg|gif|webp)$/i)) {
          results.push({
            passed: false,
            message: `Image ${index + 1} has a potentially generic file name: ${fileName}`,
            suggestion: "Use descriptive file names",
            severity: "warning",
            position: {
              line: lineNumber,
              searchText: match,
              context: getContextAroundLine(content, lineNumber)
            }
          });
        } else if (fileName.length < 5 || fileName.length > 50) {
          results.push({
            passed: false,
            message: `Image ${index + 1} exceeds suggested file name length: ${fileName}`,
            suggestion: "Use descriptive file names between 5-50 characters",
            severity: "warning",
            position: {
              line: lineNumber,
              searchText: match,
              context: getContextAroundLine(content, lineNumber)
            }
          });
        }
      }
    });
  }
  if (results.length === 0) {
    results.push({
      passed: true,
      message: "All images have good file names",
      severity: "info"
    });
  }
  return Promise.resolve(results);
}
var init_image_checks = __esm({
  "src/checks/image-checks.ts"() {
    "use strict";
    init_content_parser();
    init_position_utils();
  }
});

// src/checks/index.ts
var init_checks = __esm({
  "src/checks/index.ts"() {
    "use strict";
    init_alt_text_check();
    init_link_checks();
    init_external_link_checks();
    init_content_checks();
    init_meta_checks();
    init_heading_checks();
    init_image_checks();
    init_content_parser();
    init_position_utils();
    init_reading_level();
    init_note_utils();
  }
});

// src/checks/duplicate-detection.ts
var VaultDuplicateDetector;
var init_duplicate_detection = __esm({
  "src/checks/duplicate-detection.ts"() {
    "use strict";
    init_content_parser();
    VaultDuplicateDetector = class {
      constructor(app, settings) {
        this.vaultData = null;
        this.app = app;
        this.settings = settings;
      }
      /**
       * Collect vault-wide data for duplicate detection
       */
      async collectVaultData() {
        const vaultData = {
          files: /* @__PURE__ */ new Map(),
          titleMap: /* @__PURE__ */ new Map(),
          descriptionMap: /* @__PURE__ */ new Map(),
          lastUpdated: Date.now()
        };
        const files = this.app.vault.getMarkdownFiles();
        const filteredFiles = files.filter((file) => {
          if (this.settings.ignoreUnderscoreFiles && file.basename.startsWith("_")) {
            return false;
          }
          if (this.settings.scanDirectories) {
            const directories = this.settings.scanDirectories.split(",").map((d) => d.trim());
            return directories.some((dir) => file.path.startsWith(dir));
          }
          return true;
        });
        for (const file of filteredFiles) {
          try {
            const content = await this.app.vault.read(file);
            const metadata = this.extractFileMetadata(file, content);
            if (!metadata.ignored) {
              vaultData.files.set(file.path, metadata);
              const titleKey = metadata.title.toLowerCase().trim();
              if (titleKey) {
                if (!vaultData.titleMap.has(titleKey)) {
                  vaultData.titleMap.set(titleKey, []);
                }
                vaultData.titleMap.get(titleKey).push(file.path);
              }
              if (metadata.description) {
                const descKey = metadata.description.toLowerCase().trim();
                if (descKey) {
                  if (!vaultData.descriptionMap.has(descKey)) {
                    vaultData.descriptionMap.set(descKey, []);
                  }
                  vaultData.descriptionMap.get(descKey).push(file.path);
                }
              }
            }
          } catch (error) {
            console.warn(`Failed to process file ${file.path}:`, error);
          }
        }
        this.vaultData = vaultData;
        return vaultData;
      }
      /**
       * Extract metadata from a file
       */
      extractFileMetadata(file, content) {
        const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n/);
        const frontmatter = {};
        if (frontmatterMatch && frontmatterMatch[1]) {
          const frontmatterContent = frontmatterMatch[1];
          const lines = frontmatterContent.split("\n");
          for (const line of lines) {
            const match = line.match(/^(\w+):\s*(.*)$/);
            if (match && match[1] && match[2] !== void 0) {
              const key = match[1];
              let value = match[2];
              if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
                value = value.slice(1, -1);
              }
              frontmatter[key] = value;
            }
          }
        }
        let title = "";
        if (this.settings.titleProperty && frontmatter[this.settings.titleProperty]) {
          const titleValue = frontmatter[this.settings.titleProperty];
          title = typeof titleValue === "string" ? titleValue : file.basename;
        } else if (this.settings.useFilenameAsTitle) {
          title = file.basename;
        } else {
          title = file.basename;
        }
        let description;
        if (this.settings.descriptionProperty && frontmatter[this.settings.descriptionProperty]) {
          const descValue = frontmatter[this.settings.descriptionProperty];
          description = typeof descValue === "string" ? descValue : void 0;
        }
        return {
          path: file.path,
          title,
          description,
          content,
          ignored: false
        };
      }
      /**
       * Check for duplicate titles
       */
      async checkDuplicateTitles(file, settings) {
        var _a;
        const results = [];
        if (!settings.checkDuplicateContent || !settings.titleProperty && !settings.useFilenameAsTitle) {
          return results;
        }
        if (!this.vaultData) {
          await this.collectVaultData();
        }
        const fileMetadata = this.vaultData.files.get(file.path);
        if (!fileMetadata) {
          return results;
        }
        const titleKey = fileMetadata.title.toLowerCase().trim();
        if (!titleKey) {
          return results;
        }
        const duplicateFiles = ((_a = this.vaultData) == null ? void 0 : _a.titleMap.get(titleKey)) || [];
        if (duplicateFiles.length > 1) {
          const otherFiles = duplicateFiles.filter((f) => f !== file.path);
          const genericTitles = ["untitled", "new note", "untitled note", "new file", "document"];
          const isGeneric = genericTitles.includes(titleKey);
          const severity = isGeneric ? "warning" : "error";
          const message = isGeneric ? `Generic title "${fileMetadata.title}" used in ${duplicateFiles.length} files` : `Duplicate title "${fileMetadata.title}" found in ${duplicateFiles.length} files`;
          const suggestion = isGeneric ? "Consider using a more descriptive title" : `This title is also used in: ${otherFiles.map((f) => `<a href="#" data-file-path="${f}" class="seo-file-link">${this.getDisplayPath(f)}</a>`).join(", ")}`;
          results.push({
            passed: false,
            message,
            suggestion,
            severity,
            position: {
              line: 1,
              searchText: this.settings.titleProperty || "title",
              context: `title: "${fileMetadata.title}"`
            }
          });
        } else {
          results.push({
            passed: true,
            message: `Unique title: "${fileMetadata.title}"`,
            severity: "info"
          });
        }
        return results;
      }
      /**
       * Check for duplicate descriptions
       */
      async checkDuplicateDescriptions(file, settings) {
        var _a;
        const results = [];
        if (!settings.checkDuplicateContent || !settings.descriptionProperty) {
          return results;
        }
        if (!this.vaultData) {
          await this.collectVaultData();
        }
        const fileMetadata = this.vaultData.files.get(file.path);
        if (!fileMetadata || !fileMetadata.description) {
          return results;
        }
        const descKey = fileMetadata.description.toLowerCase().trim();
        const duplicateFiles = ((_a = this.vaultData) == null ? void 0 : _a.descriptionMap.get(descKey)) || [];
        if (duplicateFiles.length > 1) {
          const otherFiles = duplicateFiles.filter((f) => f !== file.path);
          const genericDescriptions = ["description", "meta description", "page description"];
          const isGeneric = genericDescriptions.includes(descKey);
          const severity = isGeneric ? "warning" : "error";
          const message = isGeneric ? `Generic description "${fileMetadata.description}" used in ${duplicateFiles.length} files` : `Duplicate description "${fileMetadata.description}" found in ${duplicateFiles.length} files`;
          const suggestion = isGeneric ? "Consider using a more descriptive meta description" : `This description is also used in: ${otherFiles.map((f) => `<a href="#" data-file-path="${f}" class="seo-file-link">${this.getDisplayPath(f)}</a>`).join(", ")}`;
          results.push({
            passed: false,
            message,
            suggestion,
            severity,
            position: {
              line: 1,
              searchText: this.settings.descriptionProperty || "description",
              context: `description: "${fileMetadata.description}"`
            }
          });
        } else {
          results.push({
            passed: true,
            message: `Unique description: "${fileMetadata.description}"`,
            severity: "info"
          });
        }
        return results;
      }
      /**
       * Check for duplicate content using Jaccard similarity
       */
      async checkDuplicateContent(file, settings) {
        var _a, _b;
        const results = [];
        if (!settings.checkDuplicateContent) {
          return results;
        }
        if (!this.vaultData) {
          await this.collectVaultData();
        }
        const fileMetadata = (_a = this.vaultData) == null ? void 0 : _a.files.get(file.path);
        if (!fileMetadata) {
          return results;
        }
        const paragraphs = this.splitIntoParagraphs(fileMetadata.content);
        const duplicates = [];
        for (let i = 0; i < paragraphs.length; i++) {
          const paragraph = paragraphs[i];
          if (!paragraph || paragraph.length < 50) continue;
          const similarFiles = [];
          for (const [otherFilePath, otherMetadata] of ((_b = this.vaultData) == null ? void 0 : _b.files) || []) {
            if (otherFilePath === file.path) continue;
            const otherParagraphs = this.splitIntoParagraphs(otherMetadata.content);
            for (const otherParagraph of otherParagraphs) {
              if (!otherParagraph || otherParagraph.length < 50) continue;
              const similarity = this.calculateJaccardSimilarity(paragraph, otherParagraph);
              if (similarity >= settings.duplicateThreshold) {
                similarFiles.push({
                  file: otherFilePath,
                  similarity
                });
              }
            }
          }
          if (similarFiles.length > 0) {
            duplicates.push({
              paragraph: i + 1,
              similarFiles
            });
          }
        }
        if (duplicates.length > 0) {
          duplicates.forEach(({ paragraph, similarFiles }) => {
            var _a2;
            const maxSimilarity = Math.max(...similarFiles.map((s) => s.similarity));
            const fileList = similarFiles.sort((a, b) => b.similarity - a.similarity).slice(0, 3).map((s) => `<a href="#" data-file-path="${s.file}" class="seo-file-link">${this.getDisplayPath(s.file)}</a> (${s.similarity.toFixed(1)}%)`).join(", ");
            results.push({
              passed: false,
              message: `Duplicate content detected in paragraph ${paragraph}`,
              suggestion: `Similarity: ${maxSimilarity.toFixed(1)}% with: ${fileList}`,
              severity: "error",
              position: {
                line: this.findParagraphLine(fileMetadata.content, paragraph - 1),
                searchText: ((_a2 = paragraphs[paragraph - 1]) == null ? void 0 : _a2.substring(0, 50)) + "...",
                context: `Paragraph ${paragraph} content`
              }
            });
          });
        } else {
          results.push({
            passed: true,
            message: "No duplicate content detected",
            severity: "info"
          });
        }
        return results;
      }
      /**
       * Split content into paragraphs, filtering out non-content elements
       */
      splitIntoParagraphs(content) {
        let cleanContent = removeCodeBlocks(content);
        cleanContent = cleanContent.replace(/^#{1,6}\s+/gm, "").replace(/^\s*[-*+]\s+/gm, "").replace(/^\s*\d+\.\s+/gm, "").replace(/^\s*>\s*/gm, "").replace(/^\s*\|.*\|.*$/gm, "").replace(/^\s*\|.*$/gm, "").replace(/\[\[.*?\]\]/g, "").replace(/!\[\[.*?\]\]/g, "").replace(/\[.*?\]\(.*?\)/g, "").replace(/!\[.*?\]\(.*?\)/g, "").replace(/^\s*---\s*$/gm, "").replace(/^\s*\+\+\+\s*$/gm, "").replace(/^\s*==\s*$/gm, "").replace(/^\s*!!\s*$/gm, "").replace(/^\s*>\s*\[!.*?\]\s*$/gm, "").replace(/^\s*>\s*$/gm, "").replace(/^\s*$/gm, "").trim();
        return cleanContent.split(/\n\s*\n/).map((p) => p.trim()).filter((p) => {
          if (p.length < 30) return false;
          const htmlLikeRatio = (p.match(/<[^>]*>/g) || []).join("").length / p.length;
          if (htmlLikeRatio > 0.3) return false;
          const specialCharRatio = (p.match(/[^\w\s.,!?;:'"()-]/g) || []).length / p.length;
          if (specialCharRatio > 0.5) return false;
          const wordRatio = (p.match(/\b\w+\b/g) || []).length / p.split(/\s+/).length;
          if (wordRatio < 0.3) return false;
          return true;
        });
      }
      /**
       * Calculate Jaccard similarity between two texts
       */
      calculateJaccardSimilarity(text1, text2) {
        const normalize = (text) => text.toLowerCase().replace(/\s+/g, " ").trim();
        const normalized1 = normalize(text1);
        const normalized2 = normalize(text2);
        const stopWords = /* @__PURE__ */ new Set(["the", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "by", "a", "an", "is", "are", "was", "were", "be", "been", "have", "has", "had", "do", "does", "did", "will", "would", "could", "should", "may", "might", "can", "this", "that", "these", "those", "i", "you", "he", "she", "it", "we", "they", "me", "him", "her", "us", "them"]);
        const words1 = new Set(
          normalized1.split(/\s+/).filter((w) => w.length > 2 && !stopWords.has(w)).map((w) => w.replace(/[^\w]/g, "")).filter((w) => w.length > 0)
        );
        const words2 = new Set(
          normalized2.split(/\s+/).filter((w) => w.length > 2 && !stopWords.has(w)).map((w) => w.replace(/[^\w]/g, "")).filter((w) => w.length > 0)
        );
        if (words1.size === 0 || words2.size === 0) {
          return 0;
        }
        const intersection = new Set([...words1].filter((x) => words2.has(x)));
        const union = /* @__PURE__ */ new Set([...words1, ...words2]);
        return intersection.size / union.size * 100;
      }
      /**
       * Find the line number of a paragraph in the content
       */
      findParagraphLine(content, paragraphIndex) {
        const paragraphs = this.splitIntoParagraphs(content);
        if (paragraphIndex >= paragraphs.length) return 1;
        const targetParagraph = paragraphs[paragraphIndex];
        if (!targetParagraph) return 1;
        const lines = content.split("\n");
        for (let i = 0; i < lines.length; i++) {
          const line = lines[i];
          if (line && line.includes(targetParagraph.substring(0, 50))) {
            return i + 1;
          }
        }
        return 1;
      }
      /**
       * Get display path for a file
       */
      getDisplayPath(filePath) {
        if (this.vaultData && this.settings.useNoteTitles) {
          const fileMetadata = this.vaultData.files.get(filePath);
          if (fileMetadata && fileMetadata.title) {
            return fileMetadata.title;
          }
        }
        const parts = filePath.split("/");
        return parts[parts.length - 1] || filePath;
      }
      /**
       * Clear cached vault data
       */
      clearCache() {
        this.vaultData = null;
      }
      /**
       * Get current vault data
       */
      getVaultData() {
        return this.vaultData;
      }
    };
  }
});

// src/seo-checker.ts
var seo_checker_exports = {};
__export(seo_checker_exports, {
  clearAllCache: () => clearAllCache,
  clearCache: () => clearCache,
  clearCacheForFile: () => clearCacheForFile,
  getCacheStats: () => getCacheStats,
  runSEOCheck: () => runSEOCheck
});
async function runSEOCheck(plugin, files, abortController) {
  const results = [];
  const vaultDetector = new VaultDuplicateDetector(plugin.app, plugin.settings);
  if (plugin.settings.checkDuplicateContent) {
    await vaultDetector.collectVaultData();
  }
  for (const file of files) {
    if (abortController == null ? void 0 : abortController.signal.aborted) {
      throw new DOMException("Operation was aborted", "AbortError");
    }
    try {
      const cachedResult = getCachedResult(file, plugin);
      if (cachedResult) {
        results.push(cachedResult);
        continue;
      }
      const content = await plugin.app.vault.read(file);
      const result = await checkFile(plugin, file, content, vaultDetector, abortController);
      void cacheResult(file, result, plugin);
      results.push(result);
    } catch (error) {
      if (error instanceof Error && error.name === "AbortError") {
        throw error;
      }
      console.error(`Error checking file ${file.path}:`, error);
    }
  }
  return results;
}
async function checkFile(plugin, file, content, vaultDetector, abortController) {
  const checks = {
    titleLength: await checkTitleLength(content, file, plugin.settings),
    metaDescription: await checkMetaDescription(content, file, plugin.settings),
    titleH1Uniqueness: await checkTitleH1Uniqueness(content, file, plugin.settings),
    keywordInTitle: await checkKeywordInTitle(content, file, plugin.settings),
    keywordInDescription: await checkKeywordInDescription(content, file, plugin.settings),
    keywordInHeadings: await checkKeywordInHeadings(content, file, plugin.settings),
    keywordInSlug: await checkKeywordInSlug(content, file, plugin.settings),
    slugFormat: await checkSlugFormat(content, file, plugin.settings),
    keywordDensity: await checkKeywordDensity(content, file, plugin.settings),
    headingOrder: await checkHeadingOrder(content, file, plugin.settings),
    contentLength: await checkContentLength(content, file, plugin.settings),
    duplicateTitles: await checkDuplicateTitles(content, file, plugin.settings, vaultDetector),
    duplicateDescriptions: await checkDuplicateDescriptions(content, file, plugin.settings, vaultDetector),
    duplicateContent: plugin.settings.checkDuplicateContent ? await checkVaultDuplicateContent(content, file, plugin.settings, vaultDetector) : await checkDuplicateContent(content, file, plugin.settings),
    altText: await checkAltText(content, file, plugin.settings),
    imageFileNames: await checkImageNaming(content, file, plugin.settings),
    nakedLinks: await checkNakedLinks(content, file, plugin.settings),
    brokenLinks: await checkBrokenLinks(content, file, plugin.settings, plugin.app),
    potentiallyBrokenLinks: await checkPotentiallyBrokenLinks(content, file, plugin.settings, plugin.app),
    potentiallyBrokenEmbeds: await checkNotices(content, file, plugin.settings),
    externalBrokenLinks: plugin.settings.enableExternalLinkVaultCheck ? await checkExternalBrokenLinks(content, file, plugin.settings, abortController) : plugin.settings.checkExternalLinks ? await checkExternalLinks(content, file, plugin.settings) : [],
    readingLevel: await checkReadingLevel(content, file, plugin.settings)
  };
  const allResults = Object.values(checks).flat();
  const issuesCount = allResults.filter((r) => r.severity === "error").length;
  const warningsCount = allResults.filter((r) => r.severity === "warning").length;
  const noticesCount = allResults.filter((r) => r.severity === "notice").length;
  let overallScore = 0;
  const titleResults = [...checks.titleLength, ...checks.titleH1Uniqueness];
  if (titleResults.length > 0) {
    const passedCount = titleResults.filter((r) => r.passed).length;
    overallScore += passedCount / titleResults.length * 15;
  } else {
    overallScore += 15;
  }
  const metaResults = checks.metaDescription;
  if (metaResults.length > 0) {
    const passedCount = metaResults.filter((r) => r.passed).length;
    overallScore += passedCount / metaResults.length * 15;
  } else {
    overallScore += 15;
  }
  const headingResults = checks.headingOrder;
  if (headingResults.length > 0) {
    const passedCount = headingResults.filter((r) => r.passed).length;
    overallScore += passedCount / headingResults.length * 15;
  } else {
    overallScore += 15;
  }
  const contentResults = [...checks.contentLength, ...checks.readingLevel];
  if (contentResults.length > 0) {
    const passedCount = contentResults.filter((r) => r.passed).length;
    overallScore += passedCount / contentResults.length * 20;
  } else {
    overallScore += 20;
  }
  const altTextResults = checks.altText;
  if (altTextResults.length > 0) {
    const passedCount = altTextResults.filter((r) => r.passed).length;
    overallScore += passedCount / altTextResults.length * 10;
  } else {
    overallScore += 10;
  }
  const linkResults = [...checks.brokenLinks, ...checks.nakedLinks];
  if (linkResults.length > 0) {
    const passedCount = linkResults.filter((r) => r.passed).length;
    overallScore += passedCount / linkResults.length * 15;
  } else {
    overallScore += 15;
  }
  const keywordResults = [
    ...checks.keywordDensity,
    ...checks.keywordInTitle,
    ...checks.keywordInDescription,
    ...checks.keywordInHeadings,
    ...checks.keywordInSlug
  ];
  if (keywordResults.length > 0) {
    const passedCount = keywordResults.filter((r) => r.passed).length;
    overallScore += passedCount / keywordResults.length * 10;
  } else {
    overallScore += 10;
  }
  overallScore = Math.round(Math.max(0, Math.min(100, overallScore)));
  return {
    file: file.path,
    displayName: getDisplayName(file, content, plugin.settings),
    checks,
    overallScore,
    issuesCount,
    warningsCount,
    noticesCount
  };
}
async function generateFileHash(file, app) {
  try {
    const content = await app.vault.read(file);
    const stat = await app.vault.adapter.stat(file.path);
    return `${content.length}-${(stat == null ? void 0 : stat.mtime) || Date.now()}`;
  } catch (e) {
    return `${Date.now()}`;
  }
}
function generateSettingsHash(settings) {
  const relevantSettings = {
    keywordProperty: settings.keywordProperty || "",
    keywordDensityMin: settings.keywordDensityMin,
    keywordDensityMax: settings.keywordDensityMax,
    descriptionProperty: settings.descriptionProperty || "",
    titleProperty: settings.titleProperty || "",
    useFilenameAsTitle: settings.useFilenameAsTitle,
    checkContentLength: settings.checkContentLength,
    minContentLength: settings.minContentLength,
    scanDirectories: settings.scanDirectories || "",
    enableMDXSupport: settings.enableMDXSupport,
    // External link checking settings
    checkExternalLinks: settings.checkExternalLinks,
    enableExternalLinkVaultCheck: settings.enableExternalLinkVaultCheck,
    checkPotentiallyBrokenLinks: settings.checkPotentiallyBrokenLinks,
    checkPotentiallyBrokenEmbeds: settings.checkPotentiallyBrokenEmbeds,
    // Other SEO check settings
    checkAltText: settings.checkAltText,
    checkBrokenLinks: settings.checkBrokenLinks,
    checkNakedLinks: settings.checkNakedLinks,
    checkHeadingOrder: settings.checkHeadingOrder,
    checkTitleLength: settings.checkTitleLength,
    checkImageNaming: settings.checkImageNaming,
    checkDuplicateContent: settings.checkDuplicateContent,
    checkReadingLevel: settings.checkReadingLevel,
    // Thresholds that affect results
    duplicateThreshold: settings.duplicateThreshold
    // Add other settings that affect SEO results
  };
  return JSON.stringify(relevantSettings);
}
function getCachedResult(file, plugin) {
  const entry = cache.get(file.path);
  if (!entry) {
    return null;
  }
  const now = Date.now();
  const isExpired = now - entry.timestamp > CACHE_EXPIRY_MS;
  const currentSettingsHash = generateSettingsHash(plugin.settings);
  const settingsChanged = entry.settingsHash !== currentSettingsHash;
  if (isExpired || settingsChanged) {
    cache.delete(file.path);
    return null;
  }
  return entry.result;
}
async function cacheResult(file, result, plugin) {
  const fileHash = await generateFileHash(file, plugin.app);
  const settingsHash = generateSettingsHash(plugin.settings);
  cache.set(file.path, {
    result,
    timestamp: Date.now(),
    fileHash,
    settingsHash
  });
}
function clearCache() {
  cache.clear();
}
function getCacheStats() {
  return { size: cache.size };
}
function clearCacheForFile(filePath) {
  cache.delete(filePath);
}
function clearAllCache() {
  cache.clear();
}
var cache, CACHE_EXPIRY_MS;
var init_seo_checker = __esm({
  "src/seo-checker.ts"() {
    "use strict";
    init_checks();
    init_content_checks();
    init_duplicate_detection();
    cache = /* @__PURE__ */ new Map();
    CACHE_EXPIRY_MS = 24 * 60 * 60 * 1e3;
  }
});

// src/main.ts
var main_exports = {};
__export(main_exports, {
  default: () => SEOPlugin
});
module.exports = __toCommonJS(main_exports);
var import_obsidian11 = require("obsidian");

// src/settings.ts
var DEFAULT_SETTINGS = {
  scanDirectories: "",
  ignoreUnderscoreFiles: false,
  enableMDXSupport: false,
  keywordProperty: "",
  descriptionProperty: "",
  titleProperty: "",
  useFilenameAsTitle: false,
  slugProperty: "",
  useFilenameAsSlug: false,
  parentFolderSlugFilename: "",
  titlePrefixSuffix: "",
  checkTitleLength: true,
  checkHeadingOrder: true,
  checkAltText: true,
  checkImageNaming: true,
  checkBrokenLinks: true,
  checkExternalLinks: true,
  enableExternalLinkButton: true,
  enableExternalLinkVaultCheck: false,
  checkNakedLinks: true,
  checkContentLength: true,
  checkDuplicateContent: false,
  checkReadingLevel: true,
  checkPotentiallyBrokenLinks: true,
  checkPotentiallyBrokenEmbeds: true,
  useNoteTitles: false,
  skipH1Check: false,
  publishMode: false,
  defaultSort: "issues-desc",
  showRibbonIcon: true,
  exportFormat: "csv",
  minContentLength: 300,
  keywordDensityMin: 1,
  keywordDensityMax: 2,
  duplicateThreshold: 80,
  cachedGlobalResults: [],
  lastScanTimestamp: 0
};

// src/commands/index.ts
function registerCommands(plugin) {
  plugin.addCommand({
    id: "open-current",
    name: "Open current note audit",
    icon: "search-check",
    callback: () => {
      plugin.openCurrentPanel();
    }
  });
  plugin.addCommand({
    id: "open-global",
    name: "Open vault audit",
    icon: "search-check",
    callback: () => {
      plugin.openGlobalPanel();
    }
  });
  plugin.addCommand({
    id: "run-current",
    name: "Run current note audit",
    icon: "search-check",
    callback: () => {
      plugin.openCurrentPanel();
      setTimeout(() => {
        const currentPanel = plugin.app.workspace.getLeavesOfType("seo-current-panel")[0];
        if (currentPanel && currentPanel.view) {
          const checkBtn = currentPanel.view.containerEl.querySelector(".seo-top-btn");
          if (checkBtn) {
            checkBtn.click();
          }
        }
      }, 200);
    }
  });
  plugin.addCommand({
    id: "run-global",
    name: "Run vault audit",
    icon: "search-check",
    callback: () => {
      plugin.openGlobalPanel();
      void setTimeout(() => {
        void (async () => {
          const globalPanels = plugin.app.workspace.getLeavesOfType("seo-global-panel");
          if (globalPanels.length === 0) {
            plugin.openGlobalPanel();
            return;
          }
          const panel = globalPanels[0];
          if (panel == null ? void 0 : panel.view) {
            const seoPanel = panel.view;
            await seoPanel.actions.refreshGlobalResults();
            seoPanel.render();
          }
        })();
      }, 300);
    }
  });
}

// src/settings-tab.ts
var import_obsidian = require("obsidian");
var SEOSettingTab = class extends import_obsidian.PluginSettingTab {
  constructor(app, plugin) {
    super(app, plugin);
    this.icon = "lucide-search-check";
    this.plugin = plugin;
  }
  display() {
    const { containerEl } = this;
    containerEl.empty();
    const globalGroup = new import_obsidian.SettingGroup(containerEl).setHeading("Global");
    globalGroup.addSetting((setting) => {
      setting.setName("Scan directories").setDesc("Comma-separated list of directory names to scan. Leave blank to scan all directories.").addText((text) => {
        text.setPlaceholder("like blog, posts, public");
        text.setValue(this.plugin.settings.scanDirectories);
        text.onChange(async (value) => {
          this.plugin.settings.scanDirectories = value;
          await this.plugin.saveSettings();
        });
      });
    });
    globalGroup.addSetting((setting) => {
      setting.setName("Use note titles instead of file names").setDesc("Display note titles from properties instead of file names in the issues list and current note audit").addToggle((toggle) => toggle.setValue(this.plugin.settings.useNoteTitles).onChange(async (value) => {
        this.plugin.settings.useNoteTitles = value;
        await this.plugin.saveSettings();
      }));
    });
    globalGroup.addSetting((setting) => {
      setting.setName("Title prefix / suffix").setDesc("Specify an optional prefix or suffix that gets appended to your meta title - used to factor in character count for the title length check").addText((text) => text.setPlaceholder("Author name").setValue(this.plugin.settings.titlePrefixSuffix).onChange(async (value) => {
        this.plugin.settings.titlePrefixSuffix = value;
        await this.plugin.saveSettings();
      }));
    });
    globalGroup.addSetting((setting) => {
      setting.setName("Ignores files with an underscore prefix").setDesc("Don't process files that begin with an underscore, like _example.md").addToggle((toggle) => toggle.setValue(this.plugin.settings.ignoreUnderscoreFiles).onChange(async (value) => {
        this.plugin.settings.ignoreUnderscoreFiles = value;
        await this.plugin.saveSettings();
      }));
    });
    globalGroup.addSetting((setting) => {
      setting.setName("Enable MDX file support").setDesc("Process MDX files in addition to Markdown files. MDX files use the same properties format as Markdown.").addToggle((toggle) => toggle.setValue(this.plugin.settings.enableMDXSupport).onChange(async (value) => {
        this.plugin.settings.enableMDXSupport = value;
        await this.plugin.saveSettings();
      }));
    });
    globalGroup.addSetting((setting) => {
      setting.setName("Show ribbon icon").setDesc("Show or hide the wizard icon in the left sidebar ribbon").addToggle((toggle) => toggle.setValue(this.plugin.settings.showRibbonIcon).onChange(async (value) => {
        this.plugin.settings.showRibbonIcon = value;
        await this.plugin.saveSettings();
        this.plugin.toggleRibbonIcon();
      }));
    });
    globalGroup.addSetting((setting) => {
      setting.setName("Export format").setDesc("Format for copy and download from the audit panel (CSV or Markdown). Markdown is useful for pasting into agents or docs.").addDropdown((dropdown) => dropdown.addOption("csv", "CSV").addOption("markdown", "Markdown").setValue(this.plugin.settings.exportFormat).onChange(async (value) => {
        this.plugin.settings.exportFormat = value;
        await this.plugin.saveSettings();
      }));
    });
    const propertyNamesGroup = new import_obsidian.SettingGroup(containerEl).setHeading("Property names");
    propertyNamesGroup.addSetting((setting) => {
      setting.setName("Keyword property").setDesc("Property name for target keyword (targetKeyword, seo, keyword, etc.)").addText((text) => {
        text.setPlaceholder("targetKeyword");
        text.setValue(this.plugin.settings.keywordProperty);
        text.onChange(async (value) => {
          this.plugin.settings.keywordProperty = value;
          await this.plugin.saveSettings();
        });
      });
    });
    propertyNamesGroup.addSetting((setting) => {
      setting.setName("Description property").setDesc("Property name for meta description").addText((text) => {
        text.setPlaceholder("description");
        text.setValue(this.plugin.settings.descriptionProperty);
        text.onChange(async (value) => {
          this.plugin.settings.descriptionProperty = value;
          await this.plugin.saveSettings();
        });
      });
    });
    propertyNamesGroup.addSetting((setting) => {
      setting.setName("Title property").setDesc("Property name for title (leave blank to skip title checks)").addText((text) => {
        text.setPlaceholder("title");
        text.setValue(this.plugin.settings.titleProperty);
        text.onChange(async (value) => {
          this.plugin.settings.titleProperty = value;
          await this.plugin.saveSettings();
        });
      });
    });
    propertyNamesGroup.addSetting((setting) => {
      setting.setName("Use file name as title").setDesc("Use file name as title instead of a property").addToggle((toggle) => toggle.setValue(this.plugin.settings.useFilenameAsTitle).onChange(async (value) => {
        this.plugin.settings.useFilenameAsTitle = value;
        await this.plugin.saveSettings();
      }));
    });
    propertyNamesGroup.addSetting((setting) => {
      setting.setName("Slug property").setDesc("Property name for slug (leave blank to skip slug checks)").addText((text) => {
        text.setPlaceholder("slug");
        text.setValue(this.plugin.settings.slugProperty);
        text.onChange(async (value) => {
          this.plugin.settings.slugProperty = value;
          await this.plugin.saveSettings();
        });
      });
    });
    propertyNamesGroup.addSetting((setting) => {
      setting.setName("Use file/folder name as slug").setDesc("Use file/folder name as slug instead of a property").addToggle((toggle) => toggle.setValue(this.plugin.settings.useFilenameAsSlug).onChange(async (value) => {
        this.plugin.settings.useFilenameAsSlug = value;
        await this.plugin.saveSettings();
        this.display();
      }));
    });
    if (this.plugin.settings.useFilenameAsSlug) {
      propertyNamesGroup.addSetting((setting) => {
        setting.setName("Use parent folder name instead when specified file name is used").setDesc("If a markdown file matches this file name, use the parent folder name as the slug instead").addText((text) => {
          text.setPlaceholder("index");
          text.setValue(this.plugin.settings.parentFolderSlugFilename);
          text.onChange(async (value) => {
            this.plugin.settings.parentFolderSlugFilename = value;
            await this.plugin.saveSettings();
          });
        });
      });
    }
    const auditOptionsGroup = new import_obsidian.SettingGroup(containerEl).setHeading("Audit options");
    auditOptionsGroup.addSetting((setting) => {
      setting.setName("Check meta title length").setDesc("Enable meta title length checking").addToggle((toggle) => toggle.setValue(this.plugin.settings.checkTitleLength).onChange(async (value) => {
        this.plugin.settings.checkTitleLength = value;
        await this.plugin.saveSettings();
      }));
    });
    auditOptionsGroup.addSetting((setting) => {
      setting.setName("Check heading order").setDesc("Enable heading hierarchy checking").addToggle((toggle) => toggle.setValue(this.plugin.settings.checkHeadingOrder).onChange(async (value) => {
        this.plugin.settings.checkHeadingOrder = value;
        await this.plugin.saveSettings();
      }));
    });
    auditOptionsGroup.addSetting((setting) => {
      setting.setName("Check content length").setDesc("Enable content length checking").addToggle((toggle) => toggle.setValue(this.plugin.settings.checkContentLength).onChange(async (value) => {
        this.plugin.settings.checkContentLength = value;
        await this.plugin.saveSettings();
      }));
    });
    auditOptionsGroup.addSetting((setting) => {
      setting.setName("Check duplicate content").setDesc("Enable duplicate content detection").addToggle((toggle) => toggle.setValue(this.plugin.settings.checkDuplicateContent).onChange(async (value) => {
        this.plugin.settings.checkDuplicateContent = value;
        await this.plugin.saveSettings();
      }));
      setting.descEl.createEl("div", {
        // eslint-disable-next-line obsidianmd/ui/sentence-case
        text: "Warning: This feature can be very resource-intensive with large vaults and many notes. Disable for faster audits.",
        cls: "setting-item-description seo-warning-message"
      });
    });
    auditOptionsGroup.addSetting((setting) => {
      setting.setName("Check alt text").setDesc("Enable alt text checking for media content (images, videos, embeds, etc.)").addToggle((toggle) => toggle.setValue(this.plugin.settings.checkAltText).onChange(async (value) => {
        this.plugin.settings.checkAltText = value;
        await this.plugin.saveSettings();
      }));
    });
    auditOptionsGroup.addSetting((setting) => {
      setting.setName("Check image file names").setDesc("Enable image file name checking").addToggle((toggle) => toggle.setValue(this.plugin.settings.checkImageNaming).onChange(async (value) => {
        this.plugin.settings.checkImageNaming = value;
        await this.plugin.saveSettings();
      }));
    });
    auditOptionsGroup.addSetting((setting) => {
      setting.setName("Check broken internal links").setDesc("Enable broken internal link detection").addToggle((toggle) => toggle.setValue(this.plugin.settings.checkBrokenLinks).onChange(async (value) => {
        this.plugin.settings.checkBrokenLinks = value;
        await this.plugin.saveSettings();
      }));
    });
    auditOptionsGroup.addSetting((setting) => {
      setting.setName("Check external links").setDesc("Return a list of external links").addToggle((toggle) => toggle.setValue(this.plugin.settings.checkExternalLinks).onChange(async (value) => {
        this.plugin.settings.checkExternalLinks = value;
        await this.plugin.saveSettings();
      }));
    });
    auditOptionsGroup.addSetting((setting) => {
      setting.setName("Enable broken external link check button").setDesc('Show "Check external links for 404s" button in current note panel').addToggle((toggle) => toggle.setValue(this.plugin.settings.enableExternalLinkButton).onChange(async (value) => {
        this.plugin.settings.enableExternalLinkButton = value;
        await this.plugin.saveSettings();
        if (this.plugin.sidePanel) {
          this.plugin.sidePanel.refresh();
        }
      }));
    });
    auditOptionsGroup.addSetting((setting) => {
      setting.setName("Check naked links").setDesc("Enable naked URL detection").addToggle((toggle) => toggle.setValue(this.plugin.settings.checkNakedLinks).onChange(async (value) => {
        this.plugin.settings.checkNakedLinks = value;
        await this.plugin.saveSettings();
      }));
    });
    auditOptionsGroup.addSetting((setting) => {
      setting.setName("Check reading level").setDesc("Enable reading level analysis").addToggle((toggle) => toggle.setValue(this.plugin.settings.checkReadingLevel).onChange(async (value) => {
        this.plugin.settings.checkReadingLevel = value;
        await this.plugin.saveSettings();
      }));
    });
    auditOptionsGroup.addSetting((setting) => {
      setting.setName("Check potentially broken links").setDesc("Check for potentially broken internal links that may not work on web publishing").addToggle((toggle) => toggle.setValue(this.plugin.settings.checkPotentiallyBrokenLinks).onChange(async (value) => {
        this.plugin.settings.checkPotentiallyBrokenLinks = value;
        await this.plugin.saveSettings();
      }));
    });
    auditOptionsGroup.addSetting((setting) => {
      setting.setName("Check potentially broken embeds").setDesc("Check for potentially broken markdown or wikilink-based embedded media that may not work on web publishing").addToggle((toggle) => toggle.setValue(this.plugin.settings.checkPotentiallyBrokenEmbeds).onChange(async (value) => {
        this.plugin.settings.checkPotentiallyBrokenEmbeds = value;
        await this.plugin.saveSettings();
      }));
    });
    auditOptionsGroup.addSetting((setting) => {
      setting.setName("Flexible relative link check").setDesc("Uses flexible validation for relative paths like /page that can be resolved by a static site generator, but may be considered broken by typical Obsidian validation").addToggle((toggle) => toggle.setValue(this.plugin.settings.publishMode).onChange(async (value) => {
        this.plugin.settings.publishMode = value;
        await this.plugin.saveSettings();
      }));
    });
    auditOptionsGroup.addSetting((setting) => {
      setting.setName("Title property is H1").setDesc("Enable when your static site generator uses the title property to automatically generate the H1 heading. This prevents H1 validation errors while still flagging H1s that appear after other heading levels.").addToggle((toggle) => toggle.setValue(this.plugin.settings.skipH1Check).onChange(async (value) => {
        this.plugin.settings.skipH1Check = value;
        await this.plugin.saveSettings();
      }));
    });
    auditOptionsGroup.addSetting((setting) => {
      setting.setName("Automatically include broken external link checks in audits (not recommended)").setDesc("Include broken external link checking in vault-wide and current note audits").addToggle((toggle) => toggle.setValue(this.plugin.settings.enableExternalLinkVaultCheck).onChange(async (value) => {
        this.plugin.settings.enableExternalLinkVaultCheck = value;
        await this.plugin.saveSettings();
        if (this.plugin.sidePanel) {
          this.plugin.sidePanel.refresh();
        }
      }));
      setting.descEl.createEl("div", {
        // eslint-disable-next-line obsidianmd/ui/sentence-case
        text: 'Warning: This will make vault audits extremely slow. Use the "Check external links" button instead for individual notes.',
        cls: "setting-item-description seo-warning-message"
      });
    });
    const thresholdsGroup = new import_obsidian.SettingGroup(containerEl).setHeading("Thresholds");
    thresholdsGroup.addSetting((setting) => {
      setting.setName("Minimum content length").setDesc("Minimum word count for content length check").addText((text) => text.setPlaceholder("300").setValue(this.plugin.settings.minContentLength.toString()).onChange(async (value) => {
        const num = parseInt(value);
        if (!isNaN(num)) {
          this.plugin.settings.minContentLength = num;
          await this.plugin.saveSettings();
        }
      }));
    });
    thresholdsGroup.addSetting((setting) => {
      setting.setName("Keyword density minimum").setDesc("Minimum keyword density percentage").addText((text) => text.setPlaceholder("1").setValue(this.plugin.settings.keywordDensityMin.toString()).onChange(async (value) => {
        const num = parseFloat(value);
        if (!isNaN(num)) {
          this.plugin.settings.keywordDensityMin = num;
          await this.plugin.saveSettings();
        }
      }));
    });
    thresholdsGroup.addSetting((setting) => {
      setting.setName("Keyword density maximum").setDesc("Maximum keyword density percentage").addText((text) => text.setPlaceholder("2").setValue(this.plugin.settings.keywordDensityMax.toString()).onChange(async (value) => {
        const num = parseFloat(value);
        if (!isNaN(num)) {
          this.plugin.settings.keywordDensityMax = num;
          await this.plugin.saveSettings();
        }
      }));
    });
    thresholdsGroup.addSetting((setting) => {
      setting.setName("Duplicate content threshold").setDesc("Similarity percentage threshold for duplicate content detection").addText((text) => text.setPlaceholder("80").setValue(this.plugin.settings.duplicateThreshold.toString()).onChange(async (value) => {
        const num = parseInt(value);
        if (!isNaN(num)) {
          this.plugin.settings.duplicateThreshold = num;
          await this.plugin.saveSettings();
        }
      }));
    });
  }
};

// src/ui/side-panel.ts
var import_obsidian8 = require("obsidian");

// src/ui/panel-constants.ts
var SEOCurrentPanelViewType = "seo-current-panel";
var SEOGlobalPanelViewType = "seo-global-panel";

// src/ui/side-panel.ts
init_panel_utils();

// src/ui/panel-actions.ts
var import_obsidian3 = require("obsidian");
init_panel_utils();

// src/utils/file-utils.ts
function isSupportedFile(file, settings) {
  const extension = file.extension.toLowerCase();
  if (extension === "md") {
    return true;
  }
  if (extension === "mdx" && settings.enableMDXSupport) {
    return true;
  }
  return false;
}

// src/ui/panel-actions.ts
var PanelActions = class {
  constructor(app, plugin, panelType) {
    this.app = app;
    this.plugin = plugin;
    this.panelType = panelType;
  }
  async runInitialScan() {
    try {
      const files = await this.plugin.getFilesToCheck();
      if (files.length === 0) {
        return [];
      }
      if (files.length > 20) {
        new import_obsidian3.Notice(`Running SEO audit on ${files.length} files... This may take a moment.`);
      }
      const { runSEOCheck: runSEOCheck2 } = await Promise.resolve().then(() => (init_seo_checker(), seo_checker_exports));
      const results = await runSEOCheck2(this.plugin, files);
      this.plugin.settings.cachedGlobalResults = results;
      this.plugin.settings.lastScanTimestamp = Date.now();
      await this.plugin.saveSettings();
      new import_obsidian3.Notice(`SEO audit complete with ${results.length} files.`);
      return results;
    } catch (error) {
      console.error("Error running initial scan:", error);
      new import_obsidian3.Notice("Error analyzing files. Check console for details.");
      return [];
    }
  }
  async checkCurrentNote(abortSignal) {
    const activeFile = this.app.workspace.getActiveFile();
    if (!activeFile || !isSupportedFile(activeFile, this.plugin.settings)) {
      new import_obsidian3.Notice("Please open a markdown or MDX file first.");
      return null;
    }
    try {
      const { clearCacheForFile: clearCacheForFile2 } = await Promise.resolve().then(() => (init_seo_checker(), seo_checker_exports));
      clearCacheForFile2(activeFile.path);
      const { runSEOCheck: runSEOCheck2 } = await Promise.resolve().then(() => (init_seo_checker(), seo_checker_exports));
      const results = await runSEOCheck2(this.plugin, [activeFile], abortSignal ? { signal: abortSignal } : void 0);
      if (results.length > 0) {
        new import_obsidian3.Notice("SEO audit complete.");
        return results[0] || null;
      }
      return null;
    } catch (error) {
      if (error instanceof Error && error.name === "AbortError") {
        new import_obsidian3.Notice("SEO audit cancelled.");
        throw error;
      }
      console.error("Error checking current note:", error);
      new import_obsidian3.Notice("Error analyzing current note. Check console for details.");
      return null;
    }
  }
  async checkExternalLinks() {
    const activeFile = this.app.workspace.getActiveFile();
    if (!activeFile || !isSupportedFile(activeFile, this.plugin.settings)) {
      new import_obsidian3.Notice("Please open a markdown or MDX file first.");
      return null;
    }
    try {
      const originalVaultSetting = this.plugin.settings.enableExternalLinkVaultCheck;
      const originalExternalSetting = this.plugin.settings.checkExternalLinks;
      this.plugin.settings.enableExternalLinkVaultCheck = true;
      this.plugin.settings.checkExternalLinks = false;
      const { clearCacheForFile: clearCacheForFile2 } = await Promise.resolve().then(() => (init_seo_checker(), seo_checker_exports));
      clearCacheForFile2(activeFile.path);
      const { runSEOCheck: runSEOCheck2 } = await Promise.resolve().then(() => (init_seo_checker(), seo_checker_exports));
      const results = await runSEOCheck2(this.plugin, [activeFile]);
      this.plugin.settings.enableExternalLinkVaultCheck = originalVaultSetting;
      this.plugin.settings.checkExternalLinks = originalExternalSetting;
      if (results.length > 0) {
        new import_obsidian3.Notice("External links check complete.");
        return results[0] || null;
      }
      return null;
    } catch (error) {
      console.error("Error checking external links:", error);
      new import_obsidian3.Notice("Error checking external links. Check console for details.");
      return null;
    }
  }
  async checkAllExternalLinks() {
    try {
      const files = await this.plugin.getFilesToCheck();
      if (files.length === 0) {
        const fileTypeText = this.plugin.settings.enableMDXSupport ? "markdown or MDX files" : "markdown files";
        new import_obsidian3.Notice(`No ${fileTypeText} found in configured directories.`);
        return [];
      }
      const originalVaultSetting = this.plugin.settings.enableExternalLinkVaultCheck;
      const originalExternalSetting = this.plugin.settings.checkExternalLinks;
      this.plugin.settings.enableExternalLinkVaultCheck = true;
      this.plugin.settings.checkExternalLinks = false;
      const { clearAllCache: clearAllCache3 } = await Promise.resolve().then(() => (init_seo_checker(), seo_checker_exports));
      clearAllCache3();
      if (files.length > 20) {
        new import_obsidian3.Notice(`Checking external links for 404s on ${files.length} files... This may take a moment.`);
      }
      const { runSEOCheck: runSEOCheck2 } = await Promise.resolve().then(() => (init_seo_checker(), seo_checker_exports));
      const results = await runSEOCheck2(this.plugin, files);
      this.plugin.settings.enableExternalLinkVaultCheck = originalVaultSetting;
      this.plugin.settings.checkExternalLinks = originalExternalSetting;
      this.plugin.settings.cachedGlobalResults = results;
      this.plugin.settings.lastScanTimestamp = Date.now();
      await this.plugin.saveSettings();
      new import_obsidian3.Notice(`External links check complete with ${results.length} files.`);
      return results;
    } catch (error) {
      console.error("Error checking all external links:", error);
      new import_obsidian3.Notice("Error checking external links. Check console for details.");
      return [];
    }
  }
  async refreshGlobalResults(abortSignal) {
    let auditNotice = null;
    try {
      const { clearAllCache: clearAllCache3 } = await Promise.resolve().then(() => (init_seo_checker(), seo_checker_exports));
      clearAllCache3();
      const files = await this.plugin.getFilesToCheck();
      if (files.length === 0) {
        const fileTypeText = this.plugin.settings.enableMDXSupport ? "markdown or MDX files" : "markdown files";
        new import_obsidian3.Notice(`No ${fileTypeText} found in configured directories.`);
        return [];
      }
      if (files.length > 20) {
        auditNotice = new import_obsidian3.Notice(`Refreshing SEO audit on ${files.length} files... This may take a moment.`, 0);
      }
      const { runSEOCheck: runSEOCheck2 } = await Promise.resolve().then(() => (init_seo_checker(), seo_checker_exports));
      const results = await runSEOCheck2(this.plugin, files, abortSignal ? { signal: abortSignal } : void 0);
      this.plugin.settings.cachedGlobalResults = results;
      this.plugin.settings.lastScanTimestamp = Date.now();
      await this.plugin.saveSettings();
      if (auditNotice) {
        auditNotice.hide();
      }
      new import_obsidian3.Notice(`SEO audit complete with ${results.length} files.`);
      return results;
    } catch (error) {
      if (auditNotice) {
        auditNotice.hide();
      }
      if (error instanceof Error && error.name === "AbortError") {
        console.debug("Vault audit cancelled by user");
        new import_obsidian3.Notice("Vault audit cancelled.");
        return [];
      }
      console.error("Error checking all notes:", error);
      new import_obsidian3.Notice("Error analyzing files. Check console for details.");
      return [];
    }
  }
  async openFile(filePath) {
    try {
      const file = this.app.vault.getAbstractFileByPath(filePath);
      if (file && file instanceof import_obsidian3.TFile) {
        try {
          const leaf = this.app.workspace.getLeaf();
          await leaf.openFile(file);
        } catch (e) {
          await this.app.workspace.openLinkText(filePath, "", true);
        }
      } else {
        new import_obsidian3.Notice(`File not found: ${filePath}
This file may have been renamed or deleted.`);
      }
    } catch (error) {
      console.error(`Error opening file ${filePath}:`, error);
      new import_obsidian3.Notice(`Error opening file: ${filePath}`);
    }
  }
  async openFileAndAudit(filePath) {
    var _a;
    const file = this.app.vault.getAbstractFileByPath(filePath);
    if (!file || !(file instanceof import_obsidian3.TFile)) {
      new import_obsidian3.Notice(`File not found: ${filePath}
This file may have been renamed or deleted.`);
      return;
    }
    await this.app.workspace.getLeaf().openFile(file);
    this.plugin.openCurrentPanel();
    await new Promise((resolve) => {
      const checkPanel = () => {
        const currentPanels = this.app.workspace.getLeavesOfType("seo-current-panel");
        if (currentPanels.length > 0) {
          const currentPanel = currentPanels[0];
          if (currentPanel && currentPanel.view) {
            this.app.workspace.setActiveLeaf(currentPanel);
            if (currentPanel.view instanceof SEOSidePanel) {
              currentPanel.view.render();
            }
            resolve();
          } else {
            setTimeout(checkPanel, 50);
          }
        } else {
          setTimeout(checkPanel, 50);
        }
      };
      checkPanel();
    });
    try {
      if (!SEOSidePanel.globalAuditController) {
        SEOSidePanel.globalAuditController = new AbortController();
      }
      await new Promise((resolve) => setTimeout(resolve, 100));
      const currentPanels = this.app.workspace.getLeavesOfType("seo-current-panel");
      if (currentPanels.length > 0) {
        const currentPanel = currentPanels[0];
        if (currentPanel && currentPanel.view) {
          const refreshBtn = currentPanel.view.containerEl.querySelector("button[data-refresh-btn]");
          if (refreshBtn) {
            refreshBtn.disabled = true;
            refreshBtn.textContent = "Auditing...";
          }
        }
      }
      const results = await this.checkCurrentNote((_a = SEOSidePanel.globalAuditController) == null ? void 0 : _a.signal);
      if (results) {
        if (this.plugin.sidePanel && this.plugin.sidePanel.panelType === "current") {
          this.plugin.sidePanel.currentNoteResults = results;
          this.plugin.sidePanel.render();
        }
      }
    } catch (error) {
      console.error("Error running audit after opening file:", error);
    } finally {
      const currentPanels = this.app.workspace.getLeavesOfType("seo-current-panel");
      if (currentPanels.length > 0) {
        const currentPanel = currentPanels[0];
        if (currentPanel && currentPanel.view) {
          const refreshBtn = currentPanel.view.containerEl.querySelector("button[data-refresh-btn]");
          if (refreshBtn) {
            refreshBtn.disabled = false;
            refreshBtn.textContent = "Refresh";
          }
        }
      }
    }
  }
  showSortMenu(event, issuesFiles, issuesList, currentSort, onSortChange, settings) {
    const menu = new import_obsidian3.Menu();
    const showNotices = settings ? settings.checkPotentiallyBrokenLinks && settings.checkPotentiallyBrokenEmbeds : true;
    const displaySort = !showNotices && (currentSort === "notices-desc" || currentSort === "notices-asc") ? "issues-desc" : currentSort;
    menu.addItem((item) => {
      item.setTitle("Issues (high to low)").onClick(() => {
        onSortChange("issues-desc");
        this.sortAndRenderFiles(issuesFiles, issuesList, "issues-desc", settings);
      });
      if (displaySort === "issues-desc") {
        item.setIcon("check");
      }
    });
    menu.addItem((item) => {
      item.setTitle("Issues (low to high)").onClick(() => {
        onSortChange("issues-asc");
        this.sortAndRenderFiles(issuesFiles, issuesList, "issues-asc", settings);
      });
      if (displaySort === "issues-asc") {
        item.setIcon("check");
      }
    });
    menu.addSeparator();
    menu.addItem((item) => {
      item.setTitle("Warnings (high to low)").onClick(() => {
        onSortChange("warnings-desc");
        this.sortAndRenderFiles(issuesFiles, issuesList, "warnings-desc", settings);
      });
      if (displaySort === "warnings-desc") {
        item.setIcon("check");
      }
    });
    menu.addItem((item) => {
      item.setTitle("Warnings (low to high)").onClick(() => {
        onSortChange("warnings-asc");
        this.sortAndRenderFiles(issuesFiles, issuesList, "warnings-asc", settings);
      });
      if (displaySort === "warnings-asc") {
        item.setIcon("check");
      }
    });
    menu.addSeparator();
    if (showNotices) {
      menu.addItem((item) => {
        item.setTitle("Notices (high to low)").onClick(() => {
          onSortChange("notices-desc");
          this.sortAndRenderFiles(issuesFiles, issuesList, "notices-desc", settings);
        });
        if (displaySort === "notices-desc") {
          item.setIcon("check");
        }
      });
      menu.addItem((item) => {
        item.setTitle("Notices (low to high)").onClick(() => {
          onSortChange("notices-asc");
          this.sortAndRenderFiles(issuesFiles, issuesList, "notices-asc", settings);
        });
        if (displaySort === "notices-asc") {
          item.setIcon("check");
        }
      });
      menu.addSeparator();
    }
    menu.addItem((item) => {
      item.setTitle("File name (A to Z)").onClick(() => {
        onSortChange("filename-asc");
        this.sortAndRenderFiles(issuesFiles, issuesList, "filename-asc", settings);
      });
      if (displaySort === "filename-asc") {
        item.setIcon("check");
      }
    });
    menu.addItem((item) => {
      item.setTitle("File name (Z to A)").onClick(() => {
        onSortChange("filename-desc");
        this.sortAndRenderFiles(issuesFiles, issuesList, "filename-desc", settings);
      });
      if (displaySort === "filename-desc") {
        item.setIcon("check");
      }
    });
    menu.showAtPosition({ x: event.clientX, y: event.clientY });
  }
  sortAndRenderFiles(issuesFiles, issuesList, sortType, settings) {
    const sortedFiles = sortFiles([...issuesFiles], sortType);
    const filesListContainer = issuesList.querySelector(".seo-files-list-container");
    if (!filesListContainer) return;
    filesListContainer.querySelectorAll(".seo-file-issue").forEach((el) => el.remove());
    this.renderFilesList(sortedFiles, filesListContainer, settings);
  }
  renderFilesList(files, container, settings) {
    files.forEach((result) => {
      const fileEl = container.createEl("div", { cls: "seo-file-issue" });
      fileEl.setAttribute("data-file-path", result.file);
      const fileLink = fileEl.createEl("a", {
        text: result.displayName || this.getDisplayPath(result.file),
        cls: "seo-file-link",
        href: "#"
      });
      fileLink.addEventListener("click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        void this.openFile(result.file);
      });
      const statsContainer = fileEl.createEl("div", { cls: "seo-stats-container" });
      const showNotices = settings ? settings.checkPotentiallyBrokenLinks && settings.checkPotentiallyBrokenEmbeds : true;
      const statsText = [];
      if (result.issuesCount > 0) statsText.push(`${result.issuesCount} issues`);
      if (result.warningsCount > 0) statsText.push(`${result.warningsCount} warnings`);
      if (showNotices && result.noticesCount > 0) statsText.push(`${result.noticesCount} notices`);
      statsContainer.createEl("span", {
        text: statsText.join(", "),
        cls: "seo-file-stats"
      });
      const auditBtn = statsContainer.createEl("button", {
        cls: "clickable-icon seo-audit-btn",
        attr: { "aria-label": "Audit this note" }
      });
      (0, import_obsidian3.setIcon)(auditBtn, "search-check");
      auditBtn.addEventListener("click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        void (async () => {
          await this.openFileAndAudit(result.file);
        })();
      });
    });
  }
  getDisplayPath(fullPath) {
    const cleanPath = fullPath.startsWith("/") ? fullPath.slice(1) : fullPath;
    const parts = cleanPath.split("/");
    if (parts.length <= 1) {
      return cleanPath;
    }
    return parts.slice(-2).join("/");
  }
};

// src/ui/results-display.ts
var import_obsidian4 = require("obsidian");
init_panel_utils();
function createCollapseIcon() {
  const iconEl = document.createElement("span");
  (0, import_obsidian4.setIcon)(iconEl, "chevron-down");
  return iconEl;
}
var ResultsDisplay = class {
  // Track individual item collapse states
  constructor(container, onFileClick, onFileAudit) {
    this.container = container;
    this.onFileClick = onFileClick;
    this.onFileAudit = onFileAudit;
    this.isCollapsed = false;
    // Track collapse state (false = expanded, true = collapsed)
    this.individualCollapseStates = /* @__PURE__ */ new Map();
  }
  // Method to update the container (needed for reusing instances)
  updateContainer(newContainer) {
    this.container = newContainer;
  }
  // Navigation method to jump to specific positions in the note
  async navigateToPosition(position) {
    var _a;
    try {
      const app = window.app;
      if (!app) {
        console.warn("Obsidian app not found");
        return;
      }
      const activeFile = app.workspace.getActiveFile();
      if (!activeFile) {
        console.warn("No active file found for navigation");
        return;
      }
      await this.onFileClick(activeFile.path);
      let markdownView = null;
      let editor = null;
      if (app.workspace.getLeavesOfType) {
        const leaves = app.workspace.getLeavesOfType("markdown");
        if (leaves.length > 0) {
          markdownView = (_a = leaves[0]) == null ? void 0 : _a.view;
        }
      }
      if (!markdownView) {
        app.workspace.iterateAllLeaves((leaf) => {
          if (leaf.view && "editor" in leaf.view && !markdownView) {
            markdownView = leaf.view;
          }
        });
      }
      if (!markdownView) {
        console.warn("No markdown view found");
        return;
      }
      if (typeof markdownView === "object" && markdownView !== null && "editor" in markdownView) {
        editor = markdownView.editor;
      }
      if (!editor) {
        console.warn("No editor found in markdown view");
        return;
      }
      if (typeof editor !== "object" || editor === null) {
        console.warn("Editor is not an object");
        return;
      }
      const editorObj = editor;
      if (position.line && editorObj.setCursor) {
        const lineIndex = Math.max(0, position.line - 1);
        editorObj.setCursor({ line: lineIndex, ch: 0 });
        if (editorObj.scrollIntoView) {
          try {
            editorObj.scrollIntoView({ line: lineIndex, ch: 0 });
          } catch (e) {
            try {
              editorObj.scrollIntoView(lineIndex);
            } catch (e2) {
            }
          }
        }
        if (editorObj.getLine && editorObj.addHighlights && editorObj.removeHighlights) {
          try {
            const lineLength = editorObj.getLine(lineIndex).length;
            editorObj.addHighlights([{
              from: { line: lineIndex, ch: 0 },
              to: { line: lineIndex, ch: lineLength }
            }]);
            setTimeout(() => {
              try {
                if (editorObj.removeHighlights) {
                  editorObj.removeHighlights();
                }
              } catch (e) {
              }
            }, 2e3);
          } catch (e) {
          }
        }
      }
    } catch (error) {
      console.error("Error navigating to position:", error);
    }
  }
  renderResults(results) {
    let filteredNoticesCount = results.noticesCount;
    const scoreEl = this.container.createEl("div", { cls: "seo-score-header" });
    const scoreText = scoreEl.createEl("div", { cls: "seo-score-text" });
    scoreText.createEl("span", { text: "Score: " });
    const scoreNumber = scoreText.createEl("span", { text: `${Math.round(results.overallScore)}%` });
    if (results.overallScore >= 80) {
      scoreNumber.addClass("seo-score-excellent");
    } else if (results.overallScore >= 60) {
      scoreNumber.addClass("seo-score-good");
    } else if (results.overallScore >= 40) {
      scoreNumber.addClass("seo-score-fair");
    } else {
      scoreNumber.addClass("seo-score-poor");
    }
    if (results.issuesCount > 0 || results.warningsCount > 0 || filteredNoticesCount > 0) {
      scoreText.createEl("span", { text: ` (` });
      scoreText.createEl("span", {
        text: `${results.issuesCount} issues`,
        cls: "seo-issues-count-text"
      });
      scoreText.createEl("span", { text: ", " });
      scoreText.createEl("span", {
        text: `${results.warningsCount} warnings`,
        cls: "seo-warnings-count-text"
      });
      if (filteredNoticesCount > 0) {
        scoreText.createEl("span", { text: ", " });
        scoreText.createEl("span", {
          text: `${filteredNoticesCount} notices`,
          cls: "seo-notices-count-text"
        });
      }
      scoreText.createEl("span", { text: ")" });
    } else {
      scoreText.createEl("span", {
        // eslint-disable-next-line obsidianmd/ui/sentence-case
        text: " (All checks passed!)",
        cls: "seo-success"
      });
    }
    const toggleBtn = scoreEl.createEl("div", { cls: "seo-toggle-icon" });
    toggleBtn.setAttribute("aria-label", this.isCollapsed ? "Expand all" : "Collapse all");
    (0, import_obsidian4.setIcon)(toggleBtn, this.isCollapsed ? "chevrons-up-down" : "chevrons-down-up");
    toggleBtn.addEventListener("mousedown", (e) => {
      e.preventDefault();
      e.stopPropagation();
      this.isCollapsed = !this.isCollapsed;
      toggleBtn.setAttribute("aria-label", this.isCollapsed ? "Expand all" : "Collapse all");
      (0, import_obsidian4.setIcon)(toggleBtn, this.isCollapsed ? "chevrons-up-down" : "chevrons-down-up");
      this.toggleChecksVisibility();
    });
    const checksContainer = this.container.createEl("div", { cls: "seo-checks" });
    Object.entries(results.checks).forEach(([checkName, checkResults]) => {
      if (checkResults.length === 0) return;
      const checkHasErrors = checkResults.some((r) => r.severity === "error");
      const checkHasWarnings = checkResults.some((r) => r.severity === "warning");
      const checkHasNotices = checkResults.some((r) => r.severity === "notice");
      const checkHasOnlyInfo = checkResults.every((r) => r.severity === "info");
      let statusClass = "seo-passed";
      if (checkHasErrors) {
        statusClass = "seo-error";
      } else if (checkHasWarnings) {
        statusClass = "seo-warning";
      } else if (checkHasNotices) {
        statusClass = "seo-notice";
      } else if (checkHasOnlyInfo) {
        statusClass = "seo-passed";
      }
      const checkEl = checksContainer.createEl("div", { cls: `seo-check ${statusClass}` });
      const header = checkEl.createEl("div", { cls: "seo-check-header seo-collapsible-header" });
      header.setAttribute("data-check-name", checkName);
      let displayName = checkName.replace(/([A-Z])/g, " $1").trim().replace(/^./, (str) => str.toUpperCase());
      if (checkName === "brokenLinks") {
        displayName = "Broken Internal Links";
      } else if (checkName === "externalBrokenLinks") {
        const hasNotices2 = checkResults.some((r) => r.severity === "notice");
        const hasErrors2 = checkResults.some((r) => r.severity === "error");
        if (hasNotices2 && !hasErrors2) {
          displayName = "External Links";
        } else {
          displayName = "External Broken Links";
        }
      } else if (checkName === "duplicateTitles") {
        displayName = "Duplicate Titles";
      } else if (checkName === "duplicateDescriptions") {
        displayName = "Duplicate Descriptions";
      } else if (checkName === "duplicateContent") {
        displayName = "Duplicate Content";
      }
      const collapseIcon = header.createEl("span", { cls: "seo-collapse-icon" });
      collapseIcon.appendChild(createCollapseIcon());
      header.createEl("span", { text: displayName });
      const statusIcon = header.createEl("span", { cls: "seo-status" });
      const hasErrors = checkResults.some((r) => r.severity === "error");
      const hasWarnings = checkResults.some((r) => r.severity === "warning");
      const hasNotices = checkResults.some((r) => r.severity === "notice");
      if (hasErrors) {
        (0, import_obsidian4.setIcon)(statusIcon, "x-circle");
      } else if (hasWarnings) {
        (0, import_obsidian4.setIcon)(statusIcon, "triangle-alert");
      } else if (hasNotices) {
        (0, import_obsidian4.setIcon)(statusIcon, "info");
      } else {
        (0, import_obsidian4.setIcon)(statusIcon, "circle-check");
      }
      const resultsList = checkEl.createEl("ul", { cls: "seo-results seo-results-list-expanded" });
      const savedCollapseState = this.individualCollapseStates.get(checkName);
      if (savedCollapseState !== void 0) {
        if (savedCollapseState) {
          resultsList.classList.add("seo-results-list-collapsed");
          resultsList.classList.remove("seo-results-list-expanded");
          const icon = collapseIcon.querySelector("svg");
          if (icon) {
            icon.classList.add("seo-collapse-icon-rotated");
            icon.classList.remove("seo-collapse-icon-normal");
          }
        } else {
          resultsList.classList.add("seo-results-list-expanded");
          resultsList.classList.remove("seo-results-list-collapsed");
          const icon = collapseIcon.querySelector("svg");
          if (icon) {
            icon.classList.add("seo-collapse-icon-normal");
            icon.classList.remove("seo-collapse-icon-rotated");
          }
        }
      }
      checkResults.forEach((result) => {
        const li = resultsList.createEl("li", {
          cls: `seo-result seo-${result.severity}`
        });
        if (result.position) {
          const messageEl = li.createEl("span", {
            text: result.message,
            cls: "seo-result-message seo-clickable"
          });
          messageEl.addEventListener("click", (e) => {
            e.preventDefault();
            e.stopPropagation();
            void (async () => {
              await this.navigateToPosition(result.position);
            })();
          });
          messageEl.addClass("seo-clickable-message");
          messageEl.title = "Click to jump to this issue in the note";
        } else {
          li.createEl("span", {
            text: result.message,
            cls: "seo-result-message"
          });
        }
        if (result.suggestion) {
          const suggestionEl = li.createEl("div", {
            cls: "seo-suggestion"
          });
          if (result.suggestion.includes('<a href="#" data-file-path=')) {
            const parser = new DOMParser();
            const doc = parser.parseFromString(result.suggestion, "text/html");
            const body = doc.body;
            while (body.firstChild) {
              suggestionEl.appendChild(body.firstChild);
            }
            suggestionEl.addEventListener("click", (e) => {
              void (async () => {
                const target = e.target;
                if (target && target.tagName === "A" && target.hasAttribute("data-file-path")) {
                  e.preventDefault();
                  e.stopPropagation();
                  const filePath = target.getAttribute("data-file-path");
                  if (filePath) {
                    await this.onFileClick(filePath);
                  }
                }
              })();
            });
          } else {
            suggestionEl.textContent = result.suggestion;
          }
        }
      });
      header.addEventListener("mousedown", (e) => {
        var _a;
        e.preventDefault();
        e.stopPropagation();
        const currentState = (_a = this.individualCollapseStates.get(checkName)) != null ? _a : false;
        const newState = !currentState;
        this.individualCollapseStates.set(checkName, newState);
        if (newState) {
          resultsList.classList.remove("seo-results-list-expanded");
          resultsList.classList.add("seo-results-list-collapsed");
        } else {
          resultsList.classList.remove("seo-results-list-collapsed");
          resultsList.classList.add("seo-results-list-expanded");
        }
        const icon = collapseIcon.querySelector("svg");
        if (icon) {
          if (newState) {
            icon.classList.remove("seo-collapse-icon-normal");
            icon.classList.add("seo-collapse-icon-rotated");
          } else {
            icon.classList.remove("seo-collapse-icon-rotated");
            icon.classList.add("seo-collapse-icon-normal");
          }
        }
        if (!newState) {
          this.isCollapsed = false;
          const toggleBtn2 = this.container.querySelector(".seo-toggle-icon");
          if (toggleBtn2) {
            (0, import_obsidian4.setIcon)(toggleBtn2, "chevrons-down-up");
          }
        }
      });
    });
  }
  renderGlobalResults(results, settings) {
    const summary = this.container.createEl("div", { cls: "seo-vault-summary" });
    const totalFiles = results.length;
    const totalIssues = results.reduce((sum, r) => sum + r.issuesCount, 0);
    const totalWarnings = results.reduce((sum, r) => sum + r.warningsCount, 0);
    const totalNotices = results.reduce((sum, r) => sum + r.noticesCount, 0);
    const avgScore = Math.round(
      results.reduce((sum, r) => sum + r.overallScore, 0) / totalFiles
    );
    const showNotices = settings ? settings.checkPotentiallyBrokenLinks && settings.checkPotentiallyBrokenEmbeds : true;
    const statsGrid = summary.createEl("div", { cls: "seo-stats-grid" });
    const scoreStat = statsGrid.createEl("div", { cls: "seo-stat-item" });
    const scoreNumber = scoreStat.createEl("div", { cls: "seo-stat-number", text: `${avgScore}%` });
    if (avgScore >= 80) {
      scoreNumber.addClass("seo-score-excellent");
    } else if (avgScore >= 60) {
      scoreNumber.addClass("seo-score-good");
    } else if (avgScore >= 40) {
      scoreNumber.addClass("seo-score-fair");
    } else {
      scoreNumber.addClass("seo-score-poor");
    }
    scoreStat.createEl("div", { cls: "seo-stat-label", text: "Average score" });
    const issuesStat = statsGrid.createEl("div", { cls: "seo-stat-item" });
    const issuesNumber = issuesStat.createEl("div", { cls: "seo-stat-number", text: totalIssues.toString() });
    if (totalIssues > 0) {
      issuesNumber.addClass("seo-issues-count");
    }
    issuesStat.createEl("div", { cls: "seo-stat-label", text: "Issues" });
    const warningsStat = statsGrid.createEl("div", { cls: "seo-stat-item" });
    const warningsNumber = warningsStat.createEl("div", { cls: "seo-stat-number", text: totalWarnings.toString() });
    if (totalWarnings > 0) {
      warningsNumber.addClass("seo-warnings-count");
    }
    warningsStat.createEl("div", { cls: "seo-stat-label", text: "Warnings" });
    if (showNotices) {
      const noticesStat = statsGrid.createEl("div", { cls: "seo-stat-item" });
      const noticesNumber = noticesStat.createEl("div", { cls: "seo-stat-number", text: totalNotices.toString() });
      if (totalNotices > 0) {
        noticesNumber.addClass("seo-notices-count");
      }
      noticesStat.createEl("div", { cls: "seo-stat-label", text: "Notices" });
    }
  }
  renderIssuesList(results, currentSort, onSortChange, onShowSortMenu, settings) {
    const showNotices = settings ? settings.checkPotentiallyBrokenLinks && settings.checkPotentiallyBrokenEmbeds : true;
    const issuesFiles = results.filter((r) => {
      const hasIssues = r.issuesCount > 0;
      const hasWarnings = r.warningsCount > 0;
      const hasNotices = showNotices && r.noticesCount > 0;
      return hasIssues || hasWarnings || hasNotices;
    });
    if (issuesFiles.length === 0) return;
    const issuesList = this.container.createEl("div", { cls: "seo-issues-list" });
    const issuesHeader = issuesList.createEl("div", { cls: "seo-issues-header-container" });
    const collapseIcon = issuesHeader.createEl("span", { cls: "seo-collapse-icon seo-collapsible-header" });
    collapseIcon.setAttribute("aria-label", "Collapse list");
    collapseIcon.appendChild(createCollapseIcon());
    issuesHeader.createEl("h4", { text: "Files with results", cls: "seo-issues-header" });
    const sortBtn = issuesHeader.createEl("button", {
      cls: "seo-sort-btn",
      attr: { "aria-label": "Sort files" }
    });
    (0, import_obsidian4.setIcon)(sortBtn, "arrow-up-narrow-wide");
    sortBtn.addEventListener("click", onShowSortMenu);
    const filesListContainer = issuesList.createEl("div", { cls: "seo-files-list-container" });
    const sortedFiles = this.sortFiles(issuesFiles, currentSort);
    sortedFiles.forEach((result) => {
      const fileEl = filesListContainer.createEl("div", { cls: "seo-file-issue" });
      fileEl.setAttribute("data-file-path", result.file);
      const fileLink = fileEl.createEl("a", {
        text: result.displayName || getDisplayPath(result.file),
        cls: "seo-file-link",
        href: "#"
      });
      fileLink.addEventListener("click", (e) => {
        e.preventDefault();
        void (async () => {
          await this.onFileClick(result.file);
        })();
      });
      const statsContainer = fileEl.createEl("div", { cls: "seo-stats-container" });
      const statsText = [];
      if (result.issuesCount > 0) statsText.push(`${result.issuesCount} issues`);
      if (result.warningsCount > 0) statsText.push(`${result.warningsCount} warnings`);
      if (showNotices && result.noticesCount > 0) statsText.push(`${result.noticesCount} notices`);
      statsContainer.createEl("span", {
        text: statsText.join(", "),
        cls: "seo-file-stats"
      });
      const auditBtn = statsContainer.createEl("button", {
        cls: "clickable-icon seo-audit-btn",
        attr: { "aria-label": "Audit this note" }
      });
      (0, import_obsidian4.setIcon)(auditBtn, "search-check");
      auditBtn.addEventListener("click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        void (async () => {
          await this.onFileAudit(result.file);
        })();
      });
    });
    collapseIcon.addEventListener("mousedown", (e) => {
      e.preventDefault();
      e.stopPropagation();
      const isCollapsed = filesListContainer.classList.contains("seo-results-list-collapsed");
      if (isCollapsed) {
        filesListContainer.classList.remove("seo-results-list-collapsed");
        filesListContainer.classList.add("seo-results-list-expanded");
        collapseIcon.setAttribute("aria-label", "Collapse list");
      } else {
        filesListContainer.classList.remove("seo-results-list-expanded");
        filesListContainer.classList.add("seo-results-list-collapsed");
        collapseIcon.setAttribute("aria-label", "Expand list");
      }
      const icon = collapseIcon.querySelector("svg");
      if (icon) {
        if (isCollapsed) {
          icon.classList.remove("seo-collapse-icon-rotated");
          icon.classList.add("seo-collapse-icon-normal");
        } else {
          icon.classList.remove("seo-collapse-icon-normal");
          icon.classList.add("seo-collapse-icon-rotated");
        }
      }
    });
    this.renderPassingFilesList(results, currentSort, showNotices);
  }
  renderPassingFilesList(results, currentSort, showNotices) {
    const passingFiles = results.filter((r) => {
      const hasIssues = r.issuesCount > 0;
      const hasWarnings = r.warningsCount > 0;
      return !hasIssues && !hasWarnings;
    });
    if (passingFiles.length === 0) return;
    const passingList = this.container.createEl("div", { cls: "seo-issues-list" });
    const passingHeader = passingList.createEl("div", { cls: "seo-issues-header-container" });
    const collapseIcon = passingHeader.createEl("span", { cls: "seo-collapse-icon seo-collapsible-header" });
    collapseIcon.setAttribute("aria-label", "Expand list");
    collapseIcon.appendChild(createCollapseIcon());
    const icon = collapseIcon.querySelector("svg");
    if (icon) {
      icon.classList.add("seo-collapse-icon-rotated");
    }
    const headingContainer = passingHeader.createEl("div", { cls: "seo-heading-center" });
    headingContainer.createEl("h4", { text: "Files that pass", cls: "seo-issues-header" });
    const filesListContainer = passingList.createEl("div", {
      cls: "seo-files-list-container seo-results-list-collapsed"
    });
    const sortedFiles = this.sortFiles(passingFiles, currentSort);
    sortedFiles.forEach((result) => {
      const fileEl = filesListContainer.createEl("div", { cls: "seo-file-issue" });
      fileEl.setAttribute("data-file-path", result.file);
      const fileLink = fileEl.createEl("a", {
        text: result.displayName || getDisplayPath(result.file),
        cls: "seo-file-link",
        href: "#"
      });
      fileLink.addEventListener("click", (e) => {
        e.preventDefault();
        void (async () => {
          await this.onFileClick(result.file);
        })();
      });
      const statsContainer = fileEl.createEl("div", { cls: "seo-stats-container" });
      if (showNotices && result.noticesCount > 0) {
        statsContainer.createEl("span", {
          text: `${result.noticesCount} notices`,
          cls: "seo-file-stats"
        });
      }
      const auditBtn = statsContainer.createEl("button", {
        cls: "clickable-icon seo-audit-btn",
        attr: { "aria-label": "Audit this note" }
      });
      (0, import_obsidian4.setIcon)(auditBtn, "search-check");
      auditBtn.addEventListener("click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        void (async () => {
          await this.onFileAudit(result.file);
        })();
      });
    });
    collapseIcon.addEventListener("mousedown", (e) => {
      e.preventDefault();
      e.stopPropagation();
      const isCollapsed = filesListContainer.classList.contains("seo-results-list-collapsed");
      if (isCollapsed) {
        filesListContainer.classList.remove("seo-results-list-collapsed");
        filesListContainer.classList.add("seo-results-list-expanded");
        collapseIcon.setAttribute("aria-label", "Collapse list");
      } else {
        filesListContainer.classList.remove("seo-results-list-expanded");
        filesListContainer.classList.add("seo-results-list-collapsed");
        collapseIcon.setAttribute("aria-label", "Expand list");
      }
      const icon2 = collapseIcon.querySelector("svg");
      if (icon2) {
        if (isCollapsed) {
          icon2.classList.remove("seo-collapse-icon-rotated");
          icon2.classList.add("seo-collapse-icon-normal");
        } else {
          icon2.classList.remove("seo-collapse-icon-normal");
          icon2.classList.add("seo-collapse-icon-rotated");
        }
      }
    });
  }
  sortFiles(files, sortType) {
    const sortedFiles = [...files];
    switch (sortType) {
      case "notices-desc":
        sortedFiles.sort((a, b) => {
          const noticesCompare = b.noticesCount - a.noticesCount;
          if (noticesCompare !== 0) return noticesCompare;
          const issuesCompare = b.issuesCount - a.issuesCount;
          if (issuesCompare !== 0) return issuesCompare;
          const warningsCompare = b.warningsCount - a.warningsCount;
          if (warningsCompare !== 0) return warningsCompare;
          const aFileName = a.file.split("/").pop() || "";
          const bFileName = b.file.split("/").pop() || "";
          return aFileName.localeCompare(bFileName);
        });
        break;
      case "notices-asc":
        sortedFiles.sort((a, b) => {
          const noticesCompare = a.noticesCount - b.noticesCount;
          if (noticesCompare !== 0) return noticesCompare;
          const issuesCompare = a.issuesCount - b.issuesCount;
          if (issuesCompare !== 0) return issuesCompare;
          const warningsCompare = a.warningsCount - b.warningsCount;
          if (warningsCompare !== 0) return warningsCompare;
          const aFileName = a.file.split("/").pop() || "";
          const bFileName = b.file.split("/").pop() || "";
          return aFileName.localeCompare(bFileName);
        });
        break;
      case "warnings-desc":
        sortedFiles.sort((a, b) => {
          const warningsCompare = b.warningsCount - a.warningsCount;
          if (warningsCompare !== 0) return warningsCompare;
          const issuesCompare = b.issuesCount - a.issuesCount;
          if (issuesCompare !== 0) return issuesCompare;
          const aFileName = a.file.split("/").pop() || "";
          const bFileName = b.file.split("/").pop() || "";
          return aFileName.localeCompare(bFileName);
        });
        break;
      case "warnings-asc":
        sortedFiles.sort((a, b) => {
          const warningsCompare = a.warningsCount - b.warningsCount;
          if (warningsCompare !== 0) return warningsCompare;
          const issuesCompare = a.issuesCount - b.issuesCount;
          if (issuesCompare !== 0) return issuesCompare;
          const aFileName = a.file.split("/").pop() || "";
          const bFileName = b.file.split("/").pop() || "";
          return aFileName.localeCompare(bFileName);
        });
        break;
      case "issues-desc":
        sortedFiles.sort((a, b) => {
          const issuesCompare = b.issuesCount - a.issuesCount;
          if (issuesCompare !== 0) return issuesCompare;
          const warningsCompare = b.warningsCount - a.warningsCount;
          if (warningsCompare !== 0) return warningsCompare;
          const aFileName = a.file.split("/").pop() || "";
          const bFileName = b.file.split("/").pop() || "";
          return aFileName.localeCompare(bFileName);
        });
        break;
      case "issues-asc":
        sortedFiles.sort((a, b) => {
          const issuesCompare = a.issuesCount - b.issuesCount;
          if (issuesCompare !== 0) return issuesCompare;
          const warningsCompare = a.warningsCount - b.warningsCount;
          if (warningsCompare !== 0) return warningsCompare;
          const aFileName = a.file.split("/").pop() || "";
          const bFileName = b.file.split("/").pop() || "";
          return aFileName.localeCompare(bFileName);
        });
        break;
      case "filename-asc":
        sortedFiles.sort((a, b) => {
          const aFileName = a.file.split("/").pop() || "";
          const bFileName = b.file.split("/").pop() || "";
          const fileNameCompare = aFileName.localeCompare(bFileName);
          if (fileNameCompare !== 0) return fileNameCompare;
          const aParts = a.file.split("/");
          const bParts = b.file.split("/");
          const aParent = aParts.length > 1 ? aParts[aParts.length - 2] || "" : "";
          const bParent = bParts.length > 1 ? bParts[bParts.length - 2] || "" : "";
          const parentCompare = aParent.localeCompare(bParent);
          if (parentCompare !== 0) return parentCompare;
          if (b.issuesCount !== a.issuesCount) {
            return b.issuesCount - a.issuesCount;
          }
          if (b.warningsCount !== a.warningsCount) {
            return b.warningsCount - a.warningsCount;
          }
          return b.noticesCount - a.noticesCount;
        });
        break;
      case "filename-desc":
        sortedFiles.sort((a, b) => {
          const aFileName = a.file.split("/").pop() || "";
          const bFileName = b.file.split("/").pop() || "";
          const fileNameCompare = bFileName.localeCompare(aFileName);
          if (fileNameCompare !== 0) return fileNameCompare;
          const aParts = a.file.split("/");
          const bParts = b.file.split("/");
          const aParent = aParts.length > 1 ? aParts[aParts.length - 2] || "" : "";
          const bParent = bParts.length > 1 ? bParts[bParts.length - 2] || "" : "";
          const parentCompare = bParent.localeCompare(aParent);
          if (parentCompare !== 0) return parentCompare;
          if (b.issuesCount !== a.issuesCount) {
            return b.issuesCount - a.issuesCount;
          }
          if (b.warningsCount !== a.warningsCount) {
            return b.warningsCount - a.warningsCount;
          }
          return b.noticesCount - a.noticesCount;
        });
        break;
    }
    return sortedFiles;
  }
  toggleChecksVisibility() {
    const checksContainer = this.container.querySelector(".seo-checks");
    if (!checksContainer) return;
    const allHeaders = checksContainer.querySelectorAll(".seo-check-header");
    allHeaders.forEach((header) => {
      var _a;
      const htmlHeader = header;
      const resultsList = (_a = htmlHeader.parentElement) == null ? void 0 : _a.querySelector(".seo-results");
      const collapseIcon = htmlHeader.querySelector(".seo-collapse-icon svg");
      const checkName = this.findCheckNameForHeader(htmlHeader);
      if (resultsList && collapseIcon) {
        if (checkName) {
          this.individualCollapseStates.set(checkName, this.isCollapsed);
        }
        if (this.isCollapsed) {
          resultsList.classList.remove("seo-results-list-expanded");
          resultsList.classList.add("seo-results-list-collapsed");
          collapseIcon.classList.remove("seo-collapse-icon-normal");
          collapseIcon.classList.add("seo-collapse-icon-rotated");
        } else {
          resultsList.classList.remove("seo-results-list-collapsed");
          resultsList.classList.add("seo-results-list-expanded");
          collapseIcon.classList.remove("seo-collapse-icon-rotated");
          collapseIcon.classList.add("seo-collapse-icon-normal");
        }
      }
    });
  }
  // Helper method to find the check name for a given header
  findCheckNameForHeader(header) {
    return header.getAttribute("data-check-name");
  }
};

// src/ui/panel-renderer.ts
var import_obsidian6 = require("obsidian");
init_panel_utils();
var import_obsidian7 = require("obsidian");

// src/ui/csv-export.ts
var import_obsidian5 = require("obsidian");
var ISSUE_SEVERITIES = ["error", "warning", "notice"];
var CHECK_TYPE_LABELS = {
  altText: "Alt text",
  nakedLinks: "Naked links",
  headingOrder: "Heading order",
  keywordDensity: "Keyword density",
  keywordInTitle: "Keyword in title",
  keywordInDescription: "Keyword in description",
  keywordInSlug: "Keyword in slug",
  slugFormat: "Slug format",
  brokenLinks: "Broken links",
  externalBrokenLinks: "External broken links",
  metaDescription: "Meta description",
  titleLength: "Title length",
  contentLength: "Content length",
  imageFileNames: "Image file names",
  duplicateContent: "Duplicate content",
  duplicateTitles: "Duplicate titles",
  duplicateDescriptions: "Duplicate descriptions",
  readingLevel: "Reading level",
  potentiallyBrokenEmbeds: "Potentially broken embeds",
  potentiallyBrokenLinks: "Potentially broken links"
};
function escapeCsvField(value) {
  if (value === null || value === void 0) return "";
  const s = String(value);
  if (/[",\r\n]/.test(s)) {
    return '"' + s.replace(/"/g, '""') + '"';
  }
  return s;
}
function getCheckLabel(key) {
  var _a;
  return (_a = CHECK_TYPE_LABELS[key]) != null ? _a : key.replace(/([A-Z])/g, " $1").replace(/^./, (c) => c.toUpperCase()).trim();
}
function* iterateFindings(results) {
  for (const [key, list] of Object.entries(results.checks)) {
    if (!Array.isArray(list)) continue;
    for (const finding of list) {
      if (ISSUE_SEVERITIES.includes(finding.severity)) {
        yield { checkType: getCheckLabel(key), finding };
      }
    }
  }
}
function resultsToCsv(results) {
  var _a, _b, _c, _d;
  const rows = Array.isArray(results) ? results : [results];
  const header = ["file", "displayName", "checkType", "severity", "message", "line", "suggestion"];
  const lines = [header.map(escapeCsvField).join(",")];
  for (const r of rows) {
    const file = r.file;
    const displayName = (_a = r.displayName) != null ? _a : r.file;
    for (const { checkType, finding } of iterateFindings(r)) {
      const line = (_c = (_b = finding.position) == null ? void 0 : _b.line) != null ? _c : finding.line;
      lines.push([
        escapeCsvField(file),
        escapeCsvField(displayName),
        escapeCsvField(checkType),
        escapeCsvField(finding.severity),
        escapeCsvField(finding.message),
        escapeCsvField(line != null ? String(line) : ""),
        escapeCsvField((_d = finding.suggestion) != null ? _d : "")
      ].join(","));
    }
  }
  return lines.join("\r\n");
}
function resultsToMarkdown(results) {
  var _a, _b, _c;
  const rows = Array.isArray(results) ? results : [results];
  const lines = [];
  const severityHeadings = {
    error: "### Issues",
    warning: "### Warnings",
    notice: "### Notices"
  };
  for (const r of rows) {
    const displayName = (_a = r.displayName) != null ? _a : r.file;
    const bySeverity = {
      error: [],
      warning: [],
      notice: []
    };
    for (const item of iterateFindings(r)) {
      const sev = item.finding.severity;
      if (ISSUE_SEVERITIES.includes(sev)) {
        bySeverity[sev].push(item);
      }
    }
    const hasAny = bySeverity.error.length + bySeverity.warning.length + bySeverity.notice.length > 0;
    if (!hasAny) continue;
    lines.push(`## ${displayName}`);
    lines.push("");
    for (const sev of ISSUE_SEVERITIES) {
      const items = bySeverity[sev];
      if (items.length === 0) continue;
      lines.push(severityHeadings[sev]);
      for (const { checkType, finding } of items) {
        const lineNum = (_c = (_b = finding.position) == null ? void 0 : _b.line) != null ? _c : finding.line;
        const linePart = lineNum != null ? ` (line ${lineNum})` : "";
        let bullet = `- **${checkType}**${linePart}: ${finding.message}`;
        if (finding.suggestion) bullet += ` ${finding.suggestion}`;
        lines.push(bullet);
      }
      lines.push("");
    }
    lines.push("");
  }
  return lines.length ? lines.join("\n").trimEnd() : "";
}
function resultsToExportString(results, format) {
  return format === "markdown" ? resultsToMarkdown(results) : resultsToCsv(results);
}
function downloadExport(content, filenameBase, format) {
  const ext = format === "markdown" ? "md" : "csv";
  const mime = format === "markdown" ? "text/markdown;charset=utf-8" : "text/csv;charset=utf-8";
  const filename = filenameBase.endsWith(`.${ext}`) ? filenameBase : `${filenameBase}.${ext}`;
  const blob = new Blob([content], { type: mime });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = filename;
  a.click();
  URL.revokeObjectURL(url);
}
async function copyExportToClipboard(content) {
  try {
    await navigator.clipboard.writeText(content);
    new import_obsidian5.Notice("Copied to clipboard");
  } catch (e) {
    new import_obsidian5.Notice("Failed to copy");
  }
}

// src/ui/panel-renderer.ts
var PanelRenderer = class {
  constructor(app, plugin, actions) {
    this.app = app;
    this.plugin = plugin;
    this.actions = actions;
  }
  /**
   * Renders the main panel content
   */
  render(containerEl, panelType, currentNoteResults, globalResults, onRefreshClick, onExternalLinksClick) {
    var _a, _b;
    try {
      containerEl.empty();
      containerEl.addClass("seo-panel");
      const header = containerEl.createEl("div", { cls: "seo-panel-header" });
      const headerRow = header.createEl("div", { cls: "seo-panel-header-row" });
      headerRow.createEl("h2", { text: panelType === "current" ? "SEO audit: current note" : "SEO audit: vault" });
      if (panelType === "global" && globalResults.length > 0) {
        this.renderHeaderIcons(headerRow, "global", globalResults, null);
      } else if (panelType === "current") {
        let activeFile = this.app.workspace.getActiveFile();
        if (!activeFile) {
          const markdownLeaves = this.app.workspace.getLeavesOfType("markdown");
          for (const leaf of markdownLeaves) {
            const file = (_a = leaf.view) == null ? void 0 : _a.file;
            if (file) {
              activeFile = file;
              break;
            }
          }
        }
        const currentFileResults = activeFile && this.plugin.settings.cachedGlobalResults ? (_b = this.plugin.settings.cachedGlobalResults.find((r) => r.file === activeFile.path)) != null ? _b : null : null;
        const resultsToShow = currentNoteResults || currentFileResults;
        const hasActiveFile = activeFile && isSupportedFile(activeFile, this.plugin.settings);
        if (resultsToShow || hasActiveFile) {
          this.renderHeaderIcons(headerRow, "current", resultsToShow ? [resultsToShow] : [], resultsToShow != null ? resultsToShow : null);
        }
      }
      if (panelType === "current") {
        this.renderCurrentNoteHeader(header, currentNoteResults);
      } else {
        const fileCount = globalResults.length;
        const foldersInfo = getVaultFoldersInfo(this.plugin.settings.scanDirectories, fileCount);
        const foldersEl = header.createEl("div", { cls: "seo-filename" });
        foldersEl.textContent = foldersInfo;
      }
      if (panelType === "current") {
        this.renderCurrentNoteActions(containerEl, onRefreshClick, onExternalLinksClick);
      }
      if (panelType === "current") {
        this.renderCurrentNoteContent(containerEl, currentNoteResults);
      } else {
        if (globalResults.length > 0) {
          this.renderGlobalResults(containerEl, globalResults);
        } else {
          const noGlobal = containerEl.createEl("div", { cls: "seo-no-results" });
          noGlobal.createEl("p", { text: 'Click "Audit all notes" to audit your files in your configured directories.' });
        }
      }
    } catch (error) {
      console.error("Error rendering SEO panel:", error);
      containerEl.createEl("div", { text: "Error loading SEO panel. Please try again." });
    }
  }
  /**
   * Renders the current note header with file path
   */
  renderCurrentNoteHeader(header, currentNoteResults) {
    const activeFile = this.app.workspace.getActiveFile();
    if (activeFile && isSupportedFile(activeFile, this.plugin.settings)) {
      const filenameEl = header.createEl("div", { cls: "seo-filename" });
      let displayName = activeFile.path;
      if (currentNoteResults && currentNoteResults.file === activeFile.path) {
        displayName = currentNoteResults.displayName || activeFile.path;
      } else if (this.plugin.settings.cachedGlobalResults) {
        const globalResult = this.plugin.settings.cachedGlobalResults.find(
          (result) => result.file === activeFile.path
        );
        if (globalResult) {
          displayName = globalResult.displayName || activeFile.path;
        }
      }
      filenameEl.textContent = `Target note: ${displayName}`;
    }
  }
  /**
   * Renders minimal header icons (download for vault; copy + download for current note).
   */
  renderHeaderIcons(headerRow, panelType, resultsForCsv, singleNoteResult) {
    const wrap = headerRow.createEl("div", { cls: "seo-header-icon-wrap" });
    if (panelType === "current") {
      const copyBtn = wrap.createEl("button", { type: "button", cls: "clickable-icon", attr: { "aria-label": "Copy results to clipboard" } });
      (0, import_obsidian6.setIcon)(copyBtn, "lucide-copy");
      copyBtn.addEventListener("click", () => {
        if (!singleNoteResult) {
          new import_obsidian7.Notice("No results to export. Run Refresh first.");
          return;
        }
        const format = this.plugin.settings.exportFormat;
        const content = resultsToExportString(singleNoteResult, format);
        void copyExportToClipboard(content);
      });
      const downloadBtn = wrap.createEl("button", { type: "button", cls: "clickable-icon", attr: { "aria-label": "Download results" } });
      (0, import_obsidian6.setIcon)(downloadBtn, "lucide-download");
      downloadBtn.addEventListener("click", () => {
        if (!singleNoteResult) {
          new import_obsidian7.Notice("No results to export. Run Refresh first.");
          return;
        }
        const format = this.plugin.settings.exportFormat;
        const content = resultsToExportString(singleNoteResult, format);
        const base = singleNoteResult.file.replace(/\.[^.]+$/, "").replace(/[/\\]/g, "-") || "note";
        downloadExport(content, `seo-audit-${base}`, format);
      });
    } else if (panelType === "global" && resultsForCsv.length > 0) {
      const downloadBtn = wrap.createEl("button", { type: "button", cls: "clickable-icon", attr: { "aria-label": "Download results" } });
      (0, import_obsidian6.setIcon)(downloadBtn, "lucide-download");
      downloadBtn.addEventListener("click", () => {
        const format = this.plugin.settings.exportFormat;
        const content = resultsToExportString(resultsForCsv, format);
        downloadExport(content, "seo-vault-audit", format);
      });
    }
  }
  /**
   * Renders action buttons for current note panel
   */
  renderCurrentNoteActions(containerEl, onRefreshClick, onExternalLinksClick) {
    const auditCurrentBtn = containerEl.createEl("button", {
      text: "Refresh",
      cls: "mod-cta seo-btn seo-top-btn",
      attr: { "data-refresh-btn": "true" }
    });
    auditCurrentBtn.addEventListener("click", (event) => {
      event.preventDefault();
      event.stopPropagation();
      void (async () => {
        await onRefreshClick(auditCurrentBtn);
      })();
    });
    if (this.plugin.settings.enableExternalLinkButton && !this.plugin.settings.enableExternalLinkVaultCheck) {
      const externalLinksBtn = containerEl.createEl("button", {
        text: "Check external links for 404s",
        cls: "seo-btn"
      });
      externalLinksBtn.addEventListener("click", () => {
        void (async () => {
          externalLinksBtn.disabled = true;
          externalLinksBtn.textContent = "This may take some time...";
          externalLinksBtn.addClass("seo-btn-disabled");
          try {
            await onExternalLinksClick();
          } finally {
            externalLinksBtn.disabled = false;
            externalLinksBtn.textContent = "Check external links for 404s";
            externalLinksBtn.removeClass("seo-btn-disabled");
            externalLinksBtn.addClass("seo-btn-enabled");
          }
        })();
      });
    }
  }
  /**
   * Renders current note content
   */
  renderCurrentNoteContent(containerEl, currentNoteResults) {
    const activeFile = this.app.workspace.getActiveFile();
    let currentFileResults = null;
    if (activeFile && this.plugin.settings.cachedGlobalResults) {
      currentFileResults = this.plugin.settings.cachedGlobalResults.find(
        (result) => result.file === activeFile.path
      ) || null;
    }
    const resultsToShow = currentNoteResults || currentFileResults;
    if (resultsToShow) {
      const newResultsContainer = containerEl.createEl("div", { cls: "seo-results-container" });
      const tempResultsDisplay = new ResultsDisplay(
        newResultsContainer,
        async (filePath) => await this.actions.openFile(filePath),
        async (filePath) => await this.actions.openFileAndAudit(filePath)
      );
      tempResultsDisplay.renderResults(resultsToShow);
    } else {
      const noResults = containerEl.createEl("div", { cls: "seo-no-results" });
      const fileTypeText = this.plugin.settings.enableMDXSupport ? "markdown or MDX file" : "markdown file";
      noResults.createEl("p", { text: `Open a ${fileTypeText} and click "Refresh" to audit it.` });
    }
  }
  /**
   * Renders global results with sorting and filtering
   */
  renderGlobalResults(containerEl, globalResults) {
    const tempResultsDisplay = new ResultsDisplay(
      containerEl,
      async (filePath) => await this.actions.openFile(filePath),
      async (filePath) => await this.actions.openFileAndAudit(filePath)
    );
    tempResultsDisplay.renderGlobalResults(globalResults, this.plugin.settings);
    const refreshBtn = containerEl.createEl("button", {
      text: "Refresh",
      cls: "mod-cta seo-btn seo-refresh-btn",
      attr: { "data-refresh-btn": "true" }
    });
    if (this.plugin.settings.enableExternalLinkButton && !this.plugin.settings.enableExternalLinkVaultCheck) {
      const externalLinksBtn = containerEl.createEl("button", {
        text: "Check all 404s",
        cls: "seo-btn"
      });
      externalLinksBtn.addEventListener("click", () => {
        void (async () => {
          externalLinksBtn.disabled = true;
          externalLinksBtn.textContent = "Checking...";
          externalLinksBtn.addClass("seo-btn-disabled");
          try {
            const results = await this.actions.checkAllExternalLinks();
            if (results.length > 0) {
              containerEl.dispatchEvent(new CustomEvent("seo-refresh-complete", {
                detail: { results }
              }));
            }
          } finally {
            externalLinksBtn.disabled = false;
            externalLinksBtn.textContent = "Check all 404s";
            externalLinksBtn.removeClass("seo-btn-disabled");
            externalLinksBtn.addClass("seo-btn-enabled");
          }
        })();
      });
    }
    refreshBtn.addEventListener("click", () => {
      void (async () => {
        var _a;
        if (refreshBtn.textContent === "Cancel") {
          if (SEOSidePanel.globalAuditController) {
            SEOSidePanel.globalAuditController.abort();
            SEOSidePanel.globalAuditController = null;
            console.debug("Vault audit cancelled by user");
          }
          refreshBtn.textContent = "Refresh";
          refreshBtn.disabled = false;
          return;
        }
        refreshBtn.textContent = "Cancel";
        refreshBtn.disabled = false;
        SEOSidePanel.globalAuditController = new AbortController();
        try {
          const results = await this.actions.refreshGlobalResults((_a = SEOSidePanel.globalAuditController) == null ? void 0 : _a.signal);
          if (results.length > 0) {
            if (SEOSidePanel.globalAuditController && !SEOSidePanel.globalAuditController.signal.aborted) {
              containerEl.dispatchEvent(new CustomEvent("seo-refresh-complete", {
                detail: { results }
              }));
            }
          }
        } catch (error) {
          if (error instanceof Error && error.name === "AbortError") {
            console.debug("Vault audit cancelled in panel renderer");
          } else {
            console.error("Error in vault audit:", error);
          }
        } finally {
          refreshBtn.textContent = "Refresh";
          refreshBtn.disabled = false;
        }
      })();
    });
    tempResultsDisplay.renderIssuesList(
      globalResults,
      this.plugin.settings.defaultSort,
      (sortType) => {
        containerEl.dispatchEvent(new CustomEvent("seo-sort-change", {
          detail: { sortType }
        }));
      },
      (event) => {
        const showNotices = this.plugin.settings.checkPotentiallyBrokenLinks && this.plugin.settings.checkPotentiallyBrokenEmbeds;
        const issuesFiles = globalResults.filter((r) => {
          const hasIssues = r.issuesCount > 0;
          const hasWarnings = r.warningsCount > 0;
          const hasNotices = showNotices && r.noticesCount > 0;
          return hasIssues || hasWarnings || hasNotices;
        });
        this.actions.showSortMenu(event, issuesFiles, containerEl, this.plugin.settings.defaultSort, (sortType) => {
          containerEl.dispatchEvent(new CustomEvent("seo-sort-change", {
            detail: { sortType }
          }));
        }, this.plugin.settings);
      },
      this.plugin.settings
    );
  }
};

// src/ui/side-panel.ts
var _SEOSidePanel = class _SEOSidePanel extends import_obsidian8.ItemView {
  constructor(plugin, panelType = "current", leaf) {
    super(leaf || plugin.app.workspace.getLeaf());
    this.currentNoteResults = null;
    this.globalResults = [];
    this.panelType = "current";
    this.hasRunInitialScan = false;
    this.isRefreshing = false;
    this.currentAuditController = null;
    this.plugin = plugin;
    this.panelType = panelType;
    this.currentSort = plugin.settings.defaultSort;
    this.actions = new PanelActions(this.app, this.plugin, this.panelType);
    this.resultsDisplay = new ResultsDisplay(
      this.containerEl,
      async (filePath) => await this.actions.openFile(filePath),
      async (filePath) => await this.actions.openFileAndAudit(filePath)
    );
    this.renderer = new PanelRenderer(this.app, this.plugin, this.actions);
    if (this.panelType === "current") {
      this.registerEvent(this.app.workspace.on("active-leaf-change", () => {
        if (_SEOSidePanel.globalAuditController) {
          _SEOSidePanel.globalAuditController.abort();
          _SEOSidePanel.globalAuditController = null;
          console.debug("Audit cancelled due to file switch");
        }
        if (this.isRefreshing) {
          return;
        }
        const activeFile = this.app.workspace.getActiveFile();
        if (!activeFile) {
          return;
        }
        if (activeFile) {
          this.updateCurrentNoteResults(activeFile);
        }
      }));
    }
  }
  getViewType() {
    return this.panelType === "current" ? SEOCurrentPanelViewType : SEOGlobalPanelViewType;
  }
  async handleRefreshClick(button) {
    if (button.disabled) {
      return;
    }
    if (_SEOSidePanel.globalAuditController) {
      _SEOSidePanel.globalAuditController.abort();
    }
    _SEOSidePanel.globalAuditController = new AbortController();
    this.isRefreshing = true;
    button.textContent = "Refreshing...";
    button.disabled = true;
    try {
      const result = await this.actions.checkCurrentNote(_SEOSidePanel.globalAuditController.signal);
      if (result) {
        this.currentNoteResults = result;
        this.updateGlobalResultsIfExists(result);
        this.render();
      }
    } catch (error) {
      if (error instanceof Error && error.name === "AbortError") {
        console.debug("Audit was cancelled");
        return;
      }
      console.error("Error in refresh button:", error);
    } finally {
      this.isRefreshing = false;
      button.textContent = "Refresh";
      button.disabled = false;
      _SEOSidePanel.globalAuditController = null;
    }
  }
  getDisplayText() {
    return this.panelType === "current" ? "SEO audit: current note" : "SEO audit: vault";
  }
  getIcon() {
    return "search-check";
  }
  async onOpen() {
    try {
      if (this.panelType === "global" && this.plugin.settings.cachedGlobalResults.length > 0) {
        this.globalResults = this.plugin.settings.cachedGlobalResults;
      }
      this.forceIconRefresh();
      if (this.panelType === "global" && !this.hasRunInitialScan && this.globalResults.length === 0) {
        this.showLoadingState();
        this.hasRunInitialScan = true;
        await this.runInitialScan();
      } else {
        this.render();
        if (this.panelType === "current") {
          setTimeout(() => this.render(), 300);
          setTimeout(() => this.render(), 800);
        }
      }
    } catch (error) {
      console.error("Error opening SEO panel:", error);
    }
  }
  async onClose() {
  }
  cleanup() {
  }
  // Run initial scan for global panel
  async runInitialScan() {
    const results = await this.actions.runInitialScan();
    if (results.length > 0) {
      this.globalResults = results;
      this.render();
    }
  }
  // Force icon refresh to handle Obsidian's icon caching issues
  forceIconRefresh() {
    if (this.leaf) {
      void this.leaf.setViewState({
        type: this.getViewType(),
        state: this.leaf.view.getState()
      });
    }
    setTimeout(() => {
      const iconEl = this.containerEl.querySelector(".view-header-icon");
      if (iconEl) {
        while (iconEl.firstChild) {
          iconEl.removeChild(iconEl.firstChild);
        }
        iconEl.setAttribute("data-icon", "search-check");
        iconEl.setAttribute("data-icon", "");
        void iconEl.offsetHeight;
        iconEl.setAttribute("data-icon", "search-check");
      }
      const headerEl = this.containerEl.querySelector(".view-header");
      if (headerEl) {
        headerEl.addClass("seo-header-hidden");
        void headerEl.offsetHeight;
        headerEl.removeClass("seo-header-hidden");
        headerEl.addClass("seo-header-visible");
      }
      this.app.workspace.trigger("layout-change");
    }, 50);
  }
  updateCurrentNoteResults(file) {
    if (!file || !file.path.endsWith(".md")) {
      return;
    }
    try {
      let currentFileResults = null;
      if (this.plugin.settings.cachedGlobalResults) {
        currentFileResults = this.plugin.settings.cachedGlobalResults.find(
          (result) => result.file === file.path
        ) || null;
      }
      if (currentFileResults) {
        this.currentNoteResults = currentFileResults;
      } else {
        this.currentNoteResults = null;
      }
      const resultsToShow = this.currentNoteResults;
      const existingResults = this.containerEl.querySelectorAll(".seo-results-container, .seo-file-issue, .seo-issue, .seo-warning, .seo-notice, .seo-info-note, .seo-check, .seo-result, .seo-score-header, .seo-score-text, .seo-score-number, .seo-toggle-icon, .seo-collapse-icon");
      existingResults.forEach((el) => el.remove());
      if (resultsToShow) {
        const newResultsContainer = this.containerEl.createEl("div", { cls: "seo-results-container" });
        if (!this.resultsDisplay) {
          this.resultsDisplay = new ResultsDisplay(
            newResultsContainer,
            async (filePath) => await this.actions.openFile(filePath),
            async (filePath) => await this.actions.openFileAndAudit(filePath)
          );
        } else {
          this.resultsDisplay.updateContainer(newResultsContainer);
        }
        this.resultsDisplay.renderResults(resultsToShow);
      } else {
        this.containerEl.createEl("div", {
          cls: "seo-info-note seo-no-results-message",
          // eslint-disable-next-line obsidianmd/ui/sentence-case
          text: 'No SEO results available for this file. Click "Refresh" to run an audit.'
        });
      }
      this.updateHeaderAndResults();
    } catch (error) {
      console.error("Error updating current note results:", error);
    }
  }
  /**
   * Updates only the header and results content without re-rendering the entire panel
   * This preserves button event listeners and other UI state
   */
  updateHeaderAndResults() {
    const header = this.containerEl.querySelector(".seo-panel-header");
    if (header) {
      const filenameEl = header.querySelector(".seo-filename");
      if (filenameEl) {
        const activeFile = this.app.workspace.getActiveFile();
        if (activeFile && isSupportedFile(activeFile, this.plugin.settings)) {
          let displayName = activeFile.path;
          if (this.currentNoteResults && this.currentNoteResults.file === activeFile.path) {
            displayName = this.currentNoteResults.displayName || activeFile.path;
          } else if (this.plugin.settings.cachedGlobalResults) {
            const globalResult = this.plugin.settings.cachedGlobalResults.find(
              (result) => result.file === activeFile.path
            );
            if (globalResult) {
              displayName = globalResult.displayName || activeFile.path;
            }
          }
          filenameEl.textContent = `Target note: ${displayName}`;
        }
      }
    }
  }
  updateResults(results) {
    this.globalResults = results;
    this.render();
  }
  // Show loading state for large scans
  showLoadingState() {
    const { containerEl } = this;
    containerEl.empty();
    containerEl.addClass("seo-panel");
    const header = containerEl.createEl("div", { cls: "seo-panel-header" });
    header.createEl("h2", { text: this.panelType === "current" ? "SEO audit: current note" : "SEO audit: vault" });
    if (this.panelType === "global") {
      const foldersInfo = getVaultFoldersInfo(this.plugin.settings.scanDirectories);
      const foldersEl = header.createEl("div", { cls: "seo-filename" });
      foldersEl.textContent = foldersInfo;
    }
    const loadingContainer = containerEl.createEl("div", { cls: "seo-loading-container" });
    const spinnerEl = loadingContainer.createEl("div", { cls: "seo-loading-spinner" });
    (0, import_obsidian8.setIcon)(spinnerEl, "loader-circle");
    loadingContainer.createEl("h3", {
      // eslint-disable-next-line obsidianmd/ui/sentence-case
      text: "Running SEO audit...",
      cls: "seo-loading-title"
    });
    loadingContainer.createEl("p", {
      text: "This may take a moment for large vaults. Please wait...",
      cls: "seo-loading-message"
    });
  }
  // Method to refresh the panel when settings change
  refresh() {
    this.render();
  }
  render() {
    this.renderer.render(
      this.containerEl,
      this.panelType,
      this.currentNoteResults,
      this.globalResults,
      async (btn) => await this.handleRefreshClick(btn),
      async () => {
        const result = await this.actions.checkExternalLinks();
        if (result) {
          this.currentNoteResults = result;
          this.updateGlobalResultsIfExists(result);
          this.render();
        }
      }
    );
    this.containerEl.addEventListener("seo-refresh-complete", (event) => {
      const customEvent = event;
      this.globalResults = customEvent.detail.results;
      this.render();
    });
    this.containerEl.addEventListener("seo-sort-change", (event) => {
      const customEvent = event;
      this.currentSort = customEvent.detail.sortType;
      this.plugin.settings.defaultSort = customEvent.detail.sortType;
      void this.plugin.saveSettings();
      this.render();
    });
  }
  /**
   * Legacy render method - kept as fallback during migration
   * TODO: Remove once PanelRenderer is fully tested
   */
  renderLegacy() {
    try {
      const { containerEl } = this;
      containerEl.empty();
      containerEl.addClass("seo-panel");
      const header = containerEl.createEl("div", { cls: "seo-panel-header" });
      header.createEl("h2", { text: this.panelType === "current" ? "SEO audit: current note" : "SEO audit: vault" });
      if (this.panelType === "current") {
        const activeFile = this.app.workspace.getActiveFile();
        if (activeFile && isSupportedFile(activeFile, this.plugin.settings)) {
          const filenameEl = header.createEl("div", { cls: "seo-filename" });
          let displayName = activeFile.path;
          if (this.currentNoteResults && this.currentNoteResults.file === activeFile.path) {
            displayName = this.currentNoteResults.displayName || activeFile.path;
          } else if (this.plugin.settings.cachedGlobalResults) {
            const globalResult = this.plugin.settings.cachedGlobalResults.find(
              (result) => result.file === activeFile.path
            );
            if (globalResult) {
              displayName = globalResult.displayName || activeFile.path;
            }
          }
          filenameEl.textContent = `Target note: ${displayName}`;
        }
      } else {
        const fileCount = this.globalResults.length;
        const foldersInfo = getVaultFoldersInfo(this.plugin.settings.scanDirectories, fileCount);
        const foldersEl = header.createEl("div", { cls: "seo-filename" });
        foldersEl.textContent = foldersInfo;
      }
      if (this.panelType === "current") {
        const auditCurrentBtn = containerEl.createEl("button", {
          text: "Refresh",
          cls: "mod-cta seo-btn seo-top-btn"
        });
        auditCurrentBtn.addEventListener("click", (event) => {
          event.preventDefault();
          event.stopPropagation();
          void (async () => {
            await this.handleRefreshClick(auditCurrentBtn);
          })();
        });
        if (this.plugin.settings.enableExternalLinkButton && !this.plugin.settings.enableExternalLinkVaultCheck) {
          const externalLinksBtn = containerEl.createEl("button", {
            text: "Check external links for 404s",
            cls: "seo-btn"
          });
          externalLinksBtn.addEventListener("click", () => {
            void (async () => {
              externalLinksBtn.disabled = true;
              externalLinksBtn.textContent = "This may take some time...";
              externalLinksBtn.addClass("seo-btn-disabled");
              try {
                const result = await this.actions.checkExternalLinks();
                if (result) {
                  this.currentNoteResults = result;
                  this.updateGlobalResultsIfExists(result);
                  this.render();
                }
              } finally {
                externalLinksBtn.disabled = false;
                externalLinksBtn.textContent = "Check external links for 404s";
                externalLinksBtn.removeClass("seo-btn-disabled");
                externalLinksBtn.addClass("seo-btn-enabled");
              }
            })();
          });
        }
      }
      if (this.panelType === "current") {
        const activeFile = this.app.workspace.getActiveFile();
        let currentFileResults = null;
        if (activeFile && this.plugin.settings.cachedGlobalResults) {
          currentFileResults = this.plugin.settings.cachedGlobalResults.find(
            (result) => result.file === activeFile.path
          ) || null;
        }
        const resultsToShow = this.currentNoteResults || currentFileResults;
        if (resultsToShow) {
          const newResultsContainer = this.containerEl.createEl("div", { cls: "seo-results-container" });
          const tempResultsDisplay = new ResultsDisplay(
            newResultsContainer,
            async (filePath) => await this.actions.openFile(filePath),
            async (filePath) => await this.actions.openFileAndAudit(filePath)
          );
          tempResultsDisplay.renderResults(resultsToShow);
        } else {
          const noResults = containerEl.createEl("div", { cls: "seo-no-results" });
          const fileTypeText = this.plugin.settings.enableMDXSupport ? "markdown or MDX file" : "markdown file";
          noResults.createEl("p", { text: `Open a ${fileTypeText} and click "Refresh" to audit it.` });
        }
      } else {
        if (this.globalResults.length > 0) {
          this.renderGlobalResults(containerEl);
        } else {
          const noGlobal = containerEl.createEl("div", { cls: "seo-no-results" });
          noGlobal.createEl("p", { text: 'Click "Audit all notes" to audit your files in your configured directories.' });
        }
      }
    } catch (error) {
      console.error("Error rendering SEO panel:", error);
      this.containerEl.createEl("div", { text: "Error loading SEO panel. Please try again." });
    }
  }
  shouldIncludeFileInGlobalResults(filePath) {
    if (this.plugin.settings.ignoreUnderscoreFiles) {
      const fileName = filePath.split("/").pop() || "";
      const basename = fileName.replace(/\.[^/.]+$/, "");
      if (basename.startsWith("_")) {
        return false;
      }
    }
    const { scanDirectories } = this.plugin.settings;
    if (scanDirectories.trim()) {
      const directories = scanDirectories.split(",").map((dir) => dir.trim());
      const isInConfiguredDirectory = directories.some(
        (dir) => filePath.startsWith(dir + "/") || filePath === dir
      );
      return isInConfiguredDirectory;
    }
    return true;
  }
  updateGlobalResultsIfExists(currentResult) {
    if (this.plugin.settings.cachedGlobalResults && this.plugin.settings.cachedGlobalResults.length > 0) {
      if (!this.shouldIncludeFileInGlobalResults(currentResult.file)) {
        return;
      }
      const globalResults = this.plugin.settings.cachedGlobalResults;
      const existingIndex = globalResults.findIndex((r) => r.file === currentResult.file);
      if (existingIndex !== -1) {
        globalResults[existingIndex] = currentResult;
      } else {
        globalResults.push(currentResult);
      }
      void this.plugin.saveSettings();
      this.updateGlobalPanelIfOpen();
    }
  }
  updateDisplayName() {
    const activeFile = this.app.workspace.getActiveFile();
    if (activeFile && isSupportedFile(activeFile, this.plugin.settings)) {
      const filenameEl = this.containerEl.querySelector(".seo-filename");
      if (filenameEl) {
        let displayName = activeFile.path;
        if (this.currentNoteResults && this.currentNoteResults.file === activeFile.path) {
          displayName = this.currentNoteResults.displayName || activeFile.path;
        } else if (this.plugin.settings.cachedGlobalResults) {
          const globalResult = this.plugin.settings.cachedGlobalResults.find(
            (result) => result.file === activeFile.path
          );
          if (globalResult) {
            displayName = globalResult.displayName || activeFile.path;
          }
        }
        filenameEl.textContent = `Target note: ${displayName}`;
      }
      this.updateResultsForCurrentFile();
    }
  }
  updateResultsForCurrentFile() {
    const activeFile = this.app.workspace.getActiveFile();
    let currentFileResults = null;
    if (activeFile && this.plugin.settings.cachedGlobalResults) {
      currentFileResults = this.plugin.settings.cachedGlobalResults.find(
        (result) => result.file === activeFile.path
      ) || null;
    }
    const resultsToShow = this.currentNoteResults || currentFileResults;
    const existingResults = this.containerEl.querySelectorAll(".seo-results-container, .seo-file-issue, .seo-issue, .seo-warning, .seo-notice, .seo-info-note, .seo-check, .seo-result, .seo-score-header, .seo-score-text, .seo-score-number, .seo-toggle-icon, .seo-collapse-icon");
    existingResults.forEach((el) => el.remove());
    if (resultsToShow) {
      const newResultsContainer = this.containerEl.createEl("div", { cls: "seo-results-container" });
      const tempResultsDisplay = new ResultsDisplay(
        newResultsContainer,
        async (filePath) => await this.actions.openFile(filePath),
        async (filePath) => await this.actions.openFileAndAudit(filePath)
      );
      tempResultsDisplay.renderResults(resultsToShow);
    } else {
      const noResults = this.containerEl.createEl("div", { cls: "seo-no-results" });
      noResults.createEl("p", { text: 'Open a markdown file and click "Refresh" to audit it.' });
    }
  }
  updateGlobalPanelIfOpen() {
    const globalPanels = this.app.workspace.getLeavesOfType("seo-global-panel");
    if (globalPanels.length > 0) {
      const globalPanel = globalPanels[0];
      if ((globalPanel == null ? void 0 : globalPanel.view) && globalPanel.view instanceof _SEOSidePanel) {
        const seoPanel = globalPanel.view;
        seoPanel.globalResults = [...this.plugin.settings.cachedGlobalResults];
        seoPanel.render();
      }
    }
  }
  renderGlobalResults(container) {
    this.resultsDisplay.renderGlobalResults(this.globalResults, this.plugin.settings);
    const refreshBtn = container.createEl("button", {
      text: "Refresh",
      cls: "mod-cta seo-btn seo-refresh-btn",
      attr: { "data-refresh-btn": "true" }
    });
    refreshBtn.addEventListener("click", () => {
      void (async () => {
        refreshBtn.textContent = "Refreshing...";
        refreshBtn.disabled = true;
        try {
          const results = await this.actions.refreshGlobalResults();
          if (results.length > 0) {
            this.globalResults = results;
            this.render();
          }
        } finally {
          refreshBtn.textContent = "Refresh";
          refreshBtn.disabled = false;
        }
      })();
    });
    this.resultsDisplay.renderIssuesList(
      this.globalResults,
      this.currentSort,
      (sortType) => {
        this.currentSort = sortType;
        this.plugin.settings.defaultSort = sortType;
        void this.plugin.saveSettings();
      },
      (event) => {
        const showNotices = this.plugin.settings.checkPotentiallyBrokenLinks && this.plugin.settings.checkPotentiallyBrokenEmbeds;
        const issuesFiles = this.globalResults.filter((r) => {
          const hasIssues = r.issuesCount > 0;
          const hasWarnings = r.warningsCount > 0;
          const hasNotices = showNotices && r.noticesCount > 0;
          return hasIssues || hasWarnings || hasNotices;
        });
        this.actions.showSortMenu(event, issuesFiles, container, this.currentSort, (sortType) => {
          this.currentSort = sortType;
          this.plugin.settings.defaultSort = sortType;
          void this.plugin.saveSettings();
        }, this.plugin.settings);
      },
      this.plugin.settings
    );
  }
};
// Static property to track the current audit controller globally
_SEOSidePanel.globalAuditController = null;
var SEOSidePanel = _SEOSidePanel;

// src/ui/panel-manager.ts
var import_obsidian9 = require("obsidian");
var PanelManager = class {
  constructor(app, plugin) {
    this.app = app;
    this.plugin = plugin;
  }
  openCurrentPanel() {
    void this.openPanel(SEOCurrentPanelViewType, "current");
  }
  openGlobalPanel() {
    void this.openPanel(SEOGlobalPanelViewType, "global");
  }
  async openPanel(viewType, panelType) {
    if (this.app.workspace.rightSplit.collapsed) {
      this.app.workspace.rightSplit.expand();
    }
    const existingLeaf = this.app.workspace.getLeavesOfType(viewType)[0];
    if (existingLeaf) {
      await this.app.workspace.revealLeaf(existingLeaf);
      this.app.workspace.setActiveLeaf(existingLeaf);
      if (existingLeaf.view instanceof SEOSidePanel) {
        this.plugin.sidePanel = existingLeaf.view;
      }
      if (existingLeaf.view instanceof SEOSidePanel) {
        const panel = existingLeaf.view;
        if (typeof panel.forceIconRefresh === "function") {
          setTimeout(() => panel.forceIconRefresh(), 100);
        }
      }
    } else {
      const leaf = this.app.workspace.getRightLeaf(false);
      if (leaf) {
        const panel = new SEOSidePanel(this.plugin, panelType, leaf);
        this.plugin.sidePanel = panel;
        await leaf.open(panel);
        this.app.workspace.setActiveLeaf(leaf);
        setTimeout(() => {
          if (leaf.view instanceof SEOSidePanel) {
            if (typeof leaf.view.forceIconRefresh === "function") {
              leaf.view.forceIconRefresh();
            }
          }
        }, 100);
      }
    }
  }
  getFilesToCheck() {
    const { vault } = this.app;
    const { scanDirectories, ignoreUnderscoreFiles, enableMDXSupport } = this.plugin.settings;
    let files;
    if (!scanDirectories.trim()) {
      files = vault.getMarkdownFiles();
      if (enableMDXSupport) {
        const mdxFiles = vault.getFiles().filter(
          (file) => file instanceof import_obsidian9.TFile && file.extension.toLowerCase() === "mdx"
        );
        files = [...files, ...mdxFiles];
      }
    } else {
      const directories = scanDirectories.split(",").map((dir) => dir.trim());
      files = [];
      for (const dir of directories) {
        const folder = vault.getAbstractFileByPath(dir);
        if (folder && folder instanceof import_obsidian9.TFolder) {
          const mdFiles = vault.getMarkdownFiles().filter(
            (file) => file.path.startsWith(dir + "/") || file.path === dir
          );
          files.push(...mdFiles);
          if (enableMDXSupport) {
            const mdxFiles = vault.getFiles().filter(
              (file) => file instanceof import_obsidian9.TFile && file.extension.toLowerCase() === "mdx" && (file.path.startsWith(dir + "/") || file.path === dir)
            );
            files.push(...mdxFiles);
          }
        }
      }
    }
    files = files.filter((file) => isSupportedFile(file, this.plugin.settings));
    if (ignoreUnderscoreFiles) {
      files = files.filter((file) => {
        const fileName = file.basename;
        return !fileName.startsWith("_");
      });
    }
    return Promise.resolve(files);
  }
};

// src/utils/error-handler.ts
var import_obsidian10 = require("obsidian");
var SEOPluginError = class extends Error {
  constructor(message, severity = "error" /* ERROR */, userMessage, suggestion) {
    super(message);
    this.name = "SEOPluginError";
    this.severity = severity;
    this.userMessage = userMessage || message;
    this.suggestion = suggestion;
  }
};
function handleError(error, context, showNotice = true) {
  let message;
  let severity = "error" /* ERROR */;
  let suggestion;
  if (error instanceof SEOPluginError) {
    message = error.userMessage;
    severity = error.severity;
    suggestion = error.suggestion;
  } else if (error instanceof Error) {
    message = error.message;
    severity = "error" /* ERROR */;
    suggestion = "Please try again or check the plugin settings";
  } else {
    message = "An unexpected error occurred";
    severity = "critical" /* CRITICAL */;
    suggestion = "Please restart Obsidian and try again";
  }
  console.error(`[SEO Plugin] Error in ${context}:`, error);
  if (showNotice) {
    const noticeMessage = suggestion ? `${message} - ${suggestion}` : message;
    new import_obsidian10.Notice(noticeMessage, 5e3);
  }
  return { message, severity, suggestion };
}
async function withErrorHandling(fn, context, fallback) {
  try {
    return await fn();
  } catch (error) {
    handleError(error, context, true);
    return fallback;
  }
}
function validateRequiredParams(params, context) {
  const missing = Object.entries(params).filter(([_, value]) => value === null || value === void 0).map(([name]) => name);
  if (missing.length > 0) {
    throw new SEOPluginError(
      `Missing required parameters: ${missing.join(", ")}`,
      "error" /* ERROR */,
      `Missing required parameters: ${missing.join(", ")}`,
      "Please check your plugin configuration"
    );
  }
}

// src/utils/cache-manager.ts
var CacheManager = class {
  constructor(config = {}) {
    this.cache = /* @__PURE__ */ new Map();
    this.cleanupTimer = null;
    this.config = {
      maxSize: config.maxSize || 100,
      maxAge: config.maxAge || 30 * 60 * 1e3,
      // 30 minutes
      cleanupInterval: config.cleanupInterval || 5 * 60 * 1e3
      // 5 minutes
    };
    this.startCleanupTimer();
  }
  /**
   * Get a value from cache
   * @param key - Cache key
   * @returns Cached value or undefined
   */
  get(key) {
    const entry = this.cache.get(key);
    if (!entry) {
      return void 0;
    }
    if (this.isExpired(entry)) {
      this.cache.delete(key);
      return void 0;
    }
    entry.accessCount++;
    entry.lastAccessed = Date.now();
    return entry.value;
  }
  /**
   * Set a value in cache
   * @param key - Cache key
   * @param value - Value to cache
   */
  set(key, value) {
    if (this.cache.has(key)) {
      this.cache.delete(key);
    }
    if (this.cache.size >= this.config.maxSize) {
      this.evictLRU();
    }
    this.cache.set(key, {
      value,
      timestamp: Date.now(),
      accessCount: 1,
      lastAccessed: Date.now()
    });
  }
  /**
   * Check if a key exists in cache
   * @param key - Cache key
   * @returns True if key exists and is not expired
   */
  has(key) {
    const entry = this.cache.get(key);
    if (!entry) {
      return false;
    }
    if (this.isExpired(entry)) {
      this.cache.delete(key);
      return false;
    }
    return true;
  }
  /**
   * Delete a specific key from cache
   * @param key - Cache key
   */
  delete(key) {
    return this.cache.delete(key);
  }
  /**
   * Clear all cache entries
   */
  clear() {
    this.cache.clear();
  }
  /**
   * Get cache statistics
   */
  getStats() {
    const entries = Array.from(this.cache.values());
    return {
      size: this.cache.size,
      maxSize: this.config.maxSize,
      hitRate: this.calculateHitRate(),
      oldestEntry: Math.min(...entries.map((e) => e.timestamp)),
      newestEntry: Math.max(...entries.map((e) => e.timestamp)),
      expiredEntries: entries.filter((e) => this.isExpired(e)).length
    };
  }
  /**
   * Clean up expired entries
   */
  cleanup() {
    const expiredKeys = [];
    for (const [key, entry] of this.cache.entries()) {
      if (this.isExpired(entry)) {
        expiredKeys.push(key);
      }
    }
    expiredKeys.forEach((key) => this.cache.delete(key));
  }
  /**
   * Destroy the cache manager and cleanup resources
   */
  destroy() {
    if (this.cleanupTimer) {
      clearInterval(this.cleanupTimer);
      this.cleanupTimer = null;
    }
    this.clear();
  }
  /**
   * Check if an entry has expired
   */
  isExpired(entry) {
    return Date.now() - entry.timestamp > this.config.maxAge;
  }
  /**
   * Evict least recently used entry
   */
  evictLRU() {
    let oldestKey = "";
    let oldestTime = Date.now();
    for (const [key, entry] of this.cache.entries()) {
      if (entry.lastAccessed < oldestTime) {
        oldestTime = entry.lastAccessed;
        oldestKey = key;
      }
    }
    if (oldestKey) {
      this.cache.delete(oldestKey);
    }
  }
  /**
   * Calculate cache hit rate
   */
  calculateHitRate() {
    const entries = Array.from(this.cache.values());
    const totalAccesses = entries.reduce((sum, entry) => sum + entry.accessCount, 0);
    return totalAccesses > 0 ? entries.length / totalAccesses : 0;
  }
  /**
   * Start the cleanup timer
   */
  startCleanupTimer() {
    this.cleanupTimer = setInterval(() => {
      this.cleanup();
    }, this.config.cleanupInterval);
  }
};
var SEOCacheManager = class {
  // For file content caching
  constructor() {
    this.resultsCache = new CacheManager({
      maxSize: 50,
      // Store up to 50 scan results
      maxAge: 60 * 60 * 1e3,
      // 1 hour
      cleanupInterval: 10 * 60 * 1e3
      // 10 minutes
    });
    this.fileCache = new CacheManager({
      maxSize: 200,
      // Store up to 200 file contents
      maxAge: 30 * 60 * 1e3,
      // 30 minutes
      cleanupInterval: 5 * 60 * 1e3
      // 5 minutes
    });
  }
  /**
   * Get cached scan results
   * @param key - Cache key (usually scan type + timestamp)
   * @returns Cached results or undefined
   */
  getResults(key) {
    return this.resultsCache.get(key);
  }
  /**
   * Cache scan results
   * @param key - Cache key
   * @param results - Results to cache
   */
  setResults(key, results) {
    this.resultsCache.set(key, results);
  }
  /**
   * Get cached file content
   * @param filePath - File path
   * @returns Cached content or undefined
   */
  getFileContent(filePath) {
    return this.fileCache.get(filePath);
  }
  /**
   * Cache file content
   * @param filePath - File path
   * @param content - Content to cache
   */
  setFileContent(filePath, content) {
    this.fileCache.set(filePath, content);
  }
  /**
   * Clear all caches
   */
  clearAll() {
    this.resultsCache.clear();
    this.fileCache.clear();
  }
  /**
   * Get cache statistics
   */
  getStats() {
    return {
      results: this.resultsCache.getStats(),
      files: this.fileCache.getStats()
    };
  }
  /**
   * Clean up expired entries
   */
  cleanup() {
    this.resultsCache.cleanup();
    this.fileCache.cleanup();
  }
  /**
   * Destroy cache managers
   */
  destroy() {
    this.resultsCache.destroy();
    this.fileCache.destroy();
  }
};
var seoCache = new SEOCacheManager();
function clearAllCache2() {
  seoCache.clearAll();
}

// src/main.ts
var SEOPlugin = class extends import_obsidian11.Plugin {
  constructor() {
    super(...arguments);
    this.panelManager = null;
    this.sidePanel = null;
    this.ribbonIcon = null;
  }
  /**
   * Plugin initialization - called when the plugin is loaded
   * Sets up managers, registers views, and commands
   */
  async onload() {
    try {
      await this.loadSettings();
      validateRequiredParams(
        { app: this.app, settings: this.settings },
        "plugin initialization"
      );
      this.panelManager = await withErrorHandling(
        () => Promise.resolve(new PanelManager(this.app, this)),
        "panel manager initialization",
        null
      );
      this.forceIconRefresh();
      await withErrorHandling(
        () => {
          this.registerView(SEOCurrentPanelViewType, (leaf) => {
            const panel = new SEOSidePanel(this, "current", leaf);
            this.sidePanel = panel;
            return panel;
          });
          this.registerView(SEOGlobalPanelViewType, (leaf) => {
            const panel = new SEOSidePanel(this, "global", leaf);
            this.sidePanel = panel;
            return panel;
          });
        },
        "side panel registration",
        void 0
      );
      await withErrorHandling(
        () => {
          registerCommands(this);
        },
        "command registration",
        void 0
      );
      await withErrorHandling(
        () => {
          this.addSettingTab(new SEOSettingTab(this.app, this));
        },
        "settings tab registration",
        void 0
      );
      if (this.settings.showRibbonIcon) {
        await withErrorHandling(
          () => {
            this.ribbonIcon = this.addRibbonIcon("search-check", "Open SEO audit panel", () => {
              try {
                const existingPanels = this.app.workspace.getLeavesOfType("seo-global-panel");
                const isFirstRun = existingPanels.length === 0;
                this.openGlobalPanel();
                if (!isFirstRun) {
                  void setTimeout(() => {
                    void (async () => {
                      try {
                        const globalPanels = this.app.workspace.getLeavesOfType("seo-global-panel");
                        if (globalPanels.length > 0) {
                          const panel = globalPanels[0];
                          if (panel == null ? void 0 : panel.view) {
                            const seoPanel = panel.view;
                            await seoPanel.actions.refreshGlobalResults();
                            seoPanel.render();
                          }
                        }
                      } catch (error) {
                        handleError(error, "ribbon icon panel refresh", true);
                      }
                    })();
                  }, 500);
                }
              } catch (error) {
                handleError(error, "ribbon icon click handler", true);
              }
            });
          },
          "ribbon icon registration",
          void 0
        );
      }
    } catch (error) {
      handleError(error, "plugin initialization", true);
    }
  }
  onunload() {
    try {
      seoCache.destroy();
    } catch (error) {
      handleError(error, "plugin unload", false);
    }
  }
  /**
   * Load plugin settings from Obsidian's data store
   * Merges saved settings with default values
   */
  async loadSettings() {
    try {
      const loadedData = await this.loadData();
      this.settings = Object.assign({}, DEFAULT_SETTINGS, loadedData);
    } catch (error) {
      handleError(error, "loading settings", true);
      this.settings = { ...DEFAULT_SETTINGS };
    }
  }
  /**
   * Save plugin settings to Obsidian's data store
   * Clears cache to ensure fresh results after settings changes
   */
  async saveSettings() {
    try {
      await this.saveData(this.settings);
      clearAllCache2();
    } catch (error) {
      handleError(error, "saving settings", true);
    }
  }
  addStatusBar() {
  }
  removeStatusBar() {
  }
  /**
   * Opens the current note SEO audit panel
   * Shows SEO analysis for the currently active note
   */
  openCurrentPanel() {
    try {
      if (this.panelManager) {
        this.panelManager.openCurrentPanel();
      } else {
        throw new Error("Panel manager not initialized");
      }
    } catch (error) {
      handleError(error, "opening current panel", true);
    }
  }
  /**
   * Opens the global vault SEO audit panel
   * Shows SEO analysis for all notes in the vault
   */
  openGlobalPanel() {
    try {
      if (this.panelManager) {
        this.panelManager.openGlobalPanel();
      } else {
        throw new Error("Panel manager not initialized");
      }
    } catch (error) {
      handleError(error, "opening global panel", true);
    }
  }
  /**
   * Runs bulk SEO check across all configured files
   * @returns Promise that resolves when bulk check is complete
   */
  async runBulkCheck() {
    try {
      const { runSEOCheck: runSEOCheck2 } = await Promise.resolve().then(() => (init_seo_checker(), seo_checker_exports));
      const files = await this.getFilesToCheck();
      if (files.length === 0) {
        return [];
      }
      const results = await runSEOCheck2(this, files);
      return results;
    } catch (error) {
      handleError(error, "running bulk check", true);
      return [];
    }
  }
  /**
   * Gets the list of files to check based on current settings
   * @returns Promise resolving to array of TFile objects to check
   */
  async getFilesToCheck() {
    try {
      if (this.panelManager) {
        return await this.panelManager.getFilesToCheck();
      } else {
        throw new Error("Panel manager not initialized");
      }
    } catch (error) {
      handleError(error, "getting files to check", true);
      return [];
    }
  }
  // Proper icon refresh using Obsidian's onLayoutReady API
  forceIconRefresh() {
    this.app.workspace.onLayoutReady(() => {
      const existingCurrentPanels = this.app.workspace.getLeavesOfType(SEOCurrentPanelViewType);
      const existingGlobalPanels = this.app.workspace.getLeavesOfType(SEOGlobalPanelViewType);
      [...existingCurrentPanels, ...existingGlobalPanels].forEach((leaf) => {
        if (leaf.view instanceof SEOSidePanel) {
          leaf.view.render();
        }
      });
    });
  }
  /**
   * Toggles the ribbon icon visibility
   * Adds or removes the ribbon icon based on the setting
   */
  toggleRibbonIcon() {
    try {
      if (this.settings.showRibbonIcon) {
        if (!this.ribbonIcon) {
          this.ribbonIcon = this.addRibbonIcon("search-check", "Open SEO audit panel", () => {
            try {
              const existingPanels = this.app.workspace.getLeavesOfType("seo-global-panel");
              const isFirstRun = existingPanels.length === 0;
              this.openGlobalPanel();
              if (!isFirstRun) {
                void setTimeout(() => {
                  void (async () => {
                    try {
                      const globalPanels = this.app.workspace.getLeavesOfType("seo-global-panel");
                      if (globalPanels.length > 0) {
                        const panel = globalPanels[0];
                        if (panel == null ? void 0 : panel.view) {
                          const seoPanel = panel.view;
                          await seoPanel.actions.refreshGlobalResults();
                          seoPanel.render();
                        }
                      }
                    } catch (error) {
                      handleError(error, "ribbon icon panel refresh", true);
                    }
                  })();
                }, 500);
              }
            } catch (error) {
              handleError(error, "ribbon icon click handler", true);
            }
          });
        }
      } else {
        if (this.ribbonIcon) {
          const iconToRemove = this.ribbonIcon;
          this.ribbonIcon = null;
          iconToRemove.remove();
          this.app.workspace.trigger("layout-change");
          setTimeout(() => {
            const contextMenus = document.querySelectorAll(".menu");
            contextMenus.forEach((menu) => {
              const items = menu.querySelectorAll(".menu-item");
              items.forEach((item) => {
                var _a;
                const text = (_a = item.textContent) == null ? void 0 : _a.trim();
                if (text === "Open SEO audit panel") {
                  item.remove();
                }
              });
            });
          }, 100);
        }
      }
    } catch (error) {
      handleError(error, "toggling ribbon icon", true);
    }
  }
};
//# sourceMappingURL=data:application/json;base64,[encoded data, 597940 characters]
