import { GetConfiguration } from '@octane/renderer';
import { adoptLoginTokenFromUrl, confirmSignInFromLink, positiveIdFromSearch } from './api/auth/urlToken';
import { derivePetConfig, DerivedPetConfig, PetDefinition } from './api/octane/PetData';
import { parseJsonDocument, UiJsonMode } from './json/JsonDocumentParser';
import { configFileUrl, getClientMode, installSecureFetch } from './secure-assets';

declare const __OCTANE_JSON_MODE__: UiJsonMode | undefined;

const resolveJsonMode = (): UiJsonMode => {
    try {
        if (typeof __OCTANE_JSON_MODE__ !== 'undefined' && __OCTANE_JSON_MODE__) {
            if (__OCTANE_JSON_MODE__ === 'legacy' || __OCTANE_JSON_MODE__ === 'jsonc' || __OCTANE_JSON_MODE__ === 'auto') return __OCTANE_JSON_MODE__;
        }
    } catch {}

    return 'auto';
};

const ensureMobileViewport = () => {
    let viewport = document.querySelector<HTMLMetaElement>('meta[name="viewport"]');

    if (!viewport) {
        viewport = document.createElement('meta');
        viewport.name = 'viewport';
        document.head.appendChild(viewport);
    }

    viewport.content = 'width=device-width, initial-scale=1, viewport-fit=cover';
};

ensureMobileViewport();

const setBootDebug = (message: string) => {
    try {
        (window as any).__octaneBootDebug = message;
        const secureNode = document.getElementById('octane-secure-debug');

        if (secureNode) secureNode.textContent = `${secureNode.textContent}\n${message}`;
    } catch {}
};

const deployBaseUrl = (): string => {
    try {
        const loaderBase = (window as any).__octaneLoaderBase;
        if (typeof loaderBase === 'string' && loaderBase.length) return new URL('..', loaderBase).toString();
    } catch {}

    try {
        const moduleUrl = (import.meta as any).url;
        if (typeof moduleUrl === 'string' && moduleUrl.length) return new URL('..', new URL('.', moduleUrl)).toString();
    } catch {}

    try {
        const base = (import.meta as any).env?.BASE_URL;
        if (typeof base === 'string' && base.length) {
            const trimmed = base.replace(/^\/+/, '').replace(/\/+$/, '');
            return trimmed ? `${window.location.origin}/${trimmed}/` : `${window.location.origin}/`;
        }
    } catch {}

    return `${window.location.origin}/`;
};

const loadClientMode = async () => {
    try {
        if ((window as any).__octaneClientMode) return;

        const url = new URL('configuration/client-mode.json', deployBaseUrl());
        url.searchParams.set('v', Date.now().toString(36));

        const response = await fetch(url.toString());

        if (!response.ok) throw new Error(`HTTP ${response.status}`);

        const text = await response.text();
        const mode = resolveJsonMode();

        (window as any).__octaneClientMode = parseJsonDocument(text, mode, url.toString());
        setBootDebug(`boot: client-mode loaded (mode=${mode})`);
    } catch (error) {
        setBootDebug(`boot: client-mode fallback ${error?.message || error}`);
    }
};

const loadPetConfig = async (): Promise<DerivedPetConfig | null> => {
    try {
        const url = configFileUrl('pets.json', true);
        const response = await fetch(url);

        if (!response.ok) throw new Error(`HTTP ${response.status}`);

        const text = await response.text();
        const parsed = parseJsonDocument(text, resolveJsonMode(), url) as { pets?: unknown } | unknown[];
        const list = Array.isArray(parsed) ? parsed : (parsed as { pets?: unknown })?.pets;
        const derived = derivePetConfig(list as PetDefinition[]);

        setBootDebug(derived ? 'boot: pet config loaded' : 'boot: pets.json empty, using config pet.types');

        return derived;
    } catch (error) {
        setBootDebug(`boot: pets.json fallback ${error?.message || error}`);

        return null;
    }
};

// The login secrets in the address - the SSO ticket and the CMS login token - come out of it before the first fetch,
// so none of them reaches the history, a bookmark or the Referer of a request.
const search = new URLSearchParams(window.location.search);

// ?room= and ?friend= act once: left in the address they sent the user there again at every reload.
const forwardRoomId = positiveIdFromSearch(search.get('room'));
const friendId = positiveIdFromSearch(search.get('friend'));

if (search.has('sso') || search.has('room') || search.has('friend')) {
    try {
        const url = new URL(window.location.href);

        url.searchParams.delete('sso');
        url.searchParams.delete('room');
        url.searchParams.delete('friend');
        window.history.replaceState(window.history.state, '', url.toString());
    } catch {}
}

try {
    adoptLoginTokenFromUrl();
} catch (error) {
    setBootDebug(`boot: login token from the address not kept ${error?.message || error}`);
}

// An SSO ticket from the address signs in only after the same question, when this browser remembers no login.
let ssoTicket = search.get('sso') || null;

if (ssoTicket && !confirmSignInFromLink(null)) ssoTicket = null;

await loadClientMode();

installSecureFetch();
setBootDebug('boot: secure fetch installed');

const clientMode = getClientMode();
// The pet list, the configuration files and the UI bundle download together.
const petConfigLoad = loadPetConfig();

(window as any).OctaneSecureApiUrl = clientMode.apiBaseUrl || window.location.origin;
(window as any).OctaneClientMode = clientMode;
(window as any).OctaneConfig = {
    'config.urls': [configFileUrl('renderer-config.json', true), configFileUrl('ui-config.json', true)],
    'sso.ticket': ssoTicket,
    'forward.type': forwardRoomId ? 2 : -1,
    'forward.id': forwardRoomId,
    'friend.id': friendId
};

// Legacy aliases so external scripts written against the old Nitro globals keep working
(window as any).NitroConfig = (window as any).OctaneConfig;
(window as any).NitroClientMode = clientMode;
(window as any).NitroSecureApiUrl = (window as any).OctaneSecureApiUrl;

setBootDebug('boot: OctaneConfig assigned');

const appModule = import('./index');

appModule.catch(() => undefined);

// Load renderer-config.json + ui-config.json BEFORE rendering React. Otherwise
// the first paint triggers a flood of "Missing configuration key" warnings for
// every key components read synchronously (asset.url, login.endpoint, …).
// The UI bundle is already downloading meanwhile; it only mounts below.
const configLoad = GetConfiguration().init();

configLoad.catch(() => undefined);

const petConfig = await petConfigLoad;

// As part of OctaneConfig the pet values win over the configuration files. A load still under way
// re-reads OctaneConfig when its files are in; one that already finished gets them applied below.
if (petConfig) Object.assign((window as any).OctaneConfig, petConfig);

try {
    await configLoad;

    if (petConfig) GetConfiguration().parseConfiguration(petConfig, true);

    setBootDebug('boot: configuration init done');
} catch (error) {
    setBootDebug(`boot: configuration init failed ${error?.message || error}`);
}

appModule
    .then(({ mountApp }) => {
        mountApp();
        setBootDebug('boot: app bundle imported');
    })
    .catch((error) => {
        setBootDebug(`boot: import failed ${error?.message || error}`);
        throw error;
    });
