"use strict";
let pc = null;
let dc = null;
let mediaStream = null;
let audioEl = null;
let isRecording = false;
let recognition = null;
let recognizing = false;
let micSender = null;
let holdRequested = false;
let holdTimeout;
let isStartingSession = false; 

function appendToLog(role, text, chatLogEl) {
  if (text === "") return;
  if (!chatLogEl) return;
  if (role === "user") {
    const messageEl = document.createElement("div");
    messageEl.style.marginBottom = "10px";
    messageEl.className = "ai-bubble ai-speech";
    messageEl.innerHTML = text;
    const lastTranscript = Array.from(chatLogEl.children)
      .filter(
        (div) =>
          div.classList.contains("ai-bubble") &&
          div.classList.contains("ai-transcript") &&
          !div.hasAttribute("data-message-included")
      )
      .pop();
    if (lastTranscript) {
      chatLogEl.insertBefore(messageEl, lastTranscript);
      lastTranscript.setAttribute("data-message-included", "true");
    } else {
      chatLogEl.appendChild(messageEl);
    }
  } else {
    const messageEl = document.createElement("div");
    messageEl.style.marginBottom = "10px";
    messageEl.innerHTML = `<div class="ai-bubble ai-transcript">${text}</div>`;
    chatLogEl.appendChild(messageEl);
  }
  chatLogEl.scrollTop = chatLogEl.scrollHeight;
}

async function appendStreamToLog(responseId, text, container) {
  if (text === "") return;
  if (!container) return;
  if (responseId === "user") {
    let transcriptDiv = document.createElement("div");
    transcriptDiv.className = "ai-bubble ai-speech";
    transcriptDiv.textContent = text;
    const lastTranscript = Array.from(container.children)
      .filter(
        (div) =>
          div.classList.contains("ai-bubble") &&
          div.classList.contains("ai-transcript") &&
          !div.hasAttribute("data-message-included")
      )
      .pop();
    if (lastTranscript) {
      container.insertBefore(transcriptDiv, lastTranscript);
      lastTranscript.setAttribute("data-message-included", "true");
    } else {
      container.appendChild(transcriptDiv);
    }
  } else {
    let transcriptDiv = document.querySelector(
      `[data-response-id="${responseId}"]`
    );
    if (!transcriptDiv) {
      transcriptDiv = document.createElement("div");
      transcriptDiv.className = "ai-bubble ai-transcript";
      transcriptDiv.setAttribute("data-response-id", responseId);
      container.appendChild(transcriptDiv);
    }
    transcriptDiv.textContent = transcriptDiv.textContent + text;
  }
}

