"use client";

import * as React from "react";
import { SidebarTree } from "@/components/sidebar/sidebar-tree";
import { Breadcrumbs } from "@/components/navigation/breadcrumbs";
import { FileTable, type FileRow } from "@/components/files/file-table";
import { UploadDialog } from "@/components/files/upload-dialog";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Checkbox } from "@/components/ui/checkbox";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { MarkdownEditor } from "@/components/editor/markdown-editor";
import { toast } from "sonner";
import { TagsDialog } from "@/components/files/tags-dialog";
import { ModeToggle } from "@/components/theme/mode-toggle";

type FilesListResponse = {
  total: number;
  page: number;
  perPage: number;
  items: Array<{
    id: string;
    name: string;
    path: string;
    parentPath?: string;
    kind: "File";
    sizeBytes: number;
    mimeType: string;
    etag?: string;
  }>;
};

type SearchResult = {
  total: number;
  tookMs: number;
  hits: Array<{
    score: number;
    bm25Score?: number;
    vectorScore?: number;
    doc: {
      id: string;
      name: string;
      path: string;
      parentPath?: string;
      sizeBytes: number;
      mimeType: string;
      etag?: string;
    };
  }>;
};

async function fetchFiles(path?: string, page = 1, perPage = 50) {
  const url = new URL("/api/files/list", window.location.origin);
  if (path) url.searchParams.set("path", path);
  url.searchParams.set("page", String(page));
  url.searchParams.set("perPage", String(perPage));
  const res = await fetch(url.toString(), { cache: "no-store" });
  if (!res.ok) throw new Error(`Failed to load files: ${res.status}`);
  const data = (await res.json()) as FilesListResponse;
  return data;
}

async function executeSearch(q: string, semantic: boolean, page: number, perPage: number) {
  const res = await fetch("/api/search/query", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ q, semantic, page, perPage }),
  });
  if (!res.ok) throw new Error(`Search failed: ${res.status}`);
  const data = (await res.json()) as SearchResult;
  return data;
}

