import { FileDiff, preloadHighlighter } from "@pierre/diffs";
import type {
  FileContents,
  FileDiffMetadata,
  FileDiffOptions,
  SupportedLanguages,
} from "@pierre/diffs";
import type { DiffViewerPayload, DiffLayout, DiffTheme } from "./types.js";
import { parseViewerPayloadJson } from "./viewer-payload.js";

type ViewerState = {
  theme: DiffTheme;
  layout: DiffLayout;
  backgroundEnabled: boolean;
  wrapEnabled: boolean;
};

type DiffController = {
  payload: DiffViewerPayload;
  diff: FileDiff;
};

const controllers: DiffController[] = [];

const viewerState: ViewerState = {
  theme: "dark",
  layout: "unified",
  backgroundEnabled: true,
  wrapEnabled: true,
};

function parsePayload(element: HTMLScriptElement): DiffViewerPayload {
  const raw = element.textContent?.trim();
  if (!raw) {
    throw new Error("Diff payload was empty.");
  }
  return parseViewerPayloadJson(raw);
}

function getCards(): Array<{ host: HTMLElement; payload: DiffViewerPayload }> {
  const cards: Array<{ host: HTMLElement; payload: DiffViewerPayload }> = [];
  for (const card of document.querySelectorAll<HTMLElement>(".oc-diff-card")) {
    const host = card.querySelector<HTMLElement>("[data-openclaw-diff-host]");
    const payloadNode = card.querySelector<HTMLScriptElement>("[data-openclaw-diff-payload]");
    if (!host || !payloadNode) {
      continue;
    }

    try {
      cards.push({ host, payload: parsePayload(payloadNode) });
    } catch (error) {
      console.warn("Skipping invalid diff payload", error);
    }
  }
  return cards;
}

function ensureShadowRoot(host: HTMLElement): void {
  if (host.shadowRoot) {
    return;
  }
  const template = host.querySelector<HTMLTemplateElement>(
    ":scope > template[shadowrootmode='open']",
  );
  if (!template) {
    return;
  }
  const shadowRoot = host.attachShadow({ mode: "open" });
  shadowRoot.append(template.content.cloneNode(true));
  template.remove();
}

function getHydrateProps(payload: DiffViewerPayload): {
  fileDiff?: FileDiffMetadata;
  oldFile?: FileContents;
  newFile?: FileContents;
} {
  if (payload.fileDiff) {
    return { fileDiff: payload.fileDiff };
  }
  return {
    oldFile: payload.oldFile,
    newFile: payload.newFile,
  };
}

function createToolbarButton(params: {
  title: string;
  active: boolean;
  iconMarkup: string;
  onClick: () => void;
}): HTMLButtonElement {
  const button = document.createElement("button");
  button.type = "button";
  button.className = "oc-diff-toolbar-button";
  button.dataset.active = String(params.active);
  button.title = params.title;
  button.setAttribute("aria-label", params.title);
  button.innerHTML = params.iconMarkup;
  applyToolbarButtonStyles(button, params.active);
  button.addEventListener("click", (event) => {
    event.preventDefault();
    params.onClick();
  });
  return button;
}

function applyToolbarButtonStyles(button: HTMLButtonElement, active: boolean): void {
  button.style.color =
    viewerState.theme === "dark" ? "rgba(226, 232, 240, 0.74)" : "rgba(15, 23, 42, 0.52)";
  button.dataset.active = String(active);
}

function splitIcon(): string {
  return `<svg viewBox="0 0 16 16" aria-hidden="true">
    <path fill="currentColor" d="M14 0H8.5v16H14a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2m-1.5 6.5v1h1a.5.5 0 0 1 0 1h-1v1a.5.5 0 0 1-1 0v-1h-1a.5.5 0 0 1 0-1h1v-1a.5.5 0 0 1 1 0"></path>
    <path fill="currentColor" opacity="0.5" d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h5.5V0zm.5 7.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1 0-1"></path>
  </svg>`;
}

function unifiedIcon(): string {
  return `<svg viewBox="0 0 16 16" aria-hidden="true">
    <path fill="currentColor" fill-rule="evenodd" d="M16 14a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V8.5h16zm-8-4a.5.5 0 0 0-.5.5v1h-1a.5.5 0 0 0 0 1h1v1a.5.5 0 0 0 1 0v-1h1a.5.5 0 0 0 0-1h-1v-1A.5.5 0 0 0 8 10" clip-rule="evenodd"></path>
    <path fill="currentColor" fill-rule="evenodd" opacity="0.5" d="M14 0a2 2 0 0 1 2 2v5.5H0V2a2 2 0 0 1 2-2zM6.5 3.5a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1z" clip-rule="evenodd"></path>
  </svg>`;
}

