/**
 * 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, useReducer, useRef } 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";

interface ConfFilesTabState {
  files: ConfFileEntry[];
  loading: boolean;
  error: string | null;
  openItems: string[];
  contents: Record<string, string>;
  editedContents: Record<string, string>;
  saving: string | null;
  msg: { text: string; ok: boolean } | null;
  newName: string;
  newContent: string;
  creating: boolean;
}

type ConfFilesTabAction =
  | { type: "setFiles"; value: ConfFileEntry[] }
  | { type: "setLoading"; value: boolean }
  | { type: "setError"; value: string | null }
  | { type: "setOpenItems"; value: string[] }
  | { type: "setContent"; name: string; content: string }
  | { type: "setEditedContent"; name: string; content: string }
  | { type: "setSaving"; value: string | null }
  | { type: "setMsg"; value: { text: string; ok: boolean } | null }
  | { type: "setNewName"; value: string }
  | { type: "setNewContent"; value: string }
  | { type: "setCreating"; value: boolean };

export const initialConfFilesTabState: ConfFilesTabState = {
  files: [],
  loading: true,
  error: null,
  openItems: [],
  contents: {},
  editedContents: {},
  saving: null,
  msg: null,
  newName: "",
  newContent: "",
  creating: false,
};

export function confFilesTabReducer(
  state: ConfFilesTabState,
  action: ConfFilesTabAction,
): ConfFilesTabState {
  switch (action.type) {
    case "setFiles":
      return { ...state, files: action.value };
    case "setLoading":
      return { ...state, loading: action.value };
    case "setError":
      return { ...state, error: action.value };
    case "setOpenItems":
      return { ...state, openItems: action.value };
    case "setContent":
      return {
        ...state,
        contents: { ...state.contents, [action.name]: action.content },
      };
    case "setEditedContent":
      return {
        ...state,
        editedContents: {
          ...state.editedContents,
          [action.name]: action.content,
        },
      };
    case "setSaving":
      return { ...state, saving: action.value };
    case "setMsg":
      return { ...state, msg: action.value };
    case "setNewName":
      return { ...state, newName: action.value };
    case "setNewContent":
      return { ...state, newContent: action.value };
    case "setCreating":
      return { ...state, creating: action.value };
    default:
      return state;
  }
}

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 [state, dispatch] = useReducer(
    confFilesTabReducer,
    initialConfFilesTabState,
  );
  const {
    files,
    loading,
    error,
    openItems,
    contents,
    editedContents,
    saving,
    msg,
    newName,
    newContent,
    creating,
  } = state;
  const fetchFileControllerRef = useRef<AbortController | null>(null);

  const loadFiles = useCallback(async (signal?: AbortSignal) => {
    dispatch({ type: "setLoading", value: true });
    dispatch({ type: "setError", value: null });
    try {
      const resp = await fetchList();
      if (signal?.aborted) return;
      dispatch({ type: "setFiles", value: resp.files });
    } catch (err: unknown) {
      if (signal?.aborted) return;
      dispatch({
        type: "setError",
        value:
          err instanceof ApiError
            ? err.message
            : `Failed to load ${label.toLowerCase()} files.`,
      });
    } finally {
      if (!signal?.aborted) {
        dispatch({ type: "setLoading", value: 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));
      dispatch({ type: "setOpenItems", value: next });
      for (const name of newlyOpened) {
        if (!Object.prototype.hasOwnProperty.call(contents, name)) {
          fetchFileControllerRef.current?.abort();
          const controller = new AbortController();
          fetchFileControllerRef.current = controller;

          void fetchFile(name)
            .then((c) => {
              if (controller.signal.aborted) return;
              dispatch({
                type: "setContent",
                name,
                content: c.content,
              });
              dispatch({
                type: "setEditedContent",
                name,
                content: c.content,
              });
            })
            .catch(() => {
              if (controller.signal.aborted) return;
              dispatch({
                type: "setContent",
                name,
                content: "(failed to load)",
              });
              dispatch({
                type: "setEditedContent",
                name,
                content: "(failed to load)",
              });
            });
        }
      }
    },
    [openItems, contents, fetchFile],
  );

  useEffect(() => {
    return (): void => {
      fetchFileControllerRef.current?.abort();
    };
  }, []);

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

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

  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) => {
                        dispatch({
                          type: "setEditedContent",
                          name: file.name,
                          content: 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) => {
              dispatch({ type: "setNewName", value: 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) => {
              dispatch({ type: "setNewContent", value: 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>
  );
}
