/**
 * ConfFilesTab — generic editable file list for filter.d / action.d.
 *
 * Renders an accordion of conf files, each with a read/edit textarea and
 * individual save button.  Accepts fetch/create/update callbacks so it can
 * be reused for both filter and action files.
 */

import { useCallback, useEffect, useState } from "react";
import {
  Accordion,
  AccordionHeader,
  AccordionItem,
  AccordionPanel,
  Button,
  Field,
  Input,
  MessageBar,
  MessageBarBody,
  Spinner,
  Text,
  Textarea,
  tokens,
} from "@fluentui/react-components";
import { ArrowClockwise24Regular, Save24Regular } from "@fluentui/react-icons";
import { ApiError } from "../../api/client";
import type { ConfFileEntry } from "../../types/config";
import { useConfigStyles } from "./configStyles";

export interface ConfFilesTabProps {
  /** Human-readable label, e.g. "Filter" or "Action". */
  label: string;
  /** Fetches the list of available files. */
  fetchList: () => Promise<{ files: ConfFileEntry[]; total: number }>;
  /** Fetches the content of a single file by name (without extension). */
  fetchFile: (name: string) => Promise<{
    name: string;
    filename: string;
    content: string;
  }>;
  /** Persists updated content for the given file name. */
  updateFile: (name: string, req: { content: string }) => Promise<void>;
  /** Creates a new file with the given name and content. */
  createFile: (req: {
    name: string;
    content: string;
  }) => Promise<{ name: string; filename: string; content: string }>;
}

/**
 * Generic tab component for editing raw conf files (filter.d or action.d).
 *
 * @param props - Component props.
 * @returns JSX element.
 */
