import { useState } from 'react';
import { useSearchParams } from 'react-router';

export type OpenFile = {
  path: string;
  name: string;
  content: string;
  originalContent: string;
  isDirty: boolean;
};

/**
 * Which file the editor is showing, when it is the editor that owns the address bar. Opt-in for the same
 * reason `?path=` is in the file browser: this app also mounts as a dashboard panel, and a dashboard can
 * hold two of them, which one shared param would drive in lockstep.
 */
export const EDITOR_FILE_PARAM = 'file';

/**
 * The *set* of open tabs is deliberately not in the URL. It is a working session, not an address — it
 * grows without bound, every entry costs a read on load, and nobody links someone else to a tab bar.
 * `file` is the selection, and a link to one opens it; the tabs accumulate around it as you browse.
 */
export const useEditorState = (urlState = false) => {
  const [files, setFiles] = useState<OpenFile[]>([]);
  const [localActive, setLocalActive] = useState<string | null>(null);
  const [searchParams, setSearchParams] = useSearchParams();

  const activePath = urlState ? searchParams.get(EDITOR_FILE_PARAM) : localActive;

  /** `replace` for a close — shutting a tab is a mutation, not somewhere you navigated to. */
  const setActivePath = (path: string | null, replace = false) => {
    if (path === activePath) return;
    if (!urlState) {
      setLocalActive(path);
      return;
    }
    setSearchParams(
      (prev) => {
        const next = new URLSearchParams(prev);
        if (path) next.set(EDITOR_FILE_PARAM, path);
        else next.delete(EDITOR_FILE_PARAM);
        return next;
      },
      { replace },
    );
  };

  /** Query string for a file, so a tree row and a tab can be real links. */
  const searchForFile = (path: string) => {
    const next = new URLSearchParams(searchParams);
    next.set(EDITOR_FILE_PARAM, path);
    return next.toString();
  };

  const openFile = (path: string, name: string, content: string) => {
    setFiles((prev) => {
      if (prev.some((f) => f.path === path)) return prev;
      return [...prev, { path, name, content, originalContent: content, isDirty: false }];
    });
    setActivePath(path);
  };

  // The next-active pick is computed here rather than inside the updater it used to sit in: a state
  // updater must be pure, and React invokes it twice in development precisely to catch this.
  const closeFile = (path: string) => {
    const idx = files.findIndex((f) => f.path === path);
    const next = files.filter((f) => f.path !== path);
    setFiles(next);
    if (activePath === path) setActivePath(next[Math.min(idx, next.length - 1)]?.path ?? null, true);
  };

  const setContent = (path: string, content: string) => {
    setFiles((prev) =>
      prev.map((f) => (f.path === path ? { ...f, content, isDirty: content !== f.originalContent } : f)),
    );
  };

  const markSaved = (path: string, content: string) => {
    setFiles((prev) =>
      prev.map((f) => (f.path === path ? { ...f, originalContent: content, content, isDirty: false } : f)),
    );
  };

  const getActiveFile = (): OpenFile | null => files.find((f) => f.path === activePath) ?? null;

  return {
    files,
    activePath,
    setActivePath,
    openFile,
    closeFile,
    setContent,
    markSaved,
    getActiveFile,
    // Null when this editor does not own the address bar — rows and tabs then stay buttons.
    searchForFile: urlState ? searchForFile : null,
  };
};
