import { useState, useEffect, useCallback, useRef } from "preact/hooks";
import { marked } from "marked";

interface Entry {
  name: string;
  type: "file" | "dir";
}

interface FileData {
  name: string;
  content: string;
  format: "md" | "html" | "txt";
  checkpoint: string | null;
}

type DiffLine = { type: "add" | "del" | "ctx"; oldLn: number | null; newLn: number | null; text: string };

function computeDiff(oldText: string, newText: string): DiffLine[] {
  const oldLines = oldText.split("\n");
  const newLines = newText.split("\n");
  const m = oldLines.length, n = newLines.length;
  const dp: number[][] = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
  for (let i = 1; i <= m; i++)
    for (let j = 1; j <= n; j++)
      dp[i][j] = oldLines[i - 1] === newLines[j - 1] ? dp[i - 1][j - 1] + 1 : Math.max(dp[i - 1][j], dp[i][j - 1]);
  const result: DiffLine[] = [];
  let i = m, j = n;
  while (i > 0 || j > 0) {
    if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) {
      result.push({ type: "ctx", oldLn: i, newLn: j, text: oldLines[i - 1] });
      i--; j--;
    } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
      result.push({ type: "add", oldLn: null, newLn: j, text: newLines[j - 1] });
      j--;
    } else {
      result.push({ type: "del", oldLn: i, newLn: null, text: oldLines[i - 1] });
      i--;
    }
  }
  return result.reverse();
}

