import type {
  FileTreeNode,
  PluginProjectSidebarItemProps,
  PluginDetailTabProps,
  PluginCommentAnnotationProps,
  PluginCommentContextMenuItemProps,
} from "@paperclipai/plugin-sdk/ui";
import { FileTree, usePluginAction, usePluginData } from "@paperclipai/plugin-sdk/ui";
import { useCallback, useMemo, useState, useEffect, useRef, type MouseEvent, type RefObject } from "react";
import { EditorView } from "@codemirror/view";
import { basicSetup } from "codemirror";
import { javascript } from "@codemirror/lang-javascript";
import { HighlightStyle, syntaxHighlighting } from "@codemirror/language";
import { tags } from "@lezer/highlight";

const PLUGIN_KEY = "paperclip-file-browser-example";
const FILES_TAB_SLOT_ID = "files-tab";

const editorBaseTheme = {
  "&": {
    height: "100%",
  },
  ".cm-scroller": {
    overflow: "auto",
    fontFamily:
      "ui-monospace, SFMono-Regular, SF Mono, Menlo, Monaco, Consolas, Liberation Mono, monospace",
    fontSize: "13px",
    lineHeight: "1.6",
  },
  ".cm-content": {
    padding: "12px 14px 18px",
  },
};

const editorDarkTheme = EditorView.theme({
  ...editorBaseTheme,
  "&": {
    ...editorBaseTheme["&"],
    backgroundColor: "oklch(0.23 0.02 255)",
    color: "oklch(0.93 0.01 255)",
  },
  ".cm-gutters": {
    backgroundColor: "oklch(0.25 0.015 255)",
    color: "oklch(0.74 0.015 255)",
    borderRight: "1px solid oklch(0.34 0.01 255)",
  },
  ".cm-activeLine, .cm-activeLineGutter": {
    backgroundColor: "oklch(0.30 0.012 255 / 0.55)",
  },
  ".cm-selectionBackground, .cm-content ::selection": {
    backgroundColor: "oklch(0.42 0.02 255 / 0.45)",
  },
  "&.cm-focused .cm-selectionBackground": {
    backgroundColor: "oklch(0.47 0.025 255 / 0.5)",
  },
  ".cm-cursor, .cm-dropCursor": {
    borderLeftColor: "oklch(0.93 0.01 255)",
  },
  ".cm-matchingBracket": {
    backgroundColor: "oklch(0.37 0.015 255 / 0.5)",
    color: "oklch(0.95 0.01 255)",
    outline: "none",
  },
  ".cm-nonmatchingBracket": {
    color: "oklch(0.70 0.08 24)",
  },
}, { dark: true });

const editorLightTheme = EditorView.theme({
  ...editorBaseTheme,
  "&": {
    ...editorBaseTheme["&"],
    backgroundColor: "color-mix(in oklab, var(--card) 92%, var(--background))",
    color: "var(--foreground)",
  },
  ".cm-content": {
    ...editorBaseTheme[".cm-content"],
    caretColor: "var(--foreground)",
  },
  ".cm-gutters": {
    backgroundColor: "color-mix(in oklab, var(--card) 96%, var(--background))",
    color: "var(--muted-foreground)",
    borderRight: "1px solid var(--border)",
  },
  ".cm-activeLine, .cm-activeLineGutter": {
    backgroundColor: "color-mix(in oklab, var(--accent) 52%, transparent)",
  },
  ".cm-selectionBackground, .cm-content ::selection": {
    backgroundColor: "color-mix(in oklab, var(--accent) 72%, transparent)",
  },
  "&.cm-focused .cm-selectionBackground": {
    backgroundColor: "color-mix(in oklab, var(--accent) 84%, transparent)",
  },
  ".cm-cursor, .cm-dropCursor": {
    borderLeftColor: "color-mix(in oklab, var(--foreground) 88%, transparent)",
  },
  ".cm-matchingBracket": {
    backgroundColor: "color-mix(in oklab, var(--accent) 45%, transparent)",
    color: "var(--foreground)",
    outline: "none",
  },
  ".cm-nonmatchingBracket": {
    color: "var(--destructive)",
  },
});

