// NO PROD JSON API (bucket C) — mock until backend (see PROD_API_INVENTORY.md)
// Represents: GET /myaccount · UsersController::myAccount — see docs/ISYSTEM_REFERENCE.md §Batch 9.1
/* My account — the signed-in operator's own profile.
   NEW SCREEN. The prototype had no page for this route, and the coverage check
   could not see the gap: it ran at CONTROLLER granularity, UsersController was
   marked covered by HostUsers.jsx, and its other 25 view-returning actions were
   never examined. `myAccount` is also declared `static function`, which an
   earlier `public function` regex missed. Both are fixed; see Batch 9 preamble.

   This is NOT a URL-only screen. It is the one screen in the missing set that
   the real platform links from chrome every operator sees:
     · header.blade.php:420       — the user dropdown
     · sidebar.blade.php:80-82    — mobile only (`hide-on-desktop`)

   Real action (UsersController.php, static):
       $row      = DB::table('users')->where('id', Auth::id())->first();
       $chatCode = UsersController::getUserChatCode(Auth::user()->user_path);
       return view('admin.users.myaccount', compact('id','chatCode','row'));
   Four lines. No service, no repository, no FormRequest — everything below is
   the blade (admin/users/myaccount.blade.php, 332 lines).

   Layout fidelity: the real page is a single centred card, max-width 800px,
   one <form action="/saveMyAccount" method="post"> with @csrf. Kept.

   KNOWN BUG — THE SAVE ENDPOINT HAS NO HANDLER.
   routes/admin.php:92-93 registers POST /saveMyAccount -> UsersController@saveMyAccount.
   That method does not exist: grepping app/ for `saveMyAccount` returns the
   route line and nothing else, UsersController declares no traits and no
   __call. Every save on the real screen raises BadMethodCallException -> 500.
   The blade even defines a post_save_my_account() callback that would pop
   successMessage('OK!'), so the page was written expecting an endpoint that was
   never merged.
   Per the repo's known-bug policy the prototype implements the EVIDENT INTENT
   (the save applies, to prototype state) and states the divergence on screen
   rather than only in this comment.
   <!-- SUGGESTION: implement UsersController::saveMyAccount, or point the form at an endpoint that exists. Today every My-account save 500s. -->

   Second, harmless, real-platform oddity: the blade nests a second <form>
   (line 40) inside the first (line 7). HTML parsers discard the inner tag, so
   the submit button belongs to the outer form and does post to /saveMyAccount.
   Not reproduced — there is no reason to copy invalid markup.

   Deliberately NOT added (the real screen has none of these): avatar upload,
   email change, username change, session list / "sign out everywhere", API
   tokens, notification preferences, a delete-account control, or an activity
   log. Timezone, password, 2FA, payment fields and the chat code are the whole
   surface. */

const HMA_TZ = [
  // timezones() on the real platform is the full PHP tz list. The subset here
  // covers the regions the platform actually operates in; the select is marked
  // as a subset on screen so nobody reads it as the real list.
  { id: "UTC", label: "UTC" },
  { id: "Europe/Rome", label: "Europe/Rome (CET)" },
  { id: "Europe/London", label: "Europe/London (GMT)" },
  { id: "Europe/Lisbon", label: "Europe/Lisbon (WET)" },
  { id: "Europe/Malta", label: "Europe/Malta (CET)" },
  { id: "America/Argentina/Buenos_Aires", label: "America/Argentina/Buenos_Aires (ART)" },
  { id: "America/Sao_Paulo", label: "America/Sao_Paulo (BRT)" },
  { id: "America/Santiago", label: "America/Santiago (CLT)" },
  { id: "America/Bogota", label: "America/Bogota (COT)" },
  { id: "America/Mexico_City", label: "America/Mexico_City (CST)" },
  { id: "Africa/Nairobi", label: "Africa/Nairobi (EAT)" },
  { id: "Africa/Lagos", label: "Africa/Lagos (WAT)" },
];

/* DepositsController::getPaymentFields() returns EXACTLY this today — one
   method, two fields. It is then intersected with
   DepositMethodsController::getDepositMethods('', skin_id, 0) keyed by
   method_code, so the block renders only when the method is enabled for the
   operator's skin. Both facts are surfaced on screen. */
const HMA_PAYMENT_FIELDS = {
  "wire-argentina": {
    name: "Wire transfer (Argentina)",
    fields: { recipient_name: "Recipient Name", recipient_address: "Recipient Address" },
  },
};