export default function Home() {
  const queryClient = useQueryClient();
  const [path, setPath] = React.useState<string | undefined>(undefined);
  const [page, setPage] = React.useState(1);
  const [perPage] = React.useState(50);

  const [q, setQ] = React.useState("");
  const [semantic, setSemantic] = React.useState(false);
  const searching = q.trim().length > 0;

  // Editor state
  const [editPath, setEditPath] = React.useState<string | null>(null);

  // Tags dialog state
  const [tagsPath, setTagsPath] = React.useState<string | null>(null);

  const filesQuery = useQuery({
    queryKey: ["files", path, page, perPage],
    queryFn: () => fetchFiles(path, page, perPage),
    enabled: !searching,
  });

  const searchQuery = useQuery({
    queryKey: ["search", q, semantic, page, perPage],
    queryFn: () => executeSearch(q.trim(), semantic, page, perPage),
    enabled: searching,
  });

  const files: FileRow[] = React.useMemo(() => {
    if (searching) {
      const hits = searchQuery.data?.hits ?? [];
      return hits.map((h) => ({
        id: h.doc.id,
        name: h.doc.name,
        path: h.doc.path,
        parentPath: h.doc.parentPath,
        sizeBytes: h.doc.sizeBytes,
        mimeType: h.doc.mimeType,
        etag: h.doc.etag,
      }));
    }
    const items = filesQuery.data?.items ?? [];
    return items.map((it) => ({
      id: it.id,
      name: it.name,
      path: it.path,
      parentPath: it.parentPath,
      sizeBytes: it.sizeBytes,
      mimeType: it.mimeType,
      etag: it.etag,
    }));
  }, [filesQuery.data, searchQuery.data, searching]);

  function handleDownload(item: FileRow) {
    const url = new URL("/api/files/download", window.location.origin);
    url.searchParams.set("path", item.path);
    // open in a new tab to trigger save/open dialog
    window.open(url.toString(), "_blank", "noopener,noreferrer");
  }

  function handleOpen(item: FileRow) {
    // Placeholder for preview behavior; for now, download
    handleDownload(item);
  }

  function handleEdit(item: FileRow) {
    setEditPath(item.path);
  }

  async function postJSON(url: string, body: unknown) {
    const res = await fetch(url, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(body),
    });
    if (!res.ok) {
      const payload = await res.json().catch(() => ({}));
      throw new Error(payload?.message || `Request failed (${res.status})`);
    }
    return res.json();
  }

  // Optimistic delete mutation
  const deleteMutation = useMutation({
    mutationFn: async (file: FileRow) => {
      return postJSON("/api/files/delete", { path: file.path });
    },
    onMutate: async (file: FileRow) => {
      // Cancel any outgoing refetches (so they don't overwrite our optimistic update)
      await queryClient.cancelQueries({ queryKey: ["files", path, page, perPage] });
      // Snapshot previous value
      const prev = queryClient.getQueryData(["files", path, page, perPage]) as
        | { total: number; page: number; perPage: number; items: FileRow[] }
        | undefined;

      if (prev) {
        // Optimistically remove the item
        const nextItems = prev.items.filter((x) => x.id !== file.id);
        queryClient.setQueryData(["files", path, page, perPage], {
          ...prev,
          total: Math.max(0, (prev.total ?? nextItems.length) - 1),
          items: nextItems,
        });
      }

      return { prev };
    },
    onError: (error, _file, ctx) => {
      if (ctx?.prev) {
        queryClient.setQueryData(["files", path, page, perPage], ctx.prev);
      }
      const message = error instanceof Error ? error.message : String(error);
      toast.error(message);
    },
    onSuccess: () => {
      toast.success("Deleted");
    },
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ["files", path, page, perPage] });
    },
  });
 
  // Optimistic rename mutation
  const renameMutation = useMutation({
    mutationFn: async (vars: { item: FileRow; to: string; newName: string }) => {
      return postJSON("/api/files/rename", { from: vars.item.path, to: vars.to });
    },
    onMutate: async ({ item, to, newName }) => {
      await queryClient.cancelQueries({ queryKey: ["files", path, page, perPage] });
      const prev = queryClient.getQueryData(["files", path, page, perPage]) as
        | { total: number; page: number; perPage: number; items: FileRow[] }
        | undefined;
      if (prev) {
        const nextItems = prev.items.map((x) =>
          x.id === item.id ? { ...x, name: newName, path: to } : x,
        );
        queryClient.setQueryData(["files", path, page, perPage], {
          ...prev,
          items: nextItems,
        });
      }
      return { prev };
    },
    onError: (error, _vars, ctx) => {
      if (ctx?.prev) {
        queryClient.setQueryData(["files", path, page, perPage], ctx.prev);
      }
      const message = error instanceof Error ? error.message : String(error);
      toast.error(message);
    },
    onSuccess: () => {
      toast.success("Renamed");
    },
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ["files", path, page, perPage] });
    },
  });

  // Optimistic copy mutation
  const copyMutation = useMutation({
    mutationFn: async (vars: { item: FileRow; to: string; newName: string }) => {
      return postJSON("/api/files/copy", { from: vars.item.path, to: vars.to });
    },
    onMutate: async ({ item, to, newName }) => {
      await queryClient.cancelQueries({ queryKey: ["files", path, page, perPage] });
      const prev = queryClient.getQueryData(["files", path, page, perPage]) as
        | { total: number; page: number; perPage: number; items: FileRow[] }
        | undefined;
      if (prev) {
        const temp: FileRow = {
          ...item,
          id: `temp-${Date.now()}`,
          name: newName,
          path: to,
        };
        const nextItems = [temp, ...(prev.items ?? [])];
        queryClient.setQueryData(["files", path, page, perPage], {
          ...prev,
          total: (prev.total ?? nextItems.length),
          items: nextItems,
        });
      }
      return { prev };
    },
    onError: (error, _vars, ctx) => {
      if (ctx?.prev) {
        queryClient.setQueryData(["files", path, page, perPage], ctx.prev);
      }
      const message = error instanceof Error ? error.message : String(error);
      toast.error(message);
    },
    onSuccess: () => {
      toast.success("Copied");
    },
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ["files", path, page, perPage] });
    },
  });

  async function handleRename(item: FileRow) {
    const newName = window.prompt("New name (or full destination path):", item.name);
    if (!newName) return;
    const to =
      newName.startsWith("/")
        ? newName
        : `${item.parentPath ?? ""}/${newName}`.replace(/\/+/g, "/");
    await renameMutation.mutateAsync({ item, to, newName });
  }

  async function handleCopy(item: FileRow) {
    const newPath = window.prompt("Copy to path (absolute or name in same folder):", `${item.parentPath ?? ""}/${item.name}`);
    if (!newPath) return;
    const to =
      newPath.startsWith("/")
        ? newPath
        : `${item.parentPath ?? ""}/${newPath}`.replace(/\/+/g, "/");
    const newName = newPath.startsWith("/")
      ? newPath.split("/").filter(Boolean).pop() ?? item.name
      : newPath;
    await copyMutation.mutateAsync({ item, to, newName });
  }

  async function handleDelete(item: FileRow) {
    const yes = window.confirm(`Delete "${item.name}"? This cannot be undone.`);
    if (!yes) return;
    await deleteMutation.mutateAsync(item);
  }

  function handleTags(item: FileRow) {
    setTagsPath(item.path);
  }

  function handleUploaded() {
    if (!searching) {
      filesQuery.refetch();
    }
  }

  return (
    <div className="h-screen grid grid-cols-[280px_1fr]">
      {/* Sidebar */}
      <aside className="border-r h-full">
        <SidebarTree path={path} onNavigate={(p) => { setPage(1); setPath(p); }} />
      </aside>

      {/* Main content */}
      <main className="h-full flex flex-col">
        <header className="border-b p-3 flex items-center gap-3">
          <Breadcrumbs path={path} onNavigate={(p) => { setPage(1); setPath(p); }} />
          <div className="ml-auto">
            <ModeToggle />
          </div>
        </header>

        <section className="p-3 flex items-center gap-2 border-b">
          <div className="flex items-center gap-2 w-full">
            <Input
              placeholder="Search files..."
              value={q}
              onChange={(e) => { setPage(1); setQ(e.target.value); }}
            />
            <div className="flex items-center gap-2 px-2">
              <Checkbox
                id="semantic"
                checked={semantic}
                onCheckedChange={(v: boolean) => setSemantic(Boolean(v))}
              />
              <label htmlFor="semantic" className="text-sm">Semantic</label>
            </div>
            <Button
              variant="secondary"
              onClick={() => {
                // Force refetch for the active mode
                if (searching) searchQuery.refetch();
                else filesQuery.refetch();
              }}
            >
              Refresh
            </Button>
          </div>
          <UploadDialog currentPath={path} onUploaded={handleUploaded} />
        </section>

        <section className="p-3 flex-1 overflow-auto">
          {searching ? (
            <div className="text-xs text-muted-foreground mb-2">
              {searchQuery.isLoading
                ? "Searching…"
                : `Found ${searchQuery.data?.total ?? 0} in ${searchQuery.data?.tookMs ?? 0}ms`}
            </div>
          ) : null}
          <FileTable
            items={files}
            onOpen={handleOpen}
            onDownload={handleDownload}
            onEdit={handleEdit}
            onRename={handleRename}
            onCopy={handleCopy}
            onDelete={handleDelete}
            onTags={handleTags}
          />
        </section>
      </main>

      {/* Editor dialog */}
      <Dialog open={!!editPath} onOpenChange={(v) => !v && setEditPath(null)}>
        <DialogContent className="max-w-4xl h-[80vh]">
          <DialogHeader>
            <DialogTitle>Markdown Editor</DialogTitle>
          </DialogHeader>
          <div className="h-[calc(80vh-60px)]">
            {editPath ? <MarkdownEditor path={editPath} /> : null}
          </div>
        </DialogContent>
      </Dialog>

      {/* Tags dialog */}
      <TagsDialog
        path={tagsPath ?? ""}
        open={!!tagsPath}
        onOpenChange={(v) => !v && setTagsPath(null)}
        initialTags={[]}
        onSaved={() => {
          // Optionally refresh file list to reflect updated tags when surface supports it
          if (!searching) filesQuery.refetch();
        }}
      />
    </div>
  );
}
