/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { l10nHelper } from "resource:///modules/imXPCOMUtils.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyGetter(lazy, "_", () =>
  l10nHelper("chrome://chat/locale/xmpp.properties")
);

// Get conversation object.
function getConv(aConv) {
  return aConv.wrappedJSObject;
}

// Get account object.
function getAccount(aConv) {
  return getConv(aConv)._account;
}

function getMUC(aConv) {
  let conv = getConv(aConv);
  if (conv.left) {
    conv.writeMessage(
      conv.name,
      lazy._("conversation.error.commandFailedNotInRoom"),
      { system: true }
    );
    return null;
  }
  return conv;
}

// Trims the string and splits it in two parts on the first space
// if there is one. Returns the non-empty parts in an array.
function splitInput(aString) {
  let params = aString.trim();
  if (!params) {
    return [];
  }

  let splitParams = [];
  let offset = params.indexOf(" ");
  if (offset != -1) {
    splitParams.push(params.slice(0, offset));
    splitParams.push(params.slice(offset + 1).trimLeft());
  } else {
    splitParams.push(params);
  }
  return splitParams;
}

// Trims the string and splits it in two parts (The first part is a nickname
// and the second part is the rest of string) based on nicknames of current
// participants. Returns the non-empty parts in an array.
function splitByNick(aString, aConv) {
  let params = aString.trim();
  if (!params) {
    return [];
  }

  // Match trimmed-string with the longest prefix of participant's nickname.
  let nickName = "";
  for (let participant of aConv._participants.keys()) {
    if (
      params.startsWith(participant + " ") &&
      participant.length > nickName.length
    ) {
      nickName = participant;
    }
  }
  if (!nickName) {
    let offset = params.indexOf(" ");
    let expectedNickName = offset != -1 ? params.slice(0, offset) : params;
    aConv.writeMessage(
      aConv.name,
      lazy._("conversation.error.nickNotInRoom", expectedNickName),
      { system: true }
    );
    return [];
  }

  let splitParams = [];
  splitParams.push(nickName);

  let msg = params.substring(nickName.length);
  if (msg) {
    splitParams.push(msg.trimLeft());
  }
  return splitParams;
}

// Splits aMsg in two entries and checks the first entry is a valid jid, then
// passes it to aConv.invite().
// Returns false if aMsg is empty, otherwise returns true.
function invite(aMsg, aConv) {
  let params = splitInput(aMsg);
  if (!params.length) {
    return false;
  }

  // Check user's jid is valid.
  let account = getAccount(aConv);
  let jid = account._parseJID(params[0]);
  if (!jid) {
    aConv.writeMessage(
      aConv.name,
      lazy._("conversation.error.invalidJID", params[0]),
      { system: true }
    );
    return true;
  }

  aConv.invite(...params);
  return true;
}

