const TEST_ATTEMPT_SESSION_VERSION = 1;
const STORAGE_PREFIX = "pieoneer:testAttemptSession:v1:";
const ANON_DEVICE_ID_KEY = "pieoneer:anonymousDeviceId:v1";
export function createMemoryStorage(initial = {}) {
    const store = new Map(Object.entries(initial));
    return {
        getItem: (k) => (store.has(k) ? store.get(k) : null),
        setItem: (k, v) => {
            store.set(k, v);
        },
        removeItem: (k) => {
            store.delete(k);
        },
    };
}
export function getBrowserLocalStorage() {
    try {
        // eslint-disable-next-line no-undef
        if (typeof window === "undefined")
            return null;
        // eslint-disable-next-line no-undef
        if (!window.localStorage)
            return null;
        // eslint-disable-next-line no-undef
        return window.localStorage;
    }
    catch {
        return null;
    }
}
function nowIso() {
    return new Date().toISOString();
}
// Lightweight, deterministic hash for IDs (no crypto dependency).
function fnv1a32Hex(input) {
    let hash = 0x811c9dc5; // offset basis
    for (let i = 0; i < input.length; i++) {
        hash ^= input.charCodeAt(i);
        // hash *= 16777619 (with 32-bit overflow)
        hash =
            (hash +
                ((hash << 1) +
                    (hash << 4) +
                    (hash << 7) +
                    (hash << 8) +
                    (hash << 24))) >>>
                0;
    }
    return hash.toString(16).padStart(8, "0");
}
function randomId() {
    const cryptoAny = globalThis?.crypto;
    const uuid = cryptoAny?.randomUUID?.();
    if (typeof uuid === "string" && uuid.length > 0) {
        return uuid;
    }
    return `anon_${Math.random().toString(16).slice(2)}_${Date.now().toString(16)}`;
}
export function getOrCreateAnonymousDeviceId(storage) {
    const existing = storage.getItem(ANON_DEVICE_ID_KEY);
    if (existing)
        return existing;
    const created = randomId();
    storage.setItem(ANON_DEVICE_ID_KEY, created);
    return created;
}
export function createTestAttemptSessionIdentifier(args) {
    const { assessmentId, assignmentId, userId, storage } = args;
    if (!assessmentId) {
        throw new Error("assessmentId is required to create a TestSession identifier");
    }
    const subject = userId || getOrCreateAnonymousDeviceId(storage);
    const source = `v1|${assessmentId}|${assignmentId || ""}|${subject}`;
    const seed = fnv1a32Hex(source);
    return { testAttemptSessionIdentifier: `tas_v1_${seed}`, seed };
}
export function getTestAttemptSessionStorageKey(testAttemptSessionIdentifier) {
    return `${STORAGE_PREFIX}${testAttemptSessionIdentifier}`;
}
export function loadTestAttemptSession(storage, testAttemptSessionIdentifier) {
    const raw = storage.getItem(getTestAttemptSessionStorageKey(testAttemptSessionIdentifier));
    if (!raw)
        return null;
    try {
        const parsed = JSON.parse(raw);
        if (!parsed || parsed.version !== TEST_ATTEMPT_SESSION_VERSION)
            return null;
        if (parsed.testAttemptSessionIdentifier !== testAttemptSessionIdentifier) {
            return null;
        }
        return parsed;
    }
    catch {
        return null;
    }
}
export function saveTestAttemptSession(storage, session) {
    const updated = {
        ...session,
        updatedAt: nowIso(),
    };
    storage.setItem(getTestAttemptSessionStorageKey(session.testAttemptSessionIdentifier), JSON.stringify(updated));
}
export function createNewTestAttemptSession(args) {
    const startedAt = nowIso();
    return {
        version: TEST_ATTEMPT_SESSION_VERSION,
        testAttemptSessionIdentifier: args.testAttemptSessionIdentifier,
        assessmentId: args.assessmentId,
        startedAt,
        updatedAt: startedAt,
        navigationState: {
            currentItemIndex: -1,
            visitedItemIdentifiers: [],
        },
        realization: {
            seed: args.seed,
            itemIdentifiers: args.itemIdentifiers,
        },
        itemSessions: {},
    };
}
export function upsertVisitedItem(session, itemIdentifier) {
    if (!itemIdentifier)
        return session;
    const visited = new Set(session.navigationState.visitedItemIdentifiers || []);
    visited.add(itemIdentifier);
    return {
        ...session,
        navigationState: {
            ...session.navigationState,
            visitedItemIdentifiers: Array.from(visited),
        },
    };
}
export function setCurrentPosition(session, args) {
    return {
        ...session,
        navigationState: {
            ...session.navigationState,
            currentItemIndex: args.currentItemIndex,
            currentSectionIdentifier: args.currentSectionIdentifier,
        },
    };
}
export function upsertItemSessionFromPieSessionChange(session, args) {
    const { itemIdentifier, pieSessionId, isCompleted } = args;
    if (!itemIdentifier || !pieSessionId)
        return session;
    const now = nowIso();
    const existing = session.itemSessions[itemIdentifier];
    const attemptCount = existing && existing.pieSessionId && existing.pieSessionId !== pieSessionId
        ? existing.attemptCount + 1
        : existing
            ? existing.attemptCount
            : 1;
    const completed = !!(isCompleted ?? existing?.isCompleted);
    const next = {
        itemIdentifier,
        pieSessionId,
        attemptCount,
        isCompleted: completed,
        startedAt: existing?.startedAt || now,
        updatedAt: now,
        completedAt: completed ? existing?.completedAt || now : undefined,
        session: args.session ?? existing?.session,
    };
    return {
        ...session,
        itemSessions: {
            ...session.itemSessions,
            [itemIdentifier]: next,
        },
    };
}
//# sourceMappingURL=TestSession.js.map