"use client";

import {
  ChevronDown,
  ChevronRight,
  FileText,
  Folder,
  FolderOpen,
  Plus,
  X,
} from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import { StandardDialog } from "@/components/standard-dialog";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from "@/components/ui/tooltip";
import {
  useCreateSkill,
  useSkill,
  useUpdateSkill,
} from "@/lib/skills/skill.query";
import { cn } from "@/lib/utils";

interface ResourceFile {
  path: string;
  content: string;
  encoding: "utf8" | "base64";
}

interface FolderEntry {
  file: ResourceFile;
  index: number;
}

const MANIFEST_PLACEHOLDER = `---
name: my-skill
description: One line on when an agent should use this skill.
---

# My Skill

Step-by-step instructions for the agent...`;

const BLANK_TEMPLATE = `---
name: template-skill
description: Replace with description of the skill and when Agents should use it.
---

# Insert instructions below
`;

const ROOT_ADD_KEY = "";

export interface SkillPreview {
  name: string;
  description: string;
  content: string;
  license: string | null;
  compatibility: string | null;
  metadata: Record<string, string>;
  files: (ResourceFile & { kind?: "reference" | "script" | "asset" })[];
}

export function SkillEditorDialog({
  skillId,
  open,
  onOpenChange,
  onSaved,
  preview,
  isPreviewLoading,
}: {
  skillId: string | null;
  open: boolean;
  onOpenChange: (open: boolean) => void;
  onSaved?: () => void;
  preview?: SkillPreview | null;
  isPreviewLoading?: boolean;
}) {
  const isPreview = preview !== undefined;
  const isEdit = !isPreview && skillId !== null;
  const { data: skill, isPending: isLoading } = useSkill(
    open && !isPreview ? skillId : null,
  );
  const createSkill = useCreateSkill();
  const updateSkill = useUpdateSkill();

  const [manifest, setManifest] = useState("");
  const [files, setFiles] = useState<ResourceFile[]>([]);
  // null = the SKILL.md manifest is open; otherwise an index into `files`.
  const [openFileIndex, setOpenFileIndex] = useState<number | null>(null);
  const [filesExpanded, setFilesExpanded] = useState(true);
  const [collapsedFolders, setCollapsedFolders] = useState<Set<string>>(
    new Set(),
  );
  // null = not adding; "" = adding at root; otherwise the folder name.
  const [addingIn, setAddingIn] = useState<string | null>(null);
  const [newFileName, setNewFileName] = useState("");

  useEffect(() => {
    if (!open) return;
    if (isPreview) {
      if (preview) {
        setManifest(composeManifest(preview));
        setFiles(
          preview.files.map(({ path, content, encoding }) => ({
            path,
            content,
            encoding,
          })),
        );
      } else {
        setManifest("");
        setFiles([]);
      }
    } else if (isEdit && skill) {
      setManifest(composeManifest(skill));
      setFiles(
        skill.files.map(({ path, content, encoding }) => ({
          path,
          content,
          encoding,
        })),
      );
    } else if (!isEdit) {
      setManifest(BLANK_TEMPLATE);
      setFiles([]);
    }
    setOpenFileIndex(null);
    setAddingIn(null);
    setNewFileName("");
    setCollapsedFolders(new Set());
  }, [open, isPreview, preview, isEdit, skill]);

  const parsed = useMemo(() => parseManifestFields(manifest), [manifest]);
  const isSaving = createSkill.isPending || updateSkill.isPending;
  const canSave = parsed.hasName && parsed.hasDescription && !isSaving;

  const tree = useMemo(() => buildTree(files), [files]);

  const handleSave = async () => {
    const body = { content: manifest, files };
    const result = isEdit
      ? await updateSkill.mutateAsync({ id: skillId, body })
      : await createSkill.mutateAsync(body);
    if (result) {
      onOpenChange(false);
      onSaved?.();
    }
  };

  const commitNewFile = (folder: string | null) => {
    const name = newFileName.trim();
    if (!name) return;
    const path = folder ? `${folder}/${name}` : name;
    if (files.some((f) => f.path === path)) return;
    setFiles((prev) => [...prev, { path, content: "", encoding: "utf8" }]);
    setOpenFileIndex(files.length);
    setNewFileName("");
    setAddingIn(null);
  };

  const cancelAdding = () => {
    setAddingIn(null);
    setNewFileName("");
  };

  const removeFile = (index: number) => {
    setFiles((prev) => prev.filter((_, i) => i !== index));
    setOpenFileIndex((current) => {
      if (current === index) return null;
      if (current !== null && current > index) return current - 1;
      return current;
    });
  };

  const removeFolder = (folder: string) => {
    const prefix = `${folder}/`;
    let openWasInFolder = false;
    if (openFileIndex !== null) {
      const openPath = files[openFileIndex]?.path;
      if (openPath?.startsWith(prefix)) openWasInFolder = true;
    }
    setFiles((prev) => prev.filter((f) => !f.path.startsWith(prefix)));
    if (openWasInFolder) setOpenFileIndex(null);
  };

  const toggleFolder = (folder: string) => {
    setCollapsedFolders((prev) => {
      const next = new Set(prev);
      if (next.has(folder)) {
        next.delete(folder);
      } else {
        next.add(folder);
      }
      return next;
    });
  };

  const beginAddingInFolder = (folder: string) => {
    setCollapsedFolders((prev) => {
      if (!prev.has(folder)) return prev;
      const next = new Set(prev);
      next.delete(folder);
      return next;
    });
    setAddingIn(folder);
    setNewFileName("");
  };

  const openFile = openFileIndex === null ? null : files[openFileIndex];
  const editorValue = openFile ? openFile.content : manifest;
  const setEditorValue = (value: string) => {
    if (openFileIndex === null) {
      setManifest(value);
    } else {
      setFiles((prev) =>
        prev.map((file, i) =>
          i === openFileIndex ? { ...file, content: value } : file,
        ),
      );
    }
  };

  return (
    <StandardDialog
      open={open}
      onOpenChange={onOpenChange}
      title={
        isPreview
          ? (preview?.name ?? "Preview skill")
          : isEdit
            ? "Edit skill"
            : "New skill"
      }
      description={
        isPreview
          ? "Preview of a skill that has not been imported yet."
          : "A skill is a SKILL.md instruction set plus optional resource files."
      }
      size="large"
      footer={
        isPreview ? (
          <Button
            type="button"
            variant="outline"
            onClick={() => onOpenChange(false)}
          >
            Close
          </Button>
        ) : (
          <>
            <Button
              type="button"
              variant="outline"
              onClick={() => onOpenChange(false)}
            >
              Cancel
            </Button>
            <Button type="button" disabled={!canSave} onClick={handleSave}>
              {isSaving ? "Saving..." : "Save skill"}
            </Button>
          </>
        )
      }
    >
      {(isPreview && isPreviewLoading) || (isEdit && isLoading) ? (
        <div className="py-10 text-center text-sm text-muted-foreground">
          Loading skill...
        </div>
      ) : (
        <div className="space-y-4">
          <div className="space-y-1.5">
            <div className="flex items-center justify-between">
              <Label>{openFile ? openFile.path : "SKILL.md"}</Label>
              {!openFile && !isPreview && (
                <Tooltip>
                  <TooltipTrigger asChild>
                    <span className="cursor-help text-xs text-muted-foreground">
                      frontmatter: name <Marker ok={parsed.hasName} /> ·
                      description <Marker ok={parsed.hasDescription} />
                    </span>
                  </TooltipTrigger>
                  <TooltipContent className="max-w-xs">
                    <code className="font-mono">name</code> and{" "}
                    <code className="font-mono">description</code> must be set
                    in the YAML frontmatter block (between the{" "}
                    <code className="font-mono">---</code> fences) at the top of{" "}
                    <code className="font-mono">SKILL.md</code>. Agents read
                    these to decide when to use the skill.
                  </TooltipContent>
                </Tooltip>
              )}
            </div>
            {openFile && openFile.encoding === "base64" ? (
              <div className="flex min-h-[320px] flex-col items-center justify-center gap-1 rounded-md border bg-muted/30 text-center text-sm text-muted-foreground">
                <span className="font-medium text-foreground">
                  Binary asset
                </span>
                <span className="text-xs">
                  {formatBytes(approxBase64Bytes(openFile.content))} · base64
                  encoded
                </span>
                <span className="max-w-xs text-xs">
                  Stored verbatim for redistribution. Not editable here.
                </span>
              </div>
            ) : (
              <Textarea
                value={editorValue}
                onChange={(e) => setEditorValue(e.target.value)}
                placeholder={
                  openFile ? "File contents..." : MANIFEST_PLACEHOLDER
                }
                className="min-h-[320px] font-mono text-xs"
                spellCheck={false}
                readOnly={isPreview}
              />
            )}
          </div>

          <div className="rounded-md border">
            <button
              type="button"
              className="flex w-full items-center justify-between px-3 py-2 text-sm"
              onClick={() => setFilesExpanded((v) => !v)}
            >
              <span className="flex items-center gap-1.5 font-medium">
                {filesExpanded ? (
                  <ChevronDown className="h-4 w-4" />
                ) : (
                  <ChevronRight className="h-4 w-4" />
                )}
                Files ({files.length + 1})
              </span>
            </button>

            {filesExpanded && (
              <div className="border-t p-2">
                <ul className="space-y-0.5">
                  <ManifestRow
                    isOpen={openFileIndex === null}
                    onOpen={() => setOpenFileIndex(null)}
                  />

                  {tree.folderNames.map((folder) => {
                    const isCollapsed = collapsedFolders.has(folder);
                    const entries = tree.folders[folder];
                    return (
                      <li key={folder}>
                        <FolderRow
                          folder={folder}
                          fileCount={entries.length}
                          isCollapsed={isCollapsed}
                          readOnly={isPreview}
                          onToggle={() => toggleFolder(folder)}
                          onAddFile={() => beginAddingInFolder(folder)}
                          onRemoveFolder={() => removeFolder(folder)}
                        />
                        {!isCollapsed && (
                          <ul className="ml-5 space-y-0.5 border-l pl-2">
                            {entries.map(({ file, index }) => (
                              <FileRow
                                key={file.path}
                                label={file.path.slice(folder.length + 1)}
                                isOpen={openFileIndex === index}
                                readOnly={isPreview}
                                onOpen={() => setOpenFileIndex(index)}
                                onRemove={() => removeFile(index)}
                              />
                            ))}
                            {addingIn === folder && (
                              <NewFileRow
                                placeholder="filename.md"
                                value={newFileName}
                                onChange={setNewFileName}
                                onCommit={() => commitNewFile(folder)}
                                onCancel={cancelAdding}
                              />
                            )}
                          </ul>
                        )}
                      </li>
                    );
                  })}

                  {tree.rootFiles.map(({ file, index }) => (
                    <FileRow
                      key={file.path}
                      label={file.path}
                      isOpen={openFileIndex === index}
                      readOnly={isPreview}
                      onOpen={() => setOpenFileIndex(index)}
                      onRemove={() => removeFile(index)}
                    />
                  ))}

                  {addingIn === ROOT_ADD_KEY && (
                    <NewFileRow
                      placeholder="new-file.md or folder/new-file.md"
                      value={newFileName}
                      onChange={setNewFileName}
                      onCommit={() => commitNewFile(null)}
                      onCancel={cancelAdding}
                    />
                  )}
                </ul>

                {!isPreview && addingIn === null && (
                  <div className="mt-2 border-t pt-2">
                    <button
                      type="button"
                      className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
                      onClick={() => {
                        setAddingIn(ROOT_ADD_KEY);
                        setNewFileName("");
                      }}
                    >
                      <Plus className="h-3.5 w-3.5" />
                      New file (type{" "}
                      <span className="font-mono">folder/name.md</span> to nest)
                    </button>
                  </div>
                )}
              </div>
            )}
          </div>
        </div>
      )}
    </StandardDialog>
  );
}

