import { encode, decode, authenticate, ClubsPluginCategory, fetchProfile } from '@devprotocol/clubs-core';
import Admin from '../src/pages/Admin.astro';
import Posts from '../src/pages/Posts.astro';
import { v5 } from 'uuid';
import { randomBytes, verifyMessage, JsonRpcProvider, keccak256, ZeroAddress, getDefaultProvider } from 'ethers';
import { whenDefined, whenDefinedAll, whenNotErrorAll, isNotError } from '@devprotocol/util-ts';
import { SchemaFieldTypes, createClient } from 'redis';
import { reduceBy, always, xprod } from 'ramda';
import { clientsSTokens, client } from '@devprotocol/dev-kit';
import Screenshot1 from '../src/assets/images/posts-1.jpg';
import Screenshot2 from '../src/assets/images/posts-2.jpg';
import Screenshot3 from '../src/assets/images/posts-3.jpg';
import Icon from '../src/assets/images/plugin-icon.svg';
import Readme from '../src/readme.astro';
import { CreateNavigationLink } from '@devprotocol/clubs-core/layouts';

var Index;
(function (Index) {
    Index["Post"] = "idx::devprotocol:clubs:plugin:posts::post";
    Index["Comment"] = "idx::devprotocol:clubs:plugin:posts::comment";
    Index["Reaction"] = "idx::devprotocol:clubs:plugin:posts::reaction";
    Index["Option"] = "idx::devprotocol:clubs:plugin:posts::option";
})(Index || (Index = {}));
var Prefix;
(function (Prefix) {
    Prefix["Post"] = "doc::devprotocol:clubs:plugin:posts::post:";
    Prefix["Comment"] = "doc::devprotocol:clubs:plugin:posts::comment:";
    Prefix["Reaction"] = "doc::devprotocol:clubs:plugin:posts::reaction:";
    Prefix["Option"] = "doc::devprotocol:clubs:plugin:posts::option:";
})(Prefix || (Prefix = {}));
const _raw = {
    '$._raw': {
        type: SchemaFieldTypes.TEXT,
        AS: '_raw',
    },
};
const _scope = {
    '$._scope': {
        type: SchemaFieldTypes.TEXT,
        AS: '_scope',
    },
};
const _post_id = {
    '$._post_id': {
        type: SchemaFieldTypes.TEXT,
        AS: '_post_id',
    },
};
const _parent_type = {
    '$._parent_type': {
        type: SchemaFieldTypes.TEXT,
        AS: '_parent_type',
    },
};
const _parent_id = {
    '$._parent_id': {
        type: SchemaFieldTypes.TEXT,
        AS: '_parent_id',
    },
};
const id = {
    '$.id': {
        type: SchemaFieldTypes.TEXT,
        AS: 'id',
    },
};
const title = {
    '$.title': {
        type: SchemaFieldTypes.TEXT,
        AS: 'title',
    },
};
const content = {
    '$.content': {
        type: SchemaFieldTypes.TEXT,
        AS: 'content',
    },
};
const created_by = {
    '$.created_by': {
        type: SchemaFieldTypes.TEXT,
        AS: 'created_by',
    },
};
const created_at = {
    '$.created_at': {
        type: SchemaFieldTypes.NUMERIC,
        SORTABLE: true,
        AS: 'created_at',
    },
};
const updated_at = {
    '$.updated_at': {
        type: SchemaFieldTypes.NUMERIC,
        SORTABLE: true,
        AS: 'updated_at',
    },
};
const key = {
    '$.key': {
        type: SchemaFieldTypes.TEXT,
        AS: 'key',
    },
};
const value = {
    '$.value': {
        type: SchemaFieldTypes.TEXT,
        AS: 'value',
    },
};

const uuidFactory = (url) => {
    const namespase = v5(url, v5.URL);
    return () => v5(randomBytes(32), namespase);
};

