/* The local view of a person on the network: a petname, a group, a private
   note and the two marks. All of it lives in this app's own config and is
   published nowhere — Soulseek never learns it, and neither does the person
   it describes. The desktop keeps the same index by username
   (src/lib/community.svelte.js), and the same rule: the backend drops an
   entry with nothing set, so the list it answers with is the one we take. */
import * as api from "../lib/api.js";
import { player } from "./player.svelte.js";

export const contacts = $state({
    /** username → { username, petname, group, note, trusted, favorite } */
    by: {},
    /** the list came back; false until it does, and after a failure */
    loaded: false,
    error: "",
});

const BLANK = { petname: null, group: null, note: null, trusted: false, favorite: false };
/** what the backend stores: a trimmed string, or nothing at all */
const trimmed = (v) => { const s = typeof v === "string" ? v.trim() : v; return s ? s : null; };

function index(list) {
    contacts.by = Object.fromEntries((list || []).map((c) => [c.username, c]));
    contacts.loaded = true;
    contacts.error = "";
}

let inflight = null;
/** Read the whole list — once at boot, and again whenever anyone asks. A
 *  failure is quiet: a petname is an enhancement, not the screen. */
export function loadContacts() {
    if (inflight) return inflight;
    inflight = api
        .contactsMetaList()
        .then(index)
        .catch((e) => { contacts.loaded = false; contacts.error = String(e); })
        .finally(() => { inflight = null; });
    return inflight;
}

export const metaOf = (u) => contacts.by[u] ?? { username: u, ...BLANK };
/** The name to show: the petname when there is one, the username otherwise. */
export const display = (u) => contacts.by[u]?.petname || u;
/** The username, but only while a petname stands in front of it — a stranger
 *  is never quietly turned into someone you know. */
export const realName = (u) => (contacts.by[u]?.petname ? u : "");

/** Upsert one person. The answer is the whole list after the write, so it is
 *  taken as it comes rather than patched into our own copy: that is how an
 *  entry with nothing left in it disappears. A failure goes to the bar. */
export async function setMeta(username, patch) {
    const m = { ...metaOf(username), ...patch };
    try {
        index(
            await api.contactMetaSet({
                username,
                petname: trimmed(m.petname),
                group: trimmed(m.group),
                note: trimmed(m.note),
                trusted: !!m.trusted,
                favorite: !!m.favorite,
            }),
        );
    } catch (e) {
        player.error = String(e);
    }
}

/** Everything blank: the entry leaves the list rather than being stored empty. */
export const clearMeta = (u) => setMeta(u, { petname: "", group: "", note: "", trusted: false, favorite: false });
