Backend:

/* eslint-disable require-jsdoc */
/* eslint-disable max-len */

// Import necessary modules
const functions = require("firebase-functions");
const admin = require("firebase-admin");
const express = require("express");
const cors = require("cors"); // For chatWithOpenAI function

// Access configuration variables
const databaseURL = functions.config().fbrt.database_url;
const openaiApiKey = functions.config().openai.apikey;

// Initialize Firebase Admin SDK with databaseURL
admin.initializeApp({
  credential: admin.credential.applicationDefault(),
  databaseURL: databaseURL,
});

// Create an Express app
const app = express();
app.use(cors({origin: true})); // Enable CORS for all origins
app.use(express.json()); // Parse JSON bodies

// Middleware to verify Firebase Auth token and check for 'psychologist' role
async function authenticate(req, res, next) {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith("Bearer ")) {
    res.status(401).send("Unauthorized: Missing or invalid token");
    return;
  }

  const idToken = authHeader.split("Bearer ")[1];

  try {
    const decodedToken = await admin.auth().verifyIdToken(idToken);

    // Check if the user has the 'psychologist' role
    if (decodedToken.role !== "psychologist") {
      res.status(403).send("Forbidden: Insufficient permissions");
      return;
    }

    req.user = decodedToken;
    next();
  } catch (error) {
    console.error("Error verifying Firebase ID token:", error);
    res.status(401).send("Unauthorized");
  }
}

// Route to fetch users data
app.get("/users", authenticate, async (req, res) => {
  try {
    // Fetch users data from Realtime Database
    const usersRef = admin.database().ref("users");
    const snapshot = await usersRef.once("value");
    const usersData = snapshot.val();

    res.status(200).json(usersData || {});
  } catch (error) {
    console.error("Error fetching users data:", error);
    res.status(500).send("Internal Server Error");
  }
});

// Route to fetch user activity logs
app.get("/activityLogs", authenticate, async (req, res) => {
  try {
    // Fetch activity logs from Realtime Database
    const logsRef = admin.database().ref("activityLogs");
    const snapshot = await logsRef.once("value");
    const logsData = snapshot.val();

    res.status(200).json(logsData || {});
  } catch (error) {
    console.error("Error fetching activity logs:", error);
    res.status(500).send("Internal Server Error");
  }
});

