import type * as React from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import {
  CheckCircle2,
  ChevronDown,
  CircleAlert,
  ExternalLink,
  FileSpreadsheet,
  GalleryHorizontalEnd,
  Gauge,
  HelpCircle,
  LayoutDashboard,
  MessageSquareText,
  Pause,
  Play,
  Plus,
  RotateCcw,
  Search,
  Settings,
  Upload,
  X,
  XCircle,
} from "lucide-react";
import appIcon from "./assets/app-icon.png";
import wordpressLogo from "./assets/wordpress.svg";
import wordpressExportButton from "./assets/wordpress-export-button.png";
import wordpressExportSettings from "./assets/wordpress-export-settings.png";
import wordpressProductsNavigation from "./assets/wordpress-products-navigation.png";
import {
  config,
  generateImage,
  getGenerationJob,
  getErrorMessage,
  getStatus,
  type GeneratedResult,
  type GenerationJob,
  type PluginStatus,
} from "./platform";

type ScreenId = "dashboard" | "import" | "queue" | "results" | "settings" | "help";
type RowStatus = "pending" | "generating" | "completed" | "failed" | "stopped";
type ImageSize = "1024x1024" | "auto" | "1536x1024" | "1024x1536";
type HelpTutorial = "provider" | "wordpress";
type HelpScenario = "new" | "existing";

interface HelpRequest {
  tutorial: HelpTutorial;
  scenario?: HelpScenario;
  requestId: number;
}

interface ImageGenerationSettings {
  size: ImageSize;
  concurrency: number;
}

interface BrandLogo {
  id: number;
  url: string;
  alt: string;
  filename: string;
}

interface CsvRow {
  recordNumber: number;
  values: string[];
}

interface ValidRow {
  recordNumber: number;
  sku: string;
  productName: string;
  prompt: string;
  referenceImage: string;
  status: RowStatus;
  error?: string;
  result?: GeneratedResult;
}

interface Mappings {
  sku: string;
  productName: string;
  prompt: string;
  referenceImage: string;
}

const CHATGPT_URL = "https://chatgpt.com/";
const WORDPRESS_AI_CLIENT_URL =
  "https://make.wordpress.org/core/2026/03/24/introducing-the-ai-client-in-wordpress-7-0/";
const IMAGE_SETTINGS_STORAGE_KEY = "catalogframe-image-generation-settings";

const defaultImageSettings: ImageGenerationSettings = {
  size: "1024x1024",
  concurrency: 1,
};

const navItems = [
  { id: "dashboard" as const, label: "Jobs", icon: LayoutDashboard },
  { id: "import" as const, label: "Import", icon: Upload },
  { id: "queue" as const, label: "Queue", icon: Gauge },
  { id: "results" as const, label: "Results", icon: GalleryHorizontalEnd },
  { id: "settings" as const, label: "Settings", icon: Settings },
  { id: "help" as const, label: "Help", icon: HelpCircle },
];

const screenTitles: Record<ScreenId, string> = {
  dashboard: "Jobs dashboard",
  import: "Import CSV",
  queue: "Generation queue",
  results: "Results gallery",
  settings: "Settings",
  help: "Help center",
};

function Pill({ tone = "", children }: { tone?: string; children: React.ReactNode }) {
  return <span className={`pill ${tone}`}>{children}</span>;
}

function openExternalUrl(url: string) {
  window.open(url, "_blank", "noopener,noreferrer");
}

function isEscapeKey(key: string) {
  return key === "Escape" || key === "Esc";
}

function sanitizeImageSettings(value: unknown): ImageGenerationSettings {
  if (!value || typeof value !== "object") return defaultImageSettings;
  const candidate = value as Partial<ImageGenerationSettings>;
  const sizeOptions: ImageSize[] = ["1024x1024", "auto", "1536x1024", "1024x1536"];
  const concurrency =
    typeof candidate.concurrency === "number" && Number.isFinite(candidate.concurrency)
      ? Math.min(4, Math.max(1, Math.round(candidate.concurrency)))
      : defaultImageSettings.concurrency;

  return {
    size: sizeOptions.includes(candidate.size as ImageSize)
      ? (candidate.size as ImageSize)
      : defaultImageSettings.size,
    concurrency,
  };
}

function loadImageSettings() {
  try {
    const saved = window.localStorage.getItem(IMAGE_SETTINGS_STORAGE_KEY);
    return saved ? sanitizeImageSettings(JSON.parse(saved)) : defaultImageSettings;
  } catch {
    return defaultImageSettings;
  }
}

function parseCsv(text: string) {
  const clean = text.replace(/^\uFEFF/, "");
  const records: string[][] = [];
  let row: string[] = [];
  let value = "";
  let inQuotes = false;

  for (let index = 0; index < clean.length; index += 1) {
    const char = clean[index];
    const next = clean[index + 1];

    if (char === '"') {
      if (inQuotes && next === '"') {
        value += '"';
        index += 1;
      } else {
        inQuotes = !inQuotes;
      }
      continue;
    }

    if (char === "," && !inQuotes) {
      row.push(value);
      value = "";
      continue;
    }

    if ((char === "\n" || char === "\r") && !inQuotes) {
      if (char === "\r" && next === "\n") index += 1;
      row.push(value);
      if (row.some((cell) => cell.trim() !== "")) records.push(row);
      row = [];
      value = "";
      continue;
    }

    value += char;
  }

  row.push(value);
  if (row.some((cell) => cell.trim() !== "")) records.push(row);
  return records;
}

function suggestHeader(headers: string[], candidates: string[]) {
  const normalized = headers.map((header) => ({
    header,
    key: header.toLowerCase().replace(/[^a-z0-9]+/g, ""),
  }));
  const wanted = candidates.map((candidate) => candidate.toLowerCase().replace(/[^a-z0-9]+/g, ""));
  return normalized.find((item) => wanted.includes(item.key))?.header ?? "";
}

function rowValue(headers: string[], row: CsvRow, header: string) {
  const index = headers.indexOf(header);
  return index >= 0 ? (row.values[index] ?? "").trim() : "";
}

