import test from "node:test";
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";

const cssUrls = {
  base: new URL("./styles/base.css", import.meta.url),
  feedback: new URL("./styles/feedback.css", import.meta.url),
  accounts: new URL("../features/accounts/accounts.css", import.meta.url),
  changes: new URL("../features/changes/changes.css", import.meta.url),
  changeSheet: new URL("../features/changes/change-sheet.css", import.meta.url),
  chats: new URL("../features/chats/chats.css", import.meta.url),
  projectPicker: new URL("../features/chats/project-picker.css", import.meta.url),
  context: new URL("../features/context/context.css", import.meta.url),
  conversation: new URL("../features/conversation/conversation.css", import.meta.url),
  notifications: new URL("../features/notifications/notifications.css", import.meta.url),
  status: new URL("../features/status/status.css", import.meta.url),
  connection: new URL("../features/connection/connection.css", import.meta.url)
};

async function loadCss() {
  return Object.fromEntries(await Promise.all(Object.entries(cssUrls).map(async ([name, url]) => [name, await readFile(url, "utf8")])));
}

function rulesFor(css, selectorFragment) {
  return [...css.matchAll(/([^{}]+)\{([^{}]*)\}/g)]
    .map(([, selector, declarations]) => ({ selector: selector.trim(), declarations }))
    .filter((rule) => rule.selector.includes(selectorFragment));
}

function assertRule(css, selectorFragment, predicate, message) {
  assert.ok(rulesFor(css, selectorFragment).some(predicate), message || `Missing themed rule for ${selectorFragment}`);
}