/* eslint-disable functional/no-expression-statement */
const postDocument = ({ data, scope, }) => ({
    id: data.id,
    title: data.title,
    content: data.content,
    created_by: data.created_by,
    created_at: data.created_at.getTime(),
    updated_at: data.updated_at.getTime(),
    _raw: encode(data),
    _scope: scope,
});
const commentDocument = ({ data, scope, postId, }) => ({
    id: data.id,
    content: data.content,
    created_by: data.created_by,
    created_at: data.created_at.getTime(),
    updated_at: data.updated_at.getTime(),
    _raw: encode(data),
    _scope: scope,
    _post_id: postId,
});
const reactionDocument = ({ data, scope, postId, }) => ({
    content: data.content,
    created_by: data.created_by,
    _scope: scope,
    _post_id: postId,
});
const optionDocument = ({ data, scope, parentType, parentId, }) => ({
    key: data.key,
    value: JSON.stringify(data.value),
    _scope: scope,
    _raw: encode(data),
    _parent_type: parentType,
    _parent_id: parentId,
});
const generatePrefixOf = (prefix, scope) => `${prefix}:${scope}`;
const generateKeyOf = (prefix, scope, id) => `${generatePrefixOf(prefix, scope)}:${id}`;
const createOrSkip = async (name, prefix, scm) => {
    const client = await getDefaultClient();
    return client.ft
        .create(name, scm, {
        ON: 'JSON',
        PREFIX: prefix,
    })
        .then((result) => ({ result, name }))
        .catch((err) => ({ result: err.message, name }));
};
const createIndex = async (client) => {
    const schemaPost = {
        ...id,
        ...title,
        ...content,
        ...created_by,
        ...created_at,
        ...updated_at,
        ..._raw,
        ..._scope,
    };
    const schemaComment = {
        ...id,
        ...content,
        ...created_by,
        ...created_at,
        ...updated_at,
        ..._raw,
        ..._scope,
        ..._post_id,
    };
    const schemaReaction = {
        ...content,
        ...created_by,
        ..._scope,
        ..._post_id,
    };
    const schemaOption = {
        ...key,
        ...value,
        ..._raw,
        ..._parent_type,
        ..._parent_id,
        ..._scope,
    };
    const res = await Promise.all([
        createOrSkip(Index.Post, Prefix.Post, schemaPost),
        createOrSkip(Index.Comment, Prefix.Comment, schemaComment),
        createOrSkip(Index.Reaction, Prefix.Reaction, schemaReaction),
        createOrSkip(Index.Option, Prefix.Option, schemaOption),
    ]);
    return res;
};
const setPost = async ({ scope, post, url, client, }) => {
    await implSetPost({ scope, url, post, client });
    return true;
};
const implSetPost = async ({ scope, post, url, client, }) => {
    const uuid = uuidFactory(url);
    const _post = {
        id: post.id,
        title: post.title,
        content: post.content,
        created_by: post.created_by,
        created_at: post.created_at,
        updated_at: post.updated_at,
    };
    const newPostData = postDocument({ data: _post, scope });
    const newReactionsData = Object.keys(post.reactions)
        .map((key) => {
        const users = post.reactions[key];
        return users.map((created_by) => reactionDocument({
            data: {
                content: key,
                created_by,
            },
            scope: scope,
            postId: post.id,
        }));
    })
        .flat();
    const newPostOptionsData = post.options.map((data) => optionDocument({
        data: {
            key: data.key,
            value: data.value,
        },
        scope,
        parentType: 'post',
        parentId: post.id,
    }));
    await client.json.set(generateKeyOf(Prefix.Post, scope, _post.id), '$', newPostData);
    await Promise.all(post.comments.map((data) => implSetComment({
        scope,
        url,
        comment: data,
        postId: post.id,
        client,
    })));
    await Promise.all(newReactionsData.map((data) => client.json.set(generateKeyOf(Prefix.Reaction, scope, uuid()), '$', data)));
    await Promise.all(newPostOptionsData.map((data) => client.json.set(generateKeyOf(Prefix.Option, scope, uuid()), '$', data)));
    return true;
};
const setComment = async ({ scope, comment, postId, url, client, }) => {
    await implSetComment({ scope, url, postId, comment, client });
    return true;
};
const implSetComment = async ({ scope, comment, postId, url, client, }) => {
    const uuid = uuidFactory(url);
    const { options, ..._comment } = comment;
    const newCommentData = commentDocument({
        data: _comment,
        scope,
        postId,
    });
    const newCommentOptionsData = options.map((opt) => optionDocument({
        data: {
            key: opt.key,
            value: opt.value,
        },
        scope,
        parentType: 'comment',
        parentId: comment.id,
    }));
    await client.json.set(generateKeyOf(Prefix.Comment, scope, comment.id), '$', newCommentData);
    await Promise.all(newCommentOptionsData.map((data) => client.json.set(generateKeyOf(Prefix.Option, scope, uuid()), '$', data)));
    return true;
};
/**
 * Fetch paginated comments related to the parent post
 * @param scope - the associated db scope
 * @param postId - the parent post id
 * @param client - the redis client
 * @param page - the page number
 * @returns the comments
 */
const fetchComments = async ({ scope, postId, client, page, }) => {
    // eslint-disable-next-line no-param-reassign
    page = page || 1;
    const limit = 10;
    const start = (page - 1) * limit;
    /**
     * Search comments where parent id is postId
     */
    const result = await client.ft.search(Index.Comment, `@${_post_id['$._post_id'].AS}:${postId}`, {
        LIMIT: {
            from: start,
            size: limit,
        },
    });
    /**
     * Convert the result to CommentDocument[]
     */
    const comments = result.documents.map((doc) => doc.value);
    return comments;
};
/**
 * Fetch all options related to the parent post/comment
 * @param scope - the associated db scope
 * @param parentId - the parent post/comment id
 * @param parentType - post or comment
 * @param client - the redis client
 * @param page - the page number
 * @returns the comments
 */
