import { useState } from "react";
import "../../src/i18n/i18n";
import { TechStepCorrectionPopover } from "../../src/features/recipes/steps/TechStepCorrectionPopover";

// Mounts the popover in isolation (no StepDescription/selection plumbing
// around it) — same "generic component test" posture as CheckboxOption.cy.tsx,
// but this one needs `../../src/i18n/i18n` imported for its side effect
// (initializes the default i18next instance `useTranslation` falls back to
// with no `<I18nextProvider>` in the tree — see that module's own doc
// comment) since, unlike Checkbox/Radio, this component calls
// `useTranslation()`.

const cook = { id: 1, key: "cook" };
const simmer = { id: 3, key: "simmer" };
const butter = { id: 10, key: "butter" };
const pan = { id: 20, key: "pan" };
const gram = { id: 30, key: "gram" };

/**
 * A real `StepDescription` resolves `onRequestSpan` into a fresh
 * `resolvedMetadataSpan` via an actual browser text selection — out of
 * scope for a component test of the popover alone (covered by the e2e
 * scenario instead). This harness fakes that round-trip with a fixed
 * span, so tests here can exercise everything the popover itself is
 * responsible for once a span comes back, without needing a real
 * `StepDescription` in the tree.
 */
function Harness({
  previousTechStepId = null,
  existingIngredients = [],
  existingUtensils = [],
  onClose = () => {},
  onSubmitted = () => {},
}: Partial<{
  previousTechStepId: number | null;
  existingIngredients: unknown[];
  existingUtensils: unknown[];
  onClose: () => void;
  onSubmitted: (result: unknown) => void;
}>) {
  const [resolvedMetadataSpan, setResolvedMetadataSpan] = useState<{
    nonce: number;
    kind: "ingredient" | "utensil";
    range: { start: number; end: number };
    text: string;
  } | null>(null);

  return (
    <div>
      {/* A genuinely separate sibling to click for the "outside click closes it" test — clicking blindly at a viewport coordinate would risk still landing inside the popover, which fills most of the mounted area on its own. */}
      <div data-testid="outside-popover" style={{ height: 20 }} />
      <TechStepCorrectionPopover
        recipeId={2}
        stepId={2}
        selectedText="Cuire"
        range={{ start: 0, end: 5 }}
        previousTechStepId={previousTechStepId}
        // biome-ignore lint/suspicious/noExplicitAny: test harness stands in for real StepTechStepIngredientView/UtensilView props — precise typing isn't the point here.
        existingIngredients={existingIngredients as any}
        // biome-ignore lint/suspicious/noExplicitAny: see above.
        existingUtensils={existingUtensils as any}
        resolvedMetadataSpan={resolvedMetadataSpan}
        onRequestSpan={(kind) =>
          setResolvedMetadataSpan({
            nonce: Date.now(),
            kind,
            range: { start: 20, end: 26 },
            text: "Beurre",
          })
        }
        onClose={onClose}
        // biome-ignore lint/suspicious/noExplicitAny: see above.
        onSubmitted={onSubmitted as any}
      />
    </div>
  );
}

