import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import { TARGET_ENV, DEPLOYMENT_ENV, NONCE_REFRESH_URL, NONCE_REFRESH_NONCE, API_BASE_URL, getAllowActionPanelAiRequests, getOrCreatePluginSessionId, getAuthToken, getRestNonce, setRestNonce } from './constants.ts'
import { applyStoredThemeToDocument, applyThemeToElement, readStoredThemePreference, resolveEffectiveTheme } from './utils/themePreference.ts'

applyStoredThemeToDocument();

const originalFetch = window.fetch;

const normalizeUrl = (value: string): string => {
  try {
    return new URL(value, window.location.origin).toString();
  } catch {
    return value;
  }
};

const normalizedAssistantApiBaseUrl = normalizeUrl(API_BASE_URL).replace(/\/+$/, '');

const isAssistantApiRequest = (requestUrl: URL | null): boolean => {
  if (!requestUrl || !normalizedAssistantApiBaseUrl) return false;
  const normalizedRequestUrl = normalizeUrl(requestUrl.toString());
  return normalizedRequestUrl === normalizedAssistantApiBaseUrl
    || normalizedRequestUrl.startsWith(`${normalizedAssistantApiBaseUrl}/`);
};

// Automatically attach WP REST nonce for same-origin requests when running inside WordPress.
// If the nonce expires during a long-lived admin session, refresh it once and retry.
if (TARGET_ENV === 'wordpress') {
  let nonceRefreshPromise: Promise<string | null> | null = null;

  const isNonceFailure = async (response: Response): Promise<boolean> => {
    if (response.status !== 403) return false;
    try {
      const payload = await response.clone().json();
      const code = typeof payload?.code === 'string' ? payload.code : '';
      const message = typeof payload?.message === 'string' ? payload.message : '';
      return code === 'rest_cookie_invalid_nonce' || /nonce/i.test(message);
    } catch {
      return false;
    }
  };

  const refreshRestNonce = async (): Promise<string | null> => {
    if (!NONCE_REFRESH_URL) return null;
    if (nonceRefreshPromise) return nonceRefreshPromise;

    nonceRefreshPromise = (async () => {
      const response = await originalFetch(NONCE_REFRESH_URL, {
        method: 'POST',
        credentials: 'same-origin',
        headers: {
          'X-Requested-With': 'XMLHttpRequest',
        },
        body: new URLSearchParams({
          nonce: NONCE_REFRESH_NONCE,
        }),
      });
      if (!response.ok) {
        return null;
      }

      const payload = await response.json().catch(() => null);
      const freshNonce =
        typeof payload?.data?.restNonce === 'string'
          ? payload.data.restNonce
          : typeof payload?.restNonce === 'string'
            ? payload.restNonce
            : null;

      if (freshNonce) {
        setRestNonce(freshNonce);
      }

      return freshNonce;
    })().finally(() => {
      nonceRefreshPromise = null;
    });

    return nonceRefreshPromise;
  };

  window.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
    const reqInit: RequestInit = init ? { ...init } : {};
    const inputHeaders = input instanceof Request ? input.headers : undefined;
    const baseHeaders = new Headers(inputHeaders ?? undefined);
    if (reqInit.headers) {
      new Headers(reqInit.headers).forEach((value, key) => {
        baseHeaders.set(key, value);
      });
    }
    let requestUrl: URL | null = null;
    let isSameOrigin = false;
    let isNonceRefreshRequest = false;
    let isAssistantRequest = false;

    try {
      if (typeof input === 'string' || input instanceof URL) {
        requestUrl = new URL(input.toString(), window.location.origin);
      } else {
        requestUrl = new URL(input.url, window.location.origin);
      }
      isSameOrigin = requestUrl.origin === window.location.origin;
      isNonceRefreshRequest = normalizeUrl(requestUrl.toString()) === normalizeUrl(NONCE_REFRESH_URL);
      isAssistantRequest = isAssistantApiRequest(requestUrl);
    } catch {
      // Ignore malformed URL.
    }

    if (requestUrl && !isSameOrigin) {
      reqInit.credentials = 'omit';
    }

    try {
      if (getAllowActionPanelAiRequests() && isAssistantRequest) {
        const pluginSession = getOrCreatePluginSessionId();
        baseHeaders.set('X-Plugin-Session', pluginSession);
        const token = getAuthToken();
        if (token) baseHeaders.set('Authorization', `Bearer ${token}`);
      }
    } catch {}

    const performFetch = (nonce: string | null): Promise<Response> => {
      const headers = new Headers(baseHeaders);
      if (isSameOrigin && nonce) {
        headers.set('X-WP-Nonce', nonce);
      } else if (isSameOrigin) {
        headers.delete('X-WP-Nonce');
      }
      return originalFetch(input, { ...reqInit, headers });
    };

    const initialNonce = getRestNonce();
    let response = await performFetch(initialNonce || null);

    if (!isSameOrigin || isNonceRefreshRequest || !(await isNonceFailure(response))) {
      return response;
    }

    const freshNonce = await refreshRestNonce();
    if (!freshNonce || freshNonce === initialNonce) {
      return response;
    }

    response = await performFetch(freshNonce);
    return response;
  };
} else {
  window.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
    const reqInit: RequestInit = init ? { ...init } : {};
    const inputHeaders = input instanceof Request ? input.headers : undefined;
    const headers = new Headers(inputHeaders ?? undefined);
    if (reqInit.headers) {
      new Headers(reqInit.headers).forEach((value, key) => {
        headers.set(key, value);
      });
    }

    let requestUrl: URL | null = null;
    try {
      if (typeof input === 'string' || input instanceof URL) {
        requestUrl = new URL(input.toString(), window.location.origin);
      } else {
        requestUrl = new URL(input.url, window.location.origin);
      }
    } catch {
      // Ignore malformed URL.
    }

    if (isAssistantApiRequest(requestUrl)) {
      headers.set('X-WPA-Session', getOrCreatePluginSessionId());
    }

    return originalFetch(input, { ...reqInit, headers });
  };
}