function wrapIcon(active: boolean): string {
  return `<svg viewBox="0 0 16 16" aria-hidden="true">
    <path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" opacity="${active ? "1" : "0.85"}" d="M3.868 3.449a1.21 1.21 0 0 0-.473-.329c-.274-.111-.623-.15-1.055-.076a3.5 3.5 0 0 0-.71.208c-.082.035-.16.077-.235.125l-.043.03v1.056l.168-.139c.15-.124.326-.225.527-.303.196-.074.4-.113.604-.113.188 0 .33.051.431.157.087.095.137.248.147.456l-.962.144c-.219.03-.41.086-.57.166a1.245 1.245 0 0 0-.398.311c-.103.125-.181.27-.229.426-.097.33-.093.68.011 1.008a1.096 1.096 0 0 0 .638.67c.155.063.328.093.528.093a1.25 1.25 0 0 0 .978-.441v.345h1.007V4.65c0-.255-.03-.484-.089-.681a1.423 1.423 0 0 0-.275-.52zm-.636 1.896v.236c0 .119-.018.231-.055.341a.745.745 0 0 1-.377.447.694.694 0 0 1-.512.027.454.454 0 0 1-.156-.094.389.389 0 0 1-.094-.139.474.474 0 0 1-.035-.186c0-.077.01-.147.024-.212a.33.33 0 0 1 .078-.141.436.436 0 0 1 .161-.109 1.3 1.3 0 0 1 .305-.073l.661-.097zm5.051-1.067a2.253 2.253 0 0 0-.244-.656 1.354 1.354 0 0 0-.436-.459 1.165 1.165 0 0 0-.642-.173 1.136 1.136 0 0 0-.69.223 1.33 1.33 0 0 0-.264.266V1H5.09v6.224h.918v-.281c.123.152.287.266.472.328.098.032.208.047.33.047.255 0 .483-.06.677-.177.192-.115.355-.278.486-.486a2.29 2.29 0 0 0 .293-.718 3.87 3.87 0 0 0 .096-.886 3.714 3.714 0 0 0-.078-.773zm-.86.758c0 .232-.02.439-.06.613-.036.172-.09.315-.159.424a.639.639 0 0 1-.233.237.582.582 0 0 1-.565.014.683.683 0 0 1-.21-.183.925.925 0 0 1-.142-.283A1.187 1.187 0 0 1 6 5.5v-.517c0-.164.02-.314.06-.447.036-.132.087-.242.156-.336a.668.668 0 0 1 .228-.208.584.584 0 0 1 .29-.071.554.554 0 0 1 .496.279c.063.099.108.214.143.354.031.143.05.306.05.482zM2.407 9.9a.913.913 0 0 1 .316-.239c.218-.1.547-.105.766-.018.104.042.204.1.32.184l.33.26V8.945l-.097-.062a1.932 1.932 0 0 0-.905-.215c-.308 0-.593.057-.846.168-.25.11-.467.27-.647.475-.18.21-.318.453-.403.717-.09.272-.137.57-.137.895 0 .289.043.561.13.808.086.249.211.471.373.652.161.185.361.333.597.441.232.104.493.155.778.155.233 0 .434-.028.613-.084.165-.05.322-.123.466-.217l.078-.061v-.889l-.2.095a.4.4 0 0 1-.076.026c-.05.017-.099.035-.128.049-.036.023-.227.09-.227.09-.06.024-.14.043-.218.059a.977.977 0 0 1-.599-.057.827.827 0 0 1-.306-.225 1.088 1.088 0 0 1-.205-.376 1.728 1.728 0 0 1-.076-.529c0-.21.028-.399.083-.56.054-.158.13-.294.22-.4zM14 6h-4V5h4.5l.5.5v6l-.5.5H7.879l2.07 2.071-.706.707-2.89-2.889v-.707l2.89-2.89L9.95 9l-2 2H14V6z"></path>
  </svg>`;
}