describe("TechStepCorrectionPopover", () => {
  beforeEach(() => {
    cy.intercept("GET", "**/reference/tech-steps", { statusCode: 200, body: [cook, simmer] }).as(
      "getTechSteps",
    );
    cy.intercept("GET", "**/reference/ingredients", { statusCode: 200, body: [butter] }).as(
      "getIngredients",
    );
    cy.intercept("GET", "**/reference/units", { statusCode: 200, body: [gram] }).as("getUnits");
    cy.intercept("GET", "**/reference/utensils", { statusCode: 200, body: [pan] }).as(
      "getUtensils",
    );
  });

  it("shows the selected text, the technique catalog (searchable) and the metadata sections all together", () => {
    cy.mount(<Harness />);
    cy.wait("@getTechSteps");

    cy.contains(".tech-step-correction-popover__selection", "Cuire").should("be.visible");
    // Merged editor (see TechStepCorrectionPopover's own doc comment) — no
    // separate "pick, then metadata reveals itself" step, both render at
    // once, and the technique catalog goes through the same searchable
    // `CatalogSearchPicker` as the ingredient/utensil sub-flows (a plain
    // unfiltered list of the real ~74-entry catalog isn't browsable).
    cy.get(
      ".tech-step-correction-popover__technique-section .catalog-search-picker__list button",
    ).should("have.length", 2);
    cy.contains("h4", "Ingrédients").should("be.visible");
    cy.contains("h4", "Ustensiles").should("be.visible");
    cy.contains("button", "Valider").should("be.visible");
  });

  it("offers a 'no technique here' option, and marks the current pick, only when correcting an existing match", () => {
    cy.mount(<Harness previousTechStepId={null} />);
    cy.wait("@getTechSteps");
    cy.get(".tech-step-correction-popover__remove").should("not.exist");
    cy.contains(".tech-step-correction-popover__chosen-technique", "Aucune technique sélectionnée");

    cy.mount(<Harness previousTechStepId={cook.id} />);
    cy.wait("@getTechSteps");
    cy.get(".tech-step-correction-popover__remove").should("exist");
    cy.contains(".tech-step-correction-popover__chosen-technique", "Cuire");
    cy.contains(".catalog-search-picker__list button", "Cuire").should(
      "have.class",
      "catalog-search-picker__item--selected",
    );
  });

  it("picking a technique from the catalog selects it without submitting immediately", () => {
    cy.mount(<Harness />);
    cy.wait("@getTechSteps");

    cy.contains(
      ".tech-step-correction-popover__technique-section .catalog-search-picker__list button",
      "Mijoter",
    ).click();

    cy.contains(".tech-step-correction-popover__chosen-technique", "Mijoter");
    cy.contains("button", "Valider").should("be.visible");
  });

  it("Valider stays disabled until a technique is actually picked", () => {
    cy.mount(<Harness />);
    cy.wait("@getTechSteps");

    cy.contains("button", "Valider").should("be.disabled");
    cy.contains(
      ".tech-step-correction-popover__technique-section .catalog-search-picker__list button",
      "Mijoter",
    ).click();
    cy.contains("button", "Valider").should("not.be.disabled");
  });

  it("submits the selected technique (no metadata touched) with ingredients/utensils omitted from the request", () => {
    cy.intercept("POST", "**/recipes/2/steps/2/corrections", {
      statusCode: 201,
      body: {
        id: 1,
        start: 0,
        end: 5,
        previousTechStep: null,
        correctedTechStep: simmer,
        createdAt: new Date().toISOString(),
      },
    }).as("submitCorrection");
    const onSubmitted = cy.stub().as("onSubmitted");
    cy.mount(<Harness onSubmitted={onSubmitted} />);
    cy.wait("@getTechSteps");

    cy.contains(
      ".tech-step-correction-popover__technique-section .catalog-search-picker__list button",
      "Mijoter",
    ).click();
    cy.contains("button", "Valider").click();

    cy.wait("@submitCorrection").its("request.body").should("deep.equal", {
      start: 0,
      end: 5,
      previousTechStepId: null,
      correctedTechStepId: simmer.id,
    });
    cy.get("@onSubmitted").should("have.been.calledOnce");
  });

  it("adds an ingredient with quantity/unit via the span-selection flow, included in the submitted request", () => {
    cy.intercept("POST", "**/recipes/2/steps/2/corrections", {
      statusCode: 201,
      body: {
        id: 1,
        start: 0,
        end: 5,
        previousTechStep: null,
        correctedTechStep: simmer,
        createdAt: new Date().toISOString(),
      },
    }).as("submitCorrection");
    cy.mount(<Harness />);
    cy.wait("@getTechSteps");
    cy.wait(["@getIngredients", "@getUnits", "@getUtensils"]);

    cy.contains(
      ".tech-step-correction-popover__technique-section .catalog-search-picker__list button",
      "Mijoter",
    ).click();
    cy.contains("button", "+ Ajouter un ingrédient").click();

    cy.contains(".catalog-search-picker button", "Beurre").click();
    cy.get('input[type="number"]').type("50");
    cy.get("select").select(String(gram.id));
    cy.contains("button", "Ajouter").click();

    cy.contains(".tech-step-correction-popover__chip", "50 g Beurre").should("be.visible");
    cy.contains("button", "Valider").click();

    cy.wait("@submitCorrection")
      .its("request.body")
      .should("deep.equal", {
        start: 0,
        end: 5,
        previousTechStepId: null,
        correctedTechStepId: simmer.id,
        ingredients: [
          { ingredientId: butter.id, quantity: 50, unitId: gram.id, start: 20, end: 26 },
        ],
        utensils: [],
      });
  });

  it("pre-seeds existing ingredients/utensils, removable via their own chip", () => {
    cy.mount(
      <Harness
        previousTechStepId={cook.id}
        existingIngredients={[
          { ingredient: butter, quantity: 50, unit: gram, start: 0, end: 6, source: "auto" },
        ]}
        existingUtensils={[{ utensil: pan, start: 14, end: 23, source: "auto" }]}
      />,
    );
    // An existing match starts pre-selected on itself (see
    // TechStepCorrectionPopover's own doc comment) — the metadata sections,
    // pre-seeded from `existingIngredients`/`existingUtensils`, are visible
    // immediately, no need to re-pick "Cuire" from a list first.
    cy.wait("@getTechSteps");
    cy.wait(["@getIngredients", "@getUnits", "@getUtensils"]);

    cy.contains(".tech-step-correction-popover__chip", "50 g Beurre").find("button").click();
    cy.contains(".tech-step-correction-popover__chip", "Beurre").should("not.exist");
  });

  it("shows an error message and stays open when the submission fails", () => {
    cy.intercept("POST", "**/recipes/2/steps/2/corrections", {
      statusCode: 404,
      body: { code: 4051, message: "TechStep not found" },
    }).as("submitCorrection");
    const onClose = cy.stub().as("onClose");
    cy.mount(<Harness onClose={onClose} />);
    cy.wait("@getTechSteps");

    cy.contains(
      ".tech-step-correction-popover__technique-section .catalog-search-picker__list button",
      "Cuire",
    ).click();
    cy.contains("button", "Valider").click();

    cy.wait("@submitCorrection");
    cy.get(".field-error").should("be.visible");
    cy.get("@onClose").should("not.have.been.called");
  });

  it("calls onClose on an outside click", () => {
    const onClose = cy.stub().as("onClose");
    cy.mount(<Harness onClose={onClose} />);
    cy.wait("@getTechSteps");

    cy.get('[data-testid="outside-popover"]').click();

    cy.get("@onClose").should("have.been.calledOnce");
  });
});