const fetchAllOptions = async ({ scope, parentId, parentType, client, }) => {
    const limit = 10;
    /**
     * Search options where parent id is parentId
     */
    const query = `@${_parent_type['$._parent_type'].AS}:${parentType} @${_parent_id['$._parent_id'].AS}:${parentId}`;
    const loop = async (start, list) => {
        const result = await client.ft.search(Index.Option, query, {
            LIMIT: {
                from: start,
                size: limit,
            },
        });
        const docs = [
            ...list,
            ...result.documents.map(({ value }) => value),
        ];
        const isAll = result.total <= start + limit;
        return isAll ? docs : await loop(start + limit + 1, docs);
    };
    const result = await loop(1, []);
    return result;
};
/**
 * Fetch all reactions related to the parent post
 * @param scope - the associated db scope
 * @param postId - the parent post id
 * @param client - the redis client
 * @param page - the page number
 * @returns the comments
 */
const fetchAllReactions = async ({ scope, postId, client, }) => {
    const limit = 10;
    /**
     * Search options where parent id is parentId
     */
    const query = `@${_post_id['$._post_id'].AS}:${postId}`;
    const loop = async (start, list) => {
        const result = await client.ft.search(Index.Reaction, query, {
            LIMIT: {
                from: start,
                size: limit,
            },
        });
        const docs = [
            ...list,
            ...result.documents.map(({ value }) => value),
        ];
        const isAll = result.total <= start + limit;
        return isAll ? docs : await loop(start + limit + 1, docs);
    };
    const result = await loop(1, []);
    return result;
};

var redisDocuments = /*#__PURE__*/Object.freeze({
    __proto__: null,
    commentDocument: commentDocument,
    createIndex: createIndex,
    fetchAllOptions: fetchAllOptions,
    fetchAllReactions: fetchAllReactions,
    fetchComments: fetchComments,
    generateKeyOf: generateKeyOf,
    generatePrefixOf: generatePrefixOf,
    implSetComment: implSetComment,
    implSetPost: implSetPost,
    optionDocument: optionDocument,
    postDocument: postDocument,
    reactionDocument: reactionDocument,
    setComment: setComment,
    setPost: setPost
});

/* eslint-disable functional/no-expression-statement */
// import { examplePosts } from '../constants/example-posts'
const defaultClient = createClient({
    url: import.meta.env.REDIS_URL,
    username: import.meta.env.REDIS_USERNAME ?? '',
    password: import.meta.env.REDIS_PASSWORD ?? '',
    socket: {
        keepAlive: 1,
        reconnectStrategy: 1,
    },
});
const getDefaultClient = async () => {
    if (defaultClient.isOpen === false) {
        await defaultClient.connect();
    }
    return defaultClient;
};
const getAllPosts$1 = async ({ key, client, }) => {
    try {
        const encodedData = await client.get(key);
        const decodedData = whenDefined(encodedData, (d) => decode(d));
        return decodedData
            ? decodedData // examplePosts
            : [];
    }
    catch (e) {
        return e;
    }
};
/**
 * Fetches the paginated posts from Redis
 * @param scope - The scope of the posts
 * @param client - The Redis client
 * @param page - The page number
 * @returns The paginated posts
 */
const getPaginatedPosts = async ({ scope, client, page, }) => {
    const limit = 10;
    const fetchPostKeys = await client.keys(`${Prefix.Post}:${scope}:*`);
    const slicedPostKeys = fetchPostKeys.slice(page * limit, (page + 1) * limit);
    const fetchPosts = await client.mGet(slicedPostKeys);
    const posts = fetchPosts
        // JSON parse each post
        .map((post) => (post ? JSON.parse(post) : null))
        // filter out null values from the array
        .filter((post) => post !== null)
        // decode post data (get post data without document db related keys)
        .map(({ _raw }) => decode(_raw));
    /**
     * Create fetch promises for each post
     */
    const fetchCommentsPromises = posts.map(async (post) => {
        const comments = await fetchComments({ scope, postId: post.id, client });
        const options = await Promise.all(comments.map((comment) => fetchAllOptions({
            scope,
            parentType: 'comment',
            parentId: comment.id,
            client,
        })));
        const commentsWithOpts = comments.map((comment, i) => ({
            ...comment,
            options: options[i],
        }));
        // decode each comment data (get comment data without document db related keys)
        return commentsWithOpts.map(({ _raw, options }) => ({
            ...decode(_raw),
            options: options.map(({ _raw: _rawOpt }) => decode(_rawOpt)),
        }));
    });
    /**
     * Create fetch options promises for each post
     */
    const fetchOptionsPromises = posts.map(async (post) => {
        const options = await fetchAllOptions({
            scope,
            parentType: 'post',
            parentId: post.id,
            client,
        });
        // decode each option data
        return options.map(({ _raw }) => decode(_raw));
    });
    /**
     * Create fetch reactions promises for each post
     */
    const fetchReactionsPromises = posts.map(async (post) => {
        const reactions = await fetchAllReactions({
            scope,
            postId: post.id,
            client,
        });
        const groupCreatedBy = (acc, { created_by }) => acc.concat(created_by);
        const toEmoji = ({ content }) => content;
        return reduceBy(groupCreatedBy, [], toEmoji, reactions);
    });
    /**
     * Fetch all comments/options/reactoins for each post
     */
    const [postsComments, postsOptions, postsReactions] = await Promise.all([
        Promise.all(fetchCommentsPromises),
        Promise.all(fetchOptionsPromises),
        Promise.all(fetchReactionsPromises),
    ]);
    /**
     * Map the comments to the posts
     */
    const results = posts.map((post, index) => ({
        ...post,
        comments: postsComments[index],
        options: postsOptions[index],
        reactions: postsReactions[index],
    }));
    return results;
};
const setAllPosts$1 = async ({ key, posts, client, }) => {
    const encodedData = encode(posts);
    await client.set(key, encodedData);
    return true;
};