function App() {
  const [screen, setScreen] = useState<ScreenId>("dashboard");
  const [status, setStatus] = useState<PluginStatus>({
    aiClientAvailable: false,
    imageGenerationAvailable: false,
    canConfigureProviders: false,
    wooAvailable: config.wooAvailable,
  });
  const [headers, setHeaders] = useState<string[]>([]);
  const [rows, setRows] = useState<CsvRow[]>([]);
  const [mappings, setMappings] = useState<Mappings>({
    sku: "",
    productName: "",
    prompt: "",
    referenceImage: "",
  });
  const [validRows, setValidRows] = useState<ValidRow[]>([]);
  const [validationErrors, setValidationErrors] = useState<string[]>([]);
  const [notice, setNotice] = useState<{ tone: "ok" | "error"; message: string } | null>(null);
  const [isRunning, setIsRunning] = useState(false);
  const [stopRequested, setStopRequested] = useState(false);
  const stopRequestedRef = useRef(false);
  const [autoMapProducts, setAutoMapProducts] = useState(false);
  const [brandLogo, setBrandLogo] = useState<BrandLogo | null>(null);
  const [imageSettings, setImageSettings] = useState<ImageGenerationSettings>(() =>
    loadImageSettings(),
  );
  const [helpRequest, setHelpRequest] = useState<HelpRequest | null>(null);

  useEffect(() => {
    void refreshStatus();
  }, []);

  useEffect(() => {
    window.localStorage.setItem(IMAGE_SETTINGS_STORAGE_KEY, JSON.stringify(imageSettings));
  }, [imageSettings]);

  useEffect(() => {
    function warnBeforeClose(event: BeforeUnloadEvent) {
      if (!isRunning) return;
      setStopRequested(true);
      stopRequestedRef.current = true;
      event.preventDefault();
      event.returnValue =
        config.i18n?.closeWarning ??
        "CatalogFrame is generating images. Closing this tab will stop the job.";
    }

    window.addEventListener("beforeunload", warnBeforeClose);
    return () => window.removeEventListener("beforeunload", warnBeforeClose);
  }, [isRunning]);

  async function refreshStatus() {
    try {
      setStatus(await getStatus());
    } catch (error) {
      setNotice({
        tone: "error",
        message: getErrorMessage(error, "Unable to load plugin status."),
      });
    }
  }

  function openHelpTutorial(tutorial: HelpTutorial, scenario?: HelpScenario) {
    setHelpRequest({ tutorial, scenario, requestId: Date.now() });
    setScreen("help");
  }

  const completedRows = validRows.filter((row) => row.status === "completed");
  const failedRows = validRows.filter((row) => row.status === "failed");
  const progress =
    validRows.length > 0 ? Math.round((completedRows.length / validRows.length) * 100) : 0;

  return (
    <>
      <a className="skip-link" href="#main-content">
        Skip to main content
      </a>
      <div className="app">
        <aside className="rail" aria-label="Application navigation">
          <div className="brand">
            <img className="brand-mark" src={appIcon} alt="" aria-hidden="true" />
            <span>
              <strong>CatalogFrame Image Studio</strong>
              <small>Catalog image queue</small>
            </span>
          </div>
          <nav className="nav" aria-label="Primary navigation">
            <div className="nav-group nav-group-primary" role="group" aria-label="Workflow">
              {navItems.slice(0, 4).map((item) => (
                <NavButton
                  key={item.id}
                  item={item}
                  active={screen === item.id}
                  onClick={setScreen}
                />
              ))}
            </div>
            <div className="nav-separator" aria-hidden="true" />
            <div className="nav-group nav-group-support" role="group" aria-label="Support">
              {navItems.slice(4).map((item) => (
                <NavButton
                  key={item.id}
                  item={item}
                  active={screen === item.id}
                  onClick={setScreen}
                />
              ))}
            </div>
          </nav>
          <div className="rail-footer">
            <div className="rail-status" role="status">
              <span
                className={`status-dot ${status.imageGenerationAvailable ? "ok" : "warning"}`}
              />
              <span>
                {status.imageGenerationAvailable ? "Image AI ready" : "Connect image AI"}
              </span>
            </div>
            <button
              className="coffee-button"
              onClick={() => openExternalUrl(config.donationUrl)}
              type="button"
            >
              <span>Buy me a coffee</span>
            </button>
            <p className="app-credit">
              Built and developed by
              <button onClick={() => openExternalUrl("https://taistudio.co")} type="button">
                TheAI Studio - taistudio.co
              </button>
            </p>
          </div>
        </aside>

        <main className="workspace" id="main-content" tabIndex={-1}>
          <header className="topbar">
            <div>
              <p className="eyebrow">WooCommerce CSV to Media Library</p>
              <h1 id="screen-title">{screenTitles[screen]}</h1>
            </div>
            <div className="topbar-actions" aria-label="Application actions">
              <button
                aria-label="Use AI to generate a product mockup CSV file"
                className="ghost"
                onClick={() => openExternalUrl(config.customGptUrl)}
                title="Use AI to generate a product mockup CSV file"
                type="button"
              >
                <MessageSquareText size={16} />
                AI Mockup CSV
              </button>
              <button className="ghost" onClick={() => setScreen("settings")} type="button">
                <Settings size={16} />
                AI Provider
              </button>
              <button className="primary" onClick={() => setScreen("import")} type="button">
                <FileSpreadsheet size={16} />
                New Job
              </button>
            </div>
          </header>

          {notice && (
            <div className={`notice ${notice.tone === "error" ? "error" : ""}`} role="status">
              {notice.message}
            </div>
          )}

          <section className="screen-view" aria-labelledby="screen-title">
            {screen === "dashboard" && (
              <Dashboard
                completedRows={completedRows.length}
                failedRows={failedRows.length}
                totalRows={validRows.length}
                status={status}
                onScreenChange={setScreen}
              />
            )}
            {screen === "import" && (
              <ImportScreen
                headers={headers}
                mappings={mappings}
                rows={rows}
                validRows={validRows}
                validationErrors={validationErrors}
                imageGenerationAvailable={status.imageGenerationAvailable}
                onHeadersChange={setHeaders}
                onMappingsChange={setMappings}
                onRowsChange={setRows}
                onValidRowsChange={setValidRows}
                onValidationErrorsChange={setValidationErrors}
                onNotice={setNotice}
                autoMapProducts={autoMapProducts}
                brandLogo={brandLogo}
                onAutoMapProductsChange={setAutoMapProducts}
                onBrandLogoChange={setBrandLogo}
                onOpenWordPressWorkflow={() => openHelpTutorial("wordpress", "existing")}
                onStart={() =>
                  void startGeneration(
                    validRows,
                    setValidRows,
                    setScreen,
                    setIsRunning,
                    setStopRequested,
                    stopRequestedRef,
                    autoMapProducts,
                    brandLogo,
                    imageSettings,
                    setNotice,
                  )
                }
              />
            )}
            {screen === "queue" && (
              <QueueScreen
                rows={validRows}
                isRunning={isRunning}
                progress={progress}
                onStop={() => {
                  stopRequestedRef.current = true;
                  setStopRequested(true);
                }}
                onStart={() =>
                  void startGeneration(
                    validRows,
                    setValidRows,
                    setScreen,
                    setIsRunning,
                    setStopRequested,
                    stopRequestedRef,
                    autoMapProducts,
                    brandLogo,
                    imageSettings,
                    setNotice,
                  )
                }
              />
            )}
            {screen === "results" && <ResultsScreen rows={validRows} />}
            {screen === "settings" && (
              <SettingsScreen
                status={status}
                imageSettings={imageSettings}
                onImageSettingsChange={setImageSettings}
                onRefreshStatus={() => void refreshStatus()}
              />
            )}
            {screen === "help" && <HelpScreen helpRequest={helpRequest} />}
          </section>
        </main>
      </div>
    </>
  );
}

function NavButton({
  item,
  active,
  onClick,
}: {
  item: (typeof navItems)[number];
  active: boolean;
  onClick: (screen: ScreenId) => void;
}) {
  const Icon = item.icon;
  return (
    <button
      className={`nav-item ${active ? "active" : ""}`}
      aria-current={active ? "page" : undefined}
      onClick={() => onClick(item.id)}
      type="button"
    >
      <Icon className="icon" aria-hidden="true" />
      <span>{item.label}</span>
    </button>
  );
}