const HMA_DEFAULT = {
  timezone: "America/Argentina/Buenos_Aires",
  g_twofa: true,
  customize_methods: {},
  payment_fields: { "wire-argentina": { recipient_name: "", recipient_address: "" } },
  enable_chat_code_edit: false,
  chat_code: "",
};

/* The signed-in operator — sb.me(), which resolves current_app_user() to this
   auth user's row in `users`. This used to be a literal ("j.moreno", id 1042,
   "Iwakiri BR"), which meant My account showed somebody else's name to every
   operator who opened it — on the one screen whose entire subject is "you".

   role/skin labels come from the embedded user_roles and skins rows rather
   than from a level→name map restated here. */
const useHmaMe = () => {
  const [state, setState] = React.useState({ loading: true, error: null, me: null });
  React.useEffect(() => {
    let alive = true;
    Promise.resolve(window.sb.me()).then(r => {
      if (!alive) return;
      setState(r && r.ok ? { loading: false, error: null, me: r.data }
                         : { loading: false, error: (r && r.error) || null, me: null });
    });
    return () => { alive = false; };
  }, []);
  return state;
};

/* getUserChatCode() walks UP the hierarchy, so an operator with no own code
   inherits the parent's; hasOwnChatCode() then decides which hint renders.
   Neither the code nor the walk exists in this schema — there is no chat-code
   column on users or skins — so the field shows what it is rather than a
   fabricated Tawk.to snippet with a made-up widget id.
   <!-- SUGGESTION: model the support-chat snippet. isystem stores it per user and resolves it by walking user_path upward, so an operator with none of their own inherits their parent's. Nothing in this schema carries it, so the chat block cannot be wired. --> */

const hmaToast = (title, detail) => window.hrsToast && window.hrsToast(title, detail);

/* Password rules. The real screen ships NO client-side validation at all and
   no FormRequest — the endpoint that would have validated does not exist. The
   rules below are the platform's own password rules, taken from the signup /
   user-create forms, applied here as the evident intent. */
const hmaPasswordProblems = (cur, next, confirm) => {
  const out = [];
  if (!next && !confirm && !cur) return out;          // not changing the password
  if (!cur) out.push("Current password is required to set a new one.");
  if (next && next.length < 8) out.push("New password must be at least 8 characters.");
  if (next && !/[A-Za-z]/.test(next)) out.push("New password must contain a letter.");
  if (next && !/[0-9]/.test(next)) out.push("New password must contain a digit.");
  if (next !== confirm) out.push("New password and confirmation do not match.");
  if (next && cur && next === cur) out.push("New password must differ from the current one.");
  return out;
};

const HmaField = ({ label, required, hint, children }) => (
  <div className="hma-field">
    <label className="hma-label">
      {required && <span className="hma-req">*</span>} {label}
    </label>
    {children}
    {hint && <div className="hma-hint">{hint}</div>}
  </div>
);

/* Local, deliberately NOT persisted — password visibility must never survive a
   reload. Aliased because every top-level name in this prototype is a window
   global and `useState` is already taken. */
const hmaUseState = React.useState;

const HmaSecret = ({ id, label, value, onChange, autoComplete, required }) => {
  const [shown, setShown] = hmaUseState(false);
  return (
    <HmaField label={label} required={required}>
      <div className="hma-secret">
        <input
          className="input hma-input" id={id} type={shown ? "text" : "password"}
          value={value} autoComplete={autoComplete || "off"}
          onChange={(e) => onChange(e.target.value)}
        />
        <button type="button" className="hma-eye" onClick={() => setShown(!shown)}
          aria-label={shown ? "Hide" : "Show"} title={shown ? "Hide" : "Show"}>
          <Icon name={shown ? "lock" : "eye"} size={14} />
        </button>
      </div>
    </HmaField>
  );
};

/* The My Account 2FA panel. Reads its own state rather than taking props: it is
   the only consumer, and threading a feed through the form state was how the old
   toggle ended up bound to a field nothing sent.

   Self-disable is permitted for EVERY role including the mandatory tier —
   confirmed, and 068 makes it real rather than advisory by checking the per-user
   override ahead of the role tier. Reaching the RPC at all requires aal2, which
   IS the "valid OTP as proof of identity" §4 asks for; there is no second code
   prompt bolted on top of the one the session already passed. */