function ManifestRow({
  isOpen,
  onOpen,
}: {
  isOpen: boolean;
  onOpen: () => void;
}) {
  return (
    <li
      className={cn(
        "flex items-center gap-2 rounded px-2 py-1",
        isOpen ? "bg-muted" : "hover:bg-muted/50",
      )}
    >
      <FileText className="h-4 w-4 shrink-0 text-foreground" />
      <button
        type="button"
        className="flex-1 truncate text-left font-mono text-xs font-medium"
        onClick={onOpen}
      >
        SKILL.md
      </button>
      <span className="text-xs text-muted-foreground">manifest</span>
    </li>
  );
}

function FolderRow({
  folder,
  fileCount,
  isCollapsed,
  readOnly,
  onToggle,
  onAddFile,
  onRemoveFolder,
}: {
  folder: string;
  fileCount: number;
  isCollapsed: boolean;
  readOnly: boolean;
  onToggle: () => void;
  onAddFile: () => void;
  onRemoveFolder: () => void;
}) {
  return (
    <div className="group flex items-center gap-1 rounded px-1 py-1 hover:bg-muted/50">
      <button
        type="button"
        className="flex flex-1 items-center gap-1.5 text-left"
        onClick={onToggle}
      >
        {isCollapsed ? (
          <ChevronRight className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
        ) : (
          <ChevronDown className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
        )}
        {isCollapsed ? (
          <Folder className="h-4 w-4 shrink-0 text-muted-foreground" />
        ) : (
          <FolderOpen className="h-4 w-4 shrink-0 text-muted-foreground" />
        )}
        <span className="text-sm font-medium">{folder}/</span>
        {isCollapsed && (
          <span className="text-xs text-muted-foreground">({fileCount})</span>
        )}
      </button>
      {!readOnly && (
        <>
          <Button
            type="button"
            variant="ghost"
            size="icon"
            className="h-6 w-6 opacity-0 group-hover:opacity-100"
            onClick={onAddFile}
            title={`Add file in ${folder}/`}
          >
            <Plus className="h-3.5 w-3.5" />
          </Button>
          <Button
            type="button"
            variant="ghost"
            size="icon"
            className="h-6 w-6 opacity-0 group-hover:opacity-100"
            onClick={onRemoveFolder}
            title={`Remove folder ${folder}/`}
          >
            <X className="h-3.5 w-3.5" />
          </Button>
        </>
      )}
    </div>
  );
}

function FileRow({
  label,
  isOpen,
  readOnly,
  onOpen,
  onRemove,
}: {
  label: string;
  isOpen: boolean;
  readOnly: boolean;
  onOpen: () => void;
  onRemove: () => void;
}) {
  return (
    <li
      className={cn(
        "group flex items-center gap-2 rounded px-2 py-1",
        isOpen ? "bg-muted" : "hover:bg-muted/50",
      )}
    >
      <FileText className="h-4 w-4 shrink-0 text-muted-foreground" />
      <button
        type="button"
        className="flex-1 truncate text-left font-mono text-xs"
        onClick={onOpen}
      >
        {label}
      </button>
      {!readOnly && (
        <Button
          type="button"
          variant="ghost"
          size="icon"
          className="h-6 w-6 opacity-0 group-hover:opacity-100"
          onClick={onRemove}
        >
          <X className="h-3.5 w-3.5" />
        </Button>
      )}
    </li>
  );
}

function NewFileRow({
  placeholder,
  value,
  onChange,
  onCommit,
  onCancel,
}: {
  placeholder: string;
  value: string;
  onChange: (value: string) => void;
  onCommit: () => void;
  onCancel: () => void;
}) {
  return (
    <li className="flex items-center gap-2 px-2 py-1">
      <FileText className="h-4 w-4 shrink-0 text-muted-foreground" />
      <Input
        autoFocus
        value={value}
        onChange={(e) => onChange(e.target.value)}
        onKeyDown={(e) => {
          if (e.key === "Enter") {
            e.preventDefault();
            onCommit();
          } else if (e.key === "Escape") {
            onCancel();
          }
        }}
        placeholder={placeholder}
        className="h-7 flex-1 font-mono text-xs"
      />
      <Button
        type="button"
        variant="ghost"
        size="sm"
        className="h-7 px-2 text-xs"
        onClick={onCommit}
        disabled={!value.trim()}
      >
        Add
      </Button>
      <Button
        type="button"
        variant="ghost"
        size="icon"
        className="h-7 w-7"
        onClick={onCancel}
      >
        <X className="h-3.5 w-3.5" />
      </Button>
    </li>
  );
}