async function startSession() {
  try {
    const enable_god_mode = aiomatic_realtime.enable_god_mode || "off";
    const tokenResponse = await fetch(aiomatic_realtime.ajax_url, {
      method: "POST",
      body: new URLSearchParams({
        action: "aiomatic_realtime_session",
        nonce: aiomatic_realtime.nonce,
        model: aiomatic_realtime.model,
        voice: aiomatic_realtime.voice,
        instructions: aiomatic_realtime.instructions,
        temperature: aiomatic_realtime.temperature,
        max_tokens: aiomatic_realtime.max_tokens,
        enable_god_mode,
        user_id: aiomatic_realtime.user_id,
        user_token_cap_per_day: aiomatic_realtime.user_token_cap_per_day,
      }),
    });

    const data = await tokenResponse.json();
    if (!data.success) {
      alert(data.data.message);
      return;
    }

    const EPHEMERAL_KEY = data.data.client_secret;

    pc = new RTCPeerConnection();

    pc.onconnectionstatechange = () => {
      console.log("RTCPeerConnection state:", pc.connectionState);
      if (pc.connectionState === "failed" || pc.connectionState === "closed") {
        console.error("WebRTC connection failed or closed, stopping session.");
        stopSession();
      }
    };
    pc.oniceconnectionstatechange = () => {
      console.log("ICE connection state:", pc.iceConnectionState);
    };

    audioEl = document.createElement("audio");
    audioEl.autoplay = true;
    pc.ontrack = (e) => {
      audioEl.srcObject = e.streams[0];
      document.body.appendChild(audioEl);
    };

    mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
    micSender = pc.addTrack(mediaStream.getTracks()[0], mediaStream);

    dc = pc.createDataChannel("oai-events");
    dc.addEventListener("open", () => {
      console.log("Data channel open");
    });
    dc.addEventListener("error", (err) => {
      console.error("Data channel error:", err);
    });
    dc.addEventListener("close", () => {
      console.log("Data channel closed");
    });

    let chatLogEl = document.getElementById("aiomatic-realtime-log");

    dc.addEventListener("message", async (e) => {
      const event = JSON.parse(e.data);
      console.log("Received event:", event);

      if (event.type === "error") {
        const message = event.error?.message || "";
        if (message.includes("Conversation already has an active response in progress")) {
          return;
        }

        console.error("Server error:", event.error);
        alert("Server error: " + message);
        stopSession();
        return;
      }


      if (event.type === "response.done") {
        if (event.response && event.response.usage) {
          logAiUsage(event.response, aiomatic_realtime.model);
        }
        const functionCall = event.response.output.find(
          (item) => item.type === "function_call"
        );
        if (functionCall) {
          const { name, arguments: args, call_id } = functionCall;
          console.log("Calling function:", name);
          jQuery.ajax({
            type: "POST",
            async: false,
            url: aiomatic_realtime.ajax_url,
            data: {
              action: "aiomatic_call_ai_function_realtime",
              nonce: aiomatic_realtime.persistentnonce,
              name,
              args,
              call_id,
            },
            success: function (result) {
              if (typeof result !== "object" || result === null) {
                try {
                  result = JSON.parse(result);
                } catch (error) {
                  console.error("Failed to parse function JSON:", error);
                }
              }
              if (result.scope === "fail") {
                console.log("Error while calling parsing functions:", JSON.stringify(result));
                sendFunctionResult(call_id, {
                  response: JSON.stringify({
                    status: "error",
                    message: "Error while calling parsing functions: " + JSON.stringify(result),
                  }),
                });
              } else if (result.scope === "response") {
                console.log("Sending function response to the AI:", result.data);
                sendFunctionResult(call_id, {
                  response: JSON.stringify({ status: "success", message: result.data }),
                });
              } else if (result.scope === "user_message") {
                console.log("Displaying function data to the user.");
                sendFunctionResult(call_id, {
                  response: JSON.stringify({ status: "success", message: "Data displayed to user." }),
                });
                appendToLog("assistant", result.data, chatLogEl);
              } else {
                console.log("Unknown scope returned:", JSON.stringify(result));
                sendFunctionResult(call_id, {
                  response: "Error: Unknown scope returned: " + JSON.stringify(result),
                });
              }
            },
            error: function (error) {
              console.log("Error while calling AI functions:", error.responseText);
              sendFunctionResult(call_id, {
                response: JSON.stringify({
                  status: "error",
                  message: "Error while calling AI functions: " + error.responseText,
                }),
              });
            },
          });
        }
      }

      if (event.type === "response.done") {
        console.log("Response completed.");
      }
      if (event.type === "response.audio_transcript.delta") {
        if (aiomatic_realtime.show_chat_log === "show") {
          const responseId = event.response_id;
          const text = event.delta || "";
          appendStreamToLog(responseId, text, chatLogEl);
        }
      }
    });

    if (aiomatic_realtime.show_user_log === "show") {
      if ("webkitSpeechRecognition" in window) {
        recognition = new webkitSpeechRecognition();
        recognition.continuous = true;
        recognition.interimResults = true;

        recognition.onresult = function (event) {
          for (let i = event.resultIndex; i < event.results.length; ++i) {
            if (event.results[i].isFinal) {
              const text = event.results[i][0].transcript.trim();
              appendToLog("user", text, chatLogEl);
              if (dc && dc.readyState === "open") {
                dc.send(
                  JSON.stringify({
                    type: "conversation.item.create",
                    item: {
                      type: "message",
                      role: "user",
                      content: [{ type: "input_text", text }],
                    },
                  })
                );
                dc.send(JSON.stringify({ type: "response.create" }));
              }
            }
          }
        };

        recognition.onerror = function (event) {
          console.error("Speech recognition error:", event.error);
        };

        recognition.onend = function () {
          if (isRecording && recognizing) {
            console.log("Restarting speech recognition...");
            recognition.start();
          }
        };

        recognition.start();
        recognizing = true;
      } else {
        console.log("Web Speech API is not supported in this browser.");
      }
    }

    const offer = await pc.createOffer();
    await pc.setLocalDescription(offer);

    const sdpResponse = await fetch(
      `https://api.openai.com/v1/realtime?model=${aiomatic_realtime.model}`,
      {
        method: "POST",
        body: offer.sdp,
        headers: {
          Authorization: `Bearer ${EPHEMERAL_KEY.value}`,
          "Content-Type": "application/sdp",
        },
      }
    );

    const answer = {
      type: "answer",
      sdp: await sdpResponse.text(),
    };
    await pc.setRemoteDescription(answer);

    isRecording = true;
    updateUI(true);

    console.log("Session started.");
  } catch (err) {
    console.error("Error starting session:", err);
    updateUI(false);
  } finally {
    isStartingSession = false; 
  }
}