const editorDarkHighlightStyle = HighlightStyle.define([
  { tag: tags.keyword, color: "oklch(0.78 0.025 265)" },
  { tag: [tags.name, tags.variableName], color: "oklch(0.88 0.01 255)" },
  { tag: [tags.string, tags.special(tags.string)], color: "oklch(0.80 0.02 170)" },
  { tag: [tags.number, tags.bool, tags.null], color: "oklch(0.79 0.02 95)" },
  { tag: [tags.comment, tags.lineComment, tags.blockComment], color: "oklch(0.64 0.01 255)" },
  { tag: [tags.function(tags.variableName), tags.labelName], color: "oklch(0.84 0.018 220)" },
  { tag: [tags.typeName, tags.className], color: "oklch(0.82 0.02 245)" },
  { tag: [tags.operator, tags.punctuation], color: "oklch(0.77 0.01 255)" },
  { tag: [tags.invalid, tags.deleted], color: "oklch(0.70 0.08 24)" },
]);

const editorLightHighlightStyle = HighlightStyle.define([
  { tag: tags.keyword, color: "oklch(0.45 0.07 270)" },
  { tag: [tags.name, tags.variableName], color: "oklch(0.28 0.01 255)" },
  { tag: [tags.string, tags.special(tags.string)], color: "oklch(0.45 0.06 165)" },
  { tag: [tags.number, tags.bool, tags.null], color: "oklch(0.48 0.08 90)" },
  { tag: [tags.comment, tags.lineComment, tags.blockComment], color: "oklch(0.53 0.01 255)" },
  { tag: [tags.function(tags.variableName), tags.labelName], color: "oklch(0.42 0.07 220)" },
  { tag: [tags.typeName, tags.className], color: "oklch(0.40 0.06 245)" },
  { tag: [tags.operator, tags.punctuation], color: "oklch(0.36 0.01 255)" },
  { tag: [tags.invalid, tags.deleted], color: "oklch(0.55 0.16 24)" },
]);

type Workspace = { id: string; projectId: string; name: string; path: string; isPrimary: boolean };
type FileEntry = { name: string; path: string; isDirectory: boolean };

function entryToFileTreeNode(entry: FileEntry): FileTreeNode {
  return {
    name: entry.name,
    path: entry.path,
    kind: entry.isDirectory ? "dir" : "file",
    children: [],
  };
}

function entriesToFileTreeNodes(entries: FileEntry[]): FileTreeNode[] {
  return entries.map(entryToFileTreeNode);
}

function setChildrenAtPath(nodes: FileTreeNode[], path: string, children: FileTreeNode[]): FileTreeNode[] {
  return nodes.map((node) => {
    if (node.path === path) {
      return { ...node, children };
    }
    if (node.kind === "dir" && node.children.length > 0 && (path === node.path || path.startsWith(`${node.path}/`))) {
      return { ...node, children: setChildrenAtPath(node.children, path, children) };
    }
    return node;
  });
}

const PathLikePattern = /[\\/]/;
const WindowsDrivePathPattern = /^[A-Za-z]:[\\/]/;
const UuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;

function isLikelyPath(pathValue: string): boolean {
  const trimmed = pathValue.trim();
  return PathLikePattern.test(trimmed) || WindowsDrivePathPattern.test(trimmed);
}

function workspaceLabel(workspace: Workspace): string {
  const pathLabel = workspace.path.trim();
  const nameLabel = workspace.name.trim();
  const hasPathLabel = isLikelyPath(pathLabel) && !UuidPattern.test(pathLabel);
  const hasNameLabel = nameLabel.length > 0 && !UuidPattern.test(nameLabel);
  const baseLabel = hasPathLabel ? pathLabel : hasNameLabel ? nameLabel : "";
  if (!baseLabel) {
    return workspace.isPrimary ? "(no workspace path) (primary)" : "(no workspace path)";
  }

  return workspace.isPrimary ? `${baseLabel} (primary)` : baseLabel;
}

function useIsMobile(breakpointPx = 768): boolean {
  const [isMobile, setIsMobile] = useState(() =>
    typeof window !== "undefined" ? window.innerWidth < breakpointPx : false,
  );

  useEffect(() => {
    if (typeof window === "undefined") return;
    const mediaQuery = window.matchMedia(`(max-width: ${breakpointPx - 1}px)`);
    const update = () => setIsMobile(mediaQuery.matches);
    update();
    mediaQuery.addEventListener("change", update);
    return () => mediaQuery.removeEventListener("change", update);
  }, [breakpointPx]);

  return isMobile;
}

