// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
//
// SPDX-License-Identifier: AGPL-3.0-or-later

define([
    'jquery',
    '/common/common-util.js',
    '/common/common-hash.js',
    '/common/common-interface.js',
    '/common/common-ui-elements.js',
    '/common/pad-types.js',
    '/common/inner/common-modal.js',
    '/common/hyperscript.js',
    '/customize/messages.js',
    '/components/nthen/index.js',
], function ($, Util, Hash, UI, UIElements, Types, Modal, h,
             Messages, nThen) {
    var Access = {};

    var getOwnersTab = function (Env, data, opts, _cb) {
        var cb = Util.once(Util.mkAsync(_cb));
        var common = Env.common;

        var parsed = Hash.parsePadUrl(data.href || data.roHref);
        var owned = Modal.isOwned(Env, data);
        var disabled = !owned || !parsed.hashData || parsed.hashData.type !== 'pad';
        if (disabled) { return void cb(); }

        var friends = common.getFriends(true);
        var sframeChan = common.getSframeChannel();
        var metadataMgr = common.getMetadataMgr();

        var priv = metadataMgr.getPrivateData();
        var owners = data.owners || [];
        var pending_owners = data.pending_owners || [];
        var teamOwner = data.teamId;
        var title = opts.title;

        var p = priv.propChannels;
        var otherChan;
        if (p && p.answersChannel) {
            otherChan = [p.answersChannel];
        }

        opts = opts || {};
        var redrawAll = function () {};

        var addBtn = h('button.btn.btn-primary.cp-access-add', [h('i.fa.fa-arrow-left'), h('i.fa.fa-arrow-up')]);

        var div1 = h('div.cp-share-column.cp-ownership');
        var divMid = h('div.cp-share-column-mid', addBtn);
        var div2 = h('div.cp-share-column.cp-ownership');
        var $div1 = $(div1);
        var $div2 = $(div2);

        // Remove owner column
        var drawRemove = function (pending) {
            var priv = metadataMgr.getPrivateData();
            var user = metadataMgr.getUserData();

            var _owners = {};
            var o = (pending ? pending_owners : owners) || [];
            o.forEach(function (ed) {
                var f;
                Object.keys(friends).some(function (c) {
                    if (friends[c].edPublic === ed) {
                        f = friends[c];
                        return true;
                    }
                });
                Object.keys(priv.teams).some(function (id) {
                    if (priv.teams[id].edPublic === ed) {
                        f = priv.teams[id];
                        f.teamId = id;
                    }
                });
                if (ed === priv.edPublic) {
                    f = f || user;
                    if (f.name) { f.edPublic = priv.edPublic; }
                }
                _owners[ed] = f ? Util.clone(f) : {
                    displayName: Messages._getKey('owner_unknownUser', [ed]),
                    edPublic: ed,
                };
            });

            var remove = function (el) {
                // Check selection
                var me = false;
                var $el = $(el);
                var ed = $el.attr('data-ed');
                if (!ed) { return; }
                if (teamOwner && priv.teams[teamOwner] && priv.teams[teamOwner].edPublic === ed) {
                    me = true;
                }
                if (ed === priv.edPublic && !teamOwner) { me = true; }
                nThen(function (waitFor) {
                    var msg = me ? Messages.owner_removeMeConfirm : Messages.owner_removeConfirm;
                    UI.confirm(msg, waitFor(function (yes) {
                        if (!yes) {
                            waitFor.abort();
                            return;
                        }
                    }));
                }).nThen(function (waitFor) {
                    // Send the command
                    sframeChan.query('Q_SET_PAD_METADATA', {
                        channel: data.channel || priv.channel,
                        channels: otherChan,
                        command: pending ? 'RM_PENDING_OWNERS' : 'RM_OWNERS',
                        value: [ed],
                        teamId: teamOwner
                    }, waitFor(function (err, res) {
                        err = err || (res && res.error);
                        if (err) {
                            waitFor.abort();
                            redrawAll(true);
                            var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
                                                                          : Messages.error;
                            return void UI.warn(text);
                        }
                        UI.log(Messages.saved);
                    }));
                }).nThen(function (waitFor) {
                    var curve = $el.attr('data-curve');
                    if (curve === user.curvePublic) { return; }
                    var friend = friends[curve];
                    if (!friend) { return; }
                    common.mailbox.sendTo("RM_OWNER", {
                        channel: data.channel || priv.channel,
                        title: data.title || title,
                        pending: pending
                    }, {
                        channel: friend.notifications,
                        curvePublic: friend.curvePublic
                    }, waitFor());
                }).nThen(function () {
                    redrawAll(true);
                });
            };

            if (pending && !Object.keys(_owners).length) {
                return $();
            }

            var msg = pending ? Messages.owner_removePendingText
                        : Messages.owner_removeText;
            var removeCol = UIElements.getUserGrid(msg, {
                common: common,
                large: true,
                data: _owners,
                noSelect: true,
                list: true,
                remove: remove
            }, function () {
            });
            var $div = $(removeCol.div);
            return $div;
        };

        // Add owners column
        var drawAdd = function () {
            var priv = metadataMgr.getPrivateData();
            var teamsData = Util.tryParse(JSON.stringify(priv.teams)) || {};

            var $div = $(h('div.cp-share-column'));
            var _friends = Util.clone(friends);
            var friendKeys = Object.keys(_friends);
            friendKeys.forEach(function (curve) {
                if (owners.indexOf(_friends[curve].edPublic) !== -1 ||
                    pending_owners.indexOf(_friends[curve].edPublic) !== -1 ||
                    !_friends[curve].notifications) {
                    delete _friends[curve];
                }
            });
            if (!Object.keys(_friends).length) {
                var friendText;
                if (!friendKeys.length) {
                    //console.error(UIElements.noContactsMessage(common));
                    var findContacts = UIElements.noContactsMessage(common);
                    friendText = h('span.cp-app-prop-content',
                        findContacts.content
                    );
                } else {
                    friendText = h('span.cp-app-prop-content', Messages.access_noContact);
                }

                $div.append(h('div.cp-app-prop', [
                    Messages.contacts,
                    h('br'),
                    friendText
                ]));
            } else {
                var addCol = UIElements.getUserGrid(Messages.contacts, {
                    common: common,
                    large: true,
                    data: _friends
                }, function () {
                    //console.log(arguments);
                });
                $div.append(addCol.div);
            }

            var _teamsData = Util.clone(teamsData);
            Object.keys(_teamsData).forEach(function (id) {
                var t = _teamsData[id];
                t.teamId = id;
                if (owners.indexOf(t.edPublic) !== -1 || pending_owners.indexOf(t.edPublic) !== -1) {
                    delete _teamsData[id];
                }
            });
            if (!Object.keys(_teamsData).length) { return $div; }
            var teamsList = UIElements.getUserGrid(Messages.teams, {
                common: common,
                large: true,
                noFilter: true,
                data: _teamsData
            }, function () {});
            $div.append(teamsList.div);

            return $div;
        };

        $(addBtn).click(function () {
            var priv = metadataMgr.getPrivateData();
            var user = metadataMgr.getUserData();
            var teamsData = Util.tryParse(JSON.stringify(priv.teams)) || {};

            var $div = $div2.find('.cp-share-column');
            // Check selection
            var $sel = $div.find('.cp-usergrid-user.cp-selected');
            var sel = $sel.toArray();
            if (!sel.length) { return; }
            var addMe = false;
            var toAdd = sel.map(function (el) {
                var curve = $(el).attr('data-curve');
                // If the pad is owned by a team, we can transfer ownership to ourselves
                if (curve === user.curvePublic && teamOwner) {
                    addMe = true;
                    return;
                }
                var friend = friends[curve];
                if (!friend) { return; }
                return friend.edPublic;
            }).filter(function (x) { return x; });
            var toAddTeams = sel.map(function (el) {
                var team = teamsData[$(el).attr('data-teamid')];
                if (!team || !team.edPublic) { return; }
                return {
                    edPublic: team.edPublic,
                    id: $(el).attr('data-teamid')
                };
            }).filter(function (x) { return x; });

            nThen(function (waitFor) {
                var msg = Messages.owner_addConfirm;
                UI.confirm(msg, waitFor(function (yes) {
                    if (!yes) {
                        waitFor.abort();
                        return;
                    }
                }));
            }).nThen(function (waitFor) {
                // Add one of our teams as an owner
                if (toAddTeams.length) {
                    // Send the command
                    sframeChan.query('Q_SET_PAD_METADATA', {
                        channel: data.channel || priv.channel,
                        channels: otherChan,
                        command: 'ADD_OWNERS',
                        value: toAddTeams.map(function (obj) { return obj.edPublic; }),
                        teamId: teamOwner
                    }, waitFor(function (err, res) {
                        err = err || (res && res.error);
                        if (err) {
                            waitFor.abort();
                            redrawAll(true);
                            var text = err === "INSUFFICIENT_PERMISSIONS" ?
                                    Messages.fm_forbidden : Messages.error;
                            return void UI.warn(text);
                        }
                        var isTemplate = priv.isTemplate || opts.isTemplate;

                        // never store calendars in the team drive
                        if (opts.calendar) { return; }
                        toAddTeams.forEach(function (obj) {
                            sframeChan.query('Q_STORE_IN_TEAM', {
                                href: data.href || data.rohref,
                                password: data.password,
                                path: isTemplate ? ['template'] : undefined,
                                title: data.title || title || "",
                                teamId: obj.id
                            }, waitFor(function (err) {
                                if (err) { return void console.error(err); }
                            }));
                        });
                    }));
                }
            }).nThen(function (waitFor) {
                // Offer ownership to a friend
                if (toAdd.length) {
                    // Send the command
                    sframeChan.query('Q_SET_PAD_METADATA', {
                        channel: data.channel || priv.channel,
                        channels: otherChan,
                        command: 'ADD_PENDING_OWNERS',
                        value: toAdd,
                        teamId: teamOwner
                    }, waitFor(function (err, res) {
                        err = err || (res && res.error);
                        if (err) {
                            waitFor.abort();
                            redrawAll(true);
                            var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
                                                                          : Messages.error;
                            return void UI.warn(text);
                        }
                    }));
                }
            }).nThen(function (waitFor) {
                // Offer ownership to a friend
                if (addMe) {
                    // Send the command
                    sframeChan.query('Q_SET_PAD_METADATA', {
                        channel: data.channel || priv.channel,
                        channels: otherChan,
                        command: 'ADD_OWNERS',
                        value: [priv.edPublic],
                        teamId: teamOwner
                    }, waitFor(function (err, res) {
                        err = err || (res && res.error);
                        if (err) {
                            waitFor.abort();
                            redrawAll(true);
                            var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
                                                                          : Messages.error;
                            return void UI.warn(text);
                        }
                        sframeChan.query('Q_ACCEPT_OWNERSHIP', data, function (err, res) {
                            if (err || (res && res.error)) {
                                return void console.error(err || res.error);
                            }
                            UI.log(Messages.saved);
                        });
                    }));
                }
            }).nThen(function (waitFor) {
                var href = data.href;
                sel.forEach(function (el) {
                    var curve = $(el).attr('data-curve');
                    if (curve === user.curvePublic) { return; }
                    var friend = friends[curve];
                    if (!friend) { return; }
                    common.mailbox.sendTo("ADD_OWNER", {
                        channel: data.channel || priv.channel,
                        channels: otherChan,
                        href: href,
                        calendar: opts.calendar,
                        password: data.password || priv.password,
                        title: data.title || title
                    }, {
                        channel: friend.notifications,
                        curvePublic: friend.curvePublic
                    }, waitFor());
                });
            }).nThen(function () {
                redrawAll(true);
                UI.log(Messages.saved);
            });
        });
        $(addBtn).on('keydown', function () {
            if (event.keyCode === 13) {
                event.preventDefault();
                event.stopPropagation();
                $(addBtn).click();
            }
        });

        var called = false;
        redrawAll = function (reload) {
            if (called) { return; }
            called = true;
            nThen(function (waitFor) {
                if (!reload) { return; }
                Modal.loadMetadata(Env, data, waitFor, "owner");
            }).nThen(function () {
                var owned = Modal.isOwned(Env, data);
                if (typeof(owned) !== "boolean") {
                    teamOwner = Number(owned);
                } else {
                    teamOwner = undefined;
                }
                owners = data.owners || [];
                pending_owners = data.pending_owners || [];
                $div1.empty();
                $div2.empty();
                $div1.append(h('p', Messages.owner_text));
                $div1.append(drawRemove(false)).append(drawRemove(true));
                $div2.append(drawAdd());
                called = false;
            });
        };
        redrawAll();

        Env.evRedrawAll.reg(function (type) {
            if (type === "owner") { return; }
            setTimeout(function () {
                redrawAll();
            });
        });

        // Create modal
        var link = h('div.cp-share-columns', [
            div1,
            divMid,
            div2
        ]);
        cb(void 0, link);
    };

    var getAllowTab = function (Env, data, opts, _cb) {
        var cb = Util.once(Util.mkAsync(_cb));
        var common = Env.common;

        var parsed = Hash.parsePadUrl(data.href || data.roHref);
        var owned = Modal.isOwned(Env, data);
        var disabled = !owned || !parsed.hashData || parsed.hashData.type !== 'pad';
        if (disabled) { return void cb(); }

        opts = opts || {};

        var friends = common.getFriends(true);
        var sframeChan = common.getSframeChannel();
        var metadataMgr = common.getMetadataMgr();

        var priv = metadataMgr.getPrivateData();
        var owners = data.owners || [];
        var restricted = data.restricted || false;
        var allowed = data.allowed || [];
        var teamOwner = data.teamId;

        var p = priv.propChannels;
        var otherChan;
        if (p && p.answersChannel) {
            otherChan = [p.answersChannel];
        }

        var redrawAll = function () {};

        var addBtn = h('button.btn.btn-primary.cp-access-add', [h('i.fa.fa-arrow-left'), h('i.fa.fa-arrow-up')]);

        var div1 = h('div.cp-share-column.cp-allowlist');
        var divMid = h('div.cp-share-column-mid.cp-overlay-container', [
            addBtn,
            h('div.cp-overlay')
        ]);
        var div2 = h('div.cp-share-column.cp-allowlist');
        var $div1 = $(div1);
        var $div2 = $(div2);

        // Create modal
        var link = h('div.cp-share-columns', [
            div1,
            divMid,
            div2
        ]);

        var setLock = function (locked) {
            $(link).find('.cp-overlay').toggle(locked);
            $(link).find('.cp-usergrid-user').attr('tabindex', locked ? -1 : 0);
            $(link).find('.cp-usergrid-filter input').prop('disabled', locked);
            $(link).find('.cp-access-add').prop('disabled', locked);
        };

        // Remove owner column
        var drawRemove = function () {
            var priv = metadataMgr.getPrivateData();
            var user = metadataMgr.getUserData();

            var _allowed = {};
            var all = Util.deduplicateString(owners.concat(allowed));
            all.forEach(function (ed) {
                var f;
                Object.keys(friends).some(function (c) {
                    if (friends[c].edPublic === ed) {
                        f = friends[c];
                        return true;
                    }
                });
                Object.keys(priv.teams).some(function (id) {
                    if (priv.teams[id].edPublic === ed) {
                        f = priv.teams[id];
                        f.teamId = id;
                    }
                });
                if (ed === priv.edPublic) {
                    f = f || user;
                    if (f.name) { f.edPublic = priv.edPublic; }
                }
                _allowed[ed] = f ? Util.clone(f) : {
                    displayName: Messages._getKey('owner_unknownUser', [ed]),
                    edPublic: ed,
                };

                if (owners.indexOf(ed) !== -1) {
                    _allowed[ed].notRemovable = true;
                }
            });

            var remove = function (el) {
                // Check selection
                var $el = $(el);
                var ed = $el.attr('data-ed');
                if (!ed) { return; }
                nThen(function (waitFor) {
                    /*
                    var msg = Messages.allow_removeConfirm;
                    UI.confirm(msg, waitFor(function (yes) {
                        if (!yes) {
                            waitFor.abort();
                            return;
                        }
                    }));
                }).nThen(function (waitFor) {
                    */
                    // Send the command
                    sframeChan.query('Q_SET_PAD_METADATA', {
                        channel: data.channel || priv.channel,
                        channels: otherChan,
                        command: 'RM_ALLOWED',
                        value: [ed],
                        teamId: teamOwner
                    }, waitFor(function (err, res) {
                        err = err || (res && res.error);
                        redrawAll(true);
                        if (err) {
                            waitFor.abort();
                            var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
                                                                          : Messages.error;
                            return void UI.warn(text);
                        }
                        UI.log(Messages.saved);
                    }));
                });
            };

            var cbox = UI.createCheckbox('cp-allowlist', Messages.allow_checkbox, restricted);
            var $cbox = $(cbox);
            var spinner = UI.makeSpinner($cbox);
            var $checkbox = $cbox.find('input').on('change', function () {
                if (spinner.getState()) {
                    $checkbox.prop('checked', !$checkbox.prop('checked'));
                    return;
                }
                spinner.spin();
                var val = $checkbox.is(':checked');
                sframeChan.query('Q_SET_PAD_METADATA', {
                    channel: data.channel || priv.channel,
                    channels: otherChan,
                    command: 'RESTRICT_ACCESS',
                    value: [Boolean(val)],
                    teamId: teamOwner
                }, function (err, res) {
                    err = err || (res && res.error);
                    redrawAll(true);
                    if (err) {
                        spinner.hide();
                        var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
                                                                      : Messages.error;
                        return void UI.warn(text);
                    }
                    spinner.done();
                    UI.log(Messages.saved);
                });
            });


            var msg = Messages._getKey('allow_label', ['']);
            var removeCol = UIElements.getUserGrid(msg, {
                common: common,
                large: true,
                data: _allowed,
                noSelect: true,
                list: true,
                remove: remove
            }, function () { });
            $(removeCol.div).addClass('cp-overlay-container').append(h('div.cp-overlay'));
            return h('div', [
                h('p', Messages.allow_text),
                h('p', cbox),
                removeCol.div
            ]);
        };

        // Add allow list column
        var drawAdd = function () {
            var priv = metadataMgr.getPrivateData();
            var teamsData = Util.tryParse(JSON.stringify(priv.teams)) || {};

            var $div = $(h('div.cp-share-column'));

            $div.addClass('cp-overlay-container').append(h('div.cp-overlay'));

            var _friends = Util.clone(friends);
            var friendKeys = Object.keys(_friends);
            friendKeys.forEach(function (curve) {
                if (owners.indexOf(_friends[curve].edPublic) !== -1 ||
                    allowed.indexOf(_friends[curve].edPublic) !== -1) {
                    delete _friends[curve];
                }
            });
            if (!Object.keys(_friends).length) {
                var friendText;
                if (!friendKeys.length) {
                    var findContacts = UIElements.noContactsMessage(common);
                    friendText = h('span.cp-app-prop-content',
                        findContacts.content
                    );
                } else {
                    friendText = h('span.cp-app-prop-content', Messages.access_noContact);
                }

                $div.append(h('div.cp-app-prop', [
                    Messages.contacts,
                    h('br'),
                    friendText
                ]));
            } else {
                var addCol = UIElements.getUserGrid(Messages.contacts, {
                    common: common,
                    large: true,
                    data: _friends
                }, function () {
                    //console.log(arguments);
                });
                $div.append(addCol.div);
            }

            var _teamsData = Util.clone(teamsData);
            Object.keys(_teamsData).forEach(function (id) {
                var t = _teamsData[id];
                t.teamId = id;
                if (owners.indexOf(t.edPublic) !== -1 || allowed.indexOf(t.edPublic) !== -1) {
                    delete _teamsData[id];
                }
            });
            if (!Object.keys(_teamsData).length) { return $div; }
            var teamsList = UIElements.getUserGrid(Messages.teams, {
                common: common,
                large: true,
                noFilter: true,
                data: _teamsData
            }, function () {});
            $div.append(teamsList.div);

            return $div;
        };
        $(addBtn).click(function () {
            var priv = metadataMgr.getPrivateData();
            var user = metadataMgr.getUserData();
            var teamsData = Util.tryParse(JSON.stringify(priv.teams)) || {};

            var $div = $div2.find('.cp-share-column');
            // Check selection
            var $sel = $div.find('.cp-usergrid-user.cp-selected');
            var sel = $sel.toArray();
            if (!sel.length) { return; }
            var dataToAdd = [];
            var toAdd = sel.map(function (el) {
                var curve = $(el).attr('data-curve');
                var teamId = $(el).attr('data-teamid');
                // If the pad is woned by a team, we can transfer ownership to ourselves
                if (curve === user.curvePublic && teamOwner) { return priv.edPublic; }
                var data = friends[curve] || teamsData[teamId];
                dataToAdd.push(data);
                if (!data) { return; }
                return data.edPublic;
            }).filter(function (x) { return x; });

            nThen(function (waitFor) {
                /*
                var msg = Messages.allow_addConfirm;
                UI.confirm(msg, waitFor(function (yes) {
                    if (!yes) {
                        waitFor.abort();
                        return;
                    }
                }));
            }).nThen(function (waitFor) {
                */
                // Offer ownership to a friend
                if (toAdd.length) {
                    // Send the command
                    sframeChan.query('Q_SET_PAD_METADATA', {
                        channel: data.channel || priv.channel,
                        channels: otherChan,
                        command: 'ADD_ALLOWED',
                        value: toAdd,
                        teamId: teamOwner
                    }, waitFor(function (err, res) {
                        err = err || (res && res.error);
                        redrawAll(true);
                        dataToAdd.forEach(function(mailbox) {
                            if (mailbox.notifications && mailbox.curvePublic) {
                                common.mailbox.sendTo("ADD_TO_ACCESS_LIST", {
                                    channel: data.channel || priv.channel,
                                }, {
                                    channel: mailbox.notifications,
                                    curvePublic: mailbox.curvePublic
                                });
                            } else {
                                return;
                            }
                        });

                        if (err) {
                            waitFor.abort();
                            var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
                                                                          : Messages.error;
                            return void UI.warn(text);
                        }
                    }));
                }
            }).nThen(function () {
                UI.log(Messages.saved);
            });
        });
        $(addBtn).on('keydown', function () {
            if (event.keyCode === 13) {
                event.preventDefault();
                event.stopPropagation();
                $(addBtn).click();
            }
        });

        var called = false;
        redrawAll = function (reload) {
            if (called) { return; }
            called = true;
            nThen(function (waitFor) {
                if (!reload) { return; }
                Modal.loadMetadata(Env, data, waitFor, "allow");
            }).nThen(function () {
                var owned = Modal.isOwned(Env, data);
                if (typeof(owned) !== "boolean") {
                    teamOwner = Number(owned);
                } else {
                    teamOwner = undefined;
                }
                owners = data.owners || [];
                restricted = data.restricted || false;
                allowed = data.allowed || [];
                $div1.empty();
                $div2.empty();
                $div1.append(drawRemove());
                $div2.append(drawAdd());
                setLock(!restricted);
                called = false;
            });
        };
        redrawAll();

        Env.evRedrawAll.reg(function (type) {
            if (type === "allow") { return; }
            setTimeout(function () {
                redrawAll();
            });
        });

        cb(void 0, link);
    };

    var getUserList = function (common, list) {
        if (!Array.isArray(list)) { return; }
        var priv = common.getMetadataMgr().getPrivateData();
        var user = common.getMetadataMgr().getUserData();
        var edPublic = priv.edPublic;
        //var strangers = 0;
        var _owners = {};
        list.forEach(function (ed) {
            // If a friend is an owner, add their name to the list
            // otherwise, increment the list of strangers

            // Our edPublic? print "Yourself"
            if (ed === edPublic) {
                _owners[ed] = {
                    //selected: true,
                    name: user.name,
                    avatar: user.avatar,
                    uid: user.uid
                };
                return;
            }
            // One of our teams? print the team name
            if (Object.keys(priv.teams || {}).some(function (id) {
                var team = priv.teams[id] || {};
                if (team.edPublic !== ed) { return; }
                _owners[ed] = {
                    name: team.name,
                    avatar: team.avatar
                };
                return true;
            })) {
                return;
            }
            // One of our friends? print the friend name
            if (Object.keys(priv.friends || {}).some(function (c) {
                var friend = priv.friends[c] || {};
                if (friend.edPublic !== ed || c === 'me') { return; }
                _owners[friend.edPublic] = {
                    name: friend.displayName,
                    avatar: friend.avatar,
                    uid: friend.uid,
                };
                return true;
            })) {
                return;
            }
            // Otherwise it's a stranger
            _owners[ed] = {
                avatar: '?',
                name: Messages.owner_unknownUser,
                // TODO a possible enhancement is to use data from the context
                // ie. if you have opened the access modal from within the pad
                // its owner might be present or they might have left some data
                // in the pad itself (as is the case of the uid in rich text comments)
                // TODO or just implement "Acquaintances"
            };
            //strangers++;
        });
        if (!Object.keys(_owners).length) { return; }
        /*
        if (strangers) {
            _owners['stangers'] = {
                name: Messages._getKey('properties_unknownUser', [strangers]),
            };
        }
        */
        return UIElements.getUserGrid(null, {
            common: common,
            noSelect: true,
            data: _owners,
            large: true
        }, function () {});
    };

    var getAccessTab = function (Env, data, opts, _cb) {
        var cb = Util.once(Util.mkAsync(_cb));
        var common = Env.common;
        opts = opts || {};

        var sframeChan = common.getSframeChannel();
        var metadataMgr = common.getMetadataMgr();
        var priv = metadataMgr.getPrivateData();

        var $div = $(h('div.cp-share-columns'));

        if (priv.offline) {
            $div.append(h('p', Messages.access_offline));
            return void cb(void 0, $div);
        }
        if (!data) { return void cb(void 0, $div); }

        var div1 = h('div.cp-usergrid-user.cp-share-column.cp-access');
        var div2 = h('div.cp-usergrid-user.cp-share-column.cp-access');
        var $div1 = $(div1).appendTo($div);
        var $div2 = $(div2).appendTo($div);

        var parsed = Hash.parsePadUrl(data.href || data.roHref);
        if (!parsed || !parsed.hashData) { return void console.error("Invalid href"); }

        var drawLeft = function () {
            var priv = metadataMgr.getPrivateData();

            var $d = $('<div>');

            var owned = Modal.isOwned(Env, data);

            if (!opts.noExpiration) {
                var expire = Messages.creation_expireFalse;
                if (data.expire && typeof (data.expire) === "number") {
                    expire = new Date(data.expire).toLocaleString();
                }
                $d.append(h('div.cp-app-prop', [
                    Messages.creation_expiration,
                    h('br'),
                    h('span.cp-app-prop-content', expire)
                ]));
            }

            var $pwLabel = $('<label>', {'for': 'cp-app-prop-password'})
                            .text(Messages.creation_passwordValue).appendTo($d);
            var hasPassword = data.password;
            var password = UI.passwordInput({
                id: 'cp-app-prop-password',
                readonly: 'readonly'
            });
            var $password = $(password).appendTo($d);
            var $pwInput = $password.find('.cp-password-input');
            $pwInput.val(data.password || '').click(function () {
                $pwInput[0].select();
            });
            if (!hasPassword) {
                $password.hide();
                $pwLabel.hide();
            }

            // In the properties, we should have the edit href if we know it.
            // We should know it because the pad is stored, but it's better to check...
            //if (!data.noEditPassword && !opts.noEditPassword && owned && data.href) {
            if (!data.noEditPassword && !opts.noEditPassword && owned && data.href && parsed.type !== "form") { // TODO password change in forms block responses (validation & decryption)
                var isOO = Types?.OO_APPS?.includes(parsed.type);
                var isFile = parsed.hashData.type === 'file';
                var isSharedFolder = parsed.type === 'drive';

                var changePwTitle = Messages.properties_changePassword;
                var changePwConfirm = isFile ? Messages.properties_confirmChangeFile : Messages.properties_confirmChange;
                if (!hasPassword) {
                    changePwTitle = Messages.properties_addPassword;
                    changePwConfirm = isFile ? Messages.properties_confirmNewFile : Messages.properties_confirmNew;
                }
                $('<label>', {'for': 'cp-app-prop-change-password'})
                    .text(changePwTitle).appendTo($d);
                var newPassword = UI.passwordInput({
                    id: 'cp-app-prop-change-password',
                    style: 'flex: 1;'
                });
                var passwordOk = h('button.btn', Messages.properties_changePasswordButton);
                var changePass = h('span.cp-password-change-container', [
                    newPassword,
                    passwordOk
                ]);
                var pLocked = false;
                $(passwordOk).click(function () {
                    var newPass = $(newPassword).find('input').val();
                    if (data.password === newPass ||
                        (!data.password && !newPass)) {
                        return void UI.alert(Messages.properties_passwordSame);
                    }
                    if (pLocked) { return; }
                    pLocked = true;
                    UI.confirm(changePwConfirm, function (yes) {
                        if (!yes) { pLocked = false; return; }
                        $(passwordOk).html('').append(h('span.fa.fa-spinner.fa-spin', {style: 'margin-left: 0'}));
                        var q = isFile ? 'Q_BLOB_PASSWORD_CHANGE' :
                                    (isOO ? 'Q_OO_PASSWORD_CHANGE' : 'Q_PAD_PASSWORD_CHANGE');

                        // If this is a file password change, register to the upload events:
                        // * if there is a pending upload, ask if we shoudl interrupt
                        // * display upload progress
                        var onPending;
                        var onProgress;
                        if (isFile) {
                            onPending = sframeChan.on('Q_BLOB_PASSWORD_CHANGE_PENDING', function (data, cb) {
                                onPending.stop();
                                UI.confirm(Messages.upload_uploadPending, function (yes) {
                                    cb({cancel: yes});
                                });
                            });
                            onProgress = sframeChan.on('EV_BLOB_PASSWORD_CHANGE_PROGRESS', function (data) {
                                if (typeof (data) !== "number") { return; }
                                var p = Math.round(data);
                                $(passwordOk).text(p + '%');
                            });
                        }

                        var href = data.href;
                        var isNotStored = Boolean(data.isNotStored);
                        sframeChan.query(q, {
                            teamId: typeof(owned) !== "boolean" ? owned : undefined,
                            href: href,
                            oldPassword: data.password || priv.password,
                            password: newPass
                        }, function (err, res) {
                            $(passwordOk).text(Messages.properties_changePasswordButton);
                            pLocked = false;
                            err = err || res.error;
                            if (err) {
                                if (err === "PASSWORD_ALREADY_USED") {
                                    return void UI.alert(Messages.access_passwordUsed);
                                }
                                console.error(err);
                                return void UI.alert(Messages.properties_passwordError);
                            }
                            UI.findOKButton().click();

                            data.password = newPass;
                            data.href = res.href;
                            data.roHref = res.roHref;
                            data.channel = res.channel;

                            $pwInput.val(newPass);
                            if (newPass) {
                                $password.show();
                                $pwLabel.show();
                            } else {
                                $password.hide();
                                $pwLabel.hide();
                            }

                            // If the current document is a file or if we're changing the password from a drive,
                            // we don't have to reload the page at the end.
                            // Tell the user the password change was successful and abort
                            if (isFile || priv.app !== parsed.type) {
                                if (onProgress && onProgress.stop) { onProgress.stop(); }
                                $(passwordOk).text(Messages.properties_changePasswordButton);
                                var alertMsg = res.warning ? Messages.properties_passwordWarningFile
                                                            : Messages.properties_passwordSuccessFile;
                                return void UI.alert(alertMsg, undefined, {force: true});
                            }

                            // Pad password changed: update the href
                            // Use hidden hash if needed (we're an owner of this pad so we know it is stored)
                            var useUnsafe = Util.find(priv, ['settings', 'security', 'unsafeLinks']);
                            if (isNotStored) { useUnsafe = true; }
                            var _href = (priv.readOnly && res.roHref) ? res.roHref : res.href;
                            if (useUnsafe !== true) {
                                var newParsed = Hash.parsePadUrl(_href);
                                var newSecret = Hash.getSecrets(newParsed.type, newParsed.hash, newPass);
                                var newHash = Hash.getHiddenHashFromKeys(parsed.type, newSecret, {});
                                _href = Hash.hashToHref(newHash, parsed.type);
                            }

                            // Trigger a page reload if the href didn't change
                            if (_href === href) { _href = undefined; }

                            if (res.warning) {
                                return void UI.alert(Messages.properties_passwordWarning, function () {
                                    if (isNotStored) {
                                        return sframeChan.query('Q_PASSWORD_CHECK', newPass, () => { common.gotoURL(_href);  });
                                    }
                                    common.gotoURL(_href);
                                }, {force: true});
                            }
                            return void UI.alert(UIElements.fixInlineBRs(Messages.properties_passwordSuccess), function () {
                                if (!isSharedFolder) {
                                    if (isNotStored) {
                                        return sframeChan.query('Q_PASSWORD_CHECK', newPass, () => { common.gotoURL(_href);  });
                                    }
                                    common.gotoURL(_href);
                                }
                            });
                        });
                    });
                });
                $(passwordOk).on('keydown', function (e) {
                    if (e.keyCode === 13) {
                        e.preventDefault();
                        e.stopPropagation();
                        $(passwordOk).click();
                    }
                });
                $d.append(changePass);
            }
            if (owned) {
                var deleteOwned = h('button.btn.btn-danger', [h('i.cptools.cptools-destroy'), Messages.fc_delete_owned]);
                var spinner = UI.makeSpinner();
                UI.confirmButton(deleteOwned, {
                    classes: 'btn-danger'
                }, function () {
                    spinner.spin();
                    sframeChan.query('Q_DELETE_OWNED', {
                        teamId: typeof(owned) !== "boolean" ? owned : undefined,
                        channel: data.channel || priv.channel
                    }, function (err, obj) {
                        spinner.done();
                        UI.findCancelButton().click();
                        if (err || (obj && obj.error)) { UI.warn(Messages.error); }
                    });

                    // If this is a form wiht a answer channel, delete it too
                    var p = priv.propChannels;
                    if (p && p.answersChannel) {
                        sframeChan.query('Q_DELETE_OWNED', {
                            teamId: typeof(owned) !== "boolean" ? owned : undefined,
                            channel: p.answersChannel
                        }, function () {});
                    }
                });
                if (!opts.noEditPassword) { $d.append(h('br')); }
                $d.append(h('div', [
                    h('label', Messages.access_destroyPad),
                    h('br'),
                    deleteOwned,
                    spinner.spinner
                ]));
            }
            return $d;
        };
        var drawRight = function () {
            var priv = metadataMgr.getPrivateData();

            // Owners
            var content = [];
            var _ownersGrid = getUserList(common, data.owners);
            if (_ownersGrid && _ownersGrid.div) {
                content.push(h('label', Messages.creation_owners));
                content.push(_ownersGrid.div);
            } else if (!data.rejected) {
                content.push(UI.dialog.selectable(Messages.creation_noOwner, {
                    id: 'cp-app-prop-owners',
                }));
            }

            // Stop here for files: no allow list, no access request
            // Also stop for shared folders
            if (parsed.hashData.type !== 'pad' || parsed.type === 'drive') { return h('div', content); }

            var owned = Modal.isOwned(Env, data);

            // Request edit access
            if (common.isLoggedIn() && data.roHref && !owned && !opts.calendar && priv.app !== 'form' && !data.href) {
                var requestButton = h('button.btn.btn-secondary.no-margin.cp-access-margin-right',
                                        Messages.requestEdit_button);
                var requestBlock = h('p', requestButton);
                var $requestBlock = $(requestBlock).hide();
                content.push(requestBlock);
                sframeChan.query('Q_CONTACT_OWNER', {
                    send: false,
                    metadata: data
                }, function (err, obj) {
                    // Abort if no mailbox available
                    if (!(obj && obj.state)) { return; }

                    var spinner = UI.makeSpinner($requestBlock);
                    $requestBlock.show().find('button').click(function () {
                        if (spinner.getState()) { return; }
                        spinner.spin();
                        sframeChan.query('Q_CONTACT_OWNER', {
                            send: true,
                            metadata: data,
                            query: "REQUEST_PAD_ACCESS"
                        }, function (err, obj) {
                            if (obj && obj.state) {
                                UI.log(Messages.requestEdit_sent);
                                $requestBlock.find('button').prop('disabled', true);
                                spinner.done();
                            } else {
                                spinner.hide();
                            }
                        });
                    });
                });
            }

            // Mute access requests
            var edPublic = priv.edPublic;
            var canMute = data.mailbox && owned === true && (
                    (typeof (data.mailbox) === "string" && data.owners[0] === edPublic) ||
                    data.mailbox[edPublic]);
            if (owned === true && !opts.calendar && priv.app !== 'form') {
                var cbox = UI.createCheckbox('cp-access-mute', Messages.access_muteRequests, !canMute);
                var $cbox = $(cbox);
                var spinner = UI.makeSpinner($cbox);
                var $checkbox = $cbox.find('input').on('change', function () {
                    if (spinner.getState()) {
                        $checkbox.prop('checked', !$checkbox.prop('checked'));
                        return;
                    }
                    spinner.spin();
                    var val = $checkbox.is(':checked');
                    sframeChan.query('Q_UPDATE_MAILBOX', {
                        metadata: data,
                        add: !val
                    }, function (err, res) {
                        err = err || (res && res.error);
                        if (err) {
                            spinner.hide();
                            var text = err === "INSUFFICIENT_PERMISSIONS" ? Messages.fm_forbidden
                                                                          : Messages.error;
                            console.error(err);
                            return void UI.warn(text);
                        }
                        spinner.done();
                        UI.log(Messages.saved);
                    });
                });
                $cbox.find('.cp-checkmark-label').addClass('cp-access-margin-right');
                $cbox.find('.cp-checkmark-mark')
                    .after(h('span.fa.fa-bell-slash.cp-access-margin-right'));
                content.push(h('p', cbox));
            }


            // Allow list
            var state = data.restricted ? Messages.allow_enabled : Messages.allow_disabled;
            content.push(h('label', Messages._getKey('allow_label', [state])));
            if (data.restricted) {
                var _allowed = Util.deduplicateString((data.owners || []).concat(data.allowed));
                var _allowedGrid = getUserList(common, _allowed);
                content.push(_allowedGrid.div);
            }
            return h('div', content);
        };

        var redraw = function (right) {
            if (!right) {
                $div1.empty();
                $div1.append(drawLeft());
            }
            $div2.empty();
            $div2.append(drawRight());
        };
        redraw();

        Env.evRedrawAll.reg(function (ownersOrAllow) {
            setTimeout(function () {
                redraw(ownersOrAllow);
            });
        });

        cb(void 0, $div);
    };

    Access.getAccessModal = function (common, opts, cb) {
        cb = cb || function () {};
        opts = opts || {};
        opts.wide = true;
        opts.access = true;

        var hasFriends = Object.keys(common.getFriends()).length;
        var buttons = hasFriends? []: UIElements.noContactsMessage(common).buttons;
        buttons.unshift({
            className: 'cancel',
            name: Messages.filePicker_close,
            onClick: function () {},
            keys: [27],
        });

        var tabs = [{
            getTab: getAccessTab,
            title: Messages.access_main,
            icon: "fa fa-unlock-alt",
        }, {
            getTab: getAllowTab,
            title: Messages.access_allow,
            icon: "fa fa-list",
            buttons: buttons,
        }, {
            getTab: getOwnersTab,
            title: Messages.creation_owners,
            icon: "fa fa-id-badge",
            buttons: buttons,
        }];
        Modal.getModal(common, opts, tabs, cb);
    };

    return Access;
});