const getAllPosts = async (type, opts, page) => {
    // eslint-disable-next-line functional/no-conditional-statement
    switch (type) {
        case 'encoded:redis': {
            const client = await getDefaultClient();
            const result = await getAllPosts$1({ ...opts, client });
            // eslint-disable-next-line functional/no-expression-statement
            await client.quit();
            return result;
        }
        case 'documents:redis':
            // eslint-disable-next-line no-case-declarations
            const client = await getDefaultClient();
            // eslint-disable-next-line no-case-declarations
            const result = await getPaginatedPosts({
                scope: opts.key,
                client,
                page: page ?? 0,
            });
            // eslint-disable-next-line functional/no-expression-statement
            await client.quit();
            return result;
        default:
            return new Error('Illegal database type is passed');
    }
};
const setAllPosts = async (type, opts) => {
    // eslint-disable-next-line functional/no-conditional-statement
    switch (type) {
        case 'encoded:redis': {
            const client = await getDefaultClient();
            const result = await setAllPosts$1({ ...opts, client });
            // eslint-disable-next-line functional/no-expression-statement
            await client.quit();
            return result;
        }
        default:
            return new Error('Illegal database type is passed');
    }
};

const addCommentEncodedRedis = async ({ conf, data, postId, dbQueryKey, }) => {
    try {
        // === Fetch from DB and calculate ===
        const posts = await getAllPosts('encoded:redis', { key: dbQueryKey });
        if (!posts || posts instanceof Error) {
            return new Response(JSON.stringify({
                error: 'Error fetching post',
            }), {
                status: 401,
            });
        }
        const postIndex = posts.findIndex((post) => post.id === postId);
        if (postIndex === -1) {
            return new Response(JSON.stringify({
                error: 'Error fetching post',
            }), {
                status: 401,
            });
        }
        const newPosts = posts.map((post) => post.id === postId
            ? { ...post, comments: [...post.comments, data] }
            : post);
        // === Update DB ===
        const isCommentAdded = await setAllPosts('encoded:redis', {
            key: dbQueryKey,
            posts: newPosts,
        });
        return isCommentAdded instanceof Error
            ? new Response(JSON.stringify({
                error: 'Update failed',
            }), {
                status: 500,
            })
            : new Response(JSON.stringify({
                message: isCommentAdded,
                id: data.id,
            }), {
                status: 200,
            });
    }
    catch (err) {
        return new Response(JSON.stringify({
            error: err?.message || 'Error occured',
        }), {
            status: 500,
        });
    }
};

const addCommentDocumentsRedis = async ({ conf, data, dbQueryKey, postId, }) => {
    try {
        const client = await getDefaultClient();
        const saved = await setComment({
            scope: dbQueryKey,
            comment: data,
            postId,
            url: conf.url,
            client,
        });
        return saved
            ? new Response(JSON.stringify({
                message: saved,
                data: encode(data),
            }), {
                status: 200,
            })
            : new Response(JSON.stringify({
                error: 'Some data is missing',
                data: null,
            }), {
                status: 400,
            });
    }
    catch (e) {
        return new Response(JSON.stringify({
            error: e.message,
            data: null,
        }), {
            status: 500,
        });
    }
};

const addCommentHandler = (conf, dbQueryType, dbQueryKey) => async ({ request }) => {
    const { contents, hash, sig, postId } = (await request.json());
    if (!contents || !hash || !sig || !postId) {
        return new Response(JSON.stringify({
            error: 'Missing data',
        }), {
            status: 400,
        });
    }
    // === COMMENTS ===
    const date = new Date();
    const decodedContents = decode(contents);
    const newComment = {
        ...decodedContents,
        id: uuidFactory(conf.url)(),
        created_by: verifyMessage(hash, sig),
        created_at: date,
        updated_at: date,
    };
    switch (dbQueryType) {
        case 'encoded:redis':
            return addCommentEncodedRedis({
                conf,
                data: newComment,
                postId,
                dbQueryKey,
            });
        case 'documents:redis':
            return addCommentDocumentsRedis({
                conf,
                data: newComment,
                postId,
                dbQueryKey,
            });
        default:
            return new Response(JSON.stringify({ message: 'not implemented' }));
    }
};
/**
 * This can be used to fetch more paginated post comments by page id
 * @param conf
 * @param dbQueryKey
 * @returns paginated comments
 */