function useIsDarkMode(): boolean {
  const [isDarkMode, setIsDarkMode] = useState(() =>
    typeof document !== "undefined" && document.documentElement.classList.contains("dark"),
  );

  useEffect(() => {
    if (typeof document === "undefined") return;
    const root = document.documentElement;
    const update = () => setIsDarkMode(root.classList.contains("dark"));
    update();

    const observer = new MutationObserver(update);
    observer.observe(root, { attributes: true, attributeFilter: ["class"] });
    return () => observer.disconnect();
  }, []);

  return isDarkMode;
}

function useAvailableHeight(
  ref: RefObject<HTMLElement | null>,
  options?: { bottomPadding?: number; minHeight?: number },
): number | null {
  const bottomPadding = options?.bottomPadding ?? 24;
  const minHeight = options?.minHeight ?? 384;
  const [height, setHeight] = useState<number | null>(null);

  useEffect(() => {
    if (typeof window === "undefined") return;

    const update = () => {
      const element = ref.current;
      if (!element) return;
      const rect = element.getBoundingClientRect();
      const nextHeight = Math.max(minHeight, Math.floor(window.innerHeight - rect.top - bottomPadding));
      setHeight(nextHeight);
    };

    update();
    window.addEventListener("resize", update);
    window.addEventListener("orientationchange", update);

    const observer = typeof ResizeObserver !== "undefined"
      ? new ResizeObserver(() => update())
      : null;
    if (observer && ref.current) observer.observe(ref.current);

    return () => {
      window.removeEventListener("resize", update);
      window.removeEventListener("orientationchange", update);
      observer?.disconnect();
    };
  }, [bottomPadding, minHeight, ref]);

  return height;
}

/**
 * Project sidebar item: link "Files" that opens the project detail with the Files plugin tab.
 */
export function FilesLink({ context }: PluginProjectSidebarItemProps) {
  const { data: config, loading: configLoading } = usePluginData<PluginConfig>("plugin-config", {});
  const showFilesInSidebar = config?.showFilesInSidebar ?? false;

  if (configLoading || !showFilesInSidebar) {
    return null;
  }

  const projectId = context.entityId;
  const projectRef = (context as PluginProjectSidebarItemProps["context"] & { projectRef?: string | null })
    .projectRef
    ?? projectId;
  const prefix = context.companyPrefix ? `/${context.companyPrefix}` : "";
  const tabValue = `plugin:${PLUGIN_KEY}:${FILES_TAB_SLOT_ID}`;
  const href = `${prefix}/projects/${projectRef}?tab=${encodeURIComponent(tabValue)}`;
  const isActive = typeof window !== "undefined" && (() => {
    const pathname = window.location.pathname.replace(/\/+$/, "");
    const segments = pathname.split("/").filter(Boolean);
    const projectsIndex = segments.indexOf("projects");
    const activeProjectRef = projectsIndex >= 0 ? segments[projectsIndex + 1] ?? null : null;
    const activeTab = new URLSearchParams(window.location.search).get("tab");
    if (activeTab !== tabValue) return false;
    if (!activeProjectRef) return false;
    return activeProjectRef === projectId || activeProjectRef === projectRef;
  })();

  const handleClick = (event: MouseEvent<HTMLAnchorElement>) => {
    if (
      event.defaultPrevented
      || event.button !== 0
      || event.metaKey
      || event.ctrlKey
      || event.altKey
      || event.shiftKey
    ) {
      return;
    }

    event.preventDefault();
    window.history.pushState({}, "", href);
    window.dispatchEvent(new PopStateEvent("popstate"));
  };

  return (
    <a
      href={href}
      onClick={handleClick}
      aria-current={isActive ? "page" : undefined}
      className={`block px-3 py-1 text-[12px] truncate transition-colors ${
        isActive
          ? "bg-accent text-foreground font-medium"
          : "text-muted-foreground hover:text-foreground hover:bg-accent/50"
      }`}
    >
      Files
    </a>
  );
}