export function ConfFilesTab({
  label,
  fetchList,
  fetchFile,
  updateFile,
  createFile,
}: ConfFilesTabProps): React.JSX.Element {
  const styles = useConfigStyles();
  const [files, setFiles] = useState<ConfFileEntry[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [openItems, setOpenItems] = useState<string[]>([]);
  const [contents, setContents] = useState<Record<string, string>>({});
  const [editedContents, setEditedContents] = useState<Record<string, string>>(
    {},
  );
  const [saving, setSaving] = useState<string | null>(null);
  const [msg, setMsg] = useState<{ text: string; ok: boolean } | null>(null);
  const [newName, setNewName] = useState("");
  const [newContent, setNewContent] = useState("");
  const [creating, setCreating] = useState(false);

  const loadFiles = useCallback(async (signal?: AbortSignal) => {
    setLoading(true);
    setError(null);
    try {
      const resp = await fetchList();
      if (signal?.aborted) return;
      setFiles(resp.files);
    } catch (err: unknown) {
      if (signal?.aborted) return;
      setError(
        err instanceof ApiError
          ? err.message
          : `Failed to load ${label.toLowerCase()} files.`,
      );
    } finally {
      if (!signal?.aborted) {
        setLoading(false);
      }
    }
  }, [fetchList, label]);

  useEffect(() => {
    const controller = new AbortController();
    void loadFiles(controller.signal);
    return (): void => {
      controller.abort();
    };
  }, [loadFiles]);

  const handleAccordionToggle = useCallback(
    (
      _e: React.SyntheticEvent,
      data: { openItems: (string | number)[] },
    ) => {
      const next = data.openItems as string[];
      const newlyOpened = next.filter((v) => !openItems.includes(v));
      setOpenItems(next);
      for (const name of newlyOpened) {
        if (!Object.prototype.hasOwnProperty.call(contents, name)) {
          void fetchFile(name)
            .then((c) => {
              setContents((prev) => ({ ...prev, [name]: c.content }));
              setEditedContents((prev) => ({ ...prev, [name]: c.content }));
            })
            .catch(() => {
              setContents((prev) => ({
                ...prev,
                [name]: "(failed to load)",
              }));
              setEditedContents((prev) => ({
                ...prev,
                [name]: "(failed to load)",
              }));
            });
        }
      }
    },
    [openItems, contents, fetchFile],
  );

  const handleSave = useCallback(
    async (name: string) => {
      setSaving(name);
      setMsg(null);
      try {
        const content = editedContents[name] ?? contents[name] ?? "";
        await updateFile(name, { content });
        setContents((prev) => ({ ...prev, [name]: content }));
        setMsg({ text: `${name} saved.`, ok: true });
      } catch (err: unknown) {
        setMsg({
          text: err instanceof ApiError ? err.message : "Save failed.",
          ok: false,
        });
      } finally {
        setSaving(null);
      }
    },
    [editedContents, contents, updateFile],
  );

  const handleCreate = useCallback(async () => {
    const name = newName.trim();
    if (!name) return;
    setCreating(true);
    setMsg(null);
    try {
      const created = await createFile({ name, content: newContent });
      setFiles((prev) => [
        ...prev,
        { name: created.name, filename: created.filename },
      ]);
      setContents((prev) => ({ ...prev, [created.name]: created.content }));
      setEditedContents((prev) => ({
        ...prev,
        [created.name]: created.content,
      }));
      setNewName("");
      setNewContent("");
      setMsg({ text: `${created.filename} created.`, ok: true });
    } catch (err: unknown) {
      setMsg({
        text: err instanceof ApiError ? err.message : "Create failed.",
        ok: false,
      });
    } finally {
      setCreating(false);
    }
  }, [newName, newContent, createFile]);

  if (loading) return <Spinner label={`Loading ${label.toLowerCase()} files…`} />;
  if (error)
    return (
      <MessageBar intent="error">
        <MessageBarBody>{error}</MessageBarBody>
      </MessageBar>
    );

  return (
    <div>
      {msg && (
        <MessageBar
          intent={msg.ok ? "success" : "error"}
          style={{ marginBottom: tokens.spacingVerticalS }}
        >
          <MessageBarBody>{msg.text}</MessageBarBody>
        </MessageBar>
      )}
      <div className={styles.buttonRow}>
        <Button
          appearance="secondary"
          icon={<ArrowClockwise24Regular />}
          onClick={() => void loadFiles()}
        >
          Refresh
        </Button>
      </div>
      {files.length === 0 && (
        <Text
          className={styles.infoText}
          style={{ marginTop: tokens.spacingVerticalM }}
        >
          No {label.toLowerCase()} files found.
        </Text>
      )}
      <Accordion
        multiple
        collapsible
        openItems={openItems}
        onToggle={handleAccordionToggle}
        style={{ marginTop: tokens.spacingVerticalM }}
      >
        {files.map((file) => (
          <AccordionItem key={file.name} value={file.name}>
            <AccordionHeader>
              <Text className={styles.codeFont}>{file.filename}</Text>
            </AccordionHeader>
            <AccordionPanel>
              {openItems.includes(file.name) &&
                (contents[file.name] === undefined ? (
                  <Spinner size="tiny" label="Loading…" />
                ) : (
                  <div>
                    <Textarea
                      value={editedContents[file.name] ?? ""}
                      rows={20}
                      style={{
                        width: "100%",
                        resize: "vertical",
                        fontFamily: "monospace",
                      }}
                      onChange={(_e, d) => {
                        setEditedContents((prev) => ({
                          ...prev,
                          [file.name]: d.value,
                        }));
                      }}
                    />
                    <div className={styles.buttonRow}>
                      <Button
                        appearance="primary"
                        icon={<Save24Regular />}
                        disabled={saving === file.name}
                        onClick={() => void handleSave(file.name)}
                      >
                        {saving === file.name ? "Saving…" : "Save"}
                      </Button>
                    </div>
                  </div>
                ))}
            </AccordionPanel>
          </AccordionItem>
        ))}
      </Accordion>

      {/* Create new file */}
      <div
        style={{
          marginTop: tokens.spacingVerticalXL,
          borderTop: `1px solid ${tokens.colorNeutralStroke2}`,
          paddingTop: tokens.spacingVerticalM,
        }}
      >
        <Text
          as="h3"
          size={400}
          weight="semibold"
          block
          style={{ marginBottom: tokens.spacingVerticalS }}
        >
          New {label} File
        </Text>
        <Field label="Name (without .conf extension)">
          <Input
            value={newName}
            placeholder={`e.g. my-${label.toLowerCase()}`}
            className={styles.codeFont}
            onChange={(_e, d) => {
              setNewName(d.value);
            }}
          />
        </Field>
        <Field label="Content" style={{ marginTop: tokens.spacingVerticalS }}>
          <Textarea
            value={newContent}
            rows={10}
            placeholder={`[Definition]\n# …`}
            style={{
              width: "100%",
              resize: "vertical",
              fontFamily: "monospace",
            }}
            onChange={(_e, d) => {
              setNewContent(d.value);
            }}
          />
        </Field>
        <div className={styles.buttonRow}>
          <Button
            appearance="primary"
            disabled={creating || !newName.trim()}
            onClick={() => void handleCreate()}
          >
            {creating ? "Creating…" : `Create ${label} File`}
          </Button>
        </div>
      </div>
    </div>
  );
}