function backgroundIcon(active: boolean): string {
  if (active) {
    return `<svg viewBox="0 0 16 16" aria-hidden="true">
      <path fill="currentColor" opacity="0.5" d="M0 2.25a.75.75 0 0 1 .75-.75h10.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 2.25"></path>
      <path fill="currentColor" fill-rule="evenodd" d="M15 5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1zM2.5 9a.5.5 0 0 0 0 1h8a.5.5 0 0 0 0-1zm0-2a.5.5 0 0 0 0 1h11a.5.5 0 0 0 0-1z" clip-rule="evenodd"></path>
      <path fill="currentColor" opacity="0.5" d="M0 14.75A.75.75 0 0 1 .75 14h5.5a.75.75 0 0 1 0 1.5H.75a.75.75 0 0 1-.75-.75"></path>
    </svg>`;
  }
  return `<svg viewBox="0 0 16 16" aria-hidden="true">
    <path fill="currentColor" opacity="0.34" d="M0 2.25a.75.75 0 0 1 .75-.75h10.5a.75.75 0 0 1 0 1.5H.75A.75.75 0 0 1 0 2.25"></path>
    <path fill="currentColor" opacity="0.34" fill-rule="evenodd" d="M15 5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1zM2.5 9a.5.5 0 0 0 0 1h8a.5.5 0 0 0 0-1zm0-2a.5.5 0 0 0 0 1h11a.5.5 0 0 0 0-1z" clip-rule="evenodd"></path>
    <path fill="currentColor" opacity="0.34" d="M0 14.75A.75.75 0 0 1 .75 14h5.5a.75.75 0 0 1 0 1.5H.75a.75.75 0 0 1-.75-.75"></path>
    <path d="M2.5 13.5 13.5 2.5" stroke="currentColor" stroke-width="1.35" stroke-linecap="round"></path>
  </svg>`;
}

function themeIcon(theme: DiffTheme): string {
  if (theme === "dark") {
    return `<svg viewBox="0 0 16 16" aria-hidden="true">
      <path fill="currentColor" d="M10.794 3.647a.217.217 0 0 1 .412 0l.387 1.162c.173.518.58.923 1.097 1.096l1.162.388a.217.217 0 0 1 0 .412l-1.162.386a1.73 1.73 0 0 0-1.097 1.097l-.387 1.162a.217.217 0 0 1-.412 0l-.387-1.162A1.74 1.74 0 0 0 9.31 7.092l-1.162-.386a.217.217 0 0 1 0-.412l1.162-.388a1.73 1.73 0 0 0 1.097-1.096zM13.863.598a.144.144 0 0 1 .221-.071.14.14 0 0 1 .053.07l.258.775c.115.345.386.616.732.731l.774.258a.145.145 0 0 1 0 .274l-.774.259a1.16 1.16 0 0 0-.732.732l-.258.773a.145.145 0 0 1-.274 0l-.258-.773a1.16 1.16 0 0 0-.732-.732l-.774-.259a.145.145 0 0 1 0-.273l.774-.259c.346-.115.617-.386.732-.732z"></path>
      <path fill="currentColor" d="M6.25 1.742a.67.67 0 0 1 .07.75 6.3 6.3 0 0 0-.768 3.028c0 2.746 1.746 5.084 4.193 5.979H1.774A7.2 7.2 0 0 1 1 8.245c0-3.013 1.85-5.598 4.484-6.694a.66.66 0 0 1 .766.19M.75 12.499a.75.75 0 0 0 0 1.5h14.5a.75.75 0 0 0 0-1.5z"></path>
    </svg>`;
  }
  return `<svg viewBox="0 0 16 16" aria-hidden="true">
    <path fill="currentColor" d="M8.21 2.109a.256.256 0 0 0-.42 0L6.534 3.893a.256.256 0 0 1-.316.085l-1.982-.917a.256.256 0 0 0-.362.21l-.196 2.174a.256.256 0 0 1-.232.232l-2.175.196a.256.256 0 0 0-.209.362l.917 1.982a.256.256 0 0 1-.085.316L.11 9.791a.256.256 0 0 0 0 .418L1.23 11H3.1a5 5 0 1 1 9.8 0h1.869l1.123-.79a.256.256 0 0 0 0-.42l-1.785-1.257a.256.256 0 0 1-.085-.316l.917-1.982a.256.256 0 0 0-.21-.362l-2.174-.196a.256.256 0 0 1-.232-.232l-.196-2.175a.256.256 0 0 0-.362-.209l-1.982.917a.256.256 0 0 1-.316-.085z"></path>
    <path fill="currentColor" d="M4 10q.001.519.126 1h7.748A4 4 0 1 0 4 10M.75 12a.75.75 0 0 0 0 1.5h14.5a.75.75 0 0 0 0-1.5z"></path>
  </svg>`;
}

