// =================================================================
// pw-screens-op-publisher-create.jsx — Operator · Create a publisher
//
// A page rather than a modal, for one reason: creating a publisher is step one of two, and step two
// hands back a secret that can never be shown again. `POST /admin/v1/publishers/{id}/api-keys`
// returns the raw key exactly once — the API stores only its SHA-256 — so the screen that displays
// it must not be something a stray click on a backdrop can dismiss.
//
// The two steps are deliberately not merged into one button. A publisher with no key is a harmless,
// recoverable state (issue one later from here or by API). A key minted into a page that then
// failed to render is not recoverable at all — it has to be revoked and reissued. So the publisher
// is created first and committed, and the key is a separate, explicit action.
// =================================================================

const PublisherCreateScreen = ({ data, setData }) => {
  const [name, setName] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  // Subscriber-identity settings, asked here rather than defaulted silently — see
  // docs/subscriber-identity-prd.md §4.4. Both are money or ownership decisions and both are much
  // easier to make now than to discover later.
  //
  // The grant starts EMPTY, not "0". Empty means the publisher never chose and grants stay off;
  // typing 0 is an explicit choice to grant nothing. The API treats those as different states and
  // this field must not collapse them.
  const [welcomeCoins, setWelcomeCoins] = React.useState("");
  const [autoSso, setAutoSso] = React.useState(true);
  // `created` holds the publisher once it exists; `apiKey` the one-time secret, if issued.
  const [created, setCreated] = React.useState(null);
  const [apiKey, setApiKey] = React.useState(null);
  const [copied, setCopied] = React.useState(false);

  const trimmed = name.trim();
  // Mirrors PublisherCreateRequest (packages/contracts/src/publisherAuth.ts): 1–200 characters.
  // Checked here so the operator sees it before a round trip, not instead of the server checking.
  const tooLong = trimmed.length > 200;

  const coinsRaw = welcomeCoins.trim();
  const coinsNum = coinsRaw === "" ? null : Number(coinsRaw);
  const coinsBad = coinsRaw !== "" &&
    (!Number.isInteger(coinsNum) || coinsNum < 0 || coinsNum > 100000);

  const canCreate = trimmed.length >= 1 && !tooLong && !coinsBad && !busy;

  // A name already in use is not rejected by the database — `publishers.name` carries no unique
  // constraint — so two tenants can end up indistinguishable in every list in the product. Warn,
  // do not block: a group running "Metro Daily" in two markets is a real thing.
  const duplicate = trimmed && (data.publishers || []).some(
    (p) => String(p.name || "").trim().toLowerCase() === trimmed.toLowerCase(),
  );

  const create = async () => {
    if (!canCreate) return;
    setBusy(true);
    try {
      // No optimistic insert: a publisher_id is minted by the database and the row is useless
      // without it, so there is nothing honest to show until the response comes back.
      // Only send what was actually answered. Omitting welcome_coins_amount is meaningful to the
      // API — it leaves grants disabled — so an unfilled field must not arrive as a 0.
      const body = { name: trimmed, auto_sso_enabled: autoSso };
      if (coinsRaw !== "") body.welcome_coins_amount = coinsNum;

      const res = await window.__api.write("createPublisher", null, {
        body,
        ok: "Publisher created",
        err: "Couldn’t create the publisher",
      });
      setCreated(res);
      // Refresh the listing so Overview shows the new tenant without a reload. Best-effort: the
      // publisher exists either way, and failing this must not look like the create failed.
      window.__api.read("listPublishers")
        .then((publishers) => setData((d) => ({ ...d, publishers })))
        .catch(() => {});
    } catch (_) {
      /* toasted by write() */
    } finally {
      setBusy(false);
    }
  };

  const issueKey = async () => {
    if (!created || busy) return;
    setBusy(true);
    try {
      const res = await window.__api.write("issuePublisherApiKey", null, {
        params: { publisher_id: created.publisher_id },
        ok: "API key issued",
        err: "Couldn’t issue the API key",
      });
      setApiKey(res.api_key);
    } catch (_) {
      /* toasted by write() */
    } finally {
      setBusy(false);
    }
  };

  const copy = (text) => {
    // navigator.clipboard is unavailable on insecure origins; the key is selectable either way, so
    // a failure here degrades to "select it yourself" rather than losing it.
    try {
      navigator.clipboard.writeText(text).then(() => {
        setCopied(true);
        setTimeout(() => setCopied(false), 2000);
      }, () => {});
    } catch (_) { /* select-and-copy still works */ }
  };

  return (
    <Screen>
      <PageHead
        icon={IconBuilding}
        title="Create publisher"
        subtitle="Provisions a new tenant. The API key is shown once and cannot be retrieved later."
        actions={<Btn kind="ghost" size="sm" onClick={() => window.__nav("publishers")}>Back to publishers</Btn>}
      />

      {!created ? (
        <Card title="New tenant" subtitle="A name, and two subscriber settings that are easier to answer now than to unpick later.">
          <div style={{ display: "flex", flexDirection: "column", gap: 14, maxWidth: 520 }}>
            <FieldRow
              label="Publisher name"
              hint="How this tenant appears everywhere in the product. It can be changed later."
              error={tooLong ? "Names are limited to 200 characters." : null}
            >
              <Input
                value={name}
                onChange={(e) => setName(e.target.value)}
                placeholder="Westside Weekly"
                maxLength={220}
                onKeyDown={(e) => { if (e.key === "Enter") create(); }}
              />
            </FieldRow>

            <FieldRow
              label="Welcome coins (optional)"
              hint="Coins granted once to each new reader, paid by this publisher. Leave blank to grant nothing — that is not the same as entering 0, which is an explicit choice to grant nothing and switches the feature on."
              error={coinsBad ? "Enter a whole number between 0 and 100,000." : null}
            >
              <Input
                value={welcomeCoins}
                onChange={(e) => setWelcomeCoins(e.target.value)}
                placeholder="e.g. 50"
                inputMode="numeric"
                onKeyDown={(e) => { if (e.key === "Enter") create(); }}
              />
            </FieldRow>

            <FieldRow
              label="Cross-publisher sign-in"
              hint="On: a reader already signed in at another DropCap publisher arrives here signed in, and is granted this publisher's welcome coins. Off: they sign in here normally first. On by default."
            >
              <label style={{ display: "flex", alignItems: "center", gap: 9, fontSize: 14, color: "var(--ink-2)", cursor: "pointer" }}>
                <input
                  type="checkbox"
                  checked={autoSso}
                  onChange={(e) => setAutoSso(e.target.checked)}
                />
                Recognise readers from other publishers
              </label>
            </FieldRow>

            {autoSso && coinsRaw !== "" && coinsNum > 0 && (
              // Stated plainly because it is the one combination that spends money on readers the
              // publisher never marketed to. A platform-wide cap limits how often one identity can
              // do this, but the publisher should know what they just agreed to.
              <div style={{ fontSize: 13, lineHeight: 1.55, color: "var(--muted)", background: "var(--panel-2)", border: "1px solid var(--line)", borderRadius: 9, padding: "10px 13px" }}>
                Readers arriving from another publisher will each be granted{" "}
                <strong style={{ color: "var(--ink-2)" }}>{coinsNum}</strong> coins at this
                publisher's expense.
              </div>
            )}

            {duplicate && (
              <div style={{ fontSize: 13, lineHeight: 1.55, color: "var(--warn)", background: "var(--warn-soft)", border: "1px solid var(--line)", borderRadius: 9, padding: "10px 13px" }}>
                A publisher called <strong>{trimmed}</strong> already exists. Names are not unique, so
                this will be created as a separate tenant — the two will be told apart only by their id.
              </div>
            )}

            <div>
              <Btn kind="primary" disabled={!canCreate} onClick={create}>
                {busy ? "Creating…" : "Create publisher"}
              </Btn>
            </div>
          </div>
        </Card>
      ) : (
        <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
          <Card title="Publisher created" subtitle="The tenant exists and appears in the publisher list.">
            <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
              <div style={{ fontSize: 15 }}>
                <strong style={{ color: "var(--ink)" }}>{created.name}</strong>
              </div>
              <div style={{ fontSize: 13, color: "var(--muted)" }}>
                Publisher ID
                <div style={{ fontFamily: "var(--mono)", fontSize: 13, color: "var(--ink-2)", marginTop: 4, wordBreak: "break-all" }}>
                  {created.publisher_id}
                </div>
              </div>
            </div>
          </Card>

          <Card
            title="API key"
            subtitle="Optional now, required before this publisher can call the API."
          >
            {!apiKey ? (
              <div style={{ display: "flex", flexDirection: "column", gap: 12, maxWidth: 560 }}>
                <div style={{ fontSize: 13.5, lineHeight: 1.6, color: "var(--ink-2)" }}>
                  Issuing a key shows it once and never again — only its hash is stored. Have somewhere
                  to paste it before you click. You can also skip this and issue one later.
                </div>
                <div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
                  <Btn kind="primary" disabled={busy} onClick={issueKey}>
                    {busy ? "Issuing…" : "Issue first API key"}
                  </Btn>
                  <Btn kind="ghost" onClick={() => window.__nav("publishers")}>Skip for now</Btn>
                </div>
              </div>
            ) : (
              <div style={{ display: "flex", flexDirection: "column", gap: 12, maxWidth: 620 }}>
                <div style={{ fontSize: 13.5, lineHeight: 1.6, color: "var(--warn)", background: "var(--warn-soft)", border: "1px solid var(--line)", borderRadius: 9, padding: "10px 13px" }}>
                  <strong>Copy this now.</strong> It is not stored anywhere you can read it back. If you
                  lose it, revoke the key and issue another.
                </div>
                <code
                  // Selectable and wrapped rather than truncated: if the copy button fails, reading
                  // the key off the screen has to still work.
                  style={{ fontFamily: "var(--mono)", fontSize: 13, background: "var(--panel-2)", border: "1px solid var(--line)", borderRadius: 8, padding: "11px 13px", wordBreak: "break-all", userSelect: "all", color: "var(--ink)" }}
                >
                  {apiKey}
                </code>
                <div style={{ display: "flex", gap: 10, flexWrap: "wrap" }}>
                  <Btn kind="primary" onClick={() => copy(apiKey)}>{copied ? "Copied" : "Copy key"}</Btn>
                  <Btn kind="ghost" onClick={() => window.__nav("publishers")}>Done</Btn>
                </div>
              </div>
            )}
          </Card>
        </div>
      )}
    </Screen>
  );
};

Object.assign(window, { PublisherCreateScreen });