const fetchCommentsHandler = (conf) => async ({ request, url, }) => {
    const client = await getDefaultClient();
    /** get the parent post id */
    const splitUrl = url.pathname.split('/');
    const postId = splitUrl[splitUrl.length - 2];
    /** page page number from query params */
    const page = url.searchParams.get('page');
    try {
        /**
         * fetch the comments
         */
        const comments = await fetchComments({
            scope: conf.url,
            postId,
            client,
            page: page ? parseInt(page) : 0,
        });
        return new Response(JSON.stringify({
            comments,
        }), {
            status: 200,
        });
    }
    catch (e) {
        return new Response(JSON.stringify({
            error: e,
            data: null,
        }), {
            status: 500,
        });
    }
};

const maskOptions = (options) => {
    return options
        .map((opt) => (opt.key.startsWith('#') ? undefined : opt))
        .filter((x) => x !== undefined);
};
const mask = (post) => {
    return {
        ...post,
        content: '',
        options: maskOptions(post.options),
        comments: post.comments.map((comment) => ({
            ...comment,
            options: maskOptions(comment.options),
        })),
        masked: true,
    };
};
const maskFactory = async ({ user, propertyAddress, rpcUrl, }) => {
    const provider = new JsonRpcProvider(rpcUrl);
    const sTokens = await whenDefined(user, async () => (([a, b]) => a ?? b)(await clientsSTokens(provider)));
    const isValidProperty = propertyAddress !== ZeroAddress;
    const allSTokenIDsUserHave = isValidProperty
        ? (await whenDefinedAll([sTokens, user], ([contract, account]) => client.createDetectSTokens(contract)(propertyAddress, account))) ?? []
        : [];
    const allMembershipPayloadsUserHave = await Promise.all(allSTokenIDsUserHave.map(async (id) => {
        const payload = await sTokens?.payloadOf(id);
        return payload;
    }));
    return (post) => {
        // Do not Mask : post.created_by === user
        if (post.created_by === user) {
            return post;
        }
        const requireOneOf = post.options.find((opt) => opt.key === 'require-one-of')
            ?.value ?? [];
        const requiredPayloads = requireOneOf.map((v) => keccak256(new Uint8Array([...v])));
        const verified = requiredPayloads.length > 0
            ? requiredPayloads.some((payload) => allMembershipPayloadsUserHave.includes(payload))
            : true;
        return verified ? post : mask(post);
    };
};

const emojiAllowList = [
    '👍',
    '🎉',
    '❤️',
    '🚀',
    '🙏',
    '👏',
    '🔥',
    '🤔',
    '👀',
];

const addReactionEncodedRedis = async ({ conf, data, postId, userAddress, dbQueryKey, }) => {
    try {
        // === Fetch from DB and calculate ===
        const posts = await getAllPosts('encoded:redis', { key: dbQueryKey });
        if (!posts || posts instanceof Error) {
            return new Response(JSON.stringify({
                error: 'Error fetching post',
            }), {
                status: 401,
            });
        }
        const postIndex = posts.findIndex((post) => post.id === postId);
        if (postIndex === -1) {
            return new Response(JSON.stringify({
                error: 'Error fetching post',
            }), {
                status: 401,
            });
        }
        const removeUser = (reactions) => {
            return reactions[data].filter((i) => i !== userAddress);
        };
        /**
         * Resets posts to toggle user reaction
         */
        const newPosts = posts.map((post) => post.id === postId
            ? {
                ...post,
                reactions: {
                    ...post.reactions,
                    // check if emoji exists
                    [data]: !post.reactions[data]
                        ? // if emoji doesn't exist, add user address
                            [userAddress]
                        : // emoji exists, check if emoji includes user address
                            post.reactions[data].includes(userAddress)
                                ? // if user address already associated with this emoji, remove user address
                                    // post.reactions[emoji].filter((i) => i !== userAddress)
                                    removeUser(post.reactions)
                                : // if user address not associated with this emoji, add user address
                                    [...post.reactions[data], userAddress],
                },
            }
            : post);
        // === Update DB ===
        const isReactionUpdated = await setAllPosts('encoded:redis', {
            key: dbQueryKey,
            posts: newPosts,
        });
        return isReactionUpdated instanceof Error
            ? new Response(JSON.stringify({
                error: 'Update failed',
            }), {
                status: 500,
            })
            : new Response(JSON.stringify({
                message: isReactionUpdated,
            }), {
                status: 200,
            });
    }
    catch (err) {
        return new Response(JSON.stringify({
            error: err?.message || 'Error occured',
        }), {
            status: 500,
        });
    }
};