function createToolbar(): HTMLElement {
  const toolbar = document.createElement("div");
  toolbar.className = "oc-diff-toolbar";

  toolbar.append(
    createToolbarButton({
      title: viewerState.layout === "unified" ? "Switch to split diff" : "Switch to unified diff",
      active: viewerState.layout === "split",
      iconMarkup: viewerState.layout === "split" ? splitIcon() : unifiedIcon(),
      onClick: () => {
        viewerState.layout = viewerState.layout === "unified" ? "split" : "unified";
        syncAllControllers();
      },
    }),
  );

  toolbar.append(
    createToolbarButton({
      title: viewerState.wrapEnabled ? "Disable word wrap" : "Enable word wrap",
      active: viewerState.wrapEnabled,
      iconMarkup: wrapIcon(viewerState.wrapEnabled),
      onClick: () => {
        viewerState.wrapEnabled = !viewerState.wrapEnabled;
        syncAllControllers();
      },
    }),
  );

  toolbar.append(
    createToolbarButton({
      title: viewerState.backgroundEnabled
        ? "Hide background highlights"
        : "Show background highlights",
      active: viewerState.backgroundEnabled,
      iconMarkup: backgroundIcon(viewerState.backgroundEnabled),
      onClick: () => {
        viewerState.backgroundEnabled = !viewerState.backgroundEnabled;
        syncAllControllers();
      },
    }),
  );

  toolbar.append(
    createToolbarButton({
      title: viewerState.theme === "dark" ? "Switch to light theme" : "Switch to dark theme",
      active: viewerState.theme === "dark",
      iconMarkup: themeIcon(viewerState.theme),
      onClick: () => {
        viewerState.theme = viewerState.theme === "dark" ? "light" : "dark";
        syncAllControllers();
      },
    }),
  );

  return toolbar;
}

function createRenderOptions(payload: DiffViewerPayload): FileDiffOptions<undefined> {
  return {
    theme: payload.options.theme,
    themeType: viewerState.theme,
    diffStyle: viewerState.layout,
    diffIndicators: payload.options.diffIndicators,
    expandUnchanged: payload.options.expandUnchanged,
    overflow: viewerState.wrapEnabled ? "wrap" : "scroll",
    disableLineNumbers: payload.options.disableLineNumbers,
    disableBackground: !viewerState.backgroundEnabled,
    unsafeCSS: payload.options.unsafeCSS,
    renderHeaderMetadata: () => createToolbar(),
  };
}

function syncDocumentTheme(): void {
  document.body.dataset.theme = viewerState.theme;
}

function applyState(controller: DiffController): void {
  controller.diff.setOptions(createRenderOptions(controller.payload));
  controller.diff.rerender();
}

function syncAllControllers(): void {
  syncDocumentTheme();
  for (const controller of controllers) {
    applyState(controller);
  }
}

async function hydrateViewer(): Promise<void> {
  const cards = getCards();
  const langs = new Set<SupportedLanguages>();
  const firstPayload = cards[0]?.payload;

  if (firstPayload) {
    viewerState.theme = firstPayload.options.themeType;
    viewerState.layout = firstPayload.options.diffStyle;
    viewerState.backgroundEnabled = firstPayload.options.backgroundEnabled;
    viewerState.wrapEnabled = firstPayload.options.overflow === "wrap";
  }

  for (const { payload } of cards) {
    for (const lang of payload.langs) {
      langs.add(lang);
    }
  }

  await preloadHighlighter({
    themes: ["pierre-light", "pierre-dark"],
    langs: langs.size > 0 ? [...langs] : ["text"],
  });

  syncDocumentTheme();

  for (const { host, payload } of cards) {
    ensureShadowRoot(host);
    const diff = new FileDiff(createRenderOptions(payload));
    diff.hydrate({
      fileContainer: host,
      prerenderedHTML: payload.prerenderedHTML,
      ...getHydrateProps(payload),
    });
    const controller = { payload, diff };
    controllers.push(controller);
    applyState(controller);
  }
}

async function main(): Promise<void> {
  try {
    await hydrateViewer();
    document.documentElement.dataset.openclawDiffsReady = "true";
  } catch (error) {
    document.documentElement.dataset.openclawDiffsError = "true";
    console.error("Failed to hydrate diff viewer", error);
  }
}

if (document.readyState === "loading") {
  document.addEventListener("DOMContentLoaded", () => {
    void main();
  });
} else {
  void main();
}
