<!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
 distributed with this work for additional information
 regarding copyright ownership.  The ASF licenses this file
 to you under the Apache License, Version 2.0 (the
 "License"); you may not use this file except in compliance
 with the License.  You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing,
 software distributed under the License is distributed on an
 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 KIND, either express or implied.  See the License for the
 specific language governing permissions and limitations
 under the License.
-->

<script lang="ts" module>
  type GlobalPermissionsSnakeCase = keyof KeysToSnakeCase<GlobalPermissions>;
  type StreamPermissionsSnakeCase = Exclude<keyof KeysToSnakeCase<StreamPermissions>, 'topics'>;

  type TopicsPerms = Record<
    Topic['id'],
    Record<keyof TopicPermissions, { name: string; checked: false }>
  >;

  type StreamsPerms = Record<
    Stream['id'],
    Record<
      StreamPermissionsSnakeCase,
      {
        name: string;
        checked: boolean;
        disabled: boolean;
        globalPermsKey: GlobalPermissionsSnakeCase;
      }
    > & { topicPerms: TopicsPerms }
  >;

  type GlobalPerms = Record<
    GlobalPermissionsSnakeCase,
    { name: string; checked: boolean; relatesTo?: StreamPermissionsSnakeCase }
  >;
</script>

<script lang="ts">
  import { run } from 'svelte/legacy';

  import Icon from './Icon.svelte';
  import Combobox from './Combobox.svelte';
  import type { Stream } from '$lib/domain/Stream';
  import { topicMapper, type Topic } from '$lib/domain/Topic';
  import { fetchRouteApi } from '$lib/api/fetchRouteApi';
  import { showToast } from './AppToasts.svelte';
  import type { KeysToSnakeCase } from '$lib/utils/utilTypes';
  import type {
    GlobalPermissions,
    StreamPermissions,
    TopicPermissions
  } from '$lib/domain/Permissions';
  import Checkbox from './Checkbox.svelte';
  import { twMerge } from 'tailwind-merge';
  import { noTypeCheck } from '$lib/utils/noTypeCheck';
  import { fade } from 'svelte/transition';
  import { SvelteSet } from 'svelte/reactivity';

  interface Props {
    streams: Stream[];
    value?: any;
  }

  let { streams, value = $bindable() }: Props = $props();

  let topics: Topic[] = $state([]);
  let fetchingTopics = $state(false);
  let selectedStream: { id: number; name: string } | undefined = $state(undefined);
  let selectedTopic: { id: number; name: string } | undefined = $state(undefined);

  if (streams.length > 0) {
    selectedStream = { name: streams[0].name, id: streams[0].id };
  }

  const fetchTopics = async (id: number) => {
    fetchingTopics = true;
    const { data, ok } = await fetchRouteApi({
      method: 'GET',
      path: `/streams/${id}/topics`
    });

    if (!ok) {
      showToast({ type: 'error', description: 'Something went wrong' });
      return;
    }
    fetchingTopics = false;
    const newTopics = data.map(topicMapper) as Topic[];

    if (newTopics.length > 0) {
      selectedTopic = { name: newTopics[0].name, id: newTopics[0].id };
    } else {
      selectedTopic = undefined;
    }

    topics = newTopics;
  };

  const buildTopicsPerms = (newTopics: Topic[]) => {
    const tempTopicPerms: TopicsPerms = {};

    if (!selectedStream || Object.keys(streamsPerms[selectedStream.id].topicPerms).length > 0) {
      return;
    }

    newTopics.forEach((t) => {
      tempTopicPerms[t.id] = {
        manageTopic: {
          checked: false,
          name: 'Manage topic'
        },
        pollMessages: {
          checked: false,
          name: 'Poll messages'
        },
        readTopic: {
          checked: false,
          name: 'Read topic'
        },
        sendMessages: {
          checked: false,
          name: 'Send messages'
        }
      };
    });

    streamsPerms[selectedStream.id].topicPerms = tempTopicPerms;
    streamsPerms = streamsPerms;
  };

  const onGlobalPermChanged = (key: GlobalPermissionsSnakeCase, checked: boolean) => {
    const relatesTo = globalPerms[key].relatesTo;

    if (relatesTo) {
      Object.keys(streamsPerms).forEach((k) => {
        streamsPerms[k][relatesTo] = { ...streamsPerms[k][relatesTo], checked, disabled: checked };
        streamsPerms = streamsPerms;
      });
    }
  };

  const globalPerms: GlobalPerms = $state({
    manage_servers: {
      name: 'Manage servers',
      checked: false
    },
    read_servers: {
      name: 'Read servers',
      checked: false
    },
    manage_users: {
      name: 'Manage users',
      checked: false
    },
    read_users: {
      name: 'Read users',
      checked: false
    },
    manage_streams: {
      name: 'Manage streams',
      relatesTo: 'manage_stream',
      checked: false
    },
    read_streams: {
      name: 'Read streams',
      relatesTo: 'read_stream',
      checked: false
    },
    manage_topics: {
      name: 'Manage topics',
      relatesTo: 'manage_topics',
      checked: false
    },
    read_topics: {
      name: 'Read topics',
      relatesTo: 'read_topics',
      checked: false
    },
    poll_messages: {
      name: 'Pool messages',
      relatesTo: 'poll_messages',
      checked: false
    },
    send_messages: {
      name: 'Send messages',
      relatesTo: 'send_messages',
      checked: false
    }
  });

  let streamsPerms = $state(
    (() => {
      const tempPerms: StreamsPerms = {};

      streams.forEach((s) => {
        tempPerms[s.id] = {
          manage_stream: {
            name: 'Manage stream',
            globalPermsKey: 'manage_streams',
            checked: false,
            disabled: false
          },
          read_stream: {
            name: 'Read stream',
            globalPermsKey: 'read_streams',
            checked: false,
            disabled: false
          },
          read_topics: {
            name: 'Read topics',
            globalPermsKey: 'read_topics',
            checked: false,
            disabled: false
          },
          poll_messages: {
            name: 'Poll messages',
            globalPermsKey: 'poll_messages',
            checked: false,
            disabled: false
          },
          send_messages: {
            name: 'Send messages',
            globalPermsKey: 'send_messages',
            checked: false,
            disabled: false
          },
          manage_topics: {
            name: 'Manage topics',
            globalPermsKey: 'manage_topics',
            checked: false,
            disabled: false
          },
          topicPerms: {}
        };
      });

      return tempPerms;
    })() satisfies StreamsPerms
  );

  run(() => {
    if (selectedStream) fetchTopics(selectedStream.id);
  });
  run(() => {
    buildTopicsPerms(topics);
  });

  let taintedStreams = $derived(
    (() => {
      const tainted: Set<number> = new SvelteSet([]);

      Object.keys(streamsPerms).forEach((streamId) => {
        Object.keys(streamsPerms[streamId]).forEach((permissionKey) => {
          if (permissionKey === 'topicPerms') {
            const perm = streamsPerms[streamId][permissionKey];

            Object.keys(perm).forEach((topicId) => {
              const topicPerm = perm[topicId];
              const isTopicTainted = Object.keys(topicPerm)
                .map((k) => topicPerm[k])
                .some((p) => p.checked);

              if (isTopicTainted) tainted.add(streamId);
            });
          } else {
            const perm = streamsPerms[streamId][permissionKey];
            if (perm.checked && !perm.disabled) tainted.add(streamId);
          }
        });
      });

      return Array.from(tainted);
    })().map((taintedStreamId) => {
      const name = streams.find((stream) => stream.id === +taintedStreamId)!.name;
      return { name, id: +taintedStreamId };
    })
  );

  $effect(() => {
    function formatGlobalPermissions() {
      return Object.keys(globalPerms).reduce((result, key) => {
        result[key] = globalPerms[key].checked;
        return result;
      }, {});
    }

    function hasAnyPermissionChecked(
      permissionsObj: Record<string, any>,
      excludeKeys: string[] = []
    ) {
      return Object.keys(permissionsObj)
        .filter((key) => !excludeKeys.includes(key))
        .some((key) => permissionsObj[key].checked);
    }

    function formatTopicPermissions(topicPerms: Record<string, any>) {
      const result = {};

      Object.keys(topicPerms).forEach((topicId) => {
        const topicPerm = topicPerms[topicId];

        if (!hasAnyPermissionChecked(topicPerm)) {
          return;
        }

        result[topicId] = {
          manage_topic: topicPerm.manageTopic.checked,
          read_topic: topicPerm.readTopic.checked,
          poll_messages: topicPerm.pollMessages.checked,
          send_messages: topicPerm.sendMessages.checked
        };
      });

      return result;
    }

    function formatStreamPermissions() {
      const result = {};

      Object.keys(streamsPerms).forEach((streamId) => {
        const streamPerm = streamsPerms[streamId];
        const topicPerms = streamPerm.topicPerms;

        const hasStreamPermissions = hasAnyPermissionChecked(streamPerm, ['topicPerms']);

        const formattedTopics = formatTopicPermissions(topicPerms);
        const hasTopicPermissions = Object.keys(formattedTopics).length > 0;

        if (!hasStreamPermissions && !hasTopicPermissions) {
          return;
        }

        result[streamId] = {
          manage_stream: streamPerm.manage_stream.checked,
          read_stream: streamPerm.read_stream.checked,
          manage_topics: streamPerm.manage_topics.checked,
          read_topics: streamPerm.read_topics.checked,
          poll_messages: streamPerm.poll_messages.checked,
          send_messages: streamPerm.send_messages.checked
        };

        if (hasTopicPermissions) {
          result[streamId].topics = formattedTopics;
        }
      });

      return result;
    }

    value = {
      global: formatGlobalPermissions(),
      streams: formatStreamPermissions()
    };
  });
