import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { definePlugin } from "@wrnexus/plugin";
import { CAPTCHA_IMAGE_STYLES } from "./challenges/styles.ts";

export interface CaptchaPluginOptions {
  componentDir?: string;
  exposeComponentDirectory?: boolean;
  enableDevToolbar?: boolean;
  auditExternalProviders?: boolean;
}

export interface CaptchaAuditIssue {
  id: string;
  severity: "error" | "warning" | "suggestion";
  title: string;
  message: string;
  file: string;
}

const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
const captchaClientRuntime = join(packageRoot, "assets", "client", "captcha.js");

export function captchaComponentsDir(): string {
  return join(packageRoot, "components");
}

function auditCaptchaSource(code: string, file: string, external: boolean): CaptchaAuditIssue[] {
  if (!code.includes("<Captcha") && !code.includes('data-component="Captcha"')) return [];
  const issues: CaptchaAuditIssue[] = [];
  const push = (
    id: string,
    severity: CaptchaAuditIssue["severity"],
    title: string,
    message: string,
  ) => issues.push({ id: `${id}:${file}`, severity, title, message, file });

  if (/secret(Key)?\s*=|providerSecret\s*=|captchaSecret\s*=/i.test(code)) {
    push(
      "client-secret",
      "error",
      "CAPTCHA secret exposed",
      "Never pass a provider secret or verification secret to a .wrn component.",
    );
  }
  if (!/action\s*=/.test(code)) {
    push(
      "missing-action",
      "warning",
      "CAPTCHA action is missing",
      "Bind each challenge to a stable action such as signup, login, or contact-submit.",
    );
  }
  if (/required\s*=\s*["']?false/i.test(code)) {
    push(
      "optional-captcha",
      "warning",
      "CAPTCHA is optional",
      "Protected forms should require a CAPTCHA response and verify it on the server.",
    );
  }
  if (!/showAudio\s*=|presentation\s*=\s*["']audio/i.test(code)) {
    push(
      "audio-alternative",
      "suggestion",
      "Confirm an accessible alternative",
      "Visual challenges should offer an audio or non-visual alternative.",
    );
  }

  const imageStyle = code
    .match(/imageStyle\s*=\s*["']([^"']+)["']/i)?.[1]
    ?.trim()
    .toLowerCase();
  if (
    imageStyle &&
    !CAPTCHA_IMAGE_STYLES.includes(imageStyle as (typeof CAPTCHA_IMAGE_STYLES)[number])
  ) {
    push(
      "unknown-image-style",
      "error",
      "Unknown CAPTCHA image style",
      `Use one of: ${CAPTCHA_IMAGE_STYLES.join(", ")}.`,
    );
  }

  const disturbance = Number(code.match(/disturbance\s*=\s*["']?(\d+)/i)?.[1] ?? "");
  if (
    Number.isFinite(disturbance) &&
    disturbance >= 65 &&
    /showAudio\s*=\s*["']?false/i.test(code)
  ) {
    push(
      "hard-without-audio",
      "warning",
      "Hard CAPTCHA has no audio alternative",
      "High disturbance should include audio or another non-visual challenge path.",
    );
  }
  if (
    external &&
    /provider\s*=\s*["'](?:turnstile|recaptcha|hcaptcha)/i.test(code) &&
    !/siteKey\s*=/.test(code)
  ) {
    push(
      "missing-site-key",
      "error",
      "Provider site key is missing",
      "External CAPTCHA providers require a public site key in the browser.",
    );
  }
  push(
    "server-verification",
    "suggestion",
    "Server verification required",
    "Confirm the receiving API uses captchaGuard(), parseWithCaptcha(), or provider.verify().",
  );
  return issues;
}

export function captchaPlugin(options: CaptchaPluginOptions = {}) {
  const metadataKey = "@wrnexus/captcha:audit";
  return definePlugin({
    name: "@wrnexus/captcha",
    version: "0.4.0",
    enforce: "post",
    componentDirs:
      options.exposeComponentDirectory === false
        ? []
        : [options.componentDir ?? captchaComponentsDir()],
    clientRuntimes: [
      {
        id: "captcha",
        entry: captchaClientRuntime,
        type: "script",
        load: "defer",
        singleton: true,
        bundle: false,
      },
    ],
    styleSources: [
      {
        id: "captcha-components",
        source: options.componentDir ?? captchaComponentsDir(),
        order: "normal",
      },
    ],
    configure(config, context) {
      const current = (config.captcha ?? {}) as Record<string, unknown>;
      config.captcha = {
        componentDir: options.componentDir ?? captchaComponentsDir(),
        ...current,
      };
      context.metadata.set(
        "@wrnexus/captcha:component-dir",
        options.componentDir ?? captchaComponentsDir(),
      );
    },
    transformCode(code, context) {
      if (context.mode !== "development") return;
      const previous = (context.metadata.get(metadataKey) as CaptchaAuditIssue[] | undefined) ?? [];
      const withoutFile = previous.filter((issue) => issue.file !== context.file);
      context.metadata.set(metadataKey, [
        ...withoutFile,
        ...auditCaptchaSource(code, context.file, options.auditExternalProviders ?? true),
      ]);
    },
    devToolbarPanels(context) {
      if (options.enableDevToolbar === false) return [];
      const issues = (context.metadata.get(metadataKey) as CaptchaAuditIssue[] | undefined) ?? [];
      return [
        {
          id: "wrnexus-captcha",
          title: "CAPTCHA",
          icon: "shield-check",
          badge: issues.length,
          description: "CAPTCHA security, accessibility, and integration checks",
          issues,
        },
      ];
    },
  });
}

export default captchaPlugin;