test("feedback colors come from a semantic light and dark palette", async () => {
  const { feedback } = await loadCss();

  assert.match(feedback, /:root\{[^}]*--feedback-danger-text:#ffb5ba[^}]*--feedback-danger-background:#25191b[^}]*--feedback-warning-background:#241f18[^}]*--feedback-muted-background:#181b20/);
  assert.match(feedback, /html\[data-theme="light"\]\{[^}]*--feedback-danger-text:#b4232d[^}]*--feedback-danger-background:#fff1f2[^}]*--feedback-warning-background:#fff8c5[^}]*--feedback-muted-background:#f6f8fa/);
});

test("every user-visible failure copy keeps its semantic error color in light mode", async () => {
  const css = await loadCss();
  const cases = [
    ".account-error",
    ".account-login-rate-limit",
    ".thread-state.error",
    ".project-chat-error",
    ".chat-transition-error",
    ".danger-menuitem",
    ".settings-error",
    ".change-sheet-state.error",
    ".usage-trend-empty.error",
    ".notification-center-error",
    ".status-connection-error",
    ".connection-error",
    ".activity-error",
    ".send-error",
    ".queued-action-error",
    ".activity-result-error",
    ".workspace-attach-sheet .error"
  ];

  for (const selector of cases) {
    assertRule(css.feedback, selector, (rule) => rule.declarations.includes("color:var(--feedback-danger-text)"), `${selector} does not use the shared danger color`);
    assertRule(css.feedback, selector, (rule) => rule.selector.includes('html[data-theme="light"]') && rule.declarations.includes("color:var(--feedback-danger-text)!important"), `${selector} can be overridden by the light theme wildcard`);
  }
});

test("feedback surfaces and fallback content cannot retain dark-only backgrounds", async () => {
  const css = await loadCss();

  assertRule(css.feedback, ".conversation-open-error", (rule) =>
    rule.declarations.includes("border:1px solid var(--feedback-danger-border)")
      && rule.declarations.includes("background:var(--feedback-danger-background)")
      && rule.declarations.includes("color:var(--feedback-danger-text)"));
  assertRule(css.feedback, ".status-connection-error", (rule) =>
    rule.declarations.includes("border-color:var(--feedback-danger-border)")
      && rule.declarations.includes("background:var(--feedback-danger-background)")
      && rule.declarations.includes("color:var(--feedback-danger-text)"));
  for (const selector of [".conversation-open-error", ".status-connection-error"]) {
    assertRule(css.feedback, selector, (rule) =>
      rule.selector.includes('html[data-theme="light"]')
        && rule.declarations.includes("background:var(--feedback-danger-background)!important")
        && rule.declarations.includes("color:var(--feedback-danger-text)!important"));
  }

  assertRule(css.feedback, ".approval-card", (rule) =>
    rule.declarations.includes("background:var(--feedback-warning-background)")
      && rule.declarations.includes("color:var(--feedback-warning-text)"));

  assertRule(css.feedback, ".markdown-unavailable-image", (rule) =>
    rule.declarations.includes("background:var(--feedback-muted-background)")
      && rule.declarations.includes("color:var(--feedback-muted-text)"));
  assertRule(css.feedback, ".markdown-unavailable-image", (rule) =>
    rule.selector.includes('html[data-theme="light"]')
      && rule.declarations.includes("background:var(--feedback-muted-background)!important"));
  assertRule(css.feedback, ".markdown-unavailable-link", (rule) =>
    rule.selector.includes('html[data-theme="light"]')
      && rule.declarations.includes("color:var(--feedback-muted-text)!important"));
  assertRule(css.feedback, ".usage-credit-expiry.warning", (rule) =>
    rule.selector.includes('html[data-theme="light"]')
      && rule.declarations.includes("color:var(--feedback-warning-text)!important"));

  for (const selector of [".chat-changes", ".file-row"]) {
    assertRule(css.feedback, selector, (rule) => rule.selector.includes('html[data-theme="light"]') && rule.declarations.includes("background:#fff!important"));
  }
  assertRule(css.feedback, ".project-create-popover", (rule) => rule.selector.includes('html[data-theme="light"]') && rule.declarations.includes("background:#fff!important"));
  assertRule(css.feedback, ".settings-select-wrap", (rule) => rule.selector.includes('html[data-theme="light"]') && rule.declarations.includes("background:#eaeef2!important"));
});

test("named feedback states cannot be added without a light-theme rule", async () => {
  const css = await loadCss();
  const featureCss = Object.entries(css).filter(([name]) => name !== "feedback").map(([, source]) => source).join("\n");
  const allCss = Object.values(css).join("\n");
  const namedStates = [...new Set([...featureCss.matchAll(/\.([A-Za-z0-9_-]*(?:error|warning|danger|failed|unavailable)[A-Za-z0-9_-]*)/g)].map((match) => match[1]))].sort();
  const lightSelectors = [...allCss.matchAll(/([^{}]*html\[data-theme="light"\][^{}]*)\{[^{}]*\}/g)].map((match) => match[1]).join("\n");

  for (const className of namedStates) {
    assert.ok(lightSelectors.includes(`.${className}`), `.${className} has no explicit light-theme coverage`);
  }
});

test("feedback stylesheet loads after every feature stylesheet", async () => {
  const app = await readFile(new URL("../App.jsx", import.meta.url), "utf8");
  const feedbackIndex = app.indexOf('import "./shared/styles/feedback.css"');
  const lastFeatureIndex = app.lastIndexOf('import "./features/');

  assert.ok(feedbackIndex > lastFeatureIndex);
});

test("intentionally dark image viewers retain readable light controls", async () => {
  const { feedback } = await loadCss();

  for (const selector of [".image-gallery-close", ".image-gallery-nav", ".activity-image-lightbox-close"]) {
    assertRule(feedback, selector, (rule) =>
      rule.selector.includes('html[data-theme="light"]')
        && rule.declarations.includes("background:var(--overlay-control-background)!important")
        && rule.declarations.includes("color:var(--overlay-on-dark)!important"));
  }
  for (const selector of [".image-gallery-count", ".image-gallery-error"]) {
    assertRule(feedback, selector, (rule) =>
      rule.selector.includes('html[data-theme="light"]')
        && rule.declarations.includes("color:var(--overlay-on-dark)!important"));
  }
});