// Cloud Function for chatWithOpenAI
exports.chatWithOpenAI = functions.https.onRequest(async (req, res) => {
  // Set CORS headers to allow cross-origin requests
  res.set("Access-Control-Allow-Origin", "*");
  res.set("Access-Control-Allow-Headers", "Content-Type");

  // Handle preflight request (for CORS)
  if (req.method === "OPTIONS") {
    res.status(204).send("");
    return;
  }

  // Ensure only POST requests are accepted
  if (req.method !== "POST") {
    res.status(405).send({error: "Only POST requests are accepted"});
    return;
  }

  try {
    const {conversationHistory} = req.body;
    console.log("Received conversationHistory:", conversationHistory);

    // Validate conversationHistory
    if (!Array.isArray(conversationHistory)) {
      res.status(400).send({error: "\"conversationHistory\" must be an array"});
      return;
    }

    // Use OpenAI API key from environment variables
    const apiKey = openaiApiKey;

    if (!apiKey) {
      console.error("OpenAI API key is not configured.");
      res.status(500).send({error: "OpenAI API key is not configured"});
      return;
    }

    // System prompt to guide the assistant
    const systemPrompt = `You are a compassionate and skilled mental health assistant trained in Cognitive Behavioral Therapy (CBT) and mindfulness practices. Your mission is to support users in identifying, restructuring, and challenging negative thoughts through a natural, private conversation, similar to talking with a trusted friend.

**Guidelines:**

1. **Warm and Empathetic Tone:** Maintain a friendly, understanding, and non-judgmental manner. Ensure your responses are brief, clear, and resemble everyday human conversation.

2. **CBT Techniques:**
   - **Identify Negative Thoughts:** Help users recognize and articulate their negative thoughts.
   - **Explore Underlying Beliefs:** Encourage users to delve deeper into the beliefs driving their negative thoughts.
   - **Challenge and Restructure:** Assist users in examining the validity of their thoughts by considering real-life evidence and alternative perspectives.

3. **Mindfulness Integration:**
   - **Present Moment Awareness:** Guide users to focus on the here and now when appropriate.
   - **Breathing Exercises:** Suggest simple mindfulness practices, such as deep breathing, to help users center themselves.

4. **Step-by-Step Support:**
   - **Progressive Solutions:** Introduce coping strategies and solutions gradually to avoid overwhelming the user.
   - **Actionable Steps:** Break down information into manageable, practical actions that users can implement.

5. **Encourage Self-Reflection:**
   - **Open-Ended Questions:** Pose questions that prompt users to reflect on their feelings and thoughts more deeply.
   - **Active Listening:** Acknowledge user feelings and validate their experiences to foster a sense of understanding.

6. **Concise Responses:**
   - **Avoid Lengthy Paragraphs:** Keep replies short and to the point to facilitate a smooth and engaging conversation.
   - **Clear and Direct Language:** Use straightforward language to ensure clarity and ease of understanding.

7. **Safety and Boundaries:**
   - **Recognize Distress Signals:** Be attentive to signs of severe distress or mentions of self-harm.
   - **Encourage Professional Help:** Prompt users to seek professional assistance when necessary, ensuring their safety is prioritized.

**Example Interaction:**

*User:* I've been feeling really overwhelmed and can't stop thinking about my failures.

*Assistant:* I'm sorry you're feeling this way. Can you share more about these thoughts of failure?

*User:* I keep thinking I messed up that important presentation at work.

*Assistant:* That sounds stressful. What evidence do you have that supports or contradicts this thought?

*User:* Well, my manager actually praised my effort afterwards.

*Assistant:* That's a positive sign! How can we view the presentation more balanced, considering both the feedback and your feelings?

*User:* Maybe I focused too much on one part and overlooked others, but overall it was well-received.

*Assistant:* It sounds like you did better than you initially thought. How can you remind yourself of this next time you start feeling overwhelmed by negative thoughts?

*User:* I could write down the positive feedback and review it when I feel down.

*Assistant:* That’s a great idea. Keeping a list of positive feedback can help you stay balanced. Would you like to discuss ways to implement this?

---

\n\n`;

    // Function to summarize conversation if it exceeds a certain number of turns
    const summarizeConversation = async (messages) => {
      const summaryPrompt = `Summarize the following conversation into a concise summary (3-5 sentences) that captures the main points and context:

${messages
      .map(
          (turn) =>
            `${turn.role === "user" ? "User" : "Assistant"}: ${turn.content}`,
      )
      .join("\n")}

Summary:`;

      const summaryPayload = {
        model: "gpt-4",
        messages: [
          {
            role: "system",
            content: "You are an assistant that summarizes conversations concisely.",
          },
          {role: "user", content: summaryPrompt},
        ],
        max_tokens: 150,
        temperature: 0.5,
      };

      const summaryResponse = await fetch("https://api.openai.com/v1/chat/completions", {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${apiKey}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(summaryPayload),
      });

      if (!summaryResponse.ok) {
        const errorData = await summaryResponse.json();
        console.error("Error summarizing conversation:", JSON.stringify(errorData, null, 2));
        throw new Error("Failed to summarize conversation");
      }

      const summaryData = await summaryResponse.json();
      return summaryData.choices[0].message.content.trim();
    };

    // Transform conversationHistory into OpenAI's message format
    let messages = [{role: "system", content: systemPrompt}];

    if (conversationHistory.length > 0) {
      conversationHistory.forEach((turn) => {
        if (turn.sender === "user") {
          messages.push({role: "user", content: turn.text});
        } else if (turn.sender === "bot") {
          messages.push({role: "assistant", content: turn.text});
        }
      });
    }

    // Optionally summarize if conversation is too long
    const MAX_TURNS = 40; // Adjust based on token limits and requirements
    if (messages.length > MAX_TURNS) {
      // Extract the last MAX_TURNS messages
      const recentMessages = messages.slice(-MAX_TURNS);
      const summary = await summarizeConversation(recentMessages);
      // Reset messages with system prompt and summary
      messages = [
        {role: "system", content: systemPrompt},
        {role: "user", content: `Summary of previous conversation: ${summary}`},
      ];
    }

    console.log("Constructed messages array:", messages);

    // Create the payload for OpenAI API
    const payload = {
      model: "gpt-4",
      messages: messages,
      max_tokens: 150, // Limit the length of the response
      temperature: 0.7, // Control creativity
      top_p: 0.9, // Sampling method for diverse responses
      n: 1, // Number of responses to generate
      stop: null, // Let the model decide when to stop
    };

    // OpenAI API endpoint
    const OPENAI_ENDPOINT = "https://api.openai.com/v1/chat/completions";

    // Send the request to OpenAI
    const response = await fetch(OPENAI_ENDPOINT, {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${apiKey}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(payload),
    });

    if (!response.ok) {
      const errorData = await response.json();
      console.error("Error from OpenAI API:", JSON.stringify(errorData, null, 2));
      res.status(response.status).send({error: "Error from OpenAI API", details: errorData});
      return;
    }

    // Parse the response from OpenAI
    const data = await response.json();
    console.log("Data from OpenAI API:", JSON.stringify(data, null, 2));

    // Extract assistant's reply
    const assistantReply = data.choices[0].message.content.trim();

    // Send the assistant's reply back to the client
    res.status(200).send({reply: assistantReply});
  } catch (error) {
    console.error("Error in Cloud Function:", error);
    res.status(500).send({error: "Internal server error"});
  }
});

// Export the Express app as a Firebase Cloud Function
exports.api = functions.https.onRequest(app);


Frontend: