// web/src/setup/setupEntry.ts — which step the setup wizard opens on.
//
// This exists because of a reported bug with a very specific shape: the wizard
// INFERRED the user's intent from application state instead of reading the
// intent they had just stated. Choose "+ Add company…" in the switcher with an
// established company selected and you were met with "Welcome back, <that
// other company> — it already has a chart of accounts (40 accounts). What
// would you like to do?" The user had said what they wanted; the app asked
// anyway, about a company they had not mentioned.
//
// The check itself was not wrong — it was written when #setup had exactly one
// door (App.tsx's zero-companies first run), where "did you mean to resume?"
// is a fair question because nothing was stated. It survived the arrival of
// doors that DO state something. So what is pinned below is the boundary
// between those two cases, in both directions: a stated intent is never
// second-guessed, and an arrival that states nothing still gets asked.
//
// No DOM renderer in this repo's web tests, so the decision is a pure function
// and this drives it directly — the same seam SetupWizard.tsx consumes on
// mount and again when the company switcher moves under it.

import { describe, it, expect } from "vitest";
import {
  setupStepFor,
  asksAboutSelectedCompany,
  type SetupEntry,
  type SetupCompanyState,
} from "../../web/src/setup/setupEntry.js";
import { parseHashString, hashFor } from "../../web/src/lib/useHashView.js";

const ESTABLISHED: SetupCompanyState = {
  selectedCompanyId: "51d9b348-6916-4334-9d4a-5d8a81be88bb",
  accountsCount: 40,
};

// ---------------------------------------------------------------------------
// the bug: "+ Add company…" must never be answered with somebody else's books
// ---------------------------------------------------------------------------

describe('entry "new-company" — the user already said what they want', () => {
  it("opens the name step even though an established company is selected", () => {
    expect(setupStepFor("new-company", ESTABLISHED)).toBe("name");
  });

  it("opens the name step for EVERY company state, not just this one", () => {
    // The point is that company state is not consulted at all. Anything that
    // makes one of these return "resume-notice" or "choose-path" is the
    // inference growing back.
    const states: SetupCompanyState[] = [
      { selectedCompanyId: null },
      { selectedCompanyId: null, accountsCount: 0 },
      { selectedCompanyId: "c1", accountsCount: undefined }, // still loading
      { selectedCompanyId: "c1", accountsCount: null }, // lookup failed
      { selectedCompanyId: "c1", accountsCount: 0 }, // named but never finished
      { selectedCompanyId: "c1", accountsCount: 1 },
      { selectedCompanyId: "c1", accountsCount: 40 },
      { selectedCompanyId: "c1", accountsCount: 6763 }, // a real, busy company
    ];
    for (const state of states) {
      expect(setupStepFor("new-company", state)).toBe("name");
    }
  });

  it("never shows the checking placeholder — there is nothing to check", () => {
    // A "One moment…" flash would be a frame spent asking a question that has
    // already been answered.
    expect(setupStepFor("new-company", { selectedCompanyId: "c1" })).not.toBe("checking");
  });

  it("does not even ask the server about the selected company", () => {
    expect(asksAboutSelectedCompany("new-company")).toBe(false);
  });
});

// ---------------------------------------------------------------------------
// the case the resume notice was designed for — still intact
// ---------------------------------------------------------------------------

describe('entry "unspecified" — nothing stated, so it is fair to ask', () => {
  it("shows the resume notice for an established company", () => {
    expect(setupStepFor("unspecified", ESTABLISHED)).toBe("resume-notice");
  });

  it("skips to choose-path for a company named but never set up", () => {
    // A company row with no accounts means setup was abandoned after step 1 —
    // there is nothing to "resume", just a path to pick.
    expect(setupStepFor("unspecified", { selectedCompanyId: "c1", accountsCount: 0 })).toBe(
      "choose-path",
    );
  });

  it("waits rather than guessing while the account lookup is in flight", () => {
    expect(setupStepFor("unspecified", { selectedCompanyId: "c1" })).toBe("checking");
    expect(setupStepFor("unspecified", { selectedCompanyId: "c1", accountsCount: undefined })).toBe(
      "checking",
    );
  });

  it("fails OPEN when the lookup fails — never traps the user on a hiccup", () => {
    expect(setupStepFor("unspecified", { selectedCompanyId: "c1", accountsCount: null })).toBe(
      "name",
    );
  });

  it("first run (no company selected at all) opens the name step", () => {
    expect(setupStepFor("unspecified", { selectedCompanyId: null })).toBe("name");
    expect(setupStepFor("unspecified", { selectedCompanyId: null, accountsCount: 40 })).toBe("name");
  });

  it("asks the server about the selected company", () => {
    expect(asksAboutSelectedCompany("unspecified")).toBe(true);
  });
});

// ---------------------------------------------------------------------------
// the two doors, side by side
// ---------------------------------------------------------------------------

describe("the same company state, two different doors", () => {
  it("answers differently, which is the entire fix", () => {
    const entries: SetupEntry[] = ["new-company", "unspecified"];
    const answers = entries.map((entry) => setupStepFor(entry, ESTABLISHED));
    expect(answers).toEqual(["name", "resume-notice"]);
  });
});

// ---------------------------------------------------------------------------
// the intent has to survive a reload, which means it has to be in the URL
// ---------------------------------------------------------------------------
//
// A stated intent that lives only in component state or a one-shot handoff
// evaporates on F5 — the user reloads the page they are standing on and the
// app quietly starts doing something else. These pin the round trip through
// the hash — lib/useHashView.ts's own parser and formatter, driven over a
// string rather than over `window` (which does not exist here).

describe("#/setup?new=1 — the intent round-trips through the URL", () => {
  it("parses the add-company door into a stated intent", () => {
    expect(parseHashString("#/setup?new=1")).toEqual({ view: "setup", intent: "new-company" });
  });

  it("leaves a bare #/setup stating nothing", () => {
    expect(parseHashString("#/setup")).toEqual({ view: "setup" });
    expect(parseHashString("#setup")).toEqual({ view: "setup" });
  });

  it("formats the intent back into a link that can be reloaded and bookmarked", () => {
    expect(hashFor({ view: "setup", intent: "new-company" })).toBe("/setup?new=1");
    expect(hashFor("setup")).toBe("/setup");
  });

  it("survives the reload: format, parse, and still land on the name step", () => {
    const reloaded = parseHashString(`#${hashFor({ view: "setup", intent: "new-company" })}`);
    const entry = "intent" in reloaded ? reloaded.intent : "unspecified";
    expect(setupStepFor(entry, ESTABLISHED)).toBe("name");
  });

  it("does not let an unrelated query param fake the intent", () => {
    expect(parseHashString("#/setup?new=0")).toEqual({ view: "setup" });
    expect(parseHashString("#/setup?other=1")).toEqual({ view: "setup" });
  });
});