function Dashboard({
  completedRows,
  failedRows,
  totalRows,
  status,
  onScreenChange,
}: {
  completedRows: number;
  failedRows: number;
  totalRows: number;
  status: PluginStatus;
  onScreenChange: (screen: ScreenId) => void;
}) {
  return (
    <div className="dashboard-grid">
      <section className="panel hero-panel">
        <div>
          <p className="eyebrow">Current browser job</p>
          <h2>Generate product images without leaving WordPress.</h2>
          <p>
            Import a GPT-ready CSV, save generated images to the Media Library, and optionally
            append images to matching WooCommerce products by SKU.
          </p>
        </div>
        <div className="hero-actions">
          <button className="primary" onClick={() => onScreenChange("import")} type="button">
            <FileSpreadsheet size={16} />
            New Job
          </button>
          <button className="ghost" onClick={() => onScreenChange("settings")} type="button">
            <Settings size={16} />
            AI Provider
          </button>
        </div>
      </section>
      <section className="metrics-row" aria-label="Job metrics">
        <div className="metric">
          <span>Total rows</span>
          <strong>{totalRows}</strong>
          <small>validated in this tab</small>
        </div>
        <div className="metric">
          <span>Completed</span>
          <strong>{completedRows}</strong>
          <small>saved to Media Library</small>
        </div>
        <div className="metric">
          <span>Needs attention</span>
          <strong>{failedRows}</strong>
          <small>failed generation rows</small>
        </div>
      </section>
      <section className="panel">
        <div className="panel-head">
          <h2>Preflight</h2>
          <Pill tone={status.imageGenerationAvailable ? "done" : "warning"}>
            {status.imageGenerationAvailable ? "Ready" : "Provider needed"}
          </Pill>
        </div>
        <ul className="checklist">
          <li>
            <span className={`check ${status.aiClientAvailable ? "ok" : "warn"}`} />
            {status.aiClientAvailable
              ? "The WordPress AI Client is available."
              : "The WordPress AI Client is unavailable; WordPress 7.0 or newer is required."}
          </li>
          <li>
            <span
              className={`check ${status.imageGenerationAvailable ? "ok" : "warn"}`}
            />
            {status.imageGenerationAvailable
              ? "A configured provider supports image generation."
              : "Install and configure an image-capable provider in Settings → Connectors."}
          </li>
          <li>
            <span className={`check ${status.wooAvailable ? "ok" : "warn"}`} />
            {status.wooAvailable
              ? "WooCommerce SKU mapping is available."
              : "WooCommerce not detected; Media Library save still works."}
          </li>
          <li>
            <span className="check ok" />
            Generated images are appended to product galleries only when you allow auto-mapping.
          </li>
        </ul>
      </section>
    </div>
  );
}