export default function Notebook() {
  const [path, setPath] = useState("");
  const [entries, setEntries] = useState<Entry[]>([]);
  const [file, setFile] = useState<FileData | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");
  const [editing, setEditing] = useState(false);
  const [editContent, setEditContent] = useState("");
  const [saving, setSaving] = useState(false);
  const [checkpoint, setCheckpoint] = useState<string | null>(null);
  const [creating, setCreating] = useState<"file" | "folder" | null>(null);
  const [newName, setNewName] = useState("");
  const nameInputRef = useRef<HTMLInputElement>(null);
  const editorRef = useRef<HTMLTextAreaElement>(null);

  const load = useCallback(async (p: string, pushState = true) => {
    setLoading(true);
    setError("");
    setFile(null);
    setEditing(false);
    setCreating(null);
    setCheckpoint(null);
    try {
      const res = await fetch(`/api/notebook?path=${encodeURIComponent(p)}`);
      if (!res.ok) throw new Error(await res.text());
      const data = await res.json();
      if (data.type === "dir") {
        setEntries(data.entries);
        setFile(null);
      } else {
        setFile(data);
        setCheckpoint(data.checkpoint ?? null);
        setEntries([]);
      }
      setPath(p);
      if (pushState) {
        const url = p ? `/notebook?path=${encodeURIComponent(p)}` : "/notebook";
        history.pushState({ path: p }, "", url);
      }
    } catch (e: any) {
      setError(e.message || "Failed to load");
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const initial = params.get("path") || "";
    load(initial, false);

    const onPop = (e: PopStateEvent) => {
      const p = e.state?.path ?? "";
      load(p, false);
    };
    window.addEventListener("popstate", onPop);
    return () => window.removeEventListener("popstate", onPop);
  }, []);

  useEffect(() => {
    if (creating && nameInputRef.current) nameInputRef.current.focus();
  }, [creating]);

  useEffect(() => {
    if (editing && editorRef.current) editorRef.current.focus();
  }, [editing]);

  const segments = path ? path.split("/") : [];

  const navigate = (name: string) => {
    const next = path ? `${path}/${name}` : name;
    load(next);
  };

  const goUp = () => {
    const parent = segments.slice(0, -1).join("/");
    load(parent);
  };

  const goTo = (index: number) => {
    const target = segments.slice(0, index + 1).join("/");
    load(target);
  };

  const startEdit = () => {
    if (!file) return;
    setEditContent(file.content);
    setEditing(true);
  };

  const cancelEdit = () => {
    setEditing(false);
  };

  const saveFile = async () => {
    setSaving(true);
    try {
      const res = await fetch("/api/notebook", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ action: "save", path, content: editContent }),
      });
      if (!res.ok) throw new Error(await res.text());
      setFile({ ...file!, content: editContent });
      setEditing(false);
    } catch (e: any) {
      setError(e.message || "Failed to save");
    } finally {
      setSaving(false);
    }
  };

  const handleCreate = async () => {
    const name = newName.trim();
    if (!name) return;

    const fullName = creating === "file" && !name.includes(".") ? `${name}.md` : name;
    const fullPath = path ? `${path}/${fullName}` : fullName;

    setSaving(true);
    try {
      const res = await fetch("/api/notebook", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          action: creating === "folder" ? "mkdir" : "create",
          path: fullPath,
          content: creating === "file" ? `# ${name}\n\n` : undefined,
        }),
      });
      if (!res.ok) {
        const data = await res.json();
        throw new Error(data.error || "Failed to create");
      }
      setCreating(null);
      setNewName("");
      if (creating === "file") {
        load(fullPath);
      } else {
        load(path);
      }
    } catch (e: any) {
      setError(e.message || "Failed to create");
    } finally {
      setSaving(false);
    }
  };

  const deleteItem = async (deletePath: string, name: string) => {
    if (!confirm(`Delete "${name}"?`)) return;
    try {
      const res = await fetch("/api/notebook", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ action: "delete", path: deletePath }),
      });
      if (!res.ok) throw new Error(await res.text());
      // If deleting current file, go back to parent
      if (deletePath === path) {
        goUp();
      } else {
        load(path);
      }
    } catch (e: any) {
      setError(e.message || "Failed to delete");
    }
  };

  const hasPendingChanges = checkpoint !== null && checkpoint !== file?.content;

  const approveCheckpoint = async () => {
    setSaving(true);
    try {
      const res = await fetch("/api/notebook", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ action: "approve", path }),
      });
      if (!res.ok) throw new Error(await res.text());
      setCheckpoint(file!.content);
    } catch (e: any) {
      setError(e.message || "Failed to approve");
    } finally {
      setSaving(false);
    }
  };

  const discardChanges = async () => {
    setSaving(true);
    try {
      const res = await fetch("/api/notebook", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ action: "discard", path }),
      });
      if (!res.ok) throw new Error(await res.text());
      const data = await res.json();
      setFile({ ...file!, content: data.content });
      setCheckpoint(data.content);
    } catch (e: any) {
      setError(e.message || "Failed to discard");
    } finally {
      setSaving(false);
    }
  };

  const renderDiff = () => {
    if (!file || !checkpoint) return null;
    const lines = computeDiff(checkpoint, file.content);
    return (
      <div class="nb-diff">
        {lines.map((line, i) => (
          <div key={i} class={`nb-diff-line nb-diff-${line.type}`}>
            <span class="nb-diff-ln">{line.oldLn ?? ""}</span>
            <span class="nb-diff-ln">{line.newLn ?? ""}</span>
            <span class="nb-diff-prefix">{line.type === "add" ? "+" : line.type === "del" ? "-" : " "}</span>
            <span class="nb-diff-text">{line.text}</span>
          </div>
        ))}
      </div>
    );
  };

  const renderContent = () => {
    if (!file) return null;
    if (file.format === "md") {
      const html = marked.parse(file.content) as string;
      return <div class="nb-prose" dangerouslySetInnerHTML={{ __html: html }} />;
    }
    if (file.format === "html") {
      return <div class="nb-prose" dangerouslySetInnerHTML={{ __html: file.content }} />;
    }
    return <pre class="nb-pre">{file.content}</pre>;
  };

  return (
    <div class="nb">
      {/* Breadcrumb */}
      <nav class="nb-crumbs">
        <button class="nb-crumb" onClick={() => load("")}>notebook</button>
        {segments.map((s, i) => (
          <span key={i}>
            <span class="nb-crumb-sep">/</span>
            {i === segments.length - 1
              ? <span class="nb-crumb nb-crumb-current">{s}</span>
              : <button class="nb-crumb" onClick={() => goTo(i)}>{s}</button>
            }
          </span>
        ))}
      </nav>

      {loading && <p class="nb-status">Loading...</p>}
      {error && <p class="nb-error">{error}</p>}

      {/* FolderView */}
      {!loading && !error && !file && (
        <div class="nb-folder">
          {/* Toolbar */}
          <div class="nb-toolbar">
            {path && (
              <button class="nb-toolbar-btn" onClick={goUp}>.. up</button>
            )}
            <div class="nb-toolbar-spacer" />
            <button class="nb-toolbar-btn" onClick={() => { setCreating("file"); setNewName(""); }}>+ File</button>
            <button class="nb-toolbar-btn" onClick={() => { setCreating("folder"); setNewName(""); }}>+ Folder</button>
          </div>

          {/* Create form */}
          {creating && (
            <div class="nb-create-form">
              <input
                ref={nameInputRef}
                class="nb-create-input"
                type="text"
                placeholder={creating === "folder" ? "Folder name" : "File name"}
                value={newName}
                onInput={(e) => setNewName((e.target as HTMLInputElement).value)}
                onKeyDown={(e) => {
                  if (e.key === "Enter") handleCreate();
                  if (e.key === "Escape") { setCreating(null); setNewName(""); }
                }}
              />
              <button class="nb-create-btn" onClick={handleCreate} disabled={saving || !newName.trim()}>
                {saving ? "..." : "Create"}
              </button>
              <button class="nb-create-cancel" onClick={() => { setCreating(null); setNewName(""); }}>Cancel</button>
            </div>
          )}

          {entries.length === 0 && !creating && (
            <p class="nb-empty">No files yet</p>
          )}
          {entries.map((e) => (
            <div key={e.name} class="nb-entry-row">
              <button class="nb-entry" onClick={() => navigate(e.name)}>
                <span class="nb-icon">{e.type === "dir" ? "\u{1F4C1}" : "\u{1F4C4}"}</span>
                <span class="nb-name">{e.name}</span>
              </button>
              <button
                class="nb-entry-delete"
                onClick={(ev) => { ev.stopPropagation(); deleteItem(path ? `${path}/${e.name}` : e.name, e.name); }}
                title="Delete"
              >x</button>
            </div>
          ))}
        </div>
      )}

      {/* FileView */}
      {!loading && !error && file && (
        <div class="nb-file">
          <div class="nb-file-toolbar">
            <button class="nb-back" onClick={goUp}>Back</button>
            <div class="nb-toolbar-spacer" />
            {hasPendingChanges ? (
              <>
                <button class="nb-approve-btn" onClick={approveCheckpoint} disabled={saving}>
                  {saving ? "..." : "Save"}
                </button>
                <button class="nb-discard-btn" onClick={discardChanges} disabled={saving}>
                  Discard
                </button>
              </>
            ) : !editing ? (
              <>
                {file.format === "md" && (
                  <button class="nb-approve-btn" onClick={approveCheckpoint} disabled={saving || checkpoint !== null}>
                    Save
                  </button>
                )}
                <button class="nb-edit-btn" onClick={startEdit}>Edit</button>
                <button class="nb-delete-btn" onClick={() => deleteItem(path, file!.name)}>Delete</button>
              </>
            ) : null}
          </div>
          <h2 class="nb-file-title">{file.name}</h2>

          {hasPendingChanges ? (
            renderDiff()
          ) : editing ? (
            <div class="nb-editor">
              <textarea
                ref={editorRef}
                class="nb-editor-textarea"
                value={editContent}
                onInput={(e) => setEditContent((e.target as HTMLTextAreaElement).value)}
              />
              <div class="nb-editor-actions">
                <button class="nb-save-btn" onClick={saveFile} disabled={saving}>
                  {saving ? "Saving..." : "Save"}
                </button>
                <button class="nb-cancel-btn" onClick={cancelEdit}>Cancel</button>
              </div>
            </div>
          ) : (
            renderContent()
          )}
        </div>
      )}
    </div>
  );
}
