import type { DevToolbarRule } from "./types.ts";
import { accessibleName, createIssue } from "./helpers.ts";

export const accessibilityRules: DevToolbarRule[] = [
  {
    id: "a11y/image-alt",
    category: "accessibility",
    defaultSeverity: "error",
    description: "Images need alternative text.",
    run: ({ root }) =>
      [...root.querySelectorAll("img:not([alt])")].map((element) =>
        createIssue({
          ruleId: "a11y/image-alt",
          category: "accessibility",
          severity: "error",
          title: "Image is missing alt text",
          message: 'Add alt text for meaningful images, or alt="" for decorative images.',
          element,
          recommendation: "Describe the image's purpose in a concise alt attribute.",
        }),
      ),
  },
  {
    id: "a11y/control-name",
    category: "accessibility",
    defaultSeverity: "error",
    description: "Interactive controls need accessible names.",
    run: ({ root }) =>
      [...root.querySelectorAll("button, a[href], [role='button']")]
        .filter((element) => !accessibleName(element))
        .map((element) =>
          createIssue({
            ruleId: "a11y/control-name",
            category: "accessibility",
            severity: "error",
            title: "Control has no accessible name",
            message: "Screen readers cannot identify this control.",
            element,
            recommendation: "Add visible text, aria-label, or aria-labelledby.",
          }),
        ),
  },
  {
    id: "a11y/form-label",
    category: "accessibility",
    defaultSeverity: "error",
    description: "Form controls need labels.",
    run: ({ root }) =>
      [...root.querySelectorAll("input:not([type='hidden']), select, textarea")]
        .filter((element) => {
          const id = element.id;
          return (
            !element.getAttribute("aria-label") &&
            !element.getAttribute("aria-labelledby") &&
            !(id && root.querySelector(`label[for='${CSS.escape(id)}']`)) &&
            !element.closest("label")
          );
        })
        .map((element) =>
          createIssue({
            ruleId: "a11y/form-label",
            category: "accessibility",
            severity: "error",
            title: "Form control has no label",
            message: "Users of assistive technology may not know what this field is for.",
            element,
            recommendation: "Associate a label using for/id or aria-labelledby.",
          }),
        ),
  },
  {
    id: "a11y/duplicate-id",
    category: "accessibility",
    defaultSeverity: "error",
    description: "IDs must be unique.",
    run: ({ root }) => {
      const seen = new Set<string>();
      const duplicates: Element[] = [];
      for (const element of root.querySelectorAll("[id]")) {
        if (seen.has(element.id)) duplicates.push(element);
        else seen.add(element.id);
      }
      return duplicates.map((element) =>
        createIssue({
          ruleId: "a11y/duplicate-id",
          category: "accessibility",
          severity: "error",
          title: "Duplicate element ID",
          message: `The ID “${element.id}” is used more than once.`,
          element,
          recommendation: "Use a unique ID for every element.",
        }),
      );
    },
  },
  {
    id: "a11y/heading-order",
    category: "accessibility",
    defaultSeverity: "warning",
    description: "Heading levels should not skip.",
    run: ({ root }) => {
      const issues = [];
      let previous = 0;
      for (const element of root.querySelectorAll("h1,h2,h3,h4,h5,h6")) {
        const level = Number(element.tagName.slice(1));
        if (previous && level > previous + 1)
          issues.push(
            createIssue({
              ruleId: "a11y/heading-order",
              category: "accessibility",
              severity: "warning",
              title: "Heading level is skipped",
              message: `Heading jumps from h${previous} to h${level}.`,
              element,
              recommendation: "Use headings in a logical hierarchy.",
            }),
          );
        previous = level;
      }
      return issues;
    },
  },
  {
    id: "a11y/positive-tabindex",
    category: "accessibility",
    defaultSeverity: "warning",
    description: "Positive tabindex disrupts natural keyboard order.",
    run: ({ root }) =>
      [...root.querySelectorAll("[tabindex]")]
        .filter((el) => Number(el.getAttribute("tabindex")) > 0)
        .map((element) =>
          createIssue({
            ruleId: "a11y/positive-tabindex",
            category: "accessibility",
            severity: "warning",
            title: "Positive tabindex used",
            message: "Positive tabindex values create an unexpected keyboard focus order.",
            element,
            recommendation: 'Use tabindex="0" or rely on native document order.',
          }),
        ),
  },
];