function ImportScreen({
  headers,
  mappings,
  rows,
  validRows,
  validationErrors,
  imageGenerationAvailable,
  autoMapProducts,
  brandLogo,
  onHeadersChange,
  onMappingsChange,
  onRowsChange,
  onValidRowsChange,
  onValidationErrorsChange,
  onNotice,
  onAutoMapProductsChange,
  onBrandLogoChange,
  onOpenWordPressWorkflow,
  onStart,
}: {
  headers: string[];
  mappings: Mappings;
  rows: CsvRow[];
  validRows: ValidRow[];
  validationErrors: string[];
  imageGenerationAvailable: boolean;
  autoMapProducts: boolean;
  brandLogo: BrandLogo | null;
  onHeadersChange: (headers: string[]) => void;
  onMappingsChange: (mappings: Mappings) => void;
  onRowsChange: (rows: CsvRow[]) => void;
  onValidRowsChange: (rows: ValidRow[]) => void;
  onValidationErrorsChange: (errors: string[]) => void;
  onNotice: (notice: { tone: "ok" | "error"; message: string } | null) => void;
  onAutoMapProductsChange: (checked: boolean) => void;
  onBrandLogoChange: (logo: BrandLogo | null) => void;
  onOpenWordPressWorkflow: () => void;
  onStart: () => void;
}) {
  const [showCsvDialog, setShowCsvDialog] = useState(false);

  async function selectCsv(event: React.ChangeEvent<HTMLInputElement>) {
    const file = event.target.files?.[0];
    if (!file) return;
    const text = await file.text();
    const parsed = parseCsv(text);
    const nextHeaders = parsed.shift() ?? [];
    const nextRows = parsed.map((values, index) => ({ recordNumber: index + 2, values }));

    onHeadersChange(nextHeaders);
    onRowsChange(nextRows);
    onValidRowsChange([]);
    onValidationErrorsChange([]);
    onBrandLogoChange(null);
    onMappingsChange({
      sku: suggestHeader(nextHeaders, ["SKU", "Product SKU", "Variant SKU"]),
      productName: suggestHeader(nextHeaders, [
        "Product Name",
        "Name",
        "Title",
        "Product file name",
      ]),
      prompt: suggestHeader(nextHeaders, ["GPT Image Prompt", "generation_prompt", "Prompt"]),
      referenceImage: suggestHeader(nextHeaders, ["reference_image", "Reference Image", "Images"]),
    });
    onNotice({ tone: "ok", message: `${nextRows.length} product rows detected.` });
  }

  function updateMapping(nextMappings: Mappings) {
    onMappingsChange(nextMappings);
    onValidRowsChange([]);
    onValidationErrorsChange([]);
  }

  function validateImport() {
    const errors: string[] = [];
    const accepted: ValidRow[] = [];
    const seenSkus = new Set<string>();

    if (!mappings.sku || !mappings.productName || !mappings.prompt) {
      errors.push("Map SKU, product file name, and prompt before validating.");
    }

    rows.forEach((row) => {
      const sku = rowValue(headers, row, mappings.sku);
      const productName = rowValue(headers, row, mappings.productName);
      const prompt = rowValue(headers, row, mappings.prompt);
      const referenceImage = rowValue(headers, row, mappings.referenceImage);

      if (!sku) errors.push(`Row ${row.recordNumber}: SKU is required.`);
      if (!productName) errors.push(`Row ${row.recordNumber}: product file name is required.`);
      if (!prompt) errors.push(`Row ${row.recordNumber}: prompt is required.`);
      if (sku && seenSkus.has(sku)) errors.push(`Row ${row.recordNumber}: duplicate SKU ${sku}.`);
      if (sku) seenSkus.add(sku);

      if (sku && productName && prompt) {
        accepted.push({
          recordNumber: row.recordNumber,
          sku,
          productName,
          prompt,
          referenceImage,
          status: "pending",
        });
      }
    });

    onValidationErrorsChange(errors);
    onValidRowsChange(errors.length ? [] : accepted);
    onNotice(
      errors.length
        ? { tone: "error", message: "Import has blocking validation errors." }
        : { tone: "ok", message: `${accepted.length} rows are ready for generation.` },
    );
  }

  function openBrandLogoPicker(source: "library" | "upload") {
    const createMediaFrame = window.wp?.media;
    if (!createMediaFrame) {
      onNotice({
        tone: "error",
        message: "The WordPress Media Library is unavailable. Reload this page and try again.",
      });
      return;
    }

    const frame = createMediaFrame({
      title: source === "upload" ? "Upload a brand logo" : "Choose a brand logo",
      button: { text: "Use as brand logo" },
      library: { type: "image" },
      multiple: false,
    });

    if (source === "upload") {
      frame.on("open", () => frame.content?.mode("upload"));
    }

    frame.on("select", () => {
      const attachment = frame.state().get("selection").first()?.toJSON();
      const isImage =
        attachment?.type === "image" || attachment?.mime?.toLowerCase().startsWith("image/");

      if (!attachment?.url || !isImage) {
        onNotice({ tone: "error", message: "Choose a valid image for the brand logo." });
        return;
      }

      onBrandLogoChange({
        id: Number(attachment.id ?? 0),
        url: attachment.url,
        alt: attachment.alt?.trim() || attachment.title?.trim() || "Brand logo",
        filename: attachment.filename?.trim() || "Brand logo",
      });
      onNotice({
        tone: "ok",
        message: "Brand logo selected. It will be used as a reference for every generated image.",
      });
    });

    frame.open();
  }

  return (
    <>
      <section className="panel import-file-panel">
        <div className="panel-head">
          <h2>Detected file</h2>
          <Pill tone={headers.length ? "done" : "warning"}>
            {headers.length ? "Ready" : "Waiting"}
          </Pill>
        </div>
        <div className="import-file-layout">
          <dl className="facts">
            <dt>Rows</dt>
            <dd>
              {rows.length
                ? `${rows.length} product rows detected`
                : "Select a CSV to preview rows"}
            </dd>
            <dt>Encoding</dt>
            <dd>UTF-8 CSV</dd>
            <dt>Profile</dt>
            <dd>{headers.length ? "WooCommerce / GPT mockup CSV" : "Pending"}</dd>
          </dl>
          <div className="import-actions">
            <button
              aria-label="Generate a CSV from your WooCommerce product export file"
              className="ghost stretch equal-action"
              onClick={() => setShowCsvDialog(true)}
              title="Generate a CSV from your WooCommerce product export file"
              type="button"
            >
              <MessageSquareText size={16} />
              Generate CSV
            </button>
            <span className="choice-divider">or</span>
            <label className="ghost stretch file-picker-button equal-action">
              <FileSpreadsheet size={16} />
              Select CSV
              <input accept=".csv,text/csv" hidden onChange={selectCsv} type="file" />
            </label>
          </div>
        </div>
      </section>

      <div className="content-grid mapping-layout">
        <section className="panel wide">
          <div className="panel-head">
            <h2>Column mapping</h2>
            <Pill tone={validRows.length ? "done" : "warning"}>
              {validRows.length ? "Validated" : "Mapping"}
            </Pill>
          </div>
          <div className="mapping-grid required">
            <MappingSelect
              label="SKU"
              value={mappings.sku}
              headers={headers}
              onChange={(sku) => updateMapping({ ...mappings, sku })}
            />
            <MappingSelect
              label="Product file name"
              value={mappings.productName}
              headers={headers}
              onChange={(productName) => updateMapping({ ...mappings, productName })}
            />
            <MappingSelect
              label="Prompt"
              value={mappings.prompt}
              headers={headers}
              onChange={(prompt) => updateMapping({ ...mappings, prompt })}
            />
            <MappingSelect
              label="Product reference image URL"
              value={mappings.referenceImage}
              headers={headers}
              onChange={(referenceImage) => updateMapping({ ...mappings, referenceImage })}
            />
          </div>
          <div className="panel-actions">
            <button
              className={validRows.length ? "ghost" : "primary"}
              disabled={!headers.length}
              onClick={validateImport}
              type="button"
            >
              Validate import
            </button>
          </div>
          <label className="inline-check">
            <input
              className="inline-check-input"
              checked={autoMapProducts}
              onChange={(event) => onAutoMapProductsChange(event.target.checked)}
              type="checkbox"
            />
            <span className="inline-check-box" aria-hidden="true" />
            <span>
              After generation, append each image to the matching WooCommerce product gallery by
              SKU.
            </span>
          </label>
          {validationErrors.length > 0 && (
            <div className="validation-errors">
              {validationErrors.slice(0, 10).map((error) => (
                <div className="notice error" key={error}>
                  {error}
                </div>
              ))}
            </div>
          )}
        </section>
        {validRows.length > 0 && (
          <section className="panel brand-logo-panel">
            <div className="panel-head">
              <h2>Brand logo reference</h2>
              <Pill tone={brandLogo ? "done" : ""}>{brandLogo ? "Selected" : "Optional"}</Pill>
            </div>
            <div className="brand-logo-layout">
              <div className="brand-logo-preview">
                {brandLogo ? (
                  <img src={brandLogo.url} alt={brandLogo.alt} />
                ) : (
                  <>
                    <GalleryHorizontalEnd size={30} aria-hidden="true" />
                    <span>No logo selected</span>
                  </>
                )}
              </div>
              <div className="brand-logo-content">
                <div className="brand-logo-copy">
                  <h3>{brandLogo ? brandLogo.filename : "Add a consistent brand signature"}</h3>
                  <p>
                    Select one logo for this batch. CatalogFrame will identify it as the brand logo
                    in every AI request and ask the provider to preserve it faithfully.
                  </p>
                </div>
                <div className="brand-logo-source-actions" aria-label="Brand logo source">
                  <button
                    className="brand-logo-source-button"
                    onClick={() => openBrandLogoPicker("library")}
                    type="button"
                  >
                    <span className="brand-logo-source-icon" aria-hidden="true">
                      <GalleryHorizontalEnd size={19} />
                    </span>
                    <span>
                      <strong>Media Library</strong>
                      <small>Choose an existing image</small>
                    </span>
                  </button>
                  <button
                    className="brand-logo-source-button"
                    onClick={() => openBrandLogoPicker("upload")}
                    type="button"
                  >
                    <span className="brand-logo-source-icon" aria-hidden="true">
                      <Upload size={19} />
                    </span>
                    <span>
                      <strong>Upload a logo</strong>
                      <small>Select a file from your device</small>
                    </span>
                  </button>
                </div>
              </div>
            </div>
            <div className="brand-logo-footer">
              <div>
                <strong>{brandLogo ? "Logo ready" : "Continue without a logo"}</strong>
                <span>
                  {brandLogo
                    ? "This logo will be referenced for every row in this batch."
                    : "Adding a logo is optional; you can still generate the batch now."}
                </span>
              </div>
              <div className="brand-logo-footer-actions">
                {brandLogo && (
                  <button
                    className="ghost"
                    onClick={() => {
                      onBrandLogoChange(null);
                      onNotice({ tone: "ok", message: "Brand logo removed from this job." });
                    }}
                    type="button"
                  >
                    Remove logo
                  </button>
                )}
                <button
                  className="primary"
                  disabled={!imageGenerationAvailable}
                  onClick={onStart}
                  type="button"
                >
                  <Play size={16} fill="currentColor" aria-hidden="true" />
                  Start generation
                </button>
              </div>
            </div>
          </section>
        )}
        <section className="panel">
          <div className="panel-head">
            <h2>Preview</h2>
            <Pill>{rows.length ? `${rows.length} rows` : "No CSV"}</Pill>
          </div>
          <div className="preview-table-wrap">
            <table className="preview-table">
              <thead>
                <tr>
                  {headers.slice(0, 5).map((header) => (
                    <th key={header}>{header}</th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {rows.slice(0, 6).map((row) => (
                  <tr key={row.recordNumber}>
                    {headers.slice(0, 5).map((header) => (
                      <td key={header}>{rowValue(headers, row, header)}</td>
                    ))}
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </section>
      </div>
      {showCsvDialog && (
        <div
          className="preview-backdrop"
          onMouseDown={(event) => {
            if (event.target === event.currentTarget) setShowCsvDialog(false);
          }}
        >
          <section
            className="external-link-dialog generate-csv-dialog"
            role="dialog"
            aria-modal="true"
            aria-labelledby="generate-csv-dialog-title"
          >
            <button
              aria-label="Close Generate CSV dialog"
              className="icon-button dialog-close"
              onClick={() => setShowCsvDialog(false)}
              type="button"
            >
              <X size={18} />
            </button>
            <span className="external-link-mark">
              <MessageSquareText aria-hidden="true" />
            </span>
            <p className="eyebrow">Generate CSV</p>
            <h2 id="generate-csv-dialog-title">Open the mockup CSV builder?</h2>
            <p>
              We will transfer you to the custom GPT to generate a blank product page or prepare a
              CatalogFrame CSV from your WooCommerce export.
            </p>
            <div className="preview-actions">
              <button
                className="ghost"
                onClick={() => {
                  window.location.href = config.productsUrl ?? "edit.php?post_type=product";
                }}
                type="button"
              >
                <FileSpreadsheet size={16} />
                Using the current product
              </button>
              <button
                className="ghost"
                onClick={() => {
                  setShowCsvDialog(false);
                  openExternalUrl(config.customGptUrl);
                }}
                type="button"
              >
                <ExternalLink size={16} />
                Go to ChatGPT
              </button>
              <button
                className="ghost"
                onClick={() => {
                  setShowCsvDialog(false);
                  onOpenWordPressWorkflow();
                }}
                type="button"
              >
                <HelpCircle size={16} />
                Learn more
              </button>
            </div>
          </section>
        </div>
      )}
    </>
  );
}

function MappingSelect({
  label,
  value,
  headers,
  onChange,
}: {
  label: string;
  value: string;
  headers: string[];
  onChange: (value: string) => void;
}) {
  return (
    <label className="field">
      <span>{label}</span>
      <select value={value} onChange={(event) => onChange(event.target.value)}>
        <option value="">Unused</option>
        {headers.map((header) => (
          <option key={header} value={header}>
            {header}
          </option>
        ))}
      </select>
    </label>
  );
}

function sleep(ms: number) {
  return new Promise((resolve) => {
    window.setTimeout(resolve, ms);
  });
}

async function waitForGenerationJob(job: GenerationJob) {
  const maxAttempts = 720;
  let current = job;

  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
    if (current.status === "completed" && current.result) return current.result;
    if (current.status === "failed") {
      throw new Error(current.error || "Generation failed.");
    }

    await sleep(2500);
    current = await getGenerationJob(current.jobId);
  }

  throw new Error("Generation job did not finish after 30 minutes.");
}

async function startGeneration(
  rows: ValidRow[],
  setRows: React.Dispatch<React.SetStateAction<ValidRow[]>>,
  setScreen: (screen: ScreenId) => void,
  setIsRunning: (running: boolean) => void,
  setStopRequested: (stopped: boolean) => void,
  stopRequestedRef: React.MutableRefObject<boolean>,
  autoMapProducts: boolean,
  brandLogo: BrandLogo | null,
  imageSettings: ImageGenerationSettings,
  setNotice: (notice: { tone: "ok" | "error"; message: string } | null) => void,
) {
  if (!rows.length) return;
  setScreen("queue");
  setIsRunning(true);
  setStopRequested(false);
  stopRequestedRef.current = false;
  let failedCount = 0;
  let nextIndex = 0;

  async function processRow(row: ValidRow) {
    if (stopRequestedRef.current) {
      setRows((current) =>
        current.map((item) => (item.sku === row.sku ? { ...item, status: "stopped" } : item)),
      );
      return;
    }

    setRows((current) =>
      current.map((item) =>
        item.sku === row.sku ? { ...item, status: "generating", error: undefined } : item,
      ),
    );
    try {
      const job = await generateImage({
        sku: row.sku,
        productName: row.productName,
        prompt: row.prompt,
        referenceImage: row.referenceImage,
        brandLogo: brandLogo?.url,
        autoMap: autoMapProducts,
        options: {
          size: imageSettings.size,
        },
      });
      const result = await waitForGenerationJob(job);
      setRows((current) =>
        current.map((item) =>
          item.sku === row.sku ? { ...item, status: "completed", result } : item,
        ),
      );
    } catch (error) {
      failedCount += 1;
      setRows((current) =>
        current.map((item) =>
          item.sku === row.sku
            ? {
                ...item,
                status: "failed",
                error: getErrorMessage(error, "Generation failed."),
              }
            : item,
        ),
      );
    }
  }

  async function runWorker() {
    while (!stopRequestedRef.current) {
      const row = rows[nextIndex];
      nextIndex += 1;
      if (!row) return;
      await processRow(row);
    }
  }

  const workerCount = Math.min(imageSettings.concurrency, rows.length);
  await Promise.all(Array.from({ length: workerCount }, () => runWorker()));

  if (stopRequestedRef.current) {
    setRows((current) =>
      current.map((item) => (item.status === "pending" ? { ...item, status: "stopped" } : item)),
    );
  }

  setIsRunning(false);
  setNotice(
    failedCount
      ? {
          tone: "error",
          message: `Generation job finished with ${failedCount} failed row${
            failedCount === 1 ? "" : "s"
          }. Open the failed row details below to review the provider error.`,
        }
      : { tone: "ok", message: "Generation job finished." },
  );
}

function QueueScreen({
  rows,
  isRunning,
  progress,
  onStop,
  onStart,
}: {
  rows: ValidRow[];
  isRunning: boolean;
  progress: number;
  onStop: () => void;
  onStart: () => void;
}) {
  return (
    <section className="panel">
      <div className="panel-head">
        <h2>Generation queue</h2>
        <Pill tone={isRunning ? "warning" : ""}>{isRunning ? "Running" : "Idle"}</Pill>
      </div>
      <div className="progress-track" aria-label="Generation progress">
        <span style={{ width: `${progress}%` }} />
      </div>
      <div className="queue-actions">
        <button
          className="primary"
          disabled={isRunning || rows.length === 0}
          onClick={onStart}
          type="button"
        >
          <Play size={16} />
          Start
        </button>
        <button className="ghost" disabled={!isRunning} onClick={onStop} type="button">
          <Pause size={16} />
          Stop after current image
        </button>
      </div>
      <div className="queue-list">
        {rows.length === 0 ? (
          <div className="empty-state">Validate a CSV import to create a queue.</div>
        ) : (
          rows.map((row) => <QueueRowCard key={row.sku} row={row} />)
        )}
      </div>
    </section>
  );
}

function QueueRowCard({ row }: { row: ValidRow }) {
  const Icon =
    row.status === "completed"
      ? CheckCircle2
      : row.status === "failed"
        ? XCircle
        : row.status === "generating"
          ? RotateCcw
          : CircleAlert;
  return (
    <article className="queue-row">
      <Icon className="icon" aria-hidden="true" />
      <div>
        <strong>{row.sku}</strong>
        <p>{row.productName}</p>
        {row.error && (
          <details className="queue-error-details">
            <summary>Error details</summary>
            <pre>{row.error}</pre>
          </details>
        )}
      </div>
      <Pill
        tone={
          row.status === "completed"
            ? "done"
            : row.status === "failed"
              ? "danger"
              : row.status === "generating"
                ? "warning"
                : ""
        }
      >
        {row.status}
      </Pill>
    </article>
  );
}

function ResultsScreen({ rows }: { rows: ValidRow[] }) {
  const results = rows.filter((row) => row.result);
  return (
    <section className="panel">
      <div className="panel-head">
        <h2>Results gallery</h2>
        <Pill>{results.length} images</Pill>
      </div>
      <div className="results-grid">
        {results.length === 0 ? (
          <div className="empty-state">Generated Media Library images will appear here.</div>
        ) : (
          results.map((row) => (
            <article className="asset-card" key={row.sku}>
              <img src={row.result?.attachment.url} alt="" />
              <div>
                <strong>{row.sku}</strong>
                <span>{row.result?.attachment.filename}</span>
                <a href={row.result?.attachment.url} target="_blank" rel="noreferrer">
                  Open media file <ExternalLink size={13} />
                </a>
                <small>{row.result?.mapping.message}</small>
              </div>
            </article>
          ))
        )}
      </div>
    </section>
  );
}

function SettingsScreen({
  status,
  imageSettings,
  onImageSettingsChange,
  onRefreshStatus,
}: {
  status: PluginStatus;
  imageSettings: ImageGenerationSettings;
  onImageSettingsChange: (settings: ImageGenerationSettings) => void;
  onRefreshStatus: () => void;
}) {
  function updateImageSetting<K extends keyof ImageGenerationSettings>(
    key: K,
    value: ImageGenerationSettings[K],
  ) {
    onImageSettingsChange({ ...imageSettings, [key]: value });
  }

  return (
    <>
      <div className="content-grid">
        <section className="panel wide">
          <div className="panel-head">
            <h2>WordPress AI provider</h2>
            <Pill tone={status.imageGenerationAvailable ? "done" : "warning"}>
              {status.imageGenerationAvailable ? "Image generation ready" : "Setup required"}
            </Pill>
          </div>
          <div className="notice">
            CatalogFrame uses the provider-neutral AI Client built into WordPress 7.0. Provider
            plugins and credentials are managed centrally in WordPress, not inside CatalogFrame.
          </div>
          <div className="panel-actions">
            <button
              className="primary"
              disabled={!status.canConfigureProviders}
              onClick={() => {
                window.location.href = config.connectorsUrl;
              }}
              type="button"
            >
              <Settings size={16} />
              Open Settings → Connectors
            </button>
            <button className="ghost" onClick={onRefreshStatus} type="button">
              Refresh connection status
            </button>
          </div>
          {!status.canConfigureProviders && (
            <div className="notice warning">
              Ask a site administrator to install and configure an image-capable provider in
              Settings → Connectors.
            </div>
          )}
        </section>
        <section className="panel">
          <div className="panel-head">
            <h2>Connection status</h2>
          </div>
          <ul className="checklist">
            <li>
              <span className={`check ${status.aiClientAvailable ? "ok" : "warn"}`} />
              {status.aiClientAvailable ? "WordPress AI Client available" : "WordPress 7.0 required"}
            </li>
            <li>
              <span
                className={`check ${status.imageGenerationAvailable ? "ok" : "warn"}`}
              />
              {status.imageGenerationAvailable
                ? "Image-capable provider connected"
                : "No image-capable provider connected"}
            </li>
            <li>
              <span className="check ok" />
              Provider credentials stay in WordPress Connectors.
            </li>
          </ul>
        </section>
      </div>
      <section className="panel settings-panel">
        <div className="panel-head">
          <h2>Default image generation settings</h2>
          <Pill tone="live">Provider neutral</Pill>
        </div>
        <div className="settings-grid">
          <SettingSelect
            label="Aspect preference"
            value={imageSettings.size}
            options={["1024x1024", "auto", "1536x1024", "1024x1536"]}
            optionLabels={{
              "1024x1024": "Square (1:1)",
              auto: "Provider default",
              "1536x1024": "Landscape (3:2)",
              "1024x1536": "Portrait (2:3)",
            }}
            onChange={(size) => updateImageSetting("size", size as ImageSize)}
          />
          <NumberSetting
            label="Concurrency"
            max={4}
            min={1}
            onChange={(concurrency) => updateImageSetting("concurrency", concurrency)}
            value={imageSettings.concurrency}
          />
        </div>
        <div className="notice">
          Preferences are saved in this browser and applied to new requests. The connected provider
          chooses the model, exact dimensions, image format, moderation behavior, and price.
        </div>
        <div className="notice warning">
          Higher concurrency starts more WordPress image requests at once and can increase server
          load or provider usage. Keep it at 1 if your site or provider times out.
        </div>
        <div className="panel-actions">
          <button
            className="ghost"
            onClick={() => {
              onImageSettingsChange(defaultImageSettings);
            }}
            type="button"
          >
            Reset defaults
          </button>
        </div>
      </section>
    </>
  );
}

function SettingSelect({
  label,
  value,
  options,
  optionLabels = {},
  onChange,
}: {
  label: string;
  value: string;
  options: string[];
  optionLabels?: Record<string, string>;
  onChange: (value: string) => void;
}) {
  return (
    <label className="field">
      <span>{label}</span>
      <select value={value} onChange={(event) => onChange(event.target.value)}>
        {options.map((option) => (
          <option key={option} value={option}>
            {optionLabels[option] ?? option}
          </option>
        ))}
      </select>
    </label>
  );
}

function NumberSetting({
  disabled = false,
  label,
  value,
  min,
  max,
  onChange,
}: {
  disabled?: boolean;
  label: string;
  value: number;
  min: number;
  max: number;
  onChange: (value: number) => void;
}) {
  return (
    <label className="field">
      <span>{label}</span>
      <input
        disabled={disabled}
        max={max}
        min={min}
        onChange={(event) => onChange(Number(event.target.value))}
        type="number"
        value={value}
      />
    </label>
  );
}

function TutorialExternalLink({ href, children }: { href: string; children: React.ReactNode }) {
  return (
    <a
      href={href}
      onClick={(event) => {
        event.preventDefault();
        openExternalUrl(href);
      }}
    >
      {children}
      <ExternalLink size={13} aria-hidden="true" />
    </a>
  );
}

function HelpScreen({ helpRequest }: { helpRequest?: HelpRequest | null }) {
  const [query, setQuery] = useState("");
  const [tutorial, setTutorial] = useState<HelpTutorial | null>(null);
  const [wordpressScenario, setWordpressScenario] = useState<HelpScenario>("new");
  const dialogCloseRef = useRef<HTMLButtonElement | null>(null);

  useEffect(() => {
    if (!helpRequest) return;
    if (helpRequest.tutorial === "wordpress" && helpRequest.scenario) {
      setWordpressScenario(helpRequest.scenario);
    }
    setTutorial(helpRequest.tutorial);
  }, [helpRequest]);

  const helpTopics = [
    {
      id: "provider" as const,
      eyebrow: "AI setup",
      title: "Connect an image provider",
      description:
        "Install and configure an image-capable provider through WordPress Settings → Connectors.",
      icon: Settings,
      logo: wordpressLogo,
      accent: "teal",
    },
    {
      id: "wordpress" as const,
      eyebrow: "WordPress workflow",
      title: "Prepare a product mockup file with prompts for WordPress",
      description:
        "Turn your WooCommerce product export into an import-ready CSV with an image prompt for every product.",
      icon: null,
      logo: wordpressLogo,
      accent: "blue",
    },
  ];

  const faqs = [
    {
      question: "What file format should I import?",
      answer:
        "Use a UTF-8 CSV file with a header row. At minimum, include SKU, product file name, and a clear GPT image prompt.",
    },
    {
      question: "Where are AI provider credentials stored?",
      answer:
        "WordPress manages provider credentials centrally through Settings → Connectors. CatalogFrame does not collect, store, or return provider API keys.",
    },
    {
      question: "What makes a good product image prompt?",
      answer:
        "Describe the product, setting, lighting, camera angle, composition, and details that must stay unchanged. Keep one visual direction per row.",
    },
    {
      question: "Can I review image settings before generating?",
      answer:
        "Yes. Open Settings to choose an aspect preference and concurrency. The connected provider controls its model, exact format, quality, moderation, and price.",
    },
  ];

  const normalizedQuery = query.trim().toLowerCase();
  const visibleTopics = helpTopics.filter((topic) =>
    `${topic.eyebrow} ${topic.title} ${topic.description}`.toLowerCase().includes(normalizedQuery),
  );
  const visibleFaqs = faqs.filter((faq) =>
    `${faq.question} ${faq.answer}`.toLowerCase().includes(normalizedQuery),
  );
  const resultCount = visibleTopics.length + visibleFaqs.length;

  useEffect(() => {
    if (!tutorial) return;
    dialogCloseRef.current?.focus();
    function closeOnEscape(event: KeyboardEvent) {
      if (isEscapeKey(event.key)) setTutorial(null);
    }
    window.addEventListener("keydown", closeOnEscape);
    return () => window.removeEventListener("keydown", closeOnEscape);
  }, [tutorial]);

  const wordpressSteps = useMemo(
    () =>
      wordpressScenario === "new"
        ? [
            {
              title: "Sign in to ChatGPT",
              description: (
                <>
                  You need a ChatGPT account to use the catalog builder. If you do not have one,
                  create an account at{" "}
                  <TutorialExternalLink href={CHATGPT_URL}>chatgpt.com</TutorialExternalLink>.
                </>
              ),
            },
            {
              title: "Open the WooCommerce Product CSV Builder",
              description: (
                <>
                  Start a conversation with the{" "}
                  <TutorialExternalLink href={config.customGptUrl}>custom GPT</TutorialExternalLink>
                  and provide everything you already know about your products, audience, and
                  business.
                </>
              ),
            },
            {
              title: "Shape the catalog and brand",
              description:
                "The GPT can brainstorm product ideas, recommend a brand identity, and generate a logo direction based on your answers.",
            },
            {
              title: "Download both CSV files",
              description:
                "Ask the GPT to export two files: a WordPress-ready product CSV and a separate CatalogFrame CSV for generating your product mockups.",
            },
          ]
        : [
            {
              title: "Open your WordPress dashboard",
              description:
                "Sign in to the administrator dashboard for the store you want to export.",
            },
            {
              title: "Open the product list",
              description: (
                <>
                  In the navigation menu, select <strong>Products - All Products</strong>.
                </>
              ),
              image: {
                src: wordpressProductsNavigation,
                alt: "WordPress navigation showing Products and All Products selected",
                className: "tutorial-image-wordpress-nav",
              },
            },
            {
              title: "Start the export",
              description: (
                <>
                  At the top of the Products page, select <strong>Export</strong>.
                </>
              ),
              image: {
                src: wordpressExportButton,
                alt: "WordPress Products page with the Export button highlighted",
              },
            },
            {
              title: "Choose the product data",
              description: (
                <>
                  Keep the export selections at their defaults when possible. For a limited export,
                  include at least <strong>SKU</strong>, <strong>Name</strong>,{" "}
                  <strong>Short description</strong>, <strong>Description</strong>, and{" "}
                  <strong>Categories</strong> so the AI has enough context for each mockup.
                </>
              ),
              image: {
                src: wordpressExportSettings,
                alt: "WordPress Export products screen with the default export selections",
                className: "tutorial-image-wordpress-export",
              },
            },
            {
              title: "Send the export to the custom GPT",
              description: (
                <>
                  Sign in or register at{" "}
                  <TutorialExternalLink href={CHATGPT_URL}>chatgpt.com</TutorialExternalLink>, then
                  open the{" "}
                  <TutorialExternalLink href={config.customGptUrl}>custom GPT</TutorialExternalLink>
                  and upload the WordPress export. Add any missing product, brand, or mockup
                  direction.
                </>
              ),
            },
            {
              title: "Download both prepared files",
              description:
                "The GPT can refine the ideas, brand identity, and logo direction. Export a WordPress-ready CSV and a separate CatalogFrame CSV for this tool.",
            },
          ],
    [wordpressScenario],
  );

  const tutorialContent: Record<
    HelpTutorial,
    {
      title: string;
      intro: string;
      steps: Array<{
        title: string;
        description?: React.ReactNode;
        image?: { src: string; alt: string; className?: string };
      }>;
    }
  > = {
    provider: {
      title: "Connect an AI image provider",
      intro:
        "WordPress 7.0 includes the AI Client. Add an image-capable provider once in WordPress, then CatalogFrame can use it without handling credentials.",
      steps: [
        {
          title: "Open WordPress Connectors",
          description: (
            <>
              Go to{" "}
              <TutorialExternalLink href={config.connectorsUrl}>
                Settings → Connectors
              </TutorialExternalLink>{" "}
              in the WordPress dashboard.
            </>
          ),
        },
        {
          title: "Install an image-capable provider",
          description:
            "Choose a provider plugin that advertises image generation, such as Google or OpenAI. Anthropic currently advertises text generation only and cannot run CatalogFrame image jobs.",
        },
        {
          title: "Connect the provider",
          description:
            "Activate the provider plugin, then enter its credentials in the WordPress connector screen. WordPress stores and shares those credentials; CatalogFrame never receives the raw key.",
        },
        {
          title: "Return and verify",
          description: (
            <>
              Return to CatalogFrame Settings and select <strong>Refresh connection status</strong>.
              The status changes to <strong>Image generation ready</strong> when WordPress finds a
              compatible configured model. Read the{" "}
              <TutorialExternalLink href={WORDPRESS_AI_CLIENT_URL}>
                WordPress AI Client guide
              </TutorialExternalLink>{" "}
              for more detail.
            </>
          ),
        },
      ],
    },
    wordpress: {
      title: "WordPress - Prepare product file",
      intro:
        wordpressScenario === "new"
          ? "Create a product catalog and brand direction from an idea, with two ready-to-use CSV files at the end."
          : "Export your live WooCommerce catalog, enrich it with the custom GPT, and prepare it for mockup generation.",
      steps: wordpressSteps,
    },
  };

  return (
    <div className="support-layout">
      <section className="help-hero">
        <div className="help-hero-copy">
          <p className="eyebrow">CatalogFrame field guide</p>
          <h2>How can we help?</h2>
          <p>Find setup guidance, prepare your WordPress catalog, and get generation-ready.</p>
        </div>
        <div className="help-search-wrap">
          <Search size={18} aria-hidden="true" />
          <input
            aria-label="Search help content"
            onChange={(event) => setQuery(event.target.value)}
            placeholder="Search providers, WordPress, prompts..."
            type="search"
            value={query}
          />
          {query && (
            <button aria-label="Clear search" onClick={() => setQuery("")} type="button">
              <X size={15} />
            </button>
          )}
        </div>
      </section>
      {normalizedQuery && (
        <p className="help-search-status" role="status">
          {resultCount === 0
            ? "No guidance found"
            : `${resultCount} result${resultCount === 1 ? "" : "s"}`}{" "}
          for "{query.trim()}"
        </p>
      )}
      <div className="help-steps" aria-label="Help topics">
        {visibleTopics.map((topic) => {
          const TopicIcon = topic.icon;
          return (
            <article className={`help-card ${topic.accent}`} key={topic.id}>
              <span className="help-card-icon">
                {TopicIcon ? (
                  <TopicIcon aria-hidden="true" />
                ) : (
                  <img alt="" aria-hidden="true" src={topic.logo} />
                )}
              </span>
              <p className="eyebrow">{topic.eyebrow}</p>
              <h3>{topic.title}</h3>
              <p>{topic.description}</p>
              <button className="help-learn" onClick={() => setTutorial(topic.id)} type="button">
                Learn more
              </button>
            </article>
          );
        })}
      </div>
      {resultCount === 0 && (
        <div className="help-empty">
          <Search size={24} />
          <h3>Try another search</h3>
          <p>Search for a file type, provider, prompt, or generation setting.</p>
          <button className="ghost" onClick={() => setQuery("")} type="button">
            Clear search
          </button>
        </div>
      )}
      {visibleFaqs.length > 0 && (
        <section className="help-faq" aria-labelledby="faq-title">
          <div className="help-faq-intro">
            <p className="eyebrow">Quick answers</p>
            <h2 id="faq-title">Frequently asked questions</h2>
            <p>Practical notes for a smoother first batch.</p>
            <button
              className="ghost"
              onClick={() => openExternalUrl(config.supportUrl)}
              type="button"
            >
              Still need help?
            </button>
          </div>
          <div className="help-faq-list">
            {visibleFaqs.map((faq, index) => (
              <details key={faq.question} open={!normalizedQuery && index === 0}>
                <summary>
                  {faq.question}
                  <ChevronDown size={17} />
                </summary>
                <p>{faq.answer}</p>
              </details>
            ))}
          </div>
        </section>
      )}
      {tutorial && (
        <div
          className="preview-backdrop"
          onMouseDown={(event) => {
            if (event.target === event.currentTarget) setTutorial(null);
          }}
        >
          <section
            className="help-dialog"
            role="dialog"
            aria-modal="true"
            aria-labelledby="help-dialog-title"
          >
            <div className="help-dialog-head">
              <div>
                <p className="eyebrow">Step-by-step guide</p>
                <h2 id="help-dialog-title">{tutorialContent[tutorial].title}</h2>
              </div>
              <button
                ref={dialogCloseRef}
                aria-label="Close tutorial"
                className="icon-button"
                onClick={() => setTutorial(null)}
                type="button"
              >
                <X size={18} />
              </button>
            </div>
            {tutorial === "wordpress" && (
              <div className="tutorial-tabs" role="tablist" aria-label="WordPress guide type">
                <button
                  aria-controls="wordpress-guide-panel"
                  aria-selected={wordpressScenario === "new"}
                  className={wordpressScenario === "new" ? "active" : ""}
                  id="wordpress-new-tab"
                  onClick={() => setWordpressScenario("new")}
                  role="tab"
                  type="button"
                >
                  <Plus size={16} aria-hidden="true" />
                  <span>
                    <small>No products yet</small>
                    Start a new catalog
                  </span>
                </button>
                <button
                  aria-controls="wordpress-guide-panel"
                  aria-selected={wordpressScenario === "existing"}
                  className={wordpressScenario === "existing" ? "active" : ""}
                  id="wordpress-existing-tab"
                  onClick={() => setWordpressScenario("existing")}
                  role="tab"
                  type="button"
                >
                  <FileSpreadsheet size={16} aria-hidden="true" />
                  <span>
                    <small>Products are live</small>
                    Export existing products
                  </span>
                </button>
              </div>
            )}
            <div
              aria-labelledby={
                tutorial === "wordpress"
                  ? wordpressScenario === "new"
                    ? "wordpress-new-tab"
                    : "wordpress-existing-tab"
                  : undefined
              }
              id={tutorial === "wordpress" ? "wordpress-guide-panel" : undefined}
              role={tutorial === "wordpress" ? "tabpanel" : undefined}
            >
              <p className="help-dialog-intro">{tutorialContent[tutorial].intro}</p>
              <ol className="tutorial-steps">
                {tutorialContent[tutorial].steps.map((step, index) => (
                  <li key={`${step.title}-${index}`}>
                    <span className="tutorial-step-number">{index + 1}</span>
                    <div className="tutorial-step-content">
                      <p className="tutorial-step-title">{step.title}</p>
                      {step.description && (
                        <p className="tutorial-step-description">{step.description}</p>
                      )}
                      {step.image && (
                        <figure className={`tutorial-image ${step.image.className ?? ""}`}>
                          <img src={step.image.src} alt={step.image.alt} />
                        </figure>
                      )}
                    </div>
                  </li>
                ))}
              </ol>
            </div>
            <div className="preview-actions">
              {tutorial === "provider" && (
                <button
                  className="ghost"
                  onClick={() => {
                    window.location.href = config.connectorsUrl;
                  }}
                  type="button"
                >
                  Open Settings → Connectors
                </button>
              )}
              {tutorial === "wordpress" && (
                <button
                  className="ghost"
                  onClick={() => openExternalUrl(config.customGptUrl)}
                  type="button"
                >
                  Open custom GPT <ExternalLink size={15} />
                </button>
              )}
              <button className="primary" onClick={() => setTutorial(null)} type="button">
                Got it
              </button>
            </div>
          </section>
        </div>
      )}
    </div>
  );
}

export { App };
