import {
  Avatar,
  Box,
  Button,
  Chip,
  color,
  Icon,
  IconButton,
  Icons,
  Input,
  PopOut,
  RectCords,
  Spinner,
  Text,
  TextArea,
} from 'folds';
import React, { FormEventHandler, useCallback, useMemo, useRef, useState } from 'react';
import { useAtomValue } from 'jotai';
import Linkify from 'linkify-react';
import parse from 'html-react-parser';
import classNames from 'classnames';
import { JoinRule, MatrixError } from 'matrix-js-sdk';
import { EmojiBoard } from '../../../components/emoji-board';
import { SequenceCard } from '../../../components/sequence-card';
import { SequenceCardStyle } from '../../room-settings/styles.css';
import { useRoom } from '../../../hooks/useRoom';
import {
  useRoomAvatar,
  useRoomJoinRule,
  useRoomName,
  useRoomTopic,
} from '../../../hooks/useRoomMeta';
import { mDirectAtom } from '../../../state/mDirectList';
import { BreakWord, LineClamp3 } from '../../../styles/Text.css';
import { LINKIFY_OPTS } from '../../../plugins/react-custom-html-parser';
import { sanitizeCustomHtml } from '../../../utils/sanitize';
import { RoomAvatar, RoomIcon } from '../../../components/room-avatar';
import { mxcUrlToHttp } from '../../../utils/matrix';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
import { StateEvent } from '../../../../types/matrix/room';
import { sendStateEvent } from '../../../utils/room';
import { CompactUploadCardRenderer } from '../../../components/upload-card';
import { useObjectURL } from '../../../hooks/useObjectURL';
import { createUploadAtom, UploadSuccess } from '../../../state/upload';
import { useFilePicker } from '../../../hooks/useFilePicker';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { useAlive } from '../../../hooks/useAlive';
import { RoomPermissionsAPI } from '../../../hooks/useRoomPermissions';

