"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const dayjs_1 = __importDefault(require("dayjs"));
const Addon_1 = require("../../models/Addon/Addon");
const BasketAddonRow_1 = __importDefault(require("../../models/BasketAddonRow"));
const BasketRow_1 = __importDefault(require("../../models/BasketRow"));
const Room_1 = require("../../models/Room/Room");
const RoomRate_1 = require("../../models/Room/RoomRate");
const RoomstaySession_1 = require("../../models/RoomstaySession");
const StorageProvider_1 = require("./StorageProvider");
const Debounce_1 = require("../../util/Debounce");
/**
 * Session provider is in charge of handling the loading, saving and passing of the current Engine Session.
 *
 * Desired flow:
 *   * App Opened
 *   * Session Provider loaded
 *   * New User Session Created
 *   * User moves through the app, updating the session with vital details (current step, basket context )
 *   * User refreshes browser - session is re-loaded and user resumes where they left off
 *   * User proceeds to checkout.
 *   * Session is saved.
 *
 * Mini Booking Widget:
 *   * User loads mini booking widget, selects dates, people
 *   * Rates are loaded, and user selects a rate, redirecting to Full Booking Engine
 *   * Session Provider creates a SessionID to pass to redirected path
 *   * Session Provider loads the passed SessionID and loads the correct session
 *   * Resume to Desired Flow
 *
 * Re-targeting:
 * ---Since re-targeting will happen on client site, under client domain, localStorage will be preserved, ergo we can load the previous incomplete sessions back to the user
 *   * User lands on client Homepage
 *   * Session Provider loads previous incomplete sessions
 *   * A slider with details of previous baskets is shown back to the user
 *   * User clicks on a previous session and is brought to the Booking Engine
 *   * Resume to Desired Flow
 */
