import { expect, test, vi } from "vitest";
import { fireEvent, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { renderApp } from "../test/render";
import { ObjectForm } from "./object-form";
import { pruneFields } from "./prune-fields";

test("create mode: shows visibility (draft/internal only) and submits assembled values", async () => {
  const onSubmit = vi.fn();
  renderApp(<ObjectForm mode="create" onSubmit={onSubmit} onCancel={() => {}} />);

  await userEvent.type(await screen.findByLabelText(/object number/i), "A-9");
  await userEvent.type(screen.getByLabelText(/^name/i), "Amphora");
  await userEvent.type(screen.getByLabelText(/inscription/i), "To the gods");

  await userEvent.click(screen.getByRole("combobox", { name: /visibility/i })); // open
  expect(await within(document.body).findByRole("option", { name: /draft/i })).toBeInTheDocument();
  expect(within(document.body).getByRole("option", { name: /internal/i })).toBeInTheDocument();
  expect(within(document.body).queryByRole("option", { name: /public/i })).not.toBeInTheDocument();
  await userEvent.keyboard("{Escape}"); // close, keep default draft
  await userEvent.click(screen.getByRole("button", { name: /create object/i }));
  await waitFor(() => expect(onSubmit).toHaveBeenCalledOnce());
  const values = onSubmit.mock.calls[0][0];
  expect(values.core.object_number).toBe("A-9");
  expect(values.visibility).toBe("draft");
  expect(values.fields.inscription).toBe("To the gods");
});

test("Cmd/Ctrl+Enter submits the form", async () => {
  const onSubmit = vi.fn();
  renderApp(<ObjectForm mode="create" onSubmit={onSubmit} onCancel={() => {}} />);

  await userEvent.type(await screen.findByLabelText(/object number/i), "A-9");
  await userEvent.type(screen.getByLabelText(/^name/i), "Amphora");
  await userEvent.type(screen.getByLabelText(/inscription/i), "To the gods");

  const numberInput = screen.getByLabelText(/object number/i);
  fireEvent.keyDown(numberInput, { key: "Enter", metaKey: true });

  await waitFor(() => expect(onSubmit).toHaveBeenCalledOnce());
});

test("required core + required flexible field block submit", async () => {
  const onSubmit = vi.fn();
  renderApp(<ObjectForm mode="create" onSubmit={onSubmit} onCancel={() => {}} />);
  await userEvent.click(await screen.findByRole("button", { name: /create object/i }));
  await waitFor(() => expect(screen.getAllByText(/required/i).length).toBeGreaterThan(0));
  expect(onSubmit).not.toHaveBeenCalled();
});

test("number_of_objects of 0 is blocked client-side with the minCount message", async () => {
  const onSubmit = vi.fn();
  renderApp(<ObjectForm mode="create" onSubmit={onSubmit} onCancel={() => {}} />);

  await userEvent.type(await screen.findByLabelText(/object number/i), "A-9");
  await userEvent.type(screen.getByLabelText(/^name/i), "Amphora");
  await userEvent.type(screen.getByLabelText(/inscription/i), "To the gods");

  const count = screen.getByLabelText(/number of objects/i);
  await userEvent.clear(count);
  await userEvent.type(count, "0");

  await userEvent.click(screen.getByRole("button", { name: /create object/i }));

  expect(await screen.findByText("Must be at least 1")).toBeInTheDocument();
  expect(onSubmit).not.toHaveBeenCalled();
});

test("edit mode: no visibility control, save button, prefilled values", async () => {
  const onSubmit = vi.fn();
  renderApp(
    <ObjectForm mode="edit" onSubmit={onSubmit} onCancel={() => {}}
      defaults={{
        core: { object_number: "A-1", object_name: "Amphora", number_of_objects: 1,
          brief_description: null, current_location: "Vault 3", current_owner: null,
          recorder: null, recording_date: null },
        fields: { inscription: "hi" },
      }} />,
  );
  expect(await screen.findByDisplayValue("Amphora")).toBeInTheDocument();
  expect(screen.queryByLabelText(/visibility/i)).not.toBeInTheDocument();
  expect(screen.getByRole("button", { name: /save/i })).toBeInTheDocument();
});

test("groups flexible fields by definition group with subheadings", async () => {
  renderApp(<ObjectForm mode="create" onSubmit={vi.fn()} onCancel={() => {}} />);

  expect(await screen.findByText("Description")).toBeInTheDocument();
  expect(screen.getByText(/^Other$/)).toBeInTheDocument();

  const inscription = screen.getByLabelText(/inscription/i);
  const material = screen.getByLabelText(/material/i);

  expect(inscription.compareDocumentPosition(material) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
});

test("pruneFields: localized_text keeps only the default-language key, other object fields unaffected", () => {
  const localizedTextKeys = new Set(["title_ml"]);

  const result = pruneFields(
    { title_ml: { en: "Old", sv: "Ny" }, other: "x" },
    localizedTextKeys,
    "sv",
  );

  expect(result).toEqual({ title_ml: { sv: "Ny" }, other: "x" });
  expect(Object.keys(result.title_ml as Record<string, unknown>)).not.toContain("en");
});

test("pruneFields: localized_text with only non-default lang produces empty object (key omitted)", () => {
  const localizedTextKeys = new Set(["title_ml"]);

  const result = pruneFields(
    { title_ml: { en: "English only" } },
    localizedTextKeys,
    "sv",
  );

  expect(result).toEqual({});
});

test("pruneFields: non-localized_text object fields are preserved as-is", () => {
  const localizedTextKeys = new Set(["title_ml"]);

  const result = pruneFields(
    { nested_obj: { a: "1", b: "2" } },
    localizedTextKeys,
    "sv",
  );

  expect(result).toEqual({ nested_obj: { a: "1", b: "2" } });
});