const addReactionHandler = (conf, dbQueryType, dbQueryKey) => async ({ request }) => {
    const { hash, sig, postId, emoji } = (await request.json());
    if (!emoji || !hash || !sig || !postId) {
        return new Response(JSON.stringify({
            error: 'Missing data',
        }), {
            status: 400,
        });
    }
    /**
     * Check if club has allowListEmojis option
     * If not, use default allowListEmojis
     */
    const clubAllowListEmojis = conf.options?.find((item) => item.key === 'allowListEmojis')
        ?.value ?? emojiAllowList;
    /**
     * Only allowlist emojis accepted
     */
    if (!clubAllowListEmojis.includes(emoji)) {
        return new Response(JSON.stringify({
            error: 'Emoji not allowed',
        }), {
            status: 400,
        });
    }
    // get user address
    const userAddress = verifyMessage(hash, sig);
    switch (dbQueryType) {
        case 'encoded:redis':
            return addReactionEncodedRedis({
                conf,
                data: emoji,
                userAddress,
                postId,
                dbQueryKey,
            });
        case 'documents:redis':
            return addReactionEncodedRedis({
                conf,
                data: emoji,
                userAddress,
                postId,
                dbQueryKey,
            });
        default:
            return new Response(JSON.stringify({ message: 'not implemented' }));
    }
};

const addPostEncodedRedis = async ({ conf, data, dbQueryKey, }) => {
    try {
        const allPosts = await getAllPosts('encoded:redis', { key: dbQueryKey });
        const merged = allPosts && !(allPosts instanceof Error) ? [data, ...allPosts] : [];
        const saved = await setAllPosts('encoded:redis', {
            key: dbQueryKey,
            posts: merged,
        });
        return saved instanceof Error
            ? new Response(JSON.stringify({
                error: saved,
                data: null,
            }), {
                status: 500,
            })
            : saved
                ? new Response(JSON.stringify({
                    message: saved,
                    data: encode(data),
                }), {
                    status: 200,
                })
                : new Response(JSON.stringify({
                    error: 'Some data is missing',
                    data: null,
                }), {
                    status: 400,
                });
    }
    catch (e) {
        return new Response(JSON.stringify({
            error: e.message,
            data: null,
        }), {
            status: 500,
        });
    }
};

const addPostDocumentsRedis = async ({ conf, data, dbQueryKey, }) => {
    try {
        const client = await getDefaultClient();
        const saved = await setPost({
            scope: dbQueryKey,
            post: data,
            url: conf.url,
            client,
        });
        return saved
            ? new Response(JSON.stringify({
                message: saved,
                data: encode(data),
            }), {
                status: 200,
            })
            : new Response(JSON.stringify({
                error: 'Some data is missing',
                data: null,
            }), {
                status: 400,
            });
    }
    catch (e) {
        return new Response(JSON.stringify({
            error: e.message,
            data: null,
        }), {
            status: 500,
        });
    }
};

const addPostHandler = (conf, dbQueryType, dbQueryKey) => async ({ request }) => {
    const { contents, hash, sig } = (await request.json());
    if (!hash || !sig) {
        return new Response(JSON.stringify({
            error: 'Hash or Signature are missing',
            data: null,
        }), {
            status: 400,
        });
    }
    const skipAuthentication = conf.propertyAddress === ZeroAddress;
    const authenticated = !skipAuthentication &&
        (await whenDefinedAll([hash, sig], ([h, s]) => authenticate({
            message: h,
            signature: s,
            previousConfiguration: encode(conf),
            provider: getDefaultProvider(conf.rpcUrl),
        })));
    // const { randomBytes, recoverAddress, hashMessage } = utils
    const id = uuidFactory(conf.url)();
    const created_by = verifyMessage(hash, sig);
    const now = new Date();
    const created_at = now;
    const updated_at = now;
    const decodedContents = decode(contents);
    const comments = [];
    const reactions = {};
    const composed = {
        ...decodedContents,
        id,
        created_by,
        created_at,
        updated_at,
        comments,
        reactions,
    };
    if (!authenticated) {
        return new Response(JSON.stringify({ message: 'not allowed' }), {
            status: 401,
        });
    }
    switch (dbQueryType) {
        case 'encoded:redis':
            return addPostEncodedRedis({
                conf,
                data: composed,
                dbQueryKey,
            });
        case 'documents:redis': {
            // return new Response(JSON.stringify({ message: 'not implemented' }))
            return addPostDocumentsRedis({
                conf,
                data: composed,
                dbQueryKey,
            });
        }
        default:
            return new Response(JSON.stringify({ message: 'not implemented' }));
    }
};
/**
 * This can be used to fetch a single Post by ID
 * @param dbQueryKey the scope of the query
 * @returns a single post
 */
