#!/data/data/com.termux/files/usr/bin/bash

export PATH="/data/data/com.termux/files/usr/bin:/data/data/com.termux/files/usr/bin/applets:$PATH"

echo "==============================================="
echo "      CodeDroid Native Terminal Bridge         "
echo "==============================================="

PREFIX="/data/data/com.termux/files/usr"
WORKSPACE="/sdcard/Download/CodeDroid"
PORT="9002"

# ------------------------------------------------
# 1. Storage Permission
# ------------------------------------------------

if [ ! -d "/sdcard" ]; then
    echo "[+] Requesting storage permission..."
    termux-setup-storage
    sleep 2
fi

# ------------------------------------------------
# 2. Enable External App Access
# ------------------------------------------------

echo "[+] Configuring Termux integration..."

mkdir -p ~/.termux
PROP_FILE="$HOME/.termux/termux.properties"

touch "$PROP_FILE"

grep -v "^allow-external-apps" "$PROP_FILE" > "${PROP_FILE}.tmp" || true
mv "${PROP_FILE}.tmp" "$PROP_FILE"

echo "allow-external-apps = true" >> "$PROP_FILE"

termux-reload-settings

# ------------------------------------------------
# 3. Install Dependencies
# ------------------------------------------------

install_pkg() {
    PKG="$1"
    CMD="${2:-$1}"

    if ! command -v "$CMD" >/dev/null 2>&1; then
        echo "[+] Installing $PKG ..."
        pkg update -y
        pkg install -y "$PKG"
    fi
}

install_pkg ttyd
install_pkg nodejs node

# ------------------------------------------------
# 4. Workspace Setup
# ------------------------------------------------

mkdir -p "$WORKSPACE"

cd "$WORKSPACE" || exit 1

# ------------------------------------------------
# 5. Kill Existing Server
# ------------------------------------------------

echo "[+] Cleaning old sessions..."

pkill -9 -f ttyd >/dev/null 2>&1 || true
pkill -9 -f codedroid_ws_api.js >/dev/null 2>&1 || true
sleep 1

# ------------------------------------------------
# 6. Environment Optimization
# ------------------------------------------------

export TERM=xterm-256color
export COLORTERM=truecolor
export LANG=en_US.UTF-8

# ------------------------------------------------
# 7. Launch Native PTY WebSocket Server
# ------------------------------------------------

echo "[+] Starting optimized terminal bridge..."
echo "[+] WebSocket endpoint: ws://127.0.0.1:${PORT}"
echo "[+] Workspace: ${WORKSPACE}"

# Create a custom wrapper for bash to ensure prompt commands are set inside Termux
CODEDROID_DIR="$HOME/.codedroid"
mkdir -p "$CODEDROID_DIR"

cat << 'EOF' > "$CODEDROID_DIR/codedroid_shell.sh"
if [ -f "/data/data/com.termux/files/usr/etc/bash.bashrc" ]; then
    source "/data/data/com.termux/files/usr/etc/bash.bashrc"
fi
if [ -f ~/.bashrc ]; then
    source ~/.bashrc
fi
export PROMPT_COMMAND='printf "\e]999;%s\a" "$PWD"; echo "$PWD" > /sdcard/Download/CodeDroid/.cwd'
EOF
chmod +x "$CODEDROID_DIR/codedroid_shell.sh"

# ------------------------------------------------
# 8. Start Native Node.js WebSocket API Command Server
# ------------------------------------------------
echo "[+] Starting WebSocket API server on ws://127.0.0.1:9003..."
cat << 'EOF' > "$CODEDROID_DIR/codedroid_ws_api.js"
const http = require('http');
const crypto = require('crypto');
const { exec, spawn } = require('child_process');

const server = http.createServer((req, res) => {
  res.writeHead(404);
  res.end();
});