function stopSession() {
  if (pc) {
    pc.close();
    pc = null;
  }

  if (mediaStream) {
    mediaStream.getTracks().forEach((track) => track.stop());
    mediaStream = null;
  }

  if (audioEl) {
    audioEl.remove();
    audioEl = null;
  }

  if (recognition) {
    recognition.stop();
    recognition = null;
    recognizing = false;
  }

  micSender = null;
  isRecording = false;
  isStartingSession = false; 
  updateUI(false);

  console.log("Session stopped.");
}

function updateUI(recording) {
  const statusEl = document.getElementById("aiomatic-realtime-status");
  const toggleBtn = document.getElementById("aiomatic-realtime-toggle");
  if (!statusEl || !toggleBtn) return;

  if (recording) {
    statusEl.textContent = aiomatic_realtime.recording;
    if (aiomatic_realtime.mode === "toggle") {
      toggleBtn.textContent = aiomatic_realtime.stop_recording;
    }
  } else {
    statusEl.textContent = aiomatic_realtime.not_recording;
    if (aiomatic_realtime.mode === "toggle") {
      toggleBtn.textContent = aiomatic_realtime.start_recording;
    }
  }
}

async function startOrContinueSession() {
  let chatLogEl = document.getElementById("aiomatic-realtime-log");

  if (isStartingSession) {
    console.log("Session start in progress, please wait...");
    return;
  }

  isStartingSession = true;

  if (pc && pc.connectionState !== "closed" && pc.signalingState !== "closed") {
    console.log("Reusing existing session, reattaching mic...");

    try {
      if (!mediaStream) {
        mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
        console.log("Media stream tracks:", mediaStream.getTracks());
      }
      const track = mediaStream.getTracks()[0];

      if (micSender) {
        console.log("Reattaching track to existing micSender...");
        await micSender.replaceTrack(track);
      } else {
        console.log("Creating new micSender...");
        micSender = pc.addTrack(track, mediaStream);
      }

      if (dc && dc.readyState === "open") {
        console.log("Data channel is open, ready to send audio.");
        dc.send(JSON.stringify({ type: "input_audio_buffer.clear" }));
      } else {
        console.error("Data channel is not open, state:", dc ? dc.readyState : "null");
        throw new Error("Data channel is not open");
      }

      isRecording = true;
      updateUI(true);

      if (aiomatic_realtime.show_user_log === "show" && !recognizing) {
        if ("webkitSpeechRecognition" in window) {
          if (!recognition) {
            recognition = new webkitSpeechRecognition();
            recognition.continuous = true;
            recognition.interimResults = true;

            recognition.onresult = function (event) {
              for (let i = event.resultIndex; i < event.results.length; ++i) {
                if (event.results[i].isFinal) {
                  const text = event.results[i][0].transcript.trim();
                  appendToLog("user", text, chatLogEl);
                  if (dc && dc.readyState === "open") {
                    dc.send(
                      JSON.stringify({
                        type: "conversation.item.create",
                        item: {
                          type: "message",
                          role: "user",
                          content: [{ type: "input_text", text }],
                        },
                      })
                    );
                    dc.send(JSON.stringify({ type: "response.create" }));
                  }
                }
              }
            };

            recognition.onerror = function (event) {
              console.error("Speech recognition error:", event.error);
            };

            recognition.onend = function () {
              if (isRecording && recognizing) {
                console.log("Restarting speech recognition...");
                recognition.start();
              }
            };
          }
          console.log("Starting speech recognition...");
          recognition.start();
          recognizing = true;
        } else {
          console.log("Web Speech API is not supported in this browser.");
        }
      }
    } catch (err) {
      console.error("Error enabling microphone or setting up session:", err);
      updateUI(false);
      isStartingSession = false;
      return;
    } finally {
      isStartingSession = false; 
    }
    return;
  }

  await startSession();
}

async function stopRecordingOnly() {
  console.log("Stopping recording only...");

  if (micSender) {
    try {
      await micSender.replaceTrack(null);
      console.log("Microphone track detached.");
    } catch (err) {
      console.warn("replaceTrack(null) failed:", err);
    }
  }

  if (mediaStream) {
    mediaStream.getTracks().forEach((track) => track.stop());
    mediaStream = null;
    console.log("Media stream stopped.");
  }

  if (recognition && recognizing) {
    recognition.stop();
    recognizing = false;
    console.log("Speech recognition stopped.");
  }

  isRecording = false;
  updateUI(false);
  console.log("Mic detached, AI can still respond.");
}

