/* What the phone says when things go wrong.

   The backend answers in Rust. `ClientError`'s Display impls (crates/
   soulseek-client/src/lib.rs) hand up `io: {0}`, `protocol: {0}`, `login
   failed: INVALIDPASS`, `user {0} is offline or unknown`, `timed out`; the
   commands add their own literals (`not connected to Soulseek`, `no stored
   credentials`, `no cache dir`, `could not reach server: {e}`); rusqlite and
   std::io come through `e.to_string()`. Fifty-nine places in src/mobile/ put
   one of those straight into a field a person reads, and the red toast drew
   it verbatim — a phone in a lift showed, under the password box, "could not
   reach server: io: failed to lookup address information: no address
   associated with hostname".

   ONE translator, called where the words are DRAWN rather than where they
   are caught. The stores keep the raw string — Diagnostics and the walk still
   see exactly what Rust said — and only the sentence on screen is ours, so
   every future `String(e)` is covered by the same rule as the fifty-nine
   already here. A message the backend already wrote in English ("The playlist
   needs a name") matches nothing and passes through untouched.

   The order below is the order they are tried: the most specific shape
   first, the two catch-alls (`io:` / `protocol:`) last but one, and anything
   unrecognised kept as it is, capped. */

/** The message inside whatever was thrown: an Error, a plain string (which
 *  is what Tauri rejects with), or something with neither. */
const clean = (e) => String(e?.message ?? e ?? "").replace(/^Error:\s*/, "").trim();

const RULES = [
    /* this round's own guard (src-tauri/src/pipeline.rs): the library root
       could not be made or read, so the rescan refused rather than pruning
       every row whose file it could not see */
    [/^library-folder-missing:\s*(.*)$/i, (m) => `The library folder isn't there any more — ${m[1]}. Reconnect the storage, or pick the folder again in You.`],
    /* with_client()'s funnel, and start_search's plainer version */
    [/not connected( to (soulseek|server))?$/i, () => "Not signed in to Soulseek. Sign in on Community and try again."],
    [/^no stored credentials$/i, () => "No Soulseek account saved on this phone yet. Sign in on Community."],
    /* the server's own refusal words, which are not for reading */
    [/^login failed:\s*(.*)$/i, (m) => (/invalidpass/i.test(m[1]) ? "Soulseek refused that password." : /invalidusername/i.test(m[1]) ? "Soulseek refused that username." : `Soulseek refused the sign-in (${m[1]}).`)],
    [/^could not reach server/i, () => "Couldn't reach the Soulseek server. Check the connection and try again."],
    [/^timed out$/i, () => "The network didn't answer in time."],
    [/^user (.+?) is offline or unknown$/i, (m) => `${m[1]} isn't online right now.`],
    [/^could not reach peer (.+)$/i, (m) => `${m[1]}'s client didn't answer — offline, or behind a firewall.`],
    [/^transfer rejected:\s*(.*)$/i, (m) => `They refused the transfer${m[1] ? ` (${m[1].replace(/\.$/, "")})` : ""}.`],
    [/^(io|protocol):\s*(.*)$/i, () => "The network didn't answer. Check the connection and try again."],
    /* the library's own two: a database that will not open, and a folder the
       phone has not been given */
    [/unable to open database file|os error (2|3)\b|cannot find the (path|file)/i, () => "The library folder isn't readable right now."],
    [/permission denied|os error 13\b/i, () => "The phone refused access to that folder — check the app's storage permission in You."],
    [/^no cache dir$/i, () => "This phone has no cache folder to work in."],
    /* a page with no Tauri under it — the browser walk, and only ever that */
    [/reading 'invoke'|transformCallback/i, () => "The app's backend isn't here (this is the browser walk)."],
];

/** Longer than this and it was never a sentence anyone meant to write. */
const CAP = 180;

/** The backend's error → something a person can act on. */
export function say(e) {
    const raw = clean(e);
    if (!raw) return "";
    for (const [re, fn] of RULES) {
        const m = re.exec(raw);
        if (m) return fn(m);
    }
    return raw.length > CAP ? raw.slice(0, CAP - 1) + "…" : raw;
}

/** Is this the kind of trouble Community fixes? The two places that offer a
 *  way to sign in rather than a Try again ask here. */
export const signedOut = (e) => /not connected|no stored credentials/i.test(clean(e));