function mountInShadowDom() {
  const el = document.querySelector('actionpanel-ai-app') as HTMLElement | null;
  if (!el) return false;

  const shadowRoot = el.attachShadow({ mode: 'open' });

  // --- Fit #actionpanel-ai-root exactly to viewport bottom (throttled) ---
  const rootContainer = document.getElementById('actionpanel-ai-root')!;

  const getViewportHeight = () =>
    (window as any).visualViewport?.height ?? window.innerHeight;

  const fitNow = () => {
    const rect = rootContainer.getBoundingClientRect();
    const avail = Math.max(0, getViewportHeight() - rect.top);
    // Set both logical & physical for broad engine support
    rootContainer.style.blockSize = `${avail}px`;
    rootContainer.style.height = `${avail}px`;

    // Expose admin bar height to the shadow tree
    const adminBar = document.getElementById('wpadminbar');
    const barH = adminBar ? adminBar.offsetHeight : 0;
    // put it on the shadow HOST so components can use it
    el.style.setProperty('--actionpanel-ai-admin-bar-h', `${barH}px`);
  };

  // RAF throttle to avoid layout thrash during bursts
  let rafId: number | null = null;
  const scheduleFit = () => {
    if (rafId != null) return;
    rafId = requestAnimationFrame(() => {
      rafId = null;
      fitNow();
    });
  };

  // Initial
  fitNow();

  // Window resize/orientation
  window.addEventListener('resize', scheduleFit, { passive: true });
  window.addEventListener('orientationchange', scheduleFit, { passive: true });

  // Visual viewport (mobile keyboard etc.)
  (window as any).visualViewport?.addEventListener?.('resize', scheduleFit, { passive: true });

  // Observe size changes of admin chrome that affect available height
  const ro = new ResizeObserver(scheduleFit);
  const adminBar = document.getElementById('wpadminbar');
  const wpBodyContent = document.getElementById('wpbody-content');
  if (adminBar) ro.observe(adminBar);
  if (wpBodyContent) ro.observe(wpBodyContent);

  // If you still want to react to notices being inserted/removed, use a lean MO:
  const mo = new MutationObserver(() => scheduleFit());
  if (wpBodyContent) mo.observe(wpBodyContent, { childList: true });

  // Prepare sizing stylesheet (APPEND LAST, after index.css)
  const sizing = document.createElement('style');
  sizing.textContent = `
    :host { display:block; width:100%; height:100%; }
    #actionpanel-ai-mount { width:100%; height:100%; overflow:auto; box-sizing:border-box; background:rgb(var(--app-bg)); }

    /* OVERRIDES that must beat Tailwind */
    .h-screen, .min-h-screen { height:100% !important; min-height:0 !important; }
    .h-mobile-safe { height:100% !important; min-height:0 !important; }

    /* Ensure the first app wrapper fills the mount */
    #actionpanel-ai-mount > :first-child { height: 100% !important; min-height: 0 !important; }

    /* Flex helpers */
    .min-h-0 { min-height:0 !important; }
  `;

  // Create mount node (same as yours)
  const mount = document.createElement('div');
  mount.id = 'actionpanel-ai-mount';
  mount.style.visibility = 'hidden';
  applyThemeToElement(mount, resolveEffectiveTheme(readStoredThemePreference()));
  shadowRoot.appendChild(mount);

  (window as any).actionPanelAiSetTheme = (mode:'light'|'dark') =>
    applyThemeToElement(mount, mode);

  const renderApp = () => {
    const node = (DEPLOYMENT_ENV === 'production' ? <App/> : <StrictMode><App/></StrictMode>);
    createRoot(mount).render(node);
    requestAnimationFrame(() => { mount.style.visibility = ''; });
  };

  const cssUrl = (window as any)?.actionPanelAiConfig?.cssUrl;
  if (cssUrl) {
    fetch(cssUrl)
      .then(r => r.text())
      .then(cssText => {
        const supportsConstructable =
          'adoptedStyleSheets' in Document.prototype &&
          'replaceSync' in CSSStyleSheet.prototype;

        if (supportsConstructable) {
          const sheet = new CSSStyleSheet();
          sheet.replaceSync(cssText);
          // @ts-ignore
          shadowRoot.adoptedStyleSheets = [sheet];
          // ⬇️ APPEND SIZING LAST
          shadowRoot.appendChild(sizing);
        } else {
          const style = document.createElement('style');
          style.textContent = cssText;
          shadowRoot.appendChild(style);
          // ⬇️ APPEND SIZING LAST
          shadowRoot.appendChild(sizing);
        }
        renderApp();
      })
      .catch(() => {
        // On failure, still ensure sizing exists
        shadowRoot.appendChild(sizing);
        renderApp();
      });
  } else {
    shadowRoot.appendChild(sizing);
    renderApp();
  }

  return true;
}


if (TARGET_ENV === 'wordpress') {
  // Try Shadow DOM; if not present (legacy), fall back to #root
  if (!mountInShadowDom()) {
    const root = document.getElementById('root');
    if (root) {
      const node = (
        DEPLOYMENT_ENV === 'production' ? <App /> : (
          <StrictMode>
            <App />
          </StrictMode>
        )
      );
      createRoot(root).render(node);
    }
  }
} else {
  // Web build
  const root = document.getElementById('root');
  if (root) {
    const node = (
      DEPLOYMENT_ENV === 'production' ? <App /> : (
        <StrictMode>
          <App />
        </StrictMode>
      )
    );
    createRoot(root).render(node);
  }
}