document.addEventListener("DOMContentLoaded", () => {
  const toggleBtn = document.getElementById("aiomatic-realtime-toggle");
  if (!toggleBtn) return;

  window.addEventListener("beforeunload", () => {
    if (pc || mediaStream || recognition) {
      stopSession();
    }
  });

  if (
    aiomatic_realtime.autostart === "yes" ||
    parseInt(aiomatic_realtime.autostart, 10) === 1
  ) {
    if (aiomatic_realtime.mode === "toggle") {
      startSession();
    }
  }

  if (aiomatic_realtime.mode === "hold") {
    toggleBtn.addEventListener("mousedown", async (e) => {
      e.preventDefault();
      if (isStartingSession) {
        console.log("Session start in progress, ignoring mousedown...");
        return;
      }
      holdRequested = true;
      console.log("Hold started (mousedown)");
      clearTimeout(holdTimeout);
      if (!isRecording) {
        await startOrContinueSession();
        if (!holdRequested && isRecording) {
          console.log("Quick tap detected, stopping recording...");
          holdTimeout = setTimeout(() => {
            if (!holdRequested) {
              stopRecordingOnly();
            }
          }, 100);
        }
      }
    });

    toggleBtn.addEventListener("mouseup", () => {
      console.log("Hold released (mouseup)");
      holdRequested = false;
      if (isRecording) {
        holdTimeout = setTimeout(() => {
          if (!holdRequested) {
            stopRecordingOnly();
          }
        }, 200); 
      }
    });

    toggleBtn.addEventListener("touchstart", async (e) => {
      e.preventDefault();
      if (isStartingSession) {
        console.log("Session start in progress, ignoring touchstart...");
        return;
      }
      holdRequested = true;
      console.log("Hold started (touchstart)");
      clearTimeout(holdTimeout);
      if (!isRecording) {
        await startOrContinueSession();
        if (!holdRequested && isRecording) {
          console.log("Quick tap detected, stopping recording...");
          holdTimeout = setTimeout(() => {
            if (!holdRequested) {
              stopRecordingOnly();
            }
          }, 100);
        }
      }
    });

    toggleBtn.addEventListener("touchend", (e) => {
      e.preventDefault();
      console.log("Hold released (touchend)");
      holdRequested = false;
      if (isRecording) {
        holdTimeout = setTimeout(() => {
          if (!holdRequested) {
            stopRecordingOnly();
          }
        }, 200); 
      }
    });

    toggleBtn.addEventListener("click", (e) => {
      e.preventDefault();
    });
  } else {
    toggleBtn.addEventListener("click", async () => {
      toggleBtn.disabled = true;
      toggleBtn.classList.add("btn-processing");

      try {
        if (!isRecording) {
          await startSession();
        } else {
          stopSession();
        }
      } catch (err) {
        console.error("Error in toggle click:", err);
      }

      toggleBtn.disabled = false;
      toggleBtn.classList.remove("btn-processing");
    });
  }
});

function sendFunctionResult(callId, output) {
  if (dc && dc.readyState === "open") {
    dc.send(
      JSON.stringify({
        type: "conversation.item.create",
        item: {
          type: "function_call_output",
          call_id: callId,
          output: JSON.stringify(output),
        },
      })
    );
    dc.send(JSON.stringify({ type: "response.create" }));
  } else {
    console.error("Cannot send function result: Data channel is not open");
  }
}

function logAiUsage(usageData, model) {
  jQuery.ajax({
    type: "POST",
    url: aiomatic_realtime.ajax_url,
    data: {
      action: "aiomatic_log_realtime_usage",
      model,
      user_id: aiomatic_realtime.user_id,
      user_token_cap_per_day: aiomatic_realtime.user_token_cap_per_day,
      nonce: aiomatic_realtime.nonce,
      usage_data: JSON.stringify(usageData),
    },
    success: function (response) {
      if (typeof response === "string" || response instanceof String) {
        try {
          response = JSON.parse(response);
        } catch (error) {
          console.error("An error occurred while parsing the JSON:", error, "Json:", response);
        }
      }
      if (response.status === "usage") {
        alert(response.msg);
        stopSession();
      }
    },
    error: function (err) {
      console.error("Error logging usage:", err);
    },
  });
}