const HmaTwoFactorPanel = () => {
  const [busy, setBusy] = React.useState(false);
  const [note, setNote] = React.useState("");
  const [msg, setMsg] = React.useState(null);
  const [err, setErr] = React.useState(null);
  const [tick, setTick] = React.useState(0);

  const meFeed = useHrsFetch(() => window.sb.me(), []);
  const meId = meFeed.data && meFeed.data.id;
  const pol = useHrsFetch(() => (meId ? window.sb.twofaPolicy(Number(meId)) : Promise.resolve(null)),
                          [meId, tick]);
  const st = useHrsFetch(() => (meId ? window.sb.list("user2fa", { limit: 1, filters: { user: meId } })
                                     : Promise.resolve(null)), [meId, tick]);
  const rec = ((st.data || [])[0]) || null;
  const p = pol.data || null;

  const disable = async () => {
    if (busy) return;
    setBusy(true); setErr(null); setMsg(null);
    const r = await window.sb.selfDisable2fa(note.trim() || null);
    setBusy(false);
    if (!r || !r.ok) { setErr((r && r.error && r.error.message) || "Refused."); return; }
    setMsg(r.data && r.data.was_mandatory
      ? "Disabled. Your role normally requires two-factor, so this is recorded against your account."
      : "Two-factor disabled for your account.");
    setNote(""); setTick(t => t + 1);
  };

  if (meFeed.loading || pol.loading || st.loading) return <div className="hrs-skel" style={{ height: 120 }} />;
  if (meFeed.error) return <HrsError error={meFeed.error} onRetry={() => setTick(t => t + 1)} />;

  const active = !!(rec && rec.enrolled_at && !rec.disabled_at);

  return (
    <div className="panel hma-card" style={{ display: "grid", gap: 12 }}>
      <div style={{ fontSize: 13 }}>
        <b>Status:</b>{" "}
        {rec && rec.disabled_at ? "Disabled" : active ? "Active" : "Not enrolled"}
        {p && <> · <b>Policy:</b> {p.required ? "required for your role" : "not required"}</>}
      </div>

      {p && p.mandatory && rec && rec.disabled_at && (
        <div className="hu-errbox" style={{ alignItems: "flex-start", lineHeight: 1.55 }}>
          <Icon name="alert" size={14} style={{ flex: "0 0 auto", marginTop: 2 }} />
          <span>
            Your role normally requires two-factor and it is currently disabled on
            your account. This is visible to super admins.
          </span>
        </div>
      )}

      {active ? (
        <div style={{ display: "grid", gap: 8 }}>
          <div style={{ fontSize: 13, color: "var(--n-600)" }}>
            Disabling turns the requirement off for your account. It does not
            remove the authenticator from your phone — to start over with a new
            device, ask a super admin to reset it.
          </div>
          <input className="input" value={note} placeholder="Reason (optional)"
            onChange={(e) => setNote(e.target.value)} />
          <div>
            <button className="rpt-btn rpt-btn--blue" style={{ minWidth: 0, height: 38 }}
              disabled={busy} onClick={disable}>
              {busy ? "Working…" : "Disable two-factor"}
            </button>
          </div>
        </div>
      ) : (
        <div style={{ fontSize: 13, color: "var(--n-600)" }}>
          {rec && rec.disabled_at
            ? "Two-factor is disabled for your account. A super admin can reset it, which returns you to whatever your role and brand require."
            : "You have not enrolled yet. Enrolment happens at sign-in when your role or brand requires it."}
        </div>
      )}

      {msg && <div style={{ fontSize: 13, color: "var(--g-700, #15803d)" }}>{msg}</div>}
      {err && <div className="hu-errbox"><Icon name="alert" size={14} /> {err}</div>}
    </div>
  );
};