export var commands = [
  {
    name: "join",
    get helpString() {
      return lazy._("command.join3", "join");
    },
    run(aMsg, aConv, aReturnedConv) {
      let account = getAccount(aConv);
      let params = aMsg.trim();
      let conv;

      if (!params) {
        conv = getConv(aConv);
        if (!conv.isChat) {
          return false;
        }
        if (!conv.left) {
          return true;
        }

        // Rejoin the current conversation. If the conversation was explicitly
        // parted by the user, chatRoomFields will have been deleted.
        // Otherwise, make use of it.
        if (conv.chatRoomFields) {
          account.joinChat(conv.chatRoomFields);
          return true;
        }

        params = conv.name;
      }
      let chatRoomFields = account.getChatRoomDefaultFieldValues(params);
      conv = account.joinChat(chatRoomFields);

      if (aReturnedConv) {
        aReturnedConv.value = conv;
      }
      return true;
    },
  },
  {
    name: "part",
    get helpString() {
      return lazy._("command.part2", "part");
    },
    usageContext: Ci.imICommand.CMD_CONTEXT_CHAT,
    run(aMsg, aConv) {
      let conv = getConv(aConv);
      if (!conv.left) {
        conv.part(aMsg);
      }
      return true;
    },
  },
  {
    name: "topic",
    get helpString() {
      return lazy._("command.topic", "topic");
    },
    usageContext: Ci.imICommand.CMD_CONTEXT_CHAT,
    run(aMsg, aConv) {
      let conv = getMUC(aConv);
      if (!conv) {
        return true;
      }
      conv.topic = aMsg;
      return true;
    },
  },
  {
    name: "ban",
    get helpString() {
      return lazy._("command.ban", "ban");
    },
    usageContext: Ci.imICommand.CMD_CONTEXT_CHAT,
    run(aMsg, aConv) {
      let params = splitInput(aMsg);
      if (!params.length) {
        return false;
      }

      let conv = getMUC(aConv);
      if (conv) {
        conv.ban(...params);
      }
      return true;
    },
  },
  {
    name: "kick",
    get helpString() {
      return lazy._("command.kick", "kick");
    },
    usageContext: Ci.imICommand.CMD_CONTEXT_CHAT,
    run(aMsg, aConv) {
      let conv = getMUC(aConv);
      if (!conv) {
        return true;
      }

      let params = splitByNick(aMsg, conv);
      if (!params.length) {
        return false;
      }
      conv.kick(...params);
      return true;
    },
  },
  {
    name: "invite",
    get helpString() {
      return lazy._("command.invite", "invite");
    },
    usageContext: Ci.imICommand.CMD_CONTEXT_CHAT,
    run(aMsg, aConv) {
      let conv = getMUC(aConv);
      if (!conv) {
        return true;
      }

      return invite(aMsg, conv);
    },
  },
  {
    name: "inviteto",
    get helpString() {
      return lazy._("command.inviteto", "inviteto");
    },
    usageContext: Ci.imICommand.CMD_CONTEXT_IM,
    run: (aMsg, aConv) => invite(aMsg, getConv(aConv)),
  },
  {
    name: "me",
    get helpString() {
      return lazy._("command.me", "me");
    },
    usageContext: Ci.imICommand.CMD_CONTEXT_CHAT,
    run(aMsg, aConv) {
      let params = aMsg.trim();
      if (!params) {
        return false;
      }

      let conv = getConv(aConv);
      conv.sendMsg(params, true);

      return true;
    },
  },
  {
    name: "nick",
    get helpString() {
      return lazy._("command.nick", "nick");
    },
    usageContext: Ci.imICommand.CMD_CONTEXT_CHAT,
    run(aMsg, aConv) {
      let params = aMsg.trim().split(/\s+/);
      if (!params[0]) {
        return false;
      }

      let conv = getMUC(aConv);
      if (conv) {
        conv.setNick(params[0]);
      }
      return true;
    },
  },
  {
    name: "msg",
    get helpString() {
      return lazy._("command.msg", "msg");
    },
    usageContext: Ci.imICommand.CMD_CONTEXT_CHAT,
    run(aMsg, aConv, aReturnedConv) {
      let conv = getMUC(aConv);
      if (!conv) {
        return true;
      }

      let params = splitByNick(aMsg, conv);
      if (params.length != 2) {
        return false;
      }
      let [nickName, msg] = params;

      let account = getAccount(aConv);
      let privateConv = account.createConversation(conv.name + "/" + nickName);
      if (!privateConv) {
        return true;
      }
      privateConv.sendMsg(msg.trim());

      if (aReturnedConv) {
        aReturnedConv.value = privateConv;
      }
      return true;
    },
  },
  {
    name: "version",
    get helpString() {
      return lazy._("command.version", "version");
    },
    usageContext: Ci.imICommand.CMD_CONTEXT_IM,
    run(aMsg, aConv, aReturnedConv) {
      let conv = getConv(aConv);
      if (conv.left) {
        return true;
      }

      // We do not have user's resource.
      if (!conv._targetResource) {
        conv.writeMessage(
          conv.name,
          lazy._("conversation.error.resourceNotAvailable", conv.shortName),
          {
            system: true,
          }
        );
        return true;
      }

      conv.getVersion();
      return true;
    },
  },
];