class SessionProvider {
    constructor() {
        this.cachedSession = {};
        this.syncSession = () => __awaiter(this, void 0, void 0, function* () {
            try {
                const currentSession = yield this.getSession();
                const cachedSession = this.cachedSession;
                this.cachedSession = {};
                yield this.saveSession(Object.assign(Object.assign({}, currentSession), cachedSession));
            }
            catch (error) {
                console.error('Failed to sync session:', error);
            }
        });
        this.debouncedUpdateSession = (0, Debounce_1.debounce)(() => __awaiter(this, void 0, void 0, function* () {
            yield this.syncSession();
        }), 300);
    }
    updateSessionWithAsync() {
        return __awaiter(this, void 0, void 0, function* () {
            yield (0, Debounce_1.delay)(300); // 300 milliseconds delay -- Why is a delay necessary?
            yield this.syncSession();
        });
    }
    setHotelID(hotelID) {
        return __awaiter(this, void 0, void 0, function* () {
            this.hotelID = hotelID;
            this.updateSession({ hotelID });
        });
    }
    initialize(sessionID, source) {
        return __awaiter(this, void 0, void 0, function* () {
            yield this.setSessionID(sessionID, source);
        });
    }
    setSessionID(sessionID, source) {
        return __awaiter(this, void 0, void 0, function* () {
            const foundSession = yield this.findOrCreateSessionID(sessionID, source);
            if (foundSession) {
                this.currentSessionID = foundSession;
                // Never read?
                const storage = new StorageProvider_1.StorageProvider(sessionStorage);
                yield storage.write('currentSession', this.currentSessionID);
            }
        });
    }
    /**
     * @returns the current session ID
     */
    getCurrentSessionID() {
        return this.currentSessionID;
    }
    getSession() {
        const storage = new StorageProvider_1.StorageProvider();
        const sessionFromStorage = storage.read(this.getSessionKey());
        if (sessionFromStorage) {
            return this.unSerializeSession(sessionFromStorage);
        }
        else {
            return this.generateNewSession('');
        }
    }
    saveSession(changedSession) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!changedSession) {
                return console.log('Attempted to save empty session');
            }
            const storage = new StorageProvider_1.StorageProvider();
            const serializedSession = this.serializeSession(changedSession);
            yield storage.write(this.getSessionKey(serializedSession.id), serializedSession);
        });
    }
    updateSession(partialSession) {
        return __awaiter(this, void 0, void 0, function* () {
            Object.assign(this.cachedSession, partialSession);
            this.debouncedUpdateSession();
        });
    }
    updateSessionAsync(partialSession) {
        return __awaiter(this, void 0, void 0, function* () {
            Object.assign(this.cachedSession, partialSession);
            yield this.updateSessionWithAsync();
        });
    }
    deleteSession() {
        return __awaiter(this, void 0, void 0, function* () {
            const storage = new StorageProvider_1.StorageProvider();
            storage.delete(this.getSessionKey());
        });
    }
    getAllSessions() {
        return __awaiter(this, void 0, void 0, function* () {
            const storage = new StorageProvider_1.StorageProvider();
            const sessionKeys = storage.getAllKeys((name) => name.startsWith('roomstay-session'));
            return (yield storage.readAll(sessionKeys)).map((session) => this.unSerializeSession(session));
        });
    }
    /**
     * Function to find the right session from session storage, or local storage
     *
     * If one isn't found it is then created.
     */
    findOrCreateSessionID(sessionID, source) {
        return __awaiter(this, void 0, void 0, function* () {
            let session;
            if (sessionID) {
                session = (yield this.findSession(sessionID));
                if (session) {
                    return session.id;
                }
            }
            session = yield this.generateNewSession(source);
            return session.id;
        });
    }
    findSession(sessionID) {
        return __awaiter(this, void 0, void 0, function* () {
            const storage = new StorageProvider_1.StorageProvider();
            const session = yield storage.read(this.getSessionKey(sessionID));
            if (session) {
                return this.unSerializeSession(session);
            }
        });
    }
    generateNewSession(source) {
        var _a;
        const sessionId = new Date().getTime();
        const session = {
            id: sessionId,
            startDate: null,
            endDate: null,
            step: '',
            hotelID: (_a = this.hotelID) !== null && _a !== void 0 ? _a : null,
            basketRows: [],
            source,
        };
        this.saveSession(session);
        return session;
    }
    serializeSession(session) {
        var _a, _b;
        const basketRows = [];
        // TODO possibly refactor BasketRow to serialize itself, or instead make a serializer that can do all this.
        session.basketRows.forEach((unSerializeBasketRow) => {
            var _a, _b;
            const rate = unSerializeBasketRow.getRate();
            const room = unSerializeBasketRow.getRoom();
            let serializedRate = null;
            let serializedRoom = null;
            if (rate) {
                serializedRate = rate.serialize();
            }
            if (room) {
                serializedRoom = room.serialize();
            }
            basketRows.push({
                uniqId: unSerializeBasketRow.getID(),
                startDate: (_a = unSerializeBasketRow.getStartDate()) === null || _a === void 0 ? void 0 : _a.format(RoomstaySession_1.SERIALIZED_DATE_FORMAT),
                endDate: (_b = unSerializeBasketRow.getEndDate()) === null || _b === void 0 ? void 0 : _b.format(RoomstaySession_1.SERIALIZED_DATE_FORMAT),
                adults: unSerializeBasketRow.getAdults(),
                children: unSerializeBasketRow.getChildren(),
                infants: unSerializeBasketRow.getInfants(),
                promoCode: unSerializeBasketRow.getPromoCode(),
                promoCodeStatus: unSerializeBasketRow.getPromoCodeStatus(),
                rate: serializedRate,
                room: serializedRoom,
            });
        });
        return {
            id: session.id,
            startDate: (_a = session.startDate) === null || _a === void 0 ? void 0 : _a.format(RoomstaySession_1.SERIALIZED_DATE_FORMAT),
            endDate: (_b = session.endDate) === null || _b === void 0 ? void 0 : _b.format(RoomstaySession_1.SERIALIZED_DATE_FORMAT),
            step: session.step,
            hotelID: session.hotelID,
            basketRows,
            source: session.source,
            user: JSON.stringify(session.user),
            memberId: session.memberId,
            reservation: session.reservation,
            reservationRequest: session.reservationRequest,
        };
    }
    unSerializeSession(session) {
        var _a;
        const basketRows = [];
        let startDate = null;
        let endDate = null;
        if (session.startDate) {
            startDate = (0, dayjs_1.default)(session.startDate, RoomstaySession_1.SERIALIZED_DATE_FORMAT);
        }
        if (session.endDate) {
            endDate = (0, dayjs_1.default)(session.endDate, RoomstaySession_1.SERIALIZED_DATE_FORMAT);
        }
        (_a = session.basketRows) === null || _a === void 0 ? void 0 : _a.forEach((serializedBasketRow) => {
            let rate;
            let room;
            if (serializedBasketRow.room) {
                room = new Room_1.Room();
                room.unSerialize(serializedBasketRow.room);
            }
            if (serializedBasketRow.rate && room) {
                rate = new RoomRate_1.RoomRate();
                rate.unSerialize(serializedBasketRow.rate);
                rate.setRoom(room);
            }
            const basketRow = new BasketRow_1.default(rate, serializedBasketRow.uniqId);
            if (serializedBasketRow.startDate) {
                basketRow.setStartDate((0, dayjs_1.default)(serializedBasketRow.startDate, RoomstaySession_1.SERIALIZED_DATE_FORMAT));
            }
            if (serializedBasketRow.endDate) {
                basketRow.setEndDate((0, dayjs_1.default)(serializedBasketRow.endDate, RoomstaySession_1.SERIALIZED_DATE_FORMAT));
            }
            basketRow.setAdults(serializedBasketRow.adults);
            basketRow.setChildren(serializedBasketRow.children);
            basketRow.setInfants(serializedBasketRow.infants);
            basketRows.push(basketRow);
        });
        const reservationRequest = session === null || session === void 0 ? void 0 : session.reservationRequest;
        let basketAddonRows = (reservationRequest === null || reservationRequest === void 0 ? void 0 : reservationRequest.basketAddonRows) || [];
        if (reservationRequest && basketAddonRows.length > 0) {
            basketAddonRows = basketAddonRows.map((row) => {
                const addonRowRaw = row;
                const addon = new Addon_1.Addon(Object.assign(Object.assign({}, addonRowRaw.addon), { checkInDate: (0, dayjs_1.default)(addonRowRaw.addon.checkInDate), checkOutDate: (0, dayjs_1.default)(addonRowRaw.addon.checkOutDate) }));
                const addonRow = new BasketAddonRow_1.default(addon, addonRowRaw.uniqId);
                addonRow.setAdultQuantity(addonRowRaw.adultQuantity);
                addonRow.setChildQuantity(addonRowRaw.childQuantity);
                addonRow.setQuantity(addonRowRaw.quantity);
                addonRow.setSelectedDate(addonRowRaw.selectedDate);
                addonRow.setSelectedTime(addonRowRaw.selectedTime);
                return addonRow;
            });
            reservationRequest.basketAddonRows = basketAddonRows;
        }
        return {
            id: session.id,
            startDate,
            endDate,
            step: session.step,
            hotelID: session.hotelID,
            basketRows,
            source: session.source,
            user: session.user ? JSON.parse(session.user) : undefined,
            memberId: session.memberId,
            reservation: session.reservation,
            reservationRequest: reservationRequest,
        };
    }
    getSessionKey(sessionId) {
        return `session-${sessionId !== null && sessionId !== void 0 ? sessionId : this.currentSessionID}`;
    }
}
exports.default = SessionProvider;
//# sourceMappingURL=SessionProvider.js.map