const HostMyAccount = () => {
  const meState = useHmaMe();
  const me = meState.me;
  const [form, setForm] = React.useState(HMA_DEFAULT);
  /* Timezone comes from users.timezone once the row lands. Seeding the form
     from localStorage meant the select showed a previous session's choice for a
     different account. */
  React.useEffect(() => {
    if (me && me.timezone) setForm(f => ({ ...f, timezone: me.timezone }));
  }, [me]);
  const [cur, setCur] = React.useState("");
  const [next, setNext] = React.useState("");
  const [confirm, setConfirm] = React.useState("");
  const [errors, setErrors] = React.useState([]);

  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
  const dirty = !!(me && form.timezone !== me.timezone) || !!(cur || next || confirm);

  // getDepositMethods('', skin_id, 0) — modelled as: this skin has the method on.
  const enabledMethods = Object.entries(HMA_PAYMENT_FIELDS);

  const submit = () => {
    const problems = hmaPasswordProblems(cur, next, confirm);
    if (!form.timezone) problems.unshift("Timezone is required.");
    setErrors(problems);
    if (problems.length) return;

    setCur(""); setNext(""); setConfirm("");
    hmaToast("Not saved — no write path yet",
      `Input is valid. Would set users.timezone = ${form.timezone}${next ? " and change the password through the auth provider, not this table" : ""}. Reads are live; writes land in stage 7.`);
  };

  const ownChat = false;                  // no chat-code column exists to own one
  const chatValue = form.chat_code;

  return (
    <HrsShell
      title="My account"
      subtitle="The signed-in operator's own profile — timezone, password, two-factor, payout fields and the support-chat script."
      gate={["auth", "admin", "2fa", "g2fa"]}
      gateNote={<> The screen itself applies no role check: every authenticated operator sees their own row. Two blocks are hidden for <code>isCustomCare()</code> (payment methods, chat code) and the 2FA switch renders only when <code>user_level &lt;= ADMIN_LEVEL</code>.</>}
      explainer={{
        title: "What this screen is, in plain English",
        bullets: [
          "Your own account, not somebody else's — the real action reads one row: users WHERE id = Auth::id().",
          "It is reachable from the header user dropdown, and on mobile from the sidebar. It is not in the desktop sidebar.",
          "The chat script walks UP your user_path: with none of your own, you inherit your parent's.",
          "The payment-field block only appears when a method from getPaymentFields() is enabled for your skin. Today that list has exactly one method.",
        ],
      }}
    >
      <div className="hma-wrap">

        {/* One row is the whole subject of this screen. If it did not load,
            every field below would render a default that belongs to nobody. */}
        {meState.loading && <HrsSkeleton rows={5} cols={2} />}
        {!meState.loading && meState.error && <HrsError error={meState.error} />}
        {!meState.loading && !meState.error && !me && (
          <HrsEmpty>Signed in, but this auth user has no row in <code>users</code>.</HrsEmpty>
        )}
        {!meState.loading && me && (<>

        {/* Real-platform divergence, on screen rather than only in the source. */}
        <div className="hma-bug">
          <div className="hma-bug__h"><Icon name="alert" size={13} /> The real save endpoint does not exist</div>
          <div className="hma-bug__b">
            <code>routes/admin.php:92</code> registers <code>POST /saveMyAccount</code> → <code>UsersController@saveMyAccount</code>,
            but that method is not defined anywhere in <code>app/</code> — no trait, no <code>__call</code>.
            Every save on the live screen raises <code>BadMethodCallException</code> and returns 500.
            Per this repo's known-bug policy the button below implements the <b>evident intent</b> and applies
            the change to prototype state.
          </div>
        </div>

        {errors.length > 0 && (
          <div className="hma-errs" role="alert">
            <div className="hma-errs__h"><Icon name="alert" size={13} /> Please correct the following errors</div>
            <ul>{errors.map((e, i) => <li key={i}>{e}</li>)}</ul>
          </div>
        )}

        <HrsSection title="Account" sub={me ? `${me.username} · level ${me.user_level} · skin ${me.skin_id}` : "…"}>
          <div className="panel hma-card">
            <HmaField
              label="Timezone" required
              hint={<>Written to <code>users.timezone</code>. The real select is the full PHP <code>timezones()</code> list; this is the subset the platform operates in.</>}
            >
              <select className="select hma-input" value={form.timezone} onChange={(e) => set("timezone", e.target.value)}>
                <option value="">Select an option</option>
                {HMA_TZ.map(t => <option key={t.id} value={t.id}>{t.label}</option>)}
              </select>
            </HmaField>
          </div>
        </HrsSection>

        <HrsSection title="Password" sub="Leave all three blank to keep the current password.">
          <div className="panel hma-card">
            <HmaSecret id="hma-old" label="Current password" value={cur} onChange={setCur} />
            <HmaSecret id="hma-new" label="New password" value={next} onChange={setNext} autoComplete="new-password" />
            <HmaSecret id="hma-cnf" label="Confirm password" required value={confirm} onChange={setConfirm} autoComplete="new-password" />
            <div className="hma-hint">
              The real screen ships no client-side validation and no FormRequest — the endpoint that would
              have validated is the one that is missing. The rules applied here are the platform's own,
              taken from the user-create forms: 8+ characters, at least one letter and one digit.
            </div>
          </div>
        </HrsSection>

        {/* TWO-FACTOR — §4's self-disable, for every role.

            What was here: a Toggle bound to `form.g_twofa`, rendered only when
            `me.user_level <= 2`, which recorded nothing. Two problems, and the
            second is the interesting one.

            It was a dead control — the flag was never sent anywhere, so the
            switch reported a change it had not made.

            And `user_level <= 2` is a seniority test written as an inequality,
            which this schema does not support: levels 1, 4, 6 and 9 sit BESIDE
            the chain 0→2→8→10→15→20→30, not below it. So the panel was hidden
            from Affiliate, Customer care, Administration and Regulator — four of
            the five roles for which 2FA is MANDATORY. Migration 040 aborts the
            apply on that shape in a function body; the same reasoning applies in
            a component, and nothing was checking.

            §4 grants self-disable to any user of any role, so there is no level
            condition here at all. */}
        <HrsSection title="Two-factor authentication">
          <HmaTwoFactorPanel />
        </HrsSection>

        <HrsSection
          title="Payment methods"
          sub="Your own payout details, per method. Hidden entirely for Custom Care operators."
        >
          <div className="panel hma-card">
            {enabledMethods.map(([code, m]) => (
              <div key={code} className="hma-method">
                <div className="hma-method__h">
                  <span className="hma-method__n">{m.name}</span>
                  <label className="hma-editchk">
                    <input
                      type="checkbox"
                      checked={!!form.customize_methods[code]}
                      onChange={(e) => set("customize_methods", { ...form.customize_methods, [code]: e.target.checked })}
                    />
                    <span>Edit payment method</span>
                  </label>
                </div>
                <div className="hma-tablewrap">
                  <table className="data-table hma-table">
                    <thead><tr><th style={{ width: "40%" }}>Payment field</th><th style={{ width: "60%" }}>Payment value</th></tr></thead>
                    <tbody>
                      {Object.entries(m.fields).map(([fk, flabel]) => (
                        <tr key={fk}>
                          <td className="hma-td-k">{flabel}</td>
                          <td>
                            <input
                              className="input hma-input"
                              readOnly={!form.customize_methods[code]}
                              value={(form.payment_fields[code] || {})[fk] || ""}
                              onChange={(e) => set("payment_fields", {
                                ...form.payment_fields,
                                [code]: { ...(form.payment_fields[code] || {}), [fk]: e.target.value },
                              })}
                            />
                          </td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              </div>
            ))}
            <div className="hma-hint">
              <code>DepositsController::getPaymentFields()</code> returns exactly one method today —
              <code> wire-argentina</code> with <code>recipient_name</code> and <code>recipient_address</code>.
              It is intersected with the deposit methods enabled for your skin, so on a skin without that
              method this whole block does not render.
            </div>
          </div>
        </HrsSection>

        <HrsSection title="Tawk.to chat code" sub="The support-widget script served to your players. Hidden for Custom Care operators.">
          <div className="panel hma-card">
            <label className="hma-editchk hma-editchk--block">
              <input
                type="checkbox"
                checked={form.enable_chat_code_edit}
                onChange={(e) => set("enable_chat_code_edit", e.target.checked)}
              />
              <span>Enable editing of Tawk.to chat script</span>
            </label>
            <HmaField label="Paste your full Tawk.to widget script">
              <textarea
                className="input hma-textarea" rows={6}
                readOnly={!form.enable_chat_code_edit}
                placeholder="Paste the full script here..."
                value={chatValue}
                onChange={(e) => set("chat_code", e.target.value)}
              />
            </HmaField>
            <div className="hma-chathint hma-chathint--inherited">
              <Icon name="info" size={12} />{" "}
              No chat-code column exists in this schema, so nothing is stored, inherited or served. The field
              is left in place because the real screen has it — it is empty for every operator, not for you.
            </div>
            <div className="hma-hint">
              <code>getUserChatCode(user_path)</code> walks up the hierarchy until it finds a code, so an
              operator with none of their own serves the parent's. Saving a value here makes it your own and
              stops the inheritance for everything below you.
            </div>
          </div>
        </HrsSection>

        <div className="hma-actions">
          <button className="hrs-btn hrs-btn--filters hma-save" disabled={!dirty} onClick={submit}>
            <Icon name="check" size={14} /> Save
          </button>
          <span className="hma-hint">
            {dirty
              ? <>Applies to prototype state. The real target, <code>POST /saveMyAccount</code>, has no handler.</>
              : <>Nothing changed yet.</>}
          </span>
        </div>
        </>)}
      </div>
    </HrsShell>
  );
};

window.HostMyAccount = HostMyAccount;
