import React, {
  ChangeEventHandler,
  FormEventHandler,
  useCallback,
  useEffect,
  useMemo,
  useState,
} from 'react';
import { IPushRule, IPushRules, PushRuleKind } from 'matrix-js-sdk';
import { Box, Text, Button, Input, config, IconButton, Icons, Icon, Spinner, Switch } from 'folds';
import { SettingsSelect } from '../../../components/settings-select/SettingsSelect';
import { useAccountData } from '../../../hooks/useAccountData';
import { AccountDataEvent } from '../../../../types/matrix/accountData';
import { SequenceCard } from '../../../components/sequence-card';
import { SequenceCardStyle } from '../styles.css';
import { SettingTile } from '../../../components/setting-tile';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import {
  getNotificationModeActions,
  NotificationMode,
  useNotificationModeActions,
} from '../../../hooks/useNotificationMode';
import { NotificationModeSwitcher } from './NotificationModeSwitcher';

const RULE_LABELS: Record<string, string> = {
  '.m.rule.master': 'Disable all notifications',
  '.m.rule.suppress_notices': 'Suppress bot notices',
  '.m.rule.invite_for_me': 'Invited to a room',
  '.m.rule.member_event': 'Member events (joins/leaves)',
  '.m.rule.is_user_mention': '@mention',
  '.m.rule.contains_display_name': 'Message contains my name',
  '.m.rule.is_room_mention': 'Room @mention',
  '.m.rule.tombstone': 'Room upgrade',
  '.m.rule.reaction': 'Reactions',
  '.m.rule.room_one_to_one': 'DM messages',
  '.m.rule.message': 'All messages',
  '.m.rule.encrypted': 'Encrypted messages',
};

function getRuleLabel(ruleId: string): string {
  return RULE_LABELS[ruleId] ?? ruleId;
}

const MODE_LABELS: Record<NotificationMode, string> = {
  [NotificationMode.NotifyLoud]: 'Notify Loud',
  [NotificationMode.Notify]: 'Notify Silent',
  [NotificationMode.OFF]: 'Disable',
};

const ADD_MODES: NotificationMode[] = [
  NotificationMode.NotifyLoud,
  NotificationMode.Notify,
  NotificationMode.OFF,
];

type RuleEnableToggleProps = {
  kind: PushRuleKind;
  pushRule: IPushRule;
};

function RuleEnableToggle({ kind, pushRule }: RuleEnableToggleProps) {
  const mx = useMatrixClient();
  const [enabled, setEnabled] = useState(pushRule.enabled !== false);

  // Re-sync when the rule changes externally (e.g. toggled on another device →
  // account-data sync). The useState initializer only runs once, so without
  // this the Switch would show a stale value.
  useEffect(() => {
    setEnabled(pushRule.enabled !== false);
  }, [pushRule.enabled]);

  const [toggleState, toggle] = useAsyncCallback(
    useCallback(
      async (value: boolean) => {
        await mx.setPushRuleEnabled('global', kind, pushRule.rule_id, value);
        setEnabled(value);
      },
      [mx, kind, pushRule.rule_id],
    ),
  );

  const toggling = toggleState.status === AsyncStatus.Loading;

  return (
    <Switch
      variant="Primary"
      value={enabled}
      onChange={toggling ? undefined : toggle}
      aria-label={enabled ? 'Disable rule' : 'Enable rule'}
    />
  );
}

type RuleDeleteButtonProps = {
  kind: PushRuleKind;
  pushRule: IPushRule;
};

function RuleDeleteButton({ kind, pushRule }: RuleDeleteButtonProps) {
  const mx = useMatrixClient();

  const [deleteState, doDelete] = useAsyncCallback(
    useCallback(
      () => mx.deletePushRule('global', kind, pushRule.rule_id),
      [mx, kind, pushRule.rule_id],
    ),
  );

  const deleting = deleteState.status === AsyncStatus.Loading;

  return (
    <IconButton
      onClick={doDelete}
      size="300"
      radii="Pill"
      variant="Critical"
      fill="Soft"
      disabled={deleting}
      aria-label="Delete rule"
    >
      {deleting ? <Spinner size="100" /> : <Icon src={Icons.Delete} size="100" />}
    </IconButton>
  );
}

type RuleModeSwitcherProps = {
  kind: PushRuleKind;
  pushRule: IPushRule;
};

function RuleModeSwitcher({ kind, pushRule }: RuleModeSwitcherProps) {
  const mx = useMatrixClient();
  const getModeActions = useNotificationModeActions();

  const handleChange = useCallback(
    async (mode: NotificationMode) => {
      const actions = getModeActions(mode);
      await mx.setPushRuleActions('global', kind, pushRule.rule_id, actions);
    },
    [mx, getModeActions, kind, pushRule.rule_id],
  );

  return <NotificationModeSwitcher pushRule={pushRule} onChange={handleChange} />;
}

type RuleRowProps = {
  kind: PushRuleKind;
  pushRule: IPushRule;
  custom: boolean;
};

function RuleRow({ kind, pushRule, custom }: RuleRowProps) {
  return (
    <SequenceCard
      className={SequenceCardStyle}
      variant="SurfaceVariant"
      direction="Column"
      gap="400"
    >
      <SettingTile
        title={getRuleLabel(pushRule.rule_id)}
        before={<RuleEnableToggle kind={kind} pushRule={pushRule} />}
        after={
          <Box gap="200" alignItems="Center">
            <RuleModeSwitcher kind={kind} pushRule={pushRule} />
            {custom && <RuleDeleteButton kind={kind} pushRule={pushRule} />}
          </Box>
        }
      />
    </SequenceCard>
  );
}