const MARKDOWN_PATTERN = /(\*\*|__|\*|_|~~|`|\[.+?\]\(.+?\))/;

function wrapSelection(textarea: HTMLTextAreaElement, syntax: string, placeholder: string) {
  const start = textarea.selectionStart;
  const end = textarea.selectionEnd;
  const selected = textarea.value.substring(start, end);
  const inner = selected || placeholder;
  const replacement = `${syntax}${inner}${syntax}`;
  const newValue = textarea.value.substring(0, start) + replacement + textarea.value.substring(end);
  const nativeSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set;
  nativeSetter?.call(textarea, newValue);
  textarea.dispatchEvent(new Event('input', { bubbles: true }));
  const cursorStart = start + syntax.length;
  const cursorEnd = cursorStart + inner.length;
  textarea.focus();
  textarea.setSelectionRange(cursorStart, cursorEnd);
}

function topicMarkdownToHtml(text: string): string {
  return text
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
    .replace(/__(.+?)__/g, '<strong>$1</strong>')
    .replace(/\*(.+?)\*/g, '<em>$1</em>')
    .replace(/_(.+?)_/g, '<em>$1</em>')
    .replace(/~~(.+?)~~/g, '<del>$1</del>')
    .replace(/`(.+?)`/g, '<code>$1</code>')
    .replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2">$1</a>')
    .replace(/\n/g, '<br>');
}

function buildTopicContent(topic: string): Record<string, string> {
  if (!MARKDOWN_PATTERN.test(topic)) return { topic };
  const formattedBody = topicMarkdownToHtml(topic);
  // Use HTML-stripped text as the plain topic so the header shows clean text, not raw markdown syntax
  const plainTopic = formattedBody.replace(/<br>/g, '\n').replace(/<[^>]+>/g, '');

  return { topic: plainTopic, format: 'org.matrix.custom.html', formatted_body: formattedBody };
}

type RoomProfileEditProps = {
  canEditAvatar: boolean;
  canEditName: boolean;
  canEditTopic: boolean;
  avatar?: string;
  name: string;
  topic: string;
  onClose: () => void;
};
export function RoomProfileEdit({
  canEditAvatar,
  canEditName,
  canEditTopic,
  avatar,
  name,
  topic,
  onClose,
}: RoomProfileEditProps) {
  const room = useRoom();
  const mx = useMatrixClient();
  const alive = useAlive();
  const useAuthentication = useMediaAuthentication();
  const joinRule = useRoomJoinRule(room);
  const [roomAvatar, setRoomAvatar] = useState(avatar);

  const avatarUrl = roomAvatar
    ? (mxcUrlToHttp(mx, roomAvatar, useAuthentication) ?? undefined)
    : undefined;

  const [nameValue, setNameValue] = useState(name);
  const [emojiAnchor, setEmojiAnchor] = useState<RectCords>();

  const handleEmojiSelect = useCallback((unicode: string) => {
    // Clamp to the same cap as the input's maxLength — programmatic setState isn't
    // constrained by the DOM maxLength, so the picker could otherwise exceed it.
    setNameValue((prev) => (unicode + prev).slice(0, 255));
    setEmojiAnchor(undefined);
  }, []);

  const topicRef = useRef<HTMLTextAreaElement>(null);
  const [imageFile, setImageFile] = useState<File>();
  const avatarFileUrl = useObjectURL(imageFile);
  const uploadingAvatar = avatarFileUrl ? roomAvatar === avatar : false;
  const uploadAtom = useMemo(() => {
    if (imageFile) return createUploadAtom(imageFile);
    return undefined;
  }, [imageFile]);

  const pickFile = useFilePicker(setImageFile, false);

  const handleRemoveUpload = useCallback(() => {
    setImageFile(undefined);
    setRoomAvatar(avatar);
  }, [avatar]);

  const handleUploaded = useCallback((upload: UploadSuccess) => {
    setRoomAvatar(upload.mxc);
  }, []);

  const [submitState, submit] = useAsyncCallback(
    useCallback(
      async (roomAvatarMxc?: string | null, roomName?: string, roomTopic?: string) => {
        if (roomAvatarMxc !== undefined) {
          await sendStateEvent(mx, room.roomId, StateEvent.RoomAvatar, {
            url: roomAvatarMxc,
          });
        }
        if (roomName !== undefined) {
          await sendStateEvent(mx, room.roomId, StateEvent.RoomName, { name: roomName });
        }
        if (roomTopic !== undefined) {
          const topicContent = buildTopicContent(roomTopic);
          await sendStateEvent(mx, room.roomId, StateEvent.RoomTopic, topicContent);
        }
      },
      [mx, room.roomId],
    ),
  );
  const submitting = submitState.status === AsyncStatus.Loading;

  const handleSubmit: FormEventHandler<HTMLFormElement> = (evt) => {
    evt.preventDefault();
    if (uploadingAvatar) return;

    const target = evt.target as HTMLFormElement | undefined;
    const topicTextArea = target?.topicTextArea as HTMLTextAreaElement | undefined;
    if (!topicTextArea) return;

    const roomName = nameValue.trim();
    const roomTopic = topicTextArea.value.trim();

    if (roomAvatar === avatar && roomName === name && roomTopic === topic) {
      return;
    }

    submit(
      roomAvatar === avatar ? undefined : roomAvatar || null,
      roomName === name ? undefined : roomName,
      roomTopic === topic ? undefined : roomTopic,
    ).then(() => {
      if (alive()) {
        onClose();
      }
    });
  };

  return (
    <Box as="form" onSubmit={handleSubmit} direction="Column" gap="400">
      <Box gap="400">
        <Box grow="Yes" direction="Column" gap="100">
          <Text size="L400">Avatar</Text>
          {uploadAtom ? (
            <Box gap="200" direction="Column">
              <CompactUploadCardRenderer
                uploadAtom={uploadAtom}
                onRemove={handleRemoveUpload}
                onComplete={handleUploaded}
              />
            </Box>
          ) : (
            <Box gap="200">
              <Button
                type="button"
                size="300"
                variant="Secondary"
                fill="Soft"
                radii="300"
                disabled={!canEditAvatar || submitting}
                onClick={() => pickFile('image/*')}
              >
                <Text size="B300">Upload</Text>
              </Button>
              {!roomAvatar && avatar && (
                <Button
                  type="button"
                  size="300"
                  variant="Success"
                  fill="None"
                  radii="300"
                  disabled={!canEditAvatar || submitting}
                  onClick={() => setRoomAvatar(avatar)}
                >
                  <Text size="B300">Reset</Text>
                </Button>
              )}
              {roomAvatar && (
                <Button
                  type="button"
                  size="300"
                  variant="Critical"
                  fill="None"
                  radii="300"
                  disabled={!canEditAvatar || submitting}
                  onClick={() => setRoomAvatar(undefined)}
                >
                  <Text size="B300">Remove</Text>
                </Button>
              )}
            </Box>
          )}
        </Box>
        <Box shrink="No">
          <Avatar size="500" radii="300">
            <RoomAvatar
              roomId={room.roomId}
              src={avatarUrl}
              alt={name}
              renderFallback={() => (
                <RoomIcon
                  roomType={room.getType()}
                  size="400"
                  joinRule={joinRule?.join_rule ?? JoinRule.Invite}
                  filled
                />
              )}
            />
          </Avatar>
        </Box>
      </Box>
      <Box direction="Inherit" gap="100">
        <Text size="L400">Name</Text>
        <Box direction="Row" gap="100" alignItems="Center">
          {canEditName && !submitting && (
            <PopOut
              anchor={emojiAnchor}
              position="Top"
              align="End"
              content={
                <EmojiBoard
                  imagePackRooms={[]}
                  returnFocusOnDeactivate={false}
                  onEmojiSelect={handleEmojiSelect}
                  requestClose={() => setEmojiAnchor(undefined)}
                />
              }
            >
              <IconButton
                type="button"
                size="400"
                radii="400"
                variant="Surface"
                fill="Soft"
                outlined
                aria-label="Insert emoji"
                aria-expanded={!!emojiAnchor}
                aria-haspopup="dialog"
                onClick={(evt: React.MouseEvent<HTMLButtonElement>) => {
                  const rect = evt.currentTarget.getBoundingClientRect();
                  setEmojiAnchor((prev) => (prev ? undefined : rect));
                }}
              >
                <Icon src={Icons.Smile} size="400" />
              </IconButton>
            </PopOut>
          )}
          <Box grow="Yes">
            <Input
              name="nameInput"
              value={nameValue}
              onChange={(e: React.ChangeEvent<HTMLInputElement>) => setNameValue(e.target.value)}
              variant="Secondary"
              radii="300"
              maxLength={255}
              readOnly={!canEditName || submitting}
              style={{ width: '100%' }}
            />
          </Box>
        </Box>
      </Box>
      <Box direction="Inherit" gap="100">
        <Box alignItems="Center" justifyContent="SpaceBetween">
          <Text size="L400">Topic</Text>
          {canEditTopic && !submitting && (
            <Box gap="100">
              {(
                [
                  { label: 'B', syntax: '**', placeholder: 'bold', title: 'Bold' },
                  { label: 'I', syntax: '*', placeholder: 'italic', title: 'Italic' },
                  {
                    label: 'S',
                    syntax: '~~',
                    placeholder: 'strikethrough',
                    title: 'Strikethrough',
                  },
                  { label: '`', syntax: '`', placeholder: 'code', title: 'Inline Code' },
                ] as const
              ).map(({ label, syntax, placeholder, title }) => (
                <Button
                  key={label}
                  type="button"
                  size="300"
                  radii="300"
                  variant="Secondary"
                  fill="Soft"
                  aria-label={title}
                  title={title}
                  onClick={() =>
                    topicRef.current && wrapSelection(topicRef.current, syntax, placeholder)
                  }
                >
                  <Text
                    size="B300"
                    style={{
                      fontWeight: label === 'B' ? 700 : undefined,
                      fontStyle: label === 'I' ? 'italic' : undefined,
                      fontFamily: label === '`' ? 'monospace' : undefined,
                    }}
                  >
                    {label}
                  </Text>
                </Button>
              ))}
            </Box>
          )}
        </Box>
        <TextArea
          ref={topicRef}
          name="topicTextArea"
          defaultValue={topic}
          placeholder="Describe this room… supports **bold**, *italic*, ~~strikethrough~~, `code`"
          variant="Secondary"
          radii="300"
          readOnly={!canEditTopic || submitting}
        />
      </Box>
      {submitState.status === AsyncStatus.Error && (
        <Text size="T200" style={{ color: color.Critical.Main }}>
          {(submitState.error as MatrixError).message}
        </Text>
      )}
      <Box gap="300">
        <Button
          type="submit"
          variant="Success"
          size="300"
          radii="300"
          disabled={uploadingAvatar || submitting}
          before={submitting && <Spinner size="100" variant="Success" fill="Solid" />}
        >
          <Text size="B300">Save</Text>
        </Button>
        <Button
          type="reset"
          onClick={onClose}
          variant="Secondary"
          fill="Soft"
          size="300"
          radii="300"
        >
          <Text size="B300">Cancel</Text>
        </Button>
      </Box>
    </Box>
  );
}