/**
 * Project detail tab: workspace selector, file tree, and CodeMirror editor.
 */
export function FilesTab({ context }: PluginDetailTabProps) {
  const companyId = context.companyId;
  const projectId = context.entityId;
  const isMobile = useIsMobile();
  const isDarkMode = useIsDarkMode();
  const panesRef = useRef<HTMLDivElement | null>(null);
  const availableHeight = useAvailableHeight(panesRef, {
    bottomPadding: isMobile ? 16 : 24,
    minHeight: isMobile ? 320 : 420,
  });
  const { data: workspacesData } = usePluginData<Workspace[]>("workspaces", {
    projectId,
    companyId,
  });
  const workspaces = workspacesData ?? [];
  const workspaceSelectKey = workspaces.map((w) => `${w.id}:${workspaceLabel(w)}`).join("|");
  const [workspaceId, setWorkspaceId] = useState<string | null>(null);
  const resolvedWorkspaceId = workspaceId ?? workspaces[0]?.id ?? null;
  const selectedWorkspace = useMemo(
    () => workspaces.find((w) => w.id === resolvedWorkspaceId) ?? null,
    [workspaces, resolvedWorkspaceId],
  );

  const fileListParams = useMemo(
    () => (selectedWorkspace ? { projectId, companyId, workspaceId: selectedWorkspace.id } : {}),
    [companyId, projectId, selectedWorkspace],
  );
  const { data: fileListData, loading: fileListLoading, error: fileListError } = usePluginData<{ entries: FileEntry[] }>(
    "fileList",
    fileListParams,
  );

  // Lazy-load directory children through an imperative action so the shared
  // FileTree can reuse `expandedPaths` for state without spawning a hook per
  // expanded directory.
  const loadFileList = usePluginAction("loadFileList");
  const [nodes, setNodes] = useState<FileTreeNode[]>([]);
  const [expandedPaths, setExpandedPaths] = useState<Set<string>>(() => new Set());
  const [loadedDirs, setLoadedDirs] = useState<Set<string>>(() => new Set());
  const [loadingDirs, setLoadingDirs] = useState<Set<string>>(() => new Set());

  useEffect(() => {
    setNodes(fileListData?.entries ? entriesToFileTreeNodes(fileListData.entries) : []);
    setExpandedPaths(new Set());
    setLoadedDirs(new Set());
    setLoadingDirs(new Set());
  }, [fileListData, selectedWorkspace?.id]);

  const handleToggleDir = useCallback(
    (dirPath: string) => {
      setExpandedPaths((current) => {
        const next = new Set(current);
        if (next.has(dirPath)) next.delete(dirPath);
        else next.add(dirPath);
        return next;
      });
      if (!selectedWorkspace) return;
      if (loadedDirs.has(dirPath) || loadingDirs.has(dirPath)) return;
      setLoadingDirs((current) => new Set(current).add(dirPath));
      void loadFileList({
        projectId,
        companyId,
        workspaceId: selectedWorkspace.id,
        directoryPath: dirPath,
      })
        .then((response) => {
          const entries = (response as { entries?: FileEntry[] })?.entries ?? [];
          const children = entriesToFileTreeNodes(entries);
          setNodes((current) => setChildrenAtPath(current, dirPath, children));
          setLoadedDirs((current) => new Set(current).add(dirPath));
        })
        .finally(() => {
          setLoadingDirs((current) => {
            const next = new Set(current);
            next.delete(dirPath);
            return next;
          });
        });
    },
    [companyId, loadFileList, loadedDirs, loadingDirs, projectId, selectedWorkspace],
  );

  // Track the `?file=` query parameter across navigations (popstate).
  const [urlFilePath, setUrlFilePath] = useState<string | null>(() => {
    if (typeof window === "undefined") return null;
    return new URLSearchParams(window.location.search).get("file") || null;
  });
  const lastConsumedFileRef = useRef<string | null>(null);

  useEffect(() => {
    if (typeof window === "undefined") return;
    const onNav = () => {
      const next = new URLSearchParams(window.location.search).get("file") || null;
      setUrlFilePath(next);
    };
    window.addEventListener("popstate", onNav);
    return () => window.removeEventListener("popstate", onNav);
  }, []);

  const [selectedPath, setSelectedPath] = useState<string | null>(null);
  useEffect(() => {
    setSelectedPath(null);
    setMobileView("browser");
    lastConsumedFileRef.current = null;
  }, [selectedWorkspace?.id]);

  // When a file path appears (or changes) in the URL and workspace is ready, select it.
  useEffect(() => {
    if (!urlFilePath || !selectedWorkspace) return;
    if (lastConsumedFileRef.current === urlFilePath) return;
    lastConsumedFileRef.current = urlFilePath;
    setSelectedPath(urlFilePath);
    setMobileView("editor");
  }, [urlFilePath, selectedWorkspace]);

  const fileContentParams = useMemo(
    () =>
      selectedPath && selectedWorkspace
        ? { projectId, companyId, workspaceId: selectedWorkspace.id, filePath: selectedPath }
        : null,
    [companyId, projectId, selectedWorkspace, selectedPath],
  );
  const fileContentResult = usePluginData<{ content: string | null; error?: string }>(
    "fileContent",
    fileContentParams ?? {},
  );
  const { data: fileContentData, refresh: refreshFileContent } = fileContentResult;
  const writeFile = usePluginAction("writeFile");
  const editorRef = useRef<HTMLDivElement | null>(null);
  const viewRef = useRef<EditorView | null>(null);
  const loadedContentRef = useRef("");
  const [isDirty, setIsDirty] = useState(false);
  const [isSaving, setIsSaving] = useState(false);
  const [saveMessage, setSaveMessage] = useState<string | null>(null);
  const [saveError, setSaveError] = useState<string | null>(null);
  const [mobileView, setMobileView] = useState<"browser" | "editor">("browser");

  useEffect(() => {
    if (!editorRef.current) return;
    const content = fileContentData?.content ?? "";
    loadedContentRef.current = content;
    setIsDirty(false);
    setSaveMessage(null);
    setSaveError(null);
    if (viewRef.current) {
      viewRef.current.destroy();
      viewRef.current = null;
    }
    const view = new EditorView({
      doc: content,
      extensions: [
        basicSetup,
        javascript(),
        isDarkMode ? editorDarkTheme : editorLightTheme,
        syntaxHighlighting(isDarkMode ? editorDarkHighlightStyle : editorLightHighlightStyle),
        EditorView.updateListener.of((update) => {
          if (!update.docChanged) return;
          const nextValue = update.state.doc.toString();
          setIsDirty(nextValue !== loadedContentRef.current);
          setSaveMessage(null);
          setSaveError(null);
        }),
      ],
      parent: editorRef.current,
    });
    viewRef.current = view;
    return () => {
      view.destroy();
      viewRef.current = null;
    };
  }, [fileContentData?.content, selectedPath, isDarkMode]);

  useEffect(() => {
    const handleKeydown = (event: KeyboardEvent) => {
      if (!(event.metaKey || event.ctrlKey) || event.key.toLowerCase() !== "s") {
        return;
      }
      if (!selectedWorkspace || !selectedPath || !isDirty || isSaving) {
        return;
      }
      event.preventDefault();
      void handleSave();
    };

    window.addEventListener("keydown", handleKeydown);
    return () => window.removeEventListener("keydown", handleKeydown);
  }, [selectedWorkspace, selectedPath, isDirty, isSaving]);

  async function handleSave() {
    if (!selectedWorkspace || !selectedPath || !viewRef.current) {
      return;
    }
    const content = viewRef.current.state.doc.toString();
    setIsSaving(true);
    setSaveError(null);
    setSaveMessage(null);
    try {
      await writeFile({
        projectId,
        companyId,
        workspaceId: selectedWorkspace.id,
        filePath: selectedPath,
        content,
      });
      loadedContentRef.current = content;
      setIsDirty(false);
      setSaveMessage("Saved");
      refreshFileContent();
    } catch (error) {
      setSaveError(error instanceof Error ? error.message : String(error));
    } finally {
      setIsSaving(false);
    }
  }

  return (
    <div className="space-y-4">
      <div className="rounded-lg border border-border bg-card p-4">
        <label className="text-sm font-medium text-muted-foreground">Workspace</label>
        <select
          key={workspaceSelectKey}
          className="mt-2 block w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
          value={resolvedWorkspaceId ?? ""}
          onChange={(e) => setWorkspaceId(e.target.value || null)}
        >
          {workspaces.map((w) => {
            const label = workspaceLabel(w);
            return (
              <option key={`${w.id}:${label}`} value={w.id} label={label} title={label}>
                {label}
              </option>
            );
          })}
        </select>
      </div>

      <div
        ref={panesRef}
        className="min-h-0"
        style={{
          display: isMobile ? "block" : "grid",
          gap: "1rem",
          gridTemplateColumns: isMobile ? undefined : "320px minmax(0, 1fr)",
          height: availableHeight ? `${availableHeight}px` : undefined,
          minHeight: isMobile ? "20rem" : "26rem",
        }}
      >
        <div
          className="flex min-h-0 flex-col overflow-hidden rounded-lg border border-border bg-card"
          style={{ display: isMobile && mobileView === "editor" ? "none" : "flex" }}
        >
          <div className="border-b border-border px-3 py-2 text-xs font-medium uppercase tracking-[0.12em] text-muted-foreground">
            File Tree
          </div>
          <div className="min-h-0 flex-1 overflow-auto p-2">
            {selectedWorkspace ? (
              <FileTree
                nodes={nodes}
                selectedFile={selectedPath}
                expandedPaths={expandedPaths}
                onToggleDir={handleToggleDir}
                onSelectFile={(path: string) => {
                  setSelectedPath(path);
                  setMobileView("editor");
                }}
                loading={fileListLoading}
                error={fileListError ? { message: fileListError.message } : null}
                empty={{
                  title: "No files",
                  description: "No files found in this workspace.",
                }}
                ariaLabel="Workspace files"
              />
            ) : (
              <p className="px-2 py-3 text-sm text-muted-foreground">Select a workspace to browse files.</p>
            )}
          </div>
        </div>
        <div
          className="flex min-h-0 flex-col overflow-hidden rounded-lg border border-border bg-card"
          style={{ display: isMobile && mobileView === "browser" ? "none" : "flex" }}
        >
          <div className="sticky top-0 z-10 flex items-center justify-between gap-3 border-b border-border bg-card px-4 py-2">
            <div className="min-w-0">
              <button
                type="button"
                className="mb-2 inline-flex rounded-md border border-input bg-background px-2 py-1 text-xs font-medium text-muted-foreground"
                style={{ display: isMobile ? "inline-flex" : "none" }}
                onClick={() => setMobileView("browser")}
              >
                Back to files
              </button>
              <div className="text-xs font-medium uppercase tracking-[0.12em] text-muted-foreground">Editor</div>
              <div className="truncate text-sm text-foreground">{selectedPath ?? "No file selected"}</div>
            </div>
            <div className="flex items-center gap-3">
              <button
                type="button"
                className="rounded-md border border-input bg-background px-3 py-1.5 text-sm font-medium text-foreground transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50"
                disabled={!selectedWorkspace || !selectedPath || !isDirty || isSaving}
                onClick={() => void handleSave()}
              >
                {isSaving ? "Saving..." : "Save"}
              </button>
            </div>
          </div>
          {isDirty || saveMessage || saveError ? (
            <div className="border-b border-border px-4 py-2 text-xs">
              {saveError ? (
                <span className="text-destructive">{saveError}</span>
              ) : saveMessage ? (
                <span className="text-emerald-600">{saveMessage}</span>
              ) : (
                <span className="text-muted-foreground">Unsaved changes</span>
              )}
            </div>
          ) : null}
          {selectedPath && fileContentData?.error && fileContentData.error !== "Missing file context" ? (
            <div className="border-b border-border px-4 py-2 text-xs text-destructive">{fileContentData.error}</div>
          ) : null}
          <div ref={editorRef} className="min-h-0 flex-1 overflow-auto overscroll-contain" />
        </div>
      </div>
    </div>
  );
}