const fetchPostHandler = (dbQueryKey) => async ({ request, url, }) => {
    const client = await getDefaultClient();
    /** get the parent post id */
    const splitUrl = url.pathname.split('/');
    const postId = splitUrl[splitUrl.length - 1];
    try {
        /**
         * fetch the comments
         */
        const post = await client.get(`${Prefix.Post}:${dbQueryKey}:${postId}`);
        if (!post) {
            return new Response(JSON.stringify({
                error: 'Post not found',
                data: null,
            }), {
                status: 404,
            });
        }
        return new Response(JSON.stringify({
            post: decode(post),
        }), {
            status: 200,
        });
    }
    catch (e) {
        return new Response(JSON.stringify({
            error: e.message,
            data: null,
        }), {
            status: 500,
        });
    }
};

const deleteExistingRecords = async (client, prefix, scope) => {
    // eslint-disable-next-line functional/no-loop-statement
    for await (const key of client.scanIterator({
        MATCH: `${generatePrefixOf(prefix, scope)}:*`,
        COUNT: 100,
    })) {
        // eslint-disable-next-line functional/no-expression-statement
        client.del(key).then(always(console.log('deleted', key)));
    }
    return { result: 'OK', prefix, scope };
};
const copyPostFromEncodedRedisToDocumentsRedisHandler = ({ config, srcDatabaseKey, distDatabaseKey: scope, }) => async ({ request }) => {
    const allPosts = await getAllPosts('encoded:redis', {
        key: srcDatabaseKey,
    }).catch((err) => new Error(err));
    const client = await getDefaultClient();
    const deleted = await Promise.all([
        deleteExistingRecords(client, Prefix.Comment, scope),
        deleteExistingRecords(client, Prefix.Option, scope),
        deleteExistingRecords(client, Prefix.Post, scope),
        deleteExistingRecords(client, Prefix.Reaction, scope),
    ]);
    const copied = await whenNotErrorAll([allPosts, deleted], ([posts]) => Promise.all(posts.map((post) => setPost({
        post,
        scope,
        url: config.url,
        client,
    })
        .then((result) => ({ result, post }))
        .catch((result) => ({ result, post })))));
    const finish = await client.quit().catch((err) => err);
    const result = whenNotErrorAll([copied, finish], ([res]) => res);
    return isNotError(result)
        ? new Response(JSON.stringify({
            data: result,
            message: 'Success',
        }), { status: 200 })
        : new Response(JSON.stringify({
            data: result,
            message: result.message,
        }), { status: 400 });
};

var SlotName;
(function (SlotName) {
    SlotName["PostsEditAfterContentForm"] = "posts:edit:after:content-form";
    SlotName["PostsFeedAfterPostContent"] = "posts:feed:after:post-content";
})(SlotName || (SlotName = {}));

