(() => {
  'use strict';

  const config = window.wpagentlyLiveEditor;
  const runtime = window.wpagentlyLiveEditorRuntime;
  const elementorRuntime = window.wpagentlyLiveEditorElementor;
  const providerFinalizer = window.wpagentlyLiveEditorProviderFinalizer;
  if (!config || !runtime || !elementorRuntime || !providerFinalizer) return;

  const userSelect = document.querySelector('#wpagently-live-editor-user');
  const startButton = document.querySelector('#wpagently-live-editor-start');
  const stopButton = document.querySelector('#wpagently-live-editor-stop');
  const status = document.querySelector('#wpagently-live-editor-status');
  const workspace = document.querySelector('.wpagently-live-editor-workspace');
  const sessionLabel = document.querySelector('#wpagently-live-editor-session');
  const actionList = document.querySelector('#wpagently-live-editor-action-list');
  const frame = document.querySelector('#wpagently-live-editor-frame');
  const frameStatus = document.querySelector('#wpagently-live-editor-frame-status');

  let session = null;
  let pollTimer = null;
  let heartbeatTimer = null;
  let currentCommand = null;
  let stopped = true;
  let lastPageState = null;
  let elementorPage = null;
  let requestChain = Promise.resolve();

  async function performRequest(url, body) {
    const response = await fetch(url, {
      method: 'POST',
      credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': config.nonce },
      body: JSON.stringify(body),
    });
    const data = await response.json().catch(() => ({}));
    if (!response.ok) {
      const error = new Error(data.message || config.i18n.failed);
      error.status = response.status;
      throw error;
    }
    return data;
  }

  function request(url, body) {
    const operation = requestChain.then(
      () => performRequest(url, body),
      () => performRequest(url, body),
    );
    requestChain = operation.catch(() => {});
    return operation;
  }

  function setStatus(message, failed = false) {
    status.textContent = message;
    status.classList.toggle('has-error', failed);
  }

  function setFrameStatus(page) {
    lastPageState = page || null;
    if (!page?.ready) {
      frameStatus.textContent = config.i18n.frameError;
      return;
    }
    const parts = [page.title || page.path];
    if (page.kind === 'gutenberg') parts.push(`${config.i18n.postLabel} ${page.post_id}`, page.dirty ? config.i18n.unsaved : config.i18n.saved);
    if (page.kind === 'elementor') parts.push(`${config.i18n.postLabel} ${page.post_id}`, 'Elementor');
    frameStatus.textContent = parts.filter(Boolean).join(' · ');
  }

  async function currentPageState() {
    const elementorPageState = elementorRuntime.pageState(frame, elementorPage);
    return elementorPageState || runtime.pageState(frame);
  }

  frame.addEventListener('load', () => {
    if (elementorRuntime.pageState(frame, elementorPage)) heartbeat();
  });

  function setElementorFrame(page) {
    if (!page || page.kind !== 'elementor' || !page.edit_url || !Number(page.post_id)) throw new Error(config.i18n.frameError);
    const url = new URL(page.edit_url, window.location.origin);
    if (url.origin !== window.location.origin || url.searchParams.get('post') !== String(page.post_id) || url.searchParams.get('action') !== 'elementor') {
      throw new Error(config.i18n.frameError);
    }
    elementorPage = page;
    frame.src = url.href;
  }

  function clearTimers() {
    if (pollTimer) clearTimeout(pollTimer);
    if (heartbeatTimer) clearTimeout(heartbeatTimer);
    pollTimer = null;
    heartbeatTimer = null;
  }

  function schedulePoll(delay = 700, waitForElementor = false) {
    if (stopped || !session || currentCommand) return;
    if (waitForElementor && elementorPage && !elementorRuntime.pageState(frame, elementorPage)) return;
    if (pollTimer) clearTimeout(pollTimer);
    pollTimer = setTimeout(poll, delay);
  }

  function scheduleHeartbeat(delay = 10000) {
    if (stopped || !session) return;
    if (heartbeatTimer) clearTimeout(heartbeatTimer);
    heartbeatTimer = setTimeout(heartbeat, delay);
  }

  async function heartbeat() {
    heartbeatTimer = null;
    if (stopped || !session) return;
    try {
      const page = await currentPageState();
      setFrameStatus(page);
      await request(config.heartbeatUrl, { session_id: session.session_id, page });
      setStatus(config.i18n.active);
      if (page?.kind === 'elementor' && page.ready) schedulePoll(100);
      scheduleHeartbeat();
    } catch (error) {
      if (error.status === 409) {
        scheduleHeartbeat(250);
        return;
      }
      setStatus(error.message || config.i18n.failed, true);
      if (error.status === 410 || error.status === 403) {
        stopLocal(config.i18n.closed);
      } else {
        scheduleHeartbeat(3000);
      }
    }
  }

  function commandTitle(command) {
    return config.i18n.commandTitles?.[command.action] || command.action;
  }

  function addActionCard(command) {
    const card = document.createElement('article');
    card.className = 'wpagently-live-editor-action';
    card.dataset.commandId = command.command_id;
    const heading = document.createElement('h3');
    heading.textContent = commandTitle(command);
    const reason = document.createElement('p');
    reason.textContent = String(command.args?.reason || '');
    const details = document.createElement('details');
    const summary = document.createElement('summary');
    summary.textContent = config.i18n.details;
    const pre = document.createElement('pre');
    pre.textContent = JSON.stringify(command.args || {}, null, 2);
    details.append(summary, pre);
    const state = document.createElement('p');
    state.className = 'wpagently-live-editor-action-status';
    state.textContent = config.i18n.waiting;
    const controls = document.createElement('div');
    controls.className = 'wpagently-live-editor-action-controls';
    const approve = document.createElement('button');
    approve.type = 'button';
    approve.className = 'button button-primary';
    approve.textContent = config.i18n.approve;
    const deny = document.createElement('button');
    deny.type = 'button';
    deny.className = 'button';
    deny.textContent = config.i18n.deny;
    controls.append(approve, deny);
    card.append(heading, reason, details, controls, state);
    actionList.prepend(card);

    approve.addEventListener('click', async () => {
      approve.disabled = true;
      deny.disabled = true;
      state.textContent = config.i18n.running;
      let leasedCommand = null;
      const isElementorCommand = command.action === 'open-elementor-document' || command.action === 'elementor-mutate';
      const isProviderFinalizeCommand = command.action === 'finalize-provider-attributes';
      try {
        if (isElementorCommand) {
          const priorElementorPage = elementorPage;
          if (command.action === 'elementor-mutate') {
            if (!window.confirm(config.i18n.elementorDiscardConfirm)) {
              approve.disabled = false;
              deny.disabled = false;
              state.textContent = config.i18n.waiting;
              currentCommand = null;
              schedulePoll(100);
              return;
            }
            frame.src = 'about:blank';
            elementorPage = null;
          }
          leasedCommand = await request(config.elementorExecuteUrl, {
            session_id: session.session_id,
            command_id: command.command_id,
          });
          if (leasedCommand.result?.page) setElementorFrame(leasedCommand.result.page);
          else if (priorElementorPage) setElementorFrame(priorElementorPage);
          if (leasedCommand.status !== 'completed') throw new Error(leasedCommand.error || config.i18n.failed);
        } else if (isProviderFinalizeCommand) {
          const capture = await providerFinalizer.capture(frame, command.args || {});
          leasedCommand = await request(config.providerFinalizeExecuteUrl, {
            session_id: session.session_id,
            command_id: command.command_id,
            capture,
          });
          if (leasedCommand.status !== 'completed') throw new Error(leasedCommand.error || config.i18n.failed);
        } else {
          const page = await currentPageState();
          const activeContent = await runtime.activeContent(frame, command);
          leasedCommand = await request(config.leaseUrl, {
            session_id: session.session_id,
            command_id: command.command_id,
            page,
            active_content: activeContent,
          });
          const result = await runtime.execute(frame, leasedCommand, config);
          const completed = await finishCommand(leasedCommand, 'completed', result, '');
          if (completed.status !== 'completed') throw new Error(completed.error || config.i18n.failed);
        }
        state.textContent = config.i18n.approved;
        card.classList.add('is-completed');
      } catch (error) {
        const message = runtime.errorMessage(error) || config.i18n.failed;
        if (leasedCommand && !isElementorCommand && !isProviderFinalizeCommand) {
          await finishCommand(leasedCommand, 'failed', {}, message).catch(() => {});
        } else if (!leasedCommand) {
          approve.disabled = false;
          deny.disabled = false;
        }
        state.textContent = message;
        card.classList.add('has-error');
      } finally {
        if (leasedCommand) {
          currentCommand = null;
          if (isProviderFinalizeCommand) {
            // The editor received unsaved provider attributes while mounting.
            // Always discard that transient browser state and reload the exact
            // server-verified document before accepting another command.
            frame.src = frame.src;
            schedulePoll(100);
          } else if (elementorRuntime.shouldResumePolling(isElementorCommand, leasedCommand)) {
            const waitForElementorFrame = isElementorCommand && elementorPage && !elementorRuntime.pageState(frame, elementorPage);
            if (!waitForElementorFrame) heartbeat();
            schedulePoll(100, Boolean(waitForElementorFrame));
          }
        }
      }
    });

    deny.addEventListener('click', async () => {
      approve.disabled = true;
      deny.disabled = true;
      try {
        await finishCommand(command, 'denied', {}, config.i18n.denied);
        state.textContent = config.i18n.denied;
        card.classList.add('is-denied');
      } catch (error) {
        state.textContent = runtime.errorMessage(error) || config.i18n.failed;
        card.classList.add('has-error');
      } finally {
        currentCommand = null;
        schedulePoll(100);
      }
    });
  }

  async function finishCommand(command, resultStatus, result, error) {
    const body = {
      session_id: session.session_id,
      command_id: command.command_id,
      status: resultStatus,
      result,
      error: runtime.errorMessage(error),
      execution_lease: String(command.execution_lease || ''),
    };
    try {
      return await request(config.resultUrl, body);
    } catch (requestError) {
      if (requestError.status && requestError.status < 500) throw requestError;
      await new Promise((resolve) => setTimeout(resolve, 250));
      return request(config.resultUrl, body);
    }
  }

  async function executeRead(command) {
    currentCommand = command;
    try {
      const result = await runtime.execute(frame, command, config);
      await finishCommand(command, 'completed', result, '');
    } catch (error) {
      await finishCommand(command, 'failed', {}, runtime.errorMessage(error) || config.i18n.failed).catch(() => {});
    } finally {
      currentCommand = null;
      schedulePoll(100);
    }
  }

  async function poll() {
    pollTimer = null;
    if (stopped || !session || currentCommand) return;
    try {
      const command = await request(config.pollUrl, { session_id: session.session_id });
      if (command.status === 'idle') {
        schedulePoll();
      } else if (command.requires_confirmation) {
        currentCommand = command;
        addActionCard(command);
      } else {
        executeRead(command);
      }
    } catch (error) {
      if (error.status === 409) {
        schedulePoll(250);
        return;
      }
      setStatus(error.message || config.i18n.failed, true);
      if (error.status === 410 || error.status === 403) stopLocal(config.i18n.closed);
      else schedulePoll(2000);
    }
  }

  function stopLocal(message, clearFrame = true) {
    stopped = true;
    clearTimers();
    session = null;
    currentCommand = null;
    lastPageState = null;
    elementorPage = null;
    workspace.hidden = true;
    if (clearFrame) frame.src = 'about:blank';
    sessionLabel.textContent = '';
    startButton.disabled = !userSelect?.value;
    stopButton.disabled = true;
    userSelect.disabled = false;
    setStatus(message);
  }

  async function closeSession() {
    if (!session) return;
    const page = await currentPageState();
    setFrameStatus(page);
    if ((page.dirty || page.kind === 'elementor') && !window.confirm(config.i18n.unsavedConfirm)) return;
    const current = session;
    stopped = true;
    clearTimers();
    stopButton.disabled = true;
    try {
      await request(config.closeUrl, { session_id: current.session_id });
      stopLocal(config.i18n.closed);
    } catch (error) {
      stopped = false;
      stopButton.disabled = false;
      setStatus(error.message || config.i18n.failed, true);
      schedulePoll(1000);
      scheduleHeartbeat(1000);
    }
  }

  startButton?.addEventListener('click', async () => {
    const executorId = Number(userSelect?.value || 0);
    if (!executorId || session) return;
    startButton.disabled = true;
    userSelect.disabled = true;
    setStatus('');
    try {
      session = await request(config.createUrl, { executor_id: executorId });
      stopped = false;
      workspace.hidden = false;
      stopButton.disabled = false;
      sessionLabel.textContent = `${config.i18n.sessionLabel}: ${session.session_id.slice(0, 8)}`;
      actionList.textContent = '';
      setStatus(config.i18n.active);
      schedulePoll(50);
      scheduleHeartbeat(50);
    } catch (error) {
      session = null;
      startButton.disabled = false;
      userSelect.disabled = false;
      setStatus(error.message || config.i18n.failed, true);
    }
  });

  stopButton?.addEventListener('click', closeSession);
  window.addEventListener('beforeunload', (event) => {
    if (!stopped && (lastPageState?.dirty || lastPageState?.kind === 'elementor')) {
      event.preventDefault();
      event.returnValue = '';
    }
  });
})();