</script>

<h4 class="ml-1 text-lg text-color mt-7">Global permissions</h4>

<div class="grid grid-cols-4 mt-4">
  {#each Object.keys(globalPerms) as key (key)}
    <label
      class="flex gap-2 items-center text-color cursor-pointer"
      for={`global-permissions-${key}`}
    >
      <Checkbox
        bind:checked={globalPerms[key].checked}
        value={globalPerms[key].name}
        id={`global-permissions-${key}`}
        name={globalPerms[key].name}
        onclick={(e) => onGlobalPermChanged(key, noTypeCheck(e).target.checked)}
      />
      <span class="text-sm">{globalPerms[key].name}</span>
    </label>
  {/each}
</div>

{#if selectedStream}
  <div class="flex flex-wrap gap-3 mt-6 items-center">
    <h4 class="text-lg text-color mr-2">Granular permissions</h4>

    {#each taintedStreams as { id, name } (id)}
      <button
        type="button"
        onclick={() => (selectedStream = { name, id })}
        transition:fade={{ duration: 80 }}
        class={twMerge(
          'rounded-3xl px-3 py-1 whitespace-nowrap text-xs hover:shadow-lg  hover:ring-2 transition-all dark:text-white ring-1 ring-green-500 shadow-md hover:cursor-pointer',
          selectedStream?.id === id && 'bg-green-500 text-white'
        )}
        >id: {id}, {name}
      </button>
    {/each}
  </div>

  <div class="grid grid-cols-[1fr_auto_1fr] gap-5 mt-4">
    <div class="w-full flex flex-col">
      <Combobox
        items={streams}
        formatter={(item) => `id: ${item.id}, ${item.name}`}
        label="Stream"
        bind:selectedValue={selectedStream}
      />

      <div class="grid grid-cols-2 mt-4">
        {#each Object.keys(streamsPerms[selectedStream.id]) as key (key)}
          {#if key !== 'topicPerms'}
            <label
              class={twMerge(
                'flex gap-2 items-center text-color cursor-pointer',
                streamsPerms[selectedStream.id][key].disabled &&
                  'cursor-not-allowed text-shade-l800'
              )}
              for={`stream-${key}-permission`}
            >
              <Checkbox
                bind:checked={streamsPerms[selectedStream.id][key].checked}
                value={streamsPerms[selectedStream.id][key].name}
                disabled={streamsPerms[selectedStream.id][key].disabled}
                id={`stream-${key}-permission`}
              />
              <span class={twMerge('text-sm')}>{streamsPerms[selectedStream.id][key].name}</span>
            </label>
          {/if}
        {/each}
      </div>
    </div>

    <div class="h-[68px] w-[40px] flex flex-col justify-end">
      <div class="w-fit h-fit">
        <Icon name="chevronRight" class="h-[40px] dark:stroke-white mt-auto  w-auto" />
      </div>
    </div>

    <div class="w-full flex flex-col">
      {#if topics.length === 0 && !streamsPerms[selectedStream.id].manage_topics.checked}
        <em class="italic dark:text-white text-center block mt-[34px]">
          This stream has no topics.
        </em>
      {/if}
      {#if streamsPerms[selectedStream.id].manage_topics.checked}
        <div class="dark:text-white mt-9 text-center">
          <span> Every topic in stream </span>
          <em class="text-green-500">{selectedStream.name}</em>
          <span> {topics.length === 0 ? 'will have' : 'has'} full permissions </span>
        </div>
      {/if}

      {#if selectedTopic && Object.keys(streamsPerms[selectedStream.id].topicPerms).length > 0 && !streamsPerms[selectedStream.id].manage_topics.checked}
        <div>
          <Combobox
            isLoading={fetchingTopics}
            items={topics}
            formatter={(item) => `id: ${item.id}, ${item.name}`}
            label="Topic"
            bind:selectedValue={selectedTopic}
          />

          <div class="grid grid-cols-2 mt-4">
            {#each Object.keys(streamsPerms[selectedStream.id].topicPerms[selectedTopic.id]) as key (key)}
              <label class="flex gap-2 items-center text-color cursor-pointer">
                <Checkbox
                  bind:checked={
                    streamsPerms[selectedStream.id].topicPerms[selectedTopic.id][key].checked
                  }
                  value=""
                />
                <span class="text-sm"
                  >{streamsPerms[selectedStream.id].topicPerms[selectedTopic.id][key].name}</span
                >
              </label>
            {/each}
          </div>
        </div>
      {/if}
    </div>
  </div>
{/if}