server.on('upgrade', (req, socket) => {
  const key = req.headers['sec-websocket-key'];
  if (!key) {
    socket.destroy();
    return;
  }

  const acceptKey = crypto
    .createHash('sha1')
    .update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
    .digest('base64');

  socket.write(
    'HTTP/1.1 101 Switching Protocols\r\n' +
    'Upgrade: websocket\r\n' +
    'Connection: Upgrade\r\n' +
    'Sec-WebSocket-Accept: ' + acceptKey + '\r\n\r\n'
  );

  socket.on('close', () => {
    if (socket.childProcess) {
      try { socket.childProcess.kill('SIGKILL'); } catch (e) {}
    }
  });

  let buffer = Buffer.alloc(0);

  socket.on('data', (chunk) => {
    buffer = Buffer.concat([buffer, chunk]);
    while (buffer.length >= 2) {
      const firstByte = buffer[0];
      const secondByte = buffer[1];
      const opCode = firstByte & 0x0f;
      const isMasked = (secondByte & 0x80) !== 0;
      let payloadLength = secondByte & 0x7f;
      let offset = 2;

      if (payloadLength === 126) {
        if (buffer.length < 4) break;
        payloadLength = buffer.readUInt16BE(offset);
        offset += 2;
      } else if (payloadLength === 127) {
        if (buffer.length < 10) break;
        payloadLength = Number(buffer.readBigUInt64BE(offset));
        offset += 8;
      }

      let maskingKey;
      if (isMasked) {
        if (buffer.length < offset + 4) break;
        maskingKey = buffer.slice(offset, offset + 4);
        offset += 4;
      }

      if (buffer.length < offset + payloadLength) break;
      const rawPayload = buffer.slice(offset, offset + payloadLength);
      buffer = buffer.slice(offset + payloadLength);

      let payload = rawPayload;
      if (isMasked && maskingKey) {
        payload = Buffer.alloc(rawPayload.length);
        for (let i = 0; i < rawPayload.length; i++) {
          payload[i] = rawPayload[i] ^ maskingKey[i % 4];
        }
      }

      if (opCode === 8) {
        if (socket.childProcess) {
          try { socket.childProcess.kill('SIGKILL'); } catch (e) {}
        }
        socket.end();
        return;
      }

      if (opCode === 1 || opCode === 2) {
        const msgStr = payload.toString('utf8');
        try {
          const reqObj = JSON.parse(msgStr);
          const type = reqObj.type;

          if (type === 'spawn') {
            const cmd = reqObj.command;
            const args = reqObj.args || [];
            const defaultShell = '/data/data/com.termux/files/usr/bin/sh';
            const defaultCwd = process.env.HOME || '/data/data/com.termux/files/home';
            const cwd = reqObj.cwd || defaultCwd;

            const child = spawn(cmd, args, { cwd, shell: defaultShell });
            socket.childProcess = child;

            child.stdout.on('data', (data) => {
              sendTextFrame(socket, JSON.stringify({ type: 'stdout', data: data.toString('utf8') }));
            });

            child.stderr.on('data', (data) => {
              sendTextFrame(socket, JSON.stringify({ type: 'stderr', data: data.toString('utf8') }));
            });

            child.on('close', (code) => {
              sendTextFrame(socket, JSON.stringify({ type: 'exit', code }));
            });

            child.on('error', (err) => {
              sendTextFrame(socket, JSON.stringify({ type: 'error', message: err.message }));
            });
          } else if (type === 'stdin') {
            if (socket.childProcess) {
              socket.childProcess.stdin.write(reqObj.data);
            }
          } else if (type === 'kill') {
            if (socket.childProcess) {
              try { socket.childProcess.kill('SIGKILL'); } catch (e) {}
            }
          } else {
            const cmd = reqObj.command;
            const defaultShell = '/data/data/com.termux/files/usr/bin/sh';
            const defaultCwd = process.env.HOME || '/data/data/com.termux/files/home';
            const cwd = reqObj.cwd || defaultCwd;

            exec(cmd, { cwd, shell: defaultShell }, (error, stdout, stderr) => {
              const respObj = {
                stdout: stdout,
                stderr: stderr,
                exitCode: error ? (error.code || 1) : 0
              };
              sendTextFrame(socket, JSON.stringify(respObj));
            });
          }
        } catch (err) {
          sendTextFrame(socket, JSON.stringify({ error: err.message }));
        }
      }
    }
  });
});

function sendTextFrame(socket, text) {
  const payload = Buffer.from(text, 'utf8');
  const len = payload.length;
  let header;

  if (len <= 125) {
    header = Buffer.alloc(2);
    header[0] = 0x81;
    header[1] = len;
  } else if (len <= 65535) {
    header = Buffer.alloc(4);
    header[0] = 0x81;
    header[1] = 126;
    header.writeUInt16BE(len, 2);
  } else {
    header = Buffer.alloc(10);
    header[0] = 0x81;
    header[1] = 127;
    header.writeBigUInt64BE(BigInt(len), 2);
  }

  socket.write(Buffer.concat([header, payload]));
}

server.listen(9003, '127.0.0.1');
EOF

node "$CODEDROID_DIR/codedroid_ws_api.js" > "${WORKSPACE}/codedroid_ws_api.log" 2>&1 &

exec ttyd --writable --max-clients 50 -p "${PORT}" bash --rcfile "$CODEDROID_DIR/codedroid_shell.sh" -i > /data/data/com.termux/files/home/ttyd.log 2>&1