type RoomProfileProps = {
  permissions: RoomPermissionsAPI;
};
export function RoomProfile({ permissions }: RoomProfileProps) {
  const mx = useMatrixClient();
  const useAuthentication = useMediaAuthentication();
  const room = useRoom();
  const directs = useAtomValue(mDirectAtom);

  const avatar = useRoomAvatar(room, directs.has(room.roomId));
  const name = useRoomName(room);
  const topic = useRoomTopic(room);
  const joinRule = useRoomJoinRule(room);

  const canEditAvatar = permissions.stateEvent(StateEvent.RoomAvatar, mx.getSafeUserId());
  const canEditName = permissions.stateEvent(StateEvent.RoomName, mx.getSafeUserId());
  const canEditTopic = permissions.stateEvent(StateEvent.RoomTopic, mx.getSafeUserId());
  const canEdit = canEditAvatar || canEditName || canEditTopic;

  const avatarUrl = avatar
    ? (mxcUrlToHttp(mx, avatar, useAuthentication, 96, 96, 'crop') ?? undefined)
    : undefined;

  const [edit, setEdit] = useState(false);

  const handleCloseEdit = useCallback(() => setEdit(false), []);

  return (
    <Box direction="Column" gap="100">
      <Text size="L400">Profile</Text>
      <SequenceCard
        className={SequenceCardStyle}
        variant="SurfaceVariant"
        direction="Column"
        gap="400"
      >
        {edit ? (
          <RoomProfileEdit
            canEditAvatar={canEditAvatar}
            canEditName={canEditName}
            canEditTopic={canEditTopic}
            avatar={avatar}
            name={name ?? ''}
            topic={topic?.topic ?? ''}
            onClose={handleCloseEdit}
          />
        ) : (
          <Box gap="400">
            <Box grow="Yes" direction="Column" gap="300">
              <Box direction="Column" gap="100">
                <Text className={BreakWord} size="H5">
                  {name ?? 'Unknown'}
                </Text>
                {topic && (
                  <Text className={classNames(BreakWord, LineClamp3)} size="T200">
                    {topic.format === 'org.matrix.custom.html' &&
                    typeof topic.formatted_body === 'string' ? (
                      parse(sanitizeCustomHtml(topic.formatted_body))
                    ) : (
                      <Linkify options={LINKIFY_OPTS}>{topic.topic}</Linkify>
                    )}
                  </Text>
                )}
              </Box>
              {canEdit && (
                <Box gap="200">
                  <Chip
                    variant="Secondary"
                    fill="Soft"
                    radii="300"
                    before={<Icon size="50" src={Icons.Pencil} />}
                    onClick={() => setEdit(true)}
                    outlined
                  >
                    <Text size="B300">Edit</Text>
                  </Chip>
                </Box>
              )}
            </Box>
            <Box shrink="No">
              <Avatar size="500" radii="300">
                <RoomAvatar
                  roomId={room.roomId}
                  src={avatarUrl}
                  alt={name}
                  renderFallback={() => (
                    <RoomIcon
                      roomType={room.getType()}
                      size="400"
                      joinRule={joinRule?.join_rule ?? JoinRule.Invite}
                      filled
                    />
                  )}
                />
              </Avatar>
            </Box>
          </Box>
        )}
      </SequenceCard>
    </Box>
  );
}