// ---------------------------------------------------------------------------
// Comment Annotation: renders detected file links below each comment
// ---------------------------------------------------------------------------

type PluginConfig = {
  showFilesInSidebar?: boolean;
  commentAnnotationMode: "annotation" | "contextMenu" | "both" | "none";
};

/**
 * Per-comment annotation showing file-path-like links extracted from the
 * comment body. Each link navigates to the project Files tab with the
 * matching path pre-selected.
 *
 * Respects the `commentAnnotationMode` instance config — hidden when mode
 * is `"contextMenu"` or `"none"`.
 */
function buildFileBrowserHref(prefix: string, projectId: string | null, filePath: string): string {
  if (!projectId) return "#";
  const tabValue = `plugin:${PLUGIN_KEY}:${FILES_TAB_SLOT_ID}`;
  return `${prefix}/projects/${projectId}?tab=${encodeURIComponent(tabValue)}&file=${encodeURIComponent(filePath)}`;
}

function navigateToFileBrowser(href: string, event: MouseEvent<HTMLAnchorElement>) {
  if (
    event.defaultPrevented
    || event.button !== 0
    || event.metaKey
    || event.ctrlKey
    || event.altKey
    || event.shiftKey
  ) {
    return;
  }
  event.preventDefault();
  window.history.pushState({}, "", href);
  window.dispatchEvent(new PopStateEvent("popstate"));
}

