/* global AbortController, DOMParser, HTMLFormElement, HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, MutationObserver, Node, URL, URLSearchParams, clearTimeout, document, fetch, navigator, setTimeout, window */

(() => {
  const SELECTOR = "[data-playground]";
  const DESIGN_DEFAULTS = {
    style: "default",
    palette: "violet",
    mode: "system",
    font: "jakarta",
    scale: "default",
  };
  const htmlPolicy = window.trustedTypes?.createPolicy("wrnexus-playground", {
    createHTML: (value) => value,
  });
  let request;
  let timer;

  function storedDesign() {
    const root = document.documentElement.dataset;
    return {
      style: root.uiStyle || DESIGN_DEFAULTS.style,
      palette: root.uiPalette || DESIGN_DEFAULTS.palette,
      mode: root.uiMode || DESIGN_DEFAULTS.mode,
      font: root.uiFont || DESIGN_DEFAULTS.font,
      scale: root.uiScale || DESIGN_DEFAULTS.scale,
    };
  }

  function setCookie(name, value) {
    document.cookie = `${name}=${encodeURIComponent(value)};path=/;max-age=31536000;samesite=lax`;
  }

  function applyDesign(settings, persist = true) {
    const root = document.documentElement;
    root.dataset.uiStyle = settings.style;
    root.dataset.uiPalette = settings.palette;
    root.dataset.uiMode = settings.mode;
    root.dataset.uiFont = settings.font;
    root.dataset.uiScale = settings.scale;

    const dark =
      settings.mode === "dark" ||
      (settings.mode === "system" && window.matchMedia("(prefers-color-scheme: dark)").matches);
    root.dataset.theme = dark ? "dark" : "light";

    for (const control of document.querySelectorAll("[data-design-setting]")) {
      control.value = settings[control.dataset.designSetting] || "";
    }
    if (persist) {
      setCookie("wrn-ui-style", settings.style);
      setCookie("wrn-ui-palette", settings.palette);
      setCookie("wrn-ui-mode", settings.mode);
      setCookie("wrn-ui-font", settings.font);
      setCookie("wrn-ui-scale", settings.scale);
      setCookie("wire-theme", dark ? "dark" : "light");
    }
  }

  let design = storedDesign();
  applyDesign(design, false);
  for (const link of document.querySelectorAll(".docs-directory a[href]")) {
    const target = new URL(link.href, window.location.origin);
    if (target.pathname === window.location.pathname) link.setAttribute("aria-current", "page");
  }
  window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
    if (design.mode === "system") applyDesign(design);
  });

  function sourceAttribute(value) {
    return String(value)
      .replaceAll("&", "&amp;")
      .replaceAll('"', "&quot;")
      .replaceAll("<", "&lt;")
      .replaceAll(">", "&gt;");
  }

  function updateCode(playground) {
    const form = playground.querySelector("[data-playground-form]");
    const code = playground.querySelector("[data-playground-code]");
    if (!(form instanceof HTMLFormElement) || !code) return;

    const component = playground.getAttribute("data-playground-component") || "Component";
    const publicTag = playground.getAttribute("data-playground-public-tag") === "true";
    const hasSlot = playground.getAttribute("data-playground-has-slot") === "true";
    const attributes = [];

    for (const field of form.elements) {
      if (
        !(
          field instanceof HTMLInputElement ||
          field instanceof HTMLTextAreaElement ||
          field instanceof HTMLSelectElement
        ) ||
        !field.name.startsWith("pg_")
      )
        continue;

      const name = field.name.slice(3);
      if (field instanceof HTMLInputElement && field.type === "checkbox") {
        attributes.push(`${name}="${field.checked}"`);
      } else if (field.value !== "") {
        const value = field.hasAttribute("data-playground-json")
          ? `'${field.value.replaceAll("'", "\\'")}'`
          : `"${field.value.replaceAll('"', '\\"')}"`;
        attributes.push(`${name}=${value}`);
      }
    }

    const tag = publicTag ? component : "div";
    const mount = publicTag ? [] : [`data-component="${sourceAttribute(component)}"`];
    const lines = [...mount, ...attributes].map((attribute) => `  ${attribute}`).join("\n");
    const opening = lines ? `<${tag}\n${lines}` : `<${tag}`;
    code.textContent = hasSlot
      ? `${opening}>\n  <!-- Add slot content here -->\n</${tag}>`
      : `${opening}\n/>`;
  }

  function valuesFrom(form) {
    const params = new URLSearchParams();
    let valid = true;

    for (const field of form.elements) {
      if (!(
        field instanceof HTMLInputElement ||
        field instanceof HTMLTextAreaElement ||
        field instanceof HTMLSelectElement
      ))
        continue;
      if (!field.name) continue;

      const error = field.closest(".playground-field")?.querySelector("[data-playground-error]");
      if (field.hasAttribute("data-playground-json")) {
        try {
          JSON.parse(field.value);
          field.removeAttribute("aria-invalid");
          if (error) error.textContent = "";
        } catch {
          field.setAttribute("aria-invalid", "true");
          if (error) error.textContent = "Enter valid JSON";
          valid = false;
          continue;
        }
      }

      params.set(field.name, field.type === "checkbox" ? String(field.checked) : field.value);
    }

    return valid ? params : null;
  }

  async function update(playground) {
    const form = playground.querySelector("[data-playground-form]");
    const preview = playground.querySelector("[data-playground-preview]");
    const status = playground.querySelector("[data-playground-status]");
    if (!(form instanceof HTMLFormElement) || !preview || !status) return;

    const params = valuesFrom(form);
    if (!params) {
      status.textContent = "Fix the highlighted JSON value.";
      return;
    }

    request?.abort();
    request = new AbortController();
    const url = new URL(window.location.href);
    for (const key of [...url.searchParams.keys()]) {
      if (key.startsWith("pg_")) url.searchParams.delete(key);
    }
    for (const [key, value] of params) url.searchParams.set(key, value);

    playground.setAttribute("data-updating", "true");
    status.textContent = "Updating preview…";

    try {
      const response = await fetch(url, {
        headers: { "x-wrnexus-playground": "1" },
        signal: request.signal,
      });
      if (!response.ok) throw new Error(`Preview request failed (${response.status})`);
      const responseHtml = await response.text();
      const parsed = new DOMParser().parseFromString(
        htmlPolicy ? htmlPolicy.createHTML(responseHtml) : responseHtml,
        "text/html",
      );
      const next = parsed.querySelector("[data-playground-preview]");
      if (!next) throw new Error("Preview markup was not returned");

      window.__wrnexusDisposeBehaviors?.(preview);
      preview.replaceChildren(
        ...Array.from(next.childNodes, (node) => document.importNode(node, true)),
      );
      window.__wrnexusHydrateScopes?.(preview);
      window.__wrnexusHydrateCsrFetches?.(preview);
      window.wireTheme?.bind?.(document);
      bindPlaygroundEvents(document);
      status.textContent = "Preview updated";
    } catch (error) {
      if (error?.name !== "AbortError") {
        status.textContent = error instanceof Error ? error.message : "Unable to update preview";
      }
    } finally {
      playground.removeAttribute("data-updating");
    }
  }

  const outputBindings = new WeakMap();
  const legacyObservedOutputs = new Set();

  function formatOutputPayload(payload) {
    if (payload === undefined) return "No output payload";
    if (typeof payload === "string") return payload;
    try {
      return JSON.stringify(payload, null, 2);
    } catch {
      return String(payload);
    }
  }

  function renderOutput(owner, outputName, payload) {
    const log = owner.querySelector("[data-playground-event-log]");
    if (!log) return;
    log.replaceChildren();
    const heading = document.createElement("strong");
    heading.textContent = `@${outputName}`;
    const output = document.createElement("pre");
    output.textContent = formatOutputPayload(payload);
    log.append(heading, output);
  }

  function bindDirectOutput(host, owner, outputName) {
    let byName = outputBindings.get(host);
    if (!byName) {
      byName = new Map();
      outputBindings.set(host, byName);
    }
    if (byName.has(outputName)) return;
    const registry = (host.__wrnexusOutputHandlers ??= new Map());
    const handlers = registry.get(outputName) ?? new Set();
    const handler = (payload) => renderOutput(owner, outputName, payload);
    handlers.add(handler);
    registry.set(outputName, handlers);
    byName.set(outputName, handler);
  }

  function bindPlaygroundEvents(root = document) {
    for (const playground of root.querySelectorAll?.(SELECTOR) ?? []) {
      const outputNames = (playground.getAttribute("data-playground-events") || "")
        .split(",")
        .map((name) => name.trim())
        .filter(Boolean);
      const host = playground.querySelector(
        "[data-playground-preview] [data-wrn-scope], [data-playground-preview] [data-ui-component], [data-playground-preview] [data-component]",
      );
      for (const outputName of outputNames) {
        if (host) bindDirectOutput(host, playground, outputName);
        // Legacy CustomEvent observation is retained only for compatibility-mode components.
        if (legacyObservedOutputs.has(outputName)) continue;
        legacyObservedOutputs.add(outputName);
        document.addEventListener(outputName, (legacyEvent) => {
          const owner = legacyEvent.target?.closest?.(SELECTOR);
          if (!owner) return;
          const declaredOutputs = (owner.getAttribute("data-playground-events") || "").split(",");
          if (!declaredOutputs.includes(outputName)) return;
          renderOutput(owner, outputName, legacyEvent.detail);
        });
      }
    }
  }

  bindPlaygroundEvents();
  new MutationObserver(() => bindPlaygroundEvents(document)).observe(
    document.getElementById("app") || document.body,
    { childList: true, subtree: true },
  );

  function schedule(playground) {
    updateCode(playground);
    clearTimeout(timer);
    timer = setTimeout(() => update(playground), 180);
  }

  document.addEventListener("input", (event) => {
    const playground = event.target.closest?.(SELECTOR);
    if (playground) schedule(playground);
  });

  document.addEventListener("change", (event) => {
    const playground = event.target.closest?.(SELECTOR);
    if (playground) schedule(playground);
  });

  document.addEventListener("reset", (event) => {
    const playground = event.target.closest?.(SELECTOR);
    if (playground)
      setTimeout(() => {
        updateCode(playground);
        update(playground);
      }, 0);
  });

  document.addEventListener("click", async (event) => {
    const designToggle = event.target.closest?.("[data-design-panel-toggle]");
    if (designToggle) {
      document.documentElement.toggleAttribute("data-design-panel-open");
      return;
    }

    if (event.target.closest?.("[data-design-reset]")) {
      design = { ...DESIGN_DEFAULTS };
      applyDesign(design);
      return;
    }

    const copyText = event.target.closest?.("[data-copy-text]");
    if (copyText) {
      try {
        await navigator.clipboard.writeText(copyText.dataset.copyText || "");
        copyText.setAttribute("data-copied", "true");
        setTimeout(() => copyText.removeAttribute("data-copied"), 1200);
      } catch {
        // Clipboard access may be blocked in an insecure local context.
      }
      return;
    }

    const menuToggle = event.target.closest?.("[data-docs-menu-toggle]");
    if (menuToggle) {
      document.documentElement.toggleAttribute("data-docs-menu-open");
      return;
    }

    if (event.target.closest?.("[data-docs-component-link]")) {
      document.documentElement.removeAttribute("data-docs-menu-open");
    }

    const copy = event.target.closest?.("[data-playground-copy]");
    if (!copy) return;
    const playground = copy.closest(SELECTOR);
    const code = playground?.querySelector("[data-playground-code]");
    const status = playground?.querySelector("[data-playground-status]");
    if (!code || !status) return;

    try {
      await navigator.clipboard.writeText(code.textContent || "");
      status.textContent = "Component code copied";
      const label = copy.lastChild;
      if (label?.nodeType === Node.TEXT_NODE) label.textContent = "Copied";
      setTimeout(() => {
        if (label?.nodeType === Node.TEXT_NODE) label.textContent = "Copy";
      }, 1400);
    } catch {
      status.textContent = "Copy failed. Select the code and copy it manually.";
    }
  });

  document.addEventListener("input", (event) => {
    const setting = event.target.closest?.("[data-design-setting]");
    if (setting) {
      design = { ...design, [setting.dataset.designSetting]: setting.value };
      applyDesign(design);
      return;
    }
    if (!event.target.matches?.("[data-docs-search]")) return;
    const query = event.target.value.trim().toLowerCase();
    const links = [...document.querySelectorAll("[data-docs-component-link]")];
    let visible = 0;
    for (const link of links) {
      const matches = !query || link.textContent.toLowerCase().includes(query);
      link.hidden = !matches;
      if (matches) visible++;
    }
    for (const group of document.querySelectorAll(".docs-directory-group")) {
      group.hidden = !group.querySelector("[data-docs-component-link]:not([hidden])");
    }
    const empty = document.querySelector("[data-docs-empty]");
    if (empty) empty.hidden = visible > 0;
  });

  document.addEventListener("keydown", (event) => {
    if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
      event.preventDefault();
      document.querySelector("[data-docs-search]")?.focus();
    }
  });
})();
