// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only

import type { ChangeEvent } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import lodash from 'lodash';
import type { ListRowProps } from 'react-virtualized';
import { List } from 'react-virtualized';
import classNames from 'classnames';
import type { ConversationType } from '../state/ducks/conversations.preload.ts';
import type { LocalizerType } from '../types/I18N.std.ts';
import { SearchInput } from './SearchInput.dom.tsx';
import { filterAndSortConversations } from '../util/filterAndSortConversations.std.ts';
import { NavSidebarSearchHeader } from './NavSidebar.dom.tsx';
import { ListTile } from './ListTile.dom.tsx';
import { strictAssert } from '../util/assert.std.ts';
import { UserText } from './UserText.dom.tsx';
import { Avatar, AvatarSize } from './Avatar.dom.tsx';
import { I18n } from './I18n.dom.tsx';
import { SizeObserver } from '../hooks/useSizeObserver.dom.tsx';
import { CallType } from '../types/CallDisposition.std.ts';
import type { CallsTabSelectedView } from './CallsTab.preload.tsx';
import {
  InAnotherCallTooltip,
  getTooltipContent,
} from './conversation/InAnotherCallTooltip.dom.tsx';

const { partition } = lodash;

type CallsNewCallProps = Readonly<{
  hasActiveCall: boolean;
  allConversations: ReadonlyArray<ConversationType>;
  i18n: LocalizerType;
  onChangeCallsTabSelectedView: (selectedView: CallsTabSelectedView) => void;
  onOutgoingAudioCallInConversation: (conversationId: string) => void;
  onOutgoingVideoCallInConversation: (conversationId: string) => void;
  regionCode: string | undefined;
}>;

type Row =
  | { kind: 'header'; title: string }
  | { kind: 'conversation'; conversation: ConversationType };

export function CallsNewCallButton({
  callType,
  isEnabled,
  isActive,
  isInCall,
  i18n,
  onClick,
}: {
  callType: CallType;
  isActive: boolean;
  isEnabled: boolean;
  isInCall: boolean;
  i18n: LocalizerType;
  onClick: () => void;
}): React.JSX.Element {
  let innerContent: React.ReactNode | string;
  let inAnotherCallTooltipContent = '';
  if (!isEnabled) {
    inAnotherCallTooltipContent = getTooltipContent(i18n);
  }
  // Note: isActive is only set for groups and adhoc calls
  if (isActive) {
    innerContent = isInCall
      ? i18n('icu:CallsNewCallButton--return')
      : i18n('icu:joinOngoingCall');
  } else if (callType === CallType.Audio) {
    innerContent = (
      <span className="CallsNewCall__ItemIcon CallsNewCall__ItemIcon--Phone" />
    );
  } else {
    innerContent = (
      <span className="CallsNewCall__ItemIcon CallsNewCall__ItemIcon--Video" />
    );
  }

  const buttonContent = (
    <button
      type="button"
      className={classNames(
        'CallsNewCall__ItemActionButton',
        isActive ? 'CallsNewCall__ItemActionButton--join-call' : undefined,
        isEnabled
          ? undefined
          : 'CallsNewCall__ItemActionButton--join-call-disabled'
      )}
      aria-label={inAnotherCallTooltipContent}
      onClick={event => {
        event.stopPropagation();
        onClick();
      }}
    >
      {innerContent}
    </button>
  );

  return inAnotherCallTooltipContent === '' ? (
    buttonContent
  ) : (
    <InAnotherCallTooltip i18n={i18n}>{buttonContent}</InAnotherCallTooltip>
  );
}