export function CommentFileLinks({ context }: PluginCommentAnnotationProps) {
  const { data: config } = usePluginData<PluginConfig>("plugin-config", {});
  const mode = config?.commentAnnotationMode ?? "both";

  const { data } = usePluginData<{ links: string[] }>("comment-file-links", {
    commentId: context.entityId,
    issueId: context.parentEntityId,
    companyId: context.companyId,
  });

  if (mode === "contextMenu" || mode === "none") return null;
  if (!data?.links?.length) return null;

  const prefix = context.companyPrefix ? `/${context.companyPrefix}` : "";
  const projectId = context.projectId;

  return (
    <div className="flex flex-wrap items-center gap-1.5">
      <span className="text-[10px] font-medium uppercase tracking-wider text-muted-foreground">Files:</span>
      {data.links.map((link) => {
        const href = buildFileBrowserHref(prefix, projectId, link);
        return (
          <a
            key={link}
            href={href}
            onClick={(e) => navigateToFileBrowser(href, e)}
            className="inline-flex items-center rounded-md border border-border bg-accent/30 px-1.5 py-0.5 text-xs font-mono text-primary hover:bg-accent/60 hover:underline transition-colors"
            title={`Open ${link} in file browser`}
          >
            {link}
          </a>
        );
      })}
    </div>
  );
}