function Marker({ ok }: { ok: boolean }) {
  return (
    <span className={ok ? "text-emerald-600" : "text-muted-foreground"}>
      {ok ? "✓" : "—"}
    </span>
  );
}

function approxBase64Bytes(content: string): number {
  // Each 4 chars of base64 encodes 3 bytes; ignore padding for a rough estimate.
  return Math.floor((content.length * 3) / 4);
}

function formatBytes(bytes: number): string {
  if (bytes < 1024) return `${bytes} B`;
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}

function buildTree(files: ResourceFile[]): {
  folders: Record<string, FolderEntry[]>;
  folderNames: string[];
  rootFiles: FolderEntry[];
} {
  const folders: Record<string, FolderEntry[]> = {};
  const rootFiles: FolderEntry[] = [];
  files.forEach((file, index) => {
    const slashIdx = file.path.indexOf("/");
    if (slashIdx === -1) {
      rootFiles.push({ file, index });
    } else {
      const folder = file.path.slice(0, slashIdx);
      if (!folders[folder]) folders[folder] = [];
      folders[folder].push({ file, index });
    }
  });
  return { folders, folderNames: Object.keys(folders).sort(), rootFiles };
}

function parseManifestFields(raw: string): {
  hasName: boolean;
  hasDescription: boolean;
} {
  const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
  const frontmatter = match?.[1] ?? "";
  return {
    hasName: /^name:\s*\S/m.test(frontmatter),
    hasDescription: /^description:\s*\S/m.test(frontmatter),
  };
}

function composeManifest(skill: {
  name: string;
  description: string;
  license: string | null;
  compatibility: string | null;
  metadata: Record<string, string>;
  content: string;
}): string {
  const lines = [
    "---",
    `name: ${skill.name}`,
    `description: ${skill.description}`,
  ];
  if (skill.license) lines.push(`license: ${skill.license}`);
  if (skill.compatibility) lines.push(`compatibility: ${skill.compatibility}`);
  const metadataEntries = Object.entries(skill.metadata ?? {});
  if (metadataEntries.length > 0) {
    lines.push("metadata:");
    for (const [key, value] of metadataEntries) {
      lines.push(`  ${key}: ${value}`);
    }
  }
  lines.push("---", "", skill.content);
  return lines.join("\n");
}