export function CallsNewCall({
  hasActiveCall,
  allConversations,
  i18n,
  onChangeCallsTabSelectedView,
  onOutgoingAudioCallInConversation,
  onOutgoingVideoCallInConversation,
  regionCode,
}: CallsNewCallProps): React.JSX.Element {
  const [queryInput, setQueryInput] = useState('');

  const query = useMemo(() => {
    return queryInput.toLowerCase().normalize().trim();
  }, [queryInput]);

  const activeConversations = useMemo(() => {
    return allConversations.filter(conversation => {
      return conversation.activeAt != null && conversation.isArchived !== true;
    });
  }, [allConversations]);

  const filteredConversations = useMemo(() => {
    if (query === '') {
      return activeConversations;
    }
    return filterAndSortConversations(activeConversations, query, regionCode);
  }, [activeConversations, query, regionCode]);

  const [groupConversations, directConversations] = useMemo(() => {
    return partition(filteredConversations, conversation => {
      return conversation.type === 'group';
    });
  }, [filteredConversations]);

  const handleSearchInputChange = useCallback(
    (event: ChangeEvent<HTMLInputElement>) => {
      setQueryInput(event.currentTarget.value);
    },
    []
  );

  const handleSearchInputClear = useCallback(() => {
    setQueryInput('');
  }, []);

  const rows = useMemo((): ReadonlyArray<Row> => {
    let result: Array<Row> = [];
    if (directConversations.length > 0) {
      result.push({
        kind: 'header',
        title: i18n('icu:contactsHeader'),
      });
      result = result.concat(
        directConversations.map(conversation => {
          return {
            kind: 'conversation',
            conversation,
          };
        })
      );
    }
    if (groupConversations.length > 0) {
      result.push({
        kind: 'header',
        title: i18n('icu:groupsHeader'),
      });
      result = result.concat(
        groupConversations.map((conversation): Row => {
          return {
            kind: 'conversation',
            conversation,
          };
        })
      );
    }
    return result;
  }, [directConversations, groupConversations, i18n]);

  const isRowLoaded = useCallback(
    ({ index }: { index: number }) => {
      return rows.at(index) != null;
    },
    [rows]
  );

  const rowHeight = useCallback(
    ({ index }: { index: number }) => {
      if (rows.at(index)?.kind === 'conversation') {
        return ListTile.heightCompact;
      }
      // Height of .CallsNewCall__ListHeaderItem
      return 40;
    },
    [rows]
  );

  const rowRenderer = useCallback(
    ({ key, index, style }: ListRowProps) => {
      const item = rows.at(index);
      strictAssert(item != null, 'Rendered non-existent row');

      if (item.kind === 'header') {
        return (
          <div key={key} style={style} className="CallsNewCall__ListHeaderItem">
            {item.title}
          </div>
        );
      }

      const isNewCallEnabled = !hasActiveCall;

      return (
        <div key={key} style={style}>
          <ListTile
            leading={
              <Avatar
                avatarPlaceholderGradient={
                  item.conversation.avatarPlaceholderGradient
                }
                avatarUrl={item.conversation.avatarUrl}
                conversationType="group"
                i18n={i18n}
                title={item.conversation.title}
                size={AvatarSize.THIRTY_TWO}
                badge={undefined}
              />
            }
            title={<UserText text={item.conversation.title} />}
            trailing={
              <div className="CallsNewCall__ItemActions">
                {item.conversation.type === 'direct' && (
                  <CallsNewCallButton
                    callType={CallType.Audio}
                    isActive={false}
                    isEnabled={isNewCallEnabled}
                    isInCall={false}
                    onClick={() => {
                      if (isNewCallEnabled) {
                        onOutgoingAudioCallInConversation(item.conversation.id);
                      }
                    }}
                    i18n={i18n}
                  />
                )}
                <CallsNewCallButton
                  // It's okay if this is a group
                  callType={CallType.Video}
                  isActive={false}
                  isEnabled={isNewCallEnabled}
                  isInCall={false}
                  onClick={() => {
                    if (isNewCallEnabled) {
                      onOutgoingVideoCallInConversation(item.conversation.id);
                    }
                  }}
                  i18n={i18n}
                />
              </div>
            }
            onClick={() => {
              onChangeCallsTabSelectedView({
                type: 'conversation',
                conversationId: item.conversation.id,
                callHistoryGroup: null,
              });
            }}
          />
        </div>
      );
    },
    [
      rows,
      i18n,
      hasActiveCall,
      onChangeCallsTabSelectedView,
      onOutgoingAudioCallInConversation,
      onOutgoingVideoCallInConversation,
    ]
  );

  return (
    <>
      <NavSidebarSearchHeader>
        <SearchInput
          i18n={i18n}
          placeholder="Search"
          onChange={handleSearchInputChange}
          onClear={handleSearchInputClear}
          value={queryInput}
        />
      </NavSidebarSearchHeader>
      {rows.length === 0 && (
        <div className="CallsNewCall__EmptyState">
          {query === '' ? (
            i18n('icu:CallsNewCall__EmptyState--noQuery')
          ) : (
            <I18n
              i18n={i18n}
              id="icu:CallsNewCall__EmptyState--hasQuery"
              components={{
                query: <UserText text={query} />,
              }}
            />
          )}
        </div>
      )}
      {rows.length > 0 && (
        <SizeObserver>
          {(ref, size) => {
            return (
              <div ref={ref} className="CallsNewCall__ListContainer">
                {size != null && !size.hidden && (
                  <List
                    className="CallsNewCall__List"
                    width={size.width}
                    height={size.height}
                    isRowLoaded={isRowLoaded}
                    rowCount={rows.length}
                    rowHeight={rowHeight}
                    rowRenderer={rowRenderer}
                  />
                )}
              </div>
            );
          }}
        </SizeObserver>
      )}
    </>
  );
}