// ---------------------------------------------------------------------------
// Comment Context Menu Item: "Open in Files" action per comment
// ---------------------------------------------------------------------------

/**
 * Per-comment context menu item that appears in the comment "more" (⋮) menu.
 * Extracts file paths from the comment body and, if any are found, renders
 * a button to open the first file in the project Files tab.
 *
 * Respects the `commentAnnotationMode` instance config — hidden when mode
 * is `"annotation"` or `"none"`.
 */
export function CommentOpenFiles({ context }: PluginCommentContextMenuItemProps) {
  const { data: config } = usePluginData<PluginConfig>("plugin-config", {});
  const mode = config?.commentAnnotationMode ?? "both";

  const { data } = usePluginData<{ links: string[] }>("comment-file-links", {
    commentId: context.entityId,
    issueId: context.parentEntityId,
    companyId: context.companyId,
  });

  if (mode === "annotation" || mode === "none") return null;
  if (!data?.links?.length) return null;

  const prefix = context.companyPrefix ? `/${context.companyPrefix}` : "";
  const projectId = context.projectId;

  return (
    <div>
      <div className="px-2 py-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
        Files
      </div>
      {data.links.map((link) => {
        const href = buildFileBrowserHref(prefix, projectId, link);
        const fileName = link.split("/").pop() ?? link;
        return (
          <a
            key={link}
            href={href}
            onClick={(e) => navigateToFileBrowser(href, e)}
            className="flex w-full items-center gap-2 rounded px-2 py-1 text-xs text-foreground hover:bg-accent transition-colors"
            title={`Open ${link} in file browser`}
          >
            <span className="truncate font-mono">{fileName}</span>
          </a>
        );
      })}
    </div>
  );
}