const getPagePaths = (async (options, { propertyAddress, adminRolePoints }, { getPluginConfigById }) => {
    const [membershipsPlugin] = getPluginConfigById('devprotocol:clubs:simple-memberships');
    const memberships = membershipsPlugin?.options?.find(({ key }) => key === 'memberships')?.value;
    const dbs = options.find(({ key }) => key === 'feeds')?.value;
    return [
        ...(dbs?.map(({ id, slug }) => ({
            paths: [slug ?? 'posts'],
            component: Posts,
            props: {
                options,
                feedId: id,
                propertyAddress,
                memberships,
                adminRolePoints,
            },
            // `propertyAddress` is required for calling post API, so passed to the FE here.
        })) ?? []),
    ];
});
const getAdminPaths = (async (options) => {
    return [
        {
            paths: ['posts'],
            component: Admin,
            props: { options },
        },
    ];
});
const meta = {
    id: 'devprotocol:clubs:plugin:posts',
    displayName: 'Posts',
    description: 'Add an interactive posts feed to your Club',
    category: ClubsPluginCategory.Uncategorized,
    previewImages: [Screenshot1.src, Screenshot2.src, Screenshot3.src],
    readme: Readme,
    icon: Icon.src,
};
const getApiPaths = (async (options, config) => {
    v5(config.url, v5.URL);
    encode(config);
    const dbs = options.find(({ key }) => key === 'feeds')?.value;
    const { createIndex } = await Promise.resolve().then(function () { return redisDocuments; });
    if (!dbs) {
        return [];
    }
    return [
        {
            paths: ['indexing', 'documents:redis'],
            method: 'POST',
            handler: async () => {
                const client = await getDefaultClient();
                const res = await createIndex(client);
                // eslint-disable-next-line functional/no-expression-statement
                await client.quit();
                return new Response(JSON.stringify(res));
            },
        },
        ...dbs
            .map((db) => [
            {
                paths: [db.id, 'profile'],
                method: 'GET',
                handler: async ({ url }) => {
                    const address = url.searchParams.get('address');
                    if (!address) {
                        return new Response(JSON.stringify({
                            error: 'Address is missing',
                            data: null,
                        }), {
                            status: 400,
                        });
                    }
                    //
                    try {
                        const res = await fetchProfile(address);
                        if (res.error) {
                            return new Response(JSON.stringify({
                                error: res.error,
                                data: null,
                            }), {
                                status: 500,
                            });
                        }
                        return new Response(JSON.stringify({
                            profile: res.profile,
                        }), {
                            status: 200,
                        });
                    }
                    catch (e) {
                        return new Response(JSON.stringify({
                            error: e,
                            data: null,
                        }), {
                            status: 500,
                        });
                    }
                },
            },
            {
                paths: [db.id, 'message'],
                // This will be [POST] /api/devprotocol:clubs:plugin:posts/{FEED_ID}/message
                method: 'POST',
                handler: addPostHandler(config, db.database.type, db.database.key),
            },
            /**
             * For fetching paginated posts
             */
            {
                paths: [db.id, 'message'],
                method: 'GET',
                handler: async ({ url }) => {
                    const { hash, sig, page } = {
                        hash: url.searchParams.get('hash'),
                        sig: url.searchParams.get('sig'),
                        page: url.searchParams.get('page'),
                    };
                    const parsedPage = parseInt(page ?? '0');
                    // eslint-disable-next-line functional/no-let
                    let reader;
                    try {
                        // eslint-disable-next-line functional/no-expression-statement
                        reader = whenDefinedAll([hash, sig], ([h, s]) => verifyMessage(h, s));
                    }
                    catch (error) {
                        // eslint-disable-next-line functional/no-expression-statement
                        console.log(error);
                    }
                    // eslint-disable-next-line functional/no-let
                    let allPosts;
                    // eslint-disable-next-line
                    try {
                        const _allPosts = await whenDefinedAll([db.database.type, db.database.key], ([type, key]) => getAllPosts(type, { key }, parsedPage));
                        const mask = await maskFactory({
                            user: reader,
                            propertyAddress: config.propertyAddress,
                            rpcUrl: config.rpcUrl,
                        });
                        // eslint-disable-next-line
                        allPosts =
                            whenDefinedAll([mask, _allPosts], ([maskFn, posts]) => posts instanceof Error ? posts : posts.map(maskFn)) ?? _allPosts;
                    }
                    catch (error) {
                        return new Response(JSON.stringify({
                            error: error,
                        }), {
                            status: 500,
                        });
                    }
                    return allPosts instanceof Error
                        ? new Response(JSON.stringify({
                            error: allPosts,
                        }), {
                            status: 500,
                        })
                        : allPosts
                            ? new Response(JSON.stringify({
                                contents: encode(allPosts),
                            }), {
                                status: 200,
                            })
                            : new Response(JSON.stringify({
                                error: 'Some data is missing',
                            }), {
                                status: 400,
                            });
                },
            },
            /**
             * Fetch individual post
             */
            {
                paths: [db.id, 'message', /((?!\/).)+/],
                method: 'GET',
                handler: fetchPostHandler(db.database.key),
            },
            /**
             * Fetch paginated comments by post id
             */
            {
                paths: [db.id, 'message', /((?!\/).)+/, 'comments'],
                method: 'GET',
                handler: fetchCommentsHandler(config),
            },
            {
                paths: [db.id, 'comment'], // This will be [POST] /api/devprotocol:clubs:plugin:posts/{FEED_ID}/comment
                method: 'POST',
                handler: addCommentHandler(config, db.database.type, db.database.key),
            },
            {
                paths: [db.id, 'reactions'], // This will be [POST] /api/devprotocol:clubs:plugin:posts/{FEED_ID}/reactions
                method: 'POST',
                handler: addReactionHandler(config, db.database.type, db.database.key),
            },
        ])
            .flat(),
        ...xprod(dbs.filter(({ database: { type } }) => type === 'encoded:redis'), dbs.filter(({ database: { type } }) => type === 'documents:redis'))
            .map(([encodedRedis, documentsRedis]) => [
            {
                paths: [encodedRedis.id, 'copy', 'to', documentsRedis.id],
                method: 'POST',
                handler: copyPostFromEncodedRedisToDocumentsRedisHandler({
                    config,
                    srcDatabaseKey: encodedRedis.database.key,
                    distDatabaseKey: documentsRedis.database.key,
                }),
            },
        ])
            .flat(),
    ];
});
const getSlots = (async (_, __, { paths, factory }) => {
    const [path1] = paths;
    return factory === 'admin' && path1 === 'posts'
        ? [
            {
                slot: 'admin:aside:after-built-in-buttons',
                component: CreateNavigationLink,
                props: {
                    createNavigation: {
                        label: `Add 'Posts' to the menu`,
                        link: {
                            display: 'Posts',
                            path: '/posts',
                        },
                    },
                },
            },
        ]
        : [];
});
var index = {
    getPagePaths,
    getAdminPaths,
    getApiPaths,
    getSlots,
    meta,
};

export { SlotName, index as default, getAdminPaths, getApiPaths, getPagePaths, getSlots, meta };