type AddRuleFormProps = {
  kind: PushRuleKind.RoomSpecific | PushRuleKind.SenderSpecific;
  placeholder: string;
  label: string;
};

function AddRuleForm({ kind, placeholder, label }: AddRuleFormProps) {
  const mx = useMatrixClient();
  const [ruleId, setRuleId] = useState('');
  const [mode, setMode] = useState<NotificationMode>(NotificationMode.Notify);

  const [addState, doAdd] = useAsyncCallback(
    useCallback(
      async (id: string, notifyMode: NotificationMode) => {
        const actions = getNotificationModeActions(notifyMode);
        await mx.addPushRule('global', kind, id, { actions });
        setRuleId('');
      },
      [mx, kind],
    ),
  );

  const adding = addState.status === AsyncStatus.Loading;

  const handleSubmit: FormEventHandler<HTMLFormElement> = (evt) => {
    evt.preventDefault();
    if (adding) return;
    const trimmedId = ruleId.trim();
    if (!trimmedId) return;
    doAdd(trimmedId, mode);
  };

  const handleChange: ChangeEventHandler<HTMLInputElement> = (evt) => {
    setRuleId(evt.currentTarget.value);
  };

  return (
    <Box as="form" onSubmit={handleSubmit} direction="Column" gap="200">
      <Text size="T200" priority="300">
        {label}
      </Text>
      <Box gap="200" alignItems="Center">
        <Box grow="Yes">
          <Input
            required
            aria-label={placeholder}
            placeholder={placeholder}
            value={ruleId}
            onChange={handleChange}
            variant="Secondary"
            radii="300"
            readOnly={adding}
            style={{ paddingRight: config.space.S200 }}
          />
        </Box>
        <Box shrink="No">
          <SettingsSelect
            value={mode}
            options={ADD_MODES.map((m) => ({ value: m, label: MODE_LABELS[m] }))}
            onChange={setMode}
            aria-label="Notification mode"
          />
        </Box>
        <Button
          size="400"
          variant="Secondary"
          fill="Soft"
          outlined
          radii="300"
          type="submit"
          disabled={adding}
        >
          {adding && <Spinner variant="Secondary" size="300" />}
          <Text size="B400">Add</Text>
        </Button>
      </Box>
    </Box>
  );
}

type RuleSectionProps = {
  title: string;
  kind: PushRuleKind;
  rules: IPushRule[];
  addForm?: React.ReactNode;
};

function RuleSection({ title, kind, rules, addForm }: RuleSectionProps) {
  const [expanded, setExpanded] = useState(false);

  return (
    <Box direction="Column" gap="100">
      <SequenceCard
        className={SequenceCardStyle}
        variant="SurfaceVariant"
        direction="Column"
        gap="400"
      >
        <SettingTile
          title={title}
          description={`${rules.length} rule${rules.length !== 1 ? 's' : ''}`}
          after={
            <Button
              onClick={() => setExpanded((v) => !v)}
              variant="Secondary"
              fill="Soft"
              size="300"
              radii="300"
              outlined
              before={
                <Icon src={expanded ? Icons.ChevronTop : Icons.ChevronBottom} size="100" filled />
              }
            >
              <Text size="B300">{expanded ? 'Collapse' : 'Expand'}</Text>
            </Button>
          }
        />
        {expanded && addForm && <Box direction="Column">{addForm}</Box>}
        {expanded && rules.length === 0 && !addForm && (
          <Text size="T200" priority="300">
            No rules configured.
          </Text>
        )}
      </SequenceCard>
      {expanded &&
        rules.map((pushRule) => (
          <RuleRow
            key={pushRule.rule_id}
            kind={kind}
            pushRule={pushRule}
            custom={pushRule.default === false}
          />
        ))}
    </Box>
  );
}

export function PushRuleEditor() {
  const pushRulesEvt = useAccountData(AccountDataEvent.PushRules);
  const pushRules = useMemo(
    () => pushRulesEvt?.getContent<IPushRules>() ?? { global: {} },
    [pushRulesEvt],
  );

  const overrideRules = useMemo(() => pushRules.global[PushRuleKind.Override] ?? [], [pushRules]);
  const roomRules = useMemo(() => pushRules.global[PushRuleKind.RoomSpecific] ?? [], [pushRules]);
  const senderRules = useMemo(
    () => pushRules.global[PushRuleKind.SenderSpecific] ?? [],
    [pushRules],
  );
  const underrideRules = useMemo(() => pushRules.global[PushRuleKind.Underride] ?? [], [pushRules]);

  return (
    <Box direction="Column" gap="100">
      <Text size="L400">Advanced Push Rules</Text>
      <RuleSection title="Override Rules" kind={PushRuleKind.Override} rules={overrideRules} />
      <RuleSection
        title="Room Rules"
        kind={PushRuleKind.RoomSpecific}
        rules={roomRules}
        addForm={
          <AddRuleForm
            kind={PushRuleKind.RoomSpecific}
            placeholder="!roomid:server"
            label="Add a per-room notification rule by room ID"
          />
        }
      />
      <RuleSection
        title="Sender Rules"
        kind={PushRuleKind.SenderSpecific}
        rules={senderRules}
        addForm={
          <AddRuleForm
            kind={PushRuleKind.SenderSpecific}
            placeholder="@user:server"
            label="Add a per-user notification rule by user ID"
          />
        }
      />
      <RuleSection title="Underride Rules" kind={PushRuleKind.Underride} rules={underrideRules} />
    </Box>
  );
